[
  {
    "path": ".editorconfig",
    "content": "# top-most EditorConfig file\nroot = true\n\n# Unix-style newlines with a newline ending every file\n[*]\nend_of_line = lf\ninsert_final_newline = true\n\n# Matches multiple files with brace expansion notation\n# Set default charset\n[{*.js}]\ncharset = utf-8\nindent_style = space\nindent_size = 4\n\n# Tab indentation (no size specified)\n[Makefile]\nindent_style = tab\n\n# Matches the exact files either package.json or .travis.yml\n[{package.json,.travis.yml}]\nindent_style = space\nindent_size = 2"
  },
  {
    "path": ".github/ISSUE_TEMPLATE.md",
    "content": "## Type of issue\n- [ ] Bug report\n- [ ] Feature request\n\n## Uploader type\n- [ ] Traditional\n- [ ] S3\n- [ ] Azure\n\n### Note: Support requests cannot be accepted due to lack of time.\n\n\n<details>\n<summary>Bug Report</summary>\n\n#### Fine Uploader version\n{example: 5.5.1}\n\n#### Browsers where the bug is reproducible\n{example: \"Firefox\" and \"IE11\"}\n\n#### Operating systems where the bug is reproducible\n{example: \"iOS 9.1.0\" and \"Windows 8.1\"}\n\n#### Exact steps required to reproduce the issue\nFor example:\n1. Select 3 files\n2. Pause the 2nd file before it completes, but after it has started.\n3. Attempt to resume the paused file.\n\n#### All relevant Fine Uploader-related code that you have written\n{simply copy and paste the JS used to control Fine Uploader browsers-ide}\n{also include your template HTML if related to a UI issue}\n\n#### Your Fine Uploader template markup (if using Fine Uploader UI and the issue is UI-related)\n{simply copy and paste your template markup}\n\n#### Detailed explanation of the problem\n{describe the bug here}\n</details>\n\n\n\n\n<details>\n<summary>Feature Request</summary>\n\n#### Feature request details \n{why is this feature important, not just for you, but for many others?}\n</details>\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE",
    "content": "## Brief description of the changes\n{also describe what problem(s) these changes solve & reference any related issues/PRs}\n\n\n## What browsers and operating systems have you tested these changes on?\n{example: Safari on iOS 9.1.0 and IE11 on Windows 8.1}\n\n\n## Have you written unit tests? If not, explain why.\n{unit tests should accompany almost all PRs, unless the change is to documentation}\n"
  },
  {
    "path": ".gitignore",
    "content": ".*\n!.editorconfig\n*.ipr\n*~\n.*.sw[a-z]\n*.iml\n*.iws\n!.github\n!.gitignore\n!.jshintrc\n!.jshintignore\nThumbs.db\n\n_build/\n_dist/\n*.zip\nrelease/*\nmaster\n\n!.travis.yml\nhardcopy*\nselenium.log*\n\nroot-server.PID\ntest-resources-server.PID\n\nfine-uploader/\ntest/upload/*\ntest/uploadsTemp/\ntest/coverage/*\ntest/vendor/*\ntest/uploads/*\ntest/temp*\ntest/_temp*\ntest/_vendor*\nnode_modules/\n\nbin/\n\nsrc\n\nnpm-debug.log\nVagrantfile\n\ntest/dev/handlers/s3/composer.lock\ntest/dev/handlers/traditional/files\ntest/dev/handlers/traditional/chunks\ns3keys.php\ns3keys.sh\n\ntest/dev/handlers/vendor/*\ntest/dev/handlers/composer.lock\ntest/dev/handlers/composer.phar\n"
  },
  {
    "path": ".jshintignore",
    "content": "client/js/third-party/*\n"
  },
  {
    "path": ".jshintrc",
    "content": "{\n    // --------------------------------------------------------------------\n    // JSHint Configuration, Strict Edition\n    // --------------------------------------------------------------------\n    //\n    // This is a options template for [JSHint][1], using [JSHint example][2]\n    // and [Ory Band's example][3] as basis and setting config values to\n    // be most strict:\n    //\n    // * set all enforcing options to true\n    // * set all relaxing options to false\n    // * set all environment options to false, except the browser value\n    // * set all JSLint legacy options to false\n    //\n    // [1]: http://www.jshint.com/\n    // [2]: https://github.com/jshint/node-jshint/blob/master/example/config.json\n    // [3]: https://github.com/oryband/dotfiles/blob/master/jshintrc\n    //\n    // @author http://michael.haschke.biz/\n    // @license http://unlicense.org/\n\n    // == Enforcing Options ===============================================\n    //\n    // These options tell JSHint to be more strict towards your code. Use\n    // them if you want to allow only a safe subset of JavaScript, very\n    // useful when your codebase is shared with a big number of developers\n    // with different skill levels.\n\n    \"bitwise\"       : true,     // Prohibit bitwise operators (&, |, ^, etc.).\n    \"curly\"         : true,     // Require {} for every new block or scope.\n    \"eqeqeq\"        : true,     // Require triple equals i.e. `===`.\n    \"forin\"         : true,     // Tolerate `for in` loops without `hasOwnPrototype`.\n    \"immed\"         : true,     // Require immediate invocations to be wrapped in parens e.g. `( function(){}() );`\n    \"latedef\"       : false,     // Prohibit variable use before definition.\n    \"newcap\"        : true,     // Require capitalization of all constructor functions e.g. `new F()`.\n    \"noarg\"         : true,     // Prohibit use of `arguments.caller` and `arguments.callee`.\n    \"noempty\"       : true,     // Prohibit use of empty blocks.\n    \"nonew\"         : true,     // Prohibit use of constructors for side-effects.\n    \"plusplus\"      : false,    // Prohibit use of `++` & `--`.\n    \"regexp\"        : true,     // Prohibit `.` and `[^...]` in regular expressions.\n    \"undef\"         : true,     // Require all non-global variables be declared before they are used.\n    \"unused\"        : false,    // Prohibit the use of defined, yet unused variables.\n    \"strict\"        : true,     // Require `use strict` pragma in every file.\n    \"trailing\"      : true,     // Prohibit trailing whitespaces.\n\n    // == Relaxing Options ================================================\n    //\n    // These options allow you to suppress certain types of warnings. Use\n    // them only if you are absolutely positive that you know what you are\n    // doing.\n\n    \"asi\"           : false,    // Tolerate Automatic Semicolon Insertion (no semicolons).\n    \"boss\"          : false,    // Tolerate assignments inside if, for & while. Usually conditions & loops are for comparison, not assignments.\n    \"debug\"         : false,    // Allow debugger statements e.g. browser breakpoints.\n    \"eqnull\"        : true,    // Tolerate use of `== null`.\n    \"esnext\"        : false,    // Allow ES.next specific features such as `const` and `let`.\n    \"evil\"          : false,    // Tolerate use of `eval`.\n    \"expr\"          : true,     // Tolerate `ExpressionStatement` as Programs.\n    \"funcscope\"     : false,    // Tolerate declarations of variables inside of control structures while accessing them later from the outside.\n    \"globalstrict\"  : false,    // Allow global \"use strict\" (also enables 'strict').\n    \"iterator\"      : false,    // Allow usage of __iterator__ property.\n    \"lastsemic\"     : false,    // Tolerat missing semicolons when the it is omitted for the last statement in a one-line block.\n    \"laxbreak\"      : false,    // Tolerate unsafe line breaks e.g. `return [\\n] x` without semicolons.\n    \"laxcomma\"      : false,    // Suppress warnings about comma-first coding style.\n    \"loopfunc\"      : false,    // Allow functions to be defined within loops.\n    \"multistr\"      : false,    // Tolerate multi-line strings.\n    \"onecase\"       : false,    // Tolerate switches with just one case.\n    \"proto\"         : false,    // Tolerate __proto__ property. This property is deprecated.\n    \"regexdash\"     : false,    // Tolerate unescaped last dash i.e. `[-...]`.\n    \"scripturl\"     : false,    // Tolerate script-targeted URLs.\n    \"smarttabs\"     : false,    // Tolerate mixed tabs and spaces when the latter are used for alignmnent only.\n    \"shadow\"        : false,    // Allows re-define variables later in code e.g. `var x=1; x=2;`.\n    \"sub\"           : false,    // Tolerate all forms of subscript notation besides dot notation e.g. `dict['key']` instead of `dict.key`.\n    \"supernew\"      : false,    // Tolerate `new function () { ... };` and `new Object;`.\n    \"validthis\"     : false,    // Tolerate strict violations when the code is running in strict mode and you use this in a non-constructor function.\n\n    // == Environments ====================================================\n    //\n    // These options pre-define global variables that are exposed by\n    // popular JavaScript libraries and runtime environments—such as\n    // browser or node.js.\n\n    \"browser\"       : true,     // Standard browser globals e.g. `window`, `document`.\n    \"couch\"         : false,    // Enable globals exposed by CouchDB.\n    \"devel\"         : false,    // Allow development statements e.g. `console.log();`.\n    \"dojo\"          : false,    // Enable globals exposed by Dojo Toolkit.\n    \"jquery\"        : true,     // Enable globals exposed by jQuery JavaScript library.\n    \"mootools\"      : false,    // Enable globals exposed by MooTools JavaScript framework.\n    \"node\"          : false,    // Enable globals available when code is running inside of the NodeJS runtime environment.\n    \"nonstandard\"   : true,     // Define non-standard but widely adopted globals such as escape and unescape.\n    \"prototypejs\"   : false,    // Enable globals exposed by Prototype JavaScript framework.\n    \"rhino\"         : false,    // Enable globals available when your code is running inside of the Rhino runtime environment.\n    \"wsh\"           : false,    // Enable globals available when your code is running as a script for the Windows Script Host.\n\n    // == JSLint Legacy ===================================================\n    //\n    // These options are legacy from JSLint. Aside from bug fixes they will\n    // not be improved in any way and might be removed at any point.\n\n    \"nomen\"         : false,    // Prohibit use of initial or trailing underbars in names.\n    \"onevar\"        : false,    // Allow only one `var` statement per function.\n    \"passfail\"      : false,    // Stop on first error.\n    \"white\"         : false,    // Check against strict whitespace and indentation rules.\n\n    // == Undocumented Options ============================================\n    //\n    // While I've found these options in [example1][2] and [example2][3]\n    // they are not described in the [JSHint Options documentation][4].\n    //\n    // [4]: http://www.jshint.com/options/\n\n    \"maxerr\"        : 100,      // Maximum errors before stopping.\n    \"predef\"        : [\"qq\"],\n    \"quotmark\"      : \"double\", // Enforces consistencey of quotation marks.\n    //\"maxlen\"        : \"80\",     // Enfore a maximum line length\n    \"indent\"        : 4         // Specify indentation spacing\n}\n"
  },
  {
    "path": ".travis.yml",
    "content": "addons:\n  firefox: latest\nsudo: false\nlanguage: python\npython:\n- 2.7\nenv:\n  global:\n  - DISPLAY=:99.0\n  - DOCS_GH_REF: github.com/FineUploader/docs.fineuploader.com\n  # fineuploader-docs-bot access token has been moved to Travis-CI settings in the UI due to https://github.com/travis-ci/travis-ci/issues/7806\ninstall:\n  - . $HOME/.nvm/nvm.sh\n  - nvm install 5.0.0\n  - nvm use 5.0.0\n  - npm install\nbefore_script:\n- sh -e /etc/init.d/xvfb start\nscript:\n- npm test\n- if [ $TRAVIS_TEST_RESULT -eq 0 ]; then make docs-travis; fi\n"
  },
  {
    "path": "ATTRIBUTION.txt",
    "content": "Third-party credits (client/js/third-party/)\n\nMegaPixImage module\n    Licensed under MIT (https://github.com/stomita/ios-imagefile-megapixel/blob/master/LICENSE)\n    https://github.com/stomita/ios-imagefile-megapixel\n    Copyright (c) 2012 Shinichi Tomita <shinichi.tomita@gmail.com>\n\nCryptoJS\n    Licensed under MIT (https://code.google.com/p/crypto-js/wiki/License)\n    https://code.google.com/p/crypto-js/\n    Copyright (c) 2009-2013 Jeff Mott <Jeff.Mott.OR@gmail.com>\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2010-2012, Andrew Valums\nCopyright (c) 2012-2013, Andrew Valums and Raymond S. Nicholus, III\nCopyright (c) 2013-present, Widen Enterprises, Inc.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "Makefile",
    "content": ".PHONY: clean _build publish start-test-resources-server test-resources-server.PID start-root-server root-server.PID\n\nversion=$(shell node -pe \"require('./package.json').version\")\ndist-out-dir = _dist\npub-dir = $(dist-out-dir)/$(version)\n\n\n# properly get npm-bin in cygwin (Eg. CYGWIN_NT-10.0)\nplatform = $(shell uname -s)\nifeq ($(findstring _NT,$(platform)),_NT)\n\tnpm-bin = $(shell cygpath -u $(shell npm bin))\nelse\n\tnpm-bin = $(shell npm bin)\nendif\n\nbuild-out-dir = _build\nsrc-dir = client\njs-src-dir = $(src-dir)/js\njs-3rdparty-src-dir = $(js-src-dir)/third-party\ntest-dir = test\nunit-test-dir = $(test-dir)/unit\n\nexport-file = $(js-src-dir)/export.js\n\npreamble = \"// Fine Uploader $(version) - MIT licensed. http://fineuploader.com\"\n\ncryptojs-files = \\\n\t$(js-3rdparty-src-dir)/crypto-js/core.js \\\n\t$(js-3rdparty-src-dir)/crypto-js/enc-base64.js \\\n\t$(js-3rdparty-src-dir)/crypto-js/hmac.js \\\n\t$(js-3rdparty-src-dir)/crypto-js/sha1.js \\\n\t$(js-3rdparty-src-dir)/crypto-js/sha256.js \\\n\t$(js-3rdparty-src-dir)/crypto-js/lib-typedarrays.js\n\njquery-files = \\\n\t$(js-src-dir)/jquery-plugin.js \\\n\t$(js-src-dir)/jquery-dnd.js\n\ndnd-files-only = \\\n\t$(js-src-dir)/dnd.js\n\ndnd-files = \\\n\t$(js-src-dir)/util.js \\\n\t$(export-file) \\\n\t$(js-src-dir)/version.js \\\n\t$(js-src-dir)/features.js \\\n\t$(js-src-dir)/promise.js \\\n\t$(js-src-dir)/dnd.js\n\ncore-files = \\\n\t$(js-src-dir)/util.js \\\n\t$(export-file) \\\n\t$(js-src-dir)/error/error.js \\\n\t$(js-src-dir)/version.js \\\n\t$(js-src-dir)/features.js \\\n\t$(js-src-dir)/promise.js \\\n\t$(js-src-dir)/blob-proxy.js \\\n\t$(js-src-dir)/button.js \\\n\t$(js-src-dir)/upload-data.js \\\n\t$(js-src-dir)/uploader.basic.api.js \\\n\t$(js-src-dir)/uploader.basic.js \\\n\t$(js-src-dir)/ajax.requester.js \\\n\t$(js-src-dir)/upload-handler/upload.handler.js \\\n\t$(js-src-dir)/upload-handler/upload.handler.controller.js \\\n\t$(js-src-dir)/window.receive.message.js \\\n\t$(js-src-dir)/upload-handler/form.upload.handler.js \\\n\t$(js-src-dir)/upload-handler/xhr.upload.handler.js \\\n\t$(js-src-dir)/deletefile.ajax.requester.js \\\n\t$(js-src-dir)/image-support/megapix-image.js \\\n\t$(js-src-dir)/image-support/image.js \\\n\t$(js-src-dir)/image-support/exif.js \\\n\t$(js-src-dir)/identify.js \\\n\t$(js-src-dir)/image-support/validation.image.js \\\n\t$(js-src-dir)/session.js \\\n\t$(js-src-dir)/session.ajax.requester.js \\\n\t$(js-src-dir)/image-support/scaler.js \\\n\t$(js-src-dir)/third-party/ExifRestorer.js \\\n\t$(js-src-dir)/total-progress.js \\\n\t$(js-src-dir)/paste.js \\\n\t$(js-src-dir)/form-support.js \\\n\nui-files = \\\n\t$(dnd-files-only) \\\n\t$(js-src-dir)/uploader.api.js \\\n\t$(js-src-dir)/uploader.js \\\n\t$(js-src-dir)/templating.js \\\n\t$(js-src-dir)/ui.handler.events.js \\\n    $(js-src-dir)/ui.handler.click.filebuttons.js \\\n\t$(js-src-dir)/ui.handler.click.filename.js \\\n\t$(js-src-dir)/ui.handler.focusin.filenameinput.js \\\n\t$(js-src-dir)/ui.handler.focus.filenameinput.js \\\n\t$(js-src-dir)/ui.handler.edit.filename.js\n\ntraditional-files-only = \\\n\t$(js-src-dir)/traditional/traditional.form.upload.handler.js \\\n\t$(js-src-dir)/traditional/traditional.xhr.upload.handler.js \\\n\t$(js-src-dir)/traditional/all-chunks-done.ajax.requester.js \\\n\ntraditional-files = \\\n\t$(core-files) \\\n\t$(traditional-files-only)\n\ntraditional-jquery-files = \\\n\t$(jquery-files) \\\n\t$(traditional-files)\n\ntraditional-ui-files = \\\n\t$(core-files) \\\n\t$(traditional-files-only) \\\n\t$(ui-files)\n\ntraditional-ui-jquery-files = \\\n\t$(jquery-files) \\\n\t$(traditional-ui-files)\n\ns3-files-only = \\\n\t$(cryptojs-files) \\\n\t$(js-src-dir)/s3/util.js \\\n\t$(js-src-dir)/non-traditional-common/uploader.basic.api.js \\\n\t$(js-src-dir)/s3/uploader.basic.js \\\n\t$(js-src-dir)/s3/request-signer.js \\\n\t$(js-src-dir)/uploadsuccess.ajax.requester.js \\\n\t$(js-src-dir)/s3/multipart.initiate.ajax.requester.js \\\n\t$(js-src-dir)/s3/multipart.complete.ajax.requester.js \\\n\t$(js-src-dir)/s3/multipart.abort.ajax.requester.js \\\n\t$(js-src-dir)/s3/s3.xhr.upload.handler.js \\\n\t$(js-src-dir)/s3/s3.form.upload.handler.js\n\ns3-files = \\\n\t$(core-files) \\\n\t$(s3-files-only)\n\ns3-ui-files-only = \\\n\t$(js-src-dir)/s3/uploader.js\n\ns3-ui-files = \\\n\t$(core-files) \\\n\t$(s3-files-only) \\\n\t$(ui-files) \\\n\t$(s3-ui-files-only) \\\n\ns3-ui-jquery-files = \\\n\t$(jquery-files) \\\n    $(js-src-dir)/s3/jquery-plugin.js \\\n\t$(s3-ui-files)\n\nazure-files-only = \\\n\t$(js-src-dir)/azure/util.js \\\n\t$(js-src-dir)/non-traditional-common/uploader.basic.api.js \\\n\t$(js-src-dir)/azure/uploader.basic.js \\\n\t$(js-src-dir)/azure/azure.xhr.upload.handler.js \\\n\t$(js-src-dir)/azure/get-sas.js \\\n\t$(js-src-dir)/uploadsuccess.ajax.requester.js \\\n\t$(js-src-dir)/azure/rest/delete-blob.js \\\n\t$(js-src-dir)/azure/rest/put-blob.js \\\n\t$(js-src-dir)/azure/rest/put-block.js \\\n\t$(js-src-dir)/azure/rest/put-block-list.js\n\nazure-files = \\\n\t$(core-files) \\\n\t$(azure-files-only)\n\nazure-ui-files-only = \\\n\t$(js-src-dir)/azure/uploader.js\n\nazure-ui-files = \\\n\t$(core-files) \\\n\t$(azure-files-only) \\\n\t$(ui-files) \\\n\t$(azure-ui-files-only)\n\nazure-ui-jquery-files = \\\n\t$(jquery-files) \\\n    $(js-src-dir)/azure/jquery-plugin.js \\\n\t$(azure-ui-files)\n\nall-core-files = \\\n\t$(core-files) \\\n\t$(traditional-files-only) \\\n\t$(s3-files-only) \\\n\t$(azure-files-only)\n\nall-core-jquery-files = \\\n\t$(jquery-files) \\\n\t$(all-core-files)\n\nall-files = \\\n\t$(core-files) \\\n\t$(traditional-files-only) \\\n\t$(ui-files) \\\n\t$(s3-files-only) \\\n\t$(s3-ui-files-only) \\\n\t$(azure-files-only) \\\n\t$(azure-ui-files-only)\n\nall-jquery-files = \\\n\t$(jquery-files) \\\n\t$(all-files)\n\nclean:\n\trm -rf $(build-out-dir)\n\trm -rf $(dist-out-dir)\n\nlint:\n\t$(npm-bin)/jscs $(js-src-dir)/*\n\t$(npm-bin)/jshint $(js-src-dir)/* $(unit-test-dir)/* $(test-dir)/static/local/*\n\n_build:\n\tmkdir -p $@\n\tcp -pR $(src-dir)/placeholders $@\n\tcp -pR $(src-dir)/html/templates $@\n\tcp LICENSE $@\n\tcp $(src-dir)/*.css $@\n\tcp $(src-dir)/*.gif $@\n\t$(npm-bin)/cleancss --source-map $@/fine-uploader.css -o $@/fine-uploader.min.css\n\t$(npm-bin)/cleancss --source-map $@/fine-uploader-gallery.css -o $@/fine-uploader-gallery.min.css\n\t$(npm-bin)/cleancss --source-map $@/fine-uploader-new.css -o $@/fine-uploader-new.min.css\n\nuglify = $(npm-bin)/uglifyjs -b --preamble $(preamble) -e window:global -p relative --source-map-include-sources\nuglify-min = $(npm-bin)/uglifyjs -c -m --preamble $(preamble) -e window:global -p relative --source-map-include-sources\n\nbuild-dnd-standalone: _build\n\t$(uglify) $(dnd-files) -o $(build-out-dir)/dnd.js --source-map $(build-out-dir)/dnd.js.map\n\nbuild-dnd-standalone-min: _build\n\t$(uglify-min) $(dnd-files) -o $(build-out-dir)/dnd.min.js --source-map $(build-out-dir)/dnd.min.js.map\n\nbuild-core-traditional: _build\n\t$(uglify) $(traditional-files) -o $(build-out-dir)/fine-uploader.core.js --source-map $(build-out-dir)/fine-uploader.core.js.map\n\nbuild-core-traditional-min: _build\n\t$(uglify-min) $(traditional-files) -o $(build-out-dir)/fine-uploader.core.min.js --source-map $(build-out-dir)/fine-uploader.core.min.js.map\n\nbuild-ui-traditional: _build\n\t$(uglify) $(traditional-ui-files) -o $(build-out-dir)/fine-uploader.js --source-map $(build-out-dir)/fine-uploader.js.map\n\nbuild-ui-traditional-min: _build\n\t$(uglify-min) $(traditional-ui-files) -o $(build-out-dir)/fine-uploader.min.js --source-map $(build-out-dir)/fine-uploader.min.js.map\n\nbuild-ui-traditional-jquery: _build\n\t$(uglify) $(traditional-ui-jquery-files) -o $(build-out-dir)/jquery.fine-uploader.js --source-map $(build-out-dir)/jquery.fine-uploader.js.map\n\nbuild-ui-traditional-jquery-min: _build\n\t$(uglify-min) $(traditional-ui-jquery-files) -o $(build-out-dir)/jquery.fine-uploader.min.js --source-map $(build-out-dir)/jquery.fine-uploader.min.js.map\n\nbuild-core-s3: _build\n\t$(uglify) $(s3-files) -o $(build-out-dir)/s3.fine-uploader.core.js --source-map $(build-out-dir)/s3.fine-uploader.core.js.map\n\nbuild-core-s3-min: _build\n\t$(uglify-min) $(s3-files) -o $(build-out-dir)/s3.fine-uploader.core.min.js --source-map $(build-out-dir)/s3.fine-uploader.core.min.js.map\n\nbuild-ui-s3: _build\n\t$(uglify) $(s3-ui-files) -o $(build-out-dir)/s3.fine-uploader.js --source-map $(build-out-dir)/s3.fine-uploader.js.map\n\nbuild-ui-s3-min: _build\n\t$(uglify-min) $(s3-ui-jquery-files) -o $(build-out-dir)/s3.jquery.fine-uploader.min.js --source-map $(build-out-dir)/s3.jquery.fine-uploader.min.js.map\n\nbuild-ui-s3-jquery: _build\n\t$(uglify) $(s3-ui-jquery-files) -o $(build-out-dir)/s3.jquery.fine-uploader.js --source-map $(build-out-dir)/s3.jquery.fine-uploader.js.map\n\nbuild-ui-s3-jquery-min: _build\n\t$(uglify-min) $(s3-ui-files) -o $(build-out-dir)/s3.fine-uploader.min.js -e window:global --source-map $(build-out-dir)/s3.fine-uploader.min.js.map\n\nbuild-core-azure: _build\n\t$(uglify) $(azure-files) -o $(build-out-dir)/azure.fine-uploader.core.js --source-map $(build-out-dir)/azure.fine-uploader.core.js.map \n\nbuild-core-azure-min: _build\n\t$(uglify-min) $(azure-files) -o $(build-out-dir)/azure.fine-uploader.core.min.js -e window:global --source-map $(build-out-dir)/azure.fine-uploader.core.min.js.map\n\nbuild-ui-azure: _build\n\t$(uglify) $(azure-ui-files) -o $(build-out-dir)/azure.fine-uploader.js --source-map $(build-out-dir)/azure.fine-uploader.js.map \n\nbuild-ui-azure-min: _build\n\t$(uglify-min) $(azure-ui-files) -o $(build-out-dir)/azure.fine-uploader.min.js -e window:global --source-map $(build-out-dir)/azure.fine-uploader.min.js.map\n\nbuild-ui-azure-jquery: _build\n\t$(uglify) $(azure-ui-jquery-files) -o $(build-out-dir)/azure.jquery.fine-uploader.js --source-map $(build-out-dir)/azure.jquery.fine-uploader.js.map \n\nbuild-ui-azure-jquery-min: _build\n\t$(uglify-min) $(azure-ui-jquery-files) -o $(build-out-dir)/azure.jquery.fine-uploader.min.js -e window:global --source-map $(build-out-dir)/azure.jquery.fine-uploader.min.js.map\n\nbuild-all-core: _build\n\t$(uglify) $(all-core-files) -o $(build-out-dir)/all.fine-uploader.core.js --source-map $(build-out-dir)/all.fine-uploader.core.js.map \n\nbuild-all-core-min: _build\n\t$(uglify-min) $(all-core-files) -o $(build-out-dir)/all.fine-uploader.core.min.js -e window:global --source-map $(build-out-dir)/all.fine-uploader.core.min.js.map\n\nbuild-all-ui: _build\n\t$(uglify) $(all-files) -o $(build-out-dir)/all.fine-uploader.js --source-map $(build-out-dir)/all.fine-uploader.js.map \n\nbuild-all-ui-min: _build\n\t$(uglify-min) $(all-files) -o $(build-out-dir)/all.fine-uploader.min.js --source-map $(build-out-dir)/all.fine-uploader.min.js.map\n\nbuild: \\\n\tbuild-dnd-standalone \\\n\tbuild-dnd-standalone-min \\\n\tbuild-core-traditional \\\n\tbuild-core-traditional-min \\\n\tbuild-ui-traditional \\\n\tbuild-ui-traditional-min \\\n\tbuild-ui-traditional-jquery \\\n\tbuild-ui-traditional-jquery-min \\\n\tbuild-core-s3 \\\n\tbuild-core-s3-min \\\n\tbuild-ui-s3 \\\n\tbuild-ui-s3-min \\\n\tbuild-ui-s3-jquery \\\n\tbuild-ui-s3-jquery-min \\\n\tbuild-core-azure \\\n\tbuild-core-azure-min \\\n\tbuild-ui-azure \\\n\tbuild-ui-azure-min \\\n\tbuild-ui-azure-jquery \\\n\tbuild-ui-azure-jquery-min \\\n\tbuild-all-core \\\n\tbuild-all-core-min \\\n\tbuild-all-ui \\\n\tbuild-all-ui-min\n\nstart-test-resources-server: test-resources-server.PID\n\nstart-root-server: root-server.PID\n\ntest-resources-server.PID:\n\t$(npm-bin)/static test/unit/resources -H '{\"Access-Control-Allow-Origin\": \"*\"}' -p 4000 & echo $$! > $@\n\nroot-server.PID:\n\t$(npm-bin)/static . -p 4001 & echo $$! > $@\n\nstop-test-resources-server: test-resources-server.PID\n\tkill `cat $<` && rm $<\n\nstop-root-server: root-server.PID\n\tkill `cat $<` && rm $<\n\ntest:\n\t$(MAKE) stop-test-resources-server\n\t$(MAKE) stop-root-server\n\t$(MAKE) start-test-resources-server\n\t$(MAKE) start-root-server\n\t$(MAKE) build-all-ui\n\t$(npm-bin)/karma start config/karma.conf.js\n\t$(MAKE) stop-test-resources-server\n\t$(MAKE) stop-root-server\n.PHONY: test\n\nzip: zip-traditional zip-s3 zip-azure zip-all\n\ncommon-zip-files = \\\n\tdnd*.* \\\n\tLICENSE \\\n\tplaceholders/* \\\n\ttemplates/* \\\n\t*.gif \\\n\tfine-uploader*.css*\n\nzip-traditional:\n\t(cd $(build-out-dir) ; zip fine-uploader.zip $(common-zip-files) fine-uploader*.* jquery.fine-uploader*.*)\n\nzip-s3:\n\t(cd $(build-out-dir) ; zip s3.fine-uploader.zip $(common-zip-files) s3*.*)\n\nzip-azure:\n\t(cd $(build-out-dir) ; zip azure.fine-uploader.zip $(common-zip-files) azure*.*)\n\nzip-all:\n\t(cd $(build-out-dir) ; zip all.fine-uploader.zip $(common-zip-files) all*.*)\n\nsetup-dist:\n\tmkdir -p $(pub-dir)\n\tcp LICENSE README.md package.json $(pub-dir)\n\tcp -pR $(src-dir)/commonJs/ $(pub-dir)/lib/\n\tcp -pR $(src-dir)/typescript $(pub-dir)/\n\ncopy-build-to-dist:\n\tmkdir -p $(pub-dir)/$(PUB-SUBDIR)\n\tcp -pR $(build-out-dir)/placeholders $(build-out-dir)/templates $(pub-dir)/$(PUB-SUBDIR)\n\tcp $(build-out-dir)/*.gif $(pub-dir)/$(PUB-SUBDIR)\nifneq (,$(findstring jquery,$(PUB-SUBDIR)))\nelse\n\tcp $(build-out-dir)/$(PUB-SUBDIR).core.min* $(build-out-dir)/$(PUB-SUBDIR).core.js* $(pub-dir)/$(PUB-SUBDIR)/\nendif\n\tcp $(build-out-dir)/$(PUB-SUBDIR).min* $(build-out-dir)/$(PUB-SUBDIR).js* $(pub-dir)/$(PUB-SUBDIR)\n\tcp $(build-out-dir)/fine-uploader*.css* $(pub-dir)/$(PUB-SUBDIR)\n\ncopy-dnd:\n\tmkdir -p $(pub-dir)/dnd\n\tcp $(build-out-dir)/dnd*.* $(pub-dir)/dnd\n\ncopy-traditional-dist:\n\tmake copy-build-to-dist PUB-SUBDIR=fine-uploader\n\tcp $(js-src-dir)/iframe.xss.response.js $(pub-dir)/fine-uploader\n\ncopy-traditional-jquery-dist:\n\tmake copy-build-to-dist PUB-SUBDIR=jquery.fine-uploader\n\tcp $(js-src-dir)/iframe.xss.response.js $(pub-dir)/jquery.fine-uploader\n\ncopy-s3-dist:\n\tmake copy-build-to-dist PUB-SUBDIR=s3.fine-uploader\n\ncopy-s3-jquery-dist:\n\tmake copy-build-to-dist PUB-SUBDIR=s3.jquery.fine-uploader\n\ncopy-azure-dist:\n\tmake copy-build-to-dist PUB-SUBDIR=azure.fine-uploader\n\ncopy-azure-jquery-dist:\n\tmake copy-build-to-dist PUB-SUBDIR=azure.jquery.fine-uploader\n\ncopy-all-dist:\n\tmake copy-build-to-dist PUB-SUBDIR=all.fine-uploader\n\ndocs: install-docfu\n\tgit config --global user.email \"fineuploader-docs-bot@raynicholus.com\"\n\tgit config --global user.name \"fineuploader-docs-bot\"\n\tdocfu --$(type) \"$(type-value)\" \"FineUploader/fine-uploader\" \"docfu-temp\"\n\tgit clone --depth 1 https://github.com/FineUploader/docs.fineuploader.com.git\n\tcp -pR docfu-temp/$(type) docs.fineuploader.com/\n\tmake maybe-update-root-docs\n\t(cd docs.fineuploader.com ; git add .)\n\t(cd docs.fineuploader.com ; git diff --cached --quiet || git commit -a -m \"update docs for $(type) $(type-value)\")\n\t@(cd docs.fineuploader.com ; git push https://$(DOCS_PUSH_ACCESS_TOKEN)@$(DOCS_GH_REF))\n.PHONY: docs\n\nmaybe-update-root-docs:\nifndef TRAVIS_TAG\nifeq ($(TRAVIS_BRANCH), master)\n\tcp -pR docs.fineuploader.com/branch/master/. docs.fineuploader.com/\nendif\nendif\n.PHONY: maybe-update-root-docs\n\ndocs-travis:\nifneq ($(TRAVIS_PULL_REQUEST), false)\n\t@echo skipping docs build - not a non-PR or tag push\nelse ifdef TRAVIS_TAG\n\tmake docs type=tag type-value=$(TRAVIS_TAG)\nelse\n\tmake docs type=branch type-value=$(TRAVIS_BRANCH)\nendif\n.PHONY: docs-travis\n\ninstall-docfu:\n\tgit clone --depth 1 -b 1.0.4 https://github.com/FineUploader/docfu\n\t(cd docfu ; python setup.py install)\n\trm -rf docfu\n.PHONY: install-docfu\n\ntag-release:\nifeq ($(simulate), true)\n\t@echo version is $(version)\nelse\n\tgit tag $(version)\n\tgit push origin $(version)\nendif\n\npush-to-npm:\nifeq ($(simulate), true)\n\t@echo not publishing - simulation mode\nelse\n\t(cd $(pub-dir) ; npm publish)\nendif\n\npublish: \\\n\tclean \\\n\tbuild \\\n\tzip \\\n\tsetup-dist \\\n\tcopy-dnd \\\n\tcopy-traditional-dist \\\n\tcopy-traditional-jquery-dist \\\n\tcopy-s3-dist \\\n\tcopy-s3-jquery-dist \\\n\tcopy-azure-dist \\\n\tcopy-azure-jquery-dist \\\n\tcopy-all-dist \\\n\ttag-release \\\n\tpush-to-npm\n\nsetup-dev:\n\t(cd test/dev/handlers; curl -sS https://getcomposer.org/installer | php; php composer.phar install)\n\nstart-local-dev:\n\t(. test/dev/handlers/s3keys.sh; php -S 0.0.0.0:9090 -t . -c test/dev/handlers/php.ini)\n\nupdate-dev:\n\t(cd test/dev/handlers; php composer.phar update)\n\nrev-version:\n\tsed -i \"\" -e 's/$(version)/$(target)/g' client/js/version.js\n\tsed -i \"\" -e 's/$(version)/$(target)/g' package.json\n"
  },
  {
    "path": "README.md",
    "content": "**Fine Uploader is no longer maintained and the project has been effectively shut down. For more info, see https://github.com/FineUploader/fine-uploader/issues/2073.**\n\n<a href=\"http://fineuploader.com\">\n   <img src=\"https://fineuploader.com/img/fineuploader-header-logo.png\" width=\"300\">\n</a>\n\n[![Build Status](https://travis-ci.org/FineUploader/fine-uploader.svg?branch=master)](https://travis-ci.org/FineUploader/fine-uploader)\n[![npm](https://img.shields.io/npm/v/fine-uploader.svg)](https://www.npmjs.com/package/fine-uploader)\n[![CDNJS](https://img.shields.io/cdnjs/v/file-uploader.svg)](https://cdnjs.com/libraries/file-uploader)\n[![license](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE)\n[![Twitter URL](https://img.shields.io/twitter/url/https/twitter.com/fineuploader.svg?style=social&label=Follow%20%40FineUploader)](https://twitter.com/fineuploader)\n\n[**Documentation**](http://docs.fineuploader.com) |\n[**Examples**](http://fineuploader.com/demos) |\n[**Support**](../../issues) |\n[**Blog**](http://blog.fineuploader.com/) |\n[**Changelog**](../../releases)\n\n---\n\nFine Uploader is:\n\n- Cross-browser\n- Dependency-free\n- 100% JavaScript\n- 100% Free Open Source Software\n\nFineUploader is also simple to use. In the simplest case, you only need to include one JavaScript file.\nThere are absolutely no other required external dependencies. For more information, please see the [**documentation**](http://docs.fineuploader.com).\n\n\n## Contributing\n\nIf you'd like to help and keep this project strong and relevant, you have several options.\n\n\n### Help us pay the bills\n\nFine Uploader is currently looking for a sponsor to pay the AWS bills (which have recently lapsed).\nThese add up to about $40/month. Please open an issue if you are interesting in becoming a sponsor.\nWe will happily list you as sponsor on the site and README.\n\n\n### File a bug report\n\nIf you see something that isn't quite right, whether it be in the code, or on the docs site, or even on FineUploader.com (which is hosted on GitHub), _please_ file a bug report. Be sure to make sure the [bug hasn't already been filed][issues] by someone else. If it has, feel free to upvote the issue and/or add your comments.\n\n\n### Join the team\n\nAre you interested in working on a very popular JavaScript-based file upload library with countless users? If you're strong in JavaScript, HTML, and CSS, and have a desire to help push the FOSS movement forward, let us know! The project can always use more experts.\n\n\n### Help spread the word\n\nAre you using Fine Uploader in your library or project? If so, let us know and we may add a link to your project or application _and_ your logo to FineUploader.com. If you care to write an article about Fine Uploader, we would be open to reading and publicizing it through our site, blog, or Twitter feed.\n\n\n### Develop an integration library\n\nAre you using Fine Uploader inside of a larger framework (such as React, Angular2, Ember.js, etc)? If so, perhaps you've already written a library that wraps Fine Uploader and makes it simple to use Fine Uploader in this context. Let us know and it may make sense to either link to your library, or even move it into the FineUploader GitHub organization (with your approval, of course). We'd also love to see libraries that make it simple to pair Fine Uploader with other useful libraries, such as image editors and rich text editors.\n\n\n### Contribute code\n\nThe best way to contribute code is to open up a pull request that addresses one of the open [feature requests or bugs][issues]. In order to get started developing Fine Uploader, read this entire section to get the project up and running on your local development machine. This section describes how you can build and test Fine Uploader locally. You may use these instructions to build a copy for yourself, or to contribute changes back to the library. \n\n#### Setup\n\nYou must have Node.js instaled locally (any version should be fine), _and_ you must have Unix-like environment to work with. Linux, FreeBSD/OS X, Cygwin, and Windows 10 bash all _should_ be acceptable environments. Please open up a new issue if you have trouble building. The build process is centered around a single Makefile, so GNU Make is required as well (though most if not all Unix-like OSes should already have this installed). Finally, you will need a git client.\n\nTo pull down the project & build dependencies:\n\n1. Download the project repository: `git clone https://github.com/FineUploader/fine-uploader.git`.\n2. Install all project development dependencies: `npm install`.\n\n#### Generating build artifacts\n\n- To build all build artifacts for all endpoint types: `make build`. You can speed this process up a bit by using the parallel recipes feature of Make: `make build -j`. If you would like to build only a specific endpoint type, see the Makefile for the appropriate recipe. The build output will be created in the `_build` directory. \n- To build zip files for all endpoint types: `make zip`. To build a zip for only a specific endpoint type, see the Makefile for the appropriate recipe. The zip files will be included alongside the build output in the `_build` directory.\n- To rev the version number: `make rev-version target=NEW_VERSION`, where `NEW_VERSION` is the semver-compatible target version identifier.\n\n#### Running tests\n\nTo build, run the tests & linter: `npm test` (you'll need Firefox installed locally).\n\n#### Commiting new code and changes\n\n- Follow the [Angular.js commit guidelines][angular-commit].\n- Follow the [Git Flow][git-flow] branching strategy.\n\n\n[angular-commit]: https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md#commit\n[git-flow]: http://nvie.com/posts/a-successful-git-branching-model/\n[issues]: https://github.com/FineUploader/fine-uploader/issues\n"
  },
  {
    "path": "client/README.md",
    "content": "Do not add files from this directory into your project.  Please visit the\ndownloads page for zips that contain combined and version-stamped javascript\nand css files, along with all required resources.\n\n# Download\nTo download a pre-packaged version of Fine Uploader visit: http://fineuploader.com/downloads.html\n\n# Build\nFor instructions on building your own packaged version of Fine Uploader visit: http://docs.fineuploader.com/contributing.htm\n\n"
  },
  {
    "path": "client/commonJs/all.js",
    "content": "\"use strict\";\n\nmodule.exports = require(\"../all.fine-uploader/all.fine-uploader\");\n"
  },
  {
    "path": "client/commonJs/azure.js",
    "content": "\"use strict\";\n\nmodule.exports = require(\"../azure.fine-uploader/azure.fine-uploader\");\n"
  },
  {
    "path": "client/commonJs/core/all.js",
    "content": "\"use strict\";\n\nmodule.exports = require(\"../../all.fine-uploader/all.fine-uploader.core\");\n"
  },
  {
    "path": "client/commonJs/core/azure.js",
    "content": "\"use strict\";\n\nmodule.exports = require(\"../../azure.fine-uploader/azure.fine-uploader.core\");\n"
  },
  {
    "path": "client/commonJs/core/index.js",
    "content": "\"use strict\";\n\nmodule.exports = require(\"../../fine-uploader/fine-uploader.core\");\n"
  },
  {
    "path": "client/commonJs/core/s3.js",
    "content": "\"use strict\";\n\nmodule.exports = require(\"../../s3.fine-uploader/s3.fine-uploader.core\");\n"
  },
  {
    "path": "client/commonJs/core/traditional.js",
    "content": "\"use strict\";\n\nmodule.exports = require(\"../../fine-uploader/fine-uploader.core\");\n"
  },
  {
    "path": "client/commonJs/dnd.js",
    "content": "\"use strict\";\n\nmodule.exports = require(\"../dnd/dnd\");\n"
  },
  {
    "path": "client/commonJs/jquery/azure.js",
    "content": "\"use strict\";\n\nmodule.exports = require(\"../../azure.jquery.fine-uploader/azure.jquery.fine-uploader\");\n"
  },
  {
    "path": "client/commonJs/jquery/s3.js",
    "content": "\"use strict\";\n\nmodule.exports = require(\"../../s3.jquery.fine-uploader/s3.jquery.fine-uploader\");\n"
  },
  {
    "path": "client/commonJs/jquery/traditional.js",
    "content": "\"use strict\";\n\nmodule.exports = require(\"../../jquery.fine-uploader/jquery.fine-uploader\");\n"
  },
  {
    "path": "client/commonJs/s3.js",
    "content": "\"use strict\";\n\nmodule.exports = require(\"../s3.fine-uploader/s3.fine-uploader\");\n"
  },
  {
    "path": "client/commonJs/traditional.js",
    "content": "\"use strict\";\n\nmodule.exports = require(\"../fine-uploader/fine-uploader\");\n"
  },
  {
    "path": "client/fine-uploader-gallery.css",
    "content": "/* ---------------------------------------\n/* Fine Uploader Gallery View Styles\n/* ---------------------------------------\n\n\n/* Buttons\n------------------------------------------ */\n.qq-gallery .qq-btn\n{\n    float: right;\n    border: none;\n    padding: 0;\n    margin: 0;\n    box-shadow: none;\n}\n\n/* Upload Button\n------------------------------------------ */\n.qq-gallery .qq-upload-button {\n    display: inline;\n    width: 105px;\n    padding: 7px 10px;\n    float: left;\n    text-align: center;\n    background: #00ABC7;\n    color: #FFFFFF;\n    border-radius: 2px;\n    border: 1px solid #37B7CC;\n    box-shadow: 0 1px 1px rgba(255, 255, 255, 0.37) inset,\n    1px 0 1px rgba(255, 255, 255, 0.07) inset,\n    0 1px 0 rgba(0, 0, 0, 0.36),\n    0 -2px 12px rgba(0, 0, 0, 0.08) inset\n}\n.qq-gallery .qq-upload-button-hover {\n    background: #33B6CC;\n}\n.qq-gallery .qq-upload-button-focus {\n    outline: 1px dotted #000000;\n}\n\n\n/* Drop Zone\n------------------------------------------ */\n.qq-gallery.qq-uploader {\n    position: relative;\n    min-height: 200px;\n    max-height: 490px;\n    overflow-y: hidden;\n    width: inherit;\n    border-radius: 6px;\n    border: 1px dashed #CCCCCC;\n    background-color: #FAFAFA;\n    padding: 20px;\n}\n.qq-gallery.qq-uploader:before {\n    content: attr(qq-drop-area-text) \" \";\n    position: absolute;\n    font-size: 200%;\n    left: 0;\n    width: 100%;\n    text-align: center;\n    top: 45%;\n    opacity: 0.25;\n    filter: alpha(opacity=25);\n}\n.qq-gallery .qq-upload-drop-area, .qq-upload-extra-drop-area {\n    position: absolute;\n    top: 0;\n    left: 0;\n    width: 100%;\n    height: 100%;\n    min-height: 30px;\n    z-index: 2;\n    background: #F9F9F9;\n    border-radius: 4px;\n    text-align: center;\n}\n.qq-gallery .qq-upload-drop-area span {\n    display: block;\n    position: absolute;\n    top: 50%;\n    width: 100%;\n    margin-top: -8px;\n    font-size: 16px;\n}\n.qq-gallery .qq-upload-extra-drop-area {\n    position: relative;\n    margin-top: 50px;\n    font-size: 16px;\n    padding-top: 30px;\n    height: 20px;\n    min-height: 40px;\n}\n.qq-gallery .qq-upload-drop-area-active {\n    background: #FDFDFD;\n    border-radius: 4px;\n}\n.qq-gallery .qq-upload-list {\n    margin: 0;\n    padding: 10px 0 0;\n    list-style: none;\n    max-height: 450px;\n    overflow-y: auto;\n    clear: both;\n    box-shadow: none;\n}\n\n\n/* Uploaded Elements\n------------------------------------------ */\n.qq-gallery .qq-upload-list li {\n    display: inline-block;\n    position: relative;\n    max-width: 120px;\n    margin: 0 25px 25px 0;\n    padding: 0;\n    line-height: 16px;\n    font-size: 13px;\n    color: #424242;\n    background-color: #FFFFFF;\n    border-radius: 2px;\n    box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.22);\n    vertical-align: top;\n\n    /* to ensure consistent size of tiles - may need to change if qq-max-size attr on preview img changes */\n    height: 186px;\n}\n\n.qq-gallery .qq-upload-spinner,\n.qq-gallery .qq-upload-size,\n.qq-gallery .qq-upload-retry,\n.qq-gallery .qq-upload-failed-text,\n.qq-gallery .qq-upload-delete,\n.qq-gallery .qq-upload-pause,\n.qq-gallery .qq-upload-continue {\n    display: inline;\n}\n.qq-gallery .qq-upload-retry:hover,\n.qq-gallery .qq-upload-delete:hover,\n.qq-gallery .qq-upload-pause:hover,\n.qq-gallery .qq-upload-continue:hover {\n    background-color: transparent;\n}\n.qq-gallery .qq-upload-delete,\n.qq-gallery .qq-upload-pause,\n.qq-gallery .qq-upload-continue,\n.qq-gallery .qq-upload-cancel {\n    cursor: pointer;\n}\n.qq-gallery .qq-upload-delete,\n.qq-gallery .qq-upload-pause,\n.qq-gallery .qq-upload-continue {\n    border:none;\n    background: none;\n    color: #00A0BA;\n    font-size: 12px;\n    padding: 0;\n}\n/* to ensure consistent size of tiles - only display status text before auto-retry or after failure */\n.qq-gallery .qq-upload-status-text {\n    color: #333333;\n    font-size: 12px;\n    padding-left: 3px;\n    padding-top: 2px;\n    width: inherit;\n    display: none;\n    width: 108px;\n}\n.qq-gallery .qq-upload-fail .qq-upload-status-text {\n    text-overflow: ellipsis;\n    white-space: nowrap;\n    overflow-x: hidden;\n    display: block;\n}\n.qq-gallery .qq-upload-retrying .qq-upload-status-text {\n    display: inline-block;\n}\n.qq-gallery .qq-upload-retrying .qq-progress-bar-container {\n    display: none;\n}\n\n.qq-gallery .qq-upload-cancel {\n    background-color: #525252;\n    color: #F7F7F7;\n    font-weight: bold;\n    font-family: Arial, Helvetica, sans-serif;\n    border-radius: 12px;\n    border: none;\n    height: 22px;\n    width: 22px;\n    padding: 4px;\n    position: absolute;\n    right: -5px;\n    top: -6px;\n    margin: 0;\n    line-height: 17px;\n}\n.qq-gallery .qq-upload-cancel:hover {\n    background-color: #525252;\n}\n.qq-gallery .qq-upload-retry {\n    cursor: pointer;\n    position: absolute;\n    top: 30px;\n    left: 50%;\n    margin-left: -31px;\n    box-shadow: 0 1px 1px rgba(255, 255, 255, 0.37) inset,\n                1px 0 1px rgba(255, 255, 255, 0.07) inset,\n                0 4px 4px rgba(0, 0, 0, 0.5),\n                0 -2px 12px rgba(0, 0, 0, 0.08) inset;\n    padding: 3px 4px;\n    border: 1px solid #d2ddc7;\n    border-radius: 2px;\n    color: inherit;\n    background-color: #EBF6E0;\n    z-index: 1;\n}\n.qq-gallery .qq-upload-retry:hover {\n    background-color: #f7ffec;\n}\n\n.qq-gallery .qq-file-info {\n    padding: 10px 6px 4px;\n    margin-top: -3px;\n    border-radius: 0 0 2px 2px;\n    text-align: left;\n    overflow: hidden;\n}\n\n.qq-gallery .qq-file-info .qq-file-name {\n    position: relative;\n}\n\n.qq-gallery .qq-upload-file {\n    display: block;\n    margin-right: 0;\n    margin-bottom: 3px;\n    width: auto;\n\n    /* to ensure consistent size of tiles - constrain text to single line */\n    text-overflow: ellipsis;\n    white-space: nowrap;\n    overflow-x: hidden;\n}\n.qq-gallery .qq-upload-spinner {\n    display: inline-block;\n    background: url(\"loading.gif\");\n    position: absolute;\n    left: 50%;\n    margin-left: -7px;\n    top: 53px;\n    width: 15px;\n    height: 15px;\n    vertical-align: text-bottom;\n}\n.qq-gallery .qq-drop-processing {\n    display: block;\n}\n.qq-gallery .qq-drop-processing-spinner {\n    display: inline-block;\n    background: url(\"processing.gif\");\n    width: 24px;\n    height: 24px;\n    vertical-align: text-bottom;\n}\n.qq-gallery .qq-upload-failed-text {\n    display: none;\n    font-style: italic;\n    font-weight: bold;\n}\n.qq-gallery .qq-upload-failed-icon {\n    display:none;\n    width:15px;\n    height:15px;\n    vertical-align:text-bottom;\n}\n.qq-gallery .qq-upload-fail .qq-upload-failed-text {\n    display: inline;\n}\n.qq-gallery .qq-upload-retrying .qq-upload-failed-text {\n    display: inline;\n}\n.qq-gallery .qq-upload-list li.qq-upload-success {\n    background-color: #F2F7ED;\n}\n.qq-gallery .qq-upload-list li.qq-upload-fail {\n    background-color: #F5EDED;\n    box-shadow: 0 0 1px 0 red;\n    border: 0;\n}\n.qq-gallery .qq-progress-bar {\n    display: block;\n    background: #00abc7;\n    width: 0%;\n    height: 15px;\n    border-radius: 6px;\n    margin-bottom: 3px;\n}\n\n.qq-gallery .qq-total-progress-bar {\n    height: 25px;\n    border-radius: 9px;\n}\n\n.qq-gallery .qq-total-progress-bar-container {\n    margin-left: 9px;\n    display: inline;\n    float: right;\n    width: 500px;\n}\n\n.qq-gallery .qq-upload-size {\n    float: left;\n    font-size: 11px;\n    color: #929292;\n    margin-bottom: 3px;\n    margin-right: 0;\n    display: inline-block;\n}\n\n.qq-gallery INPUT.qq-edit-filename {\n    position: absolute;\n    opacity: 0;\n    filter: alpha(opacity=0);\n    z-index: -1;\n    -ms-filter: \"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)\";\n}\n\n.qq-gallery .qq-upload-file.qq-editable {\n    cursor: pointer;\n    margin-right: 20px;\n}\n\n.qq-gallery .qq-edit-filename-icon.qq-editable {\n    display: inline-block;\n    cursor: pointer;\n    position: absolute;\n    right: 0;\n    top: 0;\n}\n\n.qq-gallery INPUT.qq-edit-filename.qq-editing {\n    position: static;\n    height: 28px;\n    width: 90px;\n    width: -moz-available;\n    padding: 0 8px;\n    margin-bottom: 3px;\n    border: 1px solid #ccc;\n    border-radius: 2px;\n    font-size: 13px;\n\n    opacity: 1;\n    filter: alpha(opacity=100);\n    -ms-filter: \"progid:DXImageTransform.Microsoft.Alpha(Opacity=100)\";\n}\n\n.qq-gallery .qq-edit-filename-icon {\n    display: none;\n    background: url(\"edit.gif\");\n    width: 15px;\n    height: 15px;\n    vertical-align: text-bottom;\n}\n.qq-gallery .qq-delete-icon {\n    background: url(\"trash.gif\");\n    width: 15px;\n    height: 15px;\n    vertical-align: sub;\n    display: inline-block;\n}\n.qq-gallery .qq-retry-icon {\n    background: url(\"retry.gif\");\n    width: 15px;\n    height: 15px;\n    vertical-align: sub;\n    display: inline-block;\n    float: none;\n}\n.qq-gallery .qq-continue-icon {\n    background: url(\"continue.gif\");\n    width: 15px;\n    height: 15px;\n    vertical-align: sub;\n    display: inline-block;\n}\n.qq-gallery .qq-pause-icon {\n    background: url(\"pause.gif\");\n    width: 15px;\n    height: 15px;\n    vertical-align: sub;\n    display: inline-block;\n}\n\n.qq-gallery .qq-hide {\n    display: none;\n}\n\n\n/* Thumbnail\n------------------------------------------ */\n.qq-gallery .qq-in-progress .qq-thumbnail-wrapper {\n    /* makes the spinner on top of the thumbnail more visible */\n    opacity: 0.5;\n    filter: alpha(opacity=50);\n}\n.qq-gallery .qq-thumbnail-wrapper {\n    overflow: hidden;\n    position: relative;\n\n    /* to ensure consistent size of tiles - should match qq-max-size attribute value on qq-thumbnail-selector IMG element */\n    height: 120px;\n    width: 120px;\n}\n.qq-gallery .qq-thumbnail-selector {\n    border-radius: 2px 2px 0 0;\n    bottom: 0;\n\n    /* we will override this in the :root thumbnail selector (to help center the preview) for everything other than IE8 */\n    top: 0;\n\n    /* center the thumb horizontally in the tile */\n    margin:auto;\n    display: block;\n}\n\n/* hack to ensure we don't try to center preview in IE8, since -ms-filter doesn't mimic translateY as expected in all cases */\n:root *> .qq-gallery .qq-thumbnail-selector {\n    /* vertically center preview image on tile */\n    position: relative;\n    top: 50%;\n    transform: translateY(-50%);\n    -moz-transform: translateY(-50%);\n    -ms-transform: translateY(-50%);\n    -webkit-transform: translateY(-50%);\n}\n\n/* <dialog> element styles */\n.qq-gallery.qq-uploader DIALOG {\n    display: none;\n}\n\n.qq-gallery.qq-uploader DIALOG[open] {\n    display: block;\n}\n\n.qq-gallery.qq-uploader DIALOG {\n    display: none;\n}\n\n.qq-gallery.qq-uploader DIALOG[open] {\n    display: block;\n}\n\n.qq-gallery.qq-uploader DIALOG .qq-dialog-buttons {\n    text-align: center;\n    padding-top: 10px;\n}\n\n.qq-gallery.qq-uploader DIALOG .qq-dialog-buttons BUTTON {\n    margin-left: 5px;\n    margin-right: 5px;\n}\n\n.qq-gallery.qq-uploader DIALOG .qq-dialog-message-selector {\n    padding-bottom: 10px;\n}\n\n.qq-gallery .qq-uploader DIALOG::backdrop {\n    background-color: rgba(0, 0, 0, 0.7);\n}"
  },
  {
    "path": "client/fine-uploader-new.css",
    "content": "/* ---------------------------------------\n/* Fine Uploader Styles\n/* ---------------------------------------\n\n/* Buttons\n------------------------------------------ */\n.qq-btn\n{\n    box-shadow: 0 1px 1px rgba(255, 255, 255, 0.37) inset,\n                1px 0 1px rgba(255, 255, 255, 0.07) inset,\n                0 1px 0 rgba(0, 0, 0, 0.36),\n                0 -2px 12px rgba(0, 0, 0, 0.08) inset;\n    padding: 3px 4px;\n    border: 1px solid #CCCCCC;\n    border-radius: 2px;\n    color: inherit;\n    background-color: #FFFFFF;\n}\n.qq-upload-delete, .qq-upload-pause, .qq-upload-continue {\n    display: inline;\n}\n.qq-upload-delete\n{\n    background-color: #e65c47;\n    color: #FAFAFA;\n    border-color: #dc523d;\n    text-shadow: 0 1px 1px rgba(0, 0, 0, 0.55);\n}\n.qq-upload-delete:hover {\n    background-color: #f56b56;\n }\n.qq-upload-cancel\n{\n    background-color: #F5D7D7;\n    border-color: #e6c8c8;\n}\n.qq-upload-cancel:hover {\n    background-color: #ffe1e1;\n}\n.qq-upload-retry\n{\n    background-color: #EBF6E0;\n    border-color: #d2ddc7;\n}\n.qq-upload-retry:hover {\n    background-color: #f7ffec;\n}\n.qq-upload-pause, .qq-upload-continue {\n    background-color: #00ABC7;\n    color: #FAFAFA;\n    border-color: #2dadc2;\n    text-shadow: 0 1px 1px rgba(0, 0, 0, 0.55);\n}\n.qq-upload-pause:hover, .qq-upload-continue:hover {\n    background-color: #0fbad6;\n}\n\n/* Upload Button\n------------------------------------------ */\n.qq-upload-button {\n    display: inline;\n    width: 105px;\n    margin-bottom: 10px;\n    padding: 7px 10px;\n    text-align: center;\n    float: left;\n    background: #00ABC7;\n    color: #FFFFFF;\n    border-radius: 2px;\n    border: 1px solid #2dadc2;\n    box-shadow: 0 1px 1px rgba(255, 255, 255, 0.37) inset,\n                1px 0 1px rgba(255, 255, 255, 0.07) inset,\n                0 1px 0 rgba(0, 0, 0, 0.36),\n                0 -2px 12px rgba(0, 0, 0, 0.08) inset;\n}\n.qq-upload-button-hover {\n    background: #33B6CC;\n}\n.qq-upload-button-focus {\n    outline: 1px dotted #000000;\n}\n\n\n/* Drop Zone\n------------------------------------------ */\n.qq-uploader {\n    position: relative;\n    min-height: 200px;\n    max-height: 490px;\n    overflow-y: hidden;\n    width: inherit;\n    border-radius: 6px;\n    background-color: #FDFDFD;\n    border: 1px dashed #CCCCCC;\n    padding: 20px;\n}\n.qq-uploader:before {\n    content: attr(qq-drop-area-text) \" \";\n    position: absolute;\n    font-size: 200%;\n    left: 0;\n    width: 100%;\n    text-align: center;\n    top: 45%;\n    opacity: 0.25;\n}\n.qq-upload-drop-area, .qq-upload-extra-drop-area {\n    position: absolute;\n    top: 0;\n    left: 0;\n    width: 100%;\n    height: 100%;\n    min-height: 30px;\n    z-index: 2;\n    background: #F9F9F9;\n    border-radius: 4px;\n    border: 1px dashed #CCCCCC;\n    text-align: center;\n}\n.qq-upload-drop-area span {\n    display: block;\n    position: absolute;\n    top: 50%;\n    width: 100%;\n    margin-top: -8px;\n    font-size: 16px;\n}\n.qq-upload-extra-drop-area {\n    position: relative;\n    margin-top: 50px;\n    font-size: 16px;\n    padding-top: 30px;\n    height: 20px;\n    min-height: 40px;\n}\n.qq-upload-drop-area-active {\n    background: #FDFDFD;\n    border-radius: 4px;\n    border: 1px dashed #CCCCCC;\n}\n.qq-upload-list {\n    margin: 0;\n    padding: 0;\n    list-style: none;\n    max-height: 450px;\n    overflow-y: auto;\n    box-shadow: 0px 1px 0px rgba(15, 15, 50, 0.14);\n    clear: both;\n}\n\n\n/* Uploaded Elements\n------------------------------------------ */\n.qq-upload-list li {\n    margin: 0;\n    padding: 9px;\n    line-height: 15px;\n    font-size: 16px;\n    color: #424242;\n    background-color: #F6F6F6;\n    border-top: 1px solid #FFFFFF;\n    border-bottom: 1px solid #DDDDDD;\n}\n.qq-upload-list li:first-child {\n    border-top: none;\n}\n.qq-upload-list li:last-child {\n    border-bottom: none;\n}\n\n.qq-upload-file, .qq-upload-spinner, .qq-upload-size,\n.qq-upload-cancel, .qq-upload-retry, .qq-upload-failed-text,\n.qq-upload-delete, .qq-upload-pause, .qq-upload-continue {\n    margin-right: 12px;\n    display: inline;\n}\n.qq-upload-file {\n    vertical-align: middle;\n    display: inline-block;\n    width: 300px;\n    text-overflow: ellipsis;\n    white-space: nowrap;\n    overflow-x: hidden;\n    height: 18px;\n}\n.qq-upload-spinner {\n    display: inline-block;\n    background: url(\"loading.gif\");\n    width: 15px;\n    height: 15px;\n    vertical-align: text-bottom;\n}\n.qq-drop-processing {\n    display: block;\n}\n.qq-drop-processing-spinner {\n    display: inline-block;\n    background: url(\"processing.gif\");\n    width: 24px;\n    height: 24px;\n    vertical-align: text-bottom;\n}\n.qq-upload-size, .qq-upload-cancel, .qq-upload-retry,\n.qq-upload-delete, .qq-upload-pause, .qq-upload-continue {\n    font-size: 12px;\n    font-weight: normal;\n    cursor: pointer;\n    vertical-align: middle;\n}\n.qq-upload-status-text {\n    font-size: 14px;\n    font-weight: bold;\n    display: block;\n}\n.qq-upload-failed-text {\n    display: none;\n    font-style: italic;\n    font-weight: bold;\n}\n.qq-upload-failed-icon {\n    display:none;\n    width:15px;\n    height:15px;\n    vertical-align:text-bottom;\n}\n.qq-upload-fail .qq-upload-failed-text {\n    display: inline;\n}\n.qq-upload-retrying .qq-upload-failed-text {\n    display: inline;\n}\n.qq-upload-list li.qq-upload-success {\n    background-color: #EBF6E0;\n    color: #424242;\n    border-bottom: 1px solid #D3DED1;\n    border-top: 1px solid #F7FFF5;\n}\n.qq-upload-list li.qq-upload-fail {\n    background-color: #F5D7D7;\n    color: #424242;\n    border-bottom: 1px solid #DECACA;\n    border-top: 1px solid #FCE6E6;\n}\n.qq-progress-bar {\n    display: block;\n    display: block;\n    background: #00abc7;\n    width: 0%;\n    height: 15px;\n    border-radius: 6px;\n    margin-bottom: 3px;\n}\n\n.qq-total-progress-bar {\n    height: 25px;\n    border-radius: 9px;\n}\n\n.qq-total-progress-bar-container {\n    margin-left: 9px;\n    display: inline;\n    float: right;\n    width: 500px;\n}\n\nINPUT.qq-edit-filename {\n    position: absolute;\n    opacity: 0;\n    filter: alpha(opacity=0);\n    z-index: -1;\n    -ms-filter: \"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)\";\n}\n\n.qq-upload-file.qq-editable {\n    cursor: pointer;\n    margin-right: 4px;\n}\n\n.qq-edit-filename-icon.qq-editable {\n    display: inline-block;\n    cursor: pointer;\n}\n\nINPUT.qq-edit-filename.qq-editing {\n    position: static;\n    height: 28px;\n    padding: 0 8px;\n    margin-right: 10px;\n    margin-bottom: -5px;\n    border: 1px solid #ccc;\n    border-radius: 2px;\n    font-size: 16px;\n\n    opacity: 1;\n    filter: alpha(opacity=100);\n    -ms-filter: \"progid:DXImageTransform.Microsoft.Alpha(Opacity=100)\";\n}\n\n.qq-edit-filename-icon {\n    display: none;\n    background: url(\"edit.gif\");\n    width: 15px;\n    height: 15px;\n    vertical-align: text-bottom;\n    margin-right: 16px;\n}\n\n.qq-hide {\n    display: none;\n}\n\n\n/* Thumbnail\n------------------------------------------ */\n.qq-thumbnail-selector {\n    vertical-align: middle;\n    margin-right: 12px;\n}\n\n\n/* <dialog> element styles */\n.qq-uploader DIALOG {\n    display: none;\n}\n\n.qq-uploader DIALOG[open] {\n    display: block;\n}\n\n.qq-uploader DIALOG {\n    display: none;\n}\n\n.qq-uploader DIALOG[open] {\n    display: block;\n}\n\n.qq-uploader DIALOG .qq-dialog-buttons {\n    text-align: center;\n    padding-top: 10px;\n}\n\n.qq-uploader DIALOG .qq-dialog-buttons BUTTON {\n    margin-left: 5px;\n    margin-right: 5px;\n}\n\n.qq-uploader DIALOG .qq-dialog-message-selector {\n    padding-bottom: 10px;\n}\n\n.qq-uploader DIALOG::backdrop {\n    background-color: rgba(0, 0, 0, 0.7);\n}"
  },
  {
    "path": "client/fine-uploader.css",
    "content": ".qq-uploader {\n    position: relative;\n    width: 100%;\n}\n.qq-upload-button {\n    display: block;\n    width: 105px;\n    padding: 7px 0;\n    text-align: center;\n    background: #880000;\n    border-bottom: 1px solid #DDD;\n    color: #FFF;\n}\n.qq-upload-button-hover {\n    background: #CC0000;\n}\n.qq-upload-button-focus {\n    outline: 1px dotted #000000;\n}\n.qq-upload-drop-area, .qq-upload-extra-drop-area {\n    position: absolute;\n    top: 0;\n    left: 0;\n    width: 100%;\n    height: 100%;\n    min-height: 30px;\n    z-index: 2;\n    background: #FF9797;\n    text-align: center;\n}\n.qq-upload-drop-area span {\n    display: block;\n    position: absolute;\n    top: 50%;\n    width: 100%;\n    margin-top: -8px;\n    font-size: 16px;\n}\n.qq-upload-extra-drop-area {\n    position: relative;\n    margin-top: 50px;\n    font-size: 16px;\n    padding-top: 30px;\n    height: 20px;\n    min-height: 40px;\n}\n.qq-upload-drop-area-active {\n    background: #FF7171;\n}\n.qq-upload-list {\n    margin: 0;\n    padding: 0;\n    list-style: none;\n}\n.qq-upload-list li {\n    margin: 0;\n    padding: 9px;\n    line-height: 15px;\n    font-size: 16px;\n    background-color: #FFF0BD;\n}\n.qq-upload-file, .qq-upload-spinner, .qq-upload-size,\n.qq-upload-cancel, .qq-upload-retry, .qq-upload-failed-text,\n.qq-upload-delete, .qq-upload-pause, .qq-upload-continue {\n    margin-right: 12px;\n    display: inline;\n}\n.qq-upload-file {\n}\n.qq-upload-spinner {\n    display: inline-block;\n    background: url(\"loading.gif\");\n    width: 15px;\n    height: 15px;\n    vertical-align: text-bottom;\n}\n.qq-drop-processing {\n    display: block;\n}\n.qq-drop-processing-spinner {\n    display: inline-block;\n    background: url(\"processing.gif\");\n    width: 24px;\n    height: 24px;\n    vertical-align: text-bottom;\n}\n\n.qq-upload-delete, .qq-upload-pause, .qq-upload-continue {\n    display: inline;\n}\n\n.qq-upload-retry, .qq-upload-delete, .qq-upload-cancel,\n.qq-upload-pause, .qq-upload-continue {\n    color: #000000;\n}\n\n.qq-upload-size, .qq-upload-cancel, .qq-upload-retry,\n.qq-upload-delete, .qq-upload-pause, .qq-upload-continue {\n    font-size: 12px;\n    font-weight: normal;\n}\n.qq-upload-failed-text {\n    display: none;\n    font-style: italic;\n    font-weight: bold;\n}\n.qq-upload-failed-icon {\n    display:none;\n    width:15px;\n    height:15px;\n    vertical-align:text-bottom;\n}\n.qq-upload-fail .qq-upload-failed-text {\n    display: inline;\n}\n.qq-upload-retrying .qq-upload-failed-text {\n    display: inline;\n    color: #D60000;\n}\n.qq-upload-list li.qq-upload-success {\n    background-color: #5DA30C;\n    color: #FFFFFF;\n}\n.qq-upload-list li.qq-upload-fail {\n    background-color: #D60000;\n    color: #FFFFFF;\n}\n.qq-progress-bar {\n    display: block;\n    background: -moz-linear-gradient(top,  rgba(30,87,153,1) 0%, rgba(41,137,216,1) 50%, rgba(32,124,202,1) 51%, rgba(125,185,232,1) 100%); /* FF3.6+ */\n    background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(30,87,153,1)), color-stop(50%,rgba(41,137,216,1)), color-stop(51%,rgba(32,124,202,1)), color-stop(100%,rgba(125,185,232,1))); /* Chrome,Safari4+ */\n    background: -webkit-linear-gradient(top,  rgba(30,87,153,1) 0%,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%); /* Chrome10+,Safari5.1+ */\n    background: -o-linear-gradient(top,  rgba(30,87,153,1) 0%,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%); /* Opera 11.10+ */\n    background: -ms-linear-gradient(top,  rgba(30,87,153,1) 0%,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%); /* IE10+ */\n    background: linear-gradient(to bottom,  rgba(30,87,153,1) 0%,rgba(41,137,216,1) 50%,rgba(32,124,202,1) 51%,rgba(125,185,232,1) 100%); /* W3C */\n    width: 0%;\n    height: 15px;\n    border-radius: 6px;\n    margin-bottom: 3px;\n}\n\n.qq-total-progress-bar {\n    height: 25px;\n    border-radius: 9px;\n}\n\n.qq-total-progress-bar-container {\n    margin: 9px;\n}\n\nINPUT.qq-edit-filename {\n    position: absolute;\n    opacity: 0;\n    filter: alpha(opacity=0);\n    z-index: -1;\n    -ms-filter: \"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)\";\n}\n\n.qq-upload-file.qq-editable {\n    cursor: pointer;\n}\n\n.qq-edit-filename-icon.qq-editable {\n    display: inline-block;\n    cursor: pointer;\n}\n\nINPUT.qq-edit-filename.qq-editing {\n    position: static;\n    margin-top: -5px;\n    margin-right: 10px;\n    margin-bottom: -5px;\n\n    opacity: 1;\n    filter: alpha(opacity=100);\n    -ms-filter: \"progid:DXImageTransform.Microsoft.Alpha(Opacity=100)\";\n}\n\n.qq-edit-filename-icon {\n    display: none;\n    background: url(\"edit.gif\");\n    width: 15px;\n    height: 15px;\n    vertical-align: text-bottom;\n    margin-right: 5px;\n}\n\n.qq-hide {\n    display: none;\n}\n\n/* <dialog> element styles */\n.qq-uploader DIALOG {\n    display: none;\n}\n\n.qq-uploader DIALOG[open] {\n    display: block;\n}\n\n.qq-uploader DIALOG {\n    display: none;\n}\n\n.qq-uploader DIALOG[open] {\n    display: block;\n}\n\n.qq-uploader DIALOG .qq-dialog-buttons {\n    text-align: center;\n    padding-top: 10px;\n}\n\n.qq-uploader DIALOG .qq-dialog-buttons BUTTON {\n    margin-left: 5px;\n    margin-right: 5px;\n}\n\n.qq-uploader DIALOG .qq-dialog-message-selector {\n    padding-bottom: 10px;\n}\n\n.qq-uploader DIALOG::backdrop {\n    background-color: rgba(0, 0, 0, 0.7);\n}"
  },
  {
    "path": "client/html/templates/default.html",
    "content": "<!--\n    This is a legacy template and is not meant to be used in new Fine Uploader integrated projects.\n    Read the \"Getting Started Guide\" at http://docs.fineuploader.com/quickstart/01-getting-started.html\n    if you are not yet familiar with Fine Uploader UI.\n-->\n<script type=\"text/template\" id=\"qq-template\">\n    <div class=\"qq-uploader-selector qq-uploader\" qq-drop-area-text=\"Drop files here\">\n        <div class=\"qq-total-progress-bar-container-selector qq-total-progress-bar-container\">\n            <div role=\"progressbar\" aria-valuenow=\"0\" aria-valuemin=\"0\" aria-valuemax=\"100\" class=\"qq-total-progress-bar-selector qq-progress-bar qq-total-progress-bar\"></div>\n        </div>\n        <div class=\"qq-upload-drop-area-selector qq-upload-drop-area\" qq-hide-dropzone>\n            <span class=\"qq-upload-drop-area-text-selector\"></span>\n        </div>\n        <div class=\"qq-upload-button-selector qq-upload-button\">\n            <div>Upload a file</div>\n        </div>\n            <span class=\"qq-drop-processing-selector qq-drop-processing\">\n                <span>Processing dropped files...</span>\n                <span class=\"qq-drop-processing-spinner-selector qq-drop-processing-spinner\"></span>\n            </span>\n        <ul class=\"qq-upload-list-selector qq-upload-list\" aria-live=\"polite\" aria-relevant=\"additions removals\">\n            <li>\n                <div class=\"qq-progress-bar-container-selector\">\n                    <div role=\"progressbar\" aria-valuenow=\"0\" aria-valuemin=\"0\" aria-valuemax=\"100\" class=\"qq-progress-bar-selector qq-progress-bar\"></div>\n                </div>\n                <span class=\"qq-upload-spinner-selector qq-upload-spinner\"></span>\n                <span class=\"qq-upload-file-selector qq-upload-file\"></span>\n                <span class=\"qq-edit-filename-icon-selector qq-edit-filename-icon\" aria-label=\"Edit filename\"></span>\n                <input class=\"qq-edit-filename-selector qq-edit-filename\" tabindex=\"0\" type=\"text\">\n                <span class=\"qq-upload-size-selector qq-upload-size\"></span>\n                <button type=\"button\" class=\"qq-btn qq-upload-cancel-selector qq-upload-cancel\">Cancel</button>\n                <button type=\"button\" class=\"qq-btn qq-upload-retry-selector qq-upload-retry\">Retry</button>\n                <button type=\"button\" class=\"qq-btn qq-upload-delete-selector qq-upload-delete\">Delete</button>\n                <span role=\"status\" class=\"qq-upload-status-text-selector qq-upload-status-text\"></span>\n            </li>\n        </ul>\n\n        <dialog class=\"qq-alert-dialog-selector\">\n            <div class=\"qq-dialog-message-selector\"></div>\n            <div class=\"qq-dialog-buttons\">\n                <button type=\"button\" class=\"qq-cancel-button-selector\">Close</button>\n            </div>\n        </dialog>\n\n        <dialog class=\"qq-confirm-dialog-selector\">\n            <div class=\"qq-dialog-message-selector\"></div>\n            <div class=\"qq-dialog-buttons\">\n                <button type=\"button\" class=\"qq-cancel-button-selector\">No</button>\n                <button type=\"button\" class=\"qq-ok-button-selector\">Yes</button>\n            </div>\n        </dialog>\n\n        <dialog class=\"qq-prompt-dialog-selector\">\n            <div class=\"qq-dialog-message-selector\"></div>\n            <input type=\"text\">\n            <div class=\"qq-dialog-buttons\">\n                <button type=\"button\" class=\"qq-cancel-button-selector\">Cancel</button>\n                <button type=\"button\" class=\"qq-ok-button-selector\">Ok</button>\n            </div>\n        </dialog>\n    </div>\n</script>\n"
  },
  {
    "path": "client/html/templates/gallery.html",
    "content": "<!--\n    Read the \"Getting Started Guide\" at http://docs.fineuploader.com/quickstart/01-getting-started.html\n    if you are not yet familiar with Fine Uploader UI.\n    Please see http://docs.fineuploader.com/features/styling.html for information\n    on how to customize this template.\n-->\n<script type=\"text/template\" id=\"qq-template\">\n    <div class=\"qq-uploader-selector qq-uploader qq-gallery\" qq-drop-area-text=\"Drop files here\">\n        <div class=\"qq-total-progress-bar-container-selector qq-total-progress-bar-container\">\n            <div role=\"progressbar\" aria-valuenow=\"0\" aria-valuemin=\"0\" aria-valuemax=\"100\" class=\"qq-total-progress-bar-selector qq-progress-bar qq-total-progress-bar\"></div>\n        </div>\n        <div class=\"qq-upload-drop-area-selector qq-upload-drop-area\" qq-hide-dropzone>\n            <span class=\"qq-upload-drop-area-text-selector\"></span>\n        </div>\n        <div class=\"qq-upload-button-selector qq-upload-button\">\n            <div>Upload a file</div>\n        </div>\n        <span class=\"qq-drop-processing-selector qq-drop-processing\">\n            <span>Processing dropped files...</span>\n            <span class=\"qq-drop-processing-spinner-selector qq-drop-processing-spinner\"></span>\n        </span>\n        <ul class=\"qq-upload-list-selector qq-upload-list\" role=\"region\" aria-live=\"polite\" aria-relevant=\"additions removals\">\n            <li>\n                <span role=\"status\" class=\"qq-upload-status-text-selector qq-upload-status-text\"></span>\n                <div class=\"qq-progress-bar-container-selector qq-progress-bar-container\">\n                    <div role=\"progressbar\" aria-valuenow=\"0\" aria-valuemin=\"0\" aria-valuemax=\"100\" class=\"qq-progress-bar-selector qq-progress-bar\"></div>\n                </div>\n                <span class=\"qq-upload-spinner-selector qq-upload-spinner\"></span>\n                <div class=\"qq-thumbnail-wrapper\">\n                    <img class=\"qq-thumbnail-selector\" qq-max-size=\"120\" qq-server-scale>\n                </div>\n                <button type=\"button\" class=\"qq-upload-cancel-selector qq-upload-cancel\">X</button>\n                <button type=\"button\" class=\"qq-upload-retry-selector qq-upload-retry\">\n                    <span class=\"qq-btn qq-retry-icon\" aria-label=\"Retry\"></span>\n                    Retry\n                </button>\n\n                <div class=\"qq-file-info\">\n                    <div class=\"qq-file-name\">\n                        <span class=\"qq-upload-file-selector qq-upload-file\"></span>\n                        <span class=\"qq-edit-filename-icon-selector qq-btn qq-edit-filename-icon\" aria-label=\"Edit filename\"></span>\n                    </div>\n                    <input class=\"qq-edit-filename-selector qq-edit-filename\" tabindex=\"0\" type=\"text\">\n                    <span class=\"qq-upload-size-selector qq-upload-size\"></span>\n                    <button type=\"button\" class=\"qq-btn qq-upload-delete-selector qq-upload-delete\">\n                        <span class=\"qq-btn qq-delete-icon\" aria-label=\"Delete\"></span>\n                    </button>\n                    <button type=\"button\" class=\"qq-btn qq-upload-pause-selector qq-upload-pause\">\n                        <span class=\"qq-btn qq-pause-icon\" aria-label=\"Pause\"></span>\n                    </button>\n                    <button type=\"button\" class=\"qq-btn qq-upload-continue-selector qq-upload-continue\">\n                        <span class=\"qq-btn qq-continue-icon\" aria-label=\"Continue\"></span>\n                    </button>\n                </div>\n            </li>\n        </ul>\n\n        <dialog class=\"qq-alert-dialog-selector\">\n            <div class=\"qq-dialog-message-selector\"></div>\n            <div class=\"qq-dialog-buttons\">\n                <button type=\"button\" class=\"qq-cancel-button-selector\">Close</button>\n            </div>\n        </dialog>\n\n        <dialog class=\"qq-confirm-dialog-selector\">\n            <div class=\"qq-dialog-message-selector\"></div>\n            <div class=\"qq-dialog-buttons\">\n                <button type=\"button\" class=\"qq-cancel-button-selector\">No</button>\n                <button type=\"button\" class=\"qq-ok-button-selector\">Yes</button>\n            </div>\n        </dialog>\n\n        <dialog class=\"qq-prompt-dialog-selector\">\n            <div class=\"qq-dialog-message-selector\"></div>\n            <input type=\"text\">\n            <div class=\"qq-dialog-buttons\">\n                <button type=\"button\" class=\"qq-cancel-button-selector\">Cancel</button>\n                <button type=\"button\" class=\"qq-ok-button-selector\">Ok</button>\n            </div>\n        </dialog>\n    </div>\n</script>\n"
  },
  {
    "path": "client/html/templates/simple-thumbnails.html",
    "content": "<!--\n    Read the \"Getting Started Guide\" at http://docs.fineuploader.com/quickstart/01-getting-started.html\n    if you are not yet familiar with Fine Uploader UI.\n    Please see http://docs.fineuploader.com/features/styling.html for information\n    on how to customize this template.\n-->\n<script type=\"text/template\" id=\"qq-simple-thumbnails-template\">\n    <div class=\"qq-uploader-selector qq-uploader\" qq-drop-area-text=\"Drop files here\">\n        <div class=\"qq-total-progress-bar-container-selector qq-total-progress-bar-container\">\n            <div role=\"progressbar\" aria-valuenow=\"0\" aria-valuemin=\"0\" aria-valuemax=\"100\" class=\"qq-total-progress-bar-selector qq-progress-bar qq-total-progress-bar\"></div>\n        </div>\n        <div class=\"qq-upload-drop-area-selector qq-upload-drop-area\" qq-hide-dropzone>\n            <span class=\"qq-upload-drop-area-text-selector\"></span>\n        </div>\n        <div class=\"qq-upload-button-selector qq-upload-button\">\n            <div>Upload a file</div>\n        </div>\n        <span class=\"qq-drop-processing-selector qq-drop-processing\">\n            <span>Processing dropped files...</span>\n            <span class=\"qq-drop-processing-spinner-selector qq-drop-processing-spinner\"></span>\n        </span>\n        <ul class=\"qq-upload-list-selector qq-upload-list\" aria-live=\"polite\" aria-relevant=\"additions removals\">\n            <li>\n                <div class=\"qq-progress-bar-container-selector\">\n                    <div role=\"progressbar\" aria-valuenow=\"0\" aria-valuemin=\"0\" aria-valuemax=\"100\" class=\"qq-progress-bar-selector qq-progress-bar\"></div>\n                </div>\n                <span class=\"qq-upload-spinner-selector qq-upload-spinner\"></span>\n                <img class=\"qq-thumbnail-selector\" qq-max-size=\"100\" qq-server-scale>\n                <span class=\"qq-upload-file-selector qq-upload-file\"></span>\n                <span class=\"qq-edit-filename-icon-selector qq-edit-filename-icon\" aria-label=\"Edit filename\"></span>\n                <input class=\"qq-edit-filename-selector qq-edit-filename\" tabindex=\"0\" type=\"text\">\n                <span class=\"qq-upload-size-selector qq-upload-size\"></span>\n                <button type=\"button\" class=\"qq-btn qq-upload-cancel-selector qq-upload-cancel\">Cancel</button>\n                <button type=\"button\" class=\"qq-btn qq-upload-retry-selector qq-upload-retry\">Retry</button>\n                <button type=\"button\" class=\"qq-btn qq-upload-delete-selector qq-upload-delete\">Delete</button>\n                <span role=\"status\" class=\"qq-upload-status-text-selector qq-upload-status-text\"></span>\n            </li>\n        </ul>\n\n        <dialog class=\"qq-alert-dialog-selector\">\n            <div class=\"qq-dialog-message-selector\"></div>\n            <div class=\"qq-dialog-buttons\">\n                <button type=\"button\" class=\"qq-cancel-button-selector\">Close</button>\n            </div>\n        </dialog>\n\n        <dialog class=\"qq-confirm-dialog-selector\">\n            <div class=\"qq-dialog-message-selector\"></div>\n            <div class=\"qq-dialog-buttons\">\n                <button type=\"button\" class=\"qq-cancel-button-selector\">No</button>\n                <button type=\"button\" class=\"qq-ok-button-selector\">Yes</button>\n            </div>\n        </dialog>\n\n        <dialog class=\"qq-prompt-dialog-selector\">\n            <div class=\"qq-dialog-message-selector\"></div>\n            <input type=\"text\">\n            <div class=\"qq-dialog-buttons\">\n                <button type=\"button\" class=\"qq-cancel-button-selector\">Cancel</button>\n                <button type=\"button\" class=\"qq-ok-button-selector\">Ok</button>\n            </div>\n        </dialog>\n    </div>\n</script>\n"
  },
  {
    "path": "client/js/ajax.requester.js",
    "content": "/*globals qq, XDomainRequest*/\n/** Generic class for sending non-upload ajax requests and handling the associated responses **/\nqq.AjaxRequester = function(o) {\n    \"use strict\";\n\n    var log, shouldParamsBeInQueryString,\n        queue = [],\n        requestData = {},\n        options = {\n            acceptHeader: null,\n            validMethods: [\"PATCH\", \"POST\", \"PUT\"],\n            method: \"POST\",\n            contentType: \"application/x-www-form-urlencoded\",\n            maxConnections: 3,\n            customHeaders: {},\n            endpointStore: {},\n            paramsStore: {},\n            mandatedParams: {},\n            allowXRequestedWithAndCacheControl: true,\n            successfulResponseCodes: {\n                DELETE: [200, 202, 204],\n                PATCH: [200, 201, 202, 203, 204],\n                POST: [200, 201, 202, 203, 204],\n                PUT: [200, 201, 202, 203, 204],\n                GET: [200]\n            },\n            cors: {\n                expected: false,\n                sendCredentials: false\n            },\n            log: function(str, level) {},\n            onSend: function(id) {},\n            onComplete: function(id, xhrOrXdr, isError) {},\n            onProgress: null\n        };\n\n    qq.extend(options, o);\n    log = options.log;\n\n    if (qq.indexOf(options.validMethods, options.method) < 0) {\n        throw new Error(\"'\" + options.method + \"' is not a supported method for this type of request!\");\n    }\n\n    // [Simple methods](http://www.w3.org/TR/cors/#simple-method)\n    // are defined by the W3C in the CORS spec as a list of methods that, in part,\n    // make a CORS request eligible to be exempt from preflighting.\n    function isSimpleMethod() {\n        return qq.indexOf([\"GET\", \"POST\", \"HEAD\"], options.method) >= 0;\n    }\n\n    // [Simple headers](http://www.w3.org/TR/cors/#simple-header)\n    // are defined by the W3C in the CORS spec as a list of headers that, in part,\n    // make a CORS request eligible to be exempt from preflighting.\n    function containsNonSimpleHeaders(headers) {\n        var containsNonSimple = false;\n\n        qq.each(containsNonSimple, function(idx, header) {\n            if (qq.indexOf([\"Accept\", \"Accept-Language\", \"Content-Language\", \"Content-Type\"], header) < 0) {\n                containsNonSimple = true;\n                return false;\n            }\n        });\n\n        return containsNonSimple;\n    }\n\n    function isXdr(xhr) {\n        //The `withCredentials` test is a commonly accepted way to determine if XHR supports CORS.\n        return options.cors.expected && xhr.withCredentials === undefined;\n    }\n\n    // Returns either a new `XMLHttpRequest` or `XDomainRequest` instance.\n    function getCorsAjaxTransport() {\n        var xhrOrXdr;\n\n        if (window.XMLHttpRequest || window.ActiveXObject) {\n            xhrOrXdr = qq.createXhrInstance();\n\n            if (xhrOrXdr.withCredentials === undefined) {\n                xhrOrXdr = new XDomainRequest();\n                // Workaround for XDR bug in IE9 - https://social.msdn.microsoft.com/Forums/ie/en-US/30ef3add-767c-4436-b8a9-f1ca19b4812e/ie9-rtm-xdomainrequest-issued-requests-may-abort-if-all-event-handlers-not-specified?forum=iewebdevelopment\n                xhrOrXdr.onload = function() {};\n                xhrOrXdr.onerror = function() {};\n                xhrOrXdr.ontimeout = function() {};\n                xhrOrXdr.onprogress = function() {};\n            }\n        }\n\n        return xhrOrXdr;\n    }\n\n    // Returns either a new XHR/XDR instance, or an existing one for the associated `File` or `Blob`.\n    function getXhrOrXdr(id, suppliedXhr) {\n        var xhrOrXdr = requestData[id] && requestData[id].xhr;\n\n        if (!xhrOrXdr) {\n            if (suppliedXhr) {\n                xhrOrXdr = suppliedXhr;\n            }\n            else {\n                if (options.cors.expected) {\n                    xhrOrXdr = getCorsAjaxTransport();\n                }\n                else {\n                    xhrOrXdr = qq.createXhrInstance();\n                }\n            }\n\n            requestData[id].xhr = xhrOrXdr;\n        }\n\n        return xhrOrXdr;\n    }\n\n    // Removes element from queue, sends next request\n    function dequeue(id) {\n        var i = qq.indexOf(queue, id),\n            max = options.maxConnections,\n            nextId;\n\n        delete requestData[id];\n        queue.splice(i, 1);\n\n        if (queue.length >= max && i < max) {\n            nextId = queue[max - 1];\n            sendRequest(nextId);\n        }\n    }\n\n    function onComplete(id, xdrError) {\n        var xhr = getXhrOrXdr(id),\n            method = options.method,\n            isError = xdrError === true;\n\n        dequeue(id);\n\n        if (isError) {\n            log(method + \" request for \" + id + \" has failed\", \"error\");\n        }\n        else if (!isXdr(xhr) && !isResponseSuccessful(xhr.status)) {\n            isError = true;\n            log(method + \" request for \" + id + \" has failed - response code \" + xhr.status, \"error\");\n        }\n\n        options.onComplete(id, xhr, isError);\n    }\n\n    function getParams(id) {\n        var onDemandParams = requestData[id].additionalParams,\n            mandatedParams = options.mandatedParams,\n            params;\n\n        if (options.paramsStore.get) {\n            params = options.paramsStore.get(id);\n        }\n\n        if (onDemandParams) {\n            qq.each(onDemandParams, function(name, val) {\n                params = params || {};\n                params[name] = val;\n            });\n        }\n\n        if (mandatedParams) {\n            qq.each(mandatedParams, function(name, val) {\n                params = params || {};\n                params[name] = val;\n            });\n        }\n\n        return params;\n    }\n\n    function sendRequest(id, optXhr) {\n        var xhr = getXhrOrXdr(id, optXhr),\n            method = options.method,\n            params = getParams(id),\n            payload = requestData[id].payload,\n            url;\n\n        options.onSend(id);\n\n        url = createUrl(id, params, requestData[id].additionalQueryParams);\n\n        // XDR and XHR status detection APIs differ a bit.\n        if (isXdr(xhr)) {\n            xhr.onload = getXdrLoadHandler(id);\n            xhr.onerror = getXdrErrorHandler(id);\n        }\n        else {\n            xhr.onreadystatechange = getXhrReadyStateChangeHandler(id);\n        }\n\n        registerForUploadProgress(id);\n\n        // The last parameter is assumed to be ignored if we are actually using `XDomainRequest`.\n        xhr.open(method, url, true);\n\n        // Instruct the transport to send cookies along with the CORS request,\n        // unless we are using `XDomainRequest`, which is not capable of this.\n        if (options.cors.expected && options.cors.sendCredentials && !isXdr(xhr)) {\n            xhr.withCredentials = true;\n        }\n\n        setHeaders(id);\n\n        log(\"Sending \" + method + \" request for \" + id);\n\n        if (payload) {\n            xhr.send(payload);\n        }\n        else if (shouldParamsBeInQueryString || !params) {\n            xhr.send();\n        }\n        else if (params && options.contentType && options.contentType.toLowerCase().indexOf(\"application/x-www-form-urlencoded\") >= 0) {\n            xhr.send(qq.obj2url(params, \"\"));\n        }\n        else if (params && options.contentType && options.contentType.toLowerCase().indexOf(\"application/json\") >= 0) {\n            xhr.send(JSON.stringify(params));\n        }\n        else {\n            xhr.send(params);\n        }\n\n        return xhr;\n    }\n\n    function createUrl(id, params, additionalQueryParams) {\n        var endpoint = options.endpointStore.get(id),\n            addToPath = requestData[id].addToPath;\n\n        /*jshint -W116,-W041 */\n        if (addToPath != undefined) {\n            endpoint += \"/\" + addToPath;\n        }\n\n        if (shouldParamsBeInQueryString && params) {\n            endpoint = qq.obj2url(params, endpoint);\n        }\n\n        if (additionalQueryParams) {\n            endpoint = qq.obj2url(additionalQueryParams, endpoint);\n        }\n\n        return endpoint;\n    }\n\n    // Invoked by the UA to indicate a number of possible states that describe\n    // a live `XMLHttpRequest` transport.\n    function getXhrReadyStateChangeHandler(id) {\n        return function() {\n            if (getXhrOrXdr(id).readyState === 4) {\n                onComplete(id);\n            }\n        };\n    }\n\n    function registerForUploadProgress(id) {\n        var onProgress = options.onProgress;\n\n        if (onProgress) {\n            getXhrOrXdr(id).upload.onprogress = function(e) {\n                if (e.lengthComputable) {\n                    onProgress(id, e.loaded, e.total);\n                }\n            };\n        }\n    }\n\n    // This will be called by IE to indicate **success** for an associated\n    // `XDomainRequest` transported request.\n    function getXdrLoadHandler(id) {\n        return function() {\n            onComplete(id);\n        };\n    }\n\n    // This will be called by IE to indicate **failure** for an associated\n    // `XDomainRequest` transported request.\n    function getXdrErrorHandler(id) {\n        return function() {\n            onComplete(id, true);\n        };\n    }\n\n    function setHeaders(id) {\n        var xhr = getXhrOrXdr(id),\n            customHeaders = options.customHeaders,\n            onDemandHeaders = requestData[id].additionalHeaders || {},\n            method = options.method,\n            allHeaders = {};\n\n        // If XDomainRequest is being used, we can't set headers, so just ignore this block.\n        if (!isXdr(xhr)) {\n            options.acceptHeader && xhr.setRequestHeader(\"Accept\", options.acceptHeader);\n\n            // Only attempt to add X-Requested-With & Cache-Control if permitted\n            if (options.allowXRequestedWithAndCacheControl) {\n                // Do not add X-Requested-With & Cache-Control if this is a cross-origin request\n                // OR the cross-origin request contains a non-simple method or header.\n                // This is done to ensure a preflight is not triggered exclusively based on the\n                // addition of these 2 non-simple headers.\n                if (!options.cors.expected || (!isSimpleMethod() || containsNonSimpleHeaders(customHeaders))) {\n                    xhr.setRequestHeader(\"X-Requested-With\", \"XMLHttpRequest\");\n                    xhr.setRequestHeader(\"Cache-Control\", \"no-cache\");\n                }\n            }\n\n            if (options.contentType && (method === \"POST\" || method === \"PUT\")) {\n                xhr.setRequestHeader(\"Content-Type\", options.contentType);\n            }\n\n            qq.extend(allHeaders, qq.isFunction(customHeaders) ? customHeaders(id) : customHeaders);\n            qq.extend(allHeaders, onDemandHeaders);\n\n            qq.each(allHeaders, function(name, val) {\n                xhr.setRequestHeader(name, val);\n            });\n        }\n    }\n\n    function isResponseSuccessful(responseCode) {\n        return qq.indexOf(options.successfulResponseCodes[options.method], responseCode) >= 0;\n    }\n\n    function prepareToSend(id, optXhr, addToPath, additionalParams, additionalQueryParams, additionalHeaders, payload) {\n        requestData[id] = {\n            addToPath: addToPath,\n            additionalParams: additionalParams,\n            additionalQueryParams: additionalQueryParams,\n            additionalHeaders: additionalHeaders,\n            payload: payload\n        };\n\n        var len = queue.push(id);\n\n        // if too many active connections, wait...\n        if (len <= options.maxConnections) {\n            return sendRequest(id, optXhr);\n        }\n    }\n\n    shouldParamsBeInQueryString = options.method === \"GET\" || options.method === \"DELETE\";\n\n    qq.extend(this, {\n        // Start the process of sending the request.  The ID refers to the file associated with the request.\n        initTransport: function(id) {\n            var path, params, headers, payload, cacheBuster, additionalQueryParams;\n\n            return {\n                // Optionally specify the end of the endpoint path for the request.\n                withPath: function(appendToPath) {\n                    path = appendToPath;\n                    return this;\n                },\n\n                // Optionally specify additional parameters to send along with the request.\n                // These will be added to the query string for GET/DELETE requests or the payload\n                // for POST/PUT requests.  The Content-Type of the request will be used to determine\n                // how these parameters should be formatted as well.\n                withParams: function(additionalParams) {\n                    params = additionalParams;\n                    return this;\n                },\n\n                withQueryParams: function(_additionalQueryParams_) {\n                    additionalQueryParams = _additionalQueryParams_;\n                    return this;\n                },\n\n                // Optionally specify additional headers to send along with the request.\n                withHeaders: function(additionalHeaders) {\n                    headers = additionalHeaders;\n                    return this;\n                },\n\n                // Optionally specify a payload/body for the request.\n                withPayload: function(thePayload) {\n                    payload = thePayload;\n                    return this;\n                },\n\n                // Appends a cache buster (timestamp) to the request URL as a query parameter (only if GET or DELETE)\n                withCacheBuster: function() {\n                    cacheBuster = true;\n                    return this;\n                },\n\n                // Send the constructed request.\n                send: function(optXhr) {\n                    if (cacheBuster && qq.indexOf([\"GET\", \"DELETE\"], options.method) >= 0) {\n                        params.qqtimestamp = new Date().getTime();\n                    }\n\n                    return prepareToSend(id, optXhr, path, params, additionalQueryParams, headers, payload);\n                }\n            };\n        },\n\n        canceled: function(id) {\n            dequeue(id);\n        }\n    });\n};\n"
  },
  {
    "path": "client/js/azure/azure.xhr.upload.handler.js",
    "content": "/*globals qq */\n/**\n * Upload handler used by the upload to Azure module that depends on File API support, and, therefore, makes use of\n * `XMLHttpRequest` level 2 to upload `File`s and `Blob`s directly to Azure Blob Storage containers via the\n * associated Azure API.\n *\n * @param spec Options passed from the base handler\n * @param proxy Callbacks & methods used to query for or push out data/changes\n */\n// TODO l18n for error messages returned to UI\nqq.azure.XhrUploadHandler = function(spec, proxy) {\n    \"use strict\";\n\n    var handler = this,\n        log = proxy.log,\n        cors = spec.cors,\n        endpointStore = spec.endpointStore,\n        paramsStore = spec.paramsStore,\n        signature = spec.signature,\n        filenameParam = spec.filenameParam,\n        minFileSizeForChunking = spec.chunking.minFileSize,\n        deleteBlob = spec.deleteBlob,\n        onGetBlobName = spec.onGetBlobName,\n        getName = proxy.getName,\n        getSize = proxy.getSize,\n\n        getBlobMetadata = function(id) {\n            var params = paramsStore.get(id);\n            params[filenameParam] = getName(id);\n            return params;\n        },\n\n        api = {\n            putBlob: new qq.azure.PutBlob({\n                getBlobMetadata: getBlobMetadata,\n                log: log\n            }),\n\n            putBlock: new qq.azure.PutBlock({\n                log: log\n            }),\n\n            putBlockList: new qq.azure.PutBlockList({\n                getBlobMetadata: getBlobMetadata,\n                log: log\n            }),\n\n            getSasForPutBlobOrBlock: new qq.azure.GetSas({\n                cors: cors,\n                customHeaders: signature.customHeaders,\n                endpointStore: {\n                    get: function() {\n                        return signature.endpoint;\n                    }\n                },\n                log: log,\n                restRequestVerb: \"PUT\"\n            })\n        };\n\n    function combineChunks(id) {\n        var promise = new qq.Promise();\n\n        getSignedUrl(id).then(function(sasUri) {\n            var mimeType = handler._getMimeType(id),\n                blockIdEntries = handler._getPersistableData(id).blockIdEntries;\n\n            api.putBlockList.send(id, sasUri, blockIdEntries, mimeType, function(xhr) {\n                handler._registerXhr(id, null, xhr, api.putBlockList);\n            })\n                .then(function(xhr) {\n                    log(\"Success combining chunks for id \" + id);\n                    promise.success({}, xhr);\n                }, function(xhr) {\n                    log(\"Attempt to combine chunks failed for id \" + id, \"error\");\n                    handleFailure(xhr, promise);\n                });\n\n        },\n        promise.failure);\n\n        return promise;\n    }\n\n    function determineBlobUrl(id) {\n        var containerUrl = endpointStore.get(id),\n            promise = new qq.Promise(),\n            getBlobNameSuccess = function(blobName) {\n                handler._setThirdPartyFileId(id, blobName);\n                promise.success(containerUrl + \"/\" + blobName);\n            },\n            getBlobNameFailure = function(reason) {\n                promise.failure(reason);\n            };\n\n        onGetBlobName(id).then(getBlobNameSuccess, getBlobNameFailure);\n\n        return promise;\n    }\n\n    function getSignedUrl(id, optChunkIdx) {\n        // We may have multiple SAS requests in progress for the same file, so we must include the chunk idx\n        // as part of the ID when communicating with the SAS ajax requester to avoid collisions.\n        var getSasId = optChunkIdx == null ? id : id + \".\" + optChunkIdx,\n\n            promise = new qq.Promise(),\n            getSasSuccess = function(sasUri) {\n                log(\"GET SAS request succeeded.\");\n                promise.success(sasUri);\n            },\n            getSasFailure = function(reason, getSasXhr) {\n                log(\"GET SAS request failed: \" + reason, \"error\");\n                promise.failure({error: \"Problem communicating with local server\"}, getSasXhr);\n            },\n            determineBlobUrlSuccess = function(blobUrl) {\n                api.getSasForPutBlobOrBlock.request(getSasId, blobUrl).then(\n                    getSasSuccess,\n                    getSasFailure\n                );\n            },\n            determineBlobUrlFailure = function(reason) {\n                log(qq.format(\"Failed to determine blob name for ID {} - {}\", id, reason), \"error\");\n                promise.failure({error: reason});\n            };\n\n        determineBlobUrl(id).then(determineBlobUrlSuccess, determineBlobUrlFailure);\n\n        return promise;\n    }\n\n    function handleFailure(xhr, promise) {\n        var azureError = qq.azure.util.parseAzureError(xhr.responseText, log),\n            errorMsg = \"Problem sending file to Azure\";\n\n        promise.failure({error: errorMsg,\n            azureError: azureError && azureError.message,\n            reset: xhr.status === 403\n        });\n    }\n\n    qq.extend(this, {\n        uploadChunk: function(params) {\n            var chunkIdx = params.chunkIdx;\n            var id = params.id;\n\n            var promise = new qq.Promise();\n\n            getSignedUrl(id, chunkIdx).then(\n                function(sasUri) {\n                    var xhr = handler._createXhr(id, chunkIdx),\n                    chunkData = handler._getChunkData(id, chunkIdx);\n\n                    handler._registerProgressHandler(id, chunkIdx, chunkData.size);\n                    handler._registerXhr(id, chunkIdx, xhr, api.putBlock);\n\n                    // We may have multiple put block requests in progress for the same file, so we must include the chunk idx\n                    // as part of the ID when communicating with the put block ajax requester to avoid collisions.\n                    api.putBlock.upload(id + \".\" + chunkIdx, xhr, sasUri, chunkIdx, chunkData.blob).then(\n                        function(blockIdEntry) {\n                            if (!handler._getPersistableData(id).blockIdEntries) {\n                                handler._getPersistableData(id).blockIdEntries = [];\n                            }\n\n                            handler._getPersistableData(id).blockIdEntries.push(blockIdEntry);\n                            log(\"Put Block call succeeded for \" + id);\n                            promise.success({}, xhr);\n                        },\n                        function() {\n                            log(qq.format(\"Put Block call failed for ID {} on part {}\", id, chunkIdx), \"error\");\n                            handleFailure(xhr, promise);\n                        }\n                    );\n                },\n                promise.failure\n            );\n\n            return promise;\n        },\n\n        uploadFile: function(id) {\n            var promise = new qq.Promise(),\n                fileOrBlob = handler.getFile(id);\n\n            getSignedUrl(id).then(function(sasUri) {\n                var xhr = handler._createXhr(id);\n\n                handler._registerProgressHandler(id);\n\n                api.putBlob.upload(id, xhr, sasUri, fileOrBlob).then(\n                    function() {\n                        log(\"Put Blob call succeeded for \" + id);\n                        promise.success({}, xhr);\n                    },\n                    function() {\n                        log(\"Put Blob call failed for \" + id, \"error\");\n                        handleFailure(xhr, promise);\n                    }\n                );\n            },\n            promise.failure);\n\n            return promise;\n        }\n    });\n\n    qq.extend(this,\n        new qq.XhrUploadHandler({\n            options: qq.extend({namespace: \"azure\"}, spec),\n            proxy: qq.extend({getEndpoint: spec.endpointStore.get}, proxy)\n        }\n    ));\n\n    qq.override(this, function(super_) {\n        return {\n            expunge: function(id) {\n                var relatedToCancel = handler._wasCanceled(id),\n                    chunkingData = handler._getPersistableData(id),\n                    blockIdEntries = (chunkingData && chunkingData.blockIdEntries) || [];\n\n                if (relatedToCancel && blockIdEntries.length > 0) {\n                    deleteBlob(id);\n                }\n\n                super_.expunge(id);\n            },\n\n            finalizeChunks: function(id) {\n                return combineChunks(id);\n            },\n\n            _shouldChunkThisFile: function(id) {\n                var maybePossible = super_._shouldChunkThisFile(id);\n                return maybePossible && getSize(id) >= minFileSizeForChunking;\n            }\n        };\n    });\n};\n"
  },
  {
    "path": "client/js/azure/get-sas.js",
    "content": "/* globals qq */\n/**\n * Sends a GET request to the integrator's server, which should return a Shared Access Signature URI used to\n * make a specific request on a Blob via the Azure REST API.\n */\nqq.azure.GetSas = function(o) {\n    \"use strict\";\n\n    var requester,\n        options = {\n            cors: {\n                expected: false,\n                sendCredentials: false\n            },\n            customHeaders: {},\n            restRequestVerb: \"PUT\",\n            endpointStore: null,\n            log: function(str, level) {}\n        },\n        requestPromises = {};\n\n    qq.extend(options, o);\n\n    function sasResponseReceived(id, xhr, isError) {\n        var promise = requestPromises[id];\n\n        if (isError) {\n            promise.failure(\"Received response code \" + xhr.status, xhr);\n        }\n        else {\n            if (xhr.responseText.length) {\n                promise.success(xhr.responseText);\n            }\n            else {\n                promise.failure(\"Empty response.\", xhr);\n            }\n        }\n\n        delete requestPromises[id];\n    }\n\n    requester = qq.extend(this, new qq.AjaxRequester({\n        acceptHeader: \"application/json\",\n        validMethods: [\"GET\"],\n        method: \"GET\",\n        successfulResponseCodes: {\n            GET: [200]\n        },\n        contentType: null,\n        customHeaders: options.customHeaders,\n        endpointStore: options.endpointStore,\n        cors: options.cors,\n        log: options.log,\n        onComplete: sasResponseReceived\n    }));\n\n    qq.extend(this, {\n        request: function(id, blobUri) {\n            var requestPromise = new qq.Promise(),\n                restVerb = options.restRequestVerb;\n\n            options.log(qq.format(\"Submitting GET SAS request for a {} REST request related to file ID {}.\", restVerb, id));\n\n            requestPromises[id] = requestPromise;\n\n            requester.initTransport(id)\n                .withParams({\n                    bloburi: blobUri,\n                    _method: restVerb\n                })\n                .withCacheBuster()\n                .send();\n\n            return requestPromise;\n        }\n    });\n};\n"
  },
  {
    "path": "client/js/azure/jquery-plugin.js",
    "content": "/*globals jQuery*/\n/**\n * Simply an alias for the `fineUploader` plug-in wrapper, but hides the required `endpointType` option from the\n * integrator.  I thought it may be confusing to convey to the integrator that, when using Fine Uploader in Azure mode,\n * you need to specify an `endpointType` with a value of \"azure\", and perhaps an `uploaderType` with a value of \"basic\" if\n * you want to use basic mode when uploading directly to Azure as well.  So, you can use this plug-in alias and not worry\n * about the `endpointType` option at all.\n */\n(function($) {\n    \"use strict\";\n\n    $.fn.fineUploaderAzure = function(optionsOrCommand) {\n        if (typeof optionsOrCommand === \"object\") {\n\n            // This option is used to tell the plug-in wrapper to instantiate the appropriate Azure-namespace modules.\n            optionsOrCommand.endpointType = \"azure\";\n        }\n\n        return $.fn.fineUploader.apply(this, arguments);\n    };\n\n}(jQuery));\n"
  },
  {
    "path": "client/js/azure/rest/delete-blob.js",
    "content": "/* globals qq */\n/**\n * Implements the Delete Blob Azure REST API call.  http://msdn.microsoft.com/en-us/library/windowsazure/dd179413.aspx.\n */\nqq.azure.DeleteBlob = function(o) {\n    \"use strict\";\n\n    var requester,\n        method = \"DELETE\",\n        options = {\n            endpointStore: {},\n            onDelete: function(id) {},\n            onDeleteComplete: function(id, xhr, isError) {},\n            log: function(str, level) {}\n        };\n\n    qq.extend(options, o);\n\n    requester = qq.extend(this, new qq.AjaxRequester({\n        validMethods: [method],\n        method: method,\n        successfulResponseCodes: (function() {\n            var codes = {};\n            codes[method] = [202];\n            return codes;\n        }()),\n        contentType: null,\n        endpointStore: options.endpointStore,\n        allowXRequestedWithAndCacheControl: false,\n        cors: {\n            expected: true\n        },\n        log: options.log,\n        onSend: options.onDelete,\n        onComplete: options.onDeleteComplete\n    }));\n\n    qq.extend(this, {\n        method: method,\n        send: function(id) {\n            options.log(\"Submitting Delete Blob request for \" + id);\n\n            return requester.initTransport(id)\n                .send();\n        }\n    });\n};\n"
  },
  {
    "path": "client/js/azure/rest/put-blob.js",
    "content": "/* globals qq */\n/**\n * Implements the Put Blob Azure REST API call.  http://msdn.microsoft.com/en-us/library/windowsazure/dd179451.aspx.\n */\nqq.azure.PutBlob = function(o) {\n    \"use strict\";\n\n    var requester,\n        method = \"PUT\",\n        options = {\n            getBlobMetadata: function(id) {},\n            log: function(str, level) {}\n        },\n        endpoints = {},\n        promises = {},\n        endpointHandler = {\n            get: function(id) {\n                return endpoints[id];\n            }\n        };\n\n    qq.extend(options, o);\n\n    requester = qq.extend(this, new qq.AjaxRequester({\n        validMethods: [method],\n        method: method,\n        successfulResponseCodes: (function() {\n            var codes = {};\n            codes[method] = [201];\n            return codes;\n        }()),\n        contentType: null,\n        customHeaders: function(id) {\n            var params = options.getBlobMetadata(id),\n                headers = qq.azure.util.getParamsAsHeaders(params);\n\n            headers[\"x-ms-blob-type\"] = \"BlockBlob\";\n\n            return headers;\n        },\n        endpointStore: endpointHandler,\n        allowXRequestedWithAndCacheControl: false,\n        cors: {\n            expected: true\n        },\n        log: options.log,\n        onComplete: function(id, xhr, isError) {\n            var promise = promises[id];\n\n            delete endpoints[id];\n            delete promises[id];\n\n            if (isError) {\n                promise.failure();\n            }\n            else {\n                promise.success();\n            }\n        }\n    }));\n\n    qq.extend(this, {\n        method: method,\n        upload: function(id, xhr, url, file) {\n            var promise = new qq.Promise();\n\n            options.log(\"Submitting Put Blob request for \" + id);\n\n            promises[id] = promise;\n            endpoints[id] = url;\n\n            requester.initTransport(id)\n                .withPayload(file)\n                .withHeaders({\"Content-Type\": file.type})\n                .send(xhr);\n\n            return promise;\n        }\n    });\n};\n"
  },
  {
    "path": "client/js/azure/rest/put-block-list.js",
    "content": "/* globals qq */\n/**\n * Implements the Put Block List Azure REST API call.  http://msdn.microsoft.com/en-us/library/windowsazure/dd179467.aspx.\n */\nqq.azure.PutBlockList = function(o) {\n    \"use strict\";\n\n    var requester,\n        method = \"PUT\",\n        promises = {},\n        options = {\n            getBlobMetadata: function(id) {},\n            log: function(str, level) {}\n        },\n        endpoints = {},\n        endpointHandler = {\n            get: function(id) {\n                return endpoints[id];\n            }\n        };\n\n    qq.extend(options, o);\n\n    requester = qq.extend(this, new qq.AjaxRequester({\n        validMethods: [method],\n        method: method,\n        successfulResponseCodes: (function() {\n            var codes = {};\n            codes[method] = [201];\n            return codes;\n        }()),\n        customHeaders: function(id) {\n            var params = options.getBlobMetadata(id);\n\n            return qq.azure.util.getParamsAsHeaders(params);\n        },\n        contentType: \"text/plain\",\n        endpointStore: endpointHandler,\n        allowXRequestedWithAndCacheControl: false,\n        cors: {\n            expected: true\n        },\n        log: options.log,\n        onSend: function() {},\n        onComplete: function(id, xhr, isError) {\n            var promise = promises[id];\n\n            delete endpoints[id];\n            delete promises[id];\n\n            if (isError) {\n                promise.failure(xhr);\n            }\n            else {\n                promise.success(xhr);\n            }\n\n        }\n    }));\n\n    function createRequestBody(blockIdEntries) {\n        var doc = document.implementation.createDocument(null, \"BlockList\", null);\n\n        // If we don't sort the block ID entries by part number, the file will be combined incorrectly by Azure\n        blockIdEntries.sort(function(a, b) {\n            return a.part - b.part;\n        });\n\n        // Construct an XML document for each pair of etag/part values that correspond to part uploads.\n        qq.each(blockIdEntries, function(idx, blockIdEntry) {\n            var latestEl = doc.createElement(\"Latest\"),\n                latestTextEl = doc.createTextNode(blockIdEntry.id);\n\n            latestEl.appendChild(latestTextEl);\n            qq(doc).children()[0].appendChild(latestEl);\n        });\n\n        // Turn the resulting XML document into a string fit for transport.\n        return new XMLSerializer().serializeToString(doc);\n    }\n\n    qq.extend(this, {\n        method: method,\n        send: function(id, sasUri, blockIdEntries, fileMimeType, registerXhrCallback) {\n            var promise = new qq.Promise(),\n                blockIdsXml = createRequestBody(blockIdEntries),\n                xhr;\n\n            promises[id] = promise;\n\n            options.log(qq.format(\"Submitting Put Block List request for {}\", id));\n\n            endpoints[id] = qq.format(\"{}&comp=blocklist\", sasUri);\n\n            xhr = requester.initTransport(id)\n                .withPayload(blockIdsXml)\n                .withHeaders({\"x-ms-blob-content-type\": fileMimeType})\n                .send();\n            registerXhrCallback(xhr);\n\n            return promise;\n        }\n    });\n};\n"
  },
  {
    "path": "client/js/azure/rest/put-block.js",
    "content": "/* globals qq */\n/**\n * Implements the Put Block Azure REST API call.  http://msdn.microsoft.com/en-us/library/windowsazure/dd135726.aspx.\n */\nqq.azure.PutBlock = function(o) {\n    \"use strict\";\n\n    var requester,\n        method = \"PUT\",\n        blockIdEntries = {},\n        promises = {},\n        options = {\n            log: function(str, level) {}\n        },\n        endpoints = {},\n        endpointHandler = {\n            get: function(id) {\n                return endpoints[id];\n            }\n        };\n\n    qq.extend(options, o);\n\n    requester = qq.extend(this, new qq.AjaxRequester({\n        validMethods: [method],\n        method: method,\n        successfulResponseCodes: (function() {\n            var codes = {};\n            codes[method] = [201];\n            return codes;\n        }()),\n        contentType: null,\n        endpointStore: endpointHandler,\n        allowXRequestedWithAndCacheControl: false,\n        cors: {\n            expected: true\n        },\n        log: options.log,\n        onComplete: function(id, xhr, isError) {\n            var promise = promises[id],\n                blockIdEntry = blockIdEntries[id];\n\n            delete endpoints[id];\n            delete promises[id];\n            delete blockIdEntries[id];\n\n            if (isError) {\n                promise.failure();\n            }\n            else {\n                promise.success(blockIdEntry);\n            }\n        }\n    }));\n\n    function createBlockId(partNum) {\n        var digits = 5,\n            zeros = new Array(digits + 1).join(\"0\"),\n            paddedPartNum = (zeros + partNum).slice(-digits);\n\n        return btoa(paddedPartNum);\n    }\n\n    qq.extend(this, {\n        method: method,\n        upload: function(id, xhr, sasUri, partNum, blob) {\n            var promise = new qq.Promise(),\n                blockId = createBlockId(partNum);\n\n            promises[id] = promise;\n\n            options.log(qq.format(\"Submitting Put Block request for {} = part {}\", id, partNum));\n\n            endpoints[id] = qq.format(\"{}&comp=block&blockid={}\", sasUri, encodeURIComponent(blockId));\n            blockIdEntries[id] = {part: partNum, id: blockId};\n\n            requester.initTransport(id)\n                .withPayload(blob)\n                .send(xhr);\n\n            return promise;\n        }\n    });\n};\n"
  },
  {
    "path": "client/js/azure/uploader.basic.js",
    "content": "/*globals qq */\n/**\n * This defines FineUploaderBasic mode w/ support for uploading to Azure, which provides all the basic\n * functionality of Fine Uploader Basic as well as code to handle uploads directly to Azure.\n * Some inherited options and API methods have a special meaning in the context of the Azure uploader.\n */\n(function() {\n    \"use strict\";\n\n    qq.azure.FineUploaderBasic = function(o) {\n        if (!qq.supportedFeatures.ajaxUploading) {\n            throw new qq.Error(\"Uploading directly to Azure is not possible in this browser.\");\n        }\n\n        var options = {\n            signature: {\n                endpoint: null,\n\n                customHeaders: {}\n            },\n\n            // 'uuid', 'filename', or a function which may be promissory\n            blobProperties: {\n                name: \"uuid\"\n            },\n\n            uploadSuccess: {\n                endpoint: null,\n\n                method: \"POST\",\n\n                // In addition to the default params sent by Fine Uploader\n                params: {},\n\n                customHeaders: {}\n            },\n\n            chunking: {\n                // If this is increased, Azure may respond with a 413\n                partSize: 4000000,\n                // Don't chunk files less than this size\n                minFileSize: 4000001\n            }\n        };\n\n        // Replace any default options with user defined ones\n        qq.extend(options, o, true);\n\n        // Call base module\n        qq.FineUploaderBasic.call(this, options);\n\n        this._uploadSuccessParamsStore = this._createStore(this._options.uploadSuccess.params);\n        this._uploadSuccessEndpointStore = this._createStore(this._options.uploadSuccess.endpoint);\n\n        // This will hold callbacks for failed uploadSuccess requests that will be invoked on retry.\n        // Indexed by file ID.\n        this._failedSuccessRequestCallbacks = {};\n\n        // Holds blob names for file representations constructed from a session request.\n        this._cannedBlobNames = {};\n    };\n\n    // Inherit basic public & private API methods.\n    qq.extend(qq.azure.FineUploaderBasic.prototype, qq.basePublicApi);\n    qq.extend(qq.azure.FineUploaderBasic.prototype, qq.basePrivateApi);\n    qq.extend(qq.azure.FineUploaderBasic.prototype, qq.nonTraditionalBasePublicApi);\n    qq.extend(qq.azure.FineUploaderBasic.prototype, qq.nonTraditionalBasePrivateApi);\n\n    // Define public & private API methods for this module.\n    qq.extend(qq.azure.FineUploaderBasic.prototype, {\n        getBlobName: function(id) {\n            /* jshint eqnull:true */\n            if (this._cannedBlobNames[id] == null) {\n                return this._handler.getThirdPartyFileId(id);\n            }\n            return this._cannedBlobNames[id];\n        },\n\n        _getEndpointSpecificParams: function(id) {\n            return {\n                blob: this.getBlobName(id),\n                uuid: this.getUuid(id),\n                name: this.getName(id),\n                container: this._endpointStore.get(id)\n            };\n        },\n\n        _createUploadHandler: function() {\n            return qq.FineUploaderBasic.prototype._createUploadHandler.call(this,\n                {\n                    signature: this._options.signature,\n                    onGetBlobName: qq.bind(this._determineBlobName, this),\n                    deleteBlob: qq.bind(this._deleteBlob, this, true)\n                },\n                \"azure\");\n        },\n\n        _determineBlobName: function(id) {\n            var self = this,\n                blobNameOptionValue = this._options.blobProperties.name,\n                uuid = this.getUuid(id),\n                filename = this.getName(id),\n                fileExtension = qq.getExtension(filename),\n                blobNameToUse = uuid;\n\n            if (qq.isString(blobNameOptionValue)) {\n                switch (blobNameOptionValue) {\n                    case \"uuid\":\n                        if (fileExtension !== undefined) {\n                            blobNameToUse += \".\" + fileExtension;\n                        }\n                        return new qq.Promise().success(blobNameToUse);\n                    case \"filename\":\n                        return new qq.Promise().success(filename);\n                    default:\n                        return new qq.Promise.failure(\"Invalid blobName option value - \" + blobNameOptionValue);\n                }\n            }\n            else {\n                return blobNameOptionValue.call(this, id);\n            }\n        },\n\n        _addCannedFile: function(sessionData) {\n            var id;\n\n            /* jshint eqnull:true */\n            if (sessionData.blobName == null) {\n                throw new qq.Error(\"Did not find blob name property in server session response.  This is required!\");\n            }\n            else {\n                id = qq.FineUploaderBasic.prototype._addCannedFile.apply(this, arguments);\n                this._cannedBlobNames[id] = sessionData.blobName;\n            }\n\n            return id;\n        },\n\n        _deleteBlob: function(relatedToCancel, id) {\n            var self = this,\n                deleteBlobSasUri = {},\n                blobUriStore = {\n                    get: function(id) {\n                        return self._endpointStore.get(id) + \"/\" + self.getBlobName(id);\n                    }\n                },\n                deleteFileEndpointStore = {\n                    get: function(id) {\n                        return deleteBlobSasUri[id];\n                    }\n                },\n                getSasSuccess = function(id, sasUri) {\n                    deleteBlobSasUri[id] = sasUri;\n                    deleteBlob.send(id);\n                },\n                getSasFailure = function(id, reason, xhr) {\n                    if (relatedToCancel) {\n                        self.log(\"Will cancel upload, but cannot remove uncommitted parts from Azure due to issue retrieving SAS\", \"error\");\n                        qq.FineUploaderBasic.prototype._onCancel.call(self, id, self.getName(id));\n                    }\n                    else {\n                        self._onDeleteComplete(id, xhr, true);\n                        self._options.callbacks.onDeleteComplete(id, xhr, true);\n                    }\n                },\n                deleteBlob = new qq.azure.DeleteBlob({\n                    endpointStore: deleteFileEndpointStore,\n                    log: qq.bind(self.log, self),\n                    onDelete: function(id) {\n                        self._onDelete(id);\n                        self._options.callbacks.onDelete(id);\n                    },\n                    onDeleteComplete: function(id, xhrOrXdr, isError) {\n                        delete deleteBlobSasUri[id];\n\n                        if (isError) {\n                            if (relatedToCancel) {\n                                self.log(\"Will cancel upload, but failed to remove uncommitted parts from Azure.\", \"error\");\n                            }\n                            else {\n                                qq.azure.util.parseAzureError(xhrOrXdr.responseText, qq.bind(self.log, self));\n                            }\n                        }\n\n                        if (relatedToCancel) {\n                            qq.FineUploaderBasic.prototype._onCancel.call(self, id, self.getName(id));\n                            self.log(\"Deleted uncommitted blob chunks for \" + id);\n                        }\n                        else {\n                            self._onDeleteComplete(id, xhrOrXdr, isError);\n                            self._options.callbacks.onDeleteComplete(id, xhrOrXdr, isError);\n                        }\n                    }\n                }),\n                getSas = new qq.azure.GetSas({\n                    cors: this._options.cors,\n                    customHeaders: this._options.signature.customHeaders,\n                    endpointStore: {\n                        get: function() {\n                            return self._options.signature.endpoint;\n                        }\n                    },\n                    restRequestVerb: deleteBlob.method,\n                    log: qq.bind(self.log, self)\n                });\n\n            getSas.request(id, blobUriStore.get(id)).then(\n                qq.bind(getSasSuccess, self, id),\n                qq.bind(getSasFailure, self, id));\n        },\n\n        _createDeleteHandler: function() {\n            var self = this;\n\n            return {\n                sendDelete: function(id, uuid) {\n                    self._deleteBlob(false, id);\n                }\n            };\n        }\n    });\n}());\n"
  },
  {
    "path": "client/js/azure/uploader.js",
    "content": "/*globals qq */\n/**\n * This defines FineUploader mode w/ support for uploading to Azure, which provides all the basic\n * functionality of Fine Uploader as well as code to handle uploads directly to Azure.\n * This module inherits all logic from UI & core mode and adds some UI-related logic\n * specific to the upload-to-Azure workflow.  Some inherited options and API methods have a special meaning\n * in the context of the Azure uploader.\n */\n(function() {\n    \"use strict\";\n\n    qq.azure.FineUploader = function(o) {\n        var options = {\n            failedUploadTextDisplay: {\n                mode: \"custom\"\n            }\n        };\n\n        // Replace any default options with user defined ones\n        qq.extend(options, o, true);\n\n        // Inherit instance data from FineUploader, which should in turn inherit from azure.FineUploaderBasic.\n        qq.FineUploader.call(this, options, \"azure\");\n    };\n\n    // Inherit the API methods from FineUploaderBasicS3\n    qq.extend(qq.azure.FineUploader.prototype, qq.azure.FineUploaderBasic.prototype);\n\n    // Inherit public and private API methods related to UI\n    qq.extend(qq.azure.FineUploader.prototype, qq.uiPublicApi);\n    qq.extend(qq.azure.FineUploader.prototype, qq.uiPrivateApi);\n\n    // Define public & private API methods for this module.\n    qq.extend(qq.azure.FineUploader.prototype, {\n    });\n}());\n"
  },
  {
    "path": "client/js/azure/util.js",
    "content": "/*globals qq */\nqq.azure = qq.azure || {};\nqq.azure.util = qq.azure.util || (function() {\n    \"use strict\";\n\n    return {\n        AZURE_PARAM_PREFIX: \"x-ms-meta-\",\n\n        /** Test if a request header is actually a known Azure parameter. See: https://msdn.microsoft.com/en-us/library/azure/dd179451.aspx\n         *\n         * @param name Name of the Request Header parameter.\n         * @returns {Boolean} Test result.\n         */\n        _paramNameMatchesAzureParameter: function(name) {\n            switch (name) {\n                case \"Cache-Control\":\n                case \"Content-Disposition\":\n                case \"Content-Encoding\":\n                case \"Content-MD5\":\n                case \"x-ms-blob-content-encoding\":\n                case \"x-ms-blob-content-disposition\":\n                case \"x-ms-blob-content-md5\":\n                case \"x-ms-blob-cache-control\":\n                    return true;\n                default:\n                    return false;\n            }\n        },\n\n        /** Create Prefixed request headers which are appropriate for Azure.\n         *\n         * If the request header is appropriate for Azure (e.g. Cache-Control) then it should be\n         * passed along without a metadata prefix. For all other request header parameter names,\n         * qq.azure.util.AZURE_PARAM_PREFIX should be prepended.\n         *\n         * @param name Name of the Request Header parameter to construct a (possibly) prefixed name.\n         * @returns {String} A valid Request Header parameter name.\n         */\n        _getPrefixedParamName: function(name) {\n            if (qq.azure.util._paramNameMatchesAzureParameter(name)) {\n                return name;\n            }\n            else {\n                return qq.azure.util.AZURE_PARAM_PREFIX + name;\n            }\n        },\n\n        getParamsAsHeaders: function(params) {\n            var headers = {};\n\n            qq.each(params, function(name, val) {\n                var headerName = qq.azure.util._getPrefixedParamName(name),\n                    value = null;\n\n                if (qq.isFunction(val)) {\n                    value = String(val());\n                }\n                else if (qq.isObject(val)) {\n                    qq.extend(headers, qq.azure.util.getParamsAsHeaders(val));\n                }\n                else {\n                    value = String(val);\n                }\n\n                if (value !== null) {\n                    if (qq.azure.util._paramNameMatchesAzureParameter(name)) {\n                        headers[headerName] = value;\n                    } else {\n                        headers[headerName] = encodeURIComponent(value);\n                    }\n                }\n            });\n\n            return headers;\n        },\n\n        parseAzureError: function(responseText, log) {\n            var domParser = new DOMParser(),\n                responseDoc = domParser.parseFromString(responseText, \"application/xml\"),\n                errorTag = responseDoc.getElementsByTagName(\"Error\")[0],\n                errorDetails = {},\n                codeTag, messageTag;\n\n            log(\"Received error response: \" + responseText, \"error\");\n\n            if (errorTag) {\n                messageTag = errorTag.getElementsByTagName(\"Message\")[0];\n                if (messageTag) {\n                    errorDetails.message = messageTag.textContent;\n                }\n\n                codeTag = errorTag.getElementsByTagName(\"Code\")[0];\n                if (codeTag) {\n                    errorDetails.code = codeTag.textContent;\n                }\n\n                log(\"Parsed Azure error: \" + JSON.stringify(errorDetails), \"error\");\n\n                return errorDetails;\n            }\n        }\n    };\n}());\n"
  },
  {
    "path": "client/js/blob-proxy.js",
    "content": "/* globals qq */\n/**\n * Placeholder for a Blob that will be generated on-demand.\n *\n * @param referenceBlob Parent of the generated blob\n * @param onCreate Function to invoke when the blob must be created.  Must be promissory.\n * @constructor\n */\nqq.BlobProxy = function(referenceBlob, onCreate) {\n    \"use strict\";\n\n    qq.extend(this, {\n        referenceBlob: referenceBlob,\n\n        create: function() {\n            return onCreate(referenceBlob);\n        }\n    });\n};\n"
  },
  {
    "path": "client/js/button.js",
    "content": "/*globals qq*/\n\n/**\n * This module represents an upload or \"Select File(s)\" button.  It's job is to embed an opaque `<input type=\"file\">`\n * element as a child of a provided \"container\" element.  This \"container\" element (`options.element`) is used to provide\n * a custom style for the `<input type=\"file\">` element.  The ability to change the style of the container element is also\n * provided here by adding CSS classes to the container on hover/focus.\n *\n * TODO Eliminate the mouseover and mouseout event handlers since the :hover CSS pseudo-class should now be\n * available on all supported browsers.\n *\n * @param o Options to override the default values\n */\nqq.UploadButton = function(o) {\n    \"use strict\";\n\n    var self = this,\n\n        disposeSupport = new qq.DisposeSupport(),\n\n        options = {\n            // Corresponds to the `accept` attribute on the associated `<input type=\"file\">`\n            acceptFiles: null,\n\n            // \"Container\" element\n            element: null,\n\n            focusClass: \"qq-upload-button-focus\",\n\n            // A true value allows folders to be selected, if supported by the UA\n            folders: false,\n\n            // **This option will be removed** in the future as the :hover CSS pseudo-class is available on all supported browsers\n            hoverClass: \"qq-upload-button-hover\",\n\n            ios8BrowserCrashWorkaround: false,\n\n            // If true adds `multiple` attribute to `<input type=\"file\">`\n            multiple: false,\n\n            // `name` attribute of `<input type=\"file\">`\n            name: \"qqfile\",\n\n            // Called when the browser invokes the onchange handler on the `<input type=\"file\">`\n            onChange: function(input) {},\n\n            title: null\n        },\n        input, buttonId;\n\n    // Overrides any of the default option values with any option values passed in during construction.\n    qq.extend(options, o);\n\n    buttonId = qq.getUniqueId();\n\n    // Embed an opaque `<input type=\"file\">` element as a child of `options.element`.\n    function createInput() {\n        var input = document.createElement(\"input\");\n\n        input.setAttribute(qq.UploadButton.BUTTON_ID_ATTR_NAME, buttonId);\n        input.setAttribute(\"title\", options.title);\n\n        self.setMultiple(options.multiple, input);\n\n        if (options.folders && qq.supportedFeatures.folderSelection) {\n            // selecting directories is only possible in Chrome now, via a vendor-specific prefixed attribute\n            input.setAttribute(\"webkitdirectory\", \"\");\n        }\n\n        if (options.acceptFiles) {\n            input.setAttribute(\"accept\", options.acceptFiles);\n        }\n\n        input.setAttribute(\"type\", \"file\");\n        input.setAttribute(\"name\", options.name);\n\n        qq(input).css({\n            position: \"absolute\",\n            // in Opera only 'browse' button\n            // is clickable and it is located at\n            // the right side of the input\n            right: 0,\n            top: 0,\n            fontFamily: \"Arial\",\n            // It's especially important to make this an arbitrarily large value\n            // to ensure the rendered input button in IE takes up the entire\n            // space of the container element.  Otherwise, the left side of the\n            // button will require a double-click to invoke the file chooser.\n            // In other browsers, this might cause other issues, so a large font-size\n            // is only used in IE.  There is a bug in IE8 where the opacity style is  ignored\n            // in some cases when the font-size is large.  So, this workaround is not applied\n            // to IE8.\n            fontSize: qq.ie() && !qq.ie8() ? \"3500px\" : \"118px\",\n            margin: 0,\n            padding: 0,\n            cursor: \"pointer\",\n            opacity: 0\n        });\n\n        // Setting the file input's height to 100% in IE7 causes\n        // most of the visible button to be unclickable.\n        !qq.ie7() && qq(input).css({height: \"100%\"});\n\n        options.element.appendChild(input);\n\n        disposeSupport.attach(input, \"change\", function() {\n            options.onChange(input);\n        });\n\n        // **These event handlers will be removed** in the future as the :hover CSS pseudo-class is available on all supported browsers\n        disposeSupport.attach(input, \"mouseover\", function() {\n            qq(options.element).addClass(options.hoverClass);\n        });\n        disposeSupport.attach(input, \"mouseout\", function() {\n            qq(options.element).removeClass(options.hoverClass);\n        });\n\n        disposeSupport.attach(input, \"focus\", function() {\n            qq(options.element).addClass(options.focusClass);\n        });\n        disposeSupport.attach(input, \"blur\", function() {\n            qq(options.element).removeClass(options.focusClass);\n        });\n\n        return input;\n    }\n\n    // Make button suitable container for input\n    qq(options.element).css({\n        position: \"relative\",\n        overflow: \"hidden\",\n        // Make sure browse button is in the right side in Internet Explorer\n        direction: \"ltr\"\n    });\n\n    // Exposed API\n    qq.extend(this, {\n        getInput: function() {\n            return input;\n        },\n\n        getButtonId: function() {\n            return buttonId;\n        },\n\n        setMultiple: function(isMultiple, optInput) {\n            var input = optInput || this.getInput();\n\n            // Temporary workaround for bug in in iOS8 UIWebView that causes the browser to crash\n            // before the file chooser appears if the file input doesn't contain a multiple attribute.\n            // See #1283.\n            if (options.ios8BrowserCrashWorkaround && qq.ios8() && (qq.iosChrome() || qq.iosSafariWebView())) {\n                input.setAttribute(\"multiple\", \"\");\n            }\n\n            else {\n                if (isMultiple) {\n                    input.setAttribute(\"multiple\", \"\");\n                }\n                else {\n                    input.removeAttribute(\"multiple\");\n                }\n            }\n        },\n\n        setAcceptFiles: function(acceptFiles) {\n            if (acceptFiles !== options.acceptFiles) {\n                input.setAttribute(\"accept\", acceptFiles);\n            }\n        },\n\n        reset: function() {\n            if (input.parentNode) {\n                qq(input).remove();\n            }\n\n            qq(options.element).removeClass(options.focusClass);\n            input = null;\n            input = createInput();\n        }\n    });\n\n    input = createInput();\n};\n\nqq.UploadButton.BUTTON_ID_ATTR_NAME = \"qq-button-id\";\n"
  },
  {
    "path": "client/js/deletefile.ajax.requester.js",
    "content": "/*globals qq, XMLHttpRequest*/\nqq.DeleteFileAjaxRequester = function(o) {\n    \"use strict\";\n\n    var requester,\n        options = {\n            method: \"DELETE\",\n            uuidParamName: \"qquuid\",\n            endpointStore: {},\n            maxConnections: 3,\n            customHeaders: function(id) {return {};},\n            paramsStore: {},\n            cors: {\n                expected: false,\n                sendCredentials: false\n            },\n            log: function(str, level) {},\n            onDelete: function(id) {},\n            onDeleteComplete: function(id, xhrOrXdr, isError) {}\n        };\n\n    qq.extend(options, o);\n\n    function getMandatedParams() {\n        if (options.method.toUpperCase() === \"POST\") {\n            return {\n                _method: \"DELETE\"\n            };\n        }\n\n        return {};\n    }\n\n    requester = qq.extend(this, new qq.AjaxRequester({\n        acceptHeader: \"application/json\",\n        validMethods: [\"POST\", \"DELETE\"],\n        method: options.method,\n        endpointStore: options.endpointStore,\n        paramsStore: options.paramsStore,\n        mandatedParams: getMandatedParams(),\n        maxConnections: options.maxConnections,\n        customHeaders: function(id) {\n            return options.customHeaders.get(id);\n        },\n        log: options.log,\n        onSend: options.onDelete,\n        onComplete: options.onDeleteComplete,\n        cors: options.cors\n    }));\n\n    qq.extend(this, {\n        sendDelete: function(id, uuid, additionalMandatedParams) {\n            var additionalOptions = additionalMandatedParams || {};\n\n            options.log(\"Submitting delete file request for \" + id);\n\n            if (options.method === \"DELETE\") {\n                requester.initTransport(id)\n                    .withPath(uuid)\n                    .withParams(additionalOptions)\n                    .send();\n            }\n            else {\n                additionalOptions[options.uuidParamName] = uuid;\n                requester.initTransport(id)\n                    .withParams(additionalOptions)\n                    .send();\n            }\n        }\n    });\n};\n"
  },
  {
    "path": "client/js/dnd.js",
    "content": "/*globals qq, document, CustomEvent*/\nqq.DragAndDrop = function(o) {\n    \"use strict\";\n\n    var options,\n        HIDE_ZONES_EVENT_NAME = \"qq-hidezones\",\n        HIDE_BEFORE_ENTER_ATTR = \"qq-hide-dropzone\",\n        uploadDropZones = [],\n        droppedFiles = [],\n        disposeSupport = new qq.DisposeSupport();\n\n    options = {\n        dropZoneElements: [],\n        allowMultipleItems: true,\n        classes: {\n            dropActive: null\n        },\n        callbacks: new qq.DragAndDrop.callbacks()\n    };\n\n    qq.extend(options, o, true);\n\n    function uploadDroppedFiles(files, uploadDropZone) {\n        // We need to convert the `FileList` to an actual `Array` to avoid iteration issues\n        var filesAsArray = Array.prototype.slice.call(files);\n\n        options.callbacks.dropLog(\"Grabbed \" + files.length + \" dropped files.\");\n        uploadDropZone.dropDisabled(false);\n        options.callbacks.processingDroppedFilesComplete(filesAsArray, uploadDropZone.getElement());\n    }\n\n    function traverseFileTree(entry) {\n        var parseEntryPromise = new qq.Promise();\n\n        if (entry.isFile) {\n            entry.file(function(file) {\n                file.qqPath = extractDirectoryPath(entry);\n                droppedFiles.push(file);\n                parseEntryPromise.success();\n            },\n            function(fileError) {\n                options.callbacks.dropLog(\"Problem parsing '\" + entry.fullPath + \"'.  FileError code \" + fileError.code + \".\", \"error\");\n                parseEntryPromise.failure();\n            });\n        }\n        else if (entry.isDirectory) {\n            getFilesInDirectory(entry).then(\n                function allEntriesRead(entries) {\n                    var entriesLeft = entries.length;\n\n                    qq.each(entries, function(idx, entry) {\n                        traverseFileTree(entry).done(function() {\n                            entriesLeft -= 1;\n\n                            if (entriesLeft === 0) {\n                                parseEntryPromise.success();\n                            }\n                        });\n                    });\n\n                    if (!entries.length) {\n                        parseEntryPromise.success();\n                    }\n                },\n\n                function readFailure(fileError) {\n                    options.callbacks.dropLog(\"Problem parsing '\" + entry.fullPath + \"'.  FileError code \" + fileError.code + \".\", \"error\");\n                    parseEntryPromise.failure();\n                }\n            );\n        }\n\n        return parseEntryPromise;\n    }\n\n    function extractDirectoryPath(entry) {\n        var name = entry.name,\n            fullPath = entry.fullPath,\n            indexOfNameInFullPath = fullPath.lastIndexOf(name);\n\n        // remove file name from full path string\n        fullPath = fullPath.substr(0, indexOfNameInFullPath);\n\n        // remove leading slash in full path string\n        if (fullPath.charAt(0) === \"/\") {\n            fullPath = fullPath.substr(1);\n        }\n\n        return fullPath;\n    }\n\n    // Promissory.  Guaranteed to read all files in the root of the passed directory.\n    function getFilesInDirectory(entry, reader, accumEntries, existingPromise) {\n        var promise = existingPromise || new qq.Promise(),\n            dirReader = reader || entry.createReader();\n\n        dirReader.readEntries(\n            function readSuccess(entries) {\n                var newEntries = accumEntries ? accumEntries.concat(entries) : entries;\n\n                if (entries.length) {\n                    setTimeout(function() { // prevent stack overflow, however unlikely\n                        getFilesInDirectory(entry, dirReader, newEntries, promise);\n                    }, 0);\n                }\n                else {\n                    promise.success(newEntries);\n                }\n            },\n\n            promise.failure\n        );\n\n        return promise;\n    }\n\n    function handleDataTransfer(dataTransfer, uploadDropZone) {\n        var pendingFolderPromises = [],\n            handleDataTransferPromise = new qq.Promise();\n\n        options.callbacks.processingDroppedFiles();\n        uploadDropZone.dropDisabled(true);\n\n        if (dataTransfer.files.length > 1 && !options.allowMultipleItems) {\n            options.callbacks.processingDroppedFilesComplete([]);\n            options.callbacks.dropError(\"tooManyFilesError\", \"\");\n            uploadDropZone.dropDisabled(false);\n            handleDataTransferPromise.failure();\n        }\n        else {\n            droppedFiles = [];\n\n            if (qq.isFolderDropSupported(dataTransfer)) {\n                qq.each(dataTransfer.items, function(idx, item) {\n                    var entry = item.webkitGetAsEntry();\n\n                    if (entry) {\n                        //due to a bug in Chrome's File System API impl - #149735\n                        if (entry.isFile) {\n                            droppedFiles.push(item.getAsFile());\n                        }\n\n                        else {\n                            pendingFolderPromises.push(traverseFileTree(entry).done(function() {\n                                pendingFolderPromises.pop();\n                                if (pendingFolderPromises.length === 0) {\n                                    handleDataTransferPromise.success();\n                                }\n                            }));\n                        }\n                    }\n                });\n            }\n            else {\n                droppedFiles = dataTransfer.files;\n            }\n\n            if (pendingFolderPromises.length === 0) {\n                handleDataTransferPromise.success();\n            }\n        }\n\n        return handleDataTransferPromise;\n    }\n\n    function setupDropzone(dropArea) {\n        var dropZone = new qq.UploadDropZone({\n            HIDE_ZONES_EVENT_NAME: HIDE_ZONES_EVENT_NAME,\n            element: dropArea,\n            onEnter: function(e) {\n                qq(dropArea).addClass(options.classes.dropActive);\n                options.callbacks.dragEnter();\n                e.stopPropagation();\n            },\n            onLeaveNotDescendants: function(e) {\n                qq(dropArea).removeClass(options.classes.dropActive);\n                options.callbacks.dragLeave();\n            },\n            onDrop: function(e) {\n                handleDataTransfer(e.dataTransfer, dropZone).then(\n                    function() {\n                        uploadDroppedFiles(droppedFiles, dropZone);\n                    },\n                    function() {\n                        options.callbacks.dropLog(\"Drop event DataTransfer parsing failed.  No files will be uploaded.\", \"error\");\n                    }\n                );\n            }\n        });\n\n        disposeSupport.addDisposer(function() {\n            dropZone.dispose();\n        });\n\n        qq(dropArea).hasAttribute(HIDE_BEFORE_ENTER_ATTR) && qq(dropArea).hide();\n\n        uploadDropZones.push(dropZone);\n\n        return dropZone;\n    }\n\n    function isFileDrag(dragEvent) {\n        var fileDrag;\n\n        qq.each(dragEvent.dataTransfer.types, function(key, val) {\n            if (val === \"Files\") {\n                fileDrag = true;\n                return false;\n            }\n        });\n\n        return fileDrag;\n    }\n\n    // Attempt to determine when the file has left the document.  It is not always possible to detect this\n    // in all cases, but it is generally possible in all browsers, with a few exceptions.\n    //\n    // Exceptions:\n    // * IE10+ & Safari: We can't detect a file leaving the document if the Explorer window housing the file\n    //                   overlays the browser window.\n    // * IE10+: If the file is dragged out of the window too quickly, IE does not set the expected values of the\n    //          event's X & Y properties.\n    function leavingDocumentOut(e) {\n        if (qq.safari()) {\n            return e.x < 0 || e.y < 0;\n        }\n\n        return e.x === 0 && e.y === 0;\n    }\n\n    function setupDragDrop() {\n        var dropZones = options.dropZoneElements,\n\n            maybeHideDropZones = function() {\n                setTimeout(function() {\n                    qq.each(dropZones, function(idx, dropZone) {\n                        qq(dropZone).hasAttribute(HIDE_BEFORE_ENTER_ATTR) && qq(dropZone).hide();\n                        qq(dropZone).removeClass(options.classes.dropActive);\n                    });\n                }, 10);\n            };\n\n        qq.each(dropZones, function(idx, dropZone) {\n            var uploadDropZone = setupDropzone(dropZone);\n\n            // IE <= 9 does not support the File API used for drag+drop uploads\n            if (dropZones.length && qq.supportedFeatures.fileDrop) {\n                disposeSupport.attach(document, \"dragenter\", function(e) {\n                    if (!uploadDropZone.dropDisabled() && isFileDrag(e)) {\n                        qq.each(dropZones, function(idx, dropZone) {\n                            // We can't apply styles to non-HTMLElements, since they lack the `style` property.\n                            // Also, if the drop zone isn't initially hidden, let's not mess with `style.display`.\n                            if (dropZone instanceof HTMLElement &&\n                                qq(dropZone).hasAttribute(HIDE_BEFORE_ENTER_ATTR)) {\n\n                                qq(dropZone).css({display: \"block\"});\n                            }\n                        });\n                    }\n                });\n            }\n        });\n\n        disposeSupport.attach(document, \"dragleave\", function(e) {\n            if (leavingDocumentOut(e)) {\n                maybeHideDropZones();\n            }\n        });\n\n        // Just in case we were not able to detect when a dragged file has left the document,\n        // hide all relevant drop zones the next time the mouse enters the document.\n        // Note that mouse events such as this one are not fired during drag operations.\n        disposeSupport.attach(qq(document).children()[0], \"mouseenter\", function(e) {\n            maybeHideDropZones();\n        });\n\n        disposeSupport.attach(document, \"drop\", function(e) {\n            if (isFileDrag(e)) {\n                e.preventDefault();\n                maybeHideDropZones();\n            }\n        });\n\n        disposeSupport.attach(document, HIDE_ZONES_EVENT_NAME, maybeHideDropZones);\n    }\n\n    setupDragDrop();\n\n    qq.extend(this, {\n        setupExtraDropzone: function(element) {\n            options.dropZoneElements.push(element);\n            setupDropzone(element);\n        },\n\n        removeDropzone: function(element) {\n            var i,\n                dzs = options.dropZoneElements;\n\n            for (i in dzs) {\n                if (dzs[i] === element) {\n                    return dzs.splice(i, 1);\n                }\n            }\n        },\n\n        dispose: function() {\n            disposeSupport.dispose();\n            qq.each(uploadDropZones, function(idx, dropZone) {\n                dropZone.dispose();\n            });\n        }\n    });\n\n    this._testing = {};\n    this._testing.extractDirectoryPath = extractDirectoryPath;\n};\n\nqq.DragAndDrop.callbacks = function() {\n    \"use strict\";\n\n    return {\n        dragEnter: function () {},\n        dragLeave: function () {},\n        processingDroppedFiles: function() {},\n        processingDroppedFilesComplete: function(files, targetEl) {},\n        dropError: function(code, errorSpecifics) {\n            qq.log(\"Drag & drop error code '\" + code + \" with these specifics: '\" + errorSpecifics + \"'\", \"error\");\n        },\n        dropLog: function(message, level) {\n            qq.log(message, level);\n        }\n    };\n};\n\nqq.UploadDropZone = function(o) {\n    \"use strict\";\n\n    var disposeSupport = new qq.DisposeSupport(),\n        options, element, preventDrop, dropOutsideDisabled;\n\n    options = {\n        element: null,\n        onEnter: function(e) {},\n        onLeave: function(e) {},\n        // is not fired when leaving element by hovering descendants\n        onLeaveNotDescendants: function(e) {},\n        onDrop: function(e) {}\n    };\n\n    qq.extend(options, o);\n    element = options.element;\n\n    function dragoverShouldBeCanceled() {\n        return qq.safari() || (qq.firefox() && qq.windows());\n    }\n\n    function disableDropOutside(e) {\n        // run only once for all instances\n        if (!dropOutsideDisabled) {\n\n            // for these cases we need to catch onDrop to reset dropArea\n            if (dragoverShouldBeCanceled) {\n                disposeSupport.attach(document, \"dragover\", function(e) {\n                    e.preventDefault();\n                });\n            } else {\n                disposeSupport.attach(document, \"dragover\", function(e) {\n                    if (e.dataTransfer) {\n                        e.dataTransfer.dropEffect = \"none\";\n                        e.preventDefault();\n                    }\n                });\n            }\n\n            dropOutsideDisabled = true;\n        }\n    }\n\n    function isValidFileDrag(e) {\n        // e.dataTransfer currently causing IE errors\n        // IE9 does NOT support file API, so drag-and-drop is not possible\n        if (!qq.supportedFeatures.fileDrop) {\n            return false;\n        }\n\n        var effectTest, dt = e.dataTransfer,\n        // do not check dt.types.contains in webkit, because it crashes safari 4\n        isSafari = qq.safari();\n\n        // dt.effectAllowed is none in Safari 5\n\n        // dt.effectAllowed crashes IE 11 & 10 when files have been dragged from\n        // the filesystem\n        effectTest = qq.ie() && qq.supportedFeatures.fileDrop ? true : dt.effectAllowed !== \"none\";\n        return dt && effectTest &&\n                (\n                    (dt.files && dt.files.length) ||                                     // Valid for drop events with files\n                    (!isSafari && dt.types.contains && dt.types.contains(\"Files\")) ||  // Valid in Chrome/Firefox\n                    (dt.types.includes && dt.types.includes(\"Files\"))               // Valid in IE\n                );\n    }\n\n    function isOrSetDropDisabled(isDisabled) {\n        if (isDisabled !== undefined) {\n            preventDrop = isDisabled;\n        }\n        return preventDrop;\n    }\n\n    function triggerHidezonesEvent() {\n        var hideZonesEvent;\n\n        function triggerUsingOldApi() {\n            hideZonesEvent = document.createEvent(\"Event\");\n            hideZonesEvent.initEvent(options.HIDE_ZONES_EVENT_NAME, true, true);\n        }\n\n        if (window.CustomEvent) {\n            try {\n                hideZonesEvent = new CustomEvent(options.HIDE_ZONES_EVENT_NAME);\n            }\n            catch (err) {\n                triggerUsingOldApi();\n            }\n        }\n        else {\n            triggerUsingOldApi();\n        }\n\n        document.dispatchEvent(hideZonesEvent);\n    }\n\n    function attachEvents() {\n        disposeSupport.attach(element, \"dragover\", function(e) {\n            if (!isValidFileDrag(e)) {\n                return;\n            }\n\n            // dt.effectAllowed crashes IE 11 & 10 when files have been dragged from\n            // the filesystem\n            var effect = qq.ie() && qq.supportedFeatures.fileDrop ? null : e.dataTransfer.effectAllowed;\n            if (effect === \"move\" || effect === \"linkMove\") {\n                e.dataTransfer.dropEffect = \"move\"; // for FF (only move allowed)\n            } else {\n                e.dataTransfer.dropEffect = \"copy\"; // for Chrome\n            }\n\n            e.stopPropagation();\n            e.preventDefault();\n        });\n\n        disposeSupport.attach(element, \"dragenter\", function(e) {\n            if (!isOrSetDropDisabled()) {\n                if (!isValidFileDrag(e)) {\n                    return;\n                }\n                options.onEnter(e);\n            }\n        });\n\n        disposeSupport.attach(element, \"dragleave\", function(e) {\n            if (!isValidFileDrag(e)) {\n                return;\n            }\n\n            options.onLeave(e);\n\n            var relatedTarget = document.elementFromPoint(e.clientX, e.clientY);\n            // do not fire when moving a mouse over a descendant\n            if (qq(this).contains(relatedTarget)) {\n                return;\n            }\n\n            options.onLeaveNotDescendants(e);\n        });\n\n        disposeSupport.attach(element, \"drop\", function(e) {\n            if (!isOrSetDropDisabled()) {\n                if (!isValidFileDrag(e)) {\n                    return;\n                }\n\n                e.preventDefault();\n                e.stopPropagation();\n                options.onDrop(e);\n\n                triggerHidezonesEvent();\n            }\n        });\n    }\n\n    disableDropOutside();\n    attachEvents();\n\n    qq.extend(this, {\n        dropDisabled: function(isDisabled) {\n            return isOrSetDropDisabled(isDisabled);\n        },\n\n        dispose: function() {\n            disposeSupport.dispose();\n        },\n\n        getElement: function() {\n            return element;\n        }\n    });\n\n    this._testing = {};\n    this._testing.isValidFileDrag = isValidFileDrag;\n};\n"
  },
  {
    "path": "client/js/error/error.js",
    "content": "/* globals qq */\n/**\n * Fine Uploader top-level Error container.  Inherits from `Error`.\n */\n(function() {\n    \"use strict\";\n\n    qq.Error = function(message) {\n        this.message = \"[Fine Uploader \" + qq.version + \"] \" + message;\n    };\n\n    qq.Error.prototype = new Error();\n}());\n"
  },
  {
    "path": "client/js/export.js",
    "content": "/* globals define, module, global, qq */\n(function() {\n    \"use strict\";\n    if (typeof define === \"function\" && define.amd) {\n        define(function() {\n            return qq;\n        });\n    }\n    else if (typeof module !== \"undefined\" && module.exports) {\n        module.exports = qq;\n    }\n    else {\n        global.qq = qq;\n    }\n}());\n"
  },
  {
    "path": "client/js/features.js",
    "content": "/* globals qq */\nqq.supportedFeatures = (function() {\n    \"use strict\";\n\n    var supportsUploading,\n        supportsUploadingBlobs,\n        supportsFileDrop,\n        supportsAjaxFileUploading,\n        supportsFolderDrop,\n        supportsChunking,\n        supportsResume,\n        supportsUploadViaPaste,\n        supportsUploadCors,\n        supportsDeleteFileXdr,\n        supportsDeleteFileCorsXhr,\n        supportsDeleteFileCors,\n        supportsFolderSelection,\n        supportsImagePreviews,\n        supportsUploadProgress;\n\n    function testSupportsFileInputElement() {\n        var supported = true,\n            tempInput;\n\n        try {\n            tempInput = document.createElement(\"input\");\n            tempInput.type = \"file\";\n            qq(tempInput).hide();\n\n            if (tempInput.disabled) {\n                supported = false;\n            }\n        }\n        catch (ex) {\n            supported = false;\n        }\n\n        return supported;\n    }\n\n    //only way to test for complete Clipboard API support at this time\n    function isChrome14OrHigher() {\n        return (qq.chrome() || qq.opera()) &&\n            navigator.userAgent.match(/Chrome\\/[1][4-9]|Chrome\\/[2-9][0-9]/) !== undefined;\n    }\n\n    //Ensure we can send cross-origin `XMLHttpRequest`s\n    function isCrossOriginXhrSupported() {\n        if (window.XMLHttpRequest) {\n            var xhr = qq.createXhrInstance();\n\n            //Commonly accepted test for XHR CORS support.\n            return xhr.withCredentials !== undefined;\n        }\n\n        return false;\n    }\n\n    //Test for (terrible) cross-origin ajax transport fallback for IE9 and IE8\n    function isXdrSupported() {\n        return window.XDomainRequest !== undefined;\n    }\n\n    // CORS Ajax requests are supported if it is either possible to send credentialed `XMLHttpRequest`s,\n    // or if `XDomainRequest` is an available alternative.\n    function isCrossOriginAjaxSupported() {\n        if (isCrossOriginXhrSupported()) {\n            return true;\n        }\n\n        return isXdrSupported();\n    }\n\n    function isFolderSelectionSupported() {\n        // We know that folder selection is only supported in Chrome via this proprietary attribute for now\n        return document.createElement(\"input\").webkitdirectory !== undefined;\n    }\n\n    function isLocalStorageSupported() {\n        try {\n            return !!window.localStorage &&\n                // unpatched versions of IE10/11 have buggy impls of localStorage where setItem is a string\n                qq.isFunction(window.localStorage.setItem);\n        }\n        catch (error) {\n            // probably caught a security exception, so no localStorage for you\n            return false;\n        }\n    }\n\n    function isDragAndDropSupported() {\n        var span = document.createElement(\"span\");\n\n        return (\"draggable\" in span || (\"ondragstart\" in span && \"ondrop\" in span)) &&\n            !qq.android() && !qq.ios();\n    }\n\n    supportsUploading = testSupportsFileInputElement();\n\n    supportsAjaxFileUploading = supportsUploading && qq.isXhrUploadSupported();\n\n    supportsUploadingBlobs = supportsAjaxFileUploading && !qq.androidStock();\n\n    supportsFileDrop = supportsAjaxFileUploading && isDragAndDropSupported();\n\n    // adapted from https://stackoverflow.com/a/23278460/486979\n    supportsFolderDrop = supportsFileDrop && (function() {\n        var input = document.createElement(\"input\");\n\n        input.type = \"file\";\n        return !!(\"webkitdirectory\" in (input || document.querySelectorAll(\"input[type=file]\")[0]));\n    }());\n\n    supportsChunking = supportsAjaxFileUploading && qq.isFileChunkingSupported();\n\n    supportsResume = supportsAjaxFileUploading && supportsChunking && isLocalStorageSupported();\n\n    supportsUploadViaPaste = supportsAjaxFileUploading && isChrome14OrHigher();\n\n    supportsUploadCors = supportsUploading && (window.postMessage !== undefined || supportsAjaxFileUploading);\n\n    supportsDeleteFileCorsXhr = isCrossOriginXhrSupported();\n\n    supportsDeleteFileXdr = isXdrSupported();\n\n    supportsDeleteFileCors = isCrossOriginAjaxSupported();\n\n    supportsFolderSelection = isFolderSelectionSupported();\n\n    supportsImagePreviews = supportsAjaxFileUploading && window.FileReader !== undefined;\n\n    supportsUploadProgress = (function() {\n        if (supportsAjaxFileUploading) {\n            return !qq.androidStock() && !qq.iosChrome();\n        }\n        return false;\n    }());\n\n    return {\n        ajaxUploading: supportsAjaxFileUploading,\n        blobUploading: supportsUploadingBlobs,\n        canDetermineSize: supportsAjaxFileUploading,\n        chunking: supportsChunking,\n        deleteFileCors: supportsDeleteFileCors,\n        deleteFileCorsXdr: supportsDeleteFileXdr, //NOTE: will also return true in IE10, where XDR is also supported\n        deleteFileCorsXhr: supportsDeleteFileCorsXhr,\n        dialogElement: !!window.HTMLDialogElement,\n        fileDrop: supportsFileDrop,\n        folderDrop: supportsFolderDrop,\n        folderSelection: supportsFolderSelection,\n        imagePreviews: supportsImagePreviews,\n        imageValidation: supportsImagePreviews,\n        itemSizeValidation: supportsAjaxFileUploading,\n        pause: supportsChunking,\n        progressBar: supportsUploadProgress,\n        resume: supportsResume,\n        scaling: supportsImagePreviews && supportsUploadingBlobs,\n        tiffPreviews: qq.safari(), // Not the best solution, but simple and probably accurate enough (for now)\n        unlimitedScaledImageSize: !qq.ios(), // false simply indicates that there is some known limit\n        uploading: supportsUploading,\n        uploadCors: supportsUploadCors,\n        uploadCustomHeaders: supportsAjaxFileUploading,\n        uploadNonMultipart: supportsAjaxFileUploading,\n        uploadViaPaste: supportsUploadViaPaste\n    };\n\n}());\n"
  },
  {
    "path": "client/js/form-support.js",
    "content": "/* globals qq */\n/**\n * Module that handles support for existing forms.\n *\n * @param options Options passed from the integrator-supplied options related to form support.\n * @param startUpload Callback to invoke when files \"stored\" should be uploaded.\n * @param log Proxy for the logger\n * @constructor\n */\nqq.FormSupport = function(options, startUpload, log) {\n    \"use strict\";\n    var self  = this,\n        interceptSubmit = options.interceptSubmit,\n        formEl = options.element,\n        autoUpload = options.autoUpload;\n\n    // Available on the public API associated with this module.\n    qq.extend(this, {\n        // To be used by the caller to determine if the endpoint will be determined by some processing\n        // that occurs in this module, such as if the form has an action attribute.\n        // Ignore if `attachToForm === false`.\n        newEndpoint: null,\n\n        // To be used by the caller to determine if auto uploading should be allowed.\n        // Ignore if `attachToForm === false`.\n        newAutoUpload: autoUpload,\n\n        // true if a form was detected and is being tracked by this module\n        attachedToForm: false,\n\n        // Returns an object with names and values for all valid form elements associated with the attached form.\n        getFormInputsAsObject: function() {\n            /* jshint eqnull:true */\n            if (formEl == null) {\n                return null;\n            }\n\n            return self._form2Obj(formEl);\n        }\n    });\n\n    // If the form contains an action attribute, this should be the new upload endpoint.\n    function determineNewEndpoint(formEl) {\n        if (formEl.getAttribute(\"action\")) {\n            self.newEndpoint = formEl.getAttribute(\"action\");\n        }\n    }\n\n    // Return true only if the form is valid, or if we cannot make this determination.\n    // If the form is invalid, ensure invalid field(s) are highlighted in the UI.\n    function validateForm(formEl, nativeSubmit) {\n        if (formEl.checkValidity && !formEl.checkValidity()) {\n            log(\"Form did not pass validation checks - will not upload.\", \"error\");\n            nativeSubmit();\n        }\n        else {\n            return true;\n        }\n    }\n\n    // Intercept form submit attempts, unless the integrator has told us not to do this.\n    function maybeUploadOnSubmit(formEl) {\n        var nativeSubmit = formEl.submit;\n\n        // Intercept and squelch submit events.\n        qq(formEl).attach(\"submit\", function(event) {\n            event = event || window.event;\n\n            if (event.preventDefault) {\n                event.preventDefault();\n            }\n            else {\n                event.returnValue = false;\n            }\n\n            validateForm(formEl, nativeSubmit) && startUpload();\n        });\n\n        // The form's `submit()` function may be called instead (i.e. via jQuery.submit()).\n        // Intercept that too.\n        formEl.submit = function() {\n            validateForm(formEl, nativeSubmit) && startUpload();\n        };\n    }\n\n    // If the element value passed from the uploader is a string, assume it is an element ID - select it.\n    // The rest of the code in this module depends on this being an HTMLElement.\n    function determineFormEl(formEl) {\n        if (formEl) {\n            if (qq.isString(formEl)) {\n                formEl = document.getElementById(formEl);\n            }\n\n            if (formEl) {\n                log(\"Attaching to form element.\");\n                determineNewEndpoint(formEl);\n                interceptSubmit && maybeUploadOnSubmit(formEl);\n            }\n        }\n\n        return formEl;\n    }\n\n    formEl = determineFormEl(formEl);\n    this.attachedToForm = !!formEl;\n};\n\nqq.extend(qq.FormSupport.prototype, {\n    // Converts all relevant form fields to key/value pairs.  This is meant to mimic the data a browser will\n    // construct from a given form when the form is submitted.\n    _form2Obj: function(form) {\n        \"use strict\";\n        var obj = {},\n            notIrrelevantType = function(type) {\n                var irrelevantTypes = [\n                    \"button\",\n                    \"image\",\n                    \"reset\",\n                    \"submit\"\n                ];\n\n                return qq.indexOf(irrelevantTypes, type.toLowerCase()) < 0;\n            },\n            radioOrCheckbox = function(type) {\n                return qq.indexOf([\"checkbox\", \"radio\"], type.toLowerCase()) >= 0;\n            },\n            ignoreValue = function(el) {\n                if (radioOrCheckbox(el.type) && !el.checked) {\n                    return true;\n                }\n\n                return el.disabled && el.type.toLowerCase() !== \"hidden\";\n            },\n            selectValue = function(select) {\n                var value = null;\n\n                qq.each(qq(select).children(), function(idx, child) {\n                    if (child.tagName.toLowerCase() === \"option\" && child.selected) {\n                        value = child.value;\n                        return false;\n                    }\n                });\n\n                return value;\n            };\n\n        qq.each(form.elements, function(idx, el) {\n            if ((qq.isInput(el, true) || el.tagName.toLowerCase() === \"textarea\") &&\n                notIrrelevantType(el.type) &&\n                !ignoreValue(el)) {\n\n                obj[el.name] = el.value;\n            }\n            else if (el.tagName.toLowerCase() === \"select\" && !ignoreValue(el)) {\n                var value = selectValue(el);\n\n                if (value !== null) {\n                    obj[el.name] = value;\n                }\n            }\n        });\n\n        return obj;\n    }\n});\n"
  },
  {
    "path": "client/js/identify.js",
    "content": "/*globals qq */\nqq.Identify = function(fileOrBlob, log) {\n    \"use strict\";\n\n    function isIdentifiable(magicBytes, questionableBytes) {\n        var identifiable = false,\n            magicBytesEntries = [].concat(magicBytes);\n\n        qq.each(magicBytesEntries, function(idx, magicBytesArrayEntry) {\n            if (questionableBytes.indexOf(magicBytesArrayEntry) === 0) {\n                identifiable = true;\n                return false;\n            }\n        });\n\n        return identifiable;\n    }\n\n    qq.extend(this, {\n        /**\n         * Determines if a Blob can be displayed natively in the current browser.  This is done by reading magic\n         * bytes in the beginning of the file, so this is an asynchronous operation.  Before we attempt to read the\n         * file, we will examine the blob's type attribute to save CPU cycles.\n         *\n         * @returns {qq.Promise} Promise that is fulfilled when identification is complete.\n         * If successful, the MIME string is passed to the success handler.\n         */\n        isPreviewable: function() {\n            var self = this,\n                identifier = new qq.Promise(),\n                previewable = false,\n                name = fileOrBlob.name === undefined ? \"blob\" : fileOrBlob.name;\n\n            log(qq.format(\"Attempting to determine if {} can be rendered in this browser\", name));\n\n            log(\"First pass: check type attribute of blob object.\");\n\n            if (this.isPreviewableSync()) {\n                log(\"Second pass: check for magic bytes in file header.\");\n\n                qq.readBlobToHex(fileOrBlob, 0, 4).then(function(hex) {\n                    qq.each(self.PREVIEWABLE_MIME_TYPES, function(mime, bytes) {\n                        if (isIdentifiable(bytes, hex)) {\n                            // Safari is the only supported browser that can deal with TIFFs natively,\n                            // so, if this is a TIFF and the UA isn't Safari, declare this file \"non-previewable\".\n                            if (mime !== \"image/tiff\" || qq.supportedFeatures.tiffPreviews) {\n                                previewable = true;\n                                identifier.success(mime);\n                            }\n\n                            return false;\n                        }\n                    });\n\n                    log(qq.format(\"'{}' is {} able to be rendered in this browser\", name, previewable ? \"\" : \"NOT\"));\n\n                    if (!previewable) {\n                        identifier.failure();\n                    }\n                },\n                function() {\n                    log(\"Error reading file w/ name '\" + name + \"'.  Not able to be rendered in this browser.\");\n                    identifier.failure();\n                });\n            }\n            else {\n                identifier.failure();\n            }\n\n            return identifier;\n        },\n\n        /**\n         * Determines if a Blob can be displayed natively in the current browser.  This is done by checking the\n         * blob's type attribute.  This is a synchronous operation, useful for situations where an asynchronous operation\n         * would be challenging to support.  Note that the blob's type property is not as accurate as reading the\n         * file's magic bytes.\n         *\n         * @returns {Boolean} true if the blob can be rendered in the current browser\n         */\n        isPreviewableSync: function() {\n            var fileMime = fileOrBlob.type,\n                // Assumption: This will only ever be executed in browsers that support `Object.keys`.\n                isRecognizedImage = qq.indexOf(Object.keys(this.PREVIEWABLE_MIME_TYPES), fileMime) >= 0,\n                previewable = false,\n                name = fileOrBlob.name === undefined ? \"blob\" : fileOrBlob.name;\n\n            if (isRecognizedImage) {\n                if (fileMime === \"image/tiff\") {\n                    previewable = qq.supportedFeatures.tiffPreviews;\n                }\n                else {\n                    previewable = true;\n                }\n            }\n\n            !previewable && log(name + \" is not previewable in this browser per the blob's type attr\");\n\n            return previewable;\n        }\n    });\n};\n\nqq.Identify.prototype.PREVIEWABLE_MIME_TYPES = {\n    \"image/jpeg\": \"ffd8ff\",\n    \"image/gif\": \"474946\",\n    \"image/png\": \"89504e\",\n    \"image/bmp\": \"424d\",\n    \"image/tiff\": [\"49492a00\", \"4d4d002a\"]\n};\n"
  },
  {
    "path": "client/js/iframe.xss.response.js",
    "content": "(function() {\n    \"use strict\";\n    var match = /(\\{.*\\})/.exec(document.body.innerHTML);\n    if (match) {\n        parent.postMessage(match[1], \"*\");\n    }\n}());\n"
  },
  {
    "path": "client/js/image-support/exif.js",
    "content": "/*globals qq */\n/**\n * EXIF image data parser.  Currently only parses the Orientation tag value,\n * but this may be expanded to other tags in the future.\n *\n * @param fileOrBlob Attempt to parse EXIF data in this `Blob`\n * @constructor\n */\nqq.Exif = function(fileOrBlob, log) {\n    \"use strict\";\n\n    // Orientation is the only tag parsed here at this time.\n    var TAG_IDS = [274],\n        TAG_INFO = {\n            274: {\n                name: \"Orientation\",\n                bytes: 2\n            }\n        };\n\n    // Convert a little endian (hex string) to big endian (decimal).\n    function parseLittleEndian(hex) {\n        var result = 0,\n            pow = 0;\n\n        while (hex.length > 0) {\n            result += parseInt(hex.substring(0, 2), 16) * Math.pow(2, pow);\n            hex = hex.substring(2, hex.length);\n            pow += 8;\n        }\n\n        return result;\n    }\n\n    // Find the byte offset, of Application Segment 1 (EXIF).\n    // External callers need not supply any arguments.\n    function seekToApp1(offset, promise) {\n        var theOffset = offset,\n            thePromise = promise;\n        if (theOffset === undefined) {\n            theOffset = 2;\n            thePromise = new qq.Promise();\n        }\n\n        qq.readBlobToHex(fileOrBlob, theOffset, 4).then(function(hex) {\n            var match = /^ffe([0-9])/.exec(hex),\n                segmentLength;\n\n            if (match) {\n                if (match[1] !== \"1\") {\n                    segmentLength = parseInt(hex.slice(4, 8), 16);\n                    seekToApp1(theOffset + segmentLength + 2, thePromise);\n                }\n                else {\n                    thePromise.success(theOffset);\n                }\n            }\n            else {\n                thePromise.failure(\"No EXIF header to be found!\");\n            }\n        });\n\n        return thePromise;\n    }\n\n    // Find the byte offset of Application Segment 1 (EXIF) for valid JPEGs only.\n    function getApp1Offset() {\n        var promise = new qq.Promise();\n\n        qq.readBlobToHex(fileOrBlob, 0, 6).then(function(hex) {\n            if (hex.indexOf(\"ffd8\") !== 0) {\n                promise.failure(\"Not a valid JPEG!\");\n            }\n            else {\n                seekToApp1().then(function(offset) {\n                    promise.success(offset);\n                },\n                function(error) {\n                    promise.failure(error);\n                });\n            }\n        });\n\n        return promise;\n    }\n\n    // Determine the byte ordering of the EXIF header.\n    function isLittleEndian(app1Start) {\n        var promise = new qq.Promise();\n\n        qq.readBlobToHex(fileOrBlob, app1Start + 10, 2).then(function(hex) {\n            promise.success(hex === \"4949\");\n        });\n\n        return promise;\n    }\n\n    // Determine the number of directory entries in the EXIF header.\n    function getDirEntryCount(app1Start, littleEndian) {\n        var promise = new qq.Promise();\n\n        qq.readBlobToHex(fileOrBlob, app1Start + 18, 2).then(function(hex) {\n            if (littleEndian) {\n                return promise.success(parseLittleEndian(hex));\n            }\n            else {\n                promise.success(parseInt(hex, 16));\n            }\n        });\n\n        return promise;\n    }\n\n    // Get the IFD portion of the EXIF header as a hex string.\n    function getIfd(app1Start, dirEntries) {\n        var offset = app1Start + 20,\n            bytes = dirEntries * 12;\n\n        return qq.readBlobToHex(fileOrBlob, offset, bytes);\n    }\n\n    // Obtain an array of all directory entries (as hex strings) in the EXIF header.\n    function getDirEntries(ifdHex) {\n        var entries = [],\n            offset = 0;\n\n        while (offset + 24 <= ifdHex.length) {\n            entries.push(ifdHex.slice(offset, offset + 24));\n            offset += 24;\n        }\n\n        return entries;\n    }\n\n    // Obtain values for all relevant tags and return them.\n    function getTagValues(littleEndian, dirEntries) {\n        var TAG_VAL_OFFSET = 16,\n            tagsToFind = qq.extend([], TAG_IDS),\n            vals = {};\n\n        qq.each(dirEntries, function(idx, entry) {\n            var idHex = entry.slice(0, 4),\n                id = littleEndian ? parseLittleEndian(idHex) : parseInt(idHex, 16),\n                tagsToFindIdx = tagsToFind.indexOf(id),\n                tagValHex, tagName, tagValLength;\n\n            if (tagsToFindIdx >= 0) {\n                tagName = TAG_INFO[id].name;\n                tagValLength = TAG_INFO[id].bytes;\n                tagValHex = entry.slice(TAG_VAL_OFFSET, TAG_VAL_OFFSET + (tagValLength * 2));\n                vals[tagName] = littleEndian ? parseLittleEndian(tagValHex) : parseInt(tagValHex, 16);\n\n                tagsToFind.splice(tagsToFindIdx, 1);\n            }\n\n            if (tagsToFind.length === 0) {\n                return false;\n            }\n        });\n\n        return vals;\n    }\n\n    qq.extend(this, {\n        /**\n         * Attempt to parse the EXIF header for the `Blob` associated with this instance.\n         *\n         * @returns {qq.Promise} To be fulfilled when the parsing is complete.\n         * If successful, the parsed EXIF header as an object will be included.\n         */\n        parse: function() {\n            var parser = new qq.Promise(),\n                onParseFailure = function(message) {\n                    log(qq.format(\"EXIF header parse failed: '{}' \", message));\n                    parser.failure(message);\n                };\n\n            getApp1Offset().then(function(app1Offset) {\n                log(qq.format(\"Moving forward with EXIF header parsing for '{}'\", fileOrBlob.name === undefined ? \"blob\" : fileOrBlob.name));\n\n                isLittleEndian(app1Offset).then(function(littleEndian) {\n\n                    log(qq.format(\"EXIF Byte order is {} endian\", littleEndian ? \"little\" : \"big\"));\n\n                    getDirEntryCount(app1Offset, littleEndian).then(function(dirEntryCount) {\n\n                        log(qq.format(\"Found {} APP1 directory entries\", dirEntryCount));\n\n                        getIfd(app1Offset, dirEntryCount).then(function(ifdHex) {\n                            var dirEntries = getDirEntries(ifdHex),\n                                tagValues = getTagValues(littleEndian, dirEntries);\n\n                            log(\"Successfully parsed some EXIF tags\");\n\n                            parser.success(tagValues);\n                        }, onParseFailure);\n                    }, onParseFailure);\n                }, onParseFailure);\n            }, onParseFailure);\n\n            return parser;\n        }\n    });\n\n    /*<testing>*/\n    this._testing = {};\n    this._testing.parseLittleEndian = parseLittleEndian;\n    /*</testing>*/\n};\n"
  },
  {
    "path": "client/js/image-support/image.js",
    "content": "/*globals qq */\n/**\n * Draws a thumbnail of a Blob/File/URL onto an <img> or <canvas>.\n *\n * @constructor\n */\nqq.ImageGenerator = function(log) {\n    \"use strict\";\n\n    function isImg(el) {\n        return el.tagName.toLowerCase() === \"img\";\n    }\n\n    function isCanvas(el) {\n        return el.tagName.toLowerCase() === \"canvas\";\n    }\n\n    function isImgCorsSupported() {\n        return new Image().crossOrigin !== undefined;\n    }\n\n    function isCanvasSupported() {\n        var canvas = document.createElement(\"canvas\");\n\n        return canvas.getContext && canvas.getContext(\"2d\");\n    }\n\n    // This is only meant to determine the MIME type of a renderable image file.\n    // It is used to ensure images drawn from a URL that have transparent backgrounds\n    // are rendered correctly, among other things.\n    function determineMimeOfFileName(nameWithPath) {\n        /*jshint -W015 */\n        var pathSegments = nameWithPath.split(\"/\"),\n            name = pathSegments[pathSegments.length - 1].split(\"?\")[0],\n            extension = qq.getExtension(name);\n\n        extension = extension && extension.toLowerCase();\n\n        switch (extension) {\n            case \"jpeg\":\n            case \"jpg\":\n                return \"image/jpeg\";\n            case \"png\":\n                return \"image/png\";\n            case \"bmp\":\n                return \"image/bmp\";\n            case \"gif\":\n                return \"image/gif\";\n            case \"tiff\":\n            case \"tif\":\n                return \"image/tiff\";\n        }\n    }\n\n    // This will likely not work correctly in IE8 and older.\n    // It's only used as part of a formula to determine\n    // if a canvas can be used to scale a server-hosted thumbnail.\n    // If canvas isn't supported by the UA (IE8 and older)\n    // this method should not even be called.\n    function isCrossOrigin(url) {\n        var targetAnchor = document.createElement(\"a\"),\n            targetProtocol, targetHostname, targetPort;\n\n        targetAnchor.href = url;\n\n        targetProtocol = targetAnchor.protocol;\n        targetPort = targetAnchor.port;\n        targetHostname = targetAnchor.hostname;\n\n        if (targetProtocol.toLowerCase() !== window.location.protocol.toLowerCase()) {\n            return true;\n        }\n\n        if (targetHostname.toLowerCase() !== window.location.hostname.toLowerCase()) {\n            return true;\n        }\n\n        // IE doesn't take ports into consideration when determining if two endpoints are same origin.\n        if (targetPort !== window.location.port && !qq.ie()) {\n            return true;\n        }\n\n        return false;\n    }\n\n    function registerImgLoadListeners(img, promise) {\n        img.onload = function() {\n            img.onload = null;\n            img.onerror = null;\n            promise.success(img);\n        };\n\n        img.onerror = function() {\n            img.onload = null;\n            img.onerror = null;\n            log(\"Problem drawing thumbnail!\", \"error\");\n            promise.failure(img, \"Problem drawing thumbnail!\");\n        };\n    }\n\n    function registerCanvasDrawImageListener(canvas, promise) {\n        // The image is drawn on the canvas by a third-party library,\n        // and we want to know when this is completed.  Since the library\n        // may invoke drawImage many times in a loop, we need to be called\n        // back when the image is fully rendered.  So, we are expecting the\n        // code that draws this image to follow a convention that involves a\n        // function attached to the canvas instance be invoked when it is done.\n        canvas.qqImageRendered = function() {\n            promise.success(canvas);\n        };\n    }\n\n    // Fulfills a `qq.Promise` when an image has been drawn onto the target,\n    // whether that is a <canvas> or an <img>.  The attempt is considered a\n    // failure if the target is not an <img> or a <canvas>, or if the drawing\n    // attempt was not successful.\n    function registerThumbnailRenderedListener(imgOrCanvas, promise) {\n        var registered = isImg(imgOrCanvas) || isCanvas(imgOrCanvas);\n\n        if (isImg(imgOrCanvas)) {\n            registerImgLoadListeners(imgOrCanvas, promise);\n        }\n        else if (isCanvas(imgOrCanvas)) {\n            registerCanvasDrawImageListener(imgOrCanvas, promise);\n        }\n        else {\n            promise.failure(imgOrCanvas);\n            log(qq.format(\"Element container of type {} is not supported!\", imgOrCanvas.tagName), \"error\");\n        }\n\n        return registered;\n    }\n\n    // Draw a preview iff the current UA can natively display it.\n    // Also rotate the image if necessary.\n    function draw(fileOrBlob, container, options) {\n        var drawPreview = new qq.Promise(),\n            identifier = new qq.Identify(fileOrBlob, log),\n            maxSize = options.maxSize,\n            // jshint eqnull:true\n            orient = options.orient == null ? true : options.orient,\n            megapixErrorHandler = function() {\n                container.onerror = null;\n                container.onload = null;\n                log(\"Could not render preview, file may be too large!\", \"error\");\n                drawPreview.failure(container, \"Browser cannot render image!\");\n            };\n\n        identifier.isPreviewable().then(\n            function(mime) {\n                // If options explicitly specify that Orientation is not desired,\n                // replace the orient task with a dummy promise that \"succeeds\" immediately.\n                var dummyExif = {\n                        parse: function() {\n                            return new qq.Promise().success();\n                        }\n                    },\n                    exif = orient ? new qq.Exif(fileOrBlob, log) : dummyExif,\n                    mpImg = new qq.MegaPixImage(fileOrBlob, megapixErrorHandler);\n\n                if (registerThumbnailRenderedListener(container, drawPreview)) {\n                    exif.parse().then(\n                        function(exif) {\n                            var orientation = exif && exif.Orientation;\n\n                            mpImg.render(container, {\n                                maxWidth: maxSize,\n                                maxHeight: maxSize,\n                                orientation: orientation,\n                                mime: mime,\n                                resize: options.customResizeFunction\n                            });\n                        },\n\n                        function(failureMsg) {\n                            log(qq.format(\"EXIF data could not be parsed ({}).  Assuming orientation = 1.\", failureMsg));\n\n                            mpImg.render(container, {\n                                maxWidth: maxSize,\n                                maxHeight: maxSize,\n                                mime: mime,\n                                resize: options.customResizeFunction\n                            });\n                        }\n                    );\n                }\n            },\n\n            function() {\n                log(\"Not previewable\");\n                drawPreview.failure(container, \"Not previewable\");\n            }\n        );\n\n        return drawPreview;\n    }\n\n    function drawOnCanvasOrImgFromUrl(url, canvasOrImg, draw, maxSize, customResizeFunction) {\n        var tempImg = new Image(),\n            tempImgRender = new qq.Promise();\n\n        registerThumbnailRenderedListener(tempImg, tempImgRender);\n\n        if (isCrossOrigin(url)) {\n            tempImg.crossOrigin = \"anonymous\";\n        }\n\n        tempImg.src = url;\n\n        tempImgRender.then(\n            function rendered() {\n                registerThumbnailRenderedListener(canvasOrImg, draw);\n\n                var mpImg = new qq.MegaPixImage(tempImg);\n                mpImg.render(canvasOrImg, {\n                    maxWidth: maxSize,\n                    maxHeight: maxSize,\n                    mime: determineMimeOfFileName(url),\n                    resize: customResizeFunction\n                });\n            },\n\n            draw.failure\n        );\n    }\n\n    function drawOnImgFromUrlWithCssScaling(url, img, draw, maxSize) {\n        registerThumbnailRenderedListener(img, draw);\n        // NOTE: The fact that maxWidth/height is set on the thumbnail for scaled images\n        // that must drop back to CSS is known and exploited by the templating module.\n        // In this module, we pre-render \"waiting\" thumbs for all files immediately after they\n        // are submitted, and we must be sure to pass any style associated with the \"waiting\" preview.\n        qq(img).css({\n            maxWidth: maxSize + \"px\",\n            maxHeight: maxSize + \"px\"\n        });\n\n        img.src = url;\n    }\n\n    // Draw a (server-hosted) thumbnail given a URL.\n    // This will optionally scale the thumbnail as well.\n    // It attempts to use <canvas> to scale, but will fall back\n    // to max-width and max-height style properties if the UA\n    // doesn't support canvas or if the images is cross-domain and\n    // the UA doesn't support the crossorigin attribute on img tags,\n    // which is required to scale a cross-origin image using <canvas> &\n    // then export it back to an <img>.\n    function drawFromUrl(url, container, options) {\n        var draw = new qq.Promise(),\n            scale = options.scale,\n            maxSize = scale ? options.maxSize : null;\n\n        // container is an img, scaling needed\n        if (scale && isImg(container)) {\n            // Iff canvas is available in this UA, try to use it for scaling.\n            // Otherwise, fall back to CSS scaling\n            if (isCanvasSupported()) {\n                // Attempt to use <canvas> for image scaling,\n                // but we must fall back to scaling via CSS/styles\n                // if this is a cross-origin image and the UA doesn't support <img> CORS.\n                if (isCrossOrigin(url) && !isImgCorsSupported()) {\n                    drawOnImgFromUrlWithCssScaling(url, container, draw, maxSize);\n                }\n                else {\n                    drawOnCanvasOrImgFromUrl(url, container, draw, maxSize);\n                }\n            }\n            else {\n                drawOnImgFromUrlWithCssScaling(url, container, draw, maxSize);\n            }\n        }\n        // container is a canvas, scaling optional\n        else if (isCanvas(container)) {\n            drawOnCanvasOrImgFromUrl(url, container, draw, maxSize);\n        }\n        // container is an img & no scaling: just set the src attr to the passed url\n        else if (registerThumbnailRenderedListener(container, draw)) {\n            container.src = url;\n        }\n\n        return draw;\n    }\n\n    qq.extend(this, {\n        /**\n         * Generate a thumbnail.  Depending on the arguments, this may either result in\n         * a client-side rendering of an image (if a `Blob` is supplied) or a server-generated\n         * image that may optionally be scaled client-side using <canvas> or CSS/styles (as a fallback).\n         *\n         * @param fileBlobOrUrl a `File`, `Blob`, or a URL pointing to the image\n         * @param container <img> or <canvas> to contain the preview\n         * @param options possible properties include `maxSize` (int), `orient` (bool - default true), resize` (bool - default true), and `customResizeFunction`.\n         * @returns qq.Promise fulfilled when the preview has been drawn, or the attempt has failed\n         */\n        generate: function(fileBlobOrUrl, container, options) {\n            if (qq.isString(fileBlobOrUrl)) {\n                log(\"Attempting to update thumbnail based on server response.\");\n                return drawFromUrl(fileBlobOrUrl, container, options || {});\n            }\n            else {\n                log(\"Attempting to draw client-side image preview.\");\n                return draw(fileBlobOrUrl, container, options || {});\n            }\n        }\n    });\n\n    /*<testing>*/\n    this._testing = {};\n    this._testing.isImg = isImg;\n    this._testing.isCanvas = isCanvas;\n    this._testing.isCrossOrigin = isCrossOrigin;\n    this._testing.determineMimeOfFileName = determineMimeOfFileName;\n    /*</testing>*/\n};\n"
  },
  {
    "path": "client/js/image-support/megapix-image.js",
    "content": "/*global qq, define */\n/*jshint strict:false,bitwise:false,nonew:false,asi:true,-W064,-W116,-W089 */\n/**\n * Mega pixel image rendering library for iOS6+\n *\n * Fixes iOS6+'s image file rendering issue for large size image (over mega-pixel),\n * which causes unexpected subsampling when drawing it in canvas.\n * By using this library, you can safely render the image with proper stretching.\n *\n * Copyright (c) 2012 Shinichi Tomita <shinichi.tomita@gmail.com>\n * Released under the MIT license\n *\n * Heavily modified by Widen for Fine Uploader\n */\n(function() {\n\n    /**\n     * Detect subsampling in loaded image.\n     * In iOS, larger images than 2M pixels may be subsampled in rendering.\n     */\n    function detectSubsampling(img) {\n        var iw = img.naturalWidth,\n            ih = img.naturalHeight,\n            canvas = document.createElement(\"canvas\"),\n            ctx;\n\n        if (iw * ih > 1024 * 1024) { // subsampling may happen over megapixel image\n            canvas.width = canvas.height = 1;\n            ctx = canvas.getContext(\"2d\");\n            ctx.drawImage(img, -iw + 1, 0);\n            // subsampled image becomes half smaller in rendering size.\n            // check alpha channel value to confirm image is covering edge pixel or not.\n            // if alpha value is 0 image is not covering, hence subsampled.\n            return ctx.getImageData(0, 0, 1, 1).data[3] === 0;\n        } else {\n            return false;\n        }\n    }\n\n    /**\n     * Detecting vertical squash in loaded image.\n     * Fixes a bug which squash image vertically while drawing into canvas for some images.\n     */\n    function detectVerticalSquash(img, iw, ih) {\n        var canvas = document.createElement(\"canvas\"),\n            sy = 0,\n            ey = ih,\n            py = ih,\n            ctx, data, alpha, ratio;\n\n        canvas.width = 1;\n        canvas.height = ih;\n        ctx = canvas.getContext(\"2d\");\n        ctx.drawImage(img, 0, 0);\n        data = ctx.getImageData(0, 0, 1, ih).data;\n\n        // search image edge pixel position in case it is squashed vertically.\n        while (py > sy) {\n            alpha = data[(py - 1) * 4 + 3];\n            if (alpha === 0) {\n                ey = py;\n            } else {\n                sy = py;\n            }\n            py = (ey + sy) >> 1;\n        }\n\n        ratio = (py / ih);\n        return (ratio === 0) ? 1 : ratio;\n    }\n\n    /**\n     * Rendering image element (with resizing) and get its data URL\n     */\n    function renderImageToDataURL(img, blob, options, doSquash) {\n        var canvas = document.createElement(\"canvas\"),\n            mime = options.mime || \"image/jpeg\",\n            promise = new qq.Promise();\n\n        renderImageToCanvas(img, blob, canvas, options, doSquash)\n            .then(function() {\n                promise.success(\n                    canvas.toDataURL(mime, options.quality || 0.8)\n                );\n            });\n\n        return promise;\n    }\n\n    function maybeCalculateDownsampledDimensions(spec) {\n        var maxPixels = 5241000; //iOS specific value\n\n        if (!qq.ios()) {\n            throw new qq.Error(\"Downsampled dimensions can only be reliably calculated for iOS!\");\n        }\n\n        if (spec.origHeight * spec.origWidth > maxPixels) {\n            return {\n                newHeight: Math.round(Math.sqrt(maxPixels * (spec.origHeight / spec.origWidth))),\n                newWidth: Math.round(Math.sqrt(maxPixels * (spec.origWidth / spec.origHeight)))\n            };\n        }\n    }\n\n    /**\n     * Rendering image element (with resizing) into the canvas element\n     */\n    function renderImageToCanvas(img, blob, canvas, options, doSquash) {\n        var iw = img.naturalWidth,\n            ih = img.naturalHeight,\n            width = options.width,\n            height = options.height,\n            ctx = canvas.getContext(\"2d\"),\n            promise = new qq.Promise(),\n            modifiedDimensions;\n\n        ctx.save();\n\n        if (options.resize) {\n            return renderImageToCanvasWithCustomResizer({\n                blob: blob,\n                canvas: canvas,\n                image: img,\n                imageHeight: ih,\n                imageWidth: iw,\n                orientation: options.orientation,\n                resize: options.resize,\n                targetHeight: height,\n                targetWidth: width\n            });\n        }\n\n        if (!qq.supportedFeatures.unlimitedScaledImageSize) {\n            modifiedDimensions = maybeCalculateDownsampledDimensions({\n                origWidth: width,\n                origHeight: height\n            });\n\n            if (modifiedDimensions) {\n                qq.log(qq.format(\"Had to reduce dimensions due to device limitations from {}w / {}h to {}w / {}h\",\n                    width, height, modifiedDimensions.newWidth, modifiedDimensions.newHeight),\n                    \"warn\");\n\n                width = modifiedDimensions.newWidth;\n                height = modifiedDimensions.newHeight;\n            }\n        }\n\n        transformCoordinate(canvas, width, height, options.orientation);\n\n        // Fine Uploader specific: Save some CPU cycles if not using iOS\n        // Assumption: This logic is only needed to overcome iOS image sampling issues\n        if (qq.ios()) {\n            (function() {\n                if (detectSubsampling(img)) {\n                    iw /= 2;\n                    ih /= 2;\n                }\n\n                var d = 1024, // size of tiling canvas\n                    tmpCanvas = document.createElement(\"canvas\"),\n                    vertSquashRatio = doSquash ? detectVerticalSquash(img, iw, ih) : 1,\n                    dw = Math.ceil(d * width / iw),\n                    dh = Math.ceil(d * height / ih / vertSquashRatio),\n                    sy = 0,\n                    dy = 0,\n                    tmpCtx, sx, dx;\n\n                tmpCanvas.width = tmpCanvas.height = d;\n                tmpCtx = tmpCanvas.getContext(\"2d\");\n\n                while (sy < ih) {\n                    sx = 0;\n                    dx = 0;\n                    while (sx < iw) {\n                        tmpCtx.clearRect(0, 0, d, d);\n                        tmpCtx.drawImage(img, -sx, -sy);\n                        ctx.drawImage(tmpCanvas, 0, 0, d, d, dx, dy, dw, dh);\n                        sx += d;\n                        dx += dw;\n                    }\n                    sy += d;\n                    dy += dh;\n                }\n                ctx.restore();\n                tmpCanvas = tmpCtx = null;\n            }());\n        }\n        else {\n            ctx.drawImage(img, 0, 0, width, height);\n        }\n\n        canvas.qqImageRendered && canvas.qqImageRendered();\n        promise.success();\n\n        return promise;\n    }\n\n    function renderImageToCanvasWithCustomResizer(resizeInfo) {\n        var blob = resizeInfo.blob,\n            image = resizeInfo.image,\n            imageHeight = resizeInfo.imageHeight,\n            imageWidth = resizeInfo.imageWidth,\n            orientation = resizeInfo.orientation,\n            promise = new qq.Promise(),\n            resize = resizeInfo.resize,\n            sourceCanvas = document.createElement(\"canvas\"),\n            sourceCanvasContext = sourceCanvas.getContext(\"2d\"),\n            targetCanvas = resizeInfo.canvas,\n            targetHeight = resizeInfo.targetHeight,\n            targetWidth = resizeInfo.targetWidth;\n\n        transformCoordinate(sourceCanvas, imageWidth, imageHeight, orientation);\n\n        targetCanvas.height = targetHeight;\n        targetCanvas.width = targetWidth;\n\n        sourceCanvasContext.drawImage(image, 0, 0);\n\n        resize({\n            blob: blob,\n            height: targetHeight,\n            image: image,\n            sourceCanvas: sourceCanvas,\n            targetCanvas: targetCanvas,\n            width: targetWidth\n        })\n            .then(\n                function success() {\n                    targetCanvas.qqImageRendered && targetCanvas.qqImageRendered();\n                    promise.success();\n                },\n                promise.failure\n            );\n\n        return promise;\n    }\n\n    /**\n     * Transform canvas coordination according to specified frame size and orientation\n     * Orientation value is from EXIF tag\n     */\n    function transformCoordinate(canvas, width, height, orientation) {\n        switch (orientation) {\n            case 5:\n            case 6:\n            case 7:\n            case 8:\n                canvas.width = height;\n                canvas.height = width;\n                break;\n            default:\n                canvas.width = width;\n                canvas.height = height;\n        }\n        var ctx = canvas.getContext(\"2d\");\n        switch (orientation) {\n            case 2:\n                // horizontal flip\n                ctx.translate(width, 0);\n                ctx.scale(-1, 1);\n                break;\n            case 3:\n                // 180 rotate left\n                ctx.translate(width, height);\n                ctx.rotate(Math.PI);\n                break;\n            case 4:\n                // vertical flip\n                ctx.translate(0, height);\n                ctx.scale(1, -1);\n                break;\n            case 5:\n                // vertical flip + 90 rotate right\n                ctx.rotate(0.5 * Math.PI);\n                ctx.scale(1, -1);\n                break;\n            case 6:\n                // 90 rotate right\n                ctx.rotate(0.5 * Math.PI);\n                ctx.translate(0, -height);\n                break;\n            case 7:\n                // horizontal flip + 90 rotate right\n                ctx.rotate(0.5 * Math.PI);\n                ctx.translate(width, -height);\n                ctx.scale(-1, 1);\n                break;\n            case 8:\n                // 90 rotate left\n                ctx.rotate(-0.5 * Math.PI);\n                ctx.translate(-width, 0);\n                break;\n            default:\n                break;\n        }\n    }\n\n    /**\n     * MegaPixImage class\n     */\n    function MegaPixImage(srcImage, errorCallback) {\n        var self = this;\n\n        if (window.Blob && srcImage instanceof Blob) {\n            (function() {\n                var img = new Image(),\n                    URL = window.URL && window.URL.createObjectURL ? window.URL :\n                        window.webkitURL && window.webkitURL.createObjectURL ? window.webkitURL : null;\n                if (!URL) { throw Error(\"No createObjectURL function found to create blob url\"); }\n                img.src = URL.createObjectURL(srcImage);\n                self.blob = srcImage;\n                srcImage = img;\n            }());\n        }\n        if (!srcImage.naturalWidth && !srcImage.naturalHeight) {\n            srcImage.onload = function() {\n                var listeners = self.imageLoadListeners;\n                if (listeners) {\n                    self.imageLoadListeners = null;\n                    // IE11 doesn't reliably report actual image dimensions immediately after onload for small files,\n                    // so let's push this to the end of the UI thread queue.\n                    setTimeout(function() {\n                        for (var i = 0, len = listeners.length; i < len; i++) {\n                            listeners[i]();\n                        }\n                    }, 0);\n                }\n            };\n            srcImage.onerror = errorCallback;\n            this.imageLoadListeners = [];\n        }\n        this.srcImage = srcImage;\n    }\n\n    /**\n     * Rendering megapix image into specified target element\n     */\n    MegaPixImage.prototype.render = function(target, options) {\n        options = options || {};\n\n        var self = this,\n            imgWidth = this.srcImage.naturalWidth,\n            imgHeight = this.srcImage.naturalHeight,\n            width = options.width,\n            height = options.height,\n            maxWidth = options.maxWidth,\n            maxHeight = options.maxHeight,\n            doSquash = !this.blob || this.blob.type === \"image/jpeg\",\n            tagName = target.tagName.toLowerCase(),\n            opt;\n\n        if (this.imageLoadListeners) {\n            this.imageLoadListeners.push(function() { self.render(target, options); });\n            return;\n        }\n\n        if (width && !height) {\n            height = (imgHeight * width / imgWidth) << 0;\n        } else if (height && !width) {\n            width = (imgWidth * height / imgHeight) << 0;\n        } else {\n            width = imgWidth;\n            height = imgHeight;\n        }\n        if (maxWidth && width > maxWidth) {\n            width = maxWidth;\n            height = (imgHeight * width / imgWidth) << 0;\n        }\n        if (maxHeight && height > maxHeight) {\n            height = maxHeight;\n            width = (imgWidth * height / imgHeight) << 0;\n        }\n\n        opt = { width: width, height: height },\n        qq.each(options, function(optionsKey, optionsValue) {\n            opt[optionsKey] = optionsValue;\n        });\n\n        if (tagName === \"img\") {\n            (function() {\n                var oldTargetSrc = target.src;\n                renderImageToDataURL(self.srcImage, self.blob, opt, doSquash)\n                    .then(function(dataUri) {\n                        target.src = dataUri;\n                        oldTargetSrc === target.src && target.onload && target.onload();\n                    });\n            }());\n        } else if (tagName === \"canvas\") {\n            renderImageToCanvas(this.srcImage, this.blob, target, opt, doSquash);\n        }\n        if (typeof this.onrender === \"function\") {\n            this.onrender(target);\n        }\n    };\n\n    qq.MegaPixImage = MegaPixImage;\n})();\n"
  },
  {
    "path": "client/js/image-support/scaler.js",
    "content": "/* globals qq, ExifRestorer */\n/**\n * Controls generation of scaled images based on a reference image encapsulated in a `File` or `Blob`.\n * Scaled images are generated and converted to blobs on-demand.\n * Multiple scaled images per reference image with varying sizes and other properties are supported.\n *\n * @param spec Information about the scaled images to generate.\n * @param log Logger instance\n * @constructor\n */\nqq.Scaler = function(spec, log) {\n    \"use strict\";\n\n    var self = this,\n        customResizeFunction = spec.customResizer,\n        includeOriginal = spec.sendOriginal,\n        orient = spec.orient,\n        defaultType = spec.defaultType,\n        defaultQuality = spec.defaultQuality / 100,\n        failedToScaleText = spec.failureText,\n        includeExif = spec.includeExif,\n        sizes = this._getSortedSizes(spec.sizes);\n\n    // Revealed API for instances of this module\n    qq.extend(this, {\n        // If no targeted sizes have been declared or if this browser doesn't support\n        // client-side image preview generation, there is no scaling to do.\n        enabled: qq.supportedFeatures.scaling && sizes.length > 0,\n\n        getFileRecords: function(originalFileUuid, originalFileName, originalBlobOrBlobData) {\n            var self = this,\n                records = [],\n                originalBlob = originalBlobOrBlobData.blob ? originalBlobOrBlobData.blob : originalBlobOrBlobData,\n                identifier = new qq.Identify(originalBlob, log);\n\n            // If the reference file cannot be rendered natively, we can't create scaled versions.\n            if (identifier.isPreviewableSync()) {\n                // Create records for each scaled version & add them to the records array, smallest first.\n                qq.each(sizes, function(idx, sizeRecord) {\n                    var outputType = self._determineOutputType({\n                        defaultType: defaultType,\n                        requestedType: sizeRecord.type,\n                        refType: originalBlob.type\n                    });\n\n                    records.push({\n                        uuid: qq.getUniqueId(),\n                        name: self._getName(originalFileName, {\n                            name: sizeRecord.name,\n                            type: outputType,\n                            refType: originalBlob.type\n                        }),\n                        blob: new qq.BlobProxy(originalBlob,\n                        qq.bind(self._generateScaledImage, self, {\n                            customResizeFunction: customResizeFunction,\n                            maxSize: sizeRecord.maxSize,\n                            orient: orient,\n                            type: outputType,\n                            quality: defaultQuality,\n                            failedText: failedToScaleText,\n                            includeExif: includeExif,\n                            log: log\n                        }))\n                    });\n                });\n\n                records.push({\n                    uuid: originalFileUuid,\n                    name: originalFileName,\n                    size: originalBlob.size,\n                    blob: includeOriginal ? originalBlob : null\n                });\n            }\n            else {\n                records.push({\n                    uuid: originalFileUuid,\n                    name: originalFileName,\n                    size: originalBlob.size,\n                    blob: originalBlob\n                });\n            }\n\n            return records;\n        },\n\n        handleNewFile: function(file, name, uuid, size, fileList, batchId, uuidParamName, api) {\n            var self = this,\n                buttonId = file.qqButtonId || (file.blob && file.blob.qqButtonId),\n                scaledIds = [],\n                originalId = null,\n                addFileToHandler = api.addFileToHandler,\n                uploadData = api.uploadData,\n                paramsStore = api.paramsStore,\n                proxyGroupId = qq.getUniqueId();\n\n            qq.each(self.getFileRecords(uuid, name, file), function(idx, record) {\n                var blobSize = record.size,\n                    id;\n\n                if (record.blob instanceof qq.BlobProxy) {\n                    blobSize = -1;\n                }\n\n                id = uploadData.addFile({\n                    uuid: record.uuid,\n                    name: record.name,\n                    size: blobSize,\n                    batchId: batchId,\n                    proxyGroupId: proxyGroupId\n                });\n\n                if (record.blob instanceof qq.BlobProxy) {\n                    scaledIds.push(id);\n                }\n                else {\n                    originalId = id;\n                }\n\n                if (record.blob) {\n                    addFileToHandler(id, record.blob);\n                    fileList.push({id: id, file: record.blob});\n                }\n                else {\n                    uploadData.setStatus(id, qq.status.REJECTED);\n                }\n            });\n\n            // If we are potentially uploading an original file and some scaled versions,\n            // ensure the scaled versions include reference's to the parent's UUID and size\n            // in their associated upload requests.\n            if (originalId !== null) {\n                qq.each(scaledIds, function(idx, scaledId) {\n                    var params = {\n                        qqparentuuid: uploadData.retrieve({id: originalId}).uuid,\n                        qqparentsize: uploadData.retrieve({id: originalId}).size\n                    };\n\n                    // Make sure the UUID for each scaled image is sent with the upload request,\n                    // to be consistent (since we may need to ensure it is sent for the original file as well).\n                    params[uuidParamName] = uploadData.retrieve({id: scaledId}).uuid;\n\n                    uploadData.setParentId(scaledId, originalId);\n                    paramsStore.addReadOnly(scaledId, params);\n                });\n\n                // If any scaled images are tied to this parent image, be SURE we send its UUID as an upload request\n                // parameter as well.\n                if (scaledIds.length) {\n                    (function() {\n                        var param = {};\n                        param[uuidParamName] = uploadData.retrieve({id: originalId}).uuid;\n                        paramsStore.addReadOnly(originalId, param);\n                    }());\n                }\n            }\n        }\n    });\n};\n\nqq.extend(qq.Scaler.prototype, {\n    scaleImage: function(id, specs, api) {\n        \"use strict\";\n\n        if (!qq.supportedFeatures.scaling) {\n            throw new qq.Error(\"Scaling is not supported in this browser!\");\n        }\n\n        var scalingEffort = new qq.Promise(),\n            log = api.log,\n            file = api.getFile(id),\n            uploadData = api.uploadData.retrieve({id: id}),\n            name = uploadData && uploadData.name,\n            uuid = uploadData && uploadData.uuid,\n            scalingOptions = {\n                customResizer: specs.customResizer,\n                sendOriginal: false,\n                orient: specs.orient,\n                defaultType: specs.type || null,\n                defaultQuality: specs.quality,\n                failedToScaleText: \"Unable to scale\",\n                sizes: [{name: \"\", maxSize: specs.maxSize}]\n            },\n            scaler = new qq.Scaler(scalingOptions, log);\n\n        if (!qq.Scaler || !qq.supportedFeatures.imagePreviews || !file) {\n            scalingEffort.failure();\n\n            log(\"Could not generate requested scaled image for \" + id + \".  \" +\n                \"Scaling is either not possible in this browser, or the file could not be located.\", \"error\");\n        }\n        else {\n            (qq.bind(function() {\n                // Assumption: There will never be more than one record\n                var record = scaler.getFileRecords(uuid, name, file)[0];\n\n                if (record && record.blob instanceof qq.BlobProxy) {\n                    record.blob.create().then(scalingEffort.success, scalingEffort.failure);\n                }\n                else {\n                    log(id + \" is not a scalable image!\", \"error\");\n                    scalingEffort.failure();\n                }\n            }, this)());\n        }\n\n        return scalingEffort;\n    },\n\n    // NOTE: We cannot reliably determine at this time if the UA supports a specific MIME type for the target format.\n    // image/jpeg and image/png are the only safe choices at this time.\n    _determineOutputType: function(spec) {\n        \"use strict\";\n\n        var requestedType = spec.requestedType,\n            defaultType = spec.defaultType,\n            referenceType = spec.refType;\n\n        // If a default type and requested type have not been specified, this should be a\n        // JPEG if the original type is a JPEG, otherwise, a PNG.\n        if (!defaultType && !requestedType) {\n            if (referenceType !== \"image/jpeg\") {\n                return \"image/png\";\n            }\n            return referenceType;\n        }\n\n        // A specified default type is used when a requested type is not specified.\n        if (!requestedType) {\n            return defaultType;\n        }\n\n        // If requested type is specified, use it, as long as this recognized type is supported by the current UA\n        if (qq.indexOf(Object.keys(qq.Identify.prototype.PREVIEWABLE_MIME_TYPES), requestedType) >= 0) {\n            if (requestedType === \"image/tiff\") {\n                return qq.supportedFeatures.tiffPreviews ? requestedType : defaultType;\n            }\n\n            return requestedType;\n        }\n\n        return defaultType;\n    },\n\n    // Get a file name for a generated scaled file record, based on the provided scaled image description\n    _getName: function(originalName, scaledVersionProperties) {\n        \"use strict\";\n\n        var startOfExt = originalName.lastIndexOf(\".\"),\n            versionType = scaledVersionProperties.type || \"image/png\",\n            referenceType = scaledVersionProperties.refType,\n            scaledName = \"\",\n            scaledExt = qq.getExtension(originalName),\n            nameAppendage = \"\";\n\n        if (scaledVersionProperties.name && scaledVersionProperties.name.trim().length) {\n            nameAppendage = \" (\" + scaledVersionProperties.name + \")\";\n        }\n\n        if (startOfExt >= 0) {\n            scaledName = originalName.substr(0, startOfExt);\n\n            if (referenceType !== versionType) {\n                scaledExt = versionType.split(\"/\")[1];\n            }\n\n            scaledName += nameAppendage + \".\" + scaledExt;\n        }\n        else {\n            scaledName = originalName + nameAppendage;\n        }\n\n        return scaledName;\n    },\n\n    // We want the smallest scaled file to be uploaded first\n    _getSortedSizes: function(sizes) {\n        \"use strict\";\n\n        sizes = qq.extend([], sizes);\n\n        return sizes.sort(function(a, b) {\n            if (a.maxSize > b.maxSize) {\n                return 1;\n            }\n            if (a.maxSize < b.maxSize) {\n                return -1;\n            }\n            return 0;\n        });\n    },\n\n    _generateScaledImage: function(spec, sourceFile) {\n        \"use strict\";\n\n        var self = this,\n            customResizeFunction = spec.customResizeFunction,\n            log = spec.log,\n            maxSize = spec.maxSize,\n            orient = spec.orient,\n            type = spec.type,\n            quality = spec.quality,\n            failedText = spec.failedText,\n            includeExif = spec.includeExif && sourceFile.type === \"image/jpeg\" && type === \"image/jpeg\",\n            scalingEffort = new qq.Promise(),\n            imageGenerator = new qq.ImageGenerator(log),\n            canvas = document.createElement(\"canvas\");\n\n        log(\"Attempting to generate scaled version for \" + sourceFile.name);\n\n        imageGenerator.generate(sourceFile, canvas, {maxSize: maxSize, orient: orient, customResizeFunction: customResizeFunction}).then(function() {\n            var scaledImageDataUri = canvas.toDataURL(type, quality),\n                signalSuccess = function() {\n                    log(\"Success generating scaled version for \" + sourceFile.name);\n                    var blob = qq.dataUriToBlob(scaledImageDataUri);\n                    scalingEffort.success(blob);\n                };\n\n            if (includeExif) {\n                self._insertExifHeader(sourceFile, scaledImageDataUri, log).then(function(scaledImageDataUriWithExif) {\n                    scaledImageDataUri = scaledImageDataUriWithExif;\n                    signalSuccess();\n                },\n                function() {\n                    log(\"Problem inserting EXIF header into scaled image.  Using scaled image w/out EXIF data.\", \"error\");\n                    signalSuccess();\n                });\n            }\n            else {\n                signalSuccess();\n            }\n        }, function() {\n            log(\"Failed attempt to generate scaled version for \" + sourceFile.name, \"error\");\n            scalingEffort.failure(failedText);\n        });\n\n        return scalingEffort;\n    },\n\n    // Attempt to insert the original image's EXIF header into a scaled version.\n    _insertExifHeader: function(originalImage, scaledImageDataUri, log) {\n        \"use strict\";\n\n        var reader = new FileReader(),\n            insertionEffort = new qq.Promise(),\n            originalImageDataUri = \"\";\n\n        reader.onload = function() {\n            originalImageDataUri = reader.result;\n            insertionEffort.success(qq.ExifRestorer.restore(originalImageDataUri, scaledImageDataUri));\n        };\n\n        reader.onerror = function() {\n            log(\"Problem reading \" + originalImage.name + \" during attempt to transfer EXIF data to scaled version.\", \"error\");\n            insertionEffort.failure();\n        };\n\n        reader.readAsDataURL(originalImage);\n\n        return insertionEffort;\n    },\n\n    _dataUriToBlob: function(dataUri) {\n        \"use strict\";\n\n        var byteString, mimeString, arrayBuffer, intArray;\n\n        // convert base64 to raw binary data held in a string\n        if (dataUri.split(\",\")[0].indexOf(\"base64\") >= 0) {\n            byteString = atob(dataUri.split(\",\")[1]);\n        }\n        else {\n            byteString = decodeURI(dataUri.split(\",\")[1]);\n        }\n\n        // extract the MIME\n        mimeString = dataUri.split(\",\")[0]\n            .split(\":\")[1]\n            .split(\";\")[0];\n\n        // write the bytes of the binary string to an ArrayBuffer\n        arrayBuffer = new ArrayBuffer(byteString.length);\n        intArray = new Uint8Array(arrayBuffer);\n        qq.each(byteString, function(idx, character) {\n            intArray[idx] = character.charCodeAt(0);\n        });\n\n        return this._createBlob(arrayBuffer, mimeString);\n    },\n\n    _createBlob: function(data, mime) {\n        \"use strict\";\n\n        var BlobBuilder = window.BlobBuilder ||\n                window.WebKitBlobBuilder ||\n                window.MozBlobBuilder ||\n                window.MSBlobBuilder,\n            blobBuilder = BlobBuilder && new BlobBuilder();\n\n        if (blobBuilder) {\n            blobBuilder.append(data);\n            return blobBuilder.getBlob(mime);\n        }\n        else {\n            return new Blob([data], {type: mime});\n        }\n    }\n});\n"
  },
  {
    "path": "client/js/image-support/validation.image.js",
    "content": "/*globals qq*/\n/**\n * Attempts to validate an image, wherever possible.\n *\n * @param blob File or Blob representing a user-selecting image.\n * @param log Uses this to post log messages to the console.\n * @constructor\n */\nqq.ImageValidation = function(blob, log) {\n    \"use strict\";\n\n    /**\n     * @param limits Object with possible image-related limits to enforce.\n     * @returns {boolean} true if at least one of the limits has a non-zero value\n     */\n    function hasNonZeroLimits(limits) {\n        var atLeastOne = false;\n\n        qq.each(limits, function(limit, value) {\n            if (value > 0) {\n                atLeastOne = true;\n                return false;\n            }\n        });\n\n        return atLeastOne;\n    }\n\n    /**\n     * @returns {qq.Promise} The promise is a failure if we can't obtain the width & height.\n     * Otherwise, `success` is called on the returned promise with an object containing\n     * `width` and `height` properties.\n     */\n    function getWidthHeight() {\n        var sizeDetermination = new qq.Promise();\n\n        new qq.Identify(blob, log).isPreviewable().then(function() {\n            var image = new Image(),\n                url = window.URL && window.URL.createObjectURL ? window.URL :\n                      window.webkitURL && window.webkitURL.createObjectURL ? window.webkitURL :\n                      null;\n\n            if (url) {\n                image.onerror = function() {\n                    log(\"Cannot determine dimensions for image.  May be too large.\", \"error\");\n                    sizeDetermination.failure();\n                };\n\n                image.onload = function() {\n                    sizeDetermination.success({\n                        width: this.width,\n                        height: this.height\n                    });\n                };\n\n                image.src = url.createObjectURL(blob);\n            }\n            else {\n                log(\"No createObjectURL function available to generate image URL!\", \"error\");\n                sizeDetermination.failure();\n            }\n        }, sizeDetermination.failure);\n\n        return sizeDetermination;\n    }\n\n    /**\n     *\n     * @param limits Object with possible image-related limits to enforce.\n     * @param dimensions Object containing `width` & `height` properties for the image to test.\n     * @returns {String || undefined} The name of the failing limit.  Undefined if no failing limits.\n     */\n    function getFailingLimit(limits, dimensions) {\n        var failingLimit;\n\n        qq.each(limits, function(limitName, limitValue) {\n            if (limitValue > 0) {\n                var limitMatcher = /(max|min)(Width|Height)/.exec(limitName),\n                    dimensionPropName = limitMatcher[2].charAt(0).toLowerCase() + limitMatcher[2].slice(1),\n                    actualValue = dimensions[dimensionPropName];\n\n                /*jshint -W015*/\n                switch (limitMatcher[1]) {\n                    case \"min\":\n                        if (actualValue < limitValue) {\n                            failingLimit = limitName;\n                            return false;\n                        }\n                        break;\n                    case \"max\":\n                        if (actualValue > limitValue) {\n                            failingLimit = limitName;\n                            return false;\n                        }\n                        break;\n                }\n            }\n        });\n\n        return failingLimit;\n    }\n\n    /**\n     * Validate the associated blob.\n     *\n     * @param limits\n     * @returns {qq.Promise} `success` is called on the promise is the image is valid or\n     * if the blob is not an image, or if the image is not verifiable.\n     * Otherwise, `failure` with the name of the failing limit.\n     */\n    this.validate = function(limits) {\n        var validationEffort = new qq.Promise();\n\n        log(\"Attempting to validate image.\");\n\n        if (hasNonZeroLimits(limits)) {\n            getWidthHeight().then(function(dimensions) {\n                var failingLimit = getFailingLimit(limits, dimensions);\n\n                if (failingLimit) {\n                    validationEffort.failure(failingLimit);\n                }\n                else {\n                    validationEffort.success();\n                }\n            }, validationEffort.success);\n        }\n        else {\n            validationEffort.success();\n        }\n\n        return validationEffort;\n    };\n};\n"
  },
  {
    "path": "client/js/jquery-dnd.js",
    "content": "/*globals jQuery, qq*/\n(function($) {\n    \"use strict\";\n    var rootDataKey = \"fineUploaderDnd\",\n        $el;\n\n    function init(options) {\n        if (!options) {\n            options = {};\n        }\n\n        options.dropZoneElements = [$el];\n        var xformedOpts = transformVariables(options);\n        addCallbacks(xformedOpts);\n        dnd(new qq.DragAndDrop(xformedOpts));\n\n        return $el;\n    }\n\n    function dataStore(key, val) {\n        var data = $el.data(rootDataKey);\n\n        if (val) {\n            if (data === undefined) {\n                data = {};\n            }\n            data[key] = val;\n            $el.data(rootDataKey, data);\n        }\n        else {\n            if (data === undefined) {\n                return null;\n            }\n            return data[key];\n        }\n    }\n\n    function dnd(instanceToStore) {\n        return dataStore(\"dndInstance\", instanceToStore);\n    }\n\n    function addCallbacks(transformedOpts) {\n        var callbacks = transformedOpts.callbacks = {};\n\n        $.each(new qq.DragAndDrop.callbacks(), function(prop, func) {\n            var name = prop,\n                $callbackEl;\n\n            $callbackEl = $el;\n\n            callbacks[prop] = function() {\n                var args = Array.prototype.slice.call(arguments),\n                    jqueryHandlerResult = $callbackEl.triggerHandler(name, args);\n\n                return jqueryHandlerResult;\n            };\n        });\n    }\n\n    //transform jQuery objects into HTMLElements, and pass along all other option properties\n    function transformVariables(source, dest) {\n        var xformed, arrayVals;\n\n        if (dest === undefined) {\n            xformed = {};\n        }\n        else {\n            xformed = dest;\n        }\n\n        $.each(source, function(prop, val) {\n            if (val instanceof $) {\n                xformed[prop] = val[0];\n            }\n            else if ($.isPlainObject(val)) {\n                xformed[prop] = {};\n                transformVariables(val, xformed[prop]);\n            }\n            else if ($.isArray(val)) {\n                arrayVals = [];\n                $.each(val, function(idx, arrayVal) {\n                    if (arrayVal instanceof $) {\n                        $.merge(arrayVals, arrayVal);\n                    }\n                    else {\n                        arrayVals.push(arrayVal);\n                    }\n                });\n                xformed[prop] = arrayVals;\n            }\n            else {\n                xformed[prop] = val;\n            }\n        });\n\n        if (dest === undefined) {\n            return xformed;\n        }\n    }\n\n    function isValidCommand(command) {\n        return $.type(command) === \"string\" &&\n            command === \"dispose\" &&\n            dnd()[command] !== undefined;\n    }\n\n    function delegateCommand(command) {\n        var xformedArgs = [], origArgs = Array.prototype.slice.call(arguments, 1);\n        transformVariables(origArgs, xformedArgs);\n        return dnd()[command].apply(dnd(), xformedArgs);\n    }\n\n    $.fn.fineUploaderDnd = function(optionsOrCommand) {\n        var self = this, selfArgs = arguments, retVals = [];\n\n        this.each(function(index, el) {\n            $el = $(el);\n\n            if (dnd() && isValidCommand(optionsOrCommand)) {\n                retVals.push(delegateCommand.apply(self, selfArgs));\n\n                if (self.length === 1) {\n                    return false;\n                }\n            }\n            else if (typeof optionsOrCommand === \"object\" || !optionsOrCommand) {\n                init.apply(self, selfArgs);\n            }\n            else {\n                $.error(\"Method \" +  optionsOrCommand + \" does not exist in Fine Uploader's DnD module.\");\n            }\n        });\n\n        if (retVals.length === 1) {\n            return retVals[0];\n        }\n        else if (retVals.length > 1) {\n            return retVals;\n        }\n\n        return this;\n    };\n\n}(jQuery));\n"
  },
  {
    "path": "client/js/jquery-plugin.js",
    "content": "/*globals jQuery, qq*/\n(function($) {\n    \"use strict\";\n    var $el,\n        pluginOptions = [\"uploaderType\", \"endpointType\"];\n\n    function init(options) {\n        var xformedOpts = transformVariables(options || {}),\n            newUploaderInstance = getNewUploaderInstance(xformedOpts);\n\n        uploader(newUploaderInstance);\n        addCallbacks(xformedOpts, newUploaderInstance);\n\n        return $el;\n    }\n\n    function getNewUploaderInstance(params) {\n        var uploaderType = pluginOption(\"uploaderType\"),\n            namespace = pluginOption(\"endpointType\");\n\n        // If the integrator has defined a specific type of uploader to load, use that, otherwise assume `qq.FineUploader`\n        if (uploaderType) {\n            // We can determine the correct constructor function to invoke by combining \"FineUploader\"\n            // with the upper camel cased `uploaderType` value.\n            uploaderType = uploaderType.charAt(0).toUpperCase() + uploaderType.slice(1).toLowerCase();\n\n            if (namespace) {\n                return new qq[namespace][\"FineUploader\" + uploaderType](params);\n            }\n\n            return new qq[\"FineUploader\" + uploaderType](params);\n        }\n        else {\n            if (namespace) {\n                return new qq[namespace].FineUploader(params);\n            }\n\n            return new qq.FineUploader(params);\n        }\n    }\n\n    function dataStore(key, val) {\n        var data = $el.data(\"fineuploader\");\n\n        if (val) {\n            if (data === undefined) {\n                data = {};\n            }\n            data[key] = val;\n            $el.data(\"fineuploader\", data);\n        }\n        else {\n            if (data === undefined) {\n                return null;\n            }\n            return data[key];\n        }\n    }\n\n    //the underlying Fine Uploader instance is stored in jQuery's data stored, associated with the element\n    // tied to this instance of the plug-in\n    function uploader(instanceToStore) {\n        return dataStore(\"uploader\", instanceToStore);\n    }\n\n    function pluginOption(option, optionVal) {\n        return dataStore(option, optionVal);\n    }\n\n    // Implement all callbacks defined in Fine Uploader as functions that trigger appropriately names events and\n    // return the result of executing the bound handler back to Fine Uploader\n    function addCallbacks(transformedOpts, newUploaderInstance) {\n        var callbacks = transformedOpts.callbacks = {};\n\n        $.each(newUploaderInstance._options.callbacks, function(prop, nonJqueryCallback) {\n            var name, callbackEventTarget;\n\n            name = /^on(\\w+)/.exec(prop)[1];\n            name = name.substring(0, 1).toLowerCase() + name.substring(1);\n            callbackEventTarget = $el;\n\n            callbacks[prop] = function() {\n                var originalArgs = Array.prototype.slice.call(arguments),\n                    transformedArgs = [],\n                    nonJqueryCallbackRetVal, jqueryEventCallbackRetVal;\n\n                $.each(originalArgs, function(idx, arg) {\n                    transformedArgs.push(maybeWrapInJquery(arg));\n                });\n\n                nonJqueryCallbackRetVal = nonJqueryCallback.apply(this, originalArgs);\n\n                try {\n                    jqueryEventCallbackRetVal = callbackEventTarget.triggerHandler(name, transformedArgs);\n                }\n                catch (error) {\n                    qq.log(\"Caught error in Fine Uploader jQuery event handler: \" + error.message, \"error\");\n                }\n\n                /*jshint -W116*/\n                if (nonJqueryCallbackRetVal != null) {\n                    return nonJqueryCallbackRetVal;\n                }\n                return jqueryEventCallbackRetVal;\n            };\n        });\n\n        newUploaderInstance._options.callbacks = callbacks;\n    }\n\n    //transform jQuery objects into HTMLElements, and pass along all other option properties\n    function transformVariables(source, dest) {\n        var xformed, arrayVals;\n\n        if (dest === undefined) {\n            if (source.uploaderType !== \"basic\") {\n                xformed = { element: $el[0] };\n            }\n            else {\n                xformed = {};\n            }\n        }\n        else {\n            xformed = dest;\n        }\n\n        $.each(source, function(prop, val) {\n            if ($.inArray(prop, pluginOptions) >= 0) {\n                pluginOption(prop, val);\n            }\n            else if (val instanceof $) {\n                xformed[prop] = val[0];\n            }\n            else if ($.isPlainObject(val)) {\n                xformed[prop] = {};\n                transformVariables(val, xformed[prop]);\n            }\n            else if ($.isArray(val)) {\n                arrayVals = [];\n                $.each(val, function(idx, arrayVal) {\n                    var arrayObjDest = {};\n\n                    if (arrayVal instanceof $) {\n                        $.merge(arrayVals, arrayVal);\n                    }\n                    else if ($.isPlainObject(arrayVal)) {\n                        transformVariables(arrayVal, arrayObjDest);\n                        arrayVals.push(arrayObjDest);\n                    }\n                    else {\n                        arrayVals.push(arrayVal);\n                    }\n                });\n                xformed[prop] = arrayVals;\n            }\n            else {\n                xformed[prop] = val;\n            }\n        });\n\n        if (dest === undefined) {\n            return xformed;\n        }\n    }\n\n    function isValidCommand(command) {\n        return $.type(command) === \"string\" &&\n            !command.match(/^_/) && //enforce private methods convention\n            uploader()[command] !== undefined;\n    }\n\n    // Assuming we have already verified that this is a valid command, call the associated function in the underlying\n    // Fine Uploader instance (passing along the arguments from the caller) and return the result of the call back to the caller\n    function delegateCommand(command) {\n        var xformedArgs = [],\n            origArgs = Array.prototype.slice.call(arguments, 1),\n            retVal;\n\n        transformVariables(origArgs, xformedArgs);\n\n        retVal = uploader()[command].apply(uploader(), xformedArgs);\n\n        return maybeWrapInJquery(retVal);\n    }\n\n    // If the value is an `HTMLElement` or `HTMLDocument`, wrap it in a `jQuery` object\n    function maybeWrapInJquery(val) {\n        var transformedVal = val;\n\n        // If the command is returning an `HTMLElement` or `HTMLDocument`, wrap it in a `jQuery` object\n        /*jshint -W116*/\n        if (val != null && typeof val === \"object\" &&\n           (val.nodeType === 1 || val.nodeType === 9) && val.cloneNode) {\n\n            transformedVal = $(val);\n        }\n\n        return transformedVal;\n    }\n\n    $.fn.fineUploader = function(optionsOrCommand) {\n        var self = this, selfArgs = arguments, retVals = [];\n\n        this.each(function(index, el) {\n            $el = $(el);\n\n            if (uploader() && isValidCommand(optionsOrCommand)) {\n                retVals.push(delegateCommand.apply(self, selfArgs));\n\n                if (self.length === 1) {\n                    return false;\n                }\n            }\n            else if (typeof optionsOrCommand === \"object\" || !optionsOrCommand) {\n                init.apply(self, selfArgs);\n            }\n            else {\n                $.error(\"Method \" +  optionsOrCommand + \" does not exist on jQuery.fineUploader\");\n            }\n        });\n\n        if (retVals.length === 1) {\n            return retVals[0];\n        }\n        else if (retVals.length > 1) {\n            return retVals;\n        }\n\n        return this;\n    };\n\n}(jQuery));\n"
  },
  {
    "path": "client/js/non-traditional-common/uploader.basic.api.js",
    "content": "/*globals qq*/\n/**\n * Defines the public API for non-traditional FineUploaderBasic mode.\n */\n(function() {\n    \"use strict\";\n\n    qq.nonTraditionalBasePublicApi = {\n        setUploadSuccessParams: function(params, id) {\n            this._uploadSuccessParamsStore.set(params, id);\n        },\n        setUploadSuccessEndpoint: function(endpoint, id) {\n            this._uploadSuccessEndpointStore.set(endpoint, id);\n        }\n    };\n\n    qq.nonTraditionalBasePrivateApi = {\n        /**\n         * When the upload has completed, if it is successful, send a request to the `successEndpoint` (if defined).\n         * This will hold up the call to the `onComplete` callback until we have determined success of the upload\n         * according to the local server, if a `successEndpoint` has been defined by the integrator.\n         *\n         * @param id ID of the completed upload\n         * @param name Name of the associated item\n         * @param result Object created from the server's parsed JSON response.\n         * @param xhr Associated XmlHttpRequest, if this was used to send the request.\n         * @returns {boolean || qq.Promise} true/false if success can be determined immediately, otherwise a `qq.Promise`\n         * if we need to ask the server.\n         * @private\n         */\n        _onComplete: function(id, name, result, xhr) {\n            var success = result.success ? true : false,\n                self = this,\n                onCompleteArgs = arguments,\n                successEndpoint = this._uploadSuccessEndpointStore.get(id),\n                successCustomHeaders = this._options.uploadSuccess.customHeaders,\n                successMethod = this._options.uploadSuccess.method,\n                cors = this._options.cors,\n                promise = new qq.Promise(),\n                uploadSuccessParams = this._uploadSuccessParamsStore.get(id),\n                fileParams = this._paramsStore.get(id),\n\n                // If we are waiting for confirmation from the local server, and have received it,\n                // include properties from the local server response in the `response` parameter\n                // sent to the `onComplete` callback, delegate to the parent `_onComplete`, and\n                // fulfill the associated promise.\n                onSuccessFromServer = function(successRequestResult) {\n                    delete self._failedSuccessRequestCallbacks[id];\n                    qq.extend(result, successRequestResult);\n                    qq.FineUploaderBasic.prototype._onComplete.apply(self, onCompleteArgs);\n                    promise.success(successRequestResult);\n                },\n\n                // If the upload success request fails, attempt to re-send the success request (via the core retry code).\n                // The entire upload may be restarted if the server returns a \"reset\" property with a value of true as well.\n                onFailureFromServer = function(successRequestResult) {\n                    var callback = submitSuccessRequest;\n\n                    qq.extend(result, successRequestResult);\n\n                    if (result && result.reset) {\n                        callback = null;\n                    }\n\n                    if (!callback) {\n                        delete self._failedSuccessRequestCallbacks[id];\n                    }\n                    else {\n                        self._failedSuccessRequestCallbacks[id] = callback;\n                    }\n\n                    if (!self._onAutoRetry(id, name, result, xhr, callback)) {\n                        qq.FineUploaderBasic.prototype._onComplete.apply(self, onCompleteArgs);\n                        promise.failure(successRequestResult);\n                    }\n                },\n                submitSuccessRequest,\n                successAjaxRequester;\n\n            // Ask the local server if the file sent is ok.\n            if (success && successEndpoint) {\n                successAjaxRequester = new qq.UploadSuccessAjaxRequester({\n                    endpoint: successEndpoint,\n                    method: successMethod,\n                    customHeaders: successCustomHeaders,\n                    cors: cors,\n                    log: qq.bind(this.log, this)\n                });\n\n                // combine custom params and default params\n                qq.extend(uploadSuccessParams, self._getEndpointSpecificParams(id, result, xhr), true);\n\n                // include any params associated with the file\n                fileParams && qq.extend(uploadSuccessParams, fileParams, true);\n\n                submitSuccessRequest = qq.bind(function() {\n                    successAjaxRequester.sendSuccessRequest(id, uploadSuccessParams)\n                        .then(onSuccessFromServer, onFailureFromServer);\n                }, self);\n\n                submitSuccessRequest();\n\n                return promise;\n            }\n\n            // If we are not asking the local server about the file, just delegate to the parent `_onComplete`.\n            return qq.FineUploaderBasic.prototype._onComplete.apply(this, arguments);\n        },\n\n        // If the failure occurred on an upload success request (and a reset was not ordered), try to resend that instead.\n        _manualRetry: function(id) {\n            var successRequestCallback = this._failedSuccessRequestCallbacks[id];\n\n            return qq.FineUploaderBasic.prototype._manualRetry.call(this, id, successRequestCallback);\n        }\n    };\n}());\n"
  },
  {
    "path": "client/js/paste.js",
    "content": "/*globals qq*/\nqq.PasteSupport = function(o) {\n    \"use strict\";\n\n    var options, detachPasteHandler;\n\n    options = {\n        targetElement: null,\n        callbacks: {\n            log: function(message, level) {},\n            pasteReceived: function(blob) {}\n        }\n    };\n\n    function isImage(item) {\n        return item.type &&\n            item.type.indexOf(\"image/\") === 0;\n    }\n\n    function registerPasteHandler() {\n        detachPasteHandler = qq(options.targetElement).attach(\"paste\", function(event) {\n            var clipboardData = event.clipboardData;\n\n            if (clipboardData) {\n                qq.each(clipboardData.items, function(idx, item) {\n                    if (isImage(item)) {\n                        var blob = item.getAsFile();\n                        options.callbacks.pasteReceived(blob);\n                    }\n                });\n            }\n        });\n    }\n\n    function unregisterPasteHandler() {\n        if (detachPasteHandler) {\n            detachPasteHandler();\n        }\n    }\n\n    qq.extend(options, o);\n    registerPasteHandler();\n\n    qq.extend(this, {\n        reset: function() {\n            unregisterPasteHandler();\n        }\n    });\n};\n"
  },
  {
    "path": "client/js/promise.js",
    "content": "/*globals qq*/\n\n// Is the passed object a promise instance?\nqq.isGenericPromise = function(maybePromise) {\n    \"use strict\";\n    return !!(maybePromise && maybePromise.then && qq.isFunction(maybePromise.then));\n};\n\nqq.Promise = function() {\n    \"use strict\";\n\n    var successArgs, failureArgs,\n        successCallbacks = [],\n        failureCallbacks = [],\n        doneCallbacks = [],\n        state = 0;\n\n    qq.extend(this, {\n        then: function(onSuccess, onFailure) {\n            if (state === 0) {\n                if (onSuccess) {\n                    successCallbacks.push(onSuccess);\n                }\n                if (onFailure) {\n                    failureCallbacks.push(onFailure);\n                }\n            }\n            else if (state === -1) {\n                onFailure && onFailure.apply(null, failureArgs);\n            }\n            else if (onSuccess) {\n                onSuccess.apply(null, successArgs);\n            }\n\n            return this;\n        },\n\n        done: function(callback) {\n            if (state === 0) {\n                doneCallbacks.push(callback);\n            }\n            else {\n                callback.apply(null, failureArgs === undefined ? successArgs : failureArgs);\n            }\n\n            return this;\n        },\n\n        success: function() {\n            state = 1;\n            successArgs = arguments;\n\n            if (successCallbacks.length) {\n                qq.each(successCallbacks, function(idx, callback) {\n                    callback.apply(null, successArgs);\n                });\n            }\n\n            if (doneCallbacks.length) {\n                qq.each(doneCallbacks, function(idx, callback) {\n                    callback.apply(null, successArgs);\n                });\n            }\n\n            return this;\n        },\n\n        failure: function() {\n            state = -1;\n            failureArgs = arguments;\n\n            if (failureCallbacks.length) {\n                qq.each(failureCallbacks, function(idx, callback) {\n                    callback.apply(null, failureArgs);\n                });\n            }\n\n            if (doneCallbacks.length) {\n                qq.each(doneCallbacks, function(idx, callback) {\n                    callback.apply(null, failureArgs);\n                });\n            }\n\n            return this;\n        }\n    });\n};\n"
  },
  {
    "path": "client/js/s3/jquery-plugin.js",
    "content": "/*globals jQuery*/\n/**\n * Simply an alias for the `fineUploader` plug-in wrapper, but hides the required `endpointType` option from the\n * integrator.  I thought it may be confusing to convey to the integrator that, when using Fine Uploader in S3 mode,\n * you need to specify an `endpointType` with a value of S3, and perhaps an `uploaderType` with a value of \"basic\" if\n * you want to use basic mode when uploading directly to S3 as well.  So, you can use this plug-in alias and not worry\n * about the `endpointType` option at all.\n */\n(function($) {\n    \"use strict\";\n\n    $.fn.fineUploaderS3 = function(optionsOrCommand) {\n        if (typeof optionsOrCommand === \"object\") {\n\n            // This option is used to tell the plug-in wrapper to instantiate the appropriate S3-namespace modules.\n            optionsOrCommand.endpointType = \"s3\";\n        }\n\n        return $.fn.fineUploader.apply(this, arguments);\n    };\n\n}(jQuery));\n"
  },
  {
    "path": "client/js/s3/multipart.abort.ajax.requester.js",
    "content": "/*globals qq */\n/**\n * Ajax requester used to send an [\"Abort Multipart Upload\"](http://docs.aws.amazon.com/AmazonS3/latest/API/mpUploadAbort.html)\n * request to S3 via the REST API.\n\n * @param o\n * @constructor\n */\nqq.s3.AbortMultipartAjaxRequester = function(o) {\n    \"use strict\";\n\n    var requester,\n        options = {\n            method: \"DELETE\",\n            endpointStore: null,\n            signatureSpec: null,\n            maxConnections: 3,\n            getBucket: function(id) {},\n            getHost: function(id) {},\n            getKey: function(id) {},\n            log: function(str, level) {}\n        },\n        getSignatureAjaxRequester;\n\n    qq.extend(options, o);\n\n    // Transport for requesting signatures (for the \"Complete\" requests) from the local server\n    getSignatureAjaxRequester = new qq.s3.RequestSigner({\n        endpointStore: options.endpointStore,\n        signatureSpec: options.signatureSpec,\n        cors: options.cors,\n        log: options.log\n    });\n\n    /**\n     * Attach all required headers (including Authorization) to the \"Abort\" request.  This is a promissory function\n     * that will fulfill the associated promise once all headers have been attached or when an error has occurred that\n     * prevents headers from being attached.\n     *\n     * @param id Associated file ID\n     * @param uploadId ID of the associated upload, according to AWS\n     * @returns {qq.Promise}\n     */\n    function getHeaders(id, uploadId) {\n        var headers = {},\n            promise = new qq.Promise(),\n            bucket = options.getBucket(id),\n            host = options.getHost(id),\n            signatureConstructor = getSignatureAjaxRequester.constructStringToSign\n                (getSignatureAjaxRequester.REQUEST_TYPE.MULTIPART_ABORT, bucket, host, options.getKey(id))\n                .withUploadId(uploadId);\n\n        // Ask the local server to sign the request.  Use this signature to form the Authorization header.\n        getSignatureAjaxRequester.getSignature(id, {signatureConstructor: signatureConstructor}).then(promise.success, promise.failure);\n\n        return promise;\n    }\n\n    /**\n     * Called by the base ajax requester when the response has been received.  We definitively determine here if the\n     * \"Abort MPU\" request has been a success or not.\n     *\n     * @param id ID associated with the file.\n     * @param xhr `XMLHttpRequest` object containing the response, among other things.\n     * @param isError A boolean indicating success or failure according to the base ajax requester (primarily based on status code).\n     */\n    function handleAbortRequestComplete(id, xhr, isError) {\n        var domParser = new DOMParser(),\n            responseDoc = domParser.parseFromString(xhr.responseText, \"application/xml\"),\n            errorEls = responseDoc.getElementsByTagName(\"Error\"),\n            awsErrorMsg;\n\n        options.log(qq.format(\"Abort response status {}, body = {}\", xhr.status, xhr.responseText));\n\n        // If the base requester has determine this a failure, give up.\n        if (isError) {\n            options.log(qq.format(\"Abort Multipart Upload request for {} failed with status {}.\", id, xhr.status), \"error\");\n        }\n        else {\n            // Make sure the correct bucket and key has been specified in the XML response from AWS.\n            if (errorEls.length) {\n                isError = true;\n                awsErrorMsg = responseDoc.getElementsByTagName(\"Message\")[0].textContent;\n                options.log(qq.format(\"Failed to Abort Multipart Upload request for {}.  Error: {}\", id, awsErrorMsg), \"error\");\n            }\n            else {\n                options.log(qq.format(\"Abort MPU request succeeded for file ID {}.\", id));\n            }\n        }\n    }\n\n    requester = qq.extend(this, new qq.AjaxRequester({\n        validMethods: [\"DELETE\"],\n        method: options.method,\n        contentType: null,\n        endpointStore: options.endpointStore,\n        maxConnections: options.maxConnections,\n        allowXRequestedWithAndCacheControl: false, //These headers are not necessary & would break some installations if added\n        log: options.log,\n        onComplete: handleAbortRequestComplete,\n        successfulResponseCodes: {\n            DELETE: [204]\n        }\n    }));\n\n    qq.extend(this, {\n        /**\n         * Sends the \"Abort\" request.\n         *\n         * @param id ID associated with the file.\n         * @param uploadId AWS uploadId for this file\n         */\n        send: function(id, uploadId) {\n            getHeaders(id, uploadId).then(function(headers, endOfUrl) {\n                options.log(\"Submitting S3 Abort multipart upload request for \" + id);\n                requester.initTransport(id)\n                    .withPath(endOfUrl)\n                    .withHeaders(headers)\n                    .send();\n            });\n        }\n    });\n};\n"
  },
  {
    "path": "client/js/s3/multipart.complete.ajax.requester.js",
    "content": "/*globals qq*/\n/**\n * Ajax requester used to send an [\"Complete Multipart Upload\"](http://docs.aws.amazon.com/AmazonS3/latest/API/mpUploadComplete.html)\n * request to S3 via the REST API.\n *\n * @param o Options passed by the creator, to overwrite any default option values.\n * @constructor\n */\nqq.s3.CompleteMultipartAjaxRequester = function(o) {\n    \"use strict\";\n\n    var requester,\n        pendingCompleteRequests = {},\n        options = {\n            method: \"POST\",\n            contentType: \"text/xml\",\n            endpointStore: null,\n            signatureSpec: null,\n            maxConnections: 3,\n            getBucket: function(id) {},\n            getHost: function(id) {},\n            getKey: function(id) {},\n            log: function(str, level) {}\n        },\n        getSignatureAjaxRequester;\n\n    qq.extend(options, o);\n\n    // Transport for requesting signatures (for the \"Complete\" requests) from the local server\n    getSignatureAjaxRequester = new qq.s3.RequestSigner({\n        endpointStore: options.endpointStore,\n        signatureSpec: options.signatureSpec,\n        cors: options.cors,\n        log: options.log\n    });\n\n    /**\n     * Attach all required headers (including Authorization) to the \"Complete\" request.  This is a promissory function\n     * that will fulfill the associated promise once all headers have been attached or when an error has occurred that\n     * prevents headers from being attached.\n     *\n     * @returns {qq.Promise}\n     */\n    function getHeaders(id, uploadId, body) {\n        var headers = {},\n            promise = new qq.Promise(),\n            bucket = options.getBucket(id),\n            host = options.getHost(id),\n            signatureConstructor = getSignatureAjaxRequester.constructStringToSign\n                (getSignatureAjaxRequester.REQUEST_TYPE.MULTIPART_COMPLETE, bucket, host, options.getKey(id))\n                .withUploadId(uploadId)\n                .withContent(body)\n                .withContentType(\"application/xml; charset=UTF-8\");\n\n        // Ask the local server to sign the request.  Use this signature to form the Authorization header.\n        getSignatureAjaxRequester.getSignature(id, {signatureConstructor: signatureConstructor}).then(promise.success, promise.failure);\n\n        return promise;\n    }\n\n    /**\n     * Called by the base ajax requester when the response has been received.  We definitively determine here if the\n     * \"Complete MPU\" request has been a success or not.\n     *\n     * @param id ID associated with the file.\n     * @param xhr `XMLHttpRequest` object containing the response, among other things.\n     * @param isError A boolean indicating success or failure according to the base ajax requester (primarily based on status code).\n     */\n    function handleCompleteRequestComplete(id, xhr, isError) {\n        var promise = pendingCompleteRequests[id],\n            domParser = new DOMParser(),\n            bucket = options.getBucket(id),\n            key = options.getKey(id),\n            responseDoc = domParser.parseFromString(xhr.responseText, \"application/xml\"),\n            bucketEls = responseDoc.getElementsByTagName(\"Bucket\"),\n            keyEls = responseDoc.getElementsByTagName(\"Key\");\n\n        delete pendingCompleteRequests[id];\n\n        options.log(qq.format(\"Complete response status {}, body = {}\", xhr.status, xhr.responseText));\n\n        // If the base requester has determine this a failure, give up.\n        if (isError) {\n            options.log(qq.format(\"Complete Multipart Upload request for {} failed with status {}.\", id, xhr.status), \"error\");\n        }\n        else {\n            // Make sure the correct bucket and key has been specified in the XML response from AWS.\n            if (bucketEls.length && keyEls.length) {\n                if (bucketEls[0].textContent !== bucket) {\n                    isError = true;\n                    options.log(qq.format(\"Wrong bucket in response to Complete Multipart Upload request for {}.\", id), \"error\");\n                }\n\n                // TODO Compare key name from response w/ expected key name if AWS ever fixes the encoding of key names in this response.\n            }\n            else {\n                isError = true;\n                options.log(qq.format(\"Missing bucket and/or key in response to Complete Multipart Upload request for {}.\", id), \"error\");\n            }\n        }\n\n        if (isError) {\n            promise.failure(\"Problem combining the file parts!\", xhr);\n        }\n        else {\n            promise.success({}, xhr);\n        }\n    }\n\n    /**\n     * @param etagEntries Array of objects containing `etag` values and their associated `part` numbers.\n     * @returns {string} XML string containing the body to send with the \"Complete\" request\n     */\n    function getCompleteRequestBody(etagEntries) {\n        var doc = document.implementation.createDocument(null, \"CompleteMultipartUpload\", null);\n\n        // The entries MUST be sorted by part number, per the AWS API spec.\n        etagEntries.sort(function(a, b) {\n            return a.part - b.part;\n        });\n\n        // Construct an XML document for each pair of etag/part values that correspond to part uploads.\n        qq.each(etagEntries, function(idx, etagEntry) {\n            var part = etagEntry.part,\n                etag = etagEntry.etag,\n                partEl = doc.createElement(\"Part\"),\n                partNumEl = doc.createElement(\"PartNumber\"),\n                partNumTextEl = doc.createTextNode(part),\n                etagTextEl = doc.createTextNode(etag),\n                etagEl = doc.createElement(\"ETag\");\n\n            etagEl.appendChild(etagTextEl);\n            partNumEl.appendChild(partNumTextEl);\n            partEl.appendChild(partNumEl);\n            partEl.appendChild(etagEl);\n            qq(doc).children()[0].appendChild(partEl);\n        });\n\n        // Turn the resulting XML document into a string fit for transport.\n        return new XMLSerializer().serializeToString(doc);\n    }\n\n    requester = qq.extend(this, new qq.AjaxRequester({\n        method: options.method,\n        contentType: \"application/xml; charset=UTF-8\",\n        endpointStore: options.endpointStore,\n        maxConnections: options.maxConnections,\n        allowXRequestedWithAndCacheControl: false, //These headers are not necessary & would break some installations if added\n        log: options.log,\n        onComplete: handleCompleteRequestComplete,\n        successfulResponseCodes: {\n            POST: [200]\n        }\n    }));\n\n    qq.extend(this, {\n        /**\n         * Sends the \"Complete\" request and fulfills the returned promise when the success of this request is known.\n         *\n         * @param id ID associated with the file.\n         * @param uploadId AWS uploadId for this file\n         * @param etagEntries Array of objects containing `etag` values and their associated `part` numbers.\n         * @returns {qq.Promise}\n         */\n        send: function(id, uploadId, etagEntries) {\n            var promise = new qq.Promise(),\n                body = getCompleteRequestBody(etagEntries);\n\n            getHeaders(id, uploadId, body).then(function(headers, endOfUrl) {\n                options.log(\"Submitting S3 complete multipart upload request for \" + id);\n\n                pendingCompleteRequests[id] = promise;\n                delete headers[\"Content-Type\"];\n\n                requester.initTransport(id)\n                    .withPath(endOfUrl)\n                    .withHeaders(headers)\n                    .withPayload(body)\n                    .send();\n            }, promise.failure);\n\n            return promise;\n        }\n    });\n};\n"
  },
  {
    "path": "client/js/s3/multipart.initiate.ajax.requester.js",
    "content": "/*globals qq*/\n/**\n * Ajax requester used to send an [\"Initiate Multipart Upload\"](http://docs.aws.amazon.com/AmazonS3/latest/API/mpUploadInitiate.html)\n * request to S3 via the REST API.\n *\n * @param o Options from the caller - will override the defaults.\n * @constructor\n */\nqq.s3.InitiateMultipartAjaxRequester = function(o) {\n    \"use strict\";\n\n    var requester,\n        pendingInitiateRequests = {},\n        options = {\n            filenameParam: \"qqfilename\",\n            method: \"POST\",\n            endpointStore: null,\n            paramsStore: null,\n            signatureSpec: null,\n            aclStore: null,\n            reducedRedundancy: false,\n            serverSideEncryption: false,\n            maxConnections: 3,\n            getContentType: function(id) {},\n            getBucket: function(id) {},\n            getHost: function(id) {},\n            getKey: function(id) {},\n            getName: function(id) {},\n            log: function(str, level) {}\n        },\n        getSignatureAjaxRequester;\n\n    qq.extend(options, o);\n\n    getSignatureAjaxRequester = new qq.s3.RequestSigner({\n        endpointStore: options.endpointStore,\n        signatureSpec: options.signatureSpec,\n        cors: options.cors,\n        log: options.log\n    });\n\n    /**\n     * Determine all headers for the \"Initiate MPU\" request, including the \"Authorization\" header, which must be determined\n     * by the local server.  This is a promissory function.  If the server responds with a signature, the headers\n     * (including the Authorization header) will be passed into the success method of the promise.  Otherwise, the failure\n     * method on the promise will be called.\n     *\n     * @param id Associated file ID\n     * @returns {qq.Promise}\n     */\n    function getHeaders(id) {\n        var bucket = options.getBucket(id),\n            host = options.getHost(id),\n            headers = {},\n            promise = new qq.Promise(),\n            key = options.getKey(id),\n            signatureConstructor;\n\n        headers[\"x-amz-acl\"] = options.aclStore.get(id);\n\n        if (options.reducedRedundancy) {\n            headers[qq.s3.util.REDUCED_REDUNDANCY_PARAM_NAME] = qq.s3.util.REDUCED_REDUNDANCY_PARAM_VALUE;\n        }\n\n        if (options.serverSideEncryption) {\n            headers[qq.s3.util.SERVER_SIDE_ENCRYPTION_PARAM_NAME] = qq.s3.util.SERVER_SIDE_ENCRYPTION_PARAM_VALUE;\n        }\n\n        headers[qq.s3.util.AWS_PARAM_PREFIX + options.filenameParam] = encodeURIComponent(options.getName(id));\n\n        qq.each(options.paramsStore.get(id), function(name, val) {\n            if (qq.indexOf(qq.s3.util.UNPREFIXED_PARAM_NAMES, name) >= 0) {\n                headers[name] = val;\n            }\n            else {\n                headers[qq.s3.util.AWS_PARAM_PREFIX + name] = encodeURIComponent(val);\n            }\n        });\n\n        signatureConstructor = getSignatureAjaxRequester.constructStringToSign\n            (getSignatureAjaxRequester.REQUEST_TYPE.MULTIPART_INITIATE, bucket, host, key)\n            .withContentType(options.getContentType(id))\n            .withHeaders(headers);\n\n        // Ask the local server to sign the request.  Use this signature to form the Authorization header.\n        getSignatureAjaxRequester.getSignature(id, {signatureConstructor: signatureConstructor}).then(promise.success, promise.failure);\n\n        return promise;\n    }\n\n    /**\n     * Called by the base ajax requester when the response has been received.  We definitively determine here if the\n     * \"Initiate MPU\" request has been a success or not.\n     *\n     * @param id ID associated with the file.\n     * @param xhr `XMLHttpRequest` object containing the response, among other things.\n     * @param isError A boolean indicating success or failure according to the base ajax requester (primarily based on status code).\n     */\n    function handleInitiateRequestComplete(id, xhr, isError) {\n        var promise = pendingInitiateRequests[id],\n            domParser = new DOMParser(),\n            responseDoc = domParser.parseFromString(xhr.responseText, \"application/xml\"),\n            uploadIdElements, messageElements, uploadId, errorMessage, status;\n\n        delete pendingInitiateRequests[id];\n\n        // The base ajax requester may declare the request to be a failure based on status code.\n        if (isError) {\n            status = xhr.status;\n\n            messageElements = responseDoc.getElementsByTagName(\"Message\");\n            if (messageElements.length > 0) {\n                errorMessage = messageElements[0].textContent;\n            }\n        }\n        // If the base ajax requester has not declared this a failure, make sure we can retrieve the uploadId from the response.\n        else {\n            uploadIdElements = responseDoc.getElementsByTagName(\"UploadId\");\n            if (uploadIdElements.length > 0) {\n                uploadId = uploadIdElements[0].textContent;\n            }\n            else {\n                errorMessage = \"Upload ID missing from request\";\n            }\n        }\n\n        // Either fail the promise (passing a descriptive error message) or declare it a success (passing the upload ID)\n        if (uploadId === undefined) {\n            if (errorMessage) {\n                options.log(qq.format(\"Specific problem detected initiating multipart upload request for {}: '{}'.\", id, errorMessage), \"error\");\n            }\n            else {\n                options.log(qq.format(\"Unexplained error with initiate multipart upload request for {}.  Status code {}.\", id, status), \"error\");\n            }\n\n            promise.failure(\"Problem initiating upload request.\", xhr);\n        }\n        else {\n            options.log(qq.format(\"Initiate multipart upload request successful for {}.  Upload ID is {}\", id, uploadId));\n            promise.success(uploadId, xhr);\n        }\n    }\n\n    requester = qq.extend(this, new qq.AjaxRequester({\n        method: options.method,\n        contentType: null,\n        endpointStore: options.endpointStore,\n        maxConnections: options.maxConnections,\n        allowXRequestedWithAndCacheControl: false, //These headers are not necessary & would break some installations if added\n        log: options.log,\n        onComplete: handleInitiateRequestComplete,\n        successfulResponseCodes: {\n            POST: [200]\n        }\n    }));\n\n    qq.extend(this, {\n        /**\n         * Sends the \"Initiate MPU\" request to AWS via the REST API.  First, though, we must get a signature from the\n         * local server for the request.  If all is successful, the uploadId from AWS will be passed into the promise's\n         * success handler. Otherwise, an error message will ultimately be passed into the failure method.\n         *\n         * @param id The ID associated with the file\n         * @returns {qq.Promise}\n         */\n        send: function(id) {\n            var promise = new qq.Promise();\n\n            getHeaders(id).then(function(headers, endOfUrl) {\n                options.log(\"Submitting S3 initiate multipart upload request for \" + id);\n\n                pendingInitiateRequests[id] = promise;\n                requester.initTransport(id)\n                    .withPath(endOfUrl)\n                    .withHeaders(headers)\n                    .send();\n            }, promise.failure);\n\n            return promise;\n        }\n    });\n};\n"
  },
  {
    "path": "client/js/s3/request-signer.js",
    "content": "/* globals qq, CryptoJS */\n\n// IE 10 does not support Uint8ClampedArray. We don't need it, but CryptoJS attempts to reference it\n// inside a conditional via an instanceof check, which breaks S3 v4 signatures for chunked uploads.\nif (!window.Uint8ClampedArray) {\n    window.Uint8ClampedArray = function() {};\n}\n/**\n * Handles signature determination for HTML Form Upload requests and Multipart Uploader requests (via the S3 REST API).\n *\n * If the S3 requests are to be signed server side, this module will send a POST request to the server in an attempt\n * to solicit signatures for various S3-related requests.  This module also parses the response and attempts\n * to determine if the effort was successful.\n *\n * If the S3 requests are to be signed client-side, without the help of a server, this module will utilize CryptoJS to\n * sign the requests directly in the browser and send them off to S3.\n *\n * @param o Options associated with all such requests\n * @returns {{getSignature: Function}} API method used to initiate the signature request.\n * @constructor\n */\nqq.s3.RequestSigner = function(o) {\n    \"use strict\";\n\n    var requester,\n        thisSignatureRequester = this,\n        pendingSignatures = {},\n        options = {\n            expectingPolicy: false,\n            method: \"POST\",\n            signatureSpec: {\n                drift: 0,\n                credentialsProvider: {},\n                endpoint: null,\n                customHeaders: {},\n                version: 2\n            },\n            maxConnections: 3,\n            endpointStore: {},\n            paramsStore: {},\n            cors: {\n                expected: false,\n                sendCredentials: false\n            },\n            log: function(str, level) {}\n        },\n        credentialsProvider,\n\n        generateHeaders = function(signatureConstructor, signature, promise) {\n            var headers = signatureConstructor.getHeaders();\n\n            if (options.signatureSpec.version === 4) {\n                headers.Authorization = qq.s3.util.V4_ALGORITHM_PARAM_VALUE +\n                    \" Credential=\" + options.signatureSpec.credentialsProvider.get().accessKey + \"/\" +\n                    qq.s3.util.getCredentialsDate(signatureConstructor.getRequestDate()) + \"/\" +\n                    options.signatureSpec.region + \"/\" +\n                    \"s3/aws4_request,\" +\n                    \"SignedHeaders=\" + signatureConstructor.getSignedHeaders() + \",\" +\n                    \"Signature=\" + signature;\n            }\n            else {\n                headers.Authorization = \"AWS \" + options.signatureSpec.credentialsProvider.get().accessKey + \":\" + signature;\n            }\n\n            promise.success(headers, signatureConstructor.getEndOfUrl());\n        },\n\n        v2 = {\n            getStringToSign: function(signatureSpec) {\n                return qq.format(\"{}\\n{}\\n{}\\n\\n{}/{}/{}\",\n                    signatureSpec.method,\n                    signatureSpec.contentMd5 || \"\",\n                    signatureSpec.contentType || \"\",\n                    signatureSpec.headersStr || \"\\n\",\n                    signatureSpec.bucket,\n                    signatureSpec.endOfUrl);\n            },\n\n            signApiRequest: function(signatureConstructor, headersStr, signatureEffort) {\n                var headersWordArray = qq.CryptoJS.enc.Utf8.parse(headersStr),\n                    headersHmacSha1 = qq.CryptoJS.HmacSHA1(headersWordArray, credentialsProvider.get().secretKey),\n                    headersHmacSha1Base64 = qq.CryptoJS.enc.Base64.stringify(headersHmacSha1);\n\n                generateHeaders(signatureConstructor, headersHmacSha1Base64, signatureEffort);\n            },\n\n            signPolicy: function(policy, signatureEffort, updatedAccessKey, updatedSessionToken) {\n                var policyStr = JSON.stringify(policy),\n                    policyWordArray = qq.CryptoJS.enc.Utf8.parse(policyStr),\n                    base64Policy = qq.CryptoJS.enc.Base64.stringify(policyWordArray),\n                    policyHmacSha1 = qq.CryptoJS.HmacSHA1(base64Policy, credentialsProvider.get().secretKey),\n                    policyHmacSha1Base64 = qq.CryptoJS.enc.Base64.stringify(policyHmacSha1);\n\n                signatureEffort.success({\n                    policy: base64Policy,\n                    signature: policyHmacSha1Base64\n                }, updatedAccessKey, updatedSessionToken);\n            }\n        },\n\n        v4 = {\n            getCanonicalQueryString: function(endOfUri) {\n                var queryParamIdx = endOfUri.indexOf(\"?\"),\n                    canonicalQueryString = \"\",\n                    encodedQueryParams, encodedQueryParamNames, queryStrings;\n\n                if (queryParamIdx >= 0) {\n                    encodedQueryParams = {};\n                    queryStrings = endOfUri.substr(queryParamIdx + 1).split(\"&\");\n\n                    qq.each(queryStrings, function(idx, queryString) {\n                        var nameAndVal = queryString.split(\"=\"),\n                            paramVal = nameAndVal[1];\n\n                        if (paramVal == null) {\n                            paramVal = \"\";\n                        }\n\n                        encodedQueryParams[encodeURIComponent(nameAndVal[0])] = encodeURIComponent(paramVal);\n                    });\n\n                    encodedQueryParamNames = Object.keys(encodedQueryParams).sort();\n                    encodedQueryParamNames.forEach(function(encodedQueryParamName, idx) {\n                        canonicalQueryString += encodedQueryParamName + \"=\" + encodedQueryParams[encodedQueryParamName];\n                        if (idx < encodedQueryParamNames.length - 1) {\n                            canonicalQueryString += \"&\";\n                        }\n                    });\n                }\n\n                return canonicalQueryString;\n            },\n\n            getCanonicalRequest: function(signatureSpec) {\n                return qq.format(\"{}\\n{}\\n{}\\n{}\\n{}\\n{}\",\n                    signatureSpec.method,\n                    v4.getCanonicalUri(signatureSpec.endOfUrl),\n                    v4.getCanonicalQueryString(signatureSpec.endOfUrl),\n                    signatureSpec.headersStr || \"\\n\",\n                    v4.getSignedHeaders(signatureSpec.headerNames),\n                    signatureSpec.hashedContent);\n            },\n\n            getCanonicalUri: function(endOfUri) {\n                var path = endOfUri,\n                    queryParamIdx = endOfUri.indexOf(\"?\");\n\n                if (queryParamIdx > 0) {\n                    path = endOfUri.substr(0, queryParamIdx);\n                }\n                return \"/\" + path;\n            },\n\n            getEncodedHashedPayload: function(body) {\n                var promise = new qq.Promise(),\n                    reader;\n\n                if (qq.isBlob(body)) {\n                    // TODO hash blob in webworker if this becomes a notable perf issue\n                    reader = new FileReader();\n                    reader.onloadend = function(e) {\n                        if (e.target.readyState === FileReader.DONE) {\n                            if (e.target.error) {\n                                promise.failure(e.target.error);\n                            }\n                            else {\n                                var wordArray = qq.CryptoJS.lib.WordArray.create(e.target.result);\n                                promise.success(qq.CryptoJS.SHA256(wordArray).toString());\n                            }\n                        }\n                    };\n                    reader.readAsArrayBuffer(body);\n                }\n                else {\n                    body = body || \"\";\n                    promise.success(qq.CryptoJS.SHA256(body).toString());\n                }\n\n                return promise;\n            },\n\n            getScope: function(date, region) {\n                return qq.s3.util.getCredentialsDate(date) + \"/\" +\n                    region + \"/s3/aws4_request\";\n            },\n\n            getStringToSign: function(signatureSpec) {\n                var canonicalRequest = v4.getCanonicalRequest(signatureSpec),\n                    date = qq.s3.util.getV4PolicyDate(signatureSpec.date, signatureSpec.drift),\n                    hashedRequest = qq.CryptoJS.SHA256(canonicalRequest).toString(),\n                    scope = v4.getScope(signatureSpec.date, options.signatureSpec.region),\n                    stringToSignTemplate = \"AWS4-HMAC-SHA256\\n{}\\n{}\\n{}\";\n\n                return {\n                    hashed: qq.format(stringToSignTemplate, date, scope, hashedRequest),\n                    raw: qq.format(stringToSignTemplate, date, scope, canonicalRequest)\n                };\n            },\n\n            getSignedHeaders: function(headerNames) {\n                var signedHeaders = \"\";\n\n                headerNames.forEach(function(headerName, idx) {\n                    signedHeaders += headerName.toLowerCase();\n\n                    if (idx < headerNames.length - 1) {\n                        signedHeaders += \";\";\n                    }\n                });\n\n                return signedHeaders;\n            },\n\n            signApiRequest: function(signatureConstructor, headersStr, signatureEffort) {\n                var secretKey = credentialsProvider.get().secretKey,\n                    headersPattern = /.+\\n.+\\n(\\d+)\\/(.+)\\/s3\\/.+\\n(.+)/,\n                    matches = headersPattern.exec(headersStr),\n                    dateKey, dateRegionKey, dateRegionServiceKey, signingKey;\n\n                dateKey = qq.CryptoJS.HmacSHA256(matches[1], \"AWS4\" + secretKey);\n                dateRegionKey = qq.CryptoJS.HmacSHA256(matches[2], dateKey);\n                dateRegionServiceKey = qq.CryptoJS.HmacSHA256(\"s3\", dateRegionKey);\n                signingKey = qq.CryptoJS.HmacSHA256(\"aws4_request\", dateRegionServiceKey);\n\n                generateHeaders(signatureConstructor, qq.CryptoJS.HmacSHA256(headersStr, signingKey), signatureEffort);\n            },\n\n            signPolicy: function(policy, signatureEffort, updatedAccessKey, updatedSessionToken) {\n                var policyStr = JSON.stringify(policy),\n                    policyWordArray = qq.CryptoJS.enc.Utf8.parse(policyStr),\n                    base64Policy = qq.CryptoJS.enc.Base64.stringify(policyWordArray),\n                    secretKey = credentialsProvider.get().secretKey,\n                    credentialPattern = /.+\\/(.+)\\/(.+)\\/s3\\/aws4_request/,\n                    credentialCondition = (function() {\n                        var credential = null;\n                        qq.each(policy.conditions, function(key, condition) {\n                            var val = condition[\"x-amz-credential\"];\n                            if (val) {\n                                credential = val;\n                                return false;\n                            }\n                        });\n                        return credential;\n                    }()),\n                    matches, dateKey, dateRegionKey, dateRegionServiceKey, signingKey;\n\n                matches = credentialPattern.exec(credentialCondition);\n                dateKey = qq.CryptoJS.HmacSHA256(matches[1], \"AWS4\" + secretKey);\n                dateRegionKey = qq.CryptoJS.HmacSHA256(matches[2], dateKey);\n                dateRegionServiceKey = qq.CryptoJS.HmacSHA256(\"s3\", dateRegionKey);\n                signingKey = qq.CryptoJS.HmacSHA256(\"aws4_request\", dateRegionServiceKey);\n\n                signatureEffort.success({\n                    policy: base64Policy,\n                    signature: qq.CryptoJS.HmacSHA256(base64Policy, signingKey).toString()\n                }, updatedAccessKey, updatedSessionToken);\n            }\n        };\n\n    qq.extend(options, o, true);\n    credentialsProvider = options.signatureSpec.credentialsProvider;\n\n    function handleSignatureReceived(id, xhrOrXdr, isError) {\n        var responseJson = xhrOrXdr.responseText,\n            pendingSignatureData = pendingSignatures[id],\n            promise = pendingSignatureData.promise,\n            signatureConstructor = pendingSignatureData.signatureConstructor,\n            errorMessage, response;\n\n        delete pendingSignatures[id];\n\n        // Attempt to parse what we would expect to be a JSON response\n        if (responseJson) {\n            try {\n                response = qq.parseJson(responseJson);\n            }\n            catch (error) {\n                options.log(\"Error attempting to parse signature response: \" + error, \"error\");\n            }\n        }\n\n        // If the response is parsable and contains an `error` property, use it as the error message\n        if (response && response.error) {\n            isError = true;\n            errorMessage = response.error;\n        }\n        // If we have received a parsable response, and it has an `invalid` property,\n        // the policy document or request headers may have been tampered with client-side.\n        else if (response && response.invalid) {\n            isError = true;\n            errorMessage = \"Invalid policy document or request headers!\";\n        }\n        // Make sure the response contains policy & signature properties\n        else if (response) {\n            if (options.expectingPolicy && !response.policy) {\n                isError = true;\n                errorMessage = \"Response does not include the base64 encoded policy!\";\n            }\n            else if (!response.signature) {\n                isError = true;\n                errorMessage = \"Response does not include the signature!\";\n            }\n        }\n        // Something unknown went wrong\n        else {\n            isError = true;\n            errorMessage = \"Received an empty or invalid response from the server!\";\n        }\n\n        if (isError) {\n            if (errorMessage) {\n                options.log(errorMessage, \"error\");\n            }\n\n            promise.failure(errorMessage);\n        }\n        else if (signatureConstructor) {\n            generateHeaders(signatureConstructor, response.signature, promise);\n        }\n        else {\n            promise.success(response);\n        }\n    }\n\n    function getStringToSignArtifacts(id, version, requestInfo) {\n        var promise = new qq.Promise(),\n            method = \"POST\",\n            headerNames = [],\n            headersStr = \"\",\n            now = new Date(),\n            endOfUrl, signatureSpec, toSign,\n\n            generateStringToSign = function(requestInfo) {\n                var contentMd5,\n                    headerIndexesToRemove = [];\n\n                qq.each(requestInfo.headers, function(name) {\n                    headerNames.push(name);\n                });\n                headerNames.sort();\n\n                qq.each(headerNames, function(idx, headerName) {\n                    if (qq.indexOf(qq.s3.util.UNSIGNABLE_REST_HEADER_NAMES, headerName) < 0) {\n                        headersStr += headerName.toLowerCase() + \":\" + requestInfo.headers[headerName].trim() + \"\\n\";\n                    }\n                    else if (headerName === \"Content-MD5\") {\n                        contentMd5 = requestInfo.headers[headerName];\n                    }\n                    else {\n                        headerIndexesToRemove.unshift(idx);\n                    }\n                });\n\n                qq.each(headerIndexesToRemove, function(idx, headerIdx) {\n                    headerNames.splice(headerIdx, 1);\n                });\n\n                signatureSpec = {\n                    bucket: requestInfo.bucket,\n                    contentMd5: contentMd5,\n                    contentType: requestInfo.contentType,\n                    date: now,\n                    drift: options.signatureSpec.drift,\n                    endOfUrl: endOfUrl,\n                    hashedContent: requestInfo.hashedContent,\n                    headerNames: headerNames,\n                    headersStr: headersStr,\n                    method: method\n                };\n\n                toSign = version === 2 ? v2.getStringToSign(signatureSpec) : v4.getStringToSign(signatureSpec);\n\n                return {\n                    date: now,\n                    endOfUrl: endOfUrl,\n                    signedHeaders: version === 4 ? v4.getSignedHeaders(signatureSpec.headerNames) : null,\n                    toSign: version === 4 ? toSign.hashed : toSign,\n                    toSignRaw: version === 4 ? toSign.raw : toSign\n                };\n            };\n\n        /*jshint indent:false */\n        switch (requestInfo.type) {\n            case thisSignatureRequester.REQUEST_TYPE.MULTIPART_ABORT:\n                method = \"DELETE\";\n                endOfUrl = qq.format(\"uploadId={}\", requestInfo.uploadId);\n                break;\n            case thisSignatureRequester.REQUEST_TYPE.MULTIPART_INITIATE:\n                endOfUrl = \"uploads\";\n                break;\n            case thisSignatureRequester.REQUEST_TYPE.MULTIPART_COMPLETE:\n                endOfUrl = qq.format(\"uploadId={}\", requestInfo.uploadId);\n                break;\n            case thisSignatureRequester.REQUEST_TYPE.MULTIPART_UPLOAD:\n                method = \"PUT\";\n                endOfUrl = qq.format(\"partNumber={}&uploadId={}\", requestInfo.partNum, requestInfo.uploadId);\n                break;\n        }\n\n        endOfUrl = requestInfo.key + \"?\" + endOfUrl;\n\n        if (version === 4) {\n            v4.getEncodedHashedPayload(requestInfo.content).then(function(hashedContent) {\n                requestInfo.headers[\"x-amz-content-sha256\"] = hashedContent;\n                requestInfo.headers.Host = requestInfo.host;\n                requestInfo.headers[\"x-amz-date\"] = qq.s3.util.getV4PolicyDate(now, options.signatureSpec.drift);\n                requestInfo.hashedContent = hashedContent;\n\n                promise.success(generateStringToSign(requestInfo));\n            }, function (err) {\n                promise.failure(err);\n            });\n        }\n        else {\n            promise.success(generateStringToSign(requestInfo));\n        }\n\n        return promise;\n    }\n\n    function determineSignatureClientSide(id, toBeSigned, signatureEffort, updatedAccessKey, updatedSessionToken) {\n        var updatedHeaders;\n\n        // REST API request\n        if (toBeSigned.signatureConstructor) {\n            if (updatedSessionToken) {\n                updatedHeaders = toBeSigned.signatureConstructor.getHeaders();\n                updatedHeaders[qq.s3.util.SESSION_TOKEN_PARAM_NAME] = updatedSessionToken;\n                toBeSigned.signatureConstructor.withHeaders(updatedHeaders);\n            }\n\n            toBeSigned.signatureConstructor.getToSign(id).then(function(signatureArtifacts) {\n                signApiRequest(toBeSigned.signatureConstructor, signatureArtifacts.stringToSign, signatureEffort);\n            }, function (err) {\n                signatureEffort.failure(err);\n            });\n        }\n        // Form upload (w/ policy document)\n        else {\n            updatedSessionToken && qq.s3.util.refreshPolicyCredentials(toBeSigned, updatedSessionToken);\n            signPolicy(toBeSigned, signatureEffort, updatedAccessKey, updatedSessionToken);\n        }\n    }\n\n    function signPolicy(policy, signatureEffort, updatedAccessKey, updatedSessionToken) {\n        if (options.signatureSpec.version === 4) {\n            v4.signPolicy(policy, signatureEffort, updatedAccessKey, updatedSessionToken);\n        }\n        else {\n            v2.signPolicy(policy, signatureEffort, updatedAccessKey, updatedSessionToken);\n        }\n    }\n\n    function signApiRequest(signatureConstructor, headersStr, signatureEffort) {\n        if (options.signatureSpec.version === 4) {\n            v4.signApiRequest(signatureConstructor, headersStr, signatureEffort);\n        }\n        else {\n            v2.signApiRequest(signatureConstructor, headersStr, signatureEffort);\n        }\n    }\n\n    requester = qq.extend(this, new qq.AjaxRequester({\n        acceptHeader: \"application/json\",\n        method: options.method,\n        contentType: \"application/json; charset=utf-8\",\n        endpointStore: {\n            get: function() {\n                return options.signatureSpec.endpoint;\n            }\n        },\n        paramsStore: options.paramsStore,\n        maxConnections: options.maxConnections,\n        customHeaders: options.signatureSpec.customHeaders,\n        log: options.log,\n        onComplete: handleSignatureReceived,\n        cors: options.cors\n    }));\n\n    qq.extend(this, {\n        /**\n         * On success, an object containing the parsed JSON response will be passed into the success handler if the\n         * request succeeds.  Otherwise an error message will be passed into the failure method.\n         *\n         * @param id File ID.\n         * @param toBeSigned an Object that holds the item(s) to be signed\n         * @returns {qq.Promise} A promise that is fulfilled when the response has been received.\n         */\n        getSignature: function(id, toBeSigned) {\n            var params = toBeSigned,\n                signatureConstructor = toBeSigned.signatureConstructor,\n                signatureEffort = new qq.Promise(),\n                queryParams;\n\n            if (options.signatureSpec.version === 4) {\n                queryParams = {v4: true};\n            }\n\n            if (credentialsProvider.get().secretKey && qq.CryptoJS) {\n                if (credentialsProvider.get().expiration.getTime() > Date.now()) {\n                    determineSignatureClientSide(id, toBeSigned, signatureEffort);\n                }\n                // If credentials are expired, ask for new ones before attempting to sign request\n                else {\n                    credentialsProvider.onExpired().then(function() {\n                        determineSignatureClientSide(id, toBeSigned,\n                            signatureEffort,\n                            credentialsProvider.get().accessKey,\n                            credentialsProvider.get().sessionToken);\n                    }, function(errorMsg) {\n                        options.log(\"Attempt to update expired credentials apparently failed! Unable to sign request.  \", \"error\");\n                        signatureEffort.failure(\"Unable to sign request - expired credentials.\");\n                    });\n                }\n            }\n            else {\n                options.log(\"Submitting S3 signature request for \" + id);\n\n                if (signatureConstructor) {\n                    signatureConstructor.getToSign(id).then(function(signatureArtifacts) {\n                        params = {headers: signatureArtifacts.stringToSignRaw};\n                        requester.initTransport(id)\n                            .withParams(params)\n                            .withQueryParams(queryParams)\n                            .send();\n                    }, function (err) {\n                        options.log(\"Failed to construct signature. \", \"error\");\n                        signatureEffort.failure(\"Failed to construct signature.\");\n                    });\n                }\n                else {\n                    requester.initTransport(id)\n                        .withParams(params)\n                        .withQueryParams(queryParams)\n                        .send();\n                }\n\n                pendingSignatures[id] = {\n                    promise: signatureEffort,\n                    signatureConstructor: signatureConstructor\n                };\n            }\n\n            return signatureEffort;\n        },\n\n        constructStringToSign: function(type, bucket, host, key) {\n            var headers = {},\n                uploadId, content, contentType, partNum, artifacts;\n\n            return {\n                withHeaders: function(theHeaders) {\n                    headers = theHeaders;\n                    return this;\n                },\n\n                withUploadId: function(theUploadId) {\n                    uploadId = theUploadId;\n                    return this;\n                },\n\n                withContent: function(theContent) {\n                    content = theContent;\n                    return this;\n                },\n\n                withContentType: function(theContentType) {\n                    contentType = theContentType;\n                    return this;\n                },\n\n                withPartNum: function(thePartNum) {\n                    partNum = thePartNum;\n                    return this;\n                },\n\n                getToSign: function(id) {\n                    var sessionToken = credentialsProvider.get().sessionToken,\n                        promise = new qq.Promise(),\n                        adjustedDate = new Date(Date.now() + options.signatureSpec.drift);\n\n                    headers[\"x-amz-date\"] = adjustedDate.toUTCString();\n\n                    if (sessionToken) {\n                        headers[qq.s3.util.SESSION_TOKEN_PARAM_NAME] = sessionToken;\n                    }\n\n                    getStringToSignArtifacts(id, options.signatureSpec.version, {\n                        bucket: bucket,\n                        content: content,\n                        contentType: contentType,\n                        headers: headers,\n                        host: host,\n                        key: key,\n                        partNum: partNum,\n                        type: type,\n                        uploadId: uploadId\n                    }).then(function(_artifacts_) {\n                        artifacts = _artifacts_;\n                        promise.success({\n                            headers: (function() {\n                                if (contentType) {\n                                    headers[\"Content-Type\"] = contentType;\n                                }\n\n                                delete headers.Host; // we don't want this to be set on the XHR-initiated request\n                                return headers;\n                            }()),\n                            date: artifacts.date,\n                            endOfUrl: artifacts.endOfUrl,\n                            signedHeaders: artifacts.signedHeaders,\n                            stringToSign: artifacts.toSign,\n                            stringToSignRaw: artifacts.toSignRaw\n                        });\n                    }, function (err) {\n                        promise.failure(err);\n                    });\n\n                    return promise;\n                },\n\n                getHeaders: function() {\n                    return qq.extend({}, headers);\n                },\n\n                getEndOfUrl: function() {\n                    return artifacts && artifacts.endOfUrl;\n                },\n\n                getRequestDate: function() {\n                    return artifacts && artifacts.date;\n                },\n\n                getSignedHeaders: function() {\n                    return artifacts && artifacts.signedHeaders;\n                }\n            };\n        }\n    });\n};\n\nqq.s3.RequestSigner.prototype.REQUEST_TYPE = {\n    MULTIPART_INITIATE: \"multipart_initiate\",\n    MULTIPART_COMPLETE: \"multipart_complete\",\n    MULTIPART_ABORT: \"multipart_abort\",\n    MULTIPART_UPLOAD: \"multipart_upload\"\n};\n"
  },
  {
    "path": "client/js/s3/s3.form.upload.handler.js",
    "content": "/*globals qq */\n/**\n * Upload handler used by the upload to S3 module that assumes the current user agent does not have any support for the\n * File API, and, therefore, makes use of iframes and forms to submit the files directly to S3 buckets via the associated\n * AWS API.\n *\n * @param options Options passed from the base handler\n * @param proxy Callbacks & methods used to query for or push out data/changes\n */\nqq.s3.FormUploadHandler = function(options, proxy) {\n    \"use strict\";\n\n    var handler = this,\n        clockDrift = options.clockDrift,\n        onUuidChanged = proxy.onUuidChanged,\n        getName = proxy.getName,\n        getUuid = proxy.getUuid,\n        log = proxy.log,\n        onGetBucket = options.getBucket,\n        onGetKeyName = options.getKeyName,\n        filenameParam = options.filenameParam,\n        paramsStore = options.paramsStore,\n        endpointStore = options.endpointStore,\n        aclStore = options.aclStore,\n        reducedRedundancy = options.objectProperties.reducedRedundancy,\n        region = options.objectProperties.region,\n        serverSideEncryption = options.objectProperties.serverSideEncryption,\n        validation = options.validation,\n        signature = options.signature,\n        successRedirectUrl = options.iframeSupport.localBlankPagePath,\n        credentialsProvider = options.signature.credentialsProvider,\n        getSignatureAjaxRequester = new qq.s3.RequestSigner({\n            signatureSpec: signature,\n            cors: options.cors,\n            log: log\n        });\n\n    if (successRedirectUrl === undefined) {\n        throw new Error(\"successRedirectEndpoint MUST be defined if you intend to use browsers that do not support the File API!\");\n    }\n\n    /**\n     * Attempt to parse the contents of an iframe after receiving a response from the server.  If the contents cannot be\n     * read (perhaps due to a security error) it is safe to assume that the upload was not successful since Amazon should\n     * have redirected to a known endpoint that should provide a parseable response.\n     *\n     * @param id ID of the associated file\n     * @param iframe target of the form submit\n     * @returns {boolean} true if the contents can be read, false otherwise\n     */\n    function isValidResponse(id, iframe) {\n        var response,\n            endpoint = options.endpointStore.get(id),\n            bucket = handler._getFileState(id).bucket,\n            doc,\n            innerHtml,\n            responseData;\n\n        //IE may throw an \"access is denied\" error when attempting to access contentDocument on the iframe in some cases\n        try {\n            // iframe.contentWindow.document - for IE<7\n            doc = iframe.contentDocument || iframe.contentWindow.document;\n            innerHtml = doc.body.innerHTML;\n\n            responseData = qq.s3.util.parseIframeResponse(iframe);\n            if (responseData.bucket === bucket &&\n                responseData.key === qq.s3.util.encodeQueryStringParam(handler.getThirdPartyFileId(id))) {\n\n                return true;\n            }\n\n            log(\"Response from AWS included an unexpected bucket or key name.\", \"error\");\n\n        }\n        catch (error) {\n            log(\"Error when attempting to parse form upload response (\" + error.message + \")\", \"error\");\n        }\n\n        return false;\n    }\n\n    function generateAwsParams(id) {\n        /*jshint -W040 */\n        var customParams = paramsStore.get(id);\n\n        customParams[filenameParam] = getName(id);\n\n        return qq.s3.util.generateAwsParams({\n            endpoint: endpointStore.get(id),\n            clockDrift: clockDrift,\n            params: customParams,\n            bucket: handler._getFileState(id).bucket,\n            key: handler.getThirdPartyFileId(id),\n            accessKey: credentialsProvider.get().accessKey,\n            sessionToken: credentialsProvider.get().sessionToken,\n            acl: aclStore.get(id),\n            minFileSize: validation.minSizeLimit,\n            maxFileSize: validation.maxSizeLimit,\n            successRedirectUrl: successRedirectUrl,\n            reducedRedundancy: reducedRedundancy,\n            region: region,\n            serverSideEncryption: serverSideEncryption,\n            signatureVersion: signature.version,\n            log: log\n        },\n        qq.bind(getSignatureAjaxRequester.getSignature, this, id));\n    }\n\n    /**\n     * Creates form, that will be submitted to iframe\n     */\n    function createForm(id, iframe) {\n        var promise = new qq.Promise(),\n            method = \"POST\",\n            endpoint = options.endpointStore.get(id),\n            fileName = getName(id);\n\n        generateAwsParams(id).then(function(params) {\n            var form = handler._initFormForUpload({\n                method: method,\n                endpoint: endpoint,\n                params: params,\n                paramsInBody: true,\n                targetName: iframe.name\n            });\n\n            promise.success(form);\n        }, function(errorMessage) {\n            promise.failure(errorMessage);\n            handleFinishedUpload(id, iframe, fileName, {error: errorMessage});\n        });\n\n        return promise;\n    }\n\n    function handleUpload(id) {\n        var iframe = handler._createIframe(id),\n            input = handler.getInput(id),\n            promise = new qq.Promise();\n\n        createForm(id, iframe).then(function(form) {\n            form.appendChild(input);\n\n            // Register a callback when the response comes in from S3\n            handler._attachLoadEvent(iframe, function(response) {\n                log(\"iframe loaded\");\n\n                // If the common response handler has determined success or failure immediately\n                if (response) {\n                    // If there is something fundamentally wrong with the response (such as iframe content is not accessible)\n                    if (response.success === false) {\n                        log(\"Amazon likely rejected the upload request\", \"error\");\n                        promise.failure(response);\n                    }\n                }\n                // The generic response (iframe onload) handler was not able to make a determination regarding the success of the request\n                else {\n                    response = {};\n                    response.success = isValidResponse(id, iframe);\n\n                    // If the more specific response handle detected a problem with the response from S3\n                    if (response.success === false) {\n                        log(\"A success response was received by Amazon, but it was invalid in some way.\", \"error\");\n                        promise.failure(response);\n                    }\n                    else {\n                        qq.extend(response, qq.s3.util.parseIframeResponse(iframe));\n                        promise.success(response);\n                    }\n                }\n\n                handleFinishedUpload(id, iframe);\n            });\n\n            log(\"Sending upload request for \" + id);\n            form.submit();\n            qq(form).remove();\n        }, promise.failure);\n\n        return promise;\n    }\n\n    function handleFinishedUpload(id, iframe) {\n        handler._detachLoadEvent(id);\n        iframe && qq(iframe).remove();\n    }\n\n    qq.extend(this, new qq.FormUploadHandler({\n        options: {\n            isCors: false,\n            inputName: \"file\"\n        },\n\n        proxy: {\n            onCancel: options.onCancel,\n            onUuidChanged: onUuidChanged,\n            getName: getName,\n            getUuid: getUuid,\n            log: log\n        }\n    }));\n\n    qq.extend(this, {\n        uploadFile: function(id) {\n            var name = getName(id),\n                promise = new qq.Promise();\n\n            if (handler.getThirdPartyFileId(id)) {\n                if (handler._getFileState(id).bucket) {\n                    handleUpload(id).then(promise.success, promise.failure);\n                }\n                else {\n                    onGetBucket(id).then(function(bucket) {\n                        handler._getFileState(id).bucket = bucket;\n                        handleUpload(id).then(promise.success, promise.failure);\n                    });\n                }\n            }\n            else {\n                // The S3 uploader module will either calculate the key or ask the server for it\n                // and will call us back once it is known.\n                onGetKeyName(id, name).then(function(key) {\n                    onGetBucket(id).then(function(bucket) {\n                        handler._getFileState(id).bucket = bucket;\n                        handler._setThirdPartyFileId(id, key);\n                        handleUpload(id).then(promise.success, promise.failure);\n                    }, function(errorReason) {\n                        promise.failure({error: errorReason});\n                    });\n                }, function(errorReason) {\n                    promise.failure({error: errorReason});\n                });\n            }\n\n            return promise;\n        }\n    });\n};\n"
  },
  {
    "path": "client/js/s3/s3.xhr.upload.handler.js",
    "content": "/*globals qq */\n/**\n * Upload handler used by the upload to S3 module that depends on File API support, and, therefore, makes use of\n * `XMLHttpRequest` level 2 to upload `File`s and `Blob`s directly to S3 buckets via the associated AWS API.\n *\n * If chunking is supported and enabled, the S3 Multipart Upload REST API is utilized.\n *\n * @param spec Options passed from the base handler\n * @param proxy Callbacks & methods used to query for or push out data/changes\n */\nqq.s3.XhrUploadHandler = function(spec, proxy) {\n    \"use strict\";\n\n    var getName = proxy.getName,\n        log = proxy.log,\n        clockDrift = spec.clockDrift,\n        expectedStatus = 200,\n        onGetBucket = spec.getBucket,\n        onGetHost = spec.getHost,\n        onGetKeyName = spec.getKeyName,\n        filenameParam = spec.filenameParam,\n        paramsStore = spec.paramsStore,\n        endpointStore = spec.endpointStore,\n        aclStore = spec.aclStore,\n        reducedRedundancy = spec.objectProperties.reducedRedundancy,\n        region = spec.objectProperties.region,\n        serverSideEncryption = spec.objectProperties.serverSideEncryption,\n        validation = spec.validation,\n        signature = qq.extend({region: region, drift: clockDrift}, spec.signature),\n        handler = this,\n        credentialsProvider = spec.signature.credentialsProvider,\n\n        chunked = {\n            // Sends a \"Complete Multipart Upload\" request and then signals completion of the upload\n            // when the response to this request has been parsed.\n            combine: function(id) {\n                var uploadId = handler._getPersistableData(id).uploadId,\n                    etagMap = handler._getPersistableData(id).etags,\n                    result = new qq.Promise();\n\n                requesters.completeMultipart.send(id, uploadId, etagMap).then(\n                    result.success,\n\n                    function failure(reason, xhr) {\n                        result.failure(upload.done(id, xhr).response, xhr);\n                    }\n                );\n\n                return result;\n            },\n\n            // The last step in handling a chunked upload.  This is called after each chunk has been sent.\n            // The request may be successful, or not.  If it was successful, we must extract the \"ETag\" element\n            // in the XML response and store that along with the associated part number.\n            // We need these items to \"Complete\" the multipart upload after all chunks have been successfully sent.\n            done: function(id, xhr, chunkIdx) {\n                var response = upload.response.parse(id, xhr),\n                    etag;\n\n                if (response.success) {\n                    etag = xhr.getResponseHeader(\"ETag\");\n\n                    if (!handler._getPersistableData(id).etags) {\n                        handler._getPersistableData(id).etags = [];\n                    }\n                    handler._getPersistableData(id).etags.push({part: chunkIdx + 1, etag: etag});\n                }\n            },\n\n            /**\n             * Determines headers that must be attached to the chunked (Multipart Upload) request.  One of these headers is an\n             * Authorization value, which must be determined by asking the local server to sign the request first.  So, this\n             * function returns a promise.  Once all headers are determined, the `success` method of the promise is called with\n             * the headers object.  If there was some problem determining the headers, we delegate to the caller's `failure`\n             * callback.\n             *\n             * @param id File ID\n             * @param chunkIdx Index of the chunk to PUT\n             * @returns {qq.Promise}\n             */\n            initHeaders: function(id, chunkIdx, blob) {\n                var headers = {},\n                    bucket = upload.bucket.getName(id),\n                    host = upload.host.getName(id),\n                    key = upload.key.urlSafe(id),\n                    promise = new qq.Promise(),\n                    signatureConstructor = requesters.restSignature.constructStringToSign\n                        (requesters.restSignature.REQUEST_TYPE.MULTIPART_UPLOAD, bucket, host, key)\n                        .withPartNum(chunkIdx + 1)\n                        .withContent(blob)\n                        .withUploadId(handler._getPersistableData(id).uploadId);\n\n                // Ask the local server to sign the request.  Use this signature to form the Authorization header.\n                requesters.restSignature.getSignature(id + \".\" + chunkIdx, {signatureConstructor: signatureConstructor}).then(promise.success, promise.failure);\n\n                return promise;\n            },\n\n            put: function(id, chunkIdx) {\n                var xhr = handler._createXhr(id, chunkIdx),\n                    chunkData = handler._getChunkData(id, chunkIdx),\n                    domain = spec.endpointStore.get(id),\n                    promise = new qq.Promise();\n\n                // Add appropriate headers to the multipart upload request.\n                // Once these have been determined (asynchronously) attach the headers and send the chunk.\n                chunked.initHeaders(id, chunkIdx, chunkData.blob).then(function(headers, endOfUrl) {\n                    if (xhr._cancelled) {\n                        log(qq.format(\"Upload of item {}.{} cancelled. Upload will not start after successful signature request.\", id, chunkIdx));\n                        promise.failure({error: \"Chunk upload cancelled\"});\n                    }\n                    else {\n                        var url = domain + \"/\" + endOfUrl;\n                        handler._registerProgressHandler(id, chunkIdx, chunkData.size);\n                        upload.track(id, xhr, chunkIdx).then(promise.success, promise.failure);\n                        xhr.open(\"PUT\", url, true);\n\n                        var hasContentType = false;\n                        qq.each(headers, function(name, val) {\n                            if (name === \"Content-Type\") {\n                                hasContentType = true;\n                            }\n\n                            xhr.setRequestHeader(name, val);\n                        });\n\n                        // Workaround for IE Edge\n                        if (!hasContentType) {\n                            xhr.setRequestHeader(\"Content-Type\", \"\");\n                        }\n\n                        xhr.send(chunkData.blob);\n                    }\n                }, function() {\n                    promise.failure({error: \"Problem signing the chunk!\"}, xhr);\n                });\n\n                return promise;\n            },\n\n            send: function(id, chunkIdx) {\n                var promise = new qq.Promise();\n\n                chunked.setup(id).then(\n                    // The \"Initiate\" request succeeded.  We are ready to send the first chunk.\n                    function() {\n                        chunked.put(id, chunkIdx).then(promise.success, promise.failure);\n                    },\n\n                    // We were unable to initiate the chunked upload process.\n                    function(errorMessage, xhr) {\n                        promise.failure({error: errorMessage}, xhr);\n                    }\n                );\n\n                return promise;\n            },\n\n            /**\n             * Sends an \"Initiate Multipart Upload\" request to S3 via the REST API, but only if the MPU has not already been\n             * initiated.\n             *\n             * @param id Associated file ID\n             * @returns {qq.Promise} A promise that is fulfilled when the initiate request has been sent and the response has been parsed.\n             */\n            setup: function(id) {\n                var promise = new qq.Promise(),\n                    uploadId = handler._getPersistableData(id).uploadId,\n                    uploadIdPromise = new qq.Promise();\n\n                if (!uploadId) {\n                    handler._getPersistableData(id).uploadId = uploadIdPromise;\n                    requesters.initiateMultipart.send(id).then(\n                        function(uploadId) {\n                            handler._getPersistableData(id).uploadId = uploadId;\n                            uploadIdPromise.success(uploadId);\n                            promise.success(uploadId);\n                        },\n                        function(errorMsg, xhr) {\n                            handler._getPersistableData(id).uploadId = null;\n                            promise.failure(errorMsg, xhr);\n                            uploadIdPromise.failure(errorMsg, xhr);\n                        }\n                    );\n                }\n                else if (uploadId instanceof qq.Promise) {\n                    uploadId.then(function(uploadId) {\n                        promise.success(uploadId);\n                    });\n                }\n                else {\n                    promise.success(uploadId);\n                }\n\n                return promise;\n            }\n        },\n\n        requesters = {\n            abortMultipart: new qq.s3.AbortMultipartAjaxRequester({\n                endpointStore: endpointStore,\n                signatureSpec: signature,\n                cors: spec.cors,\n                log: log,\n                getBucket: function(id) {\n                    return upload.bucket.getName(id);\n                },\n                getHost: function(id) {\n                    return upload.host.getName(id);\n                },\n                getKey: function(id) {\n                    return upload.key.urlSafe(id);\n                }\n            }),\n\n            completeMultipart: new qq.s3.CompleteMultipartAjaxRequester({\n                endpointStore: endpointStore,\n                signatureSpec: signature,\n                cors: spec.cors,\n                log: log,\n                getBucket: function(id) {\n                    return upload.bucket.getName(id);\n                },\n                getHost: function(id) {\n                    return upload.host.getName(id);\n                },\n                getKey: function(id) {\n                    return upload.key.urlSafe(id);\n                }\n            }),\n\n            initiateMultipart: new qq.s3.InitiateMultipartAjaxRequester({\n                filenameParam: filenameParam,\n                endpointStore: endpointStore,\n                paramsStore: paramsStore,\n                signatureSpec: signature,\n                aclStore: aclStore,\n                reducedRedundancy: reducedRedundancy,\n                serverSideEncryption: serverSideEncryption,\n                cors: spec.cors,\n                log: log,\n                getContentType: function(id) {\n                    return handler._getMimeType(id);\n                },\n                getBucket: function(id) {\n                    return upload.bucket.getName(id);\n                },\n                getHost: function(id) {\n                    return upload.host.getName(id);\n                },\n                getKey: function(id) {\n                    return upload.key.urlSafe(id);\n                },\n                getName: function(id) {\n                    return getName(id);\n                }\n            }),\n\n            policySignature: new qq.s3.RequestSigner({\n                expectingPolicy: true,\n                signatureSpec: signature,\n                cors: spec.cors,\n                log: log\n            }),\n\n            restSignature: new qq.s3.RequestSigner({\n                endpointStore: endpointStore,\n                signatureSpec: signature,\n                cors: spec.cors,\n                log: log\n            })\n        },\n\n        simple = {\n            /**\n             * Used for simple (non-chunked) uploads to determine the parameters to send along with the request.  Part of this\n             * process involves asking the local server to sign the request, so this function returns a promise.  The promise\n             * is fulfilled when all parameters are determined, or when we determine that all parameters cannot be calculated\n             * due to some error.\n             *\n             * @param id File ID\n             * @returns {qq.Promise}\n             */\n            initParams: function(id) {\n                /*jshint -W040 */\n                var customParams = paramsStore.get(id);\n                customParams[filenameParam] = getName(id);\n\n                return qq.s3.util.generateAwsParams({\n                    endpoint: endpointStore.get(id),\n                    clockDrift: clockDrift,\n                    params: customParams,\n                    type: handler._getMimeType(id),\n                    bucket: upload.bucket.getName(id),\n                    key: handler.getThirdPartyFileId(id),\n                    accessKey: credentialsProvider.get().accessKey,\n                    sessionToken: credentialsProvider.get().sessionToken,\n                    acl: aclStore.get(id),\n                    expectedStatus: expectedStatus,\n                    minFileSize: validation.minSizeLimit,\n                    maxFileSize: validation.maxSizeLimit,\n                    reducedRedundancy: reducedRedundancy,\n                    region: region,\n                    serverSideEncryption: serverSideEncryption,\n                    signatureVersion: signature.version,\n                    log: log\n                },\n                qq.bind(requesters.policySignature.getSignature, this, id));\n            },\n\n            send: function(id) {\n                var promise = new qq.Promise(),\n                    xhr = handler._createXhr(id),\n                    fileOrBlob = handler.getFile(id);\n\n                handler._registerProgressHandler(id);\n                upload.track(id, xhr).then(promise.success, promise.failure);\n\n                // Delegate to a function the sets up the XHR request and notifies us when it is ready to be sent, along w/ the payload.\n                simple.setup(id, xhr, fileOrBlob).then(function(toSend) {\n                    log(\"Sending upload request for \" + id);\n                    xhr.send(toSend);\n                }, promise.failure);\n\n                return promise;\n            },\n\n            /**\n             * Starts the upload process by delegating to an async function that determine parameters to be attached to the\n             * request.  If all params can be determined, we are called back with the params and the caller of this function is\n             * informed by invoking the `success` method on the promise returned by this function, passing the payload of the\n             * request.  If some error occurs here, we delegate to a function that signals a failure for this upload attempt.\n             *\n             * Note that this is only used by the simple (non-chunked) upload process.\n             *\n             * @param id File ID\n             * @param xhr XMLHttpRequest to use for the upload\n             * @param fileOrBlob `File` or `Blob` to send\n             * @returns {qq.Promise}\n             */\n            setup: function(id, xhr, fileOrBlob) {\n                var formData = new FormData(),\n                    endpoint = endpointStore.get(id),\n                    url = endpoint,\n                    promise = new qq.Promise();\n\n                simple.initParams(id).then(\n                    // Success - all params determined\n                    function(awsParams) {\n                        xhr.open(\"POST\", url, true);\n\n                        qq.obj2FormData(awsParams, formData);\n\n                        // AWS requires the file field be named \"file\".\n                        formData.append(\"file\", fileOrBlob);\n\n                        promise.success(formData);\n                    },\n\n                    // Failure - we couldn't determine some params (likely the signature)\n                    function(errorMessage) {\n                        promise.failure({error: errorMessage});\n                    }\n                );\n\n                return promise;\n            }\n        },\n\n        upload = {\n            /**\n             * Note that this is called when an upload has reached a termination point,\n             * regardless of success/failure.  For example, it is called when we have\n             * encountered an error during the upload or when the file may have uploaded successfully.\n             *\n             * @param id file ID\n             */\n            bucket: {\n                promise: function(id) {\n                    var promise = new qq.Promise(),\n                        cachedBucket = handler._getFileState(id).bucket;\n\n                    if (cachedBucket) {\n                        promise.success(cachedBucket);\n                    }\n                    else {\n                        onGetBucket(id).then(function(bucket) {\n                            handler._getFileState(id).bucket = bucket;\n                            promise.success(bucket);\n                        }, promise.failure);\n                    }\n\n                    return promise;\n                },\n\n                getName: function(id) {\n                    return handler._getFileState(id).bucket;\n                }\n            },\n\n            host: {\n                promise: function(id) {\n                    var promise = new qq.Promise(),\n                        cachedHost = handler._getFileState(id).host;\n\n                    if (cachedHost) {\n                        promise.success(cachedHost);\n                    }\n                    else {\n                        onGetHost(id).then(function(host) {\n                            handler._getFileState(id).host = host;\n                            promise.success(host);\n                        }, promise.failure);\n                    }\n\n                    return promise;\n                },\n\n                getName: function(id) {\n                    return handler._getFileState(id).host;\n                }\n            },\n\n            done: function(id, xhr) {\n                var response = upload.response.parse(id, xhr),\n                    isError = response.success !== true;\n\n                if (isError && upload.response.shouldReset(response.code)) {\n                    log(\"This is an unrecoverable error, we must restart the upload entirely on the next retry attempt.\", \"error\");\n                    response.reset = true;\n                }\n\n                return {\n                    success: !isError,\n                    response: response\n                };\n            },\n\n            key: {\n                promise: function(id) {\n                    var promise = new qq.Promise(),\n                        key = handler.getThirdPartyFileId(id);\n\n                    /* jshint eqnull:true */\n                    if (key == null) {\n                        handler._setThirdPartyFileId(id, promise);\n                        onGetKeyName(id, getName(id)).then(\n                            function(keyName) {\n                                handler._setThirdPartyFileId(id, keyName);\n                                promise.success(keyName);\n                            },\n                            function(errorReason) {\n                                handler._setThirdPartyFileId(id, null);\n                                promise.failure(errorReason);\n                            }\n                        );\n                    }\n                    else if (qq.isGenericPromise(key)) {\n                        key.then(promise.success, promise.failure);\n                    }\n                    else {\n                        promise.success(key);\n                    }\n\n                    return promise;\n                },\n\n                urlSafe: function(id) {\n                    var encodedKey = handler.getThirdPartyFileId(id);\n                    return qq.s3.util.uriEscapePath(encodedKey);\n                }\n            },\n\n            response: {\n                parse: function(id, xhr) {\n                    var response = {},\n                        parsedErrorProps;\n\n                    try {\n                        log(qq.format(\"Received response status {} with body: {}\", xhr.status, xhr.responseText));\n\n                        if (xhr.status === expectedStatus) {\n                            response.success = true;\n                        }\n                        else {\n                            parsedErrorProps = upload.response.parseError(xhr.responseText);\n\n                            if (parsedErrorProps) {\n                                response.error = parsedErrorProps.message;\n                                response.code = parsedErrorProps.code;\n                            }\n                        }\n                    }\n                    catch (error) {\n                        log(\"Error when attempting to parse xhr response text (\" + error.message + \")\", \"error\");\n                    }\n\n                    return response;\n                },\n\n                /**\n                 * This parses an XML response by extracting the \"Message\" and \"Code\" elements that accompany AWS error responses.\n                 *\n                 * @param awsResponseXml XML response from AWS\n                 * @returns {object} Object w/ `code` and `message` properties, or undefined if we couldn't find error info in the XML document.\n                 */\n                parseError: function(awsResponseXml) {\n                    var parser = new DOMParser(),\n                        parsedDoc = parser.parseFromString(awsResponseXml, \"application/xml\"),\n                        errorEls = parsedDoc.getElementsByTagName(\"Error\"),\n                        errorDetails = {},\n                        codeEls, messageEls;\n\n                    if (errorEls.length) {\n                        codeEls = parsedDoc.getElementsByTagName(\"Code\");\n                        messageEls = parsedDoc.getElementsByTagName(\"Message\");\n\n                        if (messageEls.length) {\n                            errorDetails.message = messageEls[0].textContent;\n                        }\n\n                        if (codeEls.length) {\n                            errorDetails.code = codeEls[0].textContent;\n                        }\n\n                        return errorDetails;\n                    }\n                },\n\n                // Determine if the upload should be restarted on the next retry attempt\n                // based on the error code returned in the response from AWS.\n                shouldReset: function(errorCode) {\n                    /*jshint -W014 */\n                    return errorCode === \"EntityTooSmall\"\n                        || errorCode === \"InvalidPart\"\n                        || errorCode === \"InvalidPartOrder\"\n                        || errorCode === \"NoSuchUpload\";\n                }\n            },\n\n            start: function(params) {\n                var id = params.id;\n                var optChunkIdx = params.chunkIdx;\n\n                var promise = new qq.Promise();\n\n                upload.key.promise(id).then(function() {\n                    upload.bucket.promise(id).then(function() {\n                        upload.host.promise(id).then(function() {\n                            /* jshint eqnull:true */\n                            if (optChunkIdx == null) {\n                                simple.send(id).then(promise.success, promise.failure);\n                            }\n                            else {\n                                chunked.send(id, optChunkIdx).then(promise.success, promise.failure);\n                            }\n                        });\n                    });\n                },\n                function(errorReason) {\n                    promise.failure({error: errorReason});\n                });\n\n                return promise;\n            },\n\n            track: function(id, xhr, optChunkIdx) {\n                var promise = new qq.Promise();\n\n                xhr.onreadystatechange = function() {\n                    if (xhr.readyState === 4) {\n                        var result;\n\n                        /* jshint eqnull:true */\n                        if (optChunkIdx == null) {\n                            result = upload.done(id, xhr);\n                            promise[result.success ? \"success\" : \"failure\"](result.response, xhr);\n                        }\n                        else {\n                            chunked.done(id, xhr, optChunkIdx);\n                            result = upload.done(id, xhr);\n                            promise[result.success ? \"success\" : \"failure\"](result.response, xhr);\n                        }\n                    }\n                };\n\n                return promise;\n            }\n        };\n\n    qq.extend(this, {\n        uploadChunk: upload.start,\n        uploadFile: function(id) {\n            return upload.start({ id: id });\n        }\n    });\n\n    qq.extend(this, new qq.XhrUploadHandler({\n        options: qq.extend({namespace: \"s3\"}, spec),\n        proxy: qq.extend({getEndpoint: spec.endpointStore.get}, proxy)\n    }));\n\n    qq.override(this, function(super_) {\n        return {\n            expunge: function(id) {\n                var uploadId = handler._getPersistableData(id) && handler._getPersistableData(id).uploadId,\n                    existedInLocalStorage = handler._maybeDeletePersistedChunkData(id);\n\n                if (uploadId !== undefined && existedInLocalStorage) {\n                    requesters.abortMultipart.send(id, uploadId);\n                }\n\n                super_.expunge(id);\n            },\n\n            finalizeChunks: function(id) {\n                return chunked.combine(id);\n            },\n\n            _getLocalStorageId: function(id) {\n                var baseStorageId = super_._getLocalStorageId(id),\n                    bucketName = upload.bucket.getName(id);\n\n                return baseStorageId + \"-\" + bucketName;\n            }\n        };\n    });\n};\n"
  },
  {
    "path": "client/js/s3/uploader.basic.js",
    "content": "/*globals qq */\n/**\n * This defines FineUploaderBasic mode w/ support for uploading to S3, which provides all the basic\n * functionality of Fine Uploader Basic as well as code to handle uploads directly to S3.\n * Some inherited options and API methods have a special meaning in the context of the S3 uploader.\n */\n(function() {\n    \"use strict\";\n\n    qq.s3.FineUploaderBasic = function(o) {\n        var options = {\n            request: {\n                // public key (required for server-side signing, ignored if `credentials` have been provided)\n                accessKey: null,\n\n                // padding, in milliseconds, to add to the x-amz-date header & the policy expiration date\n                clockDrift: 0\n            },\n\n            objectProperties: {\n                acl: \"private\",\n\n                // string or a function which may be promissory\n                bucket: qq.bind(function(id) {\n                    return qq.s3.util.getBucket(this.getEndpoint(id));\n                }, this),\n\n                // string or a function which may be promissory - only used for V4 multipart uploads\n                host: qq.bind(function(id) {\n                    return (/(?:http|https):\\/\\/(.+)(?:\\/.+)?/).exec(this._endpointStore.get(id))[1];\n                }, this),\n\n                // 'uuid', 'filename', or a function which may be promissory\n                key: \"uuid\",\n\n                reducedRedundancy: false,\n\n                // Defined at http://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region\n                region: \"us-east-1\",\n\n                serverSideEncryption: false\n            },\n\n            credentials: {\n                // Public key (required).\n                accessKey: null,\n                // Private key (required).\n                secretKey: null,\n                // Expiration date for the credentials (required).  May be an ISO string or a `Date`.\n                expiration: null,\n                // Temporary credentials session token.\n                // Only required for temporary credentials obtained via AssumeRoleWithWebIdentity.\n                sessionToken: null\n            },\n\n            // All but `version` are ignored if `credentials` is provided.\n            signature: {\n                customHeaders: {},\n                endpoint: null,\n                version: 2\n            },\n\n            uploadSuccess: {\n                endpoint: null,\n\n                method: \"POST\",\n\n                // In addition to the default params sent by Fine Uploader\n                params: {},\n\n                customHeaders: {}\n            },\n\n            // required if non-File-API browsers, such as IE9 and older, are used\n            iframeSupport: {\n                localBlankPagePath: null\n            },\n\n            chunking: {\n                // minimum part size is 5 MiB when uploading to S3\n                partSize: 5242880\n            },\n\n            cors: {\n                allowXdr: true\n            },\n\n            callbacks: {\n                onCredentialsExpired: function() {}\n            }\n        };\n\n        // Replace any default options with user defined ones\n        qq.extend(options, o, true);\n\n        if (!this.setCredentials(options.credentials, true)) {\n            this._currentCredentials.accessKey = options.request.accessKey;\n        }\n\n        this._aclStore = this._createStore(options.objectProperties.acl);\n\n        // Call base module\n        qq.FineUploaderBasic.call(this, options);\n\n        this._uploadSuccessParamsStore = this._createStore(this._options.uploadSuccess.params);\n        this._uploadSuccessEndpointStore = this._createStore(this._options.uploadSuccess.endpoint);\n\n        // This will hold callbacks for failed uploadSuccess requests that will be invoked on retry.\n        // Indexed by file ID.\n        this._failedSuccessRequestCallbacks = {};\n\n        // Holds S3 keys for file representations constructed from a session request.\n        this._cannedKeys = {};\n        // Holds S3 buckets for file representations constructed from a session request.\n        this._cannedBuckets = {};\n\n        this._buckets = {};\n        this._hosts = {};\n    };\n\n    // Inherit basic public & private API methods.\n    qq.extend(qq.s3.FineUploaderBasic.prototype, qq.basePublicApi);\n    qq.extend(qq.s3.FineUploaderBasic.prototype, qq.basePrivateApi);\n    qq.extend(qq.s3.FineUploaderBasic.prototype, qq.nonTraditionalBasePublicApi);\n    qq.extend(qq.s3.FineUploaderBasic.prototype, qq.nonTraditionalBasePrivateApi);\n\n    // Define public & private API methods for this module.\n    qq.extend(qq.s3.FineUploaderBasic.prototype, {\n        getBucket: function(id) {\n            if (this._cannedBuckets[id] == null) {\n                return this._buckets[id];\n            }\n            return this._cannedBuckets[id];\n        },\n\n        /**\n         * @param id File ID\n         * @returns {*} Key name associated w/ the file, if one exists\n         */\n        getKey: function(id) {\n            /* jshint eqnull:true */\n            if (this._cannedKeys[id] == null) {\n                return this._handler.getThirdPartyFileId(id);\n            }\n\n            return this._cannedKeys[id];\n        },\n\n        /**\n         * Override the parent's reset function to cleanup various S3-related items.\n         */\n        reset: function() {\n            qq.FineUploaderBasic.prototype.reset.call(this);\n            this._failedSuccessRequestCallbacks = [];\n            this._buckets = {};\n            this._hosts = {};\n        },\n\n        setCredentials: function(credentials, ignoreEmpty) {\n            if (credentials && credentials.secretKey) {\n                if (!credentials.accessKey) {\n                    throw new qq.Error(\"Invalid credentials: no accessKey\");\n                }\n                else if (!credentials.expiration) {\n                    throw new qq.Error(\"Invalid credentials: no expiration\");\n                }\n                else {\n                    this._currentCredentials = qq.extend({}, credentials);\n\n                    // Ensure expiration is a `Date`.  If initially a string, assuming it is in ISO format.\n                    if (qq.isString(credentials.expiration)) {\n                        this._currentCredentials.expiration = new Date(credentials.expiration);\n                    }\n                }\n\n                return true;\n            }\n            else if (!ignoreEmpty) {\n                throw new qq.Error(\"Invalid credentials parameter!\");\n            }\n            else {\n                this._currentCredentials = {};\n            }\n        },\n\n        setAcl: function(acl, id) {\n            this._aclStore.set(acl, id);\n        },\n\n        /**\n         * Ensures the parent's upload handler creator passes any additional S3-specific options to the handler as well\n         * as information required to instantiate the specific handler based on the current browser's capabilities.\n         *\n         * @returns {qq.UploadHandlerController}\n         * @private\n         */\n        _createUploadHandler: function() {\n            var self = this,\n                additionalOptions = {\n                    aclStore: this._aclStore,\n                    getBucket: qq.bind(this._determineBucket, this),\n                    getHost: qq.bind(this._determineHost, this),\n                    getKeyName: qq.bind(this._determineKeyName, this),\n                    iframeSupport: this._options.iframeSupport,\n                    objectProperties: this._options.objectProperties,\n                    signature: this._options.signature,\n                    clockDrift: this._options.request.clockDrift,\n                    // pass size limit validation values to include in the request so AWS enforces this server-side\n                    validation: {\n                        minSizeLimit: this._options.validation.minSizeLimit,\n                        maxSizeLimit: this._options.validation.sizeLimit\n                    }\n                };\n\n            // We assume HTTP if it is missing from the start of the endpoint string.\n            qq.override(this._endpointStore, function(super_) {\n                return {\n                    get: function(id) {\n                        var endpoint = super_.get(id);\n\n                        if (endpoint.indexOf(\"http\") < 0) {\n                            return \"http://\" + endpoint;\n                        }\n\n                        return endpoint;\n                    }\n                };\n            });\n\n            // Some param names should be lower case to avoid signature mismatches\n            qq.override(this._paramsStore, function(super_) {\n                return {\n                    get: function(id) {\n                        var oldParams = super_.get(id),\n                            modifiedParams = {};\n\n                        qq.each(oldParams, function(name, val) {\n                            var paramName = name;\n\n                            if (qq.indexOf(qq.s3.util.CASE_SENSITIVE_PARAM_NAMES, paramName) < 0) {\n                                paramName = paramName.toLowerCase();\n                            }\n\n                            modifiedParams[paramName] = qq.isFunction(val) ? val() : val;\n                        });\n\n                        return modifiedParams;\n                    }\n                };\n            });\n\n            additionalOptions.signature.credentialsProvider = {\n                get: function() {\n                    return self._currentCredentials;\n                },\n\n                onExpired: function() {\n                    var updateCredentials = new qq.Promise(),\n                        callbackRetVal = self._options.callbacks.onCredentialsExpired();\n\n                    if (qq.isGenericPromise(callbackRetVal)) {\n                        callbackRetVal.then(function(credentials) {\n                            try {\n                                self.setCredentials(credentials);\n                                updateCredentials.success();\n                            }\n                            catch (error) {\n                                self.log(\"Invalid credentials returned from onCredentialsExpired callback! (\" + error.message + \")\", \"error\");\n                                updateCredentials.failure(\"onCredentialsExpired did not return valid credentials.\");\n                            }\n                        }, function(errorMsg) {\n                            self.log(\"onCredentialsExpired callback indicated failure! (\" + errorMsg + \")\", \"error\");\n                            updateCredentials.failure(\"onCredentialsExpired callback failed.\");\n                        });\n                    }\n                    else {\n                        self.log(\"onCredentialsExpired callback did not return a promise!\", \"error\");\n                        updateCredentials.failure(\"Unexpected return value for onCredentialsExpired.\");\n                    }\n\n                    return updateCredentials;\n                }\n            };\n\n            return qq.FineUploaderBasic.prototype._createUploadHandler.call(this, additionalOptions, \"s3\");\n        },\n\n        _determineObjectPropertyValue: function(id, property) {\n            var maybe = this._options.objectProperties[property],\n                promise = new qq.Promise(),\n                self = this;\n\n            if (qq.isFunction(maybe)) {\n                maybe = maybe(id);\n                if (qq.isGenericPromise(maybe)) {\n                    promise = maybe;\n                }\n                else {\n                    promise.success(maybe);\n                }\n            }\n            else if (qq.isString(maybe)) {\n                promise.success(maybe);\n            }\n\n            promise.then(\n                function success(value) {\n                    self[\"_\" + property + \"s\"][id] = value;\n                },\n\n                function failure(errorMsg) {\n                    qq.log(\"Problem determining \" + property + \" for ID \" + id + \" (\" + errorMsg + \")\", \"error\");\n                }\n            );\n\n            return promise;\n        },\n\n        _determineBucket: function(id) {\n            return this._determineObjectPropertyValue(id, \"bucket\");\n        },\n\n        _determineHost: function(id) {\n            return this._determineObjectPropertyValue(id, \"host\");\n        },\n\n        /**\n         * Determine the file's key name and passes it to the caller via a promissory callback.  This also may\n         * delegate to an integrator-defined function that determines the file's key name on demand,\n         * which also may be promissory.\n         *\n         * @param id ID of the file\n         * @param filename Name of the file\n         * @returns {qq.Promise} A promise that will be fulfilled when the key name has been determined (and will be passed to the caller via the success callback).\n         * @private\n         */\n        _determineKeyName: function(id, filename) {\n            /*jshint -W015*/\n            var promise = new qq.Promise(),\n                keynameLogic = this._options.objectProperties.key,\n                extension = qq.getExtension(filename),\n                onGetKeynameFailure = promise.failure,\n                onGetKeynameSuccess = function(keyname, extension) {\n                    var keynameToUse = keyname;\n\n                    if (extension !== undefined) {\n                        keynameToUse += \".\" + extension;\n                    }\n\n                    promise.success(keynameToUse);\n                };\n\n            switch (keynameLogic) {\n                case \"uuid\":\n                    onGetKeynameSuccess(this.getUuid(id), extension);\n                    break;\n                case \"filename\":\n                    onGetKeynameSuccess(filename);\n                    break;\n                default:\n                    if (qq.isFunction(keynameLogic)) {\n                        this._handleKeynameFunction(keynameLogic, id, onGetKeynameSuccess, onGetKeynameFailure);\n                    }\n                    else {\n                        this.log(keynameLogic + \" is not a valid value for the s3.keyname option!\", \"error\");\n                        onGetKeynameFailure();\n                    }\n            }\n\n            return promise;\n        },\n\n        /**\n         * Called by the internal onUpload handler if the integrator has supplied a function to determine\n         * the file's key name.  The integrator's function may be promissory.  We also need to fulfill\n         * the promise contract associated with the caller as well.\n         *\n         * @param keynameFunc Integrator-supplied function that must be executed to determine the key name.  May be promissory.\n         * @param id ID of the associated file\n         * @param successCallback Invoke this if key name retrieval is successful, passing in the key name.\n         * @param failureCallback Invoke this if key name retrieval was unsuccessful.\n         * @private\n         */\n        _handleKeynameFunction: function(keynameFunc, id, successCallback, failureCallback) {\n            var self = this,\n                onSuccess = function(keyname) {\n                    successCallback(keyname);\n                },\n                onFailure = function(reason) {\n                    self.log(qq.format(\"Failed to retrieve key name for {}.  Reason: {}\", id, reason || \"null\"), \"error\");\n                    failureCallback(reason);\n                },\n                keyname = keynameFunc.call(this, id);\n\n            if (qq.isGenericPromise(keyname)) {\n                keyname.then(onSuccess, onFailure);\n            }\n            /*jshint -W116*/\n            else if (keyname == null) {\n                onFailure();\n            }\n            else {\n                onSuccess(keyname);\n            }\n        },\n\n        _getEndpointSpecificParams: function(id, response, maybeXhr) {\n            var params = {\n                key: this.getKey(id),\n                uuid: this.getUuid(id),\n                name: this.getName(id),\n                bucket: this.getBucket(id)\n            };\n\n            if (maybeXhr && maybeXhr.getResponseHeader(\"ETag\")) {\n                params.etag = maybeXhr.getResponseHeader(\"ETag\");\n            }\n            else if (response.etag) {\n                params.etag = response.etag;\n            }\n\n            return params;\n        },\n\n        // Hooks into the base internal `_onSubmitDelete` to add key and bucket params to the delete file request.\n        _onSubmitDelete: function(id, onSuccessCallback) {\n            var additionalMandatedParams = {\n                key: this.getKey(id),\n                bucket: this.getBucket(id)\n            };\n\n            return qq.FineUploaderBasic.prototype._onSubmitDelete.call(this, id, onSuccessCallback, additionalMandatedParams);\n        },\n\n        _addCannedFile: function(sessionData) {\n            var id;\n\n            /* jshint eqnull:true */\n            if (sessionData.s3Key == null) {\n                throw new qq.Error(\"Did not find s3Key property in server session response.  This is required!\");\n            }\n            else {\n                id = qq.FineUploaderBasic.prototype._addCannedFile.apply(this, arguments);\n                this._cannedKeys[id] = sessionData.s3Key;\n                this._cannedBuckets[id] = sessionData.s3Bucket;\n            }\n\n            return id;\n        }\n    });\n}());\n"
  },
  {
    "path": "client/js/s3/uploader.js",
    "content": "/*globals qq */\n/**\n * This defines FineUploader mode w/ support for uploading to S3, which provides all the basic\n * functionality of Fine Uploader as well as code to handle uploads directly to S3.\n * This module inherits all logic from FineUploader mode and FineUploaderBasicS3 mode and adds some UI-related logic\n * specific to the upload-to-S3 workflow.  Some inherited options and API methods have a special meaning\n * in the context of the S3 uploader.\n */\n(function() {\n    \"use strict\";\n\n    qq.s3.FineUploader = function(o) {\n        var options = {\n            failedUploadTextDisplay: {\n                mode: \"custom\"\n            }\n        };\n\n        // Replace any default options with user defined ones\n        qq.extend(options, o, true);\n\n        // Inherit instance data from FineUploader, which should in turn inherit from s3.FineUploaderBasic.\n        qq.FineUploader.call(this, options, \"s3\");\n\n        if (!qq.supportedFeatures.ajaxUploading && options.iframeSupport.localBlankPagePath === undefined) {\n            this._options.element.innerHTML = \"<div>You MUST set the <code>localBlankPagePath</code> property \" +\n                \"of the <code>iframeSupport</code> option since this browser does not support the File API!</div>\";\n        }\n    };\n\n    // Inherit the API methods from FineUploaderBasicS3\n    qq.extend(qq.s3.FineUploader.prototype, qq.s3.FineUploaderBasic.prototype);\n\n    // Inherit public and private API methods related to UI\n    qq.extend(qq.s3.FineUploader.prototype, qq.uiPublicApi);\n    qq.extend(qq.s3.FineUploader.prototype, qq.uiPrivateApi);\n}());\n"
  },
  {
    "path": "client/js/s3/util.js",
    "content": "/*globals qq */\nqq.s3 = qq.s3 || {};\n\nqq.s3.util = qq.s3.util || (function() {\n    \"use strict\";\n\n    return {\n        ALGORITHM_PARAM_NAME: \"x-amz-algorithm\",\n\n        AWS_PARAM_PREFIX: \"x-amz-meta-\",\n\n        CREDENTIAL_PARAM_NAME: \"x-amz-credential\",\n\n        DATE_PARAM_NAME: \"x-amz-date\",\n\n        REDUCED_REDUNDANCY_PARAM_NAME: \"x-amz-storage-class\",\n        REDUCED_REDUNDANCY_PARAM_VALUE: \"REDUCED_REDUNDANCY\",\n\n        SERVER_SIDE_ENCRYPTION_PARAM_NAME: \"x-amz-server-side-encryption\",\n        SERVER_SIDE_ENCRYPTION_PARAM_VALUE: \"AES256\",\n\n        SESSION_TOKEN_PARAM_NAME: \"x-amz-security-token\",\n\n        V4_ALGORITHM_PARAM_VALUE: \"AWS4-HMAC-SHA256\",\n\n        V4_SIGNATURE_PARAM_NAME: \"x-amz-signature\",\n\n        CASE_SENSITIVE_PARAM_NAMES: [\n            \"Cache-Control\",\n            \"Content-Disposition\",\n            \"Content-Encoding\",\n            \"Content-MD5\"\n        ],\n\n        UNSIGNABLE_REST_HEADER_NAMES: [\n            \"Cache-Control\",\n            \"Content-Disposition\",\n            \"Content-Encoding\",\n            \"Content-MD5\"\n        ],\n\n        UNPREFIXED_PARAM_NAMES: [\n            \"Cache-Control\",\n            \"Content-Disposition\",\n            \"Content-Encoding\",\n            \"Content-MD5\",\n            \"x-amz-server-side-encryption\",\n            \"x-amz-server-side-encryption-aws-kms-key-id\",\n            \"x-amz-server-side-encryption-customer-algorithm\",\n            \"x-amz-server-side-encryption-customer-key\",\n            \"x-amz-server-side-encryption-customer-key-MD5\"\n        ],\n\n        /**\n         * This allows for the region to be specified in the bucket's endpoint URL, or not.\n         *\n         * Examples of some valid endpoints are:\n         *     http://foo.s3.amazonaws.com\n         *     https://foo.s3.amazonaws.com\n         *     http://foo.s3-ap-northeast-1.amazonaws.com\n         *     foo.s3.amazonaws.com\n         *     http://foo.bar.com\n         *     http://s3.amazonaws.com/foo.bar.com\n         * ...etc\n         *\n         * @param endpoint The bucket's URL.\n         * @returns {String || undefined} The bucket name, or undefined if the URL cannot be parsed.\n         */\n        getBucket: function(endpoint) {\n            var patterns = [\n                    //bucket in domain\n                    /^(?:https?:\\/\\/)?([a-z0-9.\\-_]+)\\.s3(?:-[a-z0-9\\-]+)?\\.amazonaws\\.com/i,\n                    //bucket in path\n                    /^(?:https?:\\/\\/)?s3(?:-[a-z0-9\\-]+)?\\.amazonaws\\.com\\/([a-z0-9.\\-_]+)/i,\n                    //custom domain\n                    /^(?:https?:\\/\\/)?([a-z0-9.\\-_]+)/i\n                ],\n                bucket;\n\n            qq.each(patterns, function(idx, pattern) {\n                var match = pattern.exec(endpoint);\n\n                if (match) {\n                    bucket = match[1];\n                    return false;\n                }\n            });\n\n            return bucket;\n        },\n\n        /** Create Prefixed request headers which are appropriate for S3.\n         *\n         * If the request header is appropriate for S3 (e.g. Cache-Control) then pass\n         * it along without a metadata prefix. For all other request header parameter names,\n         * apply qq.s3.util.AWS_PARAM_PREFIX before the name.\n         * See: http://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectPUT.html\n         */\n        _getPrefixedParamName: function(name) {\n            if (qq.indexOf(qq.s3.util.UNPREFIXED_PARAM_NAMES, name) >= 0) {\n                return name;\n            }\n            return qq.s3.util.AWS_PARAM_PREFIX + name;\n        },\n\n        /**\n         * Create a policy document to be signed and sent along with the S3 upload request.\n         *\n         * @param spec Object with properties use to construct the policy document.\n         * @returns {Object} Policy doc.\n         */\n        getPolicy: function(spec) {\n            var policy = {},\n                conditions = [],\n                bucket = spec.bucket,\n                date = spec.date,\n                drift = spec.clockDrift,\n                key = spec.key,\n                accessKey = spec.accessKey,\n                acl = spec.acl,\n                type = spec.type,\n                expectedStatus = spec.expectedStatus,\n                sessionToken = spec.sessionToken,\n                params = spec.params,\n                successRedirectUrl = qq.s3.util.getSuccessRedirectAbsoluteUrl(spec.successRedirectUrl),\n                minFileSize = spec.minFileSize,\n                maxFileSize = spec.maxFileSize,\n                reducedRedundancy = spec.reducedRedundancy,\n                region = spec.region,\n                serverSideEncryption = spec.serverSideEncryption,\n                signatureVersion = spec.signatureVersion;\n\n            policy.expiration = qq.s3.util.getPolicyExpirationDate(date, drift);\n\n            conditions.push({acl: acl});\n            conditions.push({bucket: bucket});\n\n            if (type) {\n                conditions.push({\"Content-Type\": type});\n            }\n\n            // jscs:disable requireCamelCaseOrUpperCaseIdentifiers\n            if (expectedStatus) {\n                conditions.push({success_action_status: expectedStatus.toString()});\n            }\n\n            if (successRedirectUrl) {\n                conditions.push({success_action_redirect: successRedirectUrl});\n            }\n            // jscs:enable\n            if (reducedRedundancy) {\n                conditions.push({});\n                conditions[conditions.length - 1][qq.s3.util.REDUCED_REDUNDANCY_PARAM_NAME] = qq.s3.util.REDUCED_REDUNDANCY_PARAM_VALUE;\n            }\n\n            if (sessionToken) {\n                conditions.push({});\n                conditions[conditions.length - 1][qq.s3.util.SESSION_TOKEN_PARAM_NAME] = sessionToken;\n            }\n\n            if (serverSideEncryption) {\n                conditions.push({});\n                conditions[conditions.length - 1][qq.s3.util.SERVER_SIDE_ENCRYPTION_PARAM_NAME] = qq.s3.util.SERVER_SIDE_ENCRYPTION_PARAM_VALUE;\n            }\n\n            if (signatureVersion === 2) {\n                conditions.push({key: key});\n            }\n            else if (signatureVersion === 4) {\n                conditions.push({});\n                conditions[conditions.length - 1][qq.s3.util.ALGORITHM_PARAM_NAME] = qq.s3.util.V4_ALGORITHM_PARAM_VALUE;\n\n                conditions.push({});\n                conditions[conditions.length - 1].key = key;\n\n                conditions.push({});\n                conditions[conditions.length - 1][qq.s3.util.CREDENTIAL_PARAM_NAME] =\n                    qq.s3.util.getV4CredentialsString({date: date, key: accessKey, region: region});\n\n                conditions.push({});\n                conditions[conditions.length - 1][qq.s3.util.DATE_PARAM_NAME] =\n                    qq.s3.util.getV4PolicyDate(date, drift);\n            }\n\n            // user metadata\n            qq.each(params, function(name, val) {\n                var awsParamName = qq.s3.util._getPrefixedParamName(name),\n                    param = {};\n\n                if (qq.indexOf(qq.s3.util.UNPREFIXED_PARAM_NAMES, awsParamName) >= 0) {\n                    param[awsParamName] = val;\n                }\n                else {\n                    param[awsParamName] = encodeURIComponent(val);\n                }\n\n                conditions.push(param);\n            });\n\n            policy.conditions = conditions;\n\n            qq.s3.util.enforceSizeLimits(policy, minFileSize, maxFileSize);\n\n            return policy;\n        },\n\n        /**\n         * Update a previously constructed policy document with updated credentials.  Currently, this only requires we\n         * update the session token.  This is only relevant if requests are being signed client-side.\n         *\n         * @param policy Live policy document\n         * @param newSessionToken Updated session token.\n         */\n        refreshPolicyCredentials: function(policy, newSessionToken) {\n            var sessionTokenFound = false;\n\n            qq.each(policy.conditions, function(oldCondIdx, oldCondObj) {\n                qq.each(oldCondObj, function(oldCondName, oldCondVal) {\n                    if (oldCondName === qq.s3.util.SESSION_TOKEN_PARAM_NAME) {\n                        oldCondObj[oldCondName] = newSessionToken;\n                        sessionTokenFound = true;\n                    }\n                });\n            });\n\n            if (!sessionTokenFound) {\n                policy.conditions.push({});\n                policy.conditions[policy.conditions.length - 1][qq.s3.util.SESSION_TOKEN_PARAM_NAME] = newSessionToken;\n            }\n        },\n\n        /**\n         * Generates all parameters to be passed along with the S3 upload request.  This includes invoking a callback\n         * that is expected to asynchronously retrieve a signature for the policy document.  Note that the server\n         * signing the request should reject a \"tainted\" policy document that includes unexpected values, since it is\n         * still possible for a malicious user to tamper with these values during policy document generation,\n         * before it is sent to the server for signing.\n         *\n         * @param spec Object with properties: `params`, `type`, `key`, `accessKey`, `acl`, `expectedStatus`, `successRedirectUrl`,\n         * `reducedRedundancy`, `region`, `serverSideEncryption`, `version`, and `log()`, along with any options associated with `qq.s3.util.getPolicy()`.\n         * @returns {qq.Promise} Promise that will be fulfilled once all parameters have been determined.\n         */\n        generateAwsParams: function(spec, signPolicyCallback) {\n            var awsParams = {},\n                customParams = spec.params,\n                promise = new qq.Promise(),\n                sessionToken = spec.sessionToken,\n                drift = spec.clockDrift,\n                type = spec.type,\n                key = spec.key,\n                accessKey = spec.accessKey,\n                acl = spec.acl,\n                expectedStatus = spec.expectedStatus,\n                successRedirectUrl = qq.s3.util.getSuccessRedirectAbsoluteUrl(spec.successRedirectUrl),\n                reducedRedundancy = spec.reducedRedundancy,\n                region = spec.region,\n                serverSideEncryption = spec.serverSideEncryption,\n                signatureVersion = spec.signatureVersion,\n                now = new Date(),\n                log = spec.log,\n                policyJson;\n\n            spec.date = now;\n            policyJson = qq.s3.util.getPolicy(spec);\n\n            awsParams.key = key;\n\n            if (type) {\n                awsParams[\"Content-Type\"] = type;\n            }\n            // jscs:disable requireCamelCaseOrUpperCaseIdentifiers\n            if (expectedStatus) {\n                awsParams.success_action_status = expectedStatus;\n            }\n\n            if (successRedirectUrl) {\n                awsParams.success_action_redirect = successRedirectUrl;\n            }\n            // jscs:enable\n            if (reducedRedundancy) {\n                awsParams[qq.s3.util.REDUCED_REDUNDANCY_PARAM_NAME] = qq.s3.util.REDUCED_REDUNDANCY_PARAM_VALUE;\n            }\n\n            if (serverSideEncryption) {\n                awsParams[qq.s3.util.SERVER_SIDE_ENCRYPTION_PARAM_NAME] = qq.s3.util.SERVER_SIDE_ENCRYPTION_PARAM_VALUE;\n            }\n\n            if (sessionToken) {\n                awsParams[qq.s3.util.SESSION_TOKEN_PARAM_NAME] = sessionToken;\n            }\n\n            awsParams.acl = acl;\n\n            // Custom (user-supplied) params must be prefixed with the value of `qq.s3.util.AWS_PARAM_PREFIX`.\n            // Params such as Cache-Control or Content-Disposition will not be prefixed.\n            // Prefixed param values will be URI encoded as well.\n            qq.each(customParams, function(name, val) {\n                var awsParamName = qq.s3.util._getPrefixedParamName(name);\n\n                if (qq.indexOf(qq.s3.util.UNPREFIXED_PARAM_NAMES, awsParamName) >= 0) {\n                    awsParams[awsParamName] = val;\n                }\n                else {\n                    awsParams[awsParamName] = encodeURIComponent(val);\n                }\n            });\n\n            if (signatureVersion === 2) {\n                awsParams.AWSAccessKeyId = accessKey;\n            }\n            else if (signatureVersion === 4) {\n                awsParams[qq.s3.util.ALGORITHM_PARAM_NAME] = qq.s3.util.V4_ALGORITHM_PARAM_VALUE;\n                awsParams[qq.s3.util.CREDENTIAL_PARAM_NAME] = qq.s3.util.getV4CredentialsString({date: now, key: accessKey, region: region});\n                awsParams[qq.s3.util.DATE_PARAM_NAME] = qq.s3.util.getV4PolicyDate(now, drift);\n            }\n\n            // Invoke a promissory callback that should provide us with a base64-encoded policy doc and an\n            // HMAC signature for the policy doc.\n            signPolicyCallback(policyJson).then(\n                function(policyAndSignature, updatedAccessKey, updatedSessionToken) {\n                    awsParams.policy = policyAndSignature.policy;\n\n                    if (spec.signatureVersion === 2) {\n                        awsParams.signature = policyAndSignature.signature;\n\n                        if (updatedAccessKey) {\n                            awsParams.AWSAccessKeyId = updatedAccessKey;\n                        }\n                    }\n                    else if (spec.signatureVersion === 4) {\n                        awsParams[qq.s3.util.V4_SIGNATURE_PARAM_NAME] = policyAndSignature.signature;\n                    }\n\n                    if (updatedSessionToken) {\n                        awsParams[qq.s3.util.SESSION_TOKEN_PARAM_NAME] = updatedSessionToken;\n                    }\n\n                    promise.success(awsParams);\n                },\n                function(errorMessage) {\n                    errorMessage = errorMessage || \"Can't continue further with request to S3 as we did not receive \" +\n                                                   \"a valid signature and policy from the server.\";\n\n                    log(\"Policy signing failed.  \" + errorMessage, \"error\");\n                    promise.failure(errorMessage);\n                }\n            );\n\n            return promise;\n        },\n\n        /**\n         * Add a condition to an existing S3 upload request policy document used to ensure AWS enforces any size\n         * restrictions placed on files server-side.  This is important to do, in case users mess with the client-side\n         * checks already in place.\n         *\n         * @param policy Policy document as an `Object`, with a `conditions` property already attached\n         * @param minSize Minimum acceptable size, in bytes\n         * @param maxSize Maximum acceptable size, in bytes (0 = unlimited)\n         */\n        enforceSizeLimits: function(policy, minSize, maxSize) {\n            var adjustedMinSize = minSize < 0 ? 0 : minSize,\n                // Adjust a maxSize of 0 to the largest possible integer, since we must specify a high and a low in the request\n                adjustedMaxSize = maxSize <= 0 ? 9007199254740992 : maxSize;\n\n            if (minSize > 0 || maxSize > 0) {\n                policy.conditions.push([\"content-length-range\", adjustedMinSize.toString(), adjustedMaxSize.toString()]);\n            }\n        },\n\n        getPolicyExpirationDate: function(date, drift) {\n            var adjustedDate = new Date(date.getTime() + drift);\n            return qq.s3.util.getPolicyDate(adjustedDate, 5);\n        },\n\n        getCredentialsDate: function(date) {\n            return date.getUTCFullYear() + \"\" +\n                (\"0\" + (date.getUTCMonth() + 1)).slice(-2) +\n                (\"0\" + date.getUTCDate()).slice(-2);\n        },\n\n        getPolicyDate: function(date, _minutesToAdd_) {\n            var minutesToAdd = _minutesToAdd_ || 0,\n                pad, r;\n\n            /*jshint -W014 */\n            // Is this going to be a problem if we encounter this moments before 2 AM just before daylight savings time ends?\n            date.setMinutes(date.getMinutes() + (minutesToAdd || 0));\n\n            if (Date.prototype.toISOString) {\n                return date.toISOString();\n            }\n            else {\n                pad = function(number) {\n                    r = String(number);\n\n                    if (r.length === 1) {\n                        r = \"0\" + r;\n                    }\n\n                    return r;\n                };\n\n                return date.getUTCFullYear()\n                    + \"-\" + pad(date.getUTCMonth() + 1)\n                    + \"-\" + pad(date.getUTCDate())\n                    + \"T\" + pad(date.getUTCHours())\n                    + \":\" + pad(date.getUTCMinutes())\n                    + \":\" + pad(date.getUTCSeconds())\n                    + \".\" + String((date.getUTCMilliseconds() / 1000).toFixed(3)).slice(2, 5)\n                    + \"Z\";\n            }\n        },\n\n        /**\n         * Looks at a response from S3 contained in an iframe and parses the query string in an attempt to identify\n         * the associated resource.\n         *\n         * @param iframe Iframe containing response\n         * @returns {{bucket: *, key: *, etag: *}}\n         */\n        parseIframeResponse: function(iframe) {\n            var doc = iframe.contentDocument || iframe.contentWindow.document,\n                queryString = doc.location.search,\n                match = /bucket=(.+)&key=(.+)&etag=(.+)/.exec(queryString);\n\n            if (match) {\n                return {\n                    bucket: match[1],\n                    key: match[2],\n                    etag: match[3].replace(/%22/g, \"\")\n                };\n            }\n        },\n\n        /**\n         * @param successRedirectUrl Relative or absolute location of success redirect page\n         * @returns {*|string} undefined if the parameter is undefined, otherwise the absolute location of the success redirect page\n         */\n        getSuccessRedirectAbsoluteUrl: function(successRedirectUrl) {\n            if (successRedirectUrl) {\n                var targetAnchorContainer = document.createElement(\"div\"),\n                    targetAnchor;\n\n                if (qq.ie7()) {\n                    // Note that we must make use of `innerHTML` for IE7 only instead of simply creating an anchor via\n                    // `document.createElement('a')` and setting the `href` attribute.  The latter approach does not allow us to\n                    // obtain an absolute URL in IE7 if the `endpoint` is a relative URL.\n                    targetAnchorContainer.innerHTML = \"<a href='\" + successRedirectUrl + \"'></a>\";\n                    targetAnchor = targetAnchorContainer.firstChild;\n                    return targetAnchor.href;\n                }\n                else {\n                    // IE8 and IE9 do not seem to derive an absolute URL from a relative URL using the `innerHTML`\n                    // approach above, so we'll just create an anchor this way and set it's `href` attribute.\n                    // Due to yet another quirk in IE8 and IE9, we have to set the `href` equal to itself\n                    // in order to ensure relative URLs will be properly parsed.\n                    targetAnchor = document.createElement(\"a\");\n                    targetAnchor.href = successRedirectUrl;\n                    targetAnchor.href = targetAnchor.href;\n                    return targetAnchor.href;\n                }\n            }\n        },\n\n        getV4CredentialsString: function(spec) {\n            return spec.key + \"/\" +\n                qq.s3.util.getCredentialsDate(spec.date) + \"/\" +\n                spec.region + \"/s3/aws4_request\";\n        },\n\n        getV4PolicyDate: function(date, drift) {\n            var adjustedDate = new Date(date.getTime() + drift);\n\n            return qq.s3.util.getCredentialsDate(adjustedDate) + \"T\" +\n                    (\"0\" + adjustedDate.getUTCHours()).slice(-2) +\n                    (\"0\" + adjustedDate.getUTCMinutes()).slice(-2) +\n                    (\"0\" + adjustedDate.getUTCSeconds()).slice(-2) +\n                    \"Z\";\n        },\n\n        // AWS employs a strict interpretation of [RFC 3986](http://tools.ietf.org/html/rfc3986#page-12).\n        // So, we must ensure all reserved characters listed in the spec are percent-encoded,\n        // and spaces are replaced with \"+\".\n        encodeQueryStringParam: function(param) {\n            var percentEncoded = encodeURIComponent(param);\n\n            // %-encode characters not handled by `encodeURIComponent` (to follow RFC 3986)\n            percentEncoded = percentEncoded.replace(/[!'()]/g, escape);\n\n            // %-encode characters not handled by `escape` (to follow RFC 3986)\n            percentEncoded = percentEncoded.replace(/\\*/g, \"%2A\");\n\n            // replace percent-encoded spaces with a \"+\"\n            return percentEncoded.replace(/%20/g, \"+\");\n        },\n        /**\n         * Escapes url part as for AWS requirements\n         * AWS uriEscapePath function pulled from aws-sdk-js licensed under Apache 2.0 - http://github.com/aws/aws-sdk-js\n         */\n        uriEscape: function(string) {\n            var output = encodeURIComponent(string);\n            output = output.replace(/[^A-Za-z0-9_.~\\-%]+/g, escape);\n            output = output.replace(/[*]/g, function(ch) {\n                return \"%\" + ch.charCodeAt(0).toString(16).toUpperCase();\n            });\n            return output;\n        },\n        /**\n         * Escapes a path as for AWS requirement\n         * AWS uriEscapePath function pulled from aws-sdk-js licensed under Apache 2.0 - http://github.com/aws/aws-sdk-js\n         */\n        uriEscapePath: function(path) {\n            var parts = [];\n            qq.each(path.split(\"/\"), function(idx, item) {\n                parts.push(qq.s3.util.uriEscape(item));\n            });\n            return parts.join(\"/\");\n        }\n    };\n}());\n"
  },
  {
    "path": "client/js/session.ajax.requester.js",
    "content": "/*globals qq, XMLHttpRequest*/\n/**\n * Thin module used to send GET requests to the server, expecting information about session\n * data used to initialize an uploader instance.\n *\n * @param spec Various options used to influence the associated request.\n * @constructor\n */\nqq.SessionAjaxRequester = function(spec) {\n    \"use strict\";\n\n    var requester,\n        options = {\n            endpoint: null,\n            customHeaders: {},\n            params: {},\n            cors: {\n                expected: false,\n                sendCredentials: false\n            },\n            onComplete: function(response, success, xhrOrXdr) {},\n            log: function(str, level) {}\n        };\n\n    qq.extend(options, spec);\n\n    function onComplete(id, xhrOrXdr, isError) {\n        var response = null;\n\n        /* jshint eqnull:true */\n        if (xhrOrXdr.responseText != null) {\n            try {\n                response = qq.parseJson(xhrOrXdr.responseText);\n            }\n            catch (err) {\n                options.log(\"Problem parsing session response: \" + err.message, \"error\");\n                isError = true;\n            }\n        }\n\n        options.onComplete(response, !isError, xhrOrXdr);\n    }\n\n    requester = qq.extend(this, new qq.AjaxRequester({\n        acceptHeader: \"application/json\",\n        validMethods: [\"GET\"],\n        method: \"GET\",\n        endpointStore: {\n            get: function() {\n                return options.endpoint;\n            }\n        },\n        customHeaders: options.customHeaders,\n        log: options.log,\n        onComplete: onComplete,\n        cors: options.cors\n    }));\n\n    qq.extend(this, {\n        queryServer: function() {\n            var params = qq.extend({}, options.params);\n\n            options.log(\"Session query request.\");\n\n            requester.initTransport(\"sessionRefresh\")\n                .withParams(params)\n                .withCacheBuster()\n                .send();\n        }\n    });\n};\n"
  },
  {
    "path": "client/js/session.js",
    "content": "/* globals qq */\n/**\n * Module used to control populating the initial list of files.\n *\n * @constructor\n */\nqq.Session = function(spec) {\n    \"use strict\";\n\n    var options = {\n        endpoint: null,\n        params: {},\n        customHeaders: {},\n        cors: {},\n        addFileRecord: function(sessionData) {},\n        log: function(message, level) {}\n    };\n\n    qq.extend(options, spec, true);\n\n    function isJsonResponseValid(response) {\n        if (qq.isArray(response)) {\n            return true;\n        }\n\n        options.log(\"Session response is not an array.\", \"error\");\n    }\n\n    function handleFileItems(fileItems, success, xhrOrXdr, promise) {\n        var someItemsIgnored = false;\n\n        success = success && isJsonResponseValid(fileItems);\n\n        if (success) {\n            qq.each(fileItems, function(idx, fileItem) {\n                /* jshint eqnull:true */\n                if (fileItem.uuid == null) {\n                    someItemsIgnored = true;\n                    options.log(qq.format(\"Session response item {} did not include a valid UUID - ignoring.\", idx), \"error\");\n                }\n                else if (fileItem.name == null) {\n                    someItemsIgnored = true;\n                    options.log(qq.format(\"Session response item {} did not include a valid name - ignoring.\", idx), \"error\");\n                }\n                else {\n                    try {\n                        options.addFileRecord(fileItem);\n                        return true;\n                    }\n                    catch (err) {\n                        someItemsIgnored = true;\n                        options.log(err.message, \"error\");\n                    }\n                }\n\n                return false;\n            });\n        }\n\n        promise[success && !someItemsIgnored ? \"success\" : \"failure\"](fileItems, xhrOrXdr);\n    }\n\n    // Initiate a call to the server that will be used to populate the initial file list.\n    // Returns a `qq.Promise`.\n    this.refresh = function() {\n        /*jshint indent:false */\n        var refreshEffort = new qq.Promise(),\n            refreshCompleteCallback = function(response, success, xhrOrXdr) {\n                handleFileItems(response, success, xhrOrXdr, refreshEffort);\n            },\n            requesterOptions = qq.extend({}, options),\n            requester = new qq.SessionAjaxRequester(\n                qq.extend(requesterOptions, {onComplete: refreshCompleteCallback})\n            );\n\n        requester.queryServer();\n\n        return refreshEffort;\n    };\n};\n"
  },
  {
    "path": "client/js/templating.js",
    "content": "/* globals qq */\n/* jshint -W065 */\n/**\n * Module responsible for rendering all Fine Uploader UI templates.  This module also asserts at least\n * a limited amount of control over the template elements after they are added to the DOM.\n * Wherever possible, this module asserts total control over template elements present in the DOM.\n *\n * @param spec Specification object used to control various templating behaviors\n * @constructor\n */\nqq.Templating = function(spec) {\n    \"use strict\";\n\n    var FILE_ID_ATTR = \"qq-file-id\",\n        FILE_CLASS_PREFIX = \"qq-file-id-\",\n        THUMBNAIL_MAX_SIZE_ATTR = \"qq-max-size\",\n        THUMBNAIL_SERVER_SCALE_ATTR = \"qq-server-scale\",\n        // This variable is duplicated in the DnD module since it can function as a standalone as well\n        HIDE_DROPZONE_ATTR = \"qq-hide-dropzone\",\n        DROPZPONE_TEXT_ATTR = \"qq-drop-area-text\",\n        IN_PROGRESS_CLASS = \"qq-in-progress\",\n        HIDDEN_FOREVER_CLASS = \"qq-hidden-forever\",\n        fileBatch = {\n            content: document.createDocumentFragment(),\n            map: {}\n        },\n        isCancelDisabled = false,\n        generatedThumbnails = 0,\n        thumbnailQueueMonitorRunning = false,\n        thumbGenerationQueue = [],\n        thumbnailMaxSize = -1,\n        options = {\n            log: null,\n            limits: {\n                maxThumbs: 0,\n                timeBetweenThumbs: 750\n            },\n            templateIdOrEl: \"qq-template\",\n            containerEl: null,\n            fileContainerEl: null,\n            button: null,\n            imageGenerator: null,\n            classes: {\n                hide: \"qq-hide\",\n                editable: \"qq-editable\"\n            },\n            placeholders: {\n                waitUntilUpdate: false,\n                thumbnailNotAvailable: null,\n                waitingForThumbnail: null\n            },\n            text: {\n                paused: \"Paused\"\n            }\n        },\n        selectorClasses = {\n            button: \"qq-upload-button-selector\",\n            alertDialog: \"qq-alert-dialog-selector\",\n            dialogCancelButton: \"qq-cancel-button-selector\",\n            confirmDialog: \"qq-confirm-dialog-selector\",\n            dialogMessage: \"qq-dialog-message-selector\",\n            dialogOkButton: \"qq-ok-button-selector\",\n            promptDialog: \"qq-prompt-dialog-selector\",\n            uploader: \"qq-uploader-selector\",\n            drop: \"qq-upload-drop-area-selector\",\n            list: \"qq-upload-list-selector\",\n            progressBarContainer: \"qq-progress-bar-container-selector\",\n            progressBar: \"qq-progress-bar-selector\",\n            totalProgressBarContainer: \"qq-total-progress-bar-container-selector\",\n            totalProgressBar: \"qq-total-progress-bar-selector\",\n            file: \"qq-upload-file-selector\",\n            spinner: \"qq-upload-spinner-selector\",\n            size: \"qq-upload-size-selector\",\n            cancel: \"qq-upload-cancel-selector\",\n            pause: \"qq-upload-pause-selector\",\n            continueButton: \"qq-upload-continue-selector\",\n            deleteButton: \"qq-upload-delete-selector\",\n            retry: \"qq-upload-retry-selector\",\n            statusText: \"qq-upload-status-text-selector\",\n            editFilenameInput: \"qq-edit-filename-selector\",\n            editNameIcon: \"qq-edit-filename-icon-selector\",\n            dropText: \"qq-upload-drop-area-text-selector\",\n            dropProcessing: \"qq-drop-processing-selector\",\n            dropProcessingSpinner: \"qq-drop-processing-spinner-selector\",\n            thumbnail: \"qq-thumbnail-selector\"\n        },\n        previewGeneration = {},\n        cachedThumbnailNotAvailableImg = new qq.Promise(),\n        cachedWaitingForThumbnailImg = new qq.Promise(),\n        log,\n        isEditElementsExist,\n        isRetryElementExist,\n        templateDom,\n        container,\n        fileList,\n        showThumbnails,\n        serverScale,\n\n        // During initialization of the templating module we should cache any\n        // placeholder images so we can quickly swap them into the file list on demand.\n        // Any placeholder images that cannot be loaded/found are simply ignored.\n        cacheThumbnailPlaceholders = function() {\n            var notAvailableUrl =  options.placeholders.thumbnailNotAvailable,\n                waitingUrl = options.placeholders.waitingForThumbnail,\n                spec = {\n                    maxSize: thumbnailMaxSize,\n                    scale: serverScale\n                };\n\n            if (showThumbnails) {\n                if (notAvailableUrl) {\n                    options.imageGenerator.generate(notAvailableUrl, new Image(), spec).then(\n                        function(updatedImg) {\n                            cachedThumbnailNotAvailableImg.success(updatedImg);\n                        },\n                        function() {\n                            cachedThumbnailNotAvailableImg.failure();\n                            log(\"Problem loading 'not available' placeholder image at \" + notAvailableUrl, \"error\");\n                        }\n                    );\n                }\n                else {\n                    cachedThumbnailNotAvailableImg.failure();\n                }\n\n                if (waitingUrl) {\n                    options.imageGenerator.generate(waitingUrl, new Image(), spec).then(\n                        function(updatedImg) {\n                            cachedWaitingForThumbnailImg.success(updatedImg);\n                        },\n                        function() {\n                            cachedWaitingForThumbnailImg.failure();\n                            log(\"Problem loading 'waiting for thumbnail' placeholder image at \" + waitingUrl, \"error\");\n                        }\n                    );\n                }\n                else {\n                    cachedWaitingForThumbnailImg.failure();\n                }\n            }\n        },\n\n        // Displays a \"waiting for thumbnail\" type placeholder image\n        // iff we were able to load it during initialization of the templating module.\n        displayWaitingImg = function(thumbnail) {\n            var waitingImgPlacement = new qq.Promise();\n\n            cachedWaitingForThumbnailImg.then(function(img) {\n                maybeScalePlaceholderViaCss(img, thumbnail);\n                /* jshint eqnull:true */\n                if (!thumbnail.src) {\n                    thumbnail.src = img.src;\n                    thumbnail.onload = function() {\n                        thumbnail.onload = null;\n                        show(thumbnail);\n                        waitingImgPlacement.success();\n                    };\n                }\n                else {\n                    waitingImgPlacement.success();\n                }\n            }, function() {\n                // In some browsers (such as IE9 and older) an img w/out a src attribute\n                // are displayed as \"broken\" images, so we should just hide the img tag\n                // if we aren't going to display the \"waiting\" placeholder.\n                hide(thumbnail);\n                waitingImgPlacement.success();\n            });\n\n            return waitingImgPlacement;\n        },\n\n        generateNewPreview = function(id, blob, spec) {\n            var thumbnail = getThumbnail(id);\n\n            log(\"Generating new thumbnail for \" + id);\n            blob.qqThumbnailId = id;\n\n            return options.imageGenerator.generate(blob, thumbnail, spec).then(\n                function() {\n                    generatedThumbnails++;\n                    show(thumbnail);\n                    previewGeneration[id].success();\n                },\n                function() {\n                    previewGeneration[id].failure();\n\n                    // Display the \"not available\" placeholder img only if we are\n                    // not expecting a thumbnail at a later point, such as in a server response.\n                    if (!options.placeholders.waitUntilUpdate) {\n                        maybeSetDisplayNotAvailableImg(id, thumbnail);\n                    }\n                });\n        },\n\n        generateNextQueuedPreview = function() {\n            if (thumbGenerationQueue.length) {\n                thumbnailQueueMonitorRunning = true;\n\n                var queuedThumbRequest = thumbGenerationQueue.shift();\n\n                if (queuedThumbRequest.update) {\n                    processUpdateQueuedPreviewRequest(queuedThumbRequest);\n                }\n                else {\n                    processNewQueuedPreviewRequest(queuedThumbRequest);\n                }\n            }\n            else {\n                thumbnailQueueMonitorRunning = false;\n            }\n        },\n\n        getCancel = function(id) {\n            return getTemplateEl(getFile(id), selectorClasses.cancel);\n        },\n\n        getContinue = function(id) {\n            return getTemplateEl(getFile(id), selectorClasses.continueButton);\n        },\n\n        getDialog = function(type) {\n            return getTemplateEl(container, selectorClasses[type + \"Dialog\"]);\n        },\n\n        getDelete = function(id) {\n            return getTemplateEl(getFile(id), selectorClasses.deleteButton);\n        },\n\n        getDropProcessing = function() {\n            return getTemplateEl(container, selectorClasses.dropProcessing);\n        },\n\n        getEditIcon = function(id) {\n            return getTemplateEl(getFile(id), selectorClasses.editNameIcon);\n        },\n\n        getFile = function(id) {\n            return fileBatch.map[id] || qq(fileList).getFirstByClass(FILE_CLASS_PREFIX + id);\n        },\n\n        getFilename = function(id) {\n            return getTemplateEl(getFile(id), selectorClasses.file);\n        },\n\n        getPause = function(id) {\n            return getTemplateEl(getFile(id), selectorClasses.pause);\n        },\n\n        getProgress = function(id) {\n            /* jshint eqnull:true */\n            // Total progress bar\n            if (id == null) {\n                return getTemplateEl(container, selectorClasses.totalProgressBarContainer) ||\n                    getTemplateEl(container, selectorClasses.totalProgressBar);\n            }\n\n            // Per-file progress bar\n            return getTemplateEl(getFile(id), selectorClasses.progressBarContainer) ||\n                getTemplateEl(getFile(id), selectorClasses.progressBar);\n        },\n\n        getRetry = function(id) {\n            return getTemplateEl(getFile(id), selectorClasses.retry);\n        },\n\n        getSize = function(id) {\n            return getTemplateEl(getFile(id), selectorClasses.size);\n        },\n\n        getSpinner = function(id) {\n            return getTemplateEl(getFile(id), selectorClasses.spinner);\n        },\n\n        getTemplateEl = function(context, cssClass) {\n            return context && qq(context).getFirstByClass(cssClass);\n        },\n\n        getThumbnail = function(id) {\n            return showThumbnails && getTemplateEl(getFile(id), selectorClasses.thumbnail);\n        },\n\n        hide = function(el) {\n            el && qq(el).addClass(options.classes.hide);\n        },\n\n        // Ensures a placeholder image does not exceed any max size specified\n        // via `style` attribute properties iff <canvas> was not used to scale\n        // the placeholder AND the target <img> doesn't already have these `style` attribute properties set.\n        maybeScalePlaceholderViaCss = function(placeholder, thumbnail) {\n            var maxWidth = placeholder.style.maxWidth,\n                maxHeight = placeholder.style.maxHeight;\n\n            if (maxHeight && maxWidth && !thumbnail.style.maxWidth && !thumbnail.style.maxHeight) {\n                qq(thumbnail).css({\n                    maxWidth: maxWidth,\n                    maxHeight: maxHeight\n                });\n            }\n        },\n\n        // Displays a \"thumbnail not available\" type placeholder image\n        // iff we were able to load this placeholder during initialization\n        // of the templating module or after preview generation has failed.\n        maybeSetDisplayNotAvailableImg = function(id, thumbnail) {\n            var previewing = previewGeneration[id] || new qq.Promise().failure(),\n                notAvailableImgPlacement = new qq.Promise();\n\n            cachedThumbnailNotAvailableImg.then(function(img) {\n                previewing.then(\n                    function() {\n                        notAvailableImgPlacement.success();\n                    },\n                    function() {\n                        maybeScalePlaceholderViaCss(img, thumbnail);\n\n                        thumbnail.onload = function() {\n                            thumbnail.onload = null;\n                            notAvailableImgPlacement.success();\n                        };\n\n                        thumbnail.src = img.src;\n                        show(thumbnail);\n                    }\n                );\n            });\n\n            return notAvailableImgPlacement;\n        },\n\n        /**\n         * Grabs the HTML from the script tag holding the template markup.  This function will also adjust\n         * some internally-tracked state variables based on the contents of the template.\n         * The template is filtered so that irrelevant elements (such as the drop zone if DnD is not supported)\n         * are omitted from the DOM.  Useful errors will be thrown if the template cannot be parsed.\n         *\n         * @returns {{template: *, fileTemplate: *}} HTML for the top-level file items templates\n         */\n        parseAndGetTemplate = function() {\n            var scriptEl,\n                scriptHtml,\n                fileListNode,\n                tempTemplateEl,\n                fileListEl,\n                defaultButton,\n                dropArea,\n                thumbnail,\n                dropProcessing,\n                dropTextEl,\n                uploaderEl;\n\n            log(\"Parsing template\");\n\n            /*jshint -W116*/\n            if (options.templateIdOrEl == null) {\n                throw new Error(\"You MUST specify either a template element or ID!\");\n            }\n\n            // Grab the contents of the script tag holding the template.\n            if (qq.isString(options.templateIdOrEl)) {\n                scriptEl = document.getElementById(options.templateIdOrEl);\n\n                if (scriptEl === null) {\n                    throw new Error(qq.format(\"Cannot find template script at ID '{}'!\", options.templateIdOrEl));\n                }\n\n                scriptHtml = scriptEl.innerHTML;\n            }\n            else {\n                if (options.templateIdOrEl.innerHTML === undefined) {\n                    throw new Error(\"You have specified an invalid value for the template option!  \" +\n                        \"It must be an ID or an Element.\");\n                }\n\n                scriptHtml = options.templateIdOrEl.innerHTML;\n            }\n\n            scriptHtml = qq.trimStr(scriptHtml);\n            tempTemplateEl = document.createElement(\"div\");\n            tempTemplateEl.appendChild(qq.toElement(scriptHtml));\n            uploaderEl = qq(tempTemplateEl).getFirstByClass(selectorClasses.uploader);\n\n            // Don't include the default template button in the DOM\n            // if an alternate button container has been specified.\n            if (options.button) {\n                defaultButton = qq(tempTemplateEl).getFirstByClass(selectorClasses.button);\n                if (defaultButton) {\n                    qq(defaultButton).remove();\n                }\n            }\n\n            // Omit the drop processing element from the DOM if DnD is not supported by the UA,\n            // or the drag and drop module is not found.\n            // NOTE: We are consciously not removing the drop zone if the UA doesn't support DnD\n            // to support layouts where the drop zone is also a container for visible elements,\n            // such as the file list.\n            if (!qq.DragAndDrop || !qq.supportedFeatures.fileDrop) {\n                dropProcessing = qq(tempTemplateEl).getFirstByClass(selectorClasses.dropProcessing);\n                if (dropProcessing) {\n                    qq(dropProcessing).remove();\n                }\n            }\n\n            dropArea = qq(tempTemplateEl).getFirstByClass(selectorClasses.drop);\n\n            // If DnD is not available then remove\n            // it from the DOM as well.\n            if (dropArea && !qq.DragAndDrop) {\n                log(\"DnD module unavailable.\", \"info\");\n                qq(dropArea).remove();\n            }\n\n            if (!qq.supportedFeatures.fileDrop) {\n                // don't display any \"drop files to upload\" background text\n                uploaderEl.removeAttribute(DROPZPONE_TEXT_ATTR);\n\n                if (dropArea && qq(dropArea).hasAttribute(HIDE_DROPZONE_ATTR)) {\n                    // If there is a drop area defined in the template, and the current UA doesn't support DnD,\n                    // and the drop area is marked as \"hide before enter\", ensure it is hidden as the DnD module\n                    // will not do this (since we will not be loading the DnD module)\n                    qq(dropArea).css({\n                        display: \"none\"\n                    });\n                }\n            }\n            else if (qq(uploaderEl).hasAttribute(DROPZPONE_TEXT_ATTR) && dropArea) {\n                dropTextEl = qq(dropArea).getFirstByClass(selectorClasses.dropText);\n                dropTextEl && qq(dropTextEl).remove();\n            }\n\n            // Ensure the `showThumbnails` flag is only set if the thumbnail element\n            // is present in the template AND the current UA is capable of generating client-side previews.\n            thumbnail = qq(tempTemplateEl).getFirstByClass(selectorClasses.thumbnail);\n            if (!showThumbnails) {\n                thumbnail && qq(thumbnail).remove();\n            }\n            else if (thumbnail) {\n                thumbnailMaxSize = parseInt(thumbnail.getAttribute(THUMBNAIL_MAX_SIZE_ATTR));\n                // Only enforce max size if the attr value is non-zero\n                thumbnailMaxSize = thumbnailMaxSize > 0 ? thumbnailMaxSize : null;\n\n                serverScale = qq(thumbnail).hasAttribute(THUMBNAIL_SERVER_SCALE_ATTR);\n            }\n            showThumbnails = showThumbnails && thumbnail;\n\n            isEditElementsExist = qq(tempTemplateEl).getByClass(selectorClasses.editFilenameInput).length > 0;\n            isRetryElementExist = qq(tempTemplateEl).getByClass(selectorClasses.retry).length > 0;\n\n            fileListNode = qq(tempTemplateEl).getFirstByClass(selectorClasses.list);\n            /*jshint -W116*/\n            if (fileListNode == null) {\n                throw new Error(\"Could not find the file list container in the template!\");\n            }\n\n            fileListEl = fileListNode.children[0].cloneNode(true);\n            fileListNode.innerHTML = \"\";\n\n            // We must call `createElement` in IE8 in order to target and hide any <dialog> via CSS\n            if (tempTemplateEl.getElementsByTagName(\"DIALOG\").length) {\n                document.createElement(\"dialog\");\n            }\n\n            log(\"Template parsing complete\");\n\n            return {\n                template: tempTemplateEl,\n                fileTemplate: fileListEl\n            };\n        },\n\n        prependFile = function(el, index, fileList) {\n            var parentEl = fileList,\n                beforeEl = parentEl.firstChild;\n\n            if (index > 0) {\n                beforeEl = qq(parentEl).children()[index].nextSibling;\n\n            }\n\n            parentEl.insertBefore(el, beforeEl);\n        },\n\n        processNewQueuedPreviewRequest = function(queuedThumbRequest) {\n            var id = queuedThumbRequest.id,\n                optFileOrBlob = queuedThumbRequest.optFileOrBlob,\n                relatedThumbnailId = optFileOrBlob && optFileOrBlob.qqThumbnailId,\n                thumbnail = getThumbnail(id),\n                spec = {\n                    customResizeFunction: queuedThumbRequest.customResizeFunction,\n                    maxSize: thumbnailMaxSize,\n                    orient: true,\n                    scale: true\n                };\n\n            if (qq.supportedFeatures.imagePreviews) {\n                if (thumbnail) {\n                    if (options.limits.maxThumbs && options.limits.maxThumbs <= generatedThumbnails) {\n                        maybeSetDisplayNotAvailableImg(id, thumbnail);\n                        generateNextQueuedPreview();\n                    }\n                    else {\n                        displayWaitingImg(thumbnail).done(function() {\n                            previewGeneration[id] = new qq.Promise();\n\n                            previewGeneration[id].done(function() {\n                                setTimeout(generateNextQueuedPreview, options.limits.timeBetweenThumbs);\n                            });\n\n                            /* jshint eqnull: true */\n                            // If we've already generated an <img> for this file, use the one that exists,\n                            // don't waste resources generating a new one.\n                            if (relatedThumbnailId != null) {\n                                useCachedPreview(id, relatedThumbnailId);\n                            }\n                            else {\n                                generateNewPreview(id, optFileOrBlob, spec);\n                            }\n                        });\n                    }\n                }\n                // File element in template may have been removed, so move on to next item in queue\n                else {\n                    generateNextQueuedPreview();\n                }\n            }\n            else if (thumbnail) {\n                displayWaitingImg(thumbnail);\n                generateNextQueuedPreview();\n            }\n        },\n\n        processUpdateQueuedPreviewRequest = function(queuedThumbRequest) {\n            var id = queuedThumbRequest.id,\n                thumbnailUrl = queuedThumbRequest.thumbnailUrl,\n                showWaitingImg = queuedThumbRequest.showWaitingImg,\n                thumbnail = getThumbnail(id),\n                spec = {\n                    customResizeFunction: queuedThumbRequest.customResizeFunction,\n                    scale: serverScale,\n                    maxSize: thumbnailMaxSize\n                };\n\n            if (thumbnail) {\n                if (thumbnailUrl) {\n                    if (options.limits.maxThumbs && options.limits.maxThumbs <= generatedThumbnails) {\n                        maybeSetDisplayNotAvailableImg(id, thumbnail);\n                        generateNextQueuedPreview();\n                    }\n                    else {\n                        if (showWaitingImg) {\n                            displayWaitingImg(thumbnail);\n                        }\n\n                        return options.imageGenerator.generate(thumbnailUrl, thumbnail, spec).then(\n                            function() {\n                                show(thumbnail);\n                                generatedThumbnails++;\n                                setTimeout(generateNextQueuedPreview, options.limits.timeBetweenThumbs);\n                            },\n\n                            function() {\n                                maybeSetDisplayNotAvailableImg(id, thumbnail);\n                                setTimeout(generateNextQueuedPreview, options.limits.timeBetweenThumbs);\n                            }\n                        );\n                    }\n                }\n                else {\n                    maybeSetDisplayNotAvailableImg(id, thumbnail);\n                    generateNextQueuedPreview();\n                }\n            }\n        },\n\n        setProgressBarWidth = function(id, percent) {\n            var bar = getProgress(id),\n                /* jshint eqnull:true */\n                progressBarSelector = id == null ? selectorClasses.totalProgressBar : selectorClasses.progressBar;\n\n            if (bar && !qq(bar).hasClass(progressBarSelector)) {\n                bar = qq(bar).getFirstByClass(progressBarSelector);\n            }\n\n            if (bar) {\n                qq(bar).css({width: percent + \"%\"});\n                bar.setAttribute(\"aria-valuenow\", percent);\n            }\n        },\n\n        show = function(el) {\n            el && qq(el).removeClass(options.classes.hide);\n        },\n\n        useCachedPreview = function(targetThumbnailId, cachedThumbnailId) {\n            var targetThumbnail = getThumbnail(targetThumbnailId),\n                cachedThumbnail = getThumbnail(cachedThumbnailId);\n\n            log(qq.format(\"ID {} is the same file as ID {}.  Will use generated thumbnail from ID {} instead.\", targetThumbnailId, cachedThumbnailId, cachedThumbnailId));\n\n            // Generation of the related thumbnail may still be in progress, so, wait until it is done.\n            previewGeneration[cachedThumbnailId].then(function() {\n                generatedThumbnails++;\n                previewGeneration[targetThumbnailId].success();\n                log(qq.format(\"Now using previously generated thumbnail created for ID {} on ID {}.\", cachedThumbnailId, targetThumbnailId));\n                targetThumbnail.src = cachedThumbnail.src;\n                show(targetThumbnail);\n            },\n            function() {\n                previewGeneration[targetThumbnailId].failure();\n                if (!options.placeholders.waitUntilUpdate) {\n                    maybeSetDisplayNotAvailableImg(targetThumbnailId, targetThumbnail);\n                }\n            });\n        };\n\n    qq.extend(options, spec);\n    log = options.log;\n\n    // No need to worry about conserving CPU or memory on older browsers,\n    // since there is no ability to preview, and thumbnail display is primitive and quick.\n    if (!qq.supportedFeatures.imagePreviews) {\n        options.limits.timeBetweenThumbs = 0;\n        options.limits.maxThumbs = 0;\n    }\n\n    container = options.containerEl;\n    showThumbnails = options.imageGenerator !== undefined;\n    templateDom = parseAndGetTemplate();\n\n    cacheThumbnailPlaceholders();\n\n    qq.extend(this, {\n        render: function() {\n            log(\"Rendering template in DOM.\");\n\n            generatedThumbnails = 0;\n\n            container.appendChild(templateDom.template.cloneNode(true));\n            hide(getDropProcessing());\n            this.hideTotalProgress();\n            fileList = options.fileContainerEl || getTemplateEl(container, selectorClasses.list);\n\n            log(\"Template rendering complete\");\n        },\n\n        renderFailure: function(message) {\n            var cantRenderEl = qq.toElement(message);\n            container.innerHTML = \"\";\n            container.appendChild(cantRenderEl);\n        },\n\n        reset: function() {\n            container.innerHTML = \"\";\n            this.render();\n        },\n\n        clearFiles: function() {\n            fileList.innerHTML = \"\";\n        },\n\n        disableCancel: function() {\n            isCancelDisabled = true;\n        },\n\n        addFile: function(id, name, prependInfo, hideForever, batch) {\n            var fileEl = templateDom.fileTemplate.cloneNode(true),\n                fileNameEl = getTemplateEl(fileEl, selectorClasses.file),\n                uploaderEl = getTemplateEl(container, selectorClasses.uploader),\n                fileContainer = batch ? fileBatch.content : fileList,\n                thumb;\n\n            if (batch) {\n                fileBatch.map[id] = fileEl;\n            }\n\n            qq(fileEl).addClass(FILE_CLASS_PREFIX + id);\n            uploaderEl.removeAttribute(DROPZPONE_TEXT_ATTR);\n\n            if (fileNameEl) {\n                qq(fileNameEl).setText(name);\n                fileNameEl.setAttribute(\"title\", name);\n            }\n\n            fileEl.setAttribute(FILE_ID_ATTR, id);\n\n            if (prependInfo) {\n                prependFile(fileEl, prependInfo.index, fileContainer);\n            }\n            else {\n                fileContainer.appendChild(fileEl);\n            }\n\n            if (hideForever) {\n                fileEl.style.display = \"none\";\n                qq(fileEl).addClass(HIDDEN_FOREVER_CLASS);\n            }\n            else {\n                hide(getProgress(id));\n                hide(getSize(id));\n                hide(getDelete(id));\n                hide(getRetry(id));\n                hide(getPause(id));\n                hide(getContinue(id));\n\n                if (isCancelDisabled) {\n                    this.hideCancel(id);\n                }\n\n                thumb = getThumbnail(id);\n                if (thumb && !thumb.src) {\n                    cachedWaitingForThumbnailImg.then(function(waitingImg) {\n                        thumb.src = waitingImg.src;\n                        if (waitingImg.style.maxHeight && waitingImg.style.maxWidth) {\n                            qq(thumb).css({\n                                maxHeight: waitingImg.style.maxHeight,\n                                maxWidth: waitingImg.style.maxWidth\n                            });\n                        }\n\n                        show(thumb);\n                    });\n                }\n            }\n        },\n\n        addFileToCache: function(id, name, prependInfo, hideForever) {\n            this.addFile(id, name, prependInfo, hideForever, true);\n        },\n\n        addCacheToDom: function() {\n            fileList.appendChild(fileBatch.content);\n            fileBatch.content = document.createDocumentFragment();\n            fileBatch.map = {};\n        },\n\n        removeFile: function(id) {\n            qq(getFile(id)).remove();\n        },\n\n        getFileId: function(el) {\n            var currentNode = el;\n\n            if (currentNode) {\n                /*jshint -W116*/\n                while (currentNode.getAttribute(FILE_ID_ATTR) == null) {\n                    currentNode = currentNode.parentNode;\n                }\n\n                return parseInt(currentNode.getAttribute(FILE_ID_ATTR));\n            }\n        },\n\n        getFileList: function() {\n            return fileList;\n        },\n\n        markFilenameEditable: function(id) {\n            var filename = getFilename(id);\n\n            filename && qq(filename).addClass(options.classes.editable);\n        },\n\n        updateFilename: function(id, name) {\n            var filenameEl = getFilename(id);\n\n            if (filenameEl) {\n                qq(filenameEl).setText(name);\n                filenameEl.setAttribute(\"title\", name);\n            }\n        },\n\n        hideFilename: function(id) {\n            hide(getFilename(id));\n        },\n\n        showFilename: function(id) {\n            show(getFilename(id));\n        },\n\n        isFileName: function(el) {\n            return qq(el).hasClass(selectorClasses.file);\n        },\n\n        getButton: function() {\n            return options.button || getTemplateEl(container, selectorClasses.button);\n        },\n\n        hideDropProcessing: function() {\n            hide(getDropProcessing());\n        },\n\n        showDropProcessing: function() {\n            show(getDropProcessing());\n        },\n\n        getDropZone: function() {\n            return getTemplateEl(container, selectorClasses.drop);\n        },\n\n        isEditFilenamePossible: function() {\n            return isEditElementsExist;\n        },\n\n        hideRetry: function(id) {\n            hide(getRetry(id));\n        },\n\n        isRetryPossible: function() {\n            return isRetryElementExist;\n        },\n\n        showRetry: function(id) {\n            show(getRetry(id));\n        },\n\n        getFileContainer: function(id) {\n            return getFile(id);\n        },\n\n        showEditIcon: function(id) {\n            var icon = getEditIcon(id);\n\n            icon && qq(icon).addClass(options.classes.editable);\n        },\n\n        isHiddenForever: function(id) {\n            return qq(getFile(id)).hasClass(HIDDEN_FOREVER_CLASS);\n        },\n\n        hideEditIcon: function(id) {\n            var icon = getEditIcon(id);\n\n            icon && qq(icon).removeClass(options.classes.editable);\n        },\n\n        isEditIcon: function(el) {\n            return qq(el).hasClass(selectorClasses.editNameIcon, true);\n        },\n\n        getEditInput: function(id) {\n            return getTemplateEl(getFile(id), selectorClasses.editFilenameInput);\n        },\n\n        isEditInput: function(el) {\n            return qq(el).hasClass(selectorClasses.editFilenameInput, true);\n        },\n\n        updateProgress: function(id, loaded, total) {\n            var bar = getProgress(id),\n                percent;\n\n            if (bar && total > 0) {\n                percent = Math.round(loaded / total * 100);\n\n                if (percent === 100) {\n                    hide(bar);\n                }\n                else {\n                    show(bar);\n                }\n\n                setProgressBarWidth(id, percent);\n            }\n        },\n\n        updateTotalProgress: function(loaded, total) {\n            this.updateProgress(null, loaded, total);\n        },\n\n        hideProgress: function(id) {\n            var bar = getProgress(id);\n\n            bar && hide(bar);\n        },\n\n        hideTotalProgress: function() {\n            this.hideProgress();\n        },\n\n        resetProgress: function(id) {\n            setProgressBarWidth(id, 0);\n            this.hideTotalProgress(id);\n        },\n\n        resetTotalProgress: function() {\n            this.resetProgress();\n        },\n\n        showCancel: function(id) {\n            if (!isCancelDisabled) {\n                var cancel = getCancel(id);\n\n                cancel && qq(cancel).removeClass(options.classes.hide);\n            }\n        },\n\n        hideCancel: function(id) {\n            hide(getCancel(id));\n        },\n\n        isCancel: function(el)  {\n            return qq(el).hasClass(selectorClasses.cancel, true);\n        },\n\n        allowPause: function(id) {\n            show(getPause(id));\n            hide(getContinue(id));\n        },\n\n        uploadPaused: function(id) {\n            this.setStatusText(id, options.text.paused);\n            this.allowContinueButton(id);\n            hide(getSpinner(id));\n        },\n\n        hidePause: function(id) {\n            hide(getPause(id));\n        },\n\n        isPause: function(el) {\n            return qq(el).hasClass(selectorClasses.pause, true);\n        },\n\n        isContinueButton: function(el) {\n            return qq(el).hasClass(selectorClasses.continueButton, true);\n        },\n\n        allowContinueButton: function(id) {\n            show(getContinue(id));\n            hide(getPause(id));\n        },\n\n        uploadContinued: function(id) {\n            this.setStatusText(id, \"\");\n            this.allowPause(id);\n            show(getSpinner(id));\n        },\n\n        showDeleteButton: function(id) {\n            show(getDelete(id));\n        },\n\n        hideDeleteButton: function(id) {\n            hide(getDelete(id));\n        },\n\n        isDeleteButton: function(el) {\n            return qq(el).hasClass(selectorClasses.deleteButton, true);\n        },\n\n        isRetry: function(el) {\n            return qq(el).hasClass(selectorClasses.retry, true);\n        },\n\n        updateSize: function(id, text) {\n            var size = getSize(id);\n\n            if (size) {\n                show(size);\n                qq(size).setText(text);\n            }\n        },\n\n        setStatusText: function(id, text) {\n            var textEl = getTemplateEl(getFile(id), selectorClasses.statusText);\n\n            if (textEl) {\n                /*jshint -W116*/\n                if (text == null) {\n                    qq(textEl).clearText();\n                }\n                else {\n                    qq(textEl).setText(text);\n                }\n            }\n        },\n\n        hideSpinner: function(id) {\n            qq(getFile(id)).removeClass(IN_PROGRESS_CLASS);\n            hide(getSpinner(id));\n        },\n\n        showSpinner: function(id) {\n            qq(getFile(id)).addClass(IN_PROGRESS_CLASS);\n            show(getSpinner(id));\n        },\n\n        generatePreview: function(id, optFileOrBlob, customResizeFunction) {\n            if (!this.isHiddenForever(id)) {\n                thumbGenerationQueue.push({id: id, customResizeFunction: customResizeFunction, optFileOrBlob: optFileOrBlob});\n                !thumbnailQueueMonitorRunning && generateNextQueuedPreview();\n            }\n        },\n\n        updateThumbnail: function(id, thumbnailUrl, showWaitingImg, customResizeFunction) {\n            if (!this.isHiddenForever(id)) {\n                thumbGenerationQueue.push({customResizeFunction: customResizeFunction, update: true, id: id, thumbnailUrl: thumbnailUrl, showWaitingImg: showWaitingImg});\n                !thumbnailQueueMonitorRunning && generateNextQueuedPreview();\n            }\n        },\n\n        hasDialog: function(type) {\n            return qq.supportedFeatures.dialogElement && !!getDialog(type);\n        },\n\n        showDialog: function(type, message, defaultValue) {\n            var dialog = getDialog(type),\n                messageEl = getTemplateEl(dialog, selectorClasses.dialogMessage),\n                inputEl = dialog.getElementsByTagName(\"INPUT\")[0],\n                cancelBtn = getTemplateEl(dialog, selectorClasses.dialogCancelButton),\n                okBtn = getTemplateEl(dialog, selectorClasses.dialogOkButton),\n                promise = new qq.Promise(),\n\n                closeHandler = function() {\n                    cancelBtn.removeEventListener(\"click\", cancelClickHandler);\n                    okBtn && okBtn.removeEventListener(\"click\", okClickHandler);\n                    promise.failure();\n                },\n\n                cancelClickHandler = function() {\n                    cancelBtn.removeEventListener(\"click\", cancelClickHandler);\n                    dialog.close();\n                },\n\n                okClickHandler = function() {\n                    dialog.removeEventListener(\"close\", closeHandler);\n                    okBtn.removeEventListener(\"click\", okClickHandler);\n                    dialog.close();\n\n                    promise.success(inputEl && inputEl.value);\n                };\n\n            dialog.addEventListener(\"close\", closeHandler);\n            cancelBtn.addEventListener(\"click\", cancelClickHandler);\n            okBtn && okBtn.addEventListener(\"click\", okClickHandler);\n\n            if (inputEl) {\n                inputEl.value = defaultValue;\n            }\n            messageEl.textContent = message;\n\n            dialog.showModal();\n\n            return promise;\n        }\n    });\n};\n"
  },
  {
    "path": "client/js/third-party/ExifRestorer.js",
    "content": "//Based on MinifyJpeg\n//http://elicon.blog57.fc2.com/blog-entry-206.html\n\nqq.ExifRestorer = (function()\n{\n   \n\tvar ExifRestorer = {};\n\t \n    ExifRestorer.KEY_STR = \"ABCDEFGHIJKLMNOP\" +\n                         \"QRSTUVWXYZabcdef\" +\n                         \"ghijklmnopqrstuv\" +\n                         \"wxyz0123456789+/\" +\n                         \"=\";\n\n    ExifRestorer.encode64 = function(input)\n    {\n        var output = \"\",\n            chr1, chr2, chr3 = \"\",\n            enc1, enc2, enc3, enc4 = \"\",\n            i = 0;\n\n        do {\n            chr1 = input[i++];\n            chr2 = input[i++];\n            chr3 = input[i++];\n\n            enc1 = chr1 >> 2;\n            enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);\n            enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);\n            enc4 = chr3 & 63;\n\n            if (isNaN(chr2)) {\n               enc3 = enc4 = 64;\n            } else if (isNaN(chr3)) {\n               enc4 = 64;\n            }\n\n            output = output +\n               this.KEY_STR.charAt(enc1) +\n               this.KEY_STR.charAt(enc2) +\n               this.KEY_STR.charAt(enc3) +\n               this.KEY_STR.charAt(enc4);\n            chr1 = chr2 = chr3 = \"\";\n            enc1 = enc2 = enc3 = enc4 = \"\";\n        } while (i < input.length);\n\n        return output;\n    };\n    \n    ExifRestorer.restore = function(origFileBase64, resizedFileBase64)\n    {\n        var expectedBase64Header = \"data:image/jpeg;base64,\";\n\n        if (!origFileBase64.match(expectedBase64Header))\n        {\n        \treturn resizedFileBase64;\n        }       \n        \n        var rawImage = this.decode64(origFileBase64.replace(expectedBase64Header, \"\"));\n        var segments = this.slice2Segments(rawImage);\n                \n        var image = this.exifManipulation(resizedFileBase64, segments);\n        \n        return expectedBase64Header + this.encode64(image);\n        \n    };\n\n\n    ExifRestorer.exifManipulation = function(resizedFileBase64, segments)\n    {\n            var exifArray = this.getExifArray(segments),\n                newImageArray = this.insertExif(resizedFileBase64, exifArray),\n                aBuffer = new Uint8Array(newImageArray);\n\n            return aBuffer;\n    };\n\n\n    ExifRestorer.getExifArray = function(segments)\n    {\n            var seg;\n            for (var x = 0; x < segments.length; x++)\n            {\n                seg = segments[x];\n                if (seg[0] == 255 & seg[1] == 225) //(ff e1)\n                {\n                    return seg;\n                }\n            }\n            return [];\n    };\n\n\n    ExifRestorer.insertExif = function(resizedFileBase64, exifArray)\n    {\n            var imageData = resizedFileBase64.replace(\"data:image/jpeg;base64,\", \"\"),\n                buf = this.decode64(imageData),\n                separatePoint = buf.indexOf(255,3),\n                mae = buf.slice(0, separatePoint),\n                ato = buf.slice(separatePoint),\n                array = mae;\n\n            array = array.concat(exifArray);\n            array = array.concat(ato);\n           return array;\n    };\n\n\n    \n    ExifRestorer.slice2Segments = function(rawImageArray)\n    {\n        var head = 0,\n            segments = [];\n\n        while (1)\n        {\n            if (rawImageArray[head] == 255 & rawImageArray[head + 1] == 218){break;}\n            if (rawImageArray[head] == 255 & rawImageArray[head + 1] == 216)\n            {\n                head += 2;\n            }\n            else\n            {\n                var length = rawImageArray[head + 2] * 256 + rawImageArray[head + 3],\n                    endPoint = head + length + 2,\n                    seg = rawImageArray.slice(head, endPoint);\n                segments.push(seg);\n                head = endPoint;\n            }\n            if (head > rawImageArray.length){break;}\n        }\n\n        return segments;\n    };\n\n\n    \n    ExifRestorer.decode64 = function(input) \n    {\n        var output = \"\",\n            chr1, chr2, chr3 = \"\",\n            enc1, enc2, enc3, enc4 = \"\",\n            i = 0,\n            buf = [];\n\n        // remove all characters that are not A-Z, a-z, 0-9, +, /, or =\n        var base64test = /[^A-Za-z0-9\\+\\/\\=]/g;\n        if (base64test.exec(input)) {\n            throw new Error(\"There were invalid base64 characters in the input text.  \" +\n                \"Valid base64 characters are A-Z, a-z, 0-9, '+', '/',and '='\");\n        }\n        input = input.replace(/[^A-Za-z0-9\\+\\/\\=]/g, \"\");\n\n        do {\n            enc1 = this.KEY_STR.indexOf(input.charAt(i++));\n            enc2 = this.KEY_STR.indexOf(input.charAt(i++));\n            enc3 = this.KEY_STR.indexOf(input.charAt(i++));\n            enc4 = this.KEY_STR.indexOf(input.charAt(i++));\n\n            chr1 = (enc1 << 2) | (enc2 >> 4);\n            chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);\n            chr3 = ((enc3 & 3) << 6) | enc4;\n\n            buf.push(chr1);\n\n            if (enc3 != 64) {\n               buf.push(chr2);\n            }\n            if (enc4 != 64) {\n               buf.push(chr3);\n            }\n\n            chr1 = chr2 = chr3 = \"\";\n            enc1 = enc2 = enc3 = enc4 = \"\";\n\n        } while (i < input.length);\n\n        return buf;\n    };\n\n    \n    return ExifRestorer;\n})();\n"
  },
  {
    "path": "client/js/third-party/crypto-js/core.js",
    "content": "/*\nCryptoJS v3.1.2\ncode.google.com/p/crypto-js\n(c) 2009-2013 by Jeff Mott. All rights reserved.\ncode.google.com/p/crypto-js/wiki/License\n*/\n/**\n * CryptoJS core components.\n */\nqq.CryptoJS = (function (Math, undefined) {\n    /**\n     * CryptoJS namespace.\n     */\n    var C = {};\n\n    /**\n     * Library namespace.\n     */\n    var C_lib = C.lib = {};\n\n    /**\n     * Base object for prototypal inheritance.\n     */\n    var Base = C_lib.Base = (function () {\n        function F() {}\n\n        return {\n            /**\n             * Creates a new object that inherits from this object.\n             *\n             * @param {Object} overrides Properties to copy into the new object.\n             *\n             * @return {Object} The new object.\n             *\n             * @static\n             *\n             * @example\n             *\n             *     var MyType = CryptoJS.lib.Base.extend({\n             *         field: 'value',\n             *\n             *         method: function () {\n             *         }\n             *     });\n             */\n            extend: function (overrides) {\n                // Spawn\n                F.prototype = this;\n                var subtype = new F();\n\n                // Augment\n                if (overrides) {\n                    subtype.mixIn(overrides);\n                }\n\n                // Create default initializer\n                if (!subtype.hasOwnProperty('init')) {\n                    subtype.init = function () {\n                        subtype.$super.init.apply(this, arguments);\n                    };\n                }\n\n                // Initializer's prototype is the subtype object\n                subtype.init.prototype = subtype;\n\n                // Reference supertype\n                subtype.$super = this;\n\n                return subtype;\n            },\n\n            /**\n             * Extends this object and runs the init method.\n             * Arguments to create() will be passed to init().\n             *\n             * @return {Object} The new object.\n             *\n             * @static\n             *\n             * @example\n             *\n             *     var instance = MyType.create();\n             */\n            create: function () {\n                var instance = this.extend();\n                instance.init.apply(instance, arguments);\n\n                return instance;\n            },\n\n            /**\n             * Initializes a newly created object.\n             * Override this method to add some logic when your objects are created.\n             *\n             * @example\n             *\n             *     var MyType = CryptoJS.lib.Base.extend({\n             *         init: function () {\n             *             // ...\n             *         }\n             *     });\n             */\n            init: function () {\n            },\n\n            /**\n             * Copies properties into this object.\n             *\n             * @param {Object} properties The properties to mix in.\n             *\n             * @example\n             *\n             *     MyType.mixIn({\n             *         field: 'value'\n             *     });\n             */\n            mixIn: function (properties) {\n                for (var propertyName in properties) {\n                    if (properties.hasOwnProperty(propertyName)) {\n                        this[propertyName] = properties[propertyName];\n                    }\n                }\n\n                // IE won't copy toString using the loop above\n                if (properties.hasOwnProperty('toString')) {\n                    this.toString = properties.toString;\n                }\n            },\n\n            /**\n             * Creates a copy of this object.\n             *\n             * @return {Object} The clone.\n             *\n             * @example\n             *\n             *     var clone = instance.clone();\n             */\n            clone: function () {\n                return this.init.prototype.extend(this);\n            }\n        };\n    }());\n\n    /**\n     * An array of 32-bit words.\n     *\n     * @property {Array} words The array of 32-bit words.\n     * @property {number} sigBytes The number of significant bytes in this word array.\n     */\n    var WordArray = C_lib.WordArray = Base.extend({\n        /**\n         * Initializes a newly created word array.\n         *\n         * @param {Array} words (Optional) An array of 32-bit words.\n         * @param {number} sigBytes (Optional) The number of significant bytes in the words.\n         *\n         * @example\n         *\n         *     var wordArray = CryptoJS.lib.WordArray.create();\n         *     var wordArray = CryptoJS.lib.WordArray.create([0x00010203, 0x04050607]);\n         *     var wordArray = CryptoJS.lib.WordArray.create([0x00010203, 0x04050607], 6);\n         */\n        init: function (words, sigBytes) {\n            words = this.words = words || [];\n\n            if (sigBytes != undefined) {\n                this.sigBytes = sigBytes;\n            } else {\n                this.sigBytes = words.length * 4;\n            }\n        },\n\n        /**\n         * Converts this word array to a string.\n         *\n         * @param {Encoder} encoder (Optional) The encoding strategy to use. Default: CryptoJS.enc.Hex\n         *\n         * @return {string} The stringified word array.\n         *\n         * @example\n         *\n         *     var string = wordArray + '';\n         *     var string = wordArray.toString();\n         *     var string = wordArray.toString(CryptoJS.enc.Utf8);\n         */\n        toString: function (encoder) {\n            return (encoder || Hex).stringify(this);\n        },\n\n        /**\n         * Concatenates a word array to this word array.\n         *\n         * @param {WordArray} wordArray The word array to append.\n         *\n         * @return {WordArray} This word array.\n         *\n         * @example\n         *\n         *     wordArray1.concat(wordArray2);\n         */\n        concat: function (wordArray) {\n            // Shortcuts\n            var thisWords = this.words;\n            var thatWords = wordArray.words;\n            var thisSigBytes = this.sigBytes;\n            var thatSigBytes = wordArray.sigBytes;\n\n            // Clamp excess bits\n            this.clamp();\n\n            // Concat\n            if (thisSigBytes % 4) {\n                // Copy one byte at a time\n                for (var i = 0; i < thatSigBytes; i++) {\n                    var thatByte = (thatWords[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff;\n                    thisWords[(thisSigBytes + i) >>> 2] |= thatByte << (24 - ((thisSigBytes + i) % 4) * 8);\n                }\n            } else if (thatWords.length > 0xffff) {\n                // Copy one word at a time\n                for (var i = 0; i < thatSigBytes; i += 4) {\n                    thisWords[(thisSigBytes + i) >>> 2] = thatWords[i >>> 2];\n                }\n            } else {\n                // Copy all words at once\n                thisWords.push.apply(thisWords, thatWords);\n            }\n            this.sigBytes += thatSigBytes;\n\n            // Chainable\n            return this;\n        },\n\n        /**\n         * Removes insignificant bits.\n         *\n         * @example\n         *\n         *     wordArray.clamp();\n         */\n        clamp: function () {\n            // Shortcuts\n            var words = this.words;\n            var sigBytes = this.sigBytes;\n\n            // Clamp\n            words[sigBytes >>> 2] &= 0xffffffff << (32 - (sigBytes % 4) * 8);\n            words.length = Math.ceil(sigBytes / 4);\n        },\n\n        /**\n         * Creates a copy of this word array.\n         *\n         * @return {WordArray} The clone.\n         *\n         * @example\n         *\n         *     var clone = wordArray.clone();\n         */\n        clone: function () {\n            var clone = Base.clone.call(this);\n            clone.words = this.words.slice(0);\n\n            return clone;\n        },\n\n        /**\n         * Creates a word array filled with random bytes.\n         *\n         * @param {number} nBytes The number of random bytes to generate.\n         *\n         * @return {WordArray} The random word array.\n         *\n         * @static\n         *\n         * @example\n         *\n         *     var wordArray = CryptoJS.lib.WordArray.random(16);\n         */\n        random: function (nBytes) {\n            var words = [];\n            for (var i = 0; i < nBytes; i += 4) {\n                words.push((Math.random() * 0x100000000) | 0);\n            }\n\n            return new WordArray.init(words, nBytes);\n        }\n    });\n\n    /**\n     * Encoder namespace.\n     */\n    var C_enc = C.enc = {};\n\n    /**\n     * Hex encoding strategy.\n     */\n    var Hex = C_enc.Hex = {\n        /**\n         * Converts a word array to a hex string.\n         *\n         * @param {WordArray} wordArray The word array.\n         *\n         * @return {string} The hex string.\n         *\n         * @static\n         *\n         * @example\n         *\n         *     var hexString = CryptoJS.enc.Hex.stringify(wordArray);\n         */\n        stringify: function (wordArray) {\n            // Shortcuts\n            var words = wordArray.words;\n            var sigBytes = wordArray.sigBytes;\n\n            // Convert\n            var hexChars = [];\n            for (var i = 0; i < sigBytes; i++) {\n                var bite = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff;\n                hexChars.push((bite >>> 4).toString(16));\n                hexChars.push((bite & 0x0f).toString(16));\n            }\n\n            return hexChars.join('');\n        },\n\n        /**\n         * Converts a hex string to a word array.\n         *\n         * @param {string} hexStr The hex string.\n         *\n         * @return {WordArray} The word array.\n         *\n         * @static\n         *\n         * @example\n         *\n         *     var wordArray = CryptoJS.enc.Hex.parse(hexString);\n         */\n        parse: function (hexStr) {\n            // Shortcut\n            var hexStrLength = hexStr.length;\n\n            // Convert\n            var words = [];\n            for (var i = 0; i < hexStrLength; i += 2) {\n                words[i >>> 3] |= parseInt(hexStr.substr(i, 2), 16) << (24 - (i % 8) * 4);\n            }\n\n            return new WordArray.init(words, hexStrLength / 2);\n        }\n    };\n\n    /**\n     * Latin1 encoding strategy.\n     */\n    var Latin1 = C_enc.Latin1 = {\n        /**\n         * Converts a word array to a Latin1 string.\n         *\n         * @param {WordArray} wordArray The word array.\n         *\n         * @return {string} The Latin1 string.\n         *\n         * @static\n         *\n         * @example\n         *\n         *     var latin1String = CryptoJS.enc.Latin1.stringify(wordArray);\n         */\n        stringify: function (wordArray) {\n            // Shortcuts\n            var words = wordArray.words;\n            var sigBytes = wordArray.sigBytes;\n\n            // Convert\n            var latin1Chars = [];\n            for (var i = 0; i < sigBytes; i++) {\n                var bite = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff;\n                latin1Chars.push(String.fromCharCode(bite));\n            }\n\n            return latin1Chars.join('');\n        },\n\n        /**\n         * Converts a Latin1 string to a word array.\n         *\n         * @param {string} latin1Str The Latin1 string.\n         *\n         * @return {WordArray} The word array.\n         *\n         * @static\n         *\n         * @example\n         *\n         *     var wordArray = CryptoJS.enc.Latin1.parse(latin1String);\n         */\n        parse: function (latin1Str) {\n            // Shortcut\n            var latin1StrLength = latin1Str.length;\n\n            // Convert\n            var words = [];\n            for (var i = 0; i < latin1StrLength; i++) {\n                words[i >>> 2] |= (latin1Str.charCodeAt(i) & 0xff) << (24 - (i % 4) * 8);\n            }\n\n            return new WordArray.init(words, latin1StrLength);\n        }\n    };\n\n    /**\n     * UTF-8 encoding strategy.\n     */\n    var Utf8 = C_enc.Utf8 = {\n        /**\n         * Converts a word array to a UTF-8 string.\n         *\n         * @param {WordArray} wordArray The word array.\n         *\n         * @return {string} The UTF-8 string.\n         *\n         * @static\n         *\n         * @example\n         *\n         *     var utf8String = CryptoJS.enc.Utf8.stringify(wordArray);\n         */\n        stringify: function (wordArray) {\n            try {\n                return decodeURIComponent(escape(Latin1.stringify(wordArray)));\n            } catch (e) {\n                throw new Error('Malformed UTF-8 data');\n            }\n        },\n\n        /**\n         * Converts a UTF-8 string to a word array.\n         *\n         * @param {string} utf8Str The UTF-8 string.\n         *\n         * @return {WordArray} The word array.\n         *\n         * @static\n         *\n         * @example\n         *\n         *     var wordArray = CryptoJS.enc.Utf8.parse(utf8String);\n         */\n        parse: function (utf8Str) {\n            return Latin1.parse(unescape(encodeURIComponent(utf8Str)));\n        }\n    };\n\n    /**\n     * Abstract buffered block algorithm template.\n     *\n     * The property blockSize must be implemented in a concrete subtype.\n     *\n     * @property {number} _minBufferSize The number of blocks that should be kept unprocessed in the buffer. Default: 0\n     */\n    var BufferedBlockAlgorithm = C_lib.BufferedBlockAlgorithm = Base.extend({\n        /**\n         * Resets this block algorithm's data buffer to its initial state.\n         *\n         * @example\n         *\n         *     bufferedBlockAlgorithm.reset();\n         */\n        reset: function () {\n            // Initial values\n            this._data = new WordArray.init();\n            this._nDataBytes = 0;\n        },\n\n        /**\n         * Adds new data to this block algorithm's buffer.\n         *\n         * @param {WordArray|string} data The data to append. Strings are converted to a WordArray using UTF-8.\n         *\n         * @example\n         *\n         *     bufferedBlockAlgorithm._append('data');\n         *     bufferedBlockAlgorithm._append(wordArray);\n         */\n        _append: function (data) {\n            // Convert string to WordArray, else assume WordArray already\n            if (typeof data == 'string') {\n                data = Utf8.parse(data);\n            }\n\n            // Append\n            this._data.concat(data);\n            this._nDataBytes += data.sigBytes;\n        },\n\n        /**\n         * Processes available data blocks.\n         *\n         * This method invokes _doProcessBlock(offset), which must be implemented by a concrete subtype.\n         *\n         * @param {boolean} doFlush Whether all blocks and partial blocks should be processed.\n         *\n         * @return {WordArray} The processed data.\n         *\n         * @example\n         *\n         *     var processedData = bufferedBlockAlgorithm._process();\n         *     var processedData = bufferedBlockAlgorithm._process(!!'flush');\n         */\n        _process: function (doFlush) {\n            // Shortcuts\n            var data = this._data;\n            var dataWords = data.words;\n            var dataSigBytes = data.sigBytes;\n            var blockSize = this.blockSize;\n            var blockSizeBytes = blockSize * 4;\n\n            // Count blocks ready\n            var nBlocksReady = dataSigBytes / blockSizeBytes;\n            if (doFlush) {\n                // Round up to include partial blocks\n                nBlocksReady = Math.ceil(nBlocksReady);\n            } else {\n                // Round down to include only full blocks,\n                // less the number of blocks that must remain in the buffer\n                nBlocksReady = Math.max((nBlocksReady | 0) - this._minBufferSize, 0);\n            }\n\n            // Count words ready\n            var nWordsReady = nBlocksReady * blockSize;\n\n            // Count bytes ready\n            var nBytesReady = Math.min(nWordsReady * 4, dataSigBytes);\n\n            // Process blocks\n            if (nWordsReady) {\n                for (var offset = 0; offset < nWordsReady; offset += blockSize) {\n                    // Perform concrete-algorithm logic\n                    this._doProcessBlock(dataWords, offset);\n                }\n\n                // Remove processed words\n                var processedWords = dataWords.splice(0, nWordsReady);\n                data.sigBytes -= nBytesReady;\n            }\n\n            // Return processed words\n            return new WordArray.init(processedWords, nBytesReady);\n        },\n\n        /**\n         * Creates a copy of this object.\n         *\n         * @return {Object} The clone.\n         *\n         * @example\n         *\n         *     var clone = bufferedBlockAlgorithm.clone();\n         */\n        clone: function () {\n            var clone = Base.clone.call(this);\n            clone._data = this._data.clone();\n\n            return clone;\n        },\n\n        _minBufferSize: 0\n    });\n\n    /**\n     * Abstract hasher template.\n     *\n     * @property {number} blockSize The number of 32-bit words this hasher operates on. Default: 16 (512 bits)\n     */\n    var Hasher = C_lib.Hasher = BufferedBlockAlgorithm.extend({\n        /**\n         * Configuration options.\n         */\n        cfg: Base.extend(),\n\n        /**\n         * Initializes a newly created hasher.\n         *\n         * @param {Object} cfg (Optional) The configuration options to use for this hash computation.\n         *\n         * @example\n         *\n         *     var hasher = CryptoJS.algo.SHA256.create();\n         */\n        init: function (cfg) {\n            // Apply config defaults\n            this.cfg = this.cfg.extend(cfg);\n\n            // Set initial values\n            this.reset();\n        },\n\n        /**\n         * Resets this hasher to its initial state.\n         *\n         * @example\n         *\n         *     hasher.reset();\n         */\n        reset: function () {\n            // Reset data buffer\n            BufferedBlockAlgorithm.reset.call(this);\n\n            // Perform concrete-hasher logic\n            this._doReset();\n        },\n\n        /**\n         * Updates this hasher with a message.\n         *\n         * @param {WordArray|string} messageUpdate The message to append.\n         *\n         * @return {Hasher} This hasher.\n         *\n         * @example\n         *\n         *     hasher.update('message');\n         *     hasher.update(wordArray);\n         */\n        update: function (messageUpdate) {\n            // Append\n            this._append(messageUpdate);\n\n            // Update the hash\n            this._process();\n\n            // Chainable\n            return this;\n        },\n\n        /**\n         * Finalizes the hash computation.\n         * Note that the finalize operation is effectively a destructive, read-once operation.\n         *\n         * @param {WordArray|string} messageUpdate (Optional) A final message update.\n         *\n         * @return {WordArray} The hash.\n         *\n         * @example\n         *\n         *     var hash = hasher.finalize();\n         *     var hash = hasher.finalize('message');\n         *     var hash = hasher.finalize(wordArray);\n         */\n        finalize: function (messageUpdate) {\n            // Final message update\n            if (messageUpdate) {\n                this._append(messageUpdate);\n            }\n\n            // Perform concrete-hasher logic\n            var hash = this._doFinalize();\n\n            return hash;\n        },\n\n        blockSize: 512/32,\n\n        /**\n         * Creates a shortcut function to a hasher's object interface.\n         *\n         * @param {Hasher} hasher The hasher to create a helper for.\n         *\n         * @return {Function} The shortcut function.\n         *\n         * @static\n         *\n         * @example\n         *\n         *     var SHA256 = CryptoJS.lib.Hasher._createHelper(CryptoJS.algo.SHA256);\n         */\n        _createHelper: function (hasher) {\n            return function (message, cfg) {\n                return new hasher.init(cfg).finalize(message);\n            };\n        },\n\n        /**\n         * Creates a shortcut function to the HMAC's object interface.\n         *\n         * @param {Hasher} hasher The hasher to use in this HMAC helper.\n         *\n         * @return {Function} The shortcut function.\n         *\n         * @static\n         *\n         * @example\n         *\n         *     var HmacSHA256 = CryptoJS.lib.Hasher._createHmacHelper(CryptoJS.algo.SHA256);\n         */\n        _createHmacHelper: function (hasher) {\n            return function (message, key) {\n                return new C_algo.HMAC.init(hasher, key).finalize(message);\n            };\n        }\n    });\n\n    /**\n     * Algorithm namespace.\n     */\n    var C_algo = C.algo = {};\n\n    return C;\n}(Math));\n"
  },
  {
    "path": "client/js/third-party/crypto-js/enc-base64.js",
    "content": "/*\nCryptoJS v3.1.2\ncode.google.com/p/crypto-js\n(c) 2009-2013 by Jeff Mott. All rights reserved.\ncode.google.com/p/crypto-js/wiki/License\n*/\n(function () {\n    // Shortcuts\n    var C = qq.CryptoJS;\n    var C_lib = C.lib;\n    var WordArray = C_lib.WordArray;\n    var C_enc = C.enc;\n\n    /**\n     * Base64 encoding strategy.\n     */\n    var Base64 = C_enc.Base64 = {\n        /**\n         * Converts a word array to a Base64 string.\n         *\n         * @param {WordArray} wordArray The word array.\n         *\n         * @return {string} The Base64 string.\n         *\n         * @static\n         *\n         * @example\n         *\n         *     var base64String = CryptoJS.enc.Base64.stringify(wordArray);\n         */\n        stringify: function (wordArray) {\n            // Shortcuts\n            var words = wordArray.words;\n            var sigBytes = wordArray.sigBytes;\n            var map = this._map;\n\n            // Clamp excess bits\n            wordArray.clamp();\n\n            // Convert\n            var base64Chars = [];\n            for (var i = 0; i < sigBytes; i += 3) {\n                var byte1 = (words[i >>> 2]       >>> (24 - (i % 4) * 8))       & 0xff;\n                var byte2 = (words[(i + 1) >>> 2] >>> (24 - ((i + 1) % 4) * 8)) & 0xff;\n                var byte3 = (words[(i + 2) >>> 2] >>> (24 - ((i + 2) % 4) * 8)) & 0xff;\n\n                var triplet = (byte1 << 16) | (byte2 << 8) | byte3;\n\n                for (var j = 0; (j < 4) && (i + j * 0.75 < sigBytes); j++) {\n                    base64Chars.push(map.charAt((triplet >>> (6 * (3 - j))) & 0x3f));\n                }\n            }\n\n            // Add padding\n            var paddingChar = map.charAt(64);\n            if (paddingChar) {\n                while (base64Chars.length % 4) {\n                    base64Chars.push(paddingChar);\n                }\n            }\n\n            return base64Chars.join('');\n        },\n\n        /**\n         * Converts a Base64 string to a word array.\n         *\n         * @param {string} base64Str The Base64 string.\n         *\n         * @return {WordArray} The word array.\n         *\n         * @static\n         *\n         * @example\n         *\n         *     var wordArray = CryptoJS.enc.Base64.parse(base64String);\n         */\n        parse: function (base64Str) {\n            // Shortcuts\n            var base64StrLength = base64Str.length;\n            var map = this._map;\n\n            // Ignore padding\n            var paddingChar = map.charAt(64);\n            if (paddingChar) {\n                var paddingIndex = base64Str.indexOf(paddingChar);\n                if (paddingIndex != -1) {\n                    base64StrLength = paddingIndex;\n                }\n            }\n\n            // Convert\n            var words = [];\n            var nBytes = 0;\n            for (var i = 0; i < base64StrLength; i++) {\n                if (i % 4) {\n                    var bits1 = map.indexOf(base64Str.charAt(i - 1)) << ((i % 4) * 2);\n                    var bits2 = map.indexOf(base64Str.charAt(i)) >>> (6 - (i % 4) * 2);\n                    words[nBytes >>> 2] |= (bits1 | bits2) << (24 - (nBytes % 4) * 8);\n                    nBytes++;\n                }\n            }\n\n            return WordArray.create(words, nBytes);\n        },\n\n        _map: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='\n    };\n}());\n"
  },
  {
    "path": "client/js/third-party/crypto-js/hmac.js",
    "content": "/*\nCryptoJS v3.1.2\ncode.google.com/p/crypto-js\n(c) 2009-2013 by Jeff Mott. All rights reserved.\ncode.google.com/p/crypto-js/wiki/License\n*/\n(function () {\n    // Shortcuts\n    var C = qq.CryptoJS;\n    var C_lib = C.lib;\n    var Base = C_lib.Base;\n    var C_enc = C.enc;\n    var Utf8 = C_enc.Utf8;\n    var C_algo = C.algo;\n\n    /**\n     * HMAC algorithm.\n     */\n    var HMAC = C_algo.HMAC = Base.extend({\n        /**\n         * Initializes a newly created HMAC.\n         *\n         * @param {Hasher} hasher The hash algorithm to use.\n         * @param {WordArray|string} key The secret key.\n         *\n         * @example\n         *\n         *     var hmacHasher = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, key);\n         */\n        init: function (hasher, key) {\n            // Init hasher\n            hasher = this._hasher = new hasher.init();\n\n            // Convert string to WordArray, else assume WordArray already\n            if (typeof key == 'string') {\n                key = Utf8.parse(key);\n            }\n\n            // Shortcuts\n            var hasherBlockSize = hasher.blockSize;\n            var hasherBlockSizeBytes = hasherBlockSize * 4;\n\n            // Allow arbitrary length keys\n            if (key.sigBytes > hasherBlockSizeBytes) {\n                key = hasher.finalize(key);\n            }\n\n            // Clamp excess bits\n            key.clamp();\n\n            // Clone key for inner and outer pads\n            var oKey = this._oKey = key.clone();\n            var iKey = this._iKey = key.clone();\n\n            // Shortcuts\n            var oKeyWords = oKey.words;\n            var iKeyWords = iKey.words;\n\n            // XOR keys with pad constants\n            for (var i = 0; i < hasherBlockSize; i++) {\n                oKeyWords[i] ^= 0x5c5c5c5c;\n                iKeyWords[i] ^= 0x36363636;\n            }\n            oKey.sigBytes = iKey.sigBytes = hasherBlockSizeBytes;\n\n            // Set initial values\n            this.reset();\n        },\n\n        /**\n         * Resets this HMAC to its initial state.\n         *\n         * @example\n         *\n         *     hmacHasher.reset();\n         */\n        reset: function () {\n            // Shortcut\n            var hasher = this._hasher;\n\n            // Reset\n            hasher.reset();\n            hasher.update(this._iKey);\n        },\n\n        /**\n         * Updates this HMAC with a message.\n         *\n         * @param {WordArray|string} messageUpdate The message to append.\n         *\n         * @return {HMAC} This HMAC instance.\n         *\n         * @example\n         *\n         *     hmacHasher.update('message');\n         *     hmacHasher.update(wordArray);\n         */\n        update: function (messageUpdate) {\n            this._hasher.update(messageUpdate);\n\n            // Chainable\n            return this;\n        },\n\n        /**\n         * Finalizes the HMAC computation.\n         * Note that the finalize operation is effectively a destructive, read-once operation.\n         *\n         * @param {WordArray|string} messageUpdate (Optional) A final message update.\n         *\n         * @return {WordArray} The HMAC.\n         *\n         * @example\n         *\n         *     var hmac = hmacHasher.finalize();\n         *     var hmac = hmacHasher.finalize('message');\n         *     var hmac = hmacHasher.finalize(wordArray);\n         */\n        finalize: function (messageUpdate) {\n            // Shortcut\n            var hasher = this._hasher;\n\n            // Compute HMAC\n            var innerHash = hasher.finalize(messageUpdate);\n            hasher.reset();\n            var hmac = hasher.finalize(this._oKey.clone().concat(innerHash));\n\n            return hmac;\n        }\n    });\n}());\n"
  },
  {
    "path": "client/js/third-party/crypto-js/lib-typedarrays.js",
    "content": "/*\r\nCryptoJS v3.1.2\r\ncode.google.com/p/crypto-js\r\n(c) 2009-2013 by Jeff Mott. All rights reserved.\r\ncode.google.com/p/crypto-js/wiki/License\r\n*/\r\n(function () {\r\n    // Check if typed arrays are supported\r\n    if (typeof ArrayBuffer != 'function') {\r\n        return;\r\n    }\r\n\r\n    // Shortcuts\r\n    var C = qq.CryptoJS;\r\n    var C_lib = C.lib;\r\n    var WordArray = C_lib.WordArray;\r\n\r\n    // Reference original init\r\n    var superInit = WordArray.init;\r\n\r\n    // Augment WordArray.init to handle typed arrays\r\n    var subInit = WordArray.init = function (typedArray) {\r\n        // Convert buffers to uint8\r\n        if (typedArray instanceof ArrayBuffer) {\r\n            typedArray = new Uint8Array(typedArray);\r\n        }\r\n\r\n        // Convert other array views to uint8\r\n        if (\r\n            typedArray instanceof Int8Array ||\r\n            typedArray instanceof Uint8ClampedArray ||\r\n            typedArray instanceof Int16Array ||\r\n            typedArray instanceof Uint16Array ||\r\n            typedArray instanceof Int32Array ||\r\n            typedArray instanceof Uint32Array ||\r\n            typedArray instanceof Float32Array ||\r\n            typedArray instanceof Float64Array\r\n        ) {\r\n            typedArray = new Uint8Array(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength);\r\n        }\r\n\r\n        // Handle Uint8Array\r\n        if (typedArray instanceof Uint8Array) {\r\n            // Shortcut\r\n            var typedArrayByteLength = typedArray.byteLength;\r\n\r\n            // Extract bytes\r\n            var words = [];\r\n            for (var i = 0; i < typedArrayByteLength; i++) {\r\n                words[i >>> 2] |= typedArray[i] << (24 - (i % 4) * 8);\r\n            }\r\n\r\n            // Initialize this word array\r\n            superInit.call(this, words, typedArrayByteLength);\r\n        } else {\r\n            // Else call normal init\r\n            superInit.apply(this, arguments);\r\n        }\r\n    };\r\n\r\n    subInit.prototype = WordArray;\r\n}());\r\n"
  },
  {
    "path": "client/js/third-party/crypto-js/sha1.js",
    "content": "/*\nCryptoJS v3.1.2\ncode.google.com/p/crypto-js\n(c) 2009-2013 by Jeff Mott. All rights reserved.\ncode.google.com/p/crypto-js/wiki/License\n*/\n(function () {\n    // Shortcuts\n    var C = qq.CryptoJS;\n    var C_lib = C.lib;\n    var WordArray = C_lib.WordArray;\n    var Hasher = C_lib.Hasher;\n    var C_algo = C.algo;\n\n    // Reusable object\n    var W = [];\n\n    /**\n     * SHA-1 hash algorithm.\n     */\n    var SHA1 = C_algo.SHA1 = Hasher.extend({\n        _doReset: function () {\n            this._hash = new WordArray.init([\n                0x67452301, 0xefcdab89,\n                0x98badcfe, 0x10325476,\n                0xc3d2e1f0\n            ]);\n        },\n\n        _doProcessBlock: function (M, offset) {\n            // Shortcut\n            var H = this._hash.words;\n\n            // Working variables\n            var a = H[0];\n            var b = H[1];\n            var c = H[2];\n            var d = H[3];\n            var e = H[4];\n\n            // Computation\n            for (var i = 0; i < 80; i++) {\n                if (i < 16) {\n                    W[i] = M[offset + i] | 0;\n                } else {\n                    var n = W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16];\n                    W[i] = (n << 1) | (n >>> 31);\n                }\n\n                var t = ((a << 5) | (a >>> 27)) + e + W[i];\n                if (i < 20) {\n                    t += ((b & c) | (~b & d)) + 0x5a827999;\n                } else if (i < 40) {\n                    t += (b ^ c ^ d) + 0x6ed9eba1;\n                } else if (i < 60) {\n                    t += ((b & c) | (b & d) | (c & d)) - 0x70e44324;\n                } else /* if (i < 80) */ {\n                    t += (b ^ c ^ d) - 0x359d3e2a;\n                }\n\n                e = d;\n                d = c;\n                c = (b << 30) | (b >>> 2);\n                b = a;\n                a = t;\n            }\n\n            // Intermediate hash value\n            H[0] = (H[0] + a) | 0;\n            H[1] = (H[1] + b) | 0;\n            H[2] = (H[2] + c) | 0;\n            H[3] = (H[3] + d) | 0;\n            H[4] = (H[4] + e) | 0;\n        },\n\n        _doFinalize: function () {\n            // Shortcuts\n            var data = this._data;\n            var dataWords = data.words;\n\n            var nBitsTotal = this._nDataBytes * 8;\n            var nBitsLeft = data.sigBytes * 8;\n\n            // Add padding\n            dataWords[nBitsLeft >>> 5] |= 0x80 << (24 - nBitsLeft % 32);\n            dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 14] = Math.floor(nBitsTotal / 0x100000000);\n            dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 15] = nBitsTotal;\n            data.sigBytes = dataWords.length * 4;\n\n            // Hash final blocks\n            this._process();\n\n            // Return final computed hash\n            return this._hash;\n        },\n\n        clone: function () {\n            var clone = Hasher.clone.call(this);\n            clone._hash = this._hash.clone();\n\n            return clone;\n        }\n    });\n\n    /**\n     * Shortcut function to the hasher's object interface.\n     *\n     * @param {WordArray|string} message The message to hash.\n     *\n     * @return {WordArray} The hash.\n     *\n     * @static\n     *\n     * @example\n     *\n     *     var hash = CryptoJS.SHA1('message');\n     *     var hash = CryptoJS.SHA1(wordArray);\n     */\n    C.SHA1 = Hasher._createHelper(SHA1);\n\n    /**\n     * Shortcut function to the HMAC's object interface.\n     *\n     * @param {WordArray|string} message The message to hash.\n     * @param {WordArray|string} key The secret key.\n     *\n     * @return {WordArray} The HMAC.\n     *\n     * @static\n     *\n     * @example\n     *\n     *     var hmac = CryptoJS.HmacSHA1(message, key);\n     */\n    C.HmacSHA1 = Hasher._createHmacHelper(SHA1);\n}());\n"
  },
  {
    "path": "client/js/third-party/crypto-js/sha256.js",
    "content": "/*\r\nCryptoJS v3.1.2\r\ncode.google.com/p/crypto-js\r\n(c) 2009-2013 by Jeff Mott. All rights reserved.\r\ncode.google.com/p/crypto-js/wiki/License\r\n*/\r\n(function (Math) {\r\n    // Shortcuts\r\n    var C = qq.CryptoJS;\r\n    var C_lib = C.lib;\r\n    var WordArray = C_lib.WordArray;\r\n    var Hasher = C_lib.Hasher;\r\n    var C_algo = C.algo;\r\n\r\n    // Initialization and round constants tables\r\n    var H = [];\r\n    var K = [];\r\n\r\n    // Compute constants\r\n    (function () {\r\n        function isPrime(n) {\r\n            var sqrtN = Math.sqrt(n);\r\n            for (var factor = 2; factor <= sqrtN; factor++) {\r\n                if (!(n % factor)) {\r\n                    return false;\r\n                }\r\n            }\r\n\r\n            return true;\r\n        }\r\n\r\n        function getFractionalBits(n) {\r\n            return ((n - (n | 0)) * 0x100000000) | 0;\r\n        }\r\n\r\n        var n = 2;\r\n        var nPrime = 0;\r\n        while (nPrime < 64) {\r\n            if (isPrime(n)) {\r\n                if (nPrime < 8) {\r\n                    H[nPrime] = getFractionalBits(Math.pow(n, 1 / 2));\r\n                }\r\n                K[nPrime] = getFractionalBits(Math.pow(n, 1 / 3));\r\n\r\n                nPrime++;\r\n            }\r\n\r\n            n++;\r\n        }\r\n    }());\r\n\r\n    // Reusable object\r\n    var W = [];\r\n\r\n    /**\r\n     * SHA-256 hash algorithm.\r\n     */\r\n    var SHA256 = C_algo.SHA256 = Hasher.extend({\r\n        _doReset: function () {\r\n            this._hash = new WordArray.init(H.slice(0));\r\n        },\r\n\r\n        _doProcessBlock: function (M, offset) {\r\n            // Shortcut\r\n            var H = this._hash.words;\r\n\r\n            // Working variables\r\n            var a = H[0];\r\n            var b = H[1];\r\n            var c = H[2];\r\n            var d = H[3];\r\n            var e = H[4];\r\n            var f = H[5];\r\n            var g = H[6];\r\n            var h = H[7];\r\n\r\n            // Computation\r\n            for (var i = 0; i < 64; i++) {\r\n                if (i < 16) {\r\n                    W[i] = M[offset + i] | 0;\r\n                } else {\r\n                    var gamma0x = W[i - 15];\r\n                    var gamma0  = ((gamma0x << 25) | (gamma0x >>> 7))  ^\r\n                                  ((gamma0x << 14) | (gamma0x >>> 18)) ^\r\n                                   (gamma0x >>> 3);\r\n\r\n                    var gamma1x = W[i - 2];\r\n                    var gamma1  = ((gamma1x << 15) | (gamma1x >>> 17)) ^\r\n                                  ((gamma1x << 13) | (gamma1x >>> 19)) ^\r\n                                   (gamma1x >>> 10);\r\n\r\n                    W[i] = gamma0 + W[i - 7] + gamma1 + W[i - 16];\r\n                }\r\n\r\n                var ch  = (e & f) ^ (~e & g);\r\n                var maj = (a & b) ^ (a & c) ^ (b & c);\r\n\r\n                var sigma0 = ((a << 30) | (a >>> 2)) ^ ((a << 19) | (a >>> 13)) ^ ((a << 10) | (a >>> 22));\r\n                var sigma1 = ((e << 26) | (e >>> 6)) ^ ((e << 21) | (e >>> 11)) ^ ((e << 7)  | (e >>> 25));\r\n\r\n                var t1 = h + sigma1 + ch + K[i] + W[i];\r\n                var t2 = sigma0 + maj;\r\n\r\n                h = g;\r\n                g = f;\r\n                f = e;\r\n                e = (d + t1) | 0;\r\n                d = c;\r\n                c = b;\r\n                b = a;\r\n                a = (t1 + t2) | 0;\r\n            }\r\n\r\n            // Intermediate hash value\r\n            H[0] = (H[0] + a) | 0;\r\n            H[1] = (H[1] + b) | 0;\r\n            H[2] = (H[2] + c) | 0;\r\n            H[3] = (H[3] + d) | 0;\r\n            H[4] = (H[4] + e) | 0;\r\n            H[5] = (H[5] + f) | 0;\r\n            H[6] = (H[6] + g) | 0;\r\n            H[7] = (H[7] + h) | 0;\r\n        },\r\n\r\n        _doFinalize: function () {\r\n            // Shortcuts\r\n            var data = this._data;\r\n            var dataWords = data.words;\r\n\r\n            var nBitsTotal = this._nDataBytes * 8;\r\n            var nBitsLeft = data.sigBytes * 8;\r\n\r\n            // Add padding\r\n            dataWords[nBitsLeft >>> 5] |= 0x80 << (24 - nBitsLeft % 32);\r\n            dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 14] = Math.floor(nBitsTotal / 0x100000000);\r\n            dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 15] = nBitsTotal;\r\n            data.sigBytes = dataWords.length * 4;\r\n\r\n            // Hash final blocks\r\n            this._process();\r\n\r\n            // Return final computed hash\r\n            return this._hash;\r\n        },\r\n\r\n        clone: function () {\r\n            var clone = Hasher.clone.call(this);\r\n            clone._hash = this._hash.clone();\r\n\r\n            return clone;\r\n        }\r\n    });\r\n\r\n    /**\r\n     * Shortcut function to the hasher's object interface.\r\n     *\r\n     * @param {WordArray|string} message The message to hash.\r\n     *\r\n     * @return {WordArray} The hash.\r\n     *\r\n     * @static\r\n     *\r\n     * @example\r\n     *\r\n     *     var hash = CryptoJS.SHA256('message');\r\n     *     var hash = CryptoJS.SHA256(wordArray);\r\n     */\r\n    C.SHA256 = Hasher._createHelper(SHA256);\r\n\r\n    /**\r\n     * Shortcut function to the HMAC's object interface.\r\n     *\r\n     * @param {WordArray|string} message The message to hash.\r\n     * @param {WordArray|string} key The secret key.\r\n     *\r\n     * @return {WordArray} The HMAC.\r\n     *\r\n     * @static\r\n     *\r\n     * @example\r\n     *\r\n     *     var hmac = CryptoJS.HmacSHA256(message, key);\r\n     */\r\n    C.HmacSHA256 = Hasher._createHmacHelper(SHA256);\r\n}(Math));\r\n"
  },
  {
    "path": "client/js/total-progress.js",
    "content": "/* globals qq */\n/**\n * Keeps a running tally of total upload progress for a batch of files.\n *\n * @param callback Invoked when total progress changes, passing calculated total loaded & total size values.\n * @param getSize Function that returns the size of a file given its ID\n * @constructor\n */\nqq.TotalProgress = function(callback, getSize) {\n    \"use strict\";\n\n    var perFileProgress = {},\n        totalLoaded = 0,\n        totalSize = 0,\n\n        lastLoadedSent = -1,\n        lastTotalSent = -1,\n        callbackProxy = function(loaded, total) {\n            if (loaded !== lastLoadedSent || total !== lastTotalSent) {\n                callback(loaded, total);\n            }\n\n            lastLoadedSent = loaded;\n            lastTotalSent = total;\n        },\n\n        /**\n         * @param failed Array of file IDs that have failed\n         * @param retryable Array of file IDs that are retryable\n         * @returns true if none of the failed files are eligible for retry\n         */\n        noRetryableFiles = function(failed, retryable) {\n            var none = true;\n\n            qq.each(failed, function(idx, failedId) {\n                if (qq.indexOf(retryable, failedId) >= 0) {\n                    none = false;\n                    return false;\n                }\n            });\n\n            return none;\n        },\n\n        onCancel = function(id) {\n            updateTotalProgress(id, -1, -1);\n            delete perFileProgress[id];\n        },\n\n        onAllComplete = function(successful, failed, retryable) {\n            if (failed.length === 0 || noRetryableFiles(failed, retryable)) {\n                callbackProxy(totalSize, totalSize);\n                this.reset();\n            }\n        },\n\n        onNew = function(id) {\n            var size = getSize(id);\n\n            // We might not know the size yet, such as for blob proxies\n            if (size > 0) {\n                updateTotalProgress(id, 0, size);\n                perFileProgress[id] = {loaded: 0, total: size};\n            }\n        },\n\n        /**\n         * Invokes the callback with the current total progress of all files in the batch.  Called whenever it may\n         * be appropriate to re-calculate and disseminate this data.\n         *\n         * @param id ID of a file that has changed in some important way\n         * @param newLoaded New loaded value for this file.  -1 if this value should no longer be part of calculations\n         * @param newTotal New total size of the file.  -1 if this value should no longer be part of calculations\n         */\n        updateTotalProgress = function(id, newLoaded, newTotal) {\n            var oldLoaded = perFileProgress[id] ? perFileProgress[id].loaded : 0,\n                oldTotal = perFileProgress[id] ? perFileProgress[id].total : 0;\n\n            if (newLoaded === -1 && newTotal === -1) {\n                totalLoaded -= oldLoaded;\n                totalSize -= oldTotal;\n            }\n            else {\n                if (newLoaded) {\n                    totalLoaded += newLoaded - oldLoaded;\n                }\n                if (newTotal) {\n                    totalSize += newTotal - oldTotal;\n                }\n            }\n\n            callbackProxy(totalLoaded, totalSize);\n        };\n\n    qq.extend(this, {\n        // Called when a batch of files has completed uploading.\n        onAllComplete: onAllComplete,\n\n        // Called when the status of a file has changed.\n        onStatusChange: function(id, oldStatus, newStatus) {\n            if (newStatus === qq.status.CANCELED || newStatus === qq.status.REJECTED) {\n                onCancel(id);\n            }\n            else if (newStatus === qq.status.SUBMITTING) {\n                onNew(id);\n            }\n        },\n\n        // Called whenever the upload progress of an individual file has changed.\n        onIndividualProgress: function(id, loaded, total) {\n            updateTotalProgress(id, loaded, total);\n            perFileProgress[id] = {loaded: loaded, total: total};\n        },\n\n        // Called whenever the total size of a file has changed, such as when the size of a generated blob is known.\n        onNewSize: function(id) {\n            onNew(id);\n        },\n\n        reset: function() {\n            perFileProgress = {};\n            totalLoaded = 0;\n            totalSize = 0;\n        }\n    });\n};\n"
  },
  {
    "path": "client/js/traditional/all-chunks-done.ajax.requester.js",
    "content": "/*globals qq*/\n/**\n * Ajax requester used to send a POST to a traditional endpoint once all chunks for a specific file have uploaded\n * successfully.\n *\n * @param o Options from the caller - will override the defaults.\n * @constructor\n */\nqq.traditional.AllChunksDoneAjaxRequester = function(o) {\n    \"use strict\";\n\n    var requester,\n        options = {\n            cors: {\n                allowXdr: false,\n                expected: false,\n                sendCredentials: false\n            },\n            endpoint: null,\n            log: function(str, level) {},\n            method: \"POST\"\n        },\n        promises = {},\n        endpointHandler = {\n            get: function(id) {\n                if (qq.isFunction(options.endpoint)) {\n                    return options.endpoint(id);\n                }\n\n                return options.endpoint;\n            }\n        };\n\n    qq.extend(options, o);\n\n    requester = qq.extend(this, new qq.AjaxRequester({\n        acceptHeader: \"application/json\",\n        contentType: options.jsonPayload ? \"application/json\" : \"application/x-www-form-urlencoded\",\n        validMethods: [options.method],\n        method: options.method,\n        endpointStore: endpointHandler,\n        allowXRequestedWithAndCacheControl: false,\n        cors: options.cors,\n        log: options.log,\n        onComplete: function(id, xhr, isError) {\n            var promise = promises[id];\n\n            delete promises[id];\n\n            if (isError) {\n                promise.failure(xhr);\n            }\n            else {\n                promise.success(xhr);\n            }\n        }\n    }));\n\n    qq.extend(this, {\n        complete: function(id, xhr, params, headers) {\n            var promise = new qq.Promise();\n\n            options.log(\"Submitting All Chunks Done request for \" + id);\n\n            promises[id] = promise;\n\n            requester.initTransport(id)\n                .withParams(options.params(id) || params)\n                .withHeaders(options.headers(id) || headers)\n                .send(xhr);\n\n            return promise;\n        }\n    });\n};\n"
  },
  {
    "path": "client/js/traditional/traditional.form.upload.handler.js",
    "content": "/*globals qq*/\n/**\n * Upload handler used that assumes the current user agent does not have any support for the\n * File API, and, therefore, makes use of iframes and forms to submit the files directly to\n * a generic server.\n *\n * @param options Options passed from the base handler\n * @param proxy Callbacks & methods used to query for or push out data/changes\n */\nqq.traditional = qq.traditional || {};\nqq.traditional.FormUploadHandler = function(options, proxy) {\n    \"use strict\";\n\n    var handler = this,\n        getName = proxy.getName,\n        getUuid = proxy.getUuid,\n        log = proxy.log;\n\n    /**\n     * Returns json object received by iframe from server.\n     */\n    function getIframeContentJson(id, iframe) {\n        /*jshint evil: true*/\n\n        var response, doc, innerHtml;\n\n        //IE may throw an \"access is denied\" error when attempting to access contentDocument on the iframe in some cases\n        try {\n            // iframe.contentWindow.document - for IE<7\n            doc = iframe.contentDocument || iframe.contentWindow.document;\n            innerHtml = doc.body.innerHTML;\n\n            log(\"converting iframe's innerHTML to JSON\");\n            log(\"innerHTML = \" + innerHtml);\n            //plain text response may be wrapped in <pre> tag\n            if (innerHtml && innerHtml.match(/^<pre/i)) {\n                innerHtml = doc.body.firstChild.firstChild.nodeValue;\n            }\n\n            response = handler._parseJsonResponse(innerHtml);\n        }\n        catch (error) {\n            log(\"Error when attempting to parse form upload response (\" + error.message + \")\", \"error\");\n            response = {success: false};\n        }\n\n        return response;\n    }\n\n    /**\n     * Creates form, that will be submitted to iframe\n     */\n    function createForm(id, iframe) {\n        var params = options.paramsStore.get(id),\n            method = options.method.toLowerCase() === \"get\" ? \"GET\" : \"POST\",\n            endpoint = options.endpointStore.get(id),\n            name = getName(id);\n\n        params[options.uuidName] = getUuid(id);\n        params[options.filenameParam] = name;\n\n        return handler._initFormForUpload({\n            method: method,\n            endpoint: endpoint,\n            params: params,\n            paramsInBody: options.paramsInBody,\n            targetName: iframe.name\n        });\n    }\n\n    this.uploadFile = function(id) {\n        var input = handler.getInput(id),\n            iframe = handler._createIframe(id),\n            promise = new qq.Promise(),\n            form;\n\n        form = createForm(id, iframe);\n        form.appendChild(input);\n\n        handler._attachLoadEvent(iframe, function(responseFromMessage) {\n            log(\"iframe loaded\");\n\n            var response = responseFromMessage ? responseFromMessage : getIframeContentJson(id, iframe);\n\n            handler._detachLoadEvent(id);\n\n            //we can't remove an iframe if the iframe doesn't belong to the same domain\n            if (!options.cors.expected) {\n                qq(iframe).remove();\n            }\n\n            if (response.success) {\n                promise.success(response);\n            }\n            else {\n                promise.failure(response);\n            }\n        });\n\n        log(\"Sending upload request for \" + id);\n        form.submit();\n        qq(form).remove();\n\n        return promise;\n    };\n\n    qq.extend(this, new qq.FormUploadHandler({\n        options: {\n            isCors: options.cors.expected,\n            inputName: options.inputName\n        },\n\n        proxy: {\n            onCancel: options.onCancel,\n            getName: getName,\n            getUuid: getUuid,\n            log: log\n        }\n    }));\n};\n"
  },
  {
    "path": "client/js/traditional/traditional.xhr.upload.handler.js",
    "content": "/*globals qq*/\n/**\n * Upload handler used to upload to traditional endpoints.  It depends on File API support, and, therefore,\n * makes use of `XMLHttpRequest` level 2 to upload `File`s and `Blob`s to a generic server.\n *\n * @param spec Options passed from the base handler\n * @param proxy Callbacks & methods used to query for or push out data/changes\n */\nqq.traditional = qq.traditional || {};\nqq.traditional.XhrUploadHandler = function(spec, proxy) {\n    \"use strict\";\n\n    var handler = this,\n        getName = proxy.getName,\n        getSize = proxy.getSize,\n        getUuid = proxy.getUuid,\n        log = proxy.log,\n        multipart = spec.forceMultipart || spec.paramsInBody,\n\n        addChunkingSpecificParams = function(id, params, chunkData) {\n            var size = getSize(id),\n                name = getName(id);\n\n            if (!spec.omitDefaultParams) {\n                params[spec.chunking.paramNames.partIndex] = chunkData.part;\n                params[spec.chunking.paramNames.partByteOffset] = chunkData.start;\n                params[spec.chunking.paramNames.chunkSize] = chunkData.size;\n                params[spec.chunking.paramNames.totalParts] = chunkData.count;\n                params[spec.totalFileSizeName] = size;\n            }\n\n            /**\n             * When a Blob is sent in a multipart request, the filename value in the content-disposition header is either \"blob\"\n             * or an empty string.  So, we will need to include the actual file name as a param in this case.\n             */\n            if (multipart && !spec.omitDefaultParams) {\n                params[spec.filenameParam] = name;\n            }\n        },\n\n        allChunksDoneRequester = new qq.traditional.AllChunksDoneAjaxRequester({\n            cors: spec.cors,\n            endpoint: spec.chunking.success.endpoint,\n            headers: spec.chunking.success.headers,\n            jsonPayload: spec.chunking.success.jsonPayload,\n            log: log,\n            method: spec.chunking.success.method,\n            params: spec.chunking.success.params\n        }),\n\n        createReadyStateChangedHandler = function(id, xhr) {\n            var promise = new qq.Promise();\n\n            xhr.onreadystatechange = function() {\n                if (xhr.readyState === 4) {\n                    var result = onUploadOrChunkComplete(id, xhr);\n\n                    if (result.success) {\n                        promise.success(result.response, xhr);\n                    }\n                    else {\n                        promise.failure(result.response, xhr);\n                    }\n                }\n            };\n\n            return promise;\n        },\n\n        getChunksCompleteParams = function(id) {\n            var params = spec.paramsStore.get(id),\n                name = getName(id),\n                size = getSize(id);\n\n            params[spec.uuidName] = getUuid(id);\n            params[spec.filenameParam] = name;\n            params[spec.totalFileSizeName] = size;\n            params[spec.chunking.paramNames.totalParts] = handler._getTotalChunks(id);\n\n            return params;\n        },\n\n        isErrorUploadResponse = function(xhr, response) {\n            return qq.indexOf([200, 201, 202, 203, 204], xhr.status) < 0 ||\n                (spec.requireSuccessJson && !response.success) ||\n                response.reset;\n        },\n\n        onUploadOrChunkComplete = function(id, xhr) {\n            var response;\n\n            log(\"xhr - server response received for \" + id);\n            log(\"responseText = \" + xhr.responseText);\n\n            response = parseResponse(true, xhr);\n\n            return {\n                success: !isErrorUploadResponse(xhr, response),\n                response: response\n            };\n        },\n\n        // If this is an upload response, we require a JSON payload, otherwise, it is optional.\n        parseResponse = function(upload, xhr) {\n            var response = {};\n\n            try {\n                log(qq.format(\"Received response status {} with body: {}\", xhr.status, xhr.responseText));\n                response = qq.parseJson(xhr.responseText);\n            }\n            catch (error) {\n                upload && spec.requireSuccessJson && log(\"Error when attempting to parse xhr response text (\" + error.message + \")\", \"error\");\n            }\n\n            return response;\n        },\n\n        sendChunksCompleteRequest = function(id) {\n            var promise = new qq.Promise();\n\n            allChunksDoneRequester.complete(\n                    id,\n                    handler._createXhr(id),\n                    getChunksCompleteParams(id),\n                    spec.customHeaders.get(id)\n                )\n                .then(function(xhr) {\n                    promise.success(parseResponse(false, xhr), xhr);\n                }, function(xhr) {\n                    promise.failure(parseResponse(false, xhr), xhr);\n                });\n\n            return promise;\n        },\n\n        setParamsAndGetEntityToSend = function(entityToSendParams) {\n            var fileOrBlob = entityToSendParams.fileOrBlob;\n            var id = entityToSendParams.id;\n            var xhr = entityToSendParams.xhr;\n            var xhrOverrides = entityToSendParams.xhrOverrides || {};\n            var customParams = entityToSendParams.customParams || {};\n            var defaultParams = entityToSendParams.params || {};\n            var xhrOverrideParams = xhrOverrides.params || {};\n            var params;\n\n            var formData = multipart ? new FormData() : null,\n                method = xhrOverrides.method || spec.method,\n                endpoint = xhrOverrides.endpoint || spec.endpointStore.get(id),\n                name = getName(id),\n                size = getSize(id);\n\n            if (spec.omitDefaultParams) {\n                params = qq.extend({}, customParams);\n                qq.extend(params, xhrOverrideParams);\n            }\n            else {\n                params = qq.extend({}, customParams);\n                qq.extend(params, xhrOverrideParams);\n                qq.extend(params, defaultParams);\n\n                params[spec.uuidName] = getUuid(id);\n                params[spec.filenameParam] = name;\n\n                if (multipart) {\n                    params[spec.totalFileSizeName] = size;\n                }\n                else if (!spec.paramsInBody) {\n                    params[spec.inputName] = name;\n                }\n            }\n\n            //build query string\n            if (!spec.paramsInBody) {\n                endpoint = qq.obj2url(params, endpoint);\n            }\n\n            xhr.open(method, endpoint, true);\n\n            if (spec.cors.expected && spec.cors.sendCredentials) {\n                xhr.withCredentials = true;\n            }\n\n            if (multipart) {\n                if (spec.paramsInBody) {\n                    qq.obj2FormData(params, formData);\n                }\n\n                formData.append(spec.inputName, fileOrBlob);\n                return formData;\n            }\n\n            return fileOrBlob;\n        },\n\n        setUploadHeaders = function(headersOptions) {\n            var headerOverrides = headersOptions.headerOverrides;\n            var id = headersOptions.id;\n            var xhr = headersOptions.xhr;\n\n            if (headerOverrides) {\n                qq.each(headerOverrides, function(headerName, headerValue) {\n                    xhr.setRequestHeader(headerName, headerValue);\n                });\n            }\n            else {\n                var extraHeaders = spec.customHeaders.get(id),\n                    fileOrBlob = handler.getFile(id);\n\n                xhr.setRequestHeader(\"Accept\", \"application/json\");\n                xhr.setRequestHeader(\"X-Requested-With\", \"XMLHttpRequest\");\n                xhr.setRequestHeader(\"Cache-Control\", \"no-cache\");\n\n                if (!multipart) {\n                    xhr.setRequestHeader(\"Content-Type\", \"application/octet-stream\");\n                    //NOTE: return mime type in xhr works on chrome 16.0.9 firefox 11.0a2\n                    xhr.setRequestHeader(\"X-Mime-Type\", fileOrBlob.type);\n                }\n\n                qq.each(extraHeaders, function(name, val) {\n                    xhr.setRequestHeader(name, val);\n                });\n            }\n        };\n\n    qq.extend(this, {\n        uploadChunk: function(uploadChunkParams) {\n            var id = uploadChunkParams.id;\n            var chunkIdx = uploadChunkParams.chunkIdx;\n            var overrides = uploadChunkParams.overrides || {};\n            var resuming = uploadChunkParams.resuming;\n\n            var chunkData = handler._getChunkData(id, chunkIdx),\n                xhr = handler._createXhr(id, chunkIdx),\n                promise, toSend, customParams, params = {};\n\n            promise = createReadyStateChangedHandler(id, xhr);\n            handler._registerProgressHandler(id, chunkIdx, chunkData.size);\n            customParams = spec.paramsStore.get(id);\n            addChunkingSpecificParams(id, params, chunkData);\n\n            if (resuming) {\n                params[spec.resume.paramNames.resuming] = true;\n            }\n\n            toSend = setParamsAndGetEntityToSend({\n                fileOrBlob: chunkData.blob,\n                id: id,\n                customParams: customParams,\n                params: params,\n                xhr: xhr,\n                xhrOverrides: overrides\n            });\n\n            setUploadHeaders({\n                headerOverrides: overrides.headers,\n                id: id,\n                xhr: xhr\n            });\n\n            xhr.send(toSend);\n\n            return promise;\n        },\n\n        uploadFile: function(id) {\n            var fileOrBlob = handler.getFile(id),\n                promise, xhr, customParams, toSend;\n\n            xhr = handler._createXhr(id);\n            handler._registerProgressHandler(id);\n            promise = createReadyStateChangedHandler(id, xhr);\n            customParams = spec.paramsStore.get(id);\n\n            toSend = setParamsAndGetEntityToSend({\n                fileOrBlob: fileOrBlob,\n                id: id,\n                customParams: customParams,\n                xhr: xhr\n            });\n\n            setUploadHeaders({\n                id: id,\n                xhr: xhr\n            });\n\n            xhr.send(toSend);\n\n            return promise;\n        }\n    });\n\n    qq.extend(this, new qq.XhrUploadHandler({\n        options: qq.extend({namespace: \"traditional\"}, spec),\n        proxy: qq.extend({getEndpoint: spec.endpointStore.get}, proxy)\n    }));\n\n    qq.override(this, function(super_) {\n        return {\n            finalizeChunks: function(id) {\n                proxy.onFinalizing(id);\n\n                if (spec.chunking.success.endpoint) {\n                    return sendChunksCompleteRequest(id);\n                }\n                else {\n                    return super_.finalizeChunks(id, qq.bind(parseResponse, this, true));\n                }\n            }\n        };\n    });\n};\n"
  },
  {
    "path": "client/js/ui.handler.click.filebuttons.js",
    "content": "/* global qq */\nqq.FileButtonsClickHandler = function(s) {\n    \"use strict\";\n\n    var inheritedInternalApi = {},\n        spec = {\n            templating: null,\n            log: function(message, lvl) {},\n            onDeleteFile: function(fileId) {},\n            onCancel: function(fileId) {},\n            onRetry: function(fileId) {},\n            onPause: function(fileId) {},\n            onContinue: function(fileId) {},\n            onGetName: function(fileId) {}\n        },\n        buttonHandlers = {\n            cancel: function(id) { spec.onCancel(id); },\n            retry:  function(id) { spec.onRetry(id); },\n            deleteButton: function(id) { spec.onDeleteFile(id); },\n            pause: function(id) { spec.onPause(id); },\n            continueButton: function(id) { spec.onContinue(id); }\n        };\n\n    function examineEvent(target, event) {\n        qq.each(buttonHandlers, function(buttonType, handler) {\n            var firstLetterCapButtonType = buttonType.charAt(0).toUpperCase() + buttonType.slice(1),\n                fileId;\n\n            if (spec.templating[\"is\" + firstLetterCapButtonType](target)) {\n                fileId = spec.templating.getFileId(target);\n                qq.preventDefault(event);\n                spec.log(qq.format(\"Detected valid file button click event on file '{}', ID: {}.\", spec.onGetName(fileId), fileId));\n                handler(fileId);\n                return false;\n            }\n        });\n    }\n\n    qq.extend(spec, s);\n\n    spec.eventType = \"click\";\n    spec.onHandled = examineEvent;\n    spec.attachTo = spec.templating.getFileList();\n\n    qq.extend(this, new qq.UiEventHandler(spec, inheritedInternalApi));\n};\n"
  },
  {
    "path": "client/js/ui.handler.click.filename.js",
    "content": "/*globals qq */\n// Child of FilenameEditHandler.  Used to detect click events on filename display elements.\nqq.FilenameClickHandler = function(s) {\n    \"use strict\";\n\n    var inheritedInternalApi = {},\n        spec = {\n            templating: null,\n            log: function(message, lvl) {},\n            classes: {\n                file: \"qq-upload-file\",\n                editNameIcon: \"qq-edit-filename-icon\"\n            },\n            onGetUploadStatus: function(fileId) {},\n            onGetName: function(fileId) {}\n        };\n\n    qq.extend(spec, s);\n\n    // This will be called by the parent handler when a `click` event is received on the list element.\n    function examineEvent(target, event) {\n        if (spec.templating.isFileName(target) || spec.templating.isEditIcon(target)) {\n            var fileId = spec.templating.getFileId(target),\n                status = spec.onGetUploadStatus(fileId);\n\n            // We only allow users to change filenames of files that have been submitted but not yet uploaded.\n            if (status === qq.status.SUBMITTED) {\n                spec.log(qq.format(\"Detected valid filename click event on file '{}', ID: {}.\", spec.onGetName(fileId), fileId));\n                qq.preventDefault(event);\n\n                inheritedInternalApi.handleFilenameEdit(fileId, target, true);\n            }\n        }\n    }\n\n    spec.eventType = \"click\";\n    spec.onHandled = examineEvent;\n\n    qq.extend(this, new qq.FilenameEditHandler(spec, inheritedInternalApi));\n};\n"
  },
  {
    "path": "client/js/ui.handler.edit.filename.js",
    "content": "/*globals qq */\n// Handles edit-related events on a file item (FineUploader mode).  This is meant to be a parent handler.\n// Children will delegate to this handler when specific edit-related actions are detected.\nqq.FilenameEditHandler = function(s, inheritedInternalApi) {\n    \"use strict\";\n\n    var spec = {\n            templating: null,\n            log: function(message, lvl) {},\n            onGetUploadStatus: function(fileId) {},\n            onGetName: function(fileId) {},\n            onSetName: function(fileId, newName) {},\n            onEditingStatusChange: function(fileId, isEditing) {}\n        };\n\n    function getFilenameSansExtension(fileId) {\n        var filenameSansExt = spec.onGetName(fileId),\n            extIdx = filenameSansExt.lastIndexOf(\".\");\n\n        if (extIdx > 0) {\n            filenameSansExt = filenameSansExt.substr(0, extIdx);\n        }\n\n        return filenameSansExt;\n    }\n\n    function getOriginalExtension(fileId) {\n        var origName = spec.onGetName(fileId);\n        return qq.getExtension(origName);\n    }\n\n    // Callback iff the name has been changed\n    function handleNameUpdate(newFilenameInputEl, fileId) {\n        var newName = newFilenameInputEl.value,\n            origExtension;\n\n        if (newName !== undefined && qq.trimStr(newName).length > 0) {\n            origExtension = getOriginalExtension(fileId);\n\n            if (origExtension !== undefined) {\n                newName = newName + \".\" + origExtension;\n            }\n\n            spec.onSetName(fileId, newName);\n        }\n\n        spec.onEditingStatusChange(fileId, false);\n    }\n\n    // The name has been updated if the filename edit input loses focus.\n    function registerInputBlurHandler(inputEl, fileId) {\n        inheritedInternalApi.getDisposeSupport().attach(inputEl, \"blur\", function() {\n            handleNameUpdate(inputEl, fileId);\n        });\n    }\n\n    // The name has been updated if the user presses enter.\n    function registerInputEnterKeyHandler(inputEl, fileId) {\n        inheritedInternalApi.getDisposeSupport().attach(inputEl, \"keyup\", function(event) {\n\n            var code = event.keyCode || event.which;\n\n            if (code === 13) {\n                handleNameUpdate(inputEl, fileId);\n            }\n        });\n    }\n\n    qq.extend(spec, s);\n\n    spec.attachTo = spec.templating.getFileList();\n\n    qq.extend(this, new qq.UiEventHandler(spec, inheritedInternalApi));\n\n    qq.extend(inheritedInternalApi, {\n        handleFilenameEdit: function(id, target, focusInput) {\n            var newFilenameInputEl = spec.templating.getEditInput(id);\n\n            spec.onEditingStatusChange(id, true);\n\n            newFilenameInputEl.value = getFilenameSansExtension(id);\n\n            if (focusInput) {\n                newFilenameInputEl.focus();\n            }\n\n            registerInputBlurHandler(newFilenameInputEl, id);\n            registerInputEnterKeyHandler(newFilenameInputEl, id);\n        }\n    });\n};\n"
  },
  {
    "path": "client/js/ui.handler.events.js",
    "content": "/*globals qq */\n// Base handler for UI (FineUploader mode) events.\n// Some more specific handlers inherit from this one.\nqq.UiEventHandler = function(s, protectedApi) {\n    \"use strict\";\n\n    var disposer = new qq.DisposeSupport(),\n        spec = {\n            eventType: \"click\",\n            attachTo: null,\n            onHandled: function(target, event) {}\n        };\n\n    // This makes up the \"public\" API methods that will be accessible\n    // to instances constructing a base or child handler\n    qq.extend(this, {\n        addHandler: function(element) {\n            addHandler(element);\n        },\n\n        dispose: function() {\n            disposer.dispose();\n        }\n    });\n\n    function addHandler(element) {\n        disposer.attach(element, spec.eventType, function(event) {\n            // Only in IE: the `event` is a property of the `window`.\n            event = event || window.event;\n\n            // On older browsers, we must check the `srcElement` instead of the `target`.\n            var target = event.target || event.srcElement;\n\n            spec.onHandled(target, event);\n        });\n    }\n\n    // These make up the \"protected\" API methods that children of this base handler will utilize.\n    qq.extend(protectedApi, {\n        getFileIdFromItem: function(item) {\n            return item.qqFileId;\n        },\n\n        getDisposeSupport: function() {\n            return disposer;\n        }\n    });\n\n    qq.extend(spec, s);\n\n    if (spec.attachTo) {\n        addHandler(spec.attachTo);\n    }\n};\n"
  },
  {
    "path": "client/js/ui.handler.focus.filenameinput.js",
    "content": "/*globals qq */\n/**\n * Child of FilenameInputFocusInHandler.  Used to detect focus events on file edit input elements.  This child module is only\n * needed for UAs that do not support the focusin event.  Currently, only Firefox lacks this event.\n *\n * @param spec Overrides for default specifications\n */\nqq.FilenameInputFocusHandler = function(spec) {\n    \"use strict\";\n\n    spec.eventType = \"focus\";\n    spec.attachTo = null;\n\n    qq.extend(this, new qq.FilenameInputFocusInHandler(spec, {}));\n};\n"
  },
  {
    "path": "client/js/ui.handler.focusin.filenameinput.js",
    "content": "/*globals qq */\n// Child of FilenameEditHandler.  Used to detect focusin events on file edit input elements.\nqq.FilenameInputFocusInHandler = function(s, inheritedInternalApi) {\n    \"use strict\";\n\n    var spec = {\n            templating: null,\n            onGetUploadStatus: function(fileId) {},\n            log: function(message, lvl) {}\n        };\n\n    if (!inheritedInternalApi) {\n        inheritedInternalApi = {};\n    }\n\n    // This will be called by the parent handler when a `focusin` event is received on the list element.\n    function handleInputFocus(target, event) {\n        if (spec.templating.isEditInput(target)) {\n            var fileId = spec.templating.getFileId(target),\n                status = spec.onGetUploadStatus(fileId);\n\n            if (status === qq.status.SUBMITTED) {\n                spec.log(qq.format(\"Detected valid filename input focus event on file '{}', ID: {}.\", spec.onGetName(fileId), fileId));\n                inheritedInternalApi.handleFilenameEdit(fileId, target);\n            }\n        }\n    }\n\n    spec.eventType = \"focusin\";\n    spec.onHandled = handleInputFocus;\n\n    qq.extend(spec, s);\n    qq.extend(this, new qq.FilenameEditHandler(spec, inheritedInternalApi));\n};\n"
  },
  {
    "path": "client/js/upload-data.js",
    "content": "/*globals qq */\nqq.UploadData = function(uploaderProxy) {\n    \"use strict\";\n\n    var data = [],\n        byUuid = {},\n        byStatus = {},\n        byProxyGroupId = {},\n        byBatchId = {};\n\n    function getDataByIds(idOrIds) {\n        if (qq.isArray(idOrIds)) {\n            var entries = [];\n\n            qq.each(idOrIds, function(idx, id) {\n                entries.push(data[id]);\n            });\n\n            return entries;\n        }\n\n        return data[idOrIds];\n    }\n\n    function getDataByUuids(uuids) {\n        if (qq.isArray(uuids)) {\n            var entries = [];\n\n            qq.each(uuids, function(idx, uuid) {\n                entries.push(data[byUuid[uuid]]);\n            });\n\n            return entries;\n        }\n\n        return data[byUuid[uuids]];\n    }\n\n    function getDataByStatus(status) {\n        var statusResults = [],\n            statuses = [].concat(status);\n\n        qq.each(statuses, function(index, statusEnum) {\n            var statusResultIndexes = byStatus[statusEnum];\n\n            if (statusResultIndexes !== undefined) {\n                qq.each(statusResultIndexes, function(i, dataIndex) {\n                    statusResults.push(data[dataIndex]);\n                });\n            }\n        });\n\n        return statusResults;\n    }\n\n    qq.extend(this, {\n        /**\n         * Adds a new file to the data cache for tracking purposes.\n         *\n         * @param spec Data that describes this file.  Possible properties are:\n         *\n         * - uuid: Initial UUID for this file.\n         * - name: Initial name of this file.\n         * - size: Size of this file, omit if this cannot be determined\n         * - status: Initial `qq.status` for this file.  Omit for `qq.status.SUBMITTING`.\n         * - batchId: ID of the batch this file belongs to\n         * - proxyGroupId: ID of the proxy group associated with this file\n         * - onBeforeStatusChange(fileId): callback that is executed before the status change is broadcast\n         *\n         * @returns {number} Internal ID for this file.\n         */\n        addFile: function(spec) {\n            var status = spec.status || qq.status.SUBMITTING,\n                id = data.push({\n                    name: spec.name,\n                    originalName: spec.name,\n                    uuid: spec.uuid,\n                    size: spec.size == null ? -1 : spec.size,\n                    status: status,\n                    file: spec.file\n                }) - 1;\n\n            if (spec.batchId) {\n                data[id].batchId = spec.batchId;\n\n                if (byBatchId[spec.batchId] === undefined) {\n                    byBatchId[spec.batchId] = [];\n                }\n                byBatchId[spec.batchId].push(id);\n            }\n\n            if (spec.proxyGroupId) {\n                data[id].proxyGroupId = spec.proxyGroupId;\n\n                if (byProxyGroupId[spec.proxyGroupId] === undefined) {\n                    byProxyGroupId[spec.proxyGroupId] = [];\n                }\n                byProxyGroupId[spec.proxyGroupId].push(id);\n            }\n\n            data[id].id = id;\n            byUuid[spec.uuid] = id;\n\n            if (byStatus[status] === undefined) {\n                byStatus[status] = [];\n            }\n            byStatus[status].push(id);\n\n            spec.onBeforeStatusChange && spec.onBeforeStatusChange(id);\n            uploaderProxy.onStatusChange(id, null, status);\n\n            return id;\n        },\n\n        retrieve: function(optionalFilter) {\n            if (qq.isObject(optionalFilter) && data.length)  {\n                if (optionalFilter.id !== undefined) {\n                    return getDataByIds(optionalFilter.id);\n                }\n\n                else if (optionalFilter.uuid !== undefined) {\n                    return getDataByUuids(optionalFilter.uuid);\n                }\n\n                else if (optionalFilter.status) {\n                    return getDataByStatus(optionalFilter.status);\n                }\n            }\n            else {\n                return qq.extend([], data, true);\n            }\n        },\n\n        removeFileRef: function(id) {\n            var record = getDataByIds(id);\n\n            if (record) {\n                delete record.file;\n            }\n        },\n\n        reset: function() {\n            data = [];\n            byUuid = {};\n            byStatus = {};\n            byBatchId = {};\n        },\n\n        setStatus: function(id, newStatus) {\n            var oldStatus = data[id].status,\n                byStatusOldStatusIndex = qq.indexOf(byStatus[oldStatus], id);\n\n            byStatus[oldStatus].splice(byStatusOldStatusIndex, 1);\n\n            data[id].status = newStatus;\n\n            if (byStatus[newStatus] === undefined) {\n                byStatus[newStatus] = [];\n            }\n            byStatus[newStatus].push(id);\n\n            uploaderProxy.onStatusChange(id, oldStatus, newStatus);\n        },\n\n        uuidChanged: function(id, newUuid) {\n            var oldUuid = data[id].uuid;\n\n            data[id].uuid = newUuid;\n            byUuid[newUuid] = id;\n            delete byUuid[oldUuid];\n        },\n\n        updateName: function(id, newName) {\n            data[id].name = newName;\n        },\n\n        updateSize: function(id, newSize) {\n            data[id].size = newSize;\n        },\n\n        // Only applicable if this file has a parent that we may want to reference later.\n        setParentId: function(targetId, parentId) {\n            data[targetId].parentId = parentId;\n        },\n\n        getIdsInProxyGroup: function(id) {\n            var proxyGroupId = data[id].proxyGroupId;\n\n            if (proxyGroupId) {\n                return byProxyGroupId[proxyGroupId];\n            }\n            return [];\n        },\n\n        getIdsInBatch: function(id) {\n            var batchId = data[id].batchId;\n\n            return byBatchId[batchId];\n        }\n    });\n};\n\nqq.status = {\n    SUBMITTING: \"submitting\",\n    SUBMITTED: \"submitted\",\n    REJECTED: \"rejected\",\n    QUEUED: \"queued\",\n    CANCELED: \"canceled\",\n    PAUSED: \"paused\",\n    UPLOADING: \"uploading\",\n    UPLOAD_FINALIZING: \"upload finalizing\",\n    UPLOAD_RETRYING: \"retrying upload\",\n    UPLOAD_SUCCESSFUL: \"upload successful\",\n    UPLOAD_FAILED: \"upload failed\",\n    DELETE_FAILED: \"delete failed\",\n    DELETING: \"deleting\",\n    DELETED: \"deleted\"\n};\n"
  },
  {
    "path": "client/js/upload-handler/form.upload.handler.js",
    "content": "/* globals qq */\n/**\n * Common APIs exposed to creators of upload via form/iframe handlers.  This is reused and possibly overridden\n * in some cases by specific form upload handlers.\n *\n * @constructor\n */\nqq.FormUploadHandler = function(spec) {\n    \"use strict\";\n\n    var options = spec.options,\n        handler = this,\n        proxy = spec.proxy,\n        formHandlerInstanceId = qq.getUniqueId(),\n        onloadCallbacks = {},\n        detachLoadEvents = {},\n        postMessageCallbackTimers = {},\n        isCors = options.isCors,\n        inputName = options.inputName,\n        getUuid = proxy.getUuid,\n        log = proxy.log,\n        corsMessageReceiver = new qq.WindowReceiveMessage({log: log});\n\n    /**\n     * Remove any trace of the file from the handler.\n     *\n     * @param id ID of the associated file\n     */\n    function expungeFile(id) {\n        delete detachLoadEvents[id];\n\n        // If we are dealing with CORS, we might still be waiting for a response from a loaded iframe.\n        // In that case, terminate the timer waiting for a message from the loaded iframe\n        // and stop listening for any more messages coming from this iframe.\n        if (isCors) {\n            clearTimeout(postMessageCallbackTimers[id]);\n            delete postMessageCallbackTimers[id];\n            corsMessageReceiver.stopReceivingMessages(id);\n        }\n\n        var iframe = document.getElementById(handler._getIframeName(id));\n        if (iframe) {\n            // To cancel request set src to something else.  We use src=\"javascript:false;\"\n            // because it doesn't trigger ie6 prompt on https\n            /* jshint scripturl:true */\n            iframe.setAttribute(\"src\", \"javascript:false;\");\n\n            qq(iframe).remove();\n        }\n    }\n\n    /**\n     * @param iframeName `document`-unique Name of the associated iframe\n     * @returns {*} ID of the associated file\n     */\n    function getFileIdForIframeName(iframeName) {\n        return iframeName.split(\"_\")[0];\n    }\n\n    /**\n     * Generates an iframe to be used as a target for upload-related form submits.  This also adds the iframe\n     * to the current `document`.  Note that the iframe is hidden from view.\n     *\n     * @param name Name of the iframe.\n     * @returns {HTMLIFrameElement} The created iframe\n     */\n    function initIframeForUpload(name) {\n        var iframe = qq.toElement(\"<iframe src='javascript:false;' name='\" + name + \"' />\");\n\n        iframe.setAttribute(\"id\", name);\n\n        iframe.style.display = \"none\";\n        document.body.appendChild(iframe);\n\n        return iframe;\n    }\n\n    /**\n     * If we are in CORS mode, we must listen for messages (containing the server response) from the associated\n     * iframe, since we cannot directly parse the content of the iframe due to cross-origin restrictions.\n     *\n     * @param iframe Listen for messages on this iframe.\n     * @param callback Invoke this callback with the message from the iframe.\n     */\n    function registerPostMessageCallback(iframe, callback) {\n        var iframeName = iframe.id,\n            fileId = getFileIdForIframeName(iframeName),\n            uuid = getUuid(fileId);\n\n        onloadCallbacks[uuid] = callback;\n\n        // When the iframe has loaded (after the server responds to an upload request)\n        // declare the attempt a failure if we don't receive a valid message shortly after the response comes in.\n        detachLoadEvents[fileId] = qq(iframe).attach(\"load\", function() {\n            if (handler.getInput(fileId)) {\n                log(\"Received iframe load event for CORS upload request (iframe name \" + iframeName + \")\");\n\n                postMessageCallbackTimers[iframeName] = setTimeout(function() {\n                    var errorMessage = \"No valid message received from loaded iframe for iframe name \" + iframeName;\n                    log(errorMessage, \"error\");\n                    callback({\n                        error: errorMessage\n                    });\n                }, 1000);\n            }\n        });\n\n        // Listen for messages coming from this iframe.  When a message has been received, cancel the timer\n        // that declares the upload a failure if a message is not received within a reasonable amount of time.\n        corsMessageReceiver.receiveMessage(iframeName, function(message) {\n            log(\"Received the following window message: '\" + message + \"'\");\n            var fileId = getFileIdForIframeName(iframeName),\n                response = handler._parseJsonResponse(message),\n                uuid = response.uuid,\n                onloadCallback;\n\n            if (uuid && onloadCallbacks[uuid]) {\n                log(\"Handling response for iframe name \" + iframeName);\n                clearTimeout(postMessageCallbackTimers[iframeName]);\n                delete postMessageCallbackTimers[iframeName];\n\n                handler._detachLoadEvent(iframeName);\n\n                onloadCallback = onloadCallbacks[uuid];\n\n                delete onloadCallbacks[uuid];\n                corsMessageReceiver.stopReceivingMessages(iframeName);\n                onloadCallback(response);\n            }\n            else if (!uuid) {\n                log(\"'\" + message + \"' does not contain a UUID - ignoring.\");\n            }\n        });\n    }\n\n    qq.extend(this, new qq.UploadHandler(spec));\n\n    qq.override(this, function(super_) {\n        return {\n            /**\n             * Adds File or Blob to the queue\n             **/\n            add: function(id, fileInput) {\n                super_.add(id, {input: fileInput});\n\n                fileInput.setAttribute(\"name\", inputName);\n\n                // remove file input from DOM\n                if (fileInput.parentNode) {\n                    qq(fileInput).remove();\n                }\n            },\n\n            expunge: function(id) {\n                expungeFile(id);\n                super_.expunge(id);\n            },\n\n            isValid: function(id) {\n                return super_.isValid(id) &&\n                    handler._getFileState(id).input !== undefined;\n            }\n        };\n    });\n\n    qq.extend(this, {\n        getInput: function(id) {\n            return handler._getFileState(id).input;\n        },\n\n        /**\n         * This function either delegates to a more specific message handler if CORS is involved,\n         * or simply registers a callback when the iframe has been loaded that invokes the passed callback\n         * after determining if the content of the iframe is accessible.\n         *\n         * @param iframe Associated iframe\n         * @param callback Callback to invoke after we have determined if the iframe content is accessible.\n         */\n        _attachLoadEvent: function(iframe, callback) {\n            /*jslint eqeq: true*/\n            var responseDescriptor;\n\n            if (isCors) {\n                registerPostMessageCallback(iframe, callback);\n            }\n            else {\n                detachLoadEvents[iframe.id] = qq(iframe).attach(\"load\", function() {\n                    log(\"Received response for \" + iframe.id);\n\n                    // when we remove iframe from dom\n                    // the request stops, but in IE load\n                    // event fires\n                    if (!iframe.parentNode) {\n                        return;\n                    }\n\n                    try {\n                        // fixing Opera 10.53\n                        if (iframe.contentDocument &&\n                            iframe.contentDocument.body &&\n                            iframe.contentDocument.body.innerHTML == \"false\") {\n                            // In Opera event is fired second time\n                            // when body.innerHTML changed from false\n                            // to server response approx. after 1 sec\n                            // when we upload file with iframe\n                            return;\n                        }\n                    }\n                    catch (error) {\n                        //IE may throw an \"access is denied\" error when attempting to access contentDocument on the iframe in some cases\n                        log(\"Error when attempting to access iframe during handling of upload response (\" + error.message + \")\", \"error\");\n                        responseDescriptor = {success: false};\n                    }\n\n                    callback(responseDescriptor);\n                });\n            }\n        },\n\n        /**\n         * Creates an iframe with a specific document-unique name.\n         *\n         * @param id ID of the associated file\n         * @returns {HTMLIFrameElement}\n         */\n        _createIframe: function(id) {\n            var iframeName = handler._getIframeName(id);\n\n            return initIframeForUpload(iframeName);\n        },\n\n        /**\n         * Called when we are no longer interested in being notified when an iframe has loaded.\n         *\n         * @param id Associated file ID\n         */\n        _detachLoadEvent: function(id) {\n            if (detachLoadEvents[id] !== undefined) {\n                detachLoadEvents[id]();\n                delete detachLoadEvents[id];\n            }\n        },\n\n        /**\n         * @param fileId ID of the associated file\n         * @returns {string} The `document`-unique name of the iframe\n         */\n        _getIframeName: function(fileId) {\n            return fileId + \"_\" + formHandlerInstanceId;\n        },\n\n        /**\n         * Generates a form element and appends it to the `document`.  When the form is submitted, a specific iframe is targeted.\n         * The name of the iframe is passed in as a property of the spec parameter, and must be unique in the `document`.  Note\n         * that the form is hidden from view.\n         *\n         * @param spec An object containing various properties to be used when constructing the form.  Required properties are\n         * currently: `method`, `endpoint`, `params`, `paramsInBody`, and `targetName`.\n         * @returns {HTMLFormElement} The created form\n         */\n        _initFormForUpload: function(spec) {\n            var method = spec.method,\n                endpoint = spec.endpoint,\n                params = spec.params,\n                paramsInBody = spec.paramsInBody,\n                targetName = spec.targetName,\n                form = qq.toElement(\"<form method='\" + method + \"' enctype='multipart/form-data'></form>\"),\n                url = endpoint;\n\n            if (paramsInBody) {\n                qq.obj2Inputs(params, form);\n            }\n            else {\n                url = qq.obj2url(params, endpoint);\n            }\n\n            form.setAttribute(\"action\", url);\n            form.setAttribute(\"target\", targetName);\n            form.style.display = \"none\";\n            document.body.appendChild(form);\n\n            return form;\n        },\n\n        /**\n         * @param innerHtmlOrMessage JSON message\n         * @returns {*} The parsed response, or an empty object if the response could not be parsed\n         */\n        _parseJsonResponse: function(innerHtmlOrMessage) {\n            var response = {};\n\n            try {\n                response = qq.parseJson(innerHtmlOrMessage);\n            }\n            catch (error) {\n                log(\"Error when attempting to parse iframe upload response (\" + error.message + \")\", \"error\");\n            }\n\n            return response;\n        }\n    });\n};\n"
  },
  {
    "path": "client/js/upload-handler/upload.handler.controller.js",
    "content": "/*globals qq*/\n/**\n * Base upload handler module.  Controls more specific handlers.\n *\n * @param o Options.  Passed along to the specific handler submodule as well.\n * @param namespace [optional] Namespace for the specific handler.\n */\nqq.UploadHandlerController = function(o, namespace) {\n    \"use strict\";\n\n    var controller = this,\n        chunkingPossible = false,\n        concurrentChunkingPossible = false,\n        chunking, preventRetryResponse, log, handler,\n\n    options = {\n        paramsStore: {},\n        maxConnections: 3, // maximum number of concurrent uploads\n        chunking: {\n            enabled: false,\n            multiple: {\n                enabled: false\n            }\n        },\n        log: function(str, level) {},\n        onProgress: function(id, fileName, loaded, total) {},\n        onComplete: function(id, fileName, response, xhr) {},\n        onCancel: function(id, fileName) {},\n        onUploadPrep: function(id) {}, // Called if non-trivial operations will be performed before onUpload\n        onUpload: function(id, fileName) {},\n        onUploadChunk: function(id, fileName, chunkData) {},\n        onUploadChunkSuccess: function(id, chunkData, response, xhr) {},\n        onAutoRetry: function(id, fileName, response, xhr) {},\n        onResume: function(id, fileName, chunkData, customResumeData) {},\n        onUuidChanged: function(id, newUuid) {},\n        getName: function(id) {},\n        setSize: function(id, newSize) {},\n        isQueued: function(id) {},\n        getIdsInProxyGroup: function(id) {},\n        getIdsInBatch: function(id) {},\n        isInProgress: function(id) {}\n    },\n\n    chunked = {\n        // Called when each chunk has uploaded successfully\n        done: function(id, chunkIdx, response, xhr) {\n            var chunkData = handler._getChunkData(id, chunkIdx);\n\n            handler._getFileState(id).attemptingResume = false;\n\n            delete handler._getFileState(id).temp.chunkProgress[chunkIdx];\n            handler._getFileState(id).loaded += chunkData.size;\n\n            options.onUploadChunkSuccess(id, handler._getChunkDataForCallback(chunkData), response, xhr);\n        },\n\n        // Called when all chunks have been successfully uploaded and we want to ask the handler to perform any\n        // logic associated with closing out the file, such as combining the chunks.\n        finalize: function(id) {\n            var size = options.getSize(id),\n                name = options.getName(id);\n\n            log(\"All chunks have been uploaded for \" + id + \" - finalizing....\");\n            handler.finalizeChunks(id).then(\n                function(response, xhr) {\n                    log(\"Finalize successful for \" + id);\n\n                    var normaizedResponse = upload.normalizeResponse(response, true);\n\n                    options.onProgress(id, name, size, size);\n                    handler._maybeDeletePersistedChunkData(id);\n                    upload.cleanup(id, normaizedResponse, xhr);\n                },\n                function(response, xhr) {\n                    var normalizedResponse = upload.normalizeResponse(response, false);\n\n                    log(\"Problem finalizing chunks for file ID \" + id + \" - \" + normalizedResponse.error, \"error\");\n\n                    if (\n                        normalizedResponse.reset ||\n                        (xhr && options.chunking.success.resetOnStatus.indexOf(xhr.status) >= 0)\n                    ) {\n                        chunked.reset(id);\n                    }\n\n                    if (!options.onAutoRetry(id, name, normalizedResponse, xhr)) {\n                        upload.cleanup(id, normalizedResponse, xhr);\n                    }\n                }\n            );\n        },\n\n        handleFailure: function(chunkIdx, id, response, xhr) {\n            var name = options.getName(id);\n\n            log(\"Chunked upload request failed for \" + id + \", chunk \" + chunkIdx);\n\n            handler.clearCachedChunk(id, chunkIdx);\n\n            var responseToReport = upload.normalizeResponse(response, false),\n                inProgressIdx;\n\n            if (responseToReport.reset) {\n                chunked.reset(id);\n            }\n            else {\n                var inProgressChunksArray = handler._getFileState(id).chunking.inProgress;\n\n                inProgressIdx = inProgressChunksArray ? qq.indexOf(inProgressChunksArray, chunkIdx) : -1;\n                if (inProgressIdx >= 0) {\n                    handler._getFileState(id).chunking.inProgress.splice(inProgressIdx, 1);\n                    handler._getFileState(id).chunking.remaining.unshift(chunkIdx);\n                }\n            }\n\n            // We may have aborted all other in-progress chunks for this file due to a failure.\n            // If so, ignore the failures associated with those aborts.\n            if (!handler._getFileState(id).temp.ignoreFailure) {\n                // If this chunk has failed, we want to ignore all other failures of currently in-progress\n                // chunks since they will be explicitly aborted\n                if (concurrentChunkingPossible) {\n                    handler._getFileState(id).temp.ignoreFailure = true;\n\n                    log(qq.format(\"Going to attempt to abort these chunks: {}. These are currently in-progress: {}.\", JSON.stringify(Object.keys(handler._getXhrs(id))), JSON.stringify(handler._getFileState(id).chunking.inProgress)));\n                    qq.each(handler._getXhrs(id), function(ckid, ckXhr) {\n                        log(qq.format(\"Attempting to abort file {}.{}. XHR readyState {}. \", id, ckid, ckXhr.readyState));\n                        ckXhr.abort();\n                        // Flag the transport, in case we are waiting for some other async operation\n                        // to complete before attempting to upload the chunk\n                        ckXhr._cancelled = true;\n                    });\n\n                    // We must indicate that all aborted chunks are no longer in progress\n                    handler.moveInProgressToRemaining(id);\n\n                    // Free up any connections used by these chunks, but don't allow any\n                    // other files to take up the connections (until we have exhausted all auto-retries)\n                    connectionManager.free(id, true);\n                }\n\n                if (!options.onAutoRetry(id, name, responseToReport, xhr)) {\n                    // If one chunk fails, abort all of the others to avoid odd race conditions that occur\n                    // if a chunk succeeds immediately after one fails before we have determined if the upload\n                    // is a failure or not.\n                    upload.cleanup(id, responseToReport, xhr);\n                }\n            }\n        },\n\n        hasMoreParts: function(id) {\n            return !!handler._getFileState(id).chunking.remaining.length;\n        },\n\n        nextPart: function(id) {\n            var nextIdx = handler._getFileState(id).chunking.remaining.shift();\n\n            if (nextIdx >= handler._getTotalChunks(id)) {\n                nextIdx = null;\n            }\n\n            return nextIdx;\n        },\n\n        reset: function(id) {\n            log(\"Server or callback has ordered chunking effort to be restarted on next attempt for item ID \" + id, \"error\");\n\n            handler._maybeDeletePersistedChunkData(id);\n            handler.reevaluateChunking(id);\n            handler._getFileState(id).loaded = 0;\n            handler._getFileState(id).attemptingResume = false;\n        },\n\n        sendNext: function(id) {\n            var size = options.getSize(id),\n                name = options.getName(id),\n                chunkIdx = chunked.nextPart(id),\n                chunkData = handler._getChunkData(id, chunkIdx),\n                fileState = handler._getFileState(id),\n                resuming = fileState.attemptingResume,\n                inProgressChunks = fileState.chunking.inProgress || [];\n\n            if (fileState.loaded == null) {\n                fileState.loaded = 0;\n            }\n\n            // Don't follow-through with the resume attempt if the integrator returns false from onResume\n            if (resuming && options.onResume(id, name, chunkData, fileState.customResumeData) === false) {\n                chunked.reset(id);\n                chunkIdx = chunked.nextPart(id);\n                chunkData = handler._getChunkData(id, chunkIdx);\n                resuming = false;\n            }\n\n            // If all chunks have already uploaded successfully, we must be re-attempting the finalize step.\n            if (chunkIdx == null && inProgressChunks.length === 0) {\n                chunked.finalize(id);\n            }\n\n            // Send the next chunk\n            else {\n                inProgressChunks.push(chunkIdx);\n                handler._getFileState(id).chunking.inProgress = inProgressChunks;\n\n                if (concurrentChunkingPossible) {\n                    connectionManager.open(id, chunkIdx);\n                }\n\n                if (concurrentChunkingPossible && connectionManager.available() && handler._getFileState(id).chunking.remaining.length) {\n                    chunked.sendNext(id);\n                }\n\n                if (chunkData.blob.size === 0) {\n                    log(qq.format(\"Chunk {} for file {} will not be uploaded, zero sized chunk.\", chunkIdx, id), \"error\");\n                    chunked.handleFailure(chunkIdx, id, \"File is no longer available\", null);\n                }\n\n                var onUploadChunkPromise = options.onUploadChunk(id, name, handler._getChunkDataForCallback(chunkData));\n\n                onUploadChunkPromise.then(\n                    function(requestOverrides) {\n                        if (!options.isInProgress(id)) {\n                            log(qq.format(\"Not sending chunked upload request for item {}.{} - no longer in progress.\", id, chunkIdx));\n                        }\n                        else {\n                            log(qq.format(\"Sending chunked upload request for item {}.{}, bytes {}-{} of {}.\", id, chunkIdx, chunkData.start + 1, chunkData.end, size));\n\n                            var uploadChunkData = {\n                                chunkIdx: chunkIdx,\n                                id: id,\n                                overrides: requestOverrides,\n                                resuming: resuming\n                            };\n\n                            handler.uploadChunk(uploadChunkData).then(\n                                // upload chunk success\n                                function success(response, xhr) {\n                                    log(\"Chunked upload request succeeded for \" + id + \", chunk \" + chunkIdx);\n\n                                    handler.clearCachedChunk(id, chunkIdx);\n\n                                    var inProgressChunks = handler._getFileState(id).chunking.inProgress || [],\n                                        responseToReport = upload.normalizeResponse(response, true),\n                                        inProgressChunkIdx = qq.indexOf(inProgressChunks, chunkIdx);\n\n                                    log(qq.format(\"Chunk {} for file {} uploaded successfully.\", chunkIdx, id));\n\n                                    chunked.done(id, chunkIdx, responseToReport, xhr);\n\n                                    if (inProgressChunkIdx >= 0) {\n                                        inProgressChunks.splice(inProgressChunkIdx, 1);\n                                    }\n\n                                    handler._maybePersistChunkedState(id);\n\n                                    if (!chunked.hasMoreParts(id) && inProgressChunks.length === 0) {\n                                        chunked.finalize(id);\n                                    }\n                                    else if (chunked.hasMoreParts(id)) {\n                                        chunked.sendNext(id);\n                                    }\n                                    else {\n                                        log(qq.format(\"File ID {} has no more chunks to send and these chunk indexes are still marked as in-progress: {}\", id, JSON.stringify(inProgressChunks)));\n                                    }\n                                },\n\n                                // upload chunk failure\n                                function failure(response, xhr) {\n                                    chunked.handleFailure(chunkIdx, id, response, xhr);\n                                }\n                            )\n                                .done(function () {\n                                    handler.clearXhr(id, chunkIdx);\n                                });\n                        }\n                    },\n\n                    function(error) {\n                        chunked.handleFailure(chunkIdx, id, error, null);\n                    }\n                );\n            }\n        }\n    },\n\n    connectionManager = {\n        _open: [],\n        _openChunks: {},\n        _waiting: [],\n\n        available: function() {\n            var max = options.maxConnections,\n                openChunkEntriesCount = 0,\n                openChunksCount = 0;\n\n            qq.each(connectionManager._openChunks, function(fileId, openChunkIndexes) {\n                openChunkEntriesCount++;\n                openChunksCount += openChunkIndexes.length;\n            });\n\n            return max - (connectionManager._open.length - openChunkEntriesCount + openChunksCount);\n        },\n\n        /**\n         * Removes element from queue, starts upload of next\n         */\n        free: function(id, dontAllowNext) {\n            var allowNext = !dontAllowNext,\n                waitingIndex = qq.indexOf(connectionManager._waiting, id),\n                connectionsIndex = qq.indexOf(connectionManager._open, id),\n                nextId;\n\n            delete connectionManager._openChunks[id];\n\n            if (upload.getProxyOrBlob(id) instanceof qq.BlobProxy) {\n                log(\"Generated blob upload has ended for \" + id + \", disposing generated blob.\");\n                delete handler._getFileState(id).file;\n            }\n\n            // If this file was not consuming a connection, it was just waiting, so remove it from the waiting array\n            if (waitingIndex >= 0) {\n                connectionManager._waiting.splice(waitingIndex, 1);\n            }\n            // If this file was consuming a connection, allow the next file to be uploaded\n            else if (allowNext && connectionsIndex >= 0) {\n                connectionManager._open.splice(connectionsIndex, 1);\n\n                nextId = connectionManager._waiting.shift();\n                if (nextId >= 0) {\n                    connectionManager._open.push(nextId);\n                    upload.start(nextId);\n                }\n            }\n        },\n\n        getWaitingOrConnected: function() {\n            var waitingOrConnected = [];\n\n            // Chunked files may have multiple connections open per chunk (if concurrent chunking is enabled)\n            // We need to grab the file ID of any file that has at least one chunk consuming a connection.\n            qq.each(connectionManager._openChunks, function(fileId, chunks) {\n                if (chunks && chunks.length) {\n                    waitingOrConnected.push(parseInt(fileId));\n                }\n            });\n\n            // For non-chunked files, only one connection will be consumed per file.\n            // This is where we aggregate those file IDs.\n            qq.each(connectionManager._open, function(idx, fileId) {\n                if (!connectionManager._openChunks[fileId]) {\n                    waitingOrConnected.push(parseInt(fileId));\n                }\n            });\n\n            // There may be files waiting for a connection.\n            waitingOrConnected = waitingOrConnected.concat(connectionManager._waiting);\n\n            return waitingOrConnected;\n        },\n\n        isUsingConnection: function(id) {\n            return qq.indexOf(connectionManager._open, id) >= 0;\n        },\n\n        open: function(id, chunkIdx) {\n            if (chunkIdx == null) {\n                connectionManager._waiting.push(id);\n            }\n\n            if (connectionManager.available()) {\n                if (chunkIdx == null) {\n                    connectionManager._waiting.pop();\n                    connectionManager._open.push(id);\n                }\n                else {\n                    (function() {\n                        var openChunksEntry = connectionManager._openChunks[id] || [];\n                        openChunksEntry.push(chunkIdx);\n                        connectionManager._openChunks[id] = openChunksEntry;\n                    }());\n                }\n\n                return true;\n            }\n\n            return false;\n        },\n\n        reset: function() {\n            connectionManager._waiting = [];\n            connectionManager._open = [];\n        }\n    },\n\n    simple = {\n        send: function(id, name) {\n            var fileState = handler._getFileState(id);\n\n            if (!fileState) {\n                log(\"Ignoring send request as this upload may have been cancelled, File ID \" + id, \"warn\");\n                return;\n            }\n\n            fileState.loaded = 0;\n\n            log(\"Sending simple upload request for \" + id);\n            handler.uploadFile(id).then(\n                function(response, optXhr) {\n                    log(\"Simple upload request succeeded for \" + id);\n\n                    var responseToReport = upload.normalizeResponse(response, true),\n                        size = options.getSize(id);\n\n                    options.onProgress(id, name, size, size);\n                    upload.maybeNewUuid(id, responseToReport);\n                    upload.cleanup(id, responseToReport, optXhr);\n                },\n\n                function(response, optXhr) {\n                    log(\"Simple upload request failed for \" + id);\n\n                    var responseToReport = upload.normalizeResponse(response, false);\n\n                    if (!options.onAutoRetry(id, name, responseToReport, optXhr)) {\n                        upload.cleanup(id, responseToReport, optXhr);\n                    }\n                }\n            );\n        }\n    },\n\n    upload = {\n        cancel: function(id) {\n            log(\"Cancelling \" + id);\n            options.paramsStore.remove(id);\n            connectionManager.free(id);\n        },\n\n        cleanup: function(id, response, optXhr) {\n            var name = options.getName(id);\n\n            options.onComplete(id, name, response, optXhr);\n\n            if (handler._getFileState(id)) {\n                handler._clearXhrs && handler._clearXhrs(id);\n            }\n\n            connectionManager.free(id);\n        },\n\n        // Returns a qq.BlobProxy, or an actual File/Blob if no proxy is involved, or undefined\n        // if none of these are available for the ID\n        getProxyOrBlob: function(id) {\n            return (handler.getProxy && handler.getProxy(id)) ||\n                (handler.getFile && handler.getFile(id));\n        },\n\n        initHandler: function() {\n            var handlerType = namespace ? qq[namespace] : qq.traditional,\n                handlerModuleSubtype = qq.supportedFeatures.ajaxUploading ? \"Xhr\" : \"Form\";\n\n            handler = new handlerType[handlerModuleSubtype + \"UploadHandler\"](\n                options,\n                {\n                    getCustomResumeData: options.getCustomResumeData,\n                    getDataByUuid: options.getDataByUuid,\n                    getName: options.getName,\n                    getSize: options.getSize,\n                    getUuid: options.getUuid,\n                    log: log,\n                    onCancel: options.onCancel,\n                    onProgress: options.onProgress,\n                    onUuidChanged: options.onUuidChanged,\n                    onFinalizing: function(id) {\n                        options.setStatus(id, qq.status.UPLOAD_FINALIZING);\n                    }\n                }\n            );\n\n            if (handler._removeExpiredChunkingRecords) {\n                handler._removeExpiredChunkingRecords();\n            }\n        },\n\n        isDeferredEligibleForUpload: function(id) {\n            return options.isQueued(id);\n        },\n\n        // For Blobs that are part of a group of generated images, along with a reference image,\n        // this will ensure the blobs in the group are uploaded in the order they were triggered,\n        // even if some async processing must be completed on one or more Blobs first.\n        maybeDefer: function(id, blob) {\n            // If we don't have a file/blob yet & no file/blob exists for this item, request it,\n            // and then submit the upload to the specific handler once the blob is available.\n            // ASSUMPTION: This condition will only ever be true if XHR uploading is supported.\n            if (blob && !handler.getFile(id) && blob instanceof qq.BlobProxy) {\n\n                // Blob creation may take some time, so the caller may want to update the\n                // UI to indicate that an operation is in progress, even before the actual\n                // upload begins and an onUpload callback is invoked.\n                options.onUploadPrep(id);\n\n                log(\"Attempting to generate a blob on-demand for \" + id);\n                blob.create().then(function(generatedBlob) {\n                    log(\"Generated an on-demand blob for \" + id);\n\n                    // Update record associated with this file by providing the generated Blob\n                    handler.updateBlob(id, generatedBlob);\n\n                    // Propagate the size for this generated Blob\n                    options.setSize(id, generatedBlob.size);\n\n                    // Order handler to recalculate chunking possibility, if applicable\n                    handler.reevaluateChunking(id);\n\n                    upload.maybeSendDeferredFiles(id);\n                },\n\n                // Blob could not be generated.  Fail the upload & attempt to prevent retries.  Also bubble error message.\n                function(errorMessage) {\n                    var errorResponse = {};\n\n                    if (errorMessage) {\n                        errorResponse.error = errorMessage;\n                    }\n\n                    log(qq.format(\"Failed to generate blob for ID {}.  Error message: {}.\", id, errorMessage), \"error\");\n\n                    options.onComplete(id, options.getName(id), qq.extend(errorResponse, preventRetryResponse), null);\n                    upload.maybeSendDeferredFiles(id);\n                    connectionManager.free(id);\n                });\n            }\n            else {\n                return upload.maybeSendDeferredFiles(id);\n            }\n\n            return false;\n        },\n\n        // Upload any grouped blobs, in the proper order, that are ready to be uploaded\n        maybeSendDeferredFiles: function(id) {\n            var idsInGroup = options.getIdsInProxyGroup(id),\n                uploadedThisId = false;\n\n            if (idsInGroup && idsInGroup.length) {\n                log(\"Maybe ready to upload proxy group file \" + id);\n\n                qq.each(idsInGroup, function(idx, idInGroup) {\n                    if (upload.isDeferredEligibleForUpload(idInGroup) && !!handler.getFile(idInGroup)) {\n                        uploadedThisId = idInGroup === id;\n                        upload.now(idInGroup);\n                    }\n                    else if (upload.isDeferredEligibleForUpload(idInGroup)) {\n                        return false;\n                    }\n                });\n            }\n            else {\n                uploadedThisId = true;\n                upload.now(id);\n            }\n\n            return uploadedThisId;\n        },\n\n        maybeNewUuid: function(id, response) {\n            if (response.newUuid !== undefined) {\n                options.onUuidChanged(id, response.newUuid);\n            }\n        },\n\n        // The response coming from handler implementations may be in various formats.\n        // Instead of hoping a promise nested 5 levels deep will always return an object\n        // as its first param, let's just normalize the response here.\n        normalizeResponse: function(originalResponse, successful) {\n            var response = originalResponse;\n\n            // The passed \"response\" param may not be a response at all.\n            // It could be a string, detailing the error, for example.\n            if (!qq.isObject(originalResponse)) {\n                response = {};\n\n                if (qq.isString(originalResponse) && !successful) {\n                    response.error = originalResponse;\n                }\n            }\n\n            response.success = successful;\n\n            return response;\n        },\n\n        now: function(id) {\n            var name = options.getName(id);\n\n            if (!controller.isValid(id)) {\n                throw new qq.Error(id + \" is not a valid file ID to upload!\");\n            }\n\n            options.onUpload(id, name).then(\n                function(response) {\n                    if (response && response.pause) {\n                        options.setStatus(id, qq.status.PAUSED);\n                        handler.pause(id);\n                        connectionManager.free(id);\n                    }\n                    else {\n                        if (chunkingPossible && handler._shouldChunkThisFile(id)) {\n                            chunked.sendNext(id);\n                        }\n                        else {\n                            simple.send(id, name);\n                        }\n                    }\n                },\n\n                function(error) {\n                    error = error || {};\n\n                    log(id + \" upload start aborted due to rejected onUpload Promise - details: \" + error, \"error\");\n\n                    if (!options.onAutoRetry(id, name, error.responseJSON || {})) {\n                        var response = upload.normalizeResponse(error.responseJSON, false);\n                        upload.cleanup(id, response);\n                    }\n                }\n            );\n        },\n\n        start: function(id) {\n            var blobToUpload = upload.getProxyOrBlob(id);\n\n            if (blobToUpload) {\n                return upload.maybeDefer(id, blobToUpload);\n            }\n            else {\n                upload.now(id);\n                return true;\n            }\n        }\n    };\n\n    qq.extend(this, {\n        /**\n         * Adds file or file input to the queue\n         **/\n        add: function(id, file) {\n            handler.add.apply(this, arguments);\n        },\n\n        /**\n         * Sends the file identified by id\n         */\n        upload: function(id) {\n            if (connectionManager.open(id)) {\n                return upload.start(id);\n            }\n            return false;\n        },\n\n        retry: function(id) {\n            // On retry, if concurrent chunking has been enabled, we may have aborted all other in-progress chunks\n            // for a file when encountering a failed chunk upload.  We then signaled the controller to ignore\n            // all failures associated with these aborts.  We are now retrying, so we don't want to ignore\n            // any more failures at this point.\n            if (concurrentChunkingPossible) {\n                handler._getFileState(id).temp.ignoreFailure = false;\n            }\n\n            // If we are attempting to retry a file that is already consuming a connection, this is likely an auto-retry.\n            // Just go ahead and ask the handler to upload again.\n            if (connectionManager.isUsingConnection(id)) {\n                return upload.start(id);\n            }\n\n            // If we are attempting to retry a file that is not currently consuming a connection,\n            // this is likely a manual retry attempt.  We will need to ensure a connection is available\n            // before the retry commences.\n            else {\n                return controller.upload(id);\n            }\n        },\n\n        /**\n         * Cancels file upload by id\n         */\n        cancel: function(id) {\n            var cancelRetVal = handler.cancel(id);\n\n            if (qq.isGenericPromise(cancelRetVal)) {\n                cancelRetVal.then(function() {\n                    upload.cancel(id);\n                });\n            }\n            else if (cancelRetVal !== false) {\n                upload.cancel(id);\n            }\n        },\n\n        /**\n         * Cancels all queued or in-progress uploads\n         */\n        cancelAll: function() {\n            var waitingOrConnected = connectionManager.getWaitingOrConnected(),\n                i;\n\n            // ensure files are cancelled in reverse order which they were added\n            // to avoid a flash of time where a queued file begins to upload before it is canceled\n            if (waitingOrConnected.length) {\n                for (i = waitingOrConnected.length - 1; i >= 0; i--) {\n                    controller.cancel(waitingOrConnected[i]);\n                }\n            }\n\n            connectionManager.reset();\n        },\n\n        // Returns a File, Blob, or the Blob/File for the reference/parent file if the targeted blob is a proxy.\n        // Undefined if no file record is available.\n        getFile: function(id) {\n            if (handler.getProxy && handler.getProxy(id)) {\n                return handler.getProxy(id).referenceBlob;\n            }\n\n            return handler.getFile && handler.getFile(id);\n        },\n\n        // Returns true if the Blob associated with the ID is related to a proxy s\n        isProxied: function(id) {\n            return !!(handler.getProxy && handler.getProxy(id));\n        },\n\n        getInput: function(id) {\n            if (handler.getInput) {\n                return handler.getInput(id);\n            }\n        },\n\n        reset: function() {\n            log(\"Resetting upload handler\");\n            controller.cancelAll();\n            connectionManager.reset();\n            handler.reset();\n        },\n\n        expunge: function(id) {\n            if (controller.isValid(id)) {\n                return handler.expunge(id);\n            }\n        },\n\n        /**\n         * Determine if the file exists.\n         */\n        isValid: function(id) {\n            return handler.isValid(id);\n        },\n\n        hasResumeRecord: function(id) {\n            var key = handler.isValid(id) &&\n                handler._getLocalStorageId &&\n                handler._getLocalStorageId(id);\n\n            if (key) {\n                return !!localStorage.getItem(key);\n            }\n\n            return false;\n        },\n\n        getResumableFilesData: function() {\n            if (handler.getResumableFilesData) {\n                return handler.getResumableFilesData();\n            }\n            return [];\n        },\n\n        /**\n         * This may or may not be implemented, depending on the handler.  For handlers where a third-party ID is\n         * available (such as the \"key\" for Amazon S3), this will return that value.  Otherwise, the return value\n         * will be undefined.\n         *\n         * @param id Internal file ID\n         * @returns {*} Some identifier used by a 3rd-party service involved in the upload process\n         */\n        getThirdPartyFileId: function(id) {\n            if (controller.isValid(id)) {\n                return handler.getThirdPartyFileId(id);\n            }\n        },\n\n        /**\n         * Attempts to pause the associated upload if the specific handler supports this and the file is \"valid\".\n         * @param id ID of the upload/file to pause\n         * @returns {boolean} true if the upload was paused\n         */\n        pause: function(id) {\n            if (controller.isResumable(id) && handler.pause && controller.isValid(id) && handler.pause(id)) {\n                connectionManager.free(id);\n                handler.moveInProgressToRemaining(id);\n                return true;\n            }\n            return false;\n        },\n\n        isAttemptingResume: function(id) {\n            return !!handler.isAttemptingResume && handler.isAttemptingResume(id);\n        },\n\n        // True if the file is eligible for pause/resume.\n        isResumable: function(id) {\n            return !!handler.isResumable && handler.isResumable(id);\n        }\n    });\n\n    qq.extend(options, o);\n    log = options.log;\n    chunkingPossible = options.chunking.enabled && qq.supportedFeatures.chunking;\n    concurrentChunkingPossible = chunkingPossible && options.chunking.concurrent.enabled;\n\n    preventRetryResponse = (function() {\n        var response = {};\n\n        response[options.preventRetryParam] = true;\n\n        return response;\n    }());\n\n    upload.initHandler();\n};\n"
  },
  {
    "path": "client/js/upload-handler/upload.handler.js",
    "content": "/* globals qq */\n/**\n * Common upload handler functions.\n *\n * @constructor\n */\nqq.UploadHandler = function(spec) {\n    \"use strict\";\n\n    var proxy = spec.proxy,\n        fileState = {},\n        onCancel = proxy.onCancel,\n        getName = proxy.getName;\n\n    qq.extend(this, {\n        add: function(id, fileItem) {\n            fileState[id] = fileItem;\n            fileState[id].temp = {};\n        },\n\n        cancel: function(id) {\n            var self = this,\n                cancelFinalizationEffort = new qq.Promise(),\n                onCancelRetVal = onCancel(id, getName(id), cancelFinalizationEffort);\n\n            onCancelRetVal.then(function() {\n                if (self.isValid(id)) {\n                    fileState[id].canceled = true;\n                    self.expunge(id);\n                }\n                cancelFinalizationEffort.success();\n            });\n        },\n\n        expunge: function(id) {\n            delete fileState[id];\n        },\n\n        getThirdPartyFileId: function(id) {\n            return fileState[id].key;\n        },\n\n        isValid: function(id) {\n            return fileState[id] !== undefined;\n        },\n\n        reset: function() {\n            fileState = {};\n        },\n\n        _getFileState: function(id) {\n            return fileState[id];\n        },\n\n        _setThirdPartyFileId: function(id, thirdPartyFileId) {\n            fileState[id].key = thirdPartyFileId;\n        },\n\n        _wasCanceled: function(id) {\n            return !!fileState[id].canceled;\n        }\n    });\n};\n"
  },
  {
    "path": "client/js/upload-handler/xhr.upload.handler.js",
    "content": "/* globals qq */\n/**\n * Common API exposed to creators of XHR handlers.  This is reused and possibly overriding in some cases by specific\n * XHR upload handlers.\n *\n * @constructor\n */\nqq.XhrUploadHandler = function(spec) {\n    \"use strict\";\n\n    var handler = this,\n        namespace = spec.options.namespace,\n        proxy = spec.proxy,\n        chunking = spec.options.chunking,\n        getChunkSize = function(id) {\n            var fileState = handler._getFileState(id);\n\n            if (fileState.chunkSize) {\n                return fileState.chunkSize;\n            }\n\n            else {\n                var chunkSize = chunking.partSize;\n\n                if (qq.isFunction(chunkSize)) {\n                    chunkSize = chunkSize(id, getSize(id));\n                }\n\n                fileState.chunkSize = chunkSize;\n                return chunkSize;\n            }\n        },\n        resume = spec.options.resume,\n        chunkFiles = chunking && spec.options.chunking.enabled && qq.supportedFeatures.chunking,\n        resumeEnabled = resume && spec.options.resume.enabled && chunkFiles && qq.supportedFeatures.resume,\n        getName = proxy.getName,\n        getSize = proxy.getSize,\n        getUuid = proxy.getUuid,\n        getEndpoint = proxy.getEndpoint,\n        getDataByUuid = proxy.getDataByUuid,\n        onUuidChanged = proxy.onUuidChanged,\n        onProgress = proxy.onProgress,\n        log = proxy.log,\n        getCustomResumeData = proxy.getCustomResumeData;\n\n    function abort(id) {\n        qq.each(handler._getXhrs(id), function(xhrId, xhr) {\n            var ajaxRequester = handler._getAjaxRequester(id, xhrId);\n\n            xhr.onreadystatechange = null;\n            xhr.upload.onprogress = null;\n            xhr.abort();\n            ajaxRequester && ajaxRequester.canceled && ajaxRequester.canceled(id);\n        });\n    }\n\n    qq.extend(this, new qq.UploadHandler(spec));\n\n    qq.override(this, function(super_) {\n        return {\n            /**\n             * Adds File or Blob to the queue\n             **/\n            add: function(id, blobOrProxy) {\n                if (qq.isFile(blobOrProxy) || qq.isBlob(blobOrProxy)) {\n                    super_.add(id, {file: blobOrProxy});\n                }\n                else if (blobOrProxy instanceof qq.BlobProxy) {\n                    super_.add(id, {proxy: blobOrProxy});\n                }\n                else {\n                    throw new Error(\"Passed obj is not a File, Blob, or proxy\");\n                }\n\n                handler._initTempState(id);\n                resumeEnabled && handler._maybePrepareForResume(id);\n            },\n\n            expunge: function(id) {\n                abort(id);\n                handler._maybeDeletePersistedChunkData(id);\n                handler._clearXhrs(id);\n                super_.expunge(id);\n            }\n        };\n    });\n\n    qq.extend(this, {\n        // Clear the cached chunk `Blob` after we are done with it, just in case the `Blob` bytes are stored in memory.\n        clearCachedChunk: function(id, chunkIdx) {\n            var fileState = handler._getFileState(id);\n\n            if (fileState) {\n                delete fileState.temp.cachedChunks[chunkIdx];\n            }\n        },\n\n        clearXhr: function(id, chunkIdx) {\n            var tempState = handler._getFileState(id).temp;\n\n            if (tempState.xhrs) {\n                delete tempState.xhrs[chunkIdx];\n            }\n            if (tempState.ajaxRequesters) {\n                delete tempState.ajaxRequesters[chunkIdx];\n            }\n        },\n\n        // Called when all chunks have been successfully uploaded.  Expected promissory return type.\n        // This defines the default behavior if nothing further is required when all chunks have been uploaded.\n        finalizeChunks: function(id, responseParser) {\n            var lastChunkIdx = handler._getTotalChunks(id) - 1,\n                xhr = handler._getXhr(id, lastChunkIdx);\n\n            if (responseParser) {\n                return new qq.Promise().success(responseParser(xhr), xhr);\n            }\n\n            return new qq.Promise().success({}, xhr);\n        },\n\n        getFile: function(id) {\n            return handler.isValid(id) && handler._getFileState(id).file;\n        },\n\n        getProxy: function(id) {\n            return handler.isValid(id) && handler._getFileState(id).proxy;\n        },\n\n        /**\n         * @returns {Array} Array of objects containing properties useful to integrators\n         * when it is important to determine which files are potentially resumable.\n         */\n        getResumableFilesData: function() {\n            var resumableFilesData = [];\n\n            handler._iterateResumeRecords(function(key, uploadData) {\n                handler.moveInProgressToRemaining(null, uploadData.chunking.inProgress,  uploadData.chunking.remaining);\n\n                var data = {\n                    name: uploadData.name,\n                    remaining: uploadData.chunking.remaining,\n                    size: uploadData.size,\n                    uuid: uploadData.uuid\n                };\n\n                if (uploadData.key) {\n                    data.key = uploadData.key;\n                }\n\n                if (uploadData.customResumeData) {\n                    data.customResumeData = uploadData.customResumeData;\n                }\n\n                resumableFilesData.push(data);\n            });\n\n            return resumableFilesData;\n        },\n\n        isAttemptingResume: function(id) {\n            return handler._getFileState(id).attemptingResume;\n        },\n\n        isResumable: function(id) {\n            return !!chunking && handler.isValid(id) &&\n                !handler._getFileState(id).notResumable;\n        },\n\n        moveInProgressToRemaining: function(id, optInProgress, optRemaining) {\n            var fileState = handler._getFileState(id) || {},\n                chunkingState =  fileState.chunking || {},\n                inProgress = optInProgress || chunkingState.inProgress,\n                remaining = optRemaining || chunkingState.remaining;\n\n            if (inProgress) {\n                log(qq.format(\"Moving these chunks from in-progress {}, to remaining.\", JSON.stringify(inProgress)));\n                inProgress.reverse();\n                qq.each(inProgress, function(idx, chunkIdx) {\n                    remaining.unshift(chunkIdx);\n                });\n                inProgress.length = 0;\n            }\n        },\n\n        pause: function(id) {\n            if (handler.isValid(id)) {\n                log(qq.format(\"Aborting XHR upload for {} '{}' due to pause instruction.\", id, getName(id)));\n                handler._getFileState(id).paused = true;\n                abort(id);\n                return true;\n            }\n        },\n\n        reevaluateChunking: function(id) {\n            if (chunking && handler.isValid(id)) {\n                var state = handler._getFileState(id),\n                    totalChunks,\n                    i;\n\n                delete state.chunking;\n\n                state.chunking = {};\n                totalChunks = handler._getTotalChunks(id);\n                if (totalChunks > 1 || chunking.mandatory) {\n                    state.chunking.enabled = true;\n                    state.chunking.parts = totalChunks;\n                    state.chunking.remaining = [];\n\n                    for (i = 0; i < totalChunks; i++) {\n                        state.chunking.remaining.push(i);\n                    }\n\n                    handler._initTempState(id);\n                }\n                else {\n                    state.chunking.enabled = false;\n                }\n            }\n        },\n\n        updateBlob: function(id, newBlob) {\n            if (handler.isValid(id)) {\n                handler._getFileState(id).file = newBlob;\n            }\n        },\n\n        _clearXhrs: function(id) {\n            var tempState = handler._getFileState(id).temp;\n\n            qq.each(tempState.ajaxRequesters, function(chunkId) {\n                delete tempState.ajaxRequesters[chunkId];\n            });\n\n            qq.each(tempState.xhrs, function(chunkId) {\n                delete tempState.xhrs[chunkId];\n            });\n        },\n\n        /**\n         * Creates an XHR instance for this file and stores it in the fileState.\n         *\n         * @param id File ID\n         * @param optChunkIdx The chunk index associated with this XHR, if applicable\n         * @returns {XMLHttpRequest}\n         */\n        _createXhr: function(id, optChunkIdx) {\n            return handler._registerXhr(id, optChunkIdx, qq.createXhrInstance());\n        },\n\n        _getAjaxRequester: function(id, optChunkIdx) {\n            var chunkIdx = optChunkIdx == null ? -1 : optChunkIdx;\n            return handler._getFileState(id).temp.ajaxRequesters[chunkIdx];\n        },\n\n        _getChunkData: function(id, chunkIndex) {\n            var chunkSize = getChunkSize(id),\n                fileSize = getSize(id),\n                fileOrBlob = handler.getFile(id),\n                startBytes = chunkSize * chunkIndex,\n                endBytes = startBytes + chunkSize >= fileSize ? fileSize : startBytes + chunkSize,\n                totalChunks = handler._getTotalChunks(id),\n                cachedChunks = this._getFileState(id).temp.cachedChunks,\n\n            // To work around a Webkit GC bug, we must keep each chunk `Blob` in scope until we are done with it.\n            // See https://github.com/FineUploader/fine-uploader/issues/937#issuecomment-41418760\n                blob = cachedChunks[chunkIndex] || qq.sliceBlob(fileOrBlob, startBytes, endBytes);\n\n            cachedChunks[chunkIndex] = blob;\n\n            return {\n                part: chunkIndex,\n                start: startBytes,\n                end: endBytes,\n                count: totalChunks,\n                blob: blob,\n                size: endBytes - startBytes\n            };\n        },\n\n        _getChunkDataForCallback: function(chunkData) {\n            return {\n                partIndex: chunkData.part,\n                startByte: chunkData.start + 1,\n                endByte: chunkData.end,\n                totalParts: chunkData.count\n            };\n        },\n\n        /**\n         * @param id File ID\n         * @returns {string} Identifier for this item that may appear in the browser's local storage\n         */\n        _getLocalStorageId: function(id) {\n            var formatVersion = \"5.0\",\n                name = getName(id),\n                size = getSize(id),\n                chunkSize = getChunkSize(id),\n                endpoint = getEndpoint(id),\n                customKeys = resume.customKeys(id),\n                localStorageId = qq.format(\"qq{}resume{}-{}-{}-{}-{}\", namespace, formatVersion, name, size, chunkSize, endpoint);\n\n            customKeys.forEach(function(key) {\n                localStorageId += \"-\" + key;\n            });\n\n            return localStorageId;\n        },\n\n        _getMimeType: function(id) {\n            return handler.getFile(id).type;\n        },\n\n        _getPersistableData: function(id) {\n            return handler._getFileState(id).chunking;\n        },\n\n        /**\n         * @param id ID of the associated file\n         * @returns {number} Number of parts this file can be divided into, or undefined if chunking is not supported in this UA\n         */\n        _getTotalChunks: function(id) {\n            if (chunking) {\n                var fileSize = getSize(id),\n                    chunkSize = getChunkSize(id);\n\n                return Math.ceil(fileSize / chunkSize);\n            }\n        },\n\n        _getXhr: function(id, optChunkIdx) {\n            var chunkIdx = optChunkIdx == null ? -1 : optChunkIdx;\n            return handler._getFileState(id).temp.xhrs[chunkIdx];\n        },\n\n        _getXhrs: function(id) {\n            return handler._getFileState(id).temp.xhrs;\n        },\n\n        // Iterates through all XHR handler-created resume records (in local storage),\n        // invoking the passed callback and passing in the key and value of each local storage record.\n        _iterateResumeRecords: function(callback) {\n            if (resumeEnabled) {\n                qq.each(localStorage, function(key, item) {\n                    if (key.indexOf(qq.format(\"qq{}resume\", namespace)) === 0) {\n                        var uploadData = JSON.parse(item);\n                        callback(key, uploadData);\n                    }\n                });\n            }\n        },\n\n        _initTempState: function(id) {\n            handler._getFileState(id).temp = {\n                ajaxRequesters: {},\n                chunkProgress: {},\n                xhrs: {},\n                cachedChunks: {}\n            };\n        },\n\n        _markNotResumable: function(id) {\n            handler._getFileState(id).notResumable = true;\n        },\n\n        // Removes a chunked upload record from local storage, if possible.\n        // Returns true if the item was removed, false otherwise.\n        _maybeDeletePersistedChunkData: function(id) {\n            var localStorageId;\n\n            if (resumeEnabled && handler.isResumable(id)) {\n                localStorageId = handler._getLocalStorageId(id);\n\n                if (localStorageId && localStorage.getItem(localStorageId)) {\n                    localStorage.removeItem(localStorageId);\n                    return true;\n                }\n            }\n\n            return false;\n        },\n\n        // If this is a resumable upload, grab the relevant data from storage and items in memory that track this upload\n        // so we can pick up from where we left off.\n        _maybePrepareForResume: function(id) {\n            var state = handler._getFileState(id),\n                localStorageId, persistedData;\n\n            // Resume is enabled and possible and this is the first time we've tried to upload this file in this session,\n            // so prepare for a resume attempt.\n            if (resumeEnabled && state.key === undefined) {\n                localStorageId = handler._getLocalStorageId(id);\n                persistedData = localStorage.getItem(localStorageId);\n\n                // If we found this item in local storage, maybe we should resume it.\n                if (persistedData) {\n                    persistedData = JSON.parse(persistedData);\n\n                    // If we found a resume record but we have already handled this file in this session,\n                    // don't try to resume it & ensure we don't persist future check data\n                    if (getDataByUuid(persistedData.uuid)) {\n                        handler._markNotResumable(id);\n                    }\n                    else {\n                        log(qq.format(\"Identified file with ID {} and name of {} as resumable.\", id, getName(id)));\n\n                        onUuidChanged(id, persistedData.uuid);\n\n                        state.key = persistedData.key;\n                        state.chunking = persistedData.chunking;\n                        state.loaded = persistedData.loaded;\n                        state.customResumeData = persistedData.customResumeData;\n                        state.attemptingResume = true;\n\n                        handler.moveInProgressToRemaining(id);\n                    }\n                }\n            }\n        },\n\n        // Persist any data needed to resume this upload in a new session.\n        _maybePersistChunkedState: function(id) {\n            var state = handler._getFileState(id),\n                localStorageId, persistedData;\n\n            // If local storage isn't supported by the browser, or if resume isn't enabled or possible, give up\n            if (resumeEnabled && handler.isResumable(id)) {\n                var customResumeData = getCustomResumeData(id);\n\n                localStorageId = handler._getLocalStorageId(id);\n\n                persistedData = {\n                    name: getName(id),\n                    size: getSize(id),\n                    uuid: getUuid(id),\n                    key: state.key,\n                    chunking: state.chunking,\n                    loaded: state.loaded,\n                    lastUpdated: Date.now(),\n                };\n\n                if (customResumeData) {\n                    persistedData.customResumeData = customResumeData;\n                }\n\n                try {\n                    localStorage.setItem(localStorageId, JSON.stringify(persistedData));\n                }\n                catch (error) {\n                    log(qq.format(\"Unable to save resume data for '{}' due to error: '{}'.\", id, error.toString()), \"warn\");\n                }\n            }\n        },\n\n        _registerProgressHandler: function(id, chunkIdx, chunkSize) {\n            var xhr = handler._getXhr(id, chunkIdx),\n                name = getName(id),\n                progressCalculator = {\n                    simple: function(loaded, total) {\n                        var fileSize = getSize(id);\n\n                        if (loaded === total) {\n                            onProgress(id, name, fileSize, fileSize);\n                        }\n                        else {\n                            onProgress(id, name, (loaded >= fileSize ? fileSize - 1 : loaded), fileSize);\n                        }\n                    },\n\n                    chunked: function(loaded, total) {\n                        var chunkProgress = handler._getFileState(id).temp.chunkProgress,\n                            totalSuccessfullyLoadedForFile = handler._getFileState(id).loaded,\n                            loadedForRequest = loaded,\n                            totalForRequest = total,\n                            totalFileSize = getSize(id),\n                            estActualChunkLoaded = loadedForRequest - (totalForRequest - chunkSize),\n                            totalLoadedForFile = totalSuccessfullyLoadedForFile;\n\n                        chunkProgress[chunkIdx] = estActualChunkLoaded;\n\n                        qq.each(chunkProgress, function(chunkIdx, chunkLoaded) {\n                            totalLoadedForFile += chunkLoaded;\n                        });\n\n                        onProgress(id, name, totalLoadedForFile, totalFileSize);\n                    }\n                };\n\n            xhr.upload.onprogress = function(e) {\n                if (e.lengthComputable) {\n                    /* jshint eqnull: true */\n                    var type = chunkSize == null ? \"simple\" : \"chunked\";\n                    progressCalculator[type](e.loaded, e.total);\n                }\n            };\n        },\n\n        /**\n         * Registers an XHR transport instance created elsewhere.\n         *\n         * @param id ID of the associated file\n         * @param optChunkIdx The chunk index associated with this XHR, if applicable\n         * @param xhr XMLHttpRequest object instance\n         * @param optAjaxRequester `qq.AjaxRequester` associated with this request, if applicable.\n         * @returns {XMLHttpRequest}\n         */\n        _registerXhr: function(id, optChunkIdx, xhr, optAjaxRequester) {\n            var xhrsId = optChunkIdx == null ? -1 : optChunkIdx,\n                tempState = handler._getFileState(id).temp;\n\n            tempState.xhrs = tempState.xhrs || {};\n            tempState.ajaxRequesters = tempState.ajaxRequesters || {};\n\n            tempState.xhrs[xhrsId] = xhr;\n\n            if (optAjaxRequester) {\n                tempState.ajaxRequesters[xhrsId] = optAjaxRequester;\n            }\n\n            return xhr;\n        },\n\n        // Deletes any local storage records that are \"expired\".\n        _removeExpiredChunkingRecords: function() {\n            var expirationDays = resume.recordsExpireIn;\n\n            handler._iterateResumeRecords(function(key, uploadData) {\n                var expirationDate = new Date(uploadData.lastUpdated);\n\n                // transform updated date into expiration date\n                expirationDate.setDate(expirationDate.getDate() + expirationDays);\n\n                if (expirationDate.getTime() <= Date.now()) {\n                    log(\"Removing expired resume record with key \" + key);\n                    localStorage.removeItem(key);\n                }\n            });\n        },\n\n        /**\n         * Determine if the associated file should be chunked.\n         *\n         * @param id ID of the associated file\n         * @returns {*} true if chunking is enabled, possible, and the file can be split into more than 1 part\n         */\n        _shouldChunkThisFile: function(id) {\n            var state = handler._getFileState(id);\n\n            // file may no longer be available if it was recently cancelled\n            if (state) {\n                if (!state.chunking) {\n                    handler.reevaluateChunking(id);\n                }\n\n                return state.chunking.enabled;\n            }\n        }\n    });\n};\n"
  },
  {
    "path": "client/js/uploader.api.js",
    "content": "/*globals qq */\n/**\n * Defines the public API for FineUploader mode.\n */\n(function() {\n    \"use strict\";\n\n    qq.uiPublicApi = {\n        addInitialFiles: function(cannedFileList) {\n            this._parent.prototype.addInitialFiles.apply(this, arguments);\n            this._templating.addCacheToDom();\n        },\n\n        clearStoredFiles: function() {\n            this._parent.prototype.clearStoredFiles.apply(this, arguments);\n            this._templating.clearFiles();\n        },\n\n        addExtraDropzone: function(element) {\n            this._dnd && this._dnd.setupExtraDropzone(element);\n        },\n\n        removeExtraDropzone: function(element) {\n            if (this._dnd) {\n                return this._dnd.removeDropzone(element);\n            }\n        },\n\n        getItemByFileId: function(id) {\n            if (!this._templating.isHiddenForever(id)) {\n                return this._templating.getFileContainer(id);\n            }\n        },\n\n        reset: function() {\n            this._parent.prototype.reset.apply(this, arguments);\n            this._templating.reset();\n\n            if (!this._options.button && this._templating.getButton()) {\n                this._defaultButtonId = this._createUploadButton({\n                    element: this._templating.getButton(),\n                    title: this._options.text.fileInputTitle\n                }).getButtonId();\n            }\n\n            if (this._dnd) {\n                this._dnd.dispose();\n                this._dnd = this._setupDragAndDrop();\n            }\n\n            this._totalFilesInBatch = 0;\n            this._filesInBatchAddedToUi = 0;\n\n            this._setupClickAndEditEventHandlers();\n        },\n\n        setName: function(id, newName) {\n            var formattedFilename = this._options.formatFileName(newName);\n\n            this._parent.prototype.setName.apply(this, arguments);\n            this._templating.updateFilename(id, formattedFilename);\n        },\n\n        pauseUpload: function(id) {\n            var paused = this._parent.prototype.pauseUpload.apply(this, arguments);\n\n            paused && this._templating.uploadPaused(id);\n            return paused;\n        },\n\n        continueUpload: function(id) {\n            var continued = this._parent.prototype.continueUpload.apply(this, arguments);\n\n            continued && this._templating.uploadContinued(id);\n            return continued;\n        },\n\n        getId: function(fileContainerOrChildEl) {\n            return this._templating.getFileId(fileContainerOrChildEl);\n        },\n\n        getDropTarget: function(fileId) {\n            var file = this.getFile(fileId);\n\n            return file.qqDropTarget;\n        }\n    };\n\n    /**\n     * Defines the private (internal) API for FineUploader mode.\n     */\n    qq.uiPrivateApi = {\n        _getButton: function(buttonId) {\n            var button = this._parent.prototype._getButton.apply(this, arguments);\n\n            if (!button) {\n                if (buttonId === this._defaultButtonId) {\n                    button = this._templating.getButton();\n                }\n            }\n\n            return button;\n        },\n\n        _removeFileItem: function(fileId) {\n            this._templating.removeFile(fileId);\n        },\n\n        _setupClickAndEditEventHandlers: function() {\n            this._fileButtonsClickHandler = qq.FileButtonsClickHandler && this._bindFileButtonsClickEvent();\n\n            // A better approach would be to check specifically for focusin event support by querying the DOM API,\n            // but the DOMFocusIn event is not exposed as a property, so we have to resort to UA string sniffing.\n            this._focusinEventSupported = !qq.firefox();\n\n            if (this._isEditFilenameEnabled())\n            {\n                this._filenameClickHandler = this._bindFilenameClickEvent();\n                this._filenameInputFocusInHandler = this._bindFilenameInputFocusInEvent();\n                this._filenameInputFocusHandler = this._bindFilenameInputFocusEvent();\n            }\n        },\n\n        _setupDragAndDrop: function() {\n            var self = this,\n                dropZoneElements = this._options.dragAndDrop.extraDropzones,\n                templating = this._templating,\n                defaultDropZone = templating.getDropZone();\n\n            defaultDropZone && dropZoneElements.push(defaultDropZone);\n\n            return new qq.DragAndDrop({\n                dropZoneElements: dropZoneElements,\n                allowMultipleItems: this._options.multiple,\n                classes: {\n                    dropActive: this._options.classes.dropActive\n                },\n                callbacks: {\n                    processingDroppedFiles: function() {\n                        templating.showDropProcessing();\n                    },\n                    processingDroppedFilesComplete: function(files, targetEl) {\n                        templating.hideDropProcessing();\n\n                        qq.each(files, function(idx, file) {\n                            file.qqDropTarget = targetEl;\n                        });\n\n                        if (files.length) {\n                            self.addFiles(files, null, null);\n                        }\n                    },\n                    dropError: function(code, errorData) {\n                        self._itemError(code, errorData);\n                    },\n                    dropLog: function(message, level) {\n                        self.log(message, level);\n                    }\n                }\n            });\n        },\n\n        _bindFileButtonsClickEvent: function() {\n            var self = this;\n\n            return new qq.FileButtonsClickHandler({\n                templating: this._templating,\n\n                log: function(message, lvl) {\n                    self.log(message, lvl);\n                },\n\n                onDeleteFile: function(fileId) {\n                    self.deleteFile(fileId);\n                },\n\n                onCancel: function(fileId) {\n                    self.cancel(fileId);\n                },\n\n                onRetry: function(fileId) {\n                    self.retry(fileId);\n                },\n\n                onPause: function(fileId) {\n                    self.pauseUpload(fileId);\n                },\n\n                onContinue: function(fileId) {\n                    self.continueUpload(fileId);\n                },\n\n                onGetName: function(fileId) {\n                    return self.getName(fileId);\n                }\n            });\n        },\n\n        _isEditFilenameEnabled: function() {\n            /*jshint -W014 */\n            return this._templating.isEditFilenamePossible()\n                && !this._options.autoUpload\n                && qq.FilenameClickHandler\n                && qq.FilenameInputFocusHandler\n                && qq.FilenameInputFocusHandler;\n        },\n\n        _filenameEditHandler: function() {\n            var self = this,\n                templating = this._templating;\n\n            return {\n                templating: templating,\n                log: function(message, lvl) {\n                    self.log(message, lvl);\n                },\n                onGetUploadStatus: function(fileId) {\n                    return self.getUploads({id: fileId}).status;\n                },\n                onGetName: function(fileId) {\n                    return self.getName(fileId);\n                },\n                onSetName: function(id, newName) {\n                    self.setName(id, newName);\n                },\n                onEditingStatusChange: function(id, isEditing) {\n                    var qqInput = qq(templating.getEditInput(id)),\n                        qqFileContainer = qq(templating.getFileContainer(id));\n\n                    if (isEditing) {\n                        qqInput.addClass(\"qq-editing\");\n                        templating.hideFilename(id);\n                        templating.hideEditIcon(id);\n                    }\n                    else {\n                        qqInput.removeClass(\"qq-editing\");\n                        templating.showFilename(id);\n                        templating.showEditIcon(id);\n                    }\n\n                    // Force IE8 and older to repaint\n                    qqFileContainer.addClass(\"qq-temp\").removeClass(\"qq-temp\");\n                }\n            };\n        },\n\n        _onUploadStatusChange: function(id, oldStatus, newStatus) {\n            this._parent.prototype._onUploadStatusChange.apply(this, arguments);\n\n            if (this._isEditFilenameEnabled()) {\n                // Status for a file exists before it has been added to the DOM, so we must be careful here.\n                if (this._templating.getFileContainer(id) && newStatus !== qq.status.SUBMITTED) {\n                    this._templating.markFilenameEditable(id);\n                    this._templating.hideEditIcon(id);\n                }\n            }\n\n            if (oldStatus === qq.status.UPLOAD_RETRYING && newStatus === qq.status.UPLOADING) {\n                this._templating.hideRetry(id);\n                this._templating.setStatusText(id);\n                qq(this._templating.getFileContainer(id)).removeClass(this._classes.retrying);\n            }\n            else if (newStatus === qq.status.UPLOAD_FAILED) {\n                this._templating.hidePause(id);\n            }\n        },\n\n        _bindFilenameInputFocusInEvent: function() {\n            var spec = qq.extend({}, this._filenameEditHandler());\n\n            return new qq.FilenameInputFocusInHandler(spec);\n        },\n\n        _bindFilenameInputFocusEvent: function() {\n            var spec = qq.extend({}, this._filenameEditHandler());\n\n            return new qq.FilenameInputFocusHandler(spec);\n        },\n\n        _bindFilenameClickEvent: function() {\n            var spec = qq.extend({}, this._filenameEditHandler());\n\n            return new qq.FilenameClickHandler(spec);\n        },\n\n        _storeForLater: function(id) {\n            this._parent.prototype._storeForLater.apply(this, arguments);\n            this._templating.hideSpinner(id);\n        },\n\n        _onAllComplete: function(successful, failed) {\n            this._parent.prototype._onAllComplete.apply(this, arguments);\n            this._templating.resetTotalProgress();\n        },\n\n        _onSubmit: function(id, name) {\n            var file = this.getFile(id);\n\n            if (file && file.qqPath && this._options.dragAndDrop.reportDirectoryPaths) {\n                this._paramsStore.addReadOnly(id, {\n                    qqpath: file.qqPath\n                });\n            }\n\n            this._parent.prototype._onSubmit.apply(this, arguments);\n            this._addToList(id, name);\n        },\n\n        // The file item has been added to the DOM.\n        _onSubmitted: function(id) {\n            // If the edit filename feature is enabled, mark the filename element as \"editable\" and the associated edit icon\n            if (this._isEditFilenameEnabled()) {\n                this._templating.markFilenameEditable(id);\n                this._templating.showEditIcon(id);\n\n                // If the focusin event is not supported, we must add a focus handler to the newly create edit filename text input\n                if (!this._focusinEventSupported) {\n                    this._filenameInputFocusHandler.addHandler(this._templating.getEditInput(id));\n                }\n            }\n        },\n\n        // Update the progress bar & percentage as the file is uploaded\n        _onProgress: function(id, name, loaded, total) {\n            this._parent.prototype._onProgress.apply(this, arguments);\n\n            this._templating.updateProgress(id, loaded, total);\n\n            if (total === 0 || Math.round(loaded / total * 100) === 100) {\n                this._templating.hideCancel(id);\n                this._templating.hidePause(id);\n                this._templating.hideProgress(id);\n                this._templating.setStatusText(id, this._options.text.waitingForResponse);\n\n                // If ~last byte was sent, display total file size\n                this._displayFileSize(id);\n            }\n            else {\n                // If still uploading, display percentage - total size is actually the total request(s) size\n                this._displayFileSize(id, loaded, total);\n            }\n        },\n\n        _onTotalProgress: function(loaded, total) {\n            this._parent.prototype._onTotalProgress.apply(this, arguments);\n            this._templating.updateTotalProgress(loaded, total);\n        },\n\n        _onComplete: function(id, name, result, xhr) {\n            var parentRetVal = this._parent.prototype._onComplete.apply(this, arguments),\n                templating = this._templating,\n                fileContainer = templating.getFileContainer(id),\n                self = this;\n\n            function completeUpload(result) {\n                // If this file is not represented in the templating module, perhaps it was hidden intentionally.\n                // If so, don't perform any UI-related tasks related to this file.\n                if (!fileContainer) {\n                    return;\n                }\n\n                templating.setStatusText(id);\n\n                qq(fileContainer).removeClass(self._classes.retrying);\n                templating.hideProgress(id);\n\n                if (self.getUploads({id: id}).status !== qq.status.UPLOAD_FAILED) {\n                    templating.hideCancel(id);\n                }\n                templating.hideSpinner(id);\n\n                if (result.success) {\n                    self._markFileAsSuccessful(id);\n                }\n                else {\n                    qq(fileContainer).addClass(self._classes.fail);\n                    templating.showCancel(id);\n\n                    if (templating.isRetryPossible() && !self._preventRetries[id]) {\n                        qq(fileContainer).addClass(self._classes.retryable);\n                        templating.showRetry(id);\n                    }\n                    self._controlFailureTextDisplay(id, result);\n                }\n            }\n\n            // The parent may need to perform some async operation before we can accurately determine the status of the upload.\n            if (parentRetVal instanceof qq.Promise) {\n                parentRetVal.done(function(newResult) {\n                    completeUpload(newResult);\n                });\n\n            }\n            else {\n                completeUpload(result);\n            }\n\n            return parentRetVal;\n        },\n\n        _markFileAsSuccessful: function(id) {\n            var templating = this._templating;\n\n            if (this._isDeletePossible()) {\n                templating.showDeleteButton(id);\n            }\n\n            qq(templating.getFileContainer(id)).addClass(this._classes.success);\n\n            this._maybeUpdateThumbnail(id);\n        },\n\n        _onUploadPrep: function(id) {\n            this._parent.prototype._onUploadPrep.apply(this, arguments);\n            this._templating.showSpinner(id);\n        },\n\n        _onUpload: function(id, name) {\n            var parentRetVal = this._parent.prototype._onUpload.apply(this, arguments);\n\n            this._templating.showSpinner(id);\n\n            return parentRetVal;\n        },\n\n        _onUploadChunk: function(id, chunkData) {\n            this._parent.prototype._onUploadChunk.apply(this, arguments);\n\n            // Only display the pause button if we have finished uploading at least one chunk\n            // & this file can be resumed\n            if (chunkData.partIndex > 0 && this._handler.isResumable(id)) {\n                this._templating.allowPause(id);\n            }\n        },\n\n        _onCancel: function(id, name) {\n            this._parent.prototype._onCancel.apply(this, arguments);\n            this._removeFileItem(id);\n\n            if (this._getNotFinished() === 0) {\n                this._templating.resetTotalProgress();\n            }\n        },\n\n        _onBeforeAutoRetry: function(id) {\n            var retryNumForDisplay, maxAuto, retryNote;\n\n            this._parent.prototype._onBeforeAutoRetry.apply(this, arguments);\n\n            this._showCancelLink(id);\n\n            if (this._options.retry.showAutoRetryNote) {\n                retryNumForDisplay = this._autoRetries[id];\n                maxAuto = this._options.retry.maxAutoAttempts;\n\n                retryNote = this._options.retry.autoRetryNote.replace(/\\{retryNum\\}/g, retryNumForDisplay);\n                retryNote = retryNote.replace(/\\{maxAuto\\}/g, maxAuto);\n\n                this._templating.setStatusText(id, retryNote);\n                qq(this._templating.getFileContainer(id)).addClass(this._classes.retrying);\n            }\n        },\n\n        //return false if we should not attempt the requested retry\n        _onBeforeManualRetry: function(id) {\n            if (this._parent.prototype._onBeforeManualRetry.apply(this, arguments)) {\n                this._templating.resetProgress(id);\n                qq(this._templating.getFileContainer(id)).removeClass(this._classes.fail);\n                this._templating.setStatusText(id);\n                this._templating.showSpinner(id);\n                this._showCancelLink(id);\n                return true;\n            }\n            else {\n                qq(this._templating.getFileContainer(id)).addClass(this._classes.retryable);\n                this._templating.showRetry(id);\n                return false;\n            }\n        },\n\n        _onSubmitDelete: function(id) {\n            var onSuccessCallback = qq.bind(this._onSubmitDeleteSuccess, this);\n\n            this._parent.prototype._onSubmitDelete.call(this, id, onSuccessCallback);\n        },\n\n        _onSubmitDeleteSuccess: function(id, uuid, additionalMandatedParams) {\n            if (this._options.deleteFile.forceConfirm) {\n                this._showDeleteConfirm.apply(this, arguments);\n            }\n            else {\n                this._sendDeleteRequest.apply(this, arguments);\n            }\n        },\n\n        _onDeleteComplete: function(id, xhr, isError) {\n            this._parent.prototype._onDeleteComplete.apply(this, arguments);\n\n            this._templating.hideSpinner(id);\n\n            if (isError) {\n                this._templating.setStatusText(id, this._options.deleteFile.deletingFailedText);\n                this._templating.showDeleteButton(id);\n            }\n            else {\n                this._removeFileItem(id);\n            }\n        },\n\n        _sendDeleteRequest: function(id, uuid, additionalMandatedParams) {\n            this._templating.hideDeleteButton(id);\n            this._templating.showSpinner(id);\n            this._templating.setStatusText(id, this._options.deleteFile.deletingStatusText);\n            this._deleteHandler.sendDelete.apply(this, arguments);\n        },\n\n        _showDeleteConfirm: function(id, uuid, mandatedParams) {\n            /*jshint -W004 */\n            var fileName = this.getName(id),\n                confirmMessage = this._options.deleteFile.confirmMessage.replace(/\\{filename\\}/g, fileName),\n                uuid = this.getUuid(id),\n                deleteRequestArgs = arguments,\n                self = this,\n                retVal;\n\n            retVal = this._options.showConfirm(confirmMessage);\n\n            if (qq.isGenericPromise(retVal)) {\n                retVal.then(function() {\n                    self._sendDeleteRequest.apply(self, deleteRequestArgs);\n                });\n            }\n            else if (retVal !== false) {\n                self._sendDeleteRequest.apply(self, deleteRequestArgs);\n            }\n        },\n\n        _addToList: function(id, name, canned) {\n            var prependData,\n                prependIndex = 0,\n                dontDisplay = this._handler.isProxied(id) && this._options.scaling.hideScaled,\n                record;\n\n            if (this._options.display.prependFiles) {\n                if (this._totalFilesInBatch > 1 && this._filesInBatchAddedToUi > 0) {\n                    prependIndex = this._filesInBatchAddedToUi - 1;\n                }\n\n                prependData = {\n                    index: prependIndex\n                };\n            }\n\n            if (!canned) {\n                if (this._options.disableCancelForFormUploads && !qq.supportedFeatures.ajaxUploading) {\n                    this._templating.disableCancel();\n                }\n\n                // Cancel all existing (previous) files and clear the list if this file is not part of\n                // a scaled file group that has already been accepted, or if this file is not part of\n                // a scaled file group at all.\n                if (!this._options.multiple) {\n                    record = this.getUploads({id: id});\n\n                    this._handledProxyGroup = this._handledProxyGroup || record.proxyGroupId;\n\n                    if (record.proxyGroupId !== this._handledProxyGroup || !record.proxyGroupId) {\n                        this._handler.cancelAll();\n                        this._clearList();\n                        this._handledProxyGroup = null;\n                    }\n                }\n            }\n\n            if (canned) {\n                this._templating.addFileToCache(id, this._options.formatFileName(name), prependData, dontDisplay);\n                this._templating.updateThumbnail(id, this._thumbnailUrls[id], true, this._options.thumbnails.customResizer);\n            }\n            else {\n                this._templating.addFile(id, this._options.formatFileName(name), prependData, dontDisplay);\n                this._templating.generatePreview(id, this.getFile(id), this._options.thumbnails.customResizer);\n            }\n\n            this._filesInBatchAddedToUi += 1;\n\n            if (canned ||\n                (this._options.display.fileSizeOnSubmit && qq.supportedFeatures.ajaxUploading)) {\n\n                this._displayFileSize(id);\n            }\n        },\n\n        _clearList: function() {\n            this._templating.clearFiles();\n            this.clearStoredFiles();\n        },\n\n        _displayFileSize: function(id, loadedSize, totalSize) {\n            var size = this.getSize(id),\n                sizeForDisplay = this._formatSize(size);\n\n            if (size >= 0) {\n                if (loadedSize !== undefined && totalSize !== undefined) {\n                    sizeForDisplay = this._formatProgress(loadedSize, totalSize);\n                }\n\n                this._templating.updateSize(id, sizeForDisplay);\n            }\n        },\n\n        _formatProgress: function(uploadedSize, totalSize) {\n            var message = this._options.text.formatProgress;\n            function r(name, replacement) { message = message.replace(name, replacement); }\n\n            r(\"{percent}\", Math.round(uploadedSize / totalSize * 100));\n            r(\"{total_size}\", this._formatSize(totalSize));\n            return message;\n        },\n\n        _controlFailureTextDisplay: function(id, response) {\n            var mode, responseProperty, failureReason;\n\n            mode = this._options.failedUploadTextDisplay.mode;\n            responseProperty = this._options.failedUploadTextDisplay.responseProperty;\n\n            if (mode === \"custom\") {\n                failureReason = response[responseProperty];\n                if (!failureReason) {\n                    failureReason = this._options.text.failUpload;\n                }\n\n                this._templating.setStatusText(id, failureReason);\n\n                if (this._options.failedUploadTextDisplay.enableTooltip) {\n                    this._showTooltip(id, failureReason);\n                }\n            }\n            else if (mode === \"default\") {\n                this._templating.setStatusText(id, this._options.text.failUpload);\n            }\n            else if (mode !== \"none\") {\n                this.log(\"failedUploadTextDisplay.mode value of '\" + mode + \"' is not valid\", \"warn\");\n            }\n        },\n\n        _showTooltip: function(id, text) {\n            this._templating.getFileContainer(id).title = text;\n        },\n\n        _showCancelLink: function(id) {\n            if (!this._options.disableCancelForFormUploads || qq.supportedFeatures.ajaxUploading) {\n                this._templating.showCancel(id);\n            }\n        },\n\n        _itemError: function(code, name, item) {\n            var message = this._parent.prototype._itemError.apply(this, arguments);\n            this._options.showMessage(message);\n        },\n\n        _batchError: function(message) {\n            this._parent.prototype._batchError.apply(this, arguments);\n            this._options.showMessage(message);\n        },\n\n        _setupPastePrompt: function() {\n            var self = this;\n\n            this._options.callbacks.onPasteReceived = function() {\n                var message = self._options.paste.namePromptMessage,\n                    defaultVal = self._options.paste.defaultName;\n\n                return self._options.showPrompt(message, defaultVal);\n            };\n        },\n\n        _fileOrBlobRejected: function(id, name) {\n            this._totalFilesInBatch -= 1;\n            this._parent.prototype._fileOrBlobRejected.apply(this, arguments);\n        },\n\n        _prepareItemsForUpload: function(items, params, endpoint) {\n            this._totalFilesInBatch = items.length;\n            this._filesInBatchAddedToUi = 0;\n            this._parent.prototype._prepareItemsForUpload.apply(this, arguments);\n        },\n\n        _maybeUpdateThumbnail: function(fileId) {\n            var thumbnailUrl = this._thumbnailUrls[fileId],\n                fileStatus = this.getUploads({id: fileId}).status;\n\n            if (fileStatus !== qq.status.DELETED &&\n                (thumbnailUrl ||\n                this._options.thumbnails.placeholders.waitUntilResponse ||\n                !qq.supportedFeatures.imagePreviews)) {\n\n                // This will replace the \"waiting\" placeholder with a \"preview not available\" placeholder\n                // if called with a null thumbnailUrl.\n                this._templating.updateThumbnail(fileId, thumbnailUrl, this._options.thumbnails.customResizer);\n            }\n        },\n\n        _addCannedFile: function(sessionData) {\n            var id = this._parent.prototype._addCannedFile.apply(this, arguments);\n\n            this._addToList(id, this.getName(id), true);\n            this._templating.hideSpinner(id);\n            this._templating.hideCancel(id);\n            this._markFileAsSuccessful(id);\n\n            return id;\n        },\n\n        _setSize: function(id, newSize) {\n            this._parent.prototype._setSize.apply(this, arguments);\n\n            this._templating.updateSize(id, this._formatSize(newSize));\n        },\n\n        _sessionRequestComplete: function() {\n            this._templating.addCacheToDom();\n            this._parent.prototype._sessionRequestComplete.apply(this, arguments);\n        }\n    };\n}());\n"
  },
  {
    "path": "client/js/uploader.basic.api.js",
    "content": "/*globals qq*/\n/**\n * Defines the public API for FineUploaderBasic mode.\n */\n(function() {\n    \"use strict\";\n\n    qq.basePublicApi = {\n        // DEPRECATED - TODO REMOVE IN NEXT MAJOR RELEASE (replaced by addFiles)\n        addBlobs: function(blobDataOrArray, params, endpoint) {\n            this.addFiles(blobDataOrArray, params, endpoint);\n        },\n\n        addInitialFiles: function(cannedFileList) {\n            var self = this;\n\n            qq.each(cannedFileList, function(index, cannedFile) {\n                self._addCannedFile(cannedFile);\n            });\n        },\n\n        addFiles: function(data, params, endpoint) {\n            this._maybeHandleIos8SafariWorkaround();\n\n            var batchId = this._storedIds.length === 0 ? qq.getUniqueId() : this._currentBatchId,\n\n                processBlob = qq.bind(function(blob) {\n                    this._handleNewFile({\n                        blob: blob,\n                        name: this._options.blobs.defaultName\n                    }, batchId, verifiedFiles);\n                }, this),\n\n                processBlobData = qq.bind(function(blobData) {\n                    this._handleNewFile(blobData, batchId, verifiedFiles);\n                }, this),\n\n                processCanvas = qq.bind(function(canvas) {\n                    var blob = qq.canvasToBlob(canvas);\n\n                    this._handleNewFile({\n                        blob: blob,\n                        name: this._options.blobs.defaultName + \".png\"\n                    }, batchId, verifiedFiles);\n                }, this),\n\n                processCanvasData = qq.bind(function(canvasData) {\n                    var normalizedQuality = canvasData.quality && canvasData.quality / 100,\n                        blob = qq.canvasToBlob(canvasData.canvas, canvasData.type, normalizedQuality);\n\n                    this._handleNewFile({\n                        blob: blob,\n                        name: canvasData.name\n                    }, batchId, verifiedFiles);\n                }, this),\n\n                processFileOrInput = qq.bind(function(fileOrInput) {\n                    if (qq.isInput(fileOrInput) && qq.supportedFeatures.ajaxUploading) {\n                        var files = Array.prototype.slice.call(fileOrInput.files),\n                            self = this;\n\n                        qq.each(files, function(idx, file) {\n                            self._handleNewFile(file, batchId, verifiedFiles);\n                        });\n                    }\n                    else {\n                        this._handleNewFile(fileOrInput, batchId, verifiedFiles);\n                    }\n                }, this),\n\n                normalizeData = function() {\n                    if (qq.isFileList(data)) {\n                        data = Array.prototype.slice.call(data);\n                    }\n                    data = [].concat(data);\n                },\n\n                self = this,\n                verifiedFiles = [];\n\n            this._currentBatchId = batchId;\n\n            if (data) {\n                normalizeData();\n\n                qq.each(data, function(idx, fileContainer) {\n                    if (qq.isFileOrInput(fileContainer)) {\n                        processFileOrInput(fileContainer);\n                    }\n                    else if (qq.isBlob(fileContainer)) {\n                        processBlob(fileContainer);\n                    }\n                    else if (qq.isObject(fileContainer)) {\n                        if (fileContainer.blob && fileContainer.name) {\n                            processBlobData(fileContainer);\n                        }\n                        else if (fileContainer.canvas && fileContainer.name) {\n                            processCanvasData(fileContainer);\n                        }\n                    }\n                    else if (fileContainer.tagName && fileContainer.tagName.toLowerCase() === \"canvas\") {\n                        processCanvas(fileContainer);\n                    }\n                    else {\n                        self.log(fileContainer + \" is not a valid file container!  Ignoring!\", \"warn\");\n                    }\n                });\n\n                this.log(\"Received \" + verifiedFiles.length + \" files.\");\n                this._prepareItemsForUpload(verifiedFiles, params, endpoint);\n            }\n        },\n\n        cancel: function(id) {\n            var uploadData = this._uploadData.retrieve({id: id});\n\n            if (uploadData && uploadData.status === qq.status.UPLOAD_FINALIZING) {\n                this.log(qq.format(\"Ignoring cancel for file ID {} ({}).  Finalizing upload.\", id, this.getName(id)), \"error\");\n            }\n            else {\n                this._handler.cancel(id);\n            }\n        },\n\n        cancelAll: function() {\n            var storedIdsCopy = [],\n                self = this;\n\n            qq.extend(storedIdsCopy, this._storedIds);\n            qq.each(storedIdsCopy, function(idx, storedFileId) {\n                self.cancel(storedFileId);\n            });\n\n            this._handler.cancelAll();\n        },\n\n        clearStoredFiles: function() {\n            this._storedIds = [];\n        },\n\n        continueUpload: function(id) {\n            var uploadData = this._uploadData.retrieve({id: id});\n\n            if (!qq.supportedFeatures.pause || !this._options.chunking.enabled) {\n                return false;\n            }\n\n            if (uploadData.status === qq.status.PAUSED) {\n                this.log(qq.format(\"Paused file ID {} ({}) will be continued.  Not paused.\", id, this.getName(id)));\n                this._uploadFile(id);\n                return true;\n            }\n            else {\n                this.log(qq.format(\"Ignoring continue for file ID {} ({}).  Not paused.\", id, this.getName(id)), \"error\");\n            }\n\n            return false;\n        },\n\n        deleteFile: function(id) {\n            return this._onSubmitDelete(id);\n        },\n\n        // TODO document?\n        doesExist: function(fileOrBlobId) {\n            return this._handler.isValid(fileOrBlobId);\n        },\n\n        // Generate a variable size thumbnail on an img or canvas,\n        // returning a promise that is fulfilled when the attempt completes.\n        // Thumbnail can either be based off of a URL for an image returned\n        // by the server in the upload response, or the associated `Blob`.\n        drawThumbnail: function(fileId, imgOrCanvas, maxSize, fromServer, customResizeFunction) {\n            var promiseToReturn = new qq.Promise(),\n                fileOrUrl, options;\n\n            if (this._imageGenerator) {\n                fileOrUrl = this._thumbnailUrls[fileId];\n                options = {\n                    customResizeFunction: customResizeFunction,\n                    maxSize: maxSize > 0 ? maxSize : null,\n                    scale: maxSize > 0\n                };\n\n                // If client-side preview generation is possible\n                // and we are not specifically looking for the image URl returned by the server...\n                if (!fromServer && qq.supportedFeatures.imagePreviews) {\n                    fileOrUrl = this.getFile(fileId);\n                }\n\n                /* jshint eqeqeq:false,eqnull:true */\n                if (fileOrUrl == null) {\n                    promiseToReturn.failure({container: imgOrCanvas, error: \"File or URL not found.\"});\n                }\n                else {\n                    this._imageGenerator.generate(fileOrUrl, imgOrCanvas, options).then(\n                        function success(modifiedContainer) {\n                            promiseToReturn.success(modifiedContainer);\n                        },\n\n                        function failure(container, reason) {\n                            promiseToReturn.failure({container: container, error: reason || \"Problem generating thumbnail\"});\n                        }\n                    );\n                }\n            }\n            else {\n                promiseToReturn.failure({container: imgOrCanvas, error: \"Missing image generator module\"});\n            }\n\n            return promiseToReturn;\n        },\n\n        getButton: function(fileId) {\n            return this._getButton(this._buttonIdsForFileIds[fileId]);\n        },\n\n        getEndpoint: function(fileId) {\n            return this._endpointStore.get(fileId);\n        },\n\n        getFile: function(fileOrBlobId) {\n            var file = this._handler.getFile(fileOrBlobId);\n            var uploadDataRecord;\n\n            if (!file) {\n                uploadDataRecord = this._uploadData.retrieve({id: fileOrBlobId});\n\n                if (uploadDataRecord) {\n                    file = uploadDataRecord.file;\n                }\n            }\n\n            return file || null;\n        },\n\n        getInProgress: function() {\n            return this._uploadData.retrieve({\n                status: [\n                    qq.status.UPLOADING,\n                    qq.status.UPLOAD_RETRYING,\n                    qq.status.QUEUED\n                ]\n            }).length;\n        },\n\n        getName: function(id) {\n            return this._uploadData.retrieve({id: id}).name;\n        },\n\n        // Parent ID for a specific file, or null if this is the parent, or if it has no parent.\n        getParentId: function(id) {\n            var uploadDataEntry = this.getUploads({id: id}),\n                parentId = null;\n\n            if (uploadDataEntry) {\n                if (uploadDataEntry.parentId !== undefined) {\n                    parentId = uploadDataEntry.parentId;\n                }\n            }\n\n            return parentId;\n        },\n\n        getResumableFilesData: function() {\n            return this._handler.getResumableFilesData();\n        },\n\n        getSize: function(id) {\n            return this._uploadData.retrieve({id: id}).size;\n        },\n\n        getNetUploads: function() {\n            return this._netUploaded;\n        },\n\n        getRemainingAllowedItems: function() {\n            var allowedItems = this._currentItemLimit;\n\n            if (allowedItems > 0) {\n                return allowedItems - this._netUploadedOrQueued;\n            }\n\n            return null;\n        },\n\n        getUploads: function(optionalFilter) {\n            return this._uploadData.retrieve(optionalFilter);\n        },\n\n        getUuid: function(id) {\n            return this._uploadData.retrieve({id: id}).uuid;\n        },\n\n        isResumable: function(id) {\n            return this._handler.hasResumeRecord(id);\n        },\n\n        log: function(str, level) {\n            if (this._options.debug && (!level || level === \"info\")) {\n                qq.log(\"[Fine Uploader \" + qq.version + \"] \" + str);\n            }\n            else if (level && level !== \"info\") {\n                qq.log(\"[Fine Uploader \" + qq.version + \"] \" + str, level);\n\n            }\n        },\n\n        pauseUpload: function(id) {\n            var uploadData = this._uploadData.retrieve({id: id});\n\n            if (!qq.supportedFeatures.pause || !this._options.chunking.enabled) {\n                return false;\n            }\n\n            // Pause only really makes sense if the file is uploading or retrying\n            if (qq.indexOf([qq.status.UPLOADING, qq.status.UPLOAD_RETRYING], uploadData.status) >= 0) {\n                if (this._handler.pause(id)) {\n                    this._uploadData.setStatus(id, qq.status.PAUSED);\n                    return true;\n                }\n                else {\n                    this.log(qq.format(\"Unable to pause file ID {} ({}).\", id, this.getName(id)), \"error\");\n                }\n            }\n            else {\n                this.log(qq.format(\"Ignoring pause for file ID {} ({}).  Not in progress.\", id, this.getName(id)), \"error\");\n            }\n\n            return false;\n        },\n\n        removeFileRef: function(id) {\n            this._handler.expunge(id);\n            this._uploadData.removeFileRef(id);\n        },\n\n        reset: function() {\n            this.log(\"Resetting uploader...\");\n\n            this._handler.reset();\n            this._storedIds = [];\n            this._autoRetries = [];\n            this._retryTimeouts = [];\n            this._preventRetries = [];\n            this._thumbnailUrls = [];\n\n            qq.each(this._buttons, function(idx, button) {\n                button.reset();\n            });\n\n            this._paramsStore.reset();\n            this._endpointStore.reset();\n            this._netUploadedOrQueued = 0;\n            this._netUploaded = 0;\n            this._uploadData.reset();\n            this._buttonIdsForFileIds = [];\n\n            this._pasteHandler && this._pasteHandler.reset();\n            this._options.session.refreshOnReset && this._refreshSessionData();\n\n            this._succeededSinceLastAllComplete = [];\n            this._failedSinceLastAllComplete = [];\n\n            this._totalProgress && this._totalProgress.reset();\n\n            this._customResumeDataStore.reset();\n        },\n\n        retry: function(id) {\n            return this._manualRetry(id);\n        },\n\n        scaleImage: function(id, specs) {\n            var self = this;\n\n            return qq.Scaler.prototype.scaleImage(id, specs, {\n                log: qq.bind(self.log, self),\n                getFile: qq.bind(self.getFile, self),\n                uploadData: self._uploadData\n            });\n        },\n\n        setCustomHeaders: function(headers, id) {\n            this._customHeadersStore.set(headers, id);\n        },\n\n        setCustomResumeData: function(id, data) {\n            this._customResumeDataStore.set(data, id);\n        },\n\n        setDeleteFileCustomHeaders: function(headers, id) {\n            this._deleteFileCustomHeadersStore.set(headers, id);\n        },\n\n        setDeleteFileEndpoint: function(endpoint, id) {\n            this._deleteFileEndpointStore.set(endpoint, id);\n        },\n\n        setDeleteFileParams: function(params, id) {\n            this._deleteFileParamsStore.set(params, id);\n        },\n\n        // Re-sets the default endpoint, an endpoint for a specific file, or an endpoint for a specific button\n        setEndpoint: function(endpoint, id) {\n            this._endpointStore.set(endpoint, id);\n        },\n\n        setForm: function(elementOrId) {\n            this._updateFormSupportAndParams(elementOrId);\n        },\n\n        setItemLimit: function(newItemLimit) {\n            this._currentItemLimit = newItemLimit;\n        },\n\n        setName: function(id, newName) {\n            this._uploadData.updateName(id, newName);\n        },\n\n        setParams: function(params, id) {\n            this._paramsStore.set(params, id);\n        },\n\n        setUuid: function(id, newUuid) {\n            return this._uploadData.uuidChanged(id, newUuid);\n        },\n\n        /**\n         * Expose the internal status of a file id to the public api for manual state changes\n         * @public\n         *\n         * @param {Number} id,\n         * @param {String} newStatus\n         *\n         * @todo Implement the remaining methods\n         */\n        setStatus: function(id, newStatus) {\n            var fileRecord = this.getUploads({id: id});\n            if (!fileRecord) {\n                throw new qq.Error(id + \" is not a valid file ID.\");\n            }\n\n            switch (newStatus) {\n                case qq.status.DELETED:\n                    this._onDeleteComplete(id, null, false);\n                    break;\n                case qq.status.DELETE_FAILED:\n                    this._onDeleteComplete(id, null, true);\n                    break;\n                default:\n                    var errorMessage = \"Method setStatus called on '\" + name + \"' not implemented yet for \" + newStatus;\n                    this.log(errorMessage);\n                    throw new qq.Error(errorMessage);\n            }\n        },\n\n        uploadStoredFiles: function() {\n            if (this._storedIds.length === 0) {\n                this._itemError(\"noFilesError\");\n            }\n            else {\n                this._uploadStoredFiles();\n            }\n        }\n    };\n\n    /**\n     * Defines the private (internal) API for FineUploaderBasic mode.\n     */\n    qq.basePrivateApi = {\n        // Updates internal state with a file record (not backed by a live file).  Returns the assigned ID.\n        _addCannedFile: function(sessionData) {\n            var self = this;\n\n            return this._uploadData.addFile({\n                uuid: sessionData.uuid,\n                name: sessionData.name,\n                size: sessionData.size,\n                status: qq.status.UPLOAD_SUCCESSFUL,\n                onBeforeStatusChange: function(id) {\n                    sessionData.deleteFileEndpoint && self.setDeleteFileEndpoint(sessionData.deleteFileEndpoint, id);\n                    sessionData.deleteFileParams && self.setDeleteFileParams(sessionData.deleteFileParams, id);\n\n                    if (sessionData.thumbnailUrl) {\n                        self._thumbnailUrls[id] = sessionData.thumbnailUrl;\n                    }\n\n                    self._netUploaded++;\n                    self._netUploadedOrQueued++;\n                }\n            });\n        },\n\n        _annotateWithButtonId: function(file, associatedInput) {\n            if (qq.isFile(file)) {\n                file.qqButtonId = this._getButtonId(associatedInput);\n            }\n        },\n\n        _batchError: function(message) {\n            this._options.callbacks.onError(null, null, message, undefined);\n        },\n\n        _createDeleteHandler: function() {\n            var self = this;\n\n            return new qq.DeleteFileAjaxRequester({\n                method: this._options.deleteFile.method.toUpperCase(),\n                maxConnections: this._options.maxConnections,\n                uuidParamName: this._options.request.uuidName,\n                customHeaders: this._deleteFileCustomHeadersStore,\n                paramsStore: this._deleteFileParamsStore,\n                endpointStore: this._deleteFileEndpointStore,\n                cors: this._options.cors,\n                log: qq.bind(self.log, self),\n                onDelete: function(id) {\n                    self._onDelete(id);\n                    self._options.callbacks.onDelete(id);\n                },\n                onDeleteComplete: function(id, xhrOrXdr, isError) {\n                    self._onDeleteComplete(id, xhrOrXdr, isError);\n                    self._options.callbacks.onDeleteComplete(id, xhrOrXdr, isError);\n                }\n\n            });\n        },\n\n        _createPasteHandler: function() {\n            var self = this;\n\n            return new qq.PasteSupport({\n                targetElement: this._options.paste.targetElement,\n                callbacks: {\n                    log: qq.bind(self.log, self),\n                    pasteReceived: function(blob) {\n                        self._handleCheckedCallback({\n                            name: \"onPasteReceived\",\n                            callback: qq.bind(self._options.callbacks.onPasteReceived, self, blob),\n                            onSuccess: qq.bind(self._handlePasteSuccess, self, blob),\n                            identifier: \"pasted image\"\n                        });\n                    }\n                }\n            });\n        },\n\n        _createStore: function(initialValue, _readOnlyValues_) {\n            var store = {},\n                catchall = initialValue,\n                perIdReadOnlyValues = {},\n                readOnlyValues = _readOnlyValues_,\n                copy = function(orig) {\n                    if (qq.isObject(orig)) {\n                        return qq.extend({}, orig);\n                    }\n                    return orig;\n                },\n                getReadOnlyValues = function() {\n                    if (qq.isFunction(readOnlyValues)) {\n                        return readOnlyValues();\n                    }\n                    return readOnlyValues;\n                },\n                includeReadOnlyValues = function(id, existing) {\n                    if (readOnlyValues && qq.isObject(existing)) {\n                        qq.extend(existing, getReadOnlyValues());\n                    }\n\n                    if (perIdReadOnlyValues[id]) {\n                        qq.extend(existing, perIdReadOnlyValues[id]);\n                    }\n                };\n\n            return {\n                set: function(val, id) {\n                    /*jshint eqeqeq: true, eqnull: true*/\n                    if (id == null) {\n                        store = {};\n                        catchall = copy(val);\n                    }\n                    else {\n                        store[id] = copy(val);\n                    }\n                },\n\n                get: function(id) {\n                    var values;\n\n                    /*jshint eqeqeq: true, eqnull: true*/\n                    if (id != null && store[id]) {\n                        values = store[id];\n                    }\n                    else {\n                        values = copy(catchall);\n                    }\n\n                    includeReadOnlyValues(id, values);\n\n                    return copy(values);\n                },\n\n                addReadOnly: function(id, values) {\n                    // Only applicable to Object stores\n                    if (qq.isObject(store)) {\n                        // If null ID, apply readonly values to all files\n                        if (id === null) {\n                            if (qq.isFunction(values)) {\n                                readOnlyValues = values;\n                            }\n                            else {\n                                readOnlyValues = readOnlyValues || {};\n                                qq.extend(readOnlyValues, values);\n                            }\n                        }\n                        else {\n                            perIdReadOnlyValues[id] = perIdReadOnlyValues[id] || {};\n                            qq.extend(perIdReadOnlyValues[id], values);\n                        }\n                    }\n                },\n\n                remove: function(fileId) {\n                    return delete store[fileId];\n                },\n\n                reset: function() {\n                    store = {};\n                    perIdReadOnlyValues = {};\n                    catchall = initialValue;\n                }\n            };\n        },\n\n        _createUploadDataTracker: function() {\n            var self = this;\n\n            return new qq.UploadData({\n                getName: function(id) {\n                    return self.getName(id);\n                },\n                getUuid: function(id) {\n                    return self.getUuid(id);\n                },\n                getSize: function(id) {\n                    return self.getSize(id);\n                },\n                onStatusChange: function(id, oldStatus, newStatus) {\n                    self._onUploadStatusChange(id, oldStatus, newStatus);\n                    self._options.callbacks.onStatusChange(id, oldStatus, newStatus);\n                    self._maybeAllComplete(id, newStatus);\n\n                    if (self._totalProgress) {\n                        setTimeout(function() {\n                            self._totalProgress.onStatusChange(id, oldStatus, newStatus);\n                        }, 0);\n                    }\n                }\n            });\n        },\n\n        /**\n         * Generate a tracked upload button.\n         *\n         * @param spec Object containing a required `element` property\n         * along with optional `multiple`, `accept`, and `folders`.\n         * @returns {qq.UploadButton}\n         * @private\n         */\n        _createUploadButton: function(spec) {\n            var self = this,\n                acceptFiles = spec.accept || this._options.validation.acceptFiles,\n                allowedExtensions = spec.allowedExtensions || this._options.validation.allowedExtensions,\n                button;\n\n            function allowMultiple() {\n                if (qq.supportedFeatures.ajaxUploading) {\n                    // Workaround for bug in iOS7+ (see #1039)\n                    if (self._options.workarounds.iosEmptyVideos &&\n                        qq.ios() &&\n                        !qq.ios6() &&\n                        self._isAllowedExtension(allowedExtensions, \".mov\")) {\n\n                        return false;\n                    }\n\n                    if (spec.multiple === undefined) {\n                        return self._options.multiple;\n                    }\n\n                    return spec.multiple;\n                }\n\n                return false;\n            }\n\n            button = new qq.UploadButton({\n                acceptFiles: acceptFiles,\n                element: spec.element,\n                focusClass: this._options.classes.buttonFocus,\n                folders: spec.folders,\n                hoverClass: this._options.classes.buttonHover,\n                ios8BrowserCrashWorkaround: this._options.workarounds.ios8BrowserCrash,\n                multiple: allowMultiple(),\n                name: this._options.request.inputName,\n                onChange: function(input) {\n                    self._onInputChange(input);\n                },\n                title: spec.title == null ? this._options.text.fileInputTitle : spec.title\n            });\n\n            this._disposeSupport.addDisposer(function() {\n                button.dispose();\n            });\n\n            self._buttons.push(button);\n\n            return button;\n        },\n\n        _createUploadHandler: function(additionalOptions, namespace) {\n            var self = this,\n                lastOnProgress = {},\n                options = {\n                    debug: this._options.debug,\n                    maxConnections: this._options.maxConnections,\n                    cors: this._options.cors,\n                    paramsStore: this._paramsStore,\n                    endpointStore: this._endpointStore,\n                    chunking: this._options.chunking,\n                    resume: this._options.resume,\n                    blobs: this._options.blobs,\n                    log: qq.bind(self.log, self),\n                    preventRetryParam: this._options.retry.preventRetryResponseProperty,\n                    onProgress: function(id, name, loaded, total) {\n                        if (loaded < 0 || total < 0) {\n                            return;\n                        }\n\n                        if (lastOnProgress[id]) {\n                            if (lastOnProgress[id].loaded !== loaded || lastOnProgress[id].total !== total) {\n                                self._onProgress(id, name, loaded, total);\n                                self._options.callbacks.onProgress(id, name, loaded, total);\n                            }\n                        }\n                        else {\n                            self._onProgress(id, name, loaded, total);\n                            self._options.callbacks.onProgress(id, name, loaded, total);\n                        }\n\n                        lastOnProgress[id] = {loaded: loaded, total: total};\n\n                    },\n                    onComplete: function(id, name, result, xhr) {\n                        delete lastOnProgress[id];\n\n                        var status = self.getUploads({id: id}).status,\n                            retVal;\n\n                        // This is to deal with some observed cases where the XHR readyStateChange handler is\n                        // invoked by the browser multiple times for the same XHR instance with the same state\n                        // readyState value.  Higher level: don't invoke complete-related code if we've already\n                        // done this.\n                        if (status === qq.status.UPLOAD_SUCCESSFUL || status === qq.status.UPLOAD_FAILED) {\n                            return;\n                        }\n\n                        retVal = self._onComplete(id, name, result, xhr);\n\n                        // If the internal `_onComplete` handler returns a promise, don't invoke the `onComplete` callback\n                        // until the promise has been fulfilled.\n                        if (retVal instanceof  qq.Promise) {\n                            retVal.done(function() {\n                                self._options.callbacks.onComplete(id, name, result, xhr);\n                            });\n                        }\n                        else {\n                            self._options.callbacks.onComplete(id, name, result, xhr);\n                        }\n                    },\n                    onCancel: function(id, name, cancelFinalizationEffort) {\n                        var promise = new qq.Promise();\n\n                        self._handleCheckedCallback({\n                            name: \"onCancel\",\n                            callback: qq.bind(self._options.callbacks.onCancel, self, id, name),\n                            onFailure: promise.failure,\n                            onSuccess: function() {\n                                cancelFinalizationEffort.then(function() {\n                                    self._onCancel(id, name);\n                                });\n\n                                promise.success();\n                            },\n                            identifier: id\n                        });\n\n                        return promise;\n                    },\n                    onUploadPrep: qq.bind(this._onUploadPrep, this),\n                    onUpload: function(id, name) {\n                        self._onUpload(id, name);\n                        var onUploadResult = self._options.callbacks.onUpload(id, name);\n\n                        if (qq.isGenericPromise(onUploadResult)) {\n                            self.log(qq.format(\"onUpload for {} returned a Promise - waiting for resolution.\", id));\n                            return onUploadResult;\n                        }\n\n                        return new qq.Promise().success();\n                    },\n                    onUploadChunk: function(id, name, chunkData) {\n                        self._onUploadChunk(id, chunkData);\n                        var onUploadChunkResult = self._options.callbacks.onUploadChunk(id, name, chunkData);\n\n                        if (qq.isGenericPromise(onUploadChunkResult)) {\n                            self.log(qq.format(\"onUploadChunk for {}.{} returned a Promise - waiting for resolution.\", id, chunkData.partIndex));\n                            return onUploadChunkResult;\n                        }\n\n                        return new qq.Promise().success();\n                    },\n                    onUploadChunkSuccess: function(id, chunkData, result, xhr) {\n                        self._onUploadChunkSuccess(id, chunkData);\n                        self._options.callbacks.onUploadChunkSuccess.apply(self, arguments);\n                    },\n                    onResume: function(id, name, chunkData, customResumeData) {\n                        return self._options.callbacks.onResume(id, name, chunkData, customResumeData);\n                    },\n                    onAutoRetry: function(id, name, responseJSON, xhr) {\n                        return self._onAutoRetry.apply(self, arguments);\n                    },\n                    onUuidChanged: function(id, newUuid) {\n                        self.log(\"Server requested UUID change from '\" + self.getUuid(id) + \"' to '\" + newUuid + \"'\");\n                        self.setUuid(id, newUuid);\n                    },\n                    getName: qq.bind(self.getName, self),\n                    getUuid: qq.bind(self.getUuid, self),\n                    getSize: qq.bind(self.getSize, self),\n                    setSize: qq.bind(self._setSize, self),\n                    getDataByUuid: function(uuid) {\n                        return self.getUploads({uuid: uuid});\n                    },\n                    isQueued: function(id) {\n                        var status = self.getUploads({id: id}).status;\n                        return status === qq.status.QUEUED ||\n                            status === qq.status.SUBMITTED ||\n                            status === qq.status.UPLOAD_RETRYING ||\n                            status === qq.status.PAUSED;\n                    },\n                    getIdsInProxyGroup: self._uploadData.getIdsInProxyGroup,\n                    getIdsInBatch: self._uploadData.getIdsInBatch,\n                    isInProgress: function(id) {\n                        return self.getUploads({id: id}).status === qq.status.UPLOADING;\n                    },\n                    getCustomResumeData: qq.bind(self._getCustomResumeData, self),\n                    setStatus: function(id, status) {\n                        self._uploadData.setStatus(id, status);\n                    }\n                };\n\n            qq.each(this._options.request, function(prop, val) {\n                options[prop] = val;\n            });\n\n            options.customHeaders = this._customHeadersStore;\n\n            if (additionalOptions) {\n                qq.each(additionalOptions, function(key, val) {\n                    options[key] = val;\n                });\n            }\n\n            return new qq.UploadHandlerController(options, namespace);\n        },\n\n        _fileOrBlobRejected: function(id) {\n            this._netUploadedOrQueued--;\n            this._uploadData.setStatus(id, qq.status.REJECTED);\n        },\n\n        _formatSize: function(bytes) {\n            if (bytes === 0) {\n                return bytes + this._options.text.sizeSymbols[0];\n            }\n            var i = -1;\n            do {\n                bytes = bytes / 1000;\n                i++;\n            } while (bytes > 999);\n\n            return Math.max(bytes, 0.1).toFixed(1) + this._options.text.sizeSymbols[i];\n        },\n\n        // Creates an internal object that tracks various properties of each extra button,\n        // and then actually creates the extra button.\n        _generateExtraButtonSpecs: function() {\n            var self = this;\n\n            this._extraButtonSpecs = {};\n\n            qq.each(this._options.extraButtons, function(idx, extraButtonOptionEntry) {\n                var multiple = extraButtonOptionEntry.multiple,\n                    validation = qq.extend({}, self._options.validation, true),\n                    extraButtonSpec = qq.extend({}, extraButtonOptionEntry);\n\n                if (multiple === undefined) {\n                    multiple = self._options.multiple;\n                }\n\n                if (extraButtonSpec.validation) {\n                    qq.extend(validation, extraButtonOptionEntry.validation, true);\n                }\n\n                qq.extend(extraButtonSpec, {\n                    multiple: multiple,\n                    validation: validation\n                }, true);\n\n                self._initExtraButton(extraButtonSpec);\n            });\n        },\n\n        _getButton: function(buttonId) {\n            var extraButtonsSpec = this._extraButtonSpecs[buttonId];\n\n            if (extraButtonsSpec) {\n                return extraButtonsSpec.element;\n            }\n            else if (buttonId === this._defaultButtonId) {\n                return this._options.button;\n            }\n        },\n\n        /**\n         * Gets the internally used tracking ID for a button.\n         *\n         * @param buttonOrFileInputOrFile `File`, `<input type=\"file\">`, or a button container element\n         * @returns {*} The button's ID, or undefined if no ID is recoverable\n         * @private\n         */\n        _getButtonId: function(buttonOrFileInputOrFile) {\n            var inputs, fileInput,\n                fileBlobOrInput = buttonOrFileInputOrFile;\n\n            // We want the reference file/blob here if this is a proxy (a file that will be generated on-demand later)\n            if (fileBlobOrInput instanceof qq.BlobProxy) {\n                fileBlobOrInput = fileBlobOrInput.referenceBlob;\n            }\n\n            // If the item is a `Blob` it will never be associated with a button or drop zone.\n            if (fileBlobOrInput && !qq.isBlob(fileBlobOrInput)) {\n                if (qq.isFile(fileBlobOrInput)) {\n                    return fileBlobOrInput.qqButtonId;\n                }\n                else if (fileBlobOrInput.tagName.toLowerCase() === \"input\" &&\n                    fileBlobOrInput.type.toLowerCase() === \"file\") {\n\n                    return fileBlobOrInput.getAttribute(qq.UploadButton.BUTTON_ID_ATTR_NAME);\n                }\n\n                inputs = fileBlobOrInput.getElementsByTagName(\"input\");\n\n                qq.each(inputs, function(idx, input) {\n                    if (input.getAttribute(\"type\") === \"file\") {\n                        fileInput = input;\n                        return false;\n                    }\n                });\n\n                if (fileInput) {\n                    return fileInput.getAttribute(qq.UploadButton.BUTTON_ID_ATTR_NAME);\n                }\n            }\n        },\n\n        _getCustomResumeData: function(fileId) {\n            return this._customResumeDataStore.get(fileId);\n        },\n\n        _getNotFinished: function() {\n            return this._uploadData.retrieve({\n                status: [\n                    qq.status.UPLOADING,\n                    qq.status.UPLOAD_RETRYING,\n                    qq.status.QUEUED,\n                    qq.status.SUBMITTING,\n                    qq.status.SUBMITTED,\n                    qq.status.PAUSED\n                ]\n            }).length;\n        },\n\n        // Get the validation options for this button.  Could be the default validation option\n        // or a specific one assigned to this particular button.\n        _getValidationBase: function(buttonId) {\n            var extraButtonSpec = this._extraButtonSpecs[buttonId];\n\n            return extraButtonSpec ? extraButtonSpec.validation : this._options.validation;\n        },\n\n        _getValidationDescriptor: function(fileWrapper) {\n            if (fileWrapper.file instanceof qq.BlobProxy) {\n                return {\n                    name: qq.getFilename(fileWrapper.file.referenceBlob),\n                    size: fileWrapper.file.referenceBlob.size\n                };\n            }\n\n            return {\n                name: this.getUploads({id: fileWrapper.id}).name,\n                size: this.getUploads({id: fileWrapper.id}).size\n            };\n        },\n\n        _getValidationDescriptors: function(fileWrappers) {\n            var self = this,\n                fileDescriptors = [];\n\n            qq.each(fileWrappers, function(idx, fileWrapper) {\n                fileDescriptors.push(self._getValidationDescriptor(fileWrapper));\n            });\n\n            return fileDescriptors;\n        },\n\n        // Allows camera access on either the default or an extra button for iOS devices.\n        _handleCameraAccess: function() {\n            if (this._options.camera.ios && qq.ios()) {\n                var acceptIosCamera = \"image/*;capture=camera\",\n                    button = this._options.camera.button,\n                    buttonId = button ? this._getButtonId(button) : this._defaultButtonId,\n                    optionRoot = this._options;\n\n                // If we are not targeting the default button, it is an \"extra\" button\n                if (buttonId && buttonId !== this._defaultButtonId) {\n                    optionRoot = this._extraButtonSpecs[buttonId];\n                }\n\n                // Camera access won't work in iOS if the `multiple` attribute is present on the file input\n                optionRoot.multiple = false;\n\n                // update the options\n                if (optionRoot.validation.acceptFiles === null) {\n                    optionRoot.validation.acceptFiles = acceptIosCamera;\n                }\n                else {\n                    optionRoot.validation.acceptFiles += \",\" + acceptIosCamera;\n                }\n\n                // update the already-created button\n                qq.each(this._buttons, function(idx, button) {\n                    if (button.getButtonId() === buttonId) {\n                        button.setMultiple(optionRoot.multiple);\n                        button.setAcceptFiles(optionRoot.acceptFiles);\n\n                        return false;\n                    }\n                });\n            }\n        },\n\n        _handleCheckedCallback: function(details) {\n            var self = this,\n                callbackRetVal = details.callback();\n\n            if (qq.isGenericPromise(callbackRetVal)) {\n                this.log(details.name + \" - waiting for \" + details.name + \" promise to be fulfilled for \" + details.identifier);\n                return callbackRetVal.then(\n                    function(successParam) {\n                        self.log(details.name + \" promise success for \" + details.identifier);\n                        details.onSuccess(successParam);\n                    },\n                    function() {\n                        if (details.onFailure) {\n                            self.log(details.name + \" promise failure for \" + details.identifier);\n                            details.onFailure();\n                        }\n                        else {\n                            self.log(details.name + \" promise failure for \" + details.identifier);\n                        }\n                    });\n            }\n\n            if (callbackRetVal !== false) {\n                details.onSuccess(callbackRetVal);\n            }\n            else {\n                if (details.onFailure) {\n                    this.log(details.name + \" - return value was 'false' for \" + details.identifier + \".  Invoking failure callback.\");\n                    details.onFailure();\n                }\n                else {\n                    this.log(details.name + \" - return value was 'false' for \" + details.identifier + \".  Will not proceed.\");\n                }\n            }\n\n            return callbackRetVal;\n        },\n\n        // Updates internal state when a new file has been received, and adds it along with its ID to a passed array.\n        _handleNewFile: function(file, batchId, newFileWrapperList) {\n            var self = this,\n                uuid = qq.getUniqueId(),\n                size = -1,\n                name = qq.getFilename(file),\n                actualFile = file.blob || file,\n                handler = this._customNewFileHandler ?\n                    this._customNewFileHandler :\n                    qq.bind(self._handleNewFileGeneric, self);\n\n            if (!qq.isInput(actualFile) && actualFile.size >= 0) {\n                size = actualFile.size;\n            }\n\n            handler(actualFile, name, uuid, size, newFileWrapperList, batchId, this._options.request.uuidName, {\n                uploadData: self._uploadData,\n                paramsStore: self._paramsStore,\n                addFileToHandler: function(id, file) {\n                    self._handler.add(id, file);\n                    self._netUploadedOrQueued++;\n                    self._trackButton(id);\n                }\n            });\n        },\n\n        _handleNewFileGeneric: function(file, name, uuid, size, fileList, batchId) {\n            var id = this._uploadData.addFile({\n                uuid: uuid,\n                name: name,\n                size: size,\n                batchId: batchId,\n                file: file\n            });\n\n            this._handler.add(id, file);\n\n            this._trackButton(id);\n\n            this._netUploadedOrQueued++;\n\n            fileList.push({id: id, file: file});\n        },\n\n        _handlePasteSuccess: function(blob, extSuppliedName) {\n            var extension = blob.type.split(\"/\")[1],\n                name = extSuppliedName;\n\n            /*jshint eqeqeq: true, eqnull: true*/\n            if (name == null) {\n                name = this._options.paste.defaultName;\n            }\n\n            name += \".\" + extension;\n\n            this.addFiles({\n                name: name,\n                blob: blob\n            });\n        },\n\n        _handleDeleteSuccess: function(id) {\n            if (this.getUploads({id: id}).status !== qq.status.DELETED) {\n                var name = this.getName(id);\n\n                this._netUploadedOrQueued--;\n                this._netUploaded--;\n                this._handler.expunge(id);\n                this._uploadData.setStatus(id, qq.status.DELETED);\n                this.log(\"Delete request for '\" + name + \"' has succeeded.\");\n            }\n        },\n\n        _handleDeleteFailed: function(id, xhrOrXdr) {\n            var name = this.getName(id);\n\n            this._uploadData.setStatus(id, qq.status.DELETE_FAILED);\n            this.log(\"Delete request for '\" + name + \"' has failed.\", \"error\");\n\n            // Check first if xhrOrXdr is actually passed or valid\n            // For error reporting, we only have access to the response status if this is not\n            // an `XDomainRequest`.\n            if (!xhrOrXdr || xhrOrXdr.withCredentials === undefined) {\n                this._options.callbacks.onError(id, name, \"Delete request failed\", xhrOrXdr);\n            }\n            else {\n                this._options.callbacks.onError(id, name, \"Delete request failed with response code \" + xhrOrXdr.status, xhrOrXdr);\n            }\n        },\n\n        // Creates an extra button element\n        _initExtraButton: function(spec) {\n            var button = this._createUploadButton({\n                accept: spec.validation.acceptFiles,\n                allowedExtensions: spec.validation.allowedExtensions,\n                element: spec.element,\n                folders: spec.folders,\n                multiple: spec.multiple,\n                title: spec.fileInputTitle\n            });\n\n            this._extraButtonSpecs[button.getButtonId()] = spec;\n        },\n\n        _initFormSupportAndParams: function() {\n            this._formSupport = qq.FormSupport && new qq.FormSupport(\n                this._options.form, qq.bind(this.uploadStoredFiles, this), qq.bind(this.log, this)\n            );\n\n            if (this._formSupport && this._formSupport.attachedToForm) {\n                this._paramsStore = this._createStore(\n                    this._options.request.params,  this._formSupport.getFormInputsAsObject\n                );\n\n                this._options.autoUpload = this._formSupport.newAutoUpload;\n                if (this._formSupport.newEndpoint) {\n                    this._options.request.endpoint = this._formSupport.newEndpoint;\n                }\n            }\n            else {\n                this._paramsStore = this._createStore(this._options.request.params);\n            }\n        },\n\n        _isDeletePossible: function() {\n            if (!qq.DeleteFileAjaxRequester || !this._options.deleteFile.enabled) {\n                return false;\n            }\n\n            if (this._options.cors.expected) {\n                if (qq.supportedFeatures.deleteFileCorsXhr) {\n                    return true;\n                }\n\n                if (qq.supportedFeatures.deleteFileCorsXdr && this._options.cors.allowXdr) {\n                    return true;\n                }\n\n                return false;\n            }\n\n            return true;\n        },\n\n        _isAllowedExtension: function(allowed, fileName) {\n            var valid = false;\n\n            if (!allowed.length) {\n                return true;\n            }\n\n            qq.each(allowed, function(idx, allowedExt) {\n                /**\n                 * If an argument is not a string, ignore it.  Added when a possible issue with MooTools hijacking the\n                 * `allowedExtensions` array was discovered.  See case #735 in the issue tracker for more details.\n                 */\n                if (qq.isString(allowedExt)) {\n                    /*jshint eqeqeq: true, eqnull: true*/\n                    var extRegex = new RegExp(\"\\\\.\" + allowedExt + \"$\", \"i\");\n\n                    if (fileName.match(extRegex) != null) {\n                        valid = true;\n                        return false;\n                    }\n                }\n            });\n\n            return valid;\n        },\n\n        /**\n         * Constructs and returns a message that describes an item/file error.  Also calls `onError` callback.\n         *\n         * @param code REQUIRED - a code that corresponds to a stock message describing this type of error\n         * @param maybeNameOrNames names of the items that have failed, if applicable\n         * @param item `File`, `Blob`, or `<input type=\"file\">`\n         * @private\n         */\n        _itemError: function(code, maybeNameOrNames, item) {\n            var message = this._options.messages[code],\n                allowedExtensions = [],\n                names = [].concat(maybeNameOrNames),\n                name = names[0],\n                buttonId = this._getButtonId(item),\n                validationBase = this._getValidationBase(buttonId),\n                extensionsForMessage, placeholderMatch;\n\n            function r(name, replacement) { message = message.replace(name, replacement); }\n\n            qq.each(validationBase.allowedExtensions, function(idx, allowedExtension) {\n                /**\n                 * If an argument is not a string, ignore it.  Added when a possible issue with MooTools hijacking the\n                 * `allowedExtensions` array was discovered.  See case #735 in the issue tracker for more details.\n                 */\n                if (qq.isString(allowedExtension)) {\n                    allowedExtensions.push(allowedExtension);\n                }\n            });\n\n            extensionsForMessage = allowedExtensions.join(\", \").toLowerCase();\n\n            r(\"{file}\", this._options.formatFileName(name));\n            r(\"{extensions}\", extensionsForMessage);\n            r(\"{sizeLimit}\", this._formatSize(validationBase.sizeLimit));\n            r(\"{minSizeLimit}\", this._formatSize(validationBase.minSizeLimit));\n\n            placeholderMatch = message.match(/(\\{\\w+\\})/g);\n            if (placeholderMatch !== null) {\n                qq.each(placeholderMatch, function(idx, placeholder) {\n                    r(placeholder, names[idx]);\n                });\n            }\n\n            this._options.callbacks.onError(null, name, message, undefined);\n\n            return message;\n        },\n\n        /**\n         * Conditionally orders a manual retry of a failed upload.\n         *\n         * @param id File ID of the failed upload\n         * @param callback Optional callback to invoke if a retry is prudent.\n         * In lieu of asking the upload handler to retry.\n         * @returns {boolean} true if a manual retry will occur\n         * @private\n         */\n        _manualRetry: function(id, callback) {\n            if (this._onBeforeManualRetry(id)) {\n                this._netUploadedOrQueued++;\n                this._uploadData.setStatus(id, qq.status.UPLOAD_RETRYING);\n\n                if (callback) {\n                    callback(id);\n                }\n                else {\n                    this._handler.retry(id);\n                }\n\n                return true;\n            }\n        },\n\n        _maybeAllComplete: function(id, status) {\n            var self = this,\n                notFinished = this._getNotFinished();\n\n            if (status === qq.status.UPLOAD_SUCCESSFUL) {\n                this._succeededSinceLastAllComplete.push(id);\n            }\n            else if (status === qq.status.UPLOAD_FAILED) {\n                this._failedSinceLastAllComplete.push(id);\n            }\n\n            if (notFinished === 0 &&\n                (this._succeededSinceLastAllComplete.length || this._failedSinceLastAllComplete.length)) {\n                // Attempt to ensure onAllComplete is not invoked before other callbacks, such as onCancel & onComplete\n                setTimeout(function() {\n                    self._onAllComplete(self._succeededSinceLastAllComplete, self._failedSinceLastAllComplete);\n                }, 0);\n            }\n        },\n\n        _maybeHandleIos8SafariWorkaround: function() {\n            var self = this;\n\n            if (this._options.workarounds.ios8SafariUploads && qq.ios800() && qq.iosSafari()) {\n                setTimeout(function() {\n                    window.alert(self._options.messages.unsupportedBrowserIos8Safari);\n                }, 0);\n                throw new qq.Error(this._options.messages.unsupportedBrowserIos8Safari);\n            }\n        },\n\n        _maybeParseAndSendUploadError: function(id, name, response, xhr) {\n            // Assuming no one will actually set the response code to something other than 200\n            // and still set 'success' to true...\n            if (!response.success) {\n                if (xhr && xhr.status !== 200 && !response.error) {\n                    this._options.callbacks.onError(id, name, \"XHR returned response code \" + xhr.status, xhr);\n                }\n                else {\n                    var errorReason = response.error ? response.error : this._options.text.defaultResponseError;\n                    this._options.callbacks.onError(id, name, errorReason, xhr);\n                }\n            }\n        },\n\n        _maybeProcessNextItemAfterOnValidateCallback: function(validItem, items, index, params, endpoint) {\n            var self = this;\n\n            if (items.length > index) {\n                if (validItem || !this._options.validation.stopOnFirstInvalidFile) {\n                    //use setTimeout to prevent a stack overflow with a large number of files in the batch & non-promissory callbacks\n                    setTimeout(function() {\n                        var validationDescriptor = self._getValidationDescriptor(items[index]),\n                            buttonId = self._getButtonId(items[index].file),\n                            button = self._getButton(buttonId);\n\n                        self._handleCheckedCallback({\n                            name: \"onValidate\",\n                            callback: qq.bind(self._options.callbacks.onValidate, self, validationDescriptor, button),\n                            onSuccess: qq.bind(self._onValidateCallbackSuccess, self, items, index, params, endpoint),\n                            onFailure: qq.bind(self._onValidateCallbackFailure, self, items, index, params, endpoint),\n                            identifier: \"Item '\" + validationDescriptor.name + \"', size: \" + validationDescriptor.size\n                        });\n                    }, 0);\n                }\n                else if (!validItem) {\n                    for (; index < items.length; index++) {\n                        self._fileOrBlobRejected(items[index].id);\n                    }\n                }\n            }\n        },\n\n        _onAllComplete: function(successful, failed) {\n            this._totalProgress && this._totalProgress.onAllComplete(successful, failed, this._preventRetries);\n\n            this._options.callbacks.onAllComplete(qq.extend([], successful), qq.extend([], failed));\n\n            this._succeededSinceLastAllComplete = [];\n            this._failedSinceLastAllComplete = [];\n        },\n\n        /**\n         * Attempt to automatically retry a failed upload.\n         *\n         * @param id The file ID of the failed upload\n         * @param name The name of the file associated with the failed upload\n         * @param responseJSON Response from the server, parsed into a javascript object\n         * @param xhr Ajax transport used to send the failed request\n         * @param callback Optional callback to be invoked if a retry is prudent.\n         * Invoked in lieu of asking the upload handler to retry.\n         * @returns {boolean} true if an auto-retry will occur\n         * @private\n         */\n        _onAutoRetry: function(id, name, responseJSON, xhr, callback) {\n            var self = this;\n\n            self._preventRetries[id] = responseJSON[self._options.retry.preventRetryResponseProperty];\n\n            if (self._shouldAutoRetry(id)) {\n                var retryWaitPeriod = self._options.retry.autoAttemptDelay * 1000;\n\n                self._maybeParseAndSendUploadError.apply(self, arguments);\n                self._options.callbacks.onAutoRetry(id, name, self._autoRetries[id]);\n                self._onBeforeAutoRetry(id, name);\n\n                self._uploadData.setStatus(id, qq.status.UPLOAD_RETRYING);\n                self._retryTimeouts[id] = setTimeout(function() {\n                    self.log(\"Starting retry for \" + name + \"...\");\n\n                    if (callback) {\n                        callback(id);\n                    }\n                    else {\n                        self._handler.retry(id);\n                    }\n                }, retryWaitPeriod);\n\n                return true;\n            }\n        },\n\n        _onBeforeAutoRetry: function(id, name) {\n            this.log(\"Waiting \" + this._options.retry.autoAttemptDelay + \" seconds before retrying \" + name + \"...\");\n        },\n\n        //return false if we should not attempt the requested retry\n        _onBeforeManualRetry: function(id) {\n            var itemLimit = this._currentItemLimit,\n                fileName;\n\n            if (this._preventRetries[id]) {\n                this.log(\"Retries are forbidden for id \" + id, \"warn\");\n                return false;\n            }\n            else if (this._handler.isValid(id)) {\n                fileName = this.getName(id);\n\n                if (this._options.callbacks.onManualRetry(id, fileName) === false) {\n                    return false;\n                }\n\n                if (itemLimit > 0 && this._netUploadedOrQueued + 1 > itemLimit) {\n                    this._itemError(\"retryFailTooManyItems\");\n                    return false;\n                }\n\n                this.log(\"Retrying upload for '\" + fileName + \"' (id: \" + id + \")...\");\n                return true;\n            }\n            else {\n                this.log(\"'\" + id + \"' is not a valid file ID\", \"error\");\n                return false;\n            }\n        },\n\n        _onCancel: function(id, name) {\n            this._netUploadedOrQueued--;\n\n            clearTimeout(this._retryTimeouts[id]);\n\n            var storedItemIndex = qq.indexOf(this._storedIds, id);\n            if (!this._options.autoUpload && storedItemIndex >= 0) {\n                this._storedIds.splice(storedItemIndex, 1);\n            }\n\n            this._uploadData.setStatus(id, qq.status.CANCELED);\n        },\n\n        _onComplete: function(id, name, result, xhr) {\n            if (!result.success) {\n                this._netUploadedOrQueued--;\n                this._uploadData.setStatus(id, qq.status.UPLOAD_FAILED);\n\n                if (result[this._options.retry.preventRetryResponseProperty] === true) {\n                    this._preventRetries[id] = true;\n                }\n            }\n            else {\n                if (result.thumbnailUrl) {\n                    this._thumbnailUrls[id] = result.thumbnailUrl;\n                }\n\n                this._netUploaded++;\n                this._uploadData.setStatus(id, qq.status.UPLOAD_SUCCESSFUL);\n            }\n\n            this._maybeParseAndSendUploadError(id, name, result, xhr);\n\n            return result.success ? true : false;\n        },\n\n        _onDelete: function(id) {\n            this._uploadData.setStatus(id, qq.status.DELETING);\n        },\n\n        _onDeleteComplete: function(id, xhrOrXdr, isError) {\n            var name = this.getName(id);\n\n            if (isError) {\n                this._handleDeleteFailed(id, xhrOrXdr);\n            }\n            else {\n                this._handleDeleteSuccess(id);\n            }\n        },\n\n        _onInputChange: function(input) {\n            var fileIndex;\n\n            if (qq.supportedFeatures.ajaxUploading) {\n                for (fileIndex = 0; fileIndex < input.files.length; fileIndex++) {\n                    this._annotateWithButtonId(input.files[fileIndex], input);\n                }\n\n                this.addFiles(input.files);\n            }\n            // Android 2.3.x will fire `onchange` even if no file has been selected\n            else if (input.value.length > 0) {\n                this.addFiles(input);\n            }\n\n            qq.each(this._buttons, function(idx, button) {\n                button.reset();\n            });\n        },\n\n        _onProgress: function(id, name, loaded, total) {\n            this._totalProgress && this._totalProgress.onIndividualProgress(id, loaded, total);\n        },\n\n        _onSubmit: function(id, name) {\n            //nothing to do yet in core uploader\n        },\n\n        _onSubmitCallbackSuccess: function(id, name) {\n            this._onSubmit.apply(this, arguments);\n            this._uploadData.setStatus(id, qq.status.SUBMITTED);\n            this._onSubmitted.apply(this, arguments);\n\n            if (this._options.autoUpload) {\n                this._options.callbacks.onSubmitted.apply(this, arguments);\n                this._uploadFile(id);\n            }\n            else {\n                this._storeForLater(id);\n                this._options.callbacks.onSubmitted.apply(this, arguments);\n            }\n        },\n\n        _onSubmitDelete: function(id, onSuccessCallback, additionalMandatedParams) {\n            var uuid = this.getUuid(id),\n                adjustedOnSuccessCallback;\n\n            if (onSuccessCallback) {\n                adjustedOnSuccessCallback = qq.bind(onSuccessCallback, this, id, uuid, additionalMandatedParams);\n            }\n\n            if (this._isDeletePossible()) {\n                this._handleCheckedCallback({\n                    name: \"onSubmitDelete\",\n                    callback: qq.bind(this._options.callbacks.onSubmitDelete, this, id),\n                    onSuccess: adjustedOnSuccessCallback ||\n                        qq.bind(this._deleteHandler.sendDelete, this, id, uuid, additionalMandatedParams),\n                    identifier: id\n                });\n                return true;\n            }\n            else {\n                this.log(\"Delete request ignored for ID \" + id + \", delete feature is disabled or request not possible \" +\n                    \"due to CORS on a user agent that does not support pre-flighting.\", \"warn\");\n                return false;\n            }\n        },\n\n        _onSubmitted: function(id) {\n            //nothing to do in the base uploader\n        },\n\n        _onTotalProgress: function(loaded, total) {\n            this._options.callbacks.onTotalProgress(loaded, total);\n        },\n\n        _onUploadPrep: function(id) {\n            // nothing to do in the core uploader for now\n        },\n\n        _onUpload: function(id, name) {\n            this._uploadData.setStatus(id, qq.status.UPLOADING);\n        },\n\n        _onUploadChunk: function(id, chunkData) {\n            //nothing to do in the base uploader\n        },\n\n        _onUploadChunkSuccess: function(id, chunkData) {\n            if (!this._preventRetries[id] && this._options.retry.enableAuto) {\n                this._autoRetries[id] = 0;\n            }\n        },\n\n        _onUploadStatusChange: function(id, oldStatus, newStatus) {\n            // Make sure a \"queued\" retry attempt is canceled if the upload has been paused\n            if (newStatus === qq.status.PAUSED) {\n                clearTimeout(this._retryTimeouts[id]);\n            }\n        },\n\n        _onValidateBatchCallbackFailure: function(fileWrappers) {\n            var self = this;\n\n            qq.each(fileWrappers, function(idx, fileWrapper) {\n                self._fileOrBlobRejected(fileWrapper.id);\n            });\n        },\n\n        _onValidateBatchCallbackSuccess: function(validationDescriptors, items, params, endpoint, button) {\n            var errorMessage,\n                itemLimit = this._currentItemLimit,\n                proposedNetFilesUploadedOrQueued = this._netUploadedOrQueued;\n\n            if (itemLimit === 0 || proposedNetFilesUploadedOrQueued <= itemLimit) {\n                if (items.length > 0) {\n                    this._handleCheckedCallback({\n                        name: \"onValidate\",\n                        callback: qq.bind(this._options.callbacks.onValidate, this, validationDescriptors[0], button),\n                        onSuccess: qq.bind(this._onValidateCallbackSuccess, this, items, 0, params, endpoint),\n                        onFailure: qq.bind(this._onValidateCallbackFailure, this, items, 0, params, endpoint),\n                        identifier: \"Item '\" + items[0].file.name + \"', size: \" + items[0].file.size\n                    });\n                }\n                else {\n                    this._itemError(\"noFilesError\");\n                }\n            }\n            else {\n                this._onValidateBatchCallbackFailure(items);\n                errorMessage = this._options.messages.tooManyItemsError\n                    .replace(/\\{netItems\\}/g, proposedNetFilesUploadedOrQueued)\n                    .replace(/\\{itemLimit\\}/g, itemLimit);\n                this._batchError(errorMessage);\n            }\n        },\n\n        _onValidateCallbackFailure: function(items, index, params, endpoint) {\n            var nextIndex = index + 1;\n\n            this._fileOrBlobRejected(items[index].id, items[index].file.name);\n\n            this._maybeProcessNextItemAfterOnValidateCallback(false, items, nextIndex, params, endpoint);\n        },\n\n        _onValidateCallbackSuccess: function(items, index, params, endpoint) {\n            var self = this,\n                nextIndex = index + 1,\n                validationDescriptor = this._getValidationDescriptor(items[index]);\n\n            this._validateFileOrBlobData(items[index], validationDescriptor)\n                .then(\n                function() {\n                    self._upload(items[index].id, params, endpoint);\n                    self._maybeProcessNextItemAfterOnValidateCallback(true, items, nextIndex, params, endpoint);\n                },\n                function() {\n                    self._maybeProcessNextItemAfterOnValidateCallback(false, items, nextIndex, params, endpoint);\n                }\n            );\n        },\n\n        _prepareItemsForUpload: function(items, params, endpoint) {\n            if (items.length === 0) {\n                this._itemError(\"noFilesError\");\n                return;\n            }\n\n            var validationDescriptors = this._getValidationDescriptors(items),\n                buttonId = this._getButtonId(items[0].file),\n                button = this._getButton(buttonId);\n\n            this._handleCheckedCallback({\n                name: \"onValidateBatch\",\n                callback: qq.bind(this._options.callbacks.onValidateBatch, this, validationDescriptors, button),\n                onSuccess: qq.bind(this._onValidateBatchCallbackSuccess, this, validationDescriptors, items, params, endpoint, button),\n                onFailure: qq.bind(this._onValidateBatchCallbackFailure, this, items),\n                identifier: \"batch validation\"\n            });\n        },\n\n        _preventLeaveInProgress: function() {\n            var self = this;\n\n            this._disposeSupport.attach(window, \"beforeunload\", function(e) {\n                if (self.getInProgress()) {\n                    e = e || window.event;\n                    // for ie, ff\n                    e.returnValue = self._options.messages.onLeave;\n                    // for webkit\n                    return self._options.messages.onLeave;\n                }\n            });\n        },\n\n        // Attempts to refresh session data only if the `qq.Session` module exists\n        // and a session endpoint has been specified.  The `onSessionRequestComplete`\n        // callback will be invoked once the refresh is complete.\n        _refreshSessionData: function() {\n            var self = this,\n                options = this._options.session;\n\n            /* jshint eqnull:true */\n            if (qq.Session && this._options.session.endpoint != null) {\n                if (!this._session) {\n                    qq.extend(options, {cors: this._options.cors});\n\n                    options.log = qq.bind(this.log, this);\n                    options.addFileRecord = qq.bind(this._addCannedFile, this);\n\n                    this._session = new qq.Session(options);\n                }\n\n                setTimeout(function() {\n                    self._session.refresh().then(function(response, xhrOrXdr) {\n                        self._sessionRequestComplete();\n                        self._options.callbacks.onSessionRequestComplete(response, true, xhrOrXdr);\n\n                    }, function(response, xhrOrXdr) {\n\n                        self._options.callbacks.onSessionRequestComplete(response, false, xhrOrXdr);\n                    });\n                }, 0);\n            }\n        },\n\n        _sessionRequestComplete: function() {},\n\n        _setSize: function(id, newSize) {\n            this._uploadData.updateSize(id, newSize);\n            this._totalProgress && this._totalProgress.onNewSize(id);\n        },\n\n        _shouldAutoRetry: function(id) {\n            var uploadData = this._uploadData.retrieve({id: id});\n\n            /*jshint laxbreak: true */\n            if (!this._preventRetries[id]\n                && this._options.retry.enableAuto\n                && uploadData.status !== qq.status.PAUSED) {\n\n                if (this._autoRetries[id] === undefined) {\n                    this._autoRetries[id] = 0;\n                }\n\n                if (this._autoRetries[id] < this._options.retry.maxAutoAttempts) {\n                    this._autoRetries[id] += 1;\n                    return true;\n                }\n            }\n\n            return false;\n        },\n\n        _storeForLater: function(id) {\n            this._storedIds.push(id);\n        },\n\n        // Maps a file with the button that was used to select it.\n        _trackButton: function(id) {\n            var buttonId;\n\n            if (qq.supportedFeatures.ajaxUploading) {\n                buttonId = this._handler.getFile(id).qqButtonId;\n            }\n            else {\n                buttonId = this._getButtonId(this._handler.getInput(id));\n            }\n\n            if (buttonId) {\n                this._buttonIdsForFileIds[id] = buttonId;\n            }\n        },\n\n        _updateFormSupportAndParams: function(formElementOrId) {\n            this._options.form.element = formElementOrId;\n\n            this._formSupport = qq.FormSupport && new qq.FormSupport(\n                    this._options.form, qq.bind(this.uploadStoredFiles, this), qq.bind(this.log, this)\n                );\n\n            if (this._formSupport && this._formSupport.attachedToForm) {\n                this._paramsStore.addReadOnly(null, this._formSupport.getFormInputsAsObject);\n\n                this._options.autoUpload = this._formSupport.newAutoUpload;\n                if (this._formSupport.newEndpoint) {\n                    this.setEndpoint(this._formSupport.newEndpoint);\n                }\n            }\n        },\n\n        _upload: function(id, params, endpoint) {\n            var name = this.getName(id);\n\n            if (params) {\n                this.setParams(params, id);\n            }\n\n            if (endpoint) {\n                this.setEndpoint(endpoint, id);\n            }\n\n            this._handleCheckedCallback({\n                name: \"onSubmit\",\n                callback: qq.bind(this._options.callbacks.onSubmit, this, id, name),\n                onSuccess: qq.bind(this._onSubmitCallbackSuccess, this, id, name),\n                onFailure: qq.bind(this._fileOrBlobRejected, this, id, name),\n                identifier: id\n            });\n        },\n\n        _uploadFile: function(id) {\n            if (!this._handler.upload(id)) {\n                this._uploadData.setStatus(id, qq.status.QUEUED);\n            }\n        },\n\n        _uploadStoredFiles: function() {\n            var idToUpload, stillSubmitting,\n                self = this;\n\n            while (this._storedIds.length) {\n                idToUpload = this._storedIds.shift();\n                this._uploadFile(idToUpload);\n            }\n\n            // If we are still waiting for some files to clear validation, attempt to upload these again in a bit\n            stillSubmitting = this.getUploads({status: qq.status.SUBMITTING}).length;\n            if (stillSubmitting) {\n                qq.log(\"Still waiting for \" + stillSubmitting + \" files to clear submit queue. Will re-parse stored IDs array shortly.\");\n                setTimeout(function() {\n                    self._uploadStoredFiles();\n                }, 1000);\n            }\n        },\n\n        /**\n         * Performs some internal validation checks on an item, defined in the `validation` option.\n         *\n         * @param fileWrapper Wrapper containing a `file` along with an `id`\n         * @param validationDescriptor Normalized information about the item (`size`, `name`).\n         * @returns qq.Promise with appropriate callbacks invoked depending on the validity of the file\n         * @private\n         */\n        _validateFileOrBlobData: function(fileWrapper, validationDescriptor) {\n            var self = this,\n                file = (function() {\n                    if (fileWrapper.file instanceof qq.BlobProxy) {\n                        return fileWrapper.file.referenceBlob;\n                    }\n                    return fileWrapper.file;\n                }()),\n                name = validationDescriptor.name,\n                size = validationDescriptor.size,\n                buttonId = this._getButtonId(fileWrapper.file),\n                validationBase = this._getValidationBase(buttonId),\n                validityChecker = new qq.Promise();\n\n            validityChecker.then(\n                function() {},\n                function() {\n                    self._fileOrBlobRejected(fileWrapper.id, name);\n                });\n\n            if (qq.isFileOrInput(file) && !this._isAllowedExtension(validationBase.allowedExtensions, name)) {\n                this._itemError(\"typeError\", name, file);\n                return validityChecker.failure();\n            }\n\n            if (!this._options.validation.allowEmpty && size === 0) {\n                this._itemError(\"emptyError\", name, file);\n                return validityChecker.failure();\n            }\n\n            if (size > 0 && validationBase.sizeLimit && size > validationBase.sizeLimit) {\n                this._itemError(\"sizeError\", name, file);\n                return validityChecker.failure();\n            }\n\n            if (size > 0 && size < validationBase.minSizeLimit) {\n                this._itemError(\"minSizeError\", name, file);\n                return validityChecker.failure();\n            }\n\n            if (qq.ImageValidation && qq.supportedFeatures.imagePreviews && qq.isFile(file)) {\n                new qq.ImageValidation(file, qq.bind(self.log, self)).validate(validationBase.image).then(\n                    validityChecker.success,\n                    function(errorCode) {\n                        self._itemError(errorCode + \"ImageError\", name, file);\n                        validityChecker.failure();\n                    }\n                );\n            }\n            else {\n                validityChecker.success();\n            }\n\n            return validityChecker;\n        },\n\n        _wrapCallbacks: function() {\n            var self, safeCallback, prop;\n\n            self = this;\n\n            safeCallback = function(name, callback, args) {\n                var errorMsg;\n\n                try {\n                    return callback.apply(self, args);\n                }\n                catch (exception) {\n                    errorMsg = exception.message || exception.toString();\n                    self.log(\"Caught exception in '\" + name + \"' callback - \" + errorMsg, \"error\");\n                }\n            };\n\n            /* jshint forin: false, loopfunc: true */\n            for (prop in this._options.callbacks) {\n                (function() {\n                    var callbackName, callbackFunc;\n                    callbackName = prop;\n                    callbackFunc = self._options.callbacks[callbackName];\n                    self._options.callbacks[callbackName] = function() {\n                        return safeCallback(callbackName, callbackFunc, arguments);\n                    };\n                }());\n            }\n        }\n    };\n}());\n"
  },
  {
    "path": "client/js/uploader.basic.js",
    "content": "/*globals qq*/\n(function() {\n    \"use strict\";\n\n    qq.FineUploaderBasic = function(o) {\n        var self = this;\n\n        // These options define FineUploaderBasic mode.\n        this._options = {\n            debug: false,\n            button: null,\n            multiple: true,\n            maxConnections: 3,\n            disableCancelForFormUploads: false,\n            autoUpload: true,\n            warnBeforeUnload: true,\n\n            request: {\n                customHeaders: {},\n                endpoint: \"/server/upload\",\n                filenameParam: \"qqfilename\",\n                forceMultipart: true,\n                inputName: \"qqfile\",\n                method: \"POST\",\n                omitDefaultParams: false,\n                params: {},\n                paramsInBody: true,\n                requireSuccessJson: true,\n                totalFileSizeName: \"qqtotalfilesize\",\n                uuidName: \"qquuid\"\n            },\n\n            validation: {\n                allowedExtensions: [],\n                sizeLimit: 0,\n                minSizeLimit: 0,\n                itemLimit: 0,\n                stopOnFirstInvalidFile: true,\n                acceptFiles: null,\n                image: {\n                    maxHeight: 0,\n                    maxWidth: 0,\n                    minHeight: 0,\n                    minWidth: 0\n                },\n                allowEmpty: false\n            },\n\n            callbacks: {\n                onSubmit: function(id, name) {},\n                onSubmitted: function(id, name) {},\n                onComplete: function(id, name, responseJSON, maybeXhr) {},\n                onAllComplete: function(successful, failed) {},\n                onCancel: function(id, name) {},\n                onUpload: function(id, name) {},\n                onUploadChunk: function(id, name, chunkData) {},\n                onUploadChunkSuccess: function(id, chunkData, responseJSON, xhr) {},\n                onResume: function(id, fileName, chunkData, customResumeData) {},\n                onProgress: function(id, name, loaded, total) {},\n                onTotalProgress: function(loaded, total) {},\n                onError: function(id, name, reason, maybeXhrOrXdr) {},\n                onAutoRetry: function(id, name, attemptNumber) {},\n                onManualRetry: function(id, name) {},\n                onValidateBatch: function(fileOrBlobData) {},\n                onValidate: function(fileOrBlobData) {},\n                onSubmitDelete: function(id) {},\n                onDelete: function(id) {},\n                onDeleteComplete: function(id, xhrOrXdr, isError) {},\n                onPasteReceived: function(blob) {},\n                onStatusChange: function(id, oldStatus, newStatus) {},\n                onSessionRequestComplete: function(response, success, xhrOrXdr) {}\n            },\n\n            messages: {\n                typeError: \"{file} has an invalid extension. Valid extension(s): {extensions}.\",\n                sizeError: \"{file} is too large, maximum file size is {sizeLimit}.\",\n                minSizeError: \"{file} is too small, minimum file size is {minSizeLimit}.\",\n                emptyError: \"{file} is empty, please select files again without it.\",\n                noFilesError: \"No files to upload.\",\n                tooManyItemsError: \"Too many items ({netItems}) would be uploaded.  Item limit is {itemLimit}.\",\n                maxHeightImageError: \"Image is too tall.\",\n                maxWidthImageError: \"Image is too wide.\",\n                minHeightImageError: \"Image is not tall enough.\",\n                minWidthImageError: \"Image is not wide enough.\",\n                retryFailTooManyItems: \"Retry failed - you have reached your file limit.\",\n                onLeave: \"The files are being uploaded, if you leave now the upload will be canceled.\",\n                unsupportedBrowserIos8Safari: \"Unrecoverable error - this browser does not permit file uploading of any kind due to serious bugs in iOS8 Safari.  Please use iOS8 Chrome until Apple fixes these issues.\"\n            },\n\n            retry: {\n                enableAuto: false,\n                maxAutoAttempts: 3,\n                autoAttemptDelay: 5,\n                preventRetryResponseProperty: \"preventRetry\"\n            },\n\n            classes: {\n                buttonHover: \"qq-upload-button-hover\",\n                buttonFocus: \"qq-upload-button-focus\"\n            },\n\n            chunking: {\n                enabled: false,\n                concurrent: {\n                    enabled: false\n                },\n                mandatory: false,\n                paramNames: {\n                    partIndex: \"qqpartindex\",\n                    partByteOffset: \"qqpartbyteoffset\",\n                    chunkSize: \"qqchunksize\",\n                    totalFileSize: \"qqtotalfilesize\",\n                    totalParts: \"qqtotalparts\"\n                },\n                partSize: function(id) {\n                    return 2000000;\n                },\n                // only relevant for traditional endpoints, only required when concurrent.enabled === true\n                success: {\n                    endpoint: null,\n\n                    headers: function(id) {\n                        return null;\n                    },\n\n                    jsonPayload: false,\n\n                    method: \"POST\",\n\n                    params: function(id) {\n                        return null;\n                    },\n\n                    resetOnStatus: []\n                }\n            },\n\n            resume: {\n                enabled: false,\n                recordsExpireIn: 7, //days\n                paramNames: {\n                    resuming: \"qqresume\"\n                },\n                customKeys: function(fileId) {\n                    return [];\n                }\n            },\n\n            formatFileName: function(fileOrBlobName) {\n                return fileOrBlobName;\n            },\n\n            text: {\n                defaultResponseError: \"Upload failure reason unknown\",\n                fileInputTitle: \"file input\",\n                sizeSymbols: [\"kB\", \"MB\", \"GB\", \"TB\", \"PB\", \"EB\"]\n            },\n\n            deleteFile: {\n                enabled: false,\n                method: \"DELETE\",\n                endpoint: \"/server/upload\",\n                customHeaders: {},\n                params: {}\n            },\n\n            cors: {\n                expected: false,\n                sendCredentials: false,\n                allowXdr: false\n            },\n\n            blobs: {\n                defaultName: \"misc_data\"\n            },\n\n            paste: {\n                targetElement: null,\n                defaultName: \"pasted_image\"\n            },\n\n            camera: {\n                ios: false,\n\n                // if ios is true: button is null means target the default button, otherwise target the button specified\n                button: null\n            },\n\n            // This refers to additional upload buttons to be handled by Fine Uploader.\n            // Each element is an object, containing `element` as the only required\n            // property.  The `element` must be a container that will ultimately\n            // contain an invisible `<input type=\"file\">` created by Fine Uploader.\n            // Optional properties of each object include `multiple`, `validation`,\n            // and `folders`.\n            extraButtons: [],\n\n            // Depends on the session module.  Used to query the server for an initial file list\n            // during initialization and optionally after a `reset`.\n            session: {\n                endpoint: null,\n                params: {},\n                customHeaders: {},\n                refreshOnReset: true\n            },\n\n            // Send parameters associated with an existing form along with the files\n            form: {\n                // Element ID, HTMLElement, or null\n                element: \"qq-form\",\n\n                // Overrides the base `autoUpload`, unless `element` is null.\n                autoUpload: false,\n\n                // true = upload files on form submission (and squelch submit event)\n                interceptSubmit: true\n            },\n\n            // scale images client side, upload a new file for each scaled version\n            scaling: {\n                customResizer: null,\n\n                // send the original file as well\n                sendOriginal: true,\n\n                // fox orientation for scaled images\n                orient: true,\n\n                // If null, scaled image type will match reference image type.  This value will be referred to\n                // for any size record that does not specific a type.\n                defaultType: null,\n\n                defaultQuality: 80,\n\n                failureText: \"Failed to scale\",\n\n                includeExif: false,\n\n                // metadata about each requested scaled version\n                sizes: []\n            },\n\n            workarounds: {\n                iosEmptyVideos: true,\n                ios8SafariUploads: true,\n                ios8BrowserCrash: false\n            }\n        };\n\n        // Replace any default options with user defined ones\n        qq.extend(this._options, o, true);\n\n        this._buttons = [];\n        this._extraButtonSpecs = {};\n        this._buttonIdsForFileIds = [];\n\n        this._wrapCallbacks();\n        this._disposeSupport =  new qq.DisposeSupport();\n\n        this._storedIds = [];\n        this._autoRetries = [];\n        this._retryTimeouts = [];\n        this._preventRetries = [];\n        this._thumbnailUrls = [];\n\n        this._netUploadedOrQueued = 0;\n        this._netUploaded = 0;\n        this._uploadData = this._createUploadDataTracker();\n\n        this._initFormSupportAndParams();\n\n        this._customHeadersStore = this._createStore(this._options.request.customHeaders);\n        this._deleteFileCustomHeadersStore = this._createStore(this._options.deleteFile.customHeaders);\n\n        this._deleteFileParamsStore = this._createStore(this._options.deleteFile.params);\n\n        this._endpointStore = this._createStore(this._options.request.endpoint);\n        this._deleteFileEndpointStore = this._createStore(this._options.deleteFile.endpoint);\n\n        this._handler = this._createUploadHandler();\n\n        this._deleteHandler = qq.DeleteFileAjaxRequester && this._createDeleteHandler();\n\n        if (this._options.button) {\n            this._defaultButtonId = this._createUploadButton({\n                element: this._options.button,\n                title: this._options.text.fileInputTitle\n            }).getButtonId();\n        }\n\n        this._generateExtraButtonSpecs();\n\n        this._handleCameraAccess();\n\n        if (this._options.paste.targetElement) {\n            if (qq.PasteSupport) {\n                this._pasteHandler = this._createPasteHandler();\n            }\n            else {\n                this.log(\"Paste support module not found\", \"error\");\n            }\n        }\n\n        this._options.warnBeforeUnload && this._preventLeaveInProgress();\n\n        this._imageGenerator = qq.ImageGenerator && new qq.ImageGenerator(qq.bind(this.log, this));\n        this._refreshSessionData();\n\n        this._succeededSinceLastAllComplete = [];\n        this._failedSinceLastAllComplete = [];\n\n        this._scaler = (qq.Scaler && new qq.Scaler(this._options.scaling, qq.bind(this.log, this))) || {};\n        if (this._scaler.enabled) {\n            this._customNewFileHandler = qq.bind(this._scaler.handleNewFile, this._scaler);\n        }\n\n        if (qq.TotalProgress && qq.supportedFeatures.progressBar) {\n            this._totalProgress = new qq.TotalProgress(\n                qq.bind(this._onTotalProgress, this),\n\n                function(id) {\n                    var entry = self._uploadData.retrieve({id: id});\n                    return (entry && entry.size) || 0;\n                }\n            );\n        }\n\n        this._currentItemLimit = this._options.validation.itemLimit;\n\n        this._customResumeDataStore = this._createStore();\n    };\n\n    // Define the private & public API methods.\n    qq.FineUploaderBasic.prototype = qq.basePublicApi;\n    qq.extend(qq.FineUploaderBasic.prototype, qq.basePrivateApi);\n}());\n"
  },
  {
    "path": "client/js/uploader.js",
    "content": "/*globals qq */\n/**\n * This defines FineUploader mode, which is a default UI w/ drag & drop uploading.\n */\nqq.FineUploader = function(o, namespace) {\n    \"use strict\";\n\n    var self = this;\n\n    // By default this should inherit instance data from FineUploaderBasic, but this can be overridden\n    // if the (internal) caller defines a different parent.  The parent is also used by\n    // the private and public API functions that need to delegate to a parent function.\n    this._parent = namespace ? qq[namespace].FineUploaderBasic : qq.FineUploaderBasic;\n    this._parent.apply(this, arguments);\n\n    // Options provided by FineUploader mode\n    qq.extend(this._options, {\n        element: null,\n\n        button: null,\n\n        listElement: null,\n\n        dragAndDrop: {\n            extraDropzones: [],\n            reportDirectoryPaths: false\n        },\n\n        text: {\n            formatProgress: \"{percent}% of {total_size}\",\n            failUpload: \"Upload failed\",\n            waitingForResponse: \"Processing...\",\n            paused: \"Paused\"\n        },\n\n        template: \"qq-template\",\n\n        classes: {\n            retrying: \"qq-upload-retrying\",\n            retryable: \"qq-upload-retryable\",\n            success: \"qq-upload-success\",\n            fail: \"qq-upload-fail\",\n            editable: \"qq-editable\",\n            hide: \"qq-hide\",\n            dropActive: \"qq-upload-drop-area-active\"\n        },\n\n        failedUploadTextDisplay: {\n            mode: \"default\", //default, custom, or none\n            responseProperty: \"error\",\n            enableTooltip: true\n        },\n\n        messages: {\n            tooManyFilesError: \"You may only drop one file\",\n            unsupportedBrowser: \"Unrecoverable error - this browser does not permit file uploading of any kind.\"\n        },\n\n        retry: {\n            showAutoRetryNote: true,\n            autoRetryNote: \"Retrying {retryNum}/{maxAuto}...\"\n        },\n\n        deleteFile: {\n            forceConfirm: false,\n            confirmMessage: \"Are you sure you want to delete {filename}?\",\n            deletingStatusText: \"Deleting...\",\n            deletingFailedText: \"Delete failed\"\n\n        },\n\n        display: {\n            fileSizeOnSubmit: false,\n            prependFiles: false\n        },\n\n        paste: {\n            promptForName: false,\n            namePromptMessage: \"Please name this image\"\n        },\n\n        thumbnails: {\n            customResizer: null,\n            maxCount: 0,\n            placeholders: {\n                waitUntilResponse: false,\n                notAvailablePath: null,\n                waitingPath: null\n            },\n            timeBetweenThumbs: 750\n        },\n\n        scaling: {\n            hideScaled: false\n        },\n\n        showMessage: function(message) {\n            if (self._templating.hasDialog(\"alert\")) {\n                return self._templating.showDialog(\"alert\", message);\n            }\n            else {\n                setTimeout(function() {\n                    window.alert(message);\n                }, 0);\n            }\n        },\n\n        showConfirm: function(message) {\n            if (self._templating.hasDialog(\"confirm\")) {\n                return self._templating.showDialog(\"confirm\", message);\n            }\n            else {\n                return window.confirm(message);\n            }\n        },\n\n        showPrompt: function(message, defaultValue) {\n            if (self._templating.hasDialog(\"prompt\")) {\n                return self._templating.showDialog(\"prompt\", message, defaultValue);\n            }\n            else {\n                return window.prompt(message, defaultValue);\n            }\n        }\n    }, true);\n\n    // Replace any default options with user defined ones\n    qq.extend(this._options, o, true);\n\n    this._templating = new qq.Templating({\n        log: qq.bind(this.log, this),\n        templateIdOrEl: this._options.template,\n        containerEl: this._options.element,\n        fileContainerEl: this._options.listElement,\n        button: this._options.button,\n        imageGenerator: this._imageGenerator,\n        classes: {\n            hide: this._options.classes.hide,\n            editable: this._options.classes.editable\n        },\n        limits: {\n            maxThumbs: this._options.thumbnails.maxCount,\n            timeBetweenThumbs: this._options.thumbnails.timeBetweenThumbs\n        },\n        placeholders: {\n            waitUntilUpdate: this._options.thumbnails.placeholders.waitUntilResponse,\n            thumbnailNotAvailable: this._options.thumbnails.placeholders.notAvailablePath,\n            waitingForThumbnail: this._options.thumbnails.placeholders.waitingPath\n        },\n        text: this._options.text\n    });\n\n    if (this._options.workarounds.ios8SafariUploads && qq.ios800() && qq.iosSafari()) {\n        this._templating.renderFailure(this._options.messages.unsupportedBrowserIos8Safari);\n    }\n    else if (!qq.supportedFeatures.uploading || (this._options.cors.expected && !qq.supportedFeatures.uploadCors)) {\n        this._templating.renderFailure(this._options.messages.unsupportedBrowser);\n    }\n    else {\n        this._wrapCallbacks();\n\n        this._templating.render();\n\n        this._classes = this._options.classes;\n\n        if (!this._options.button && this._templating.getButton()) {\n            this._defaultButtonId = this._createUploadButton({\n                element: this._templating.getButton(),\n                title: this._options.text.fileInputTitle\n            }).getButtonId();\n        }\n\n        this._setupClickAndEditEventHandlers();\n\n        if (qq.DragAndDrop && qq.supportedFeatures.fileDrop) {\n            this._dnd = this._setupDragAndDrop();\n        }\n\n        if (this._options.paste.targetElement && this._options.paste.promptForName) {\n            if (qq.PasteSupport) {\n                this._setupPastePrompt();\n            }\n            else {\n                this.log(\"Paste support module not found.\", \"error\");\n            }\n        }\n\n        this._totalFilesInBatch = 0;\n        this._filesInBatchAddedToUi = 0;\n    }\n};\n\n// Inherit the base public & private API methods\nqq.extend(qq.FineUploader.prototype, qq.basePublicApi);\nqq.extend(qq.FineUploader.prototype, qq.basePrivateApi);\n\n// Add the FineUploader/default UI public & private UI methods, which may override some base methods.\nqq.extend(qq.FineUploader.prototype, qq.uiPublicApi);\nqq.extend(qq.FineUploader.prototype, qq.uiPrivateApi);\n"
  },
  {
    "path": "client/js/uploadsuccess.ajax.requester.js",
    "content": "/*globals qq, XMLHttpRequest*/\n/**\n * Sends a POST request to the server to notify it of a successful upload to an endpoint.  The server is expected to indicate success\n * or failure via the response status.  Specific information about the failure can be passed from the server via an `error`\n * property (by default) in an \"application/json\" response.\n *\n * @param o Options associated with all requests.\n * @constructor\n */\nqq.UploadSuccessAjaxRequester = function(o) {\n    \"use strict\";\n\n    var requester,\n        pendingRequests = [],\n        options = {\n            method: \"POST\",\n            endpoint: null,\n            maxConnections: 3,\n            customHeaders: {},\n            paramsStore: {},\n            cors: {\n                expected: false,\n                sendCredentials: false\n            },\n            log: function(str, level) {}\n        };\n\n    qq.extend(options, o);\n\n    function handleSuccessResponse(id, xhrOrXdr, isError) {\n        var promise = pendingRequests[id],\n            responseJson = xhrOrXdr.responseText,\n            successIndicator = {success: true},\n            failureIndicator = {success: false},\n            parsedResponse;\n\n        delete pendingRequests[id];\n\n        options.log(qq.format(\"Received the following response body to an upload success request for id {}: {}\", id, responseJson));\n\n        try {\n            parsedResponse = qq.parseJson(responseJson);\n\n            // If this is a cross-origin request, the server may return a 200 response w/ error or success properties\n            // in order to ensure any specific error message is picked up by Fine Uploader for all browsers,\n            // since XDomainRequest (used in IE9 and IE8) doesn't give you access to the\n            // response body for an \"error\" response.\n            if (isError || (parsedResponse && (parsedResponse.error || parsedResponse.success === false))) {\n                options.log(\"Upload success request was rejected by the server.\", \"error\");\n                promise.failure(qq.extend(parsedResponse, failureIndicator));\n            }\n            else {\n                options.log(\"Upload success was acknowledged by the server.\");\n                promise.success(qq.extend(parsedResponse, successIndicator));\n            }\n        }\n        catch (error) {\n            // This will be executed if a JSON response is not present.  This is not mandatory, so account for this properly.\n            if (isError) {\n                options.log(qq.format(\"Your server indicated failure in its upload success request response for id {}!\", id), \"error\");\n                promise.failure(failureIndicator);\n            }\n            else {\n                options.log(\"Upload success was acknowledged by the server.\");\n                promise.success(successIndicator);\n            }\n        }\n    }\n\n    requester = qq.extend(this, new qq.AjaxRequester({\n        acceptHeader: \"application/json\",\n        method: options.method,\n        endpointStore: {\n            get: function() {\n                return options.endpoint;\n            }\n        },\n        paramsStore: options.paramsStore,\n        maxConnections: options.maxConnections,\n        customHeaders: options.customHeaders,\n        log: options.log,\n        onComplete: handleSuccessResponse,\n        cors: options.cors\n    }));\n\n    qq.extend(this, {\n        /**\n         * Sends a request to the server, notifying it that a recently submitted file was successfully sent.\n         *\n         * @param id ID of the associated file\n         * @param spec `Object` with the properties that correspond to important values that we want to\n         * send to the server with this request.\n         * @returns {qq.Promise} A promise to be fulfilled when the response has been received and parsed.  The parsed\n         * payload of the response will be passed into the `failure` or `success` promise method.\n         */\n        sendSuccessRequest: function(id, spec) {\n            var promise = new qq.Promise();\n\n            options.log(\"Submitting upload success request/notification for \" + id);\n\n            requester.initTransport(id)\n                .withParams(spec)\n                .send();\n\n            pendingRequests[id] = promise;\n\n            return promise;\n        }\n    });\n};\n"
  },
  {
    "path": "client/js/util.js",
    "content": "/*globals window, navigator, document, FormData, File, HTMLInputElement, XMLHttpRequest, Blob, Storage, ActiveXObject */\n/* jshint -W079 */\nvar qq = function(element) {\n    \"use strict\";\n\n    return {\n        hide: function() {\n            element.style.display = \"none\";\n            return this;\n        },\n\n        /** Returns the function which detaches attached event */\n        attach: function(type, fn) {\n            if (element.addEventListener) {\n                element.addEventListener(type, fn, false);\n            } else if (element.attachEvent) {\n                element.attachEvent(\"on\" + type, fn);\n            }\n            return function() {\n                qq(element).detach(type, fn);\n            };\n        },\n\n        detach: function(type, fn) {\n            if (element.removeEventListener) {\n                element.removeEventListener(type, fn, false);\n            } else if (element.attachEvent) {\n                element.detachEvent(\"on\" + type, fn);\n            }\n            return this;\n        },\n\n        contains: function(descendant) {\n            // The [W3C spec](http://www.w3.org/TR/domcore/#dom-node-contains)\n            // says a `null` (or ostensibly `undefined`) parameter\n            // passed into `Node.contains` should result in a false return value.\n            // IE7 throws an exception if the parameter is `undefined` though.\n            if (!descendant) {\n                return false;\n            }\n\n            // compareposition returns false in this case\n            if (element === descendant) {\n                return true;\n            }\n\n            if (element.contains) {\n                return element.contains(descendant);\n            } else {\n                /*jslint bitwise: true*/\n                return !!(descendant.compareDocumentPosition(element) & 8);\n            }\n        },\n\n        /**\n         * Insert this element before elementB.\n         */\n        insertBefore: function(elementB) {\n            elementB.parentNode.insertBefore(element, elementB);\n            return this;\n        },\n\n        remove: function() {\n            element.parentNode.removeChild(element);\n            return this;\n        },\n\n        /**\n         * Sets styles for an element.\n         * Fixes opacity in IE6-8.\n         */\n        css: function(styles) {\n            /*jshint eqnull: true*/\n            if (element.style == null) {\n                throw new qq.Error(\"Can't apply style to node as it is not on the HTMLElement prototype chain!\");\n            }\n\n            /*jshint -W116*/\n            if (styles.opacity != null) {\n                if (typeof element.style.opacity !== \"string\" && typeof (element.filters) !== \"undefined\") {\n                    styles.filter = \"alpha(opacity=\" + Math.round(100 * styles.opacity) + \")\";\n                }\n            }\n            qq.extend(element.style, styles);\n\n            return this;\n        },\n\n        hasClass: function(name, considerParent) {\n            var re = new RegExp(\"(^| )\" + name + \"( |$)\");\n            return re.test(element.className) || !!(considerParent && re.test(element.parentNode.className));\n        },\n\n        addClass: function(name) {\n            if (!qq(element).hasClass(name)) {\n                element.className += \" \" + name;\n            }\n            return this;\n        },\n\n        removeClass: function(name) {\n            var re = new RegExp(\"(^| )\" + name + \"( |$)\");\n            element.className = element.className.replace(re, \" \").replace(/^\\s+|\\s+$/g, \"\");\n            return this;\n        },\n\n        getByClass: function(className, first) {\n            var candidates,\n                result = [];\n\n            if (first && element.querySelector) {\n                return element.querySelector(\".\" + className);\n            }\n            else if (element.querySelectorAll) {\n                return element.querySelectorAll(\".\" + className);\n            }\n\n            candidates = element.getElementsByTagName(\"*\");\n\n            qq.each(candidates, function(idx, val) {\n                if (qq(val).hasClass(className)) {\n                    result.push(val);\n                }\n            });\n            return first ? result[0] : result;\n        },\n\n        getFirstByClass: function(className) {\n            return qq(element).getByClass(className, true);\n        },\n\n        children: function() {\n            var children = [],\n                child = element.firstChild;\n\n            while (child) {\n                if (child.nodeType === 1) {\n                    children.push(child);\n                }\n                child = child.nextSibling;\n            }\n\n            return children;\n        },\n\n        setText: function(text) {\n            element.innerText = text;\n            element.textContent = text;\n            return this;\n        },\n\n        clearText: function() {\n            return qq(element).setText(\"\");\n        },\n\n        // Returns true if the attribute exists on the element\n        // AND the value of the attribute is NOT \"false\" (case-insensitive)\n        hasAttribute: function(attrName) {\n            var attrVal;\n\n            if (element.hasAttribute) {\n\n                if (!element.hasAttribute(attrName)) {\n                    return false;\n                }\n\n                /*jshint -W116*/\n                return (/^false$/i).exec(element.getAttribute(attrName)) == null;\n            }\n            else {\n                attrVal = element[attrName];\n\n                if (attrVal === undefined) {\n                    return false;\n                }\n\n                /*jshint -W116*/\n                return (/^false$/i).exec(attrVal) == null;\n            }\n        }\n    };\n};\n\n(function() {\n    \"use strict\";\n\n    qq.canvasToBlob = function(canvas, mime, quality) {\n        return qq.dataUriToBlob(canvas.toDataURL(mime, quality));\n    };\n\n    qq.dataUriToBlob = function(dataUri) {\n        var arrayBuffer, byteString,\n            createBlob = function(data, mime) {\n                var BlobBuilder = window.BlobBuilder ||\n                        window.WebKitBlobBuilder ||\n                        window.MozBlobBuilder ||\n                        window.MSBlobBuilder,\n                    blobBuilder = BlobBuilder && new BlobBuilder();\n\n                if (blobBuilder) {\n                    blobBuilder.append(data);\n                    return blobBuilder.getBlob(mime);\n                }\n                else {\n                    return new Blob([data], {type: mime});\n                }\n            },\n            intArray, mimeString;\n\n        // convert base64 to raw binary data held in a string\n        if (dataUri.split(\",\")[0].indexOf(\"base64\") >= 0) {\n            byteString = atob(dataUri.split(\",\")[1]);\n        }\n        else {\n            byteString = decodeURI(dataUri.split(\",\")[1]);\n        }\n\n        // extract the MIME\n        mimeString = dataUri.split(\",\")[0]\n            .split(\":\")[1]\n            .split(\";\")[0];\n\n        // write the bytes of the binary string to an ArrayBuffer\n        arrayBuffer = new ArrayBuffer(byteString.length);\n        intArray = new Uint8Array(arrayBuffer);\n        qq.each(byteString, function(idx, character) {\n            intArray[idx] = character.charCodeAt(0);\n        });\n\n        return createBlob(arrayBuffer, mimeString);\n    };\n\n    qq.log = function(message, level) {\n        if (window.console) {\n            if (!level || level === \"info\") {\n                window.console.log(message);\n            }\n            else\n            {\n                if (window.console[level]) {\n                    window.console[level](message);\n                }\n                else {\n                    window.console.log(\"<\" + level + \"> \" + message);\n                }\n            }\n        }\n    };\n\n    qq.isObject = function(variable) {\n        return variable && !variable.nodeType && Object.prototype.toString.call(variable) === \"[object Object]\";\n    };\n\n    qq.isFunction = function(variable) {\n        return typeof (variable) === \"function\";\n    };\n\n    /**\n     * Check the type of a value.  Is it an \"array\"?\n     *\n     * @param value value to test.\n     * @returns true if the value is an array or associated with an `ArrayBuffer`\n     */\n    qq.isArray = function(value) {\n        return Object.prototype.toString.call(value) === \"[object Array]\" ||\n            (value && window.ArrayBuffer && value.buffer && value.buffer.constructor === ArrayBuffer);\n    };\n\n    // Looks for an object on a `DataTransfer` object that is associated with drop events when utilizing the Filesystem API.\n    qq.isItemList = function(maybeItemList) {\n        return Object.prototype.toString.call(maybeItemList) === \"[object DataTransferItemList]\";\n    };\n\n    // Looks for an object on a `NodeList` or an `HTMLCollection`|`HTMLFormElement`|`HTMLSelectElement`\n    // object that is associated with collections of Nodes.\n    qq.isNodeList = function(maybeNodeList) {\n        return Object.prototype.toString.call(maybeNodeList) === \"[object NodeList]\" ||\n            // If `HTMLCollection` is the actual type of the object, we must determine this\n            // by checking for expected properties/methods on the object\n            (maybeNodeList.item && maybeNodeList.namedItem);\n    };\n\n    qq.isString = function(maybeString) {\n        return Object.prototype.toString.call(maybeString) === \"[object String]\";\n    };\n\n    qq.trimStr = function(string) {\n        if (String.prototype.trim) {\n            return string.trim();\n        }\n\n        return string.replace(/^\\s+|\\s+$/g, \"\");\n    };\n\n    /**\n     * @param str String to format.\n     * @returns {string} A string, swapping argument values with the associated occurrence of {} in the passed string.\n     */\n    qq.format = function(str) {\n\n        var args =  Array.prototype.slice.call(arguments, 1),\n            newStr = str,\n            nextIdxToReplace = newStr.indexOf(\"{}\");\n\n        qq.each(args, function(idx, val) {\n            var strBefore = newStr.substring(0, nextIdxToReplace),\n                strAfter = newStr.substring(nextIdxToReplace + 2);\n\n            newStr = strBefore + val + strAfter;\n            nextIdxToReplace = newStr.indexOf(\"{}\", nextIdxToReplace + val.length);\n\n            // End the loop if we have run out of tokens (when the arguments exceed the # of tokens)\n            if (nextIdxToReplace < 0) {\n                return false;\n            }\n        });\n\n        return newStr;\n    };\n\n    qq.isFile = function(maybeFile) {\n        return window.File && Object.prototype.toString.call(maybeFile) === \"[object File]\";\n    };\n\n    qq.isFileList = function(maybeFileList) {\n        return window.FileList && Object.prototype.toString.call(maybeFileList) === \"[object FileList]\";\n    };\n\n    qq.isFileOrInput = function(maybeFileOrInput) {\n        return qq.isFile(maybeFileOrInput) || qq.isInput(maybeFileOrInput);\n    };\n\n    qq.isInput = function(maybeInput, notFile) {\n        var evaluateType = function(type) {\n            var normalizedType = type.toLowerCase();\n\n            if (notFile) {\n                return normalizedType !== \"file\";\n            }\n\n            return normalizedType === \"file\";\n        };\n\n        if (window.HTMLInputElement) {\n            if (Object.prototype.toString.call(maybeInput) === \"[object HTMLInputElement]\") {\n                if (maybeInput.type && evaluateType(maybeInput.type)) {\n                    return true;\n                }\n            }\n        }\n        if (maybeInput.tagName) {\n            if (maybeInput.tagName.toLowerCase() === \"input\") {\n                if (maybeInput.type && evaluateType(maybeInput.type)) {\n                    return true;\n                }\n            }\n        }\n\n        return false;\n    };\n\n    qq.isBlob = function(maybeBlob) {\n        if (window.Blob && Object.prototype.toString.call(maybeBlob) === \"[object Blob]\") {\n            return true;\n        }\n    };\n\n    qq.isXhrUploadSupported = function() {\n        var input = document.createElement(\"input\");\n        input.type = \"file\";\n\n        return (\n            input.multiple !== undefined &&\n                typeof File !== \"undefined\" &&\n                typeof FormData !== \"undefined\" &&\n                typeof (qq.createXhrInstance()).upload !== \"undefined\");\n    };\n\n    // Fall back to ActiveX is native XHR is disabled (possible in any version of IE).\n    qq.createXhrInstance = function() {\n        if (window.XMLHttpRequest) {\n            return new XMLHttpRequest();\n        }\n\n        try {\n            return new ActiveXObject(\"MSXML2.XMLHTTP.3.0\");\n        }\n        catch (error) {\n            qq.log(\"Neither XHR or ActiveX are supported!\", \"error\");\n            return null;\n        }\n    };\n\n    qq.isFolderDropSupported = function(dataTransfer) {\n        return dataTransfer.items &&\n            dataTransfer.items.length > 0 &&\n            dataTransfer.items[0].webkitGetAsEntry;\n    };\n\n    qq.isFileChunkingSupported = function() {\n        return !qq.androidStock() && //Android's stock browser cannot upload Blobs correctly\n            qq.isXhrUploadSupported() &&\n            (File.prototype.slice !== undefined || File.prototype.webkitSlice !== undefined || File.prototype.mozSlice !== undefined);\n    };\n\n    qq.sliceBlob = function(fileOrBlob, start, end) {\n        var slicer = fileOrBlob.slice || fileOrBlob.mozSlice || fileOrBlob.webkitSlice;\n\n        return slicer.call(fileOrBlob, start, end);\n    };\n\n    qq.arrayBufferToHex = function(buffer) {\n        var bytesAsHex = \"\",\n            bytes = new Uint8Array(buffer);\n\n        qq.each(bytes, function(idx, byt) {\n            var byteAsHexStr = byt.toString(16);\n\n            if (byteAsHexStr.length < 2) {\n                byteAsHexStr = \"0\" + byteAsHexStr;\n            }\n\n            bytesAsHex += byteAsHexStr;\n        });\n\n        return bytesAsHex;\n    };\n\n    qq.readBlobToHex = function(blob, startOffset, length) {\n        var initialBlob = qq.sliceBlob(blob, startOffset, startOffset + length),\n            fileReader = new FileReader(),\n            promise = new qq.Promise();\n\n        fileReader.onload = function() {\n            promise.success(qq.arrayBufferToHex(fileReader.result));\n        };\n\n        fileReader.onerror = promise.failure;\n\n        fileReader.readAsArrayBuffer(initialBlob);\n\n        return promise;\n    };\n\n    qq.extend = function(first, second, extendNested) {\n        qq.each(second, function(prop, val) {\n            if (extendNested && qq.isObject(val)) {\n                if (first[prop] === undefined) {\n                    first[prop] = {};\n                }\n                qq.extend(first[prop], val, true);\n            }\n            else {\n                first[prop] = val;\n            }\n        });\n\n        return first;\n    };\n\n    /**\n     * Allow properties in one object to override properties in another,\n     * keeping track of the original values from the target object.\n     *\n     * Note that the pre-overriden properties to be overriden by the source will be passed into the `sourceFn` when it is invoked.\n     *\n     * @param target Update properties in this object from some source\n     * @param sourceFn A function that, when invoked, will return properties that will replace properties with the same name in the target.\n     * @returns {object} The target object\n     */\n    qq.override = function(target, sourceFn) {\n        var super_ = {},\n            source = sourceFn(super_);\n\n        qq.each(source, function(srcPropName, srcPropVal) {\n            if (target[srcPropName] !== undefined) {\n                super_[srcPropName] = target[srcPropName];\n            }\n\n            target[srcPropName] = srcPropVal;\n        });\n\n        return target;\n    };\n\n    /**\n     * Searches for a given element (elt) in the array, returns -1 if it is not present.\n     */\n    qq.indexOf = function(arr, elt, from) {\n        if (arr.indexOf) {\n            return arr.indexOf(elt, from);\n        }\n\n        from = from || 0;\n        var len = arr.length;\n\n        if (from < 0) {\n            from += len;\n        }\n\n        for (; from < len; from += 1) {\n            if (arr.hasOwnProperty(from) && arr[from] === elt) {\n                return from;\n            }\n        }\n        return -1;\n    };\n\n    //this is a version 4 UUID\n    qq.getUniqueId = function() {\n        return \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(/[xy]/g, function(c) {\n            /*jslint eqeq: true, bitwise: true*/\n            var r = Math.random() * 16 | 0, v = c == \"x\" ? r : (r & 0x3 | 0x8);\n            return v.toString(16);\n        });\n    };\n\n    //\n    // Browsers and platforms detection\n    qq.ie = function() {\n        return navigator.userAgent.indexOf(\"MSIE\") !== -1 ||\n            navigator.userAgent.indexOf(\"Trident\") !== -1;\n    };\n\n    qq.ie7 = function() {\n        return navigator.userAgent.indexOf(\"MSIE 7\") !== -1;\n    };\n\n    qq.ie8 = function() {\n        return navigator.userAgent.indexOf(\"MSIE 8\") !== -1;\n    };\n\n    qq.ie10 = function() {\n        return navigator.userAgent.indexOf(\"MSIE 10\") !== -1;\n    };\n\n    qq.ie11 = function() {\n        return qq.ie() && navigator.userAgent.indexOf(\"rv:11\") !== -1;\n    };\n\n    qq.edge = function() {\n        return navigator.userAgent.indexOf(\"Edge\") >= 0;\n    };\n\n    qq.safari = function() {\n        return navigator.vendor !== undefined && navigator.vendor.indexOf(\"Apple\") !== -1;\n    };\n\n    qq.chrome = function() {\n        return navigator.vendor !== undefined && navigator.vendor.indexOf(\"Google\") !== -1;\n    };\n\n    qq.opera = function() {\n        return navigator.vendor !== undefined && navigator.vendor.indexOf(\"Opera\") !== -1;\n    };\n\n    qq.firefox = function() {\n        return (!qq.edge() && !qq.ie11() && navigator.userAgent.indexOf(\"Mozilla\") !== -1 && navigator.vendor !== undefined && navigator.vendor === \"\");\n    };\n\n    qq.windows = function() {\n        return navigator.platform === \"Win32\";\n    };\n\n    qq.android = function() {\n        return navigator.userAgent.toLowerCase().indexOf(\"android\") !== -1;\n    };\n\n    // We need to identify the Android stock browser via the UA string to work around various bugs in this browser,\n    // such as the one that prevents a `Blob` from being uploaded.\n    qq.androidStock = function() {\n        return qq.android() && navigator.userAgent.toLowerCase().indexOf(\"chrome\") < 0 && navigator.userAgent.toLowerCase().indexOf(\"firefox\") < 0;\n    };\n\n    qq.ios6 = function() {\n        return qq.ios() && navigator.userAgent.indexOf(\" OS 6_\") !== -1;\n    };\n\n    qq.ios7 = function() {\n        return qq.ios() && navigator.userAgent.indexOf(\" OS 7_\") !== -1;\n    };\n\n    qq.ios8 = function() {\n        return qq.ios() && navigator.userAgent.indexOf(\" OS 8_\") !== -1;\n    };\n\n    // iOS 8.0.0\n    qq.ios800 = function() {\n        return qq.ios() && navigator.userAgent.indexOf(\" OS 8_0 \") !== -1;\n    };\n\n    qq.ios = function() {\n        /*jshint -W014 */\n        return navigator.userAgent.indexOf(\"iPad\") !== -1\n            || navigator.userAgent.indexOf(\"iPod\") !== -1\n            || navigator.userAgent.indexOf(\"iPhone\") !== -1;\n    };\n\n    qq.iosChrome = function() {\n        return qq.ios() && navigator.userAgent.indexOf(\"CriOS\") !== -1;\n    };\n\n    qq.iosSafari = function() {\n        return qq.ios() && !qq.iosChrome() && navigator.userAgent.indexOf(\"Safari\") !== -1;\n    };\n\n    qq.iosSafariWebView = function() {\n        return qq.ios() && !qq.iosChrome() && !qq.iosSafari();\n    };\n\n    //\n    // Events\n\n    qq.preventDefault = function(e) {\n        if (e.preventDefault) {\n            e.preventDefault();\n        } else {\n            e.returnValue = false;\n        }\n    };\n\n    /**\n     * Creates and returns element from html string\n     * Uses innerHTML to create an element\n     */\n    qq.toElement = (function() {\n        var div = document.createElement(\"div\");\n        return function(html) {\n            div.innerHTML = html;\n            var element = div.firstChild;\n            div.removeChild(element);\n            return element;\n        };\n    }());\n\n    //key and value are passed to callback for each entry in the iterable item\n    qq.each = function(iterableItem, callback) {\n        var keyOrIndex, retVal;\n\n        if (iterableItem) {\n            // Iterate through [`Storage`](http://www.w3.org/TR/webstorage/#the-storage-interface) items\n            if (window.Storage && iterableItem.constructor === window.Storage) {\n                for (keyOrIndex = 0; keyOrIndex < iterableItem.length; keyOrIndex++) {\n                    retVal = callback(iterableItem.key(keyOrIndex), iterableItem.getItem(iterableItem.key(keyOrIndex)));\n                    if (retVal === false) {\n                        break;\n                    }\n                }\n            }\n            // `DataTransferItemList` & `NodeList` objects are array-like and should be treated as arrays\n            // when iterating over items inside the object.\n            else if (qq.isArray(iterableItem) || qq.isItemList(iterableItem) || qq.isNodeList(iterableItem)) {\n                for (keyOrIndex = 0; keyOrIndex < iterableItem.length; keyOrIndex++) {\n                    retVal = callback(keyOrIndex, iterableItem[keyOrIndex]);\n                    if (retVal === false) {\n                        break;\n                    }\n                }\n            }\n            else if (qq.isString(iterableItem)) {\n                for (keyOrIndex = 0; keyOrIndex < iterableItem.length; keyOrIndex++) {\n                    retVal = callback(keyOrIndex, iterableItem.charAt(keyOrIndex));\n                    if (retVal === false) {\n                        break;\n                    }\n                }\n            }\n            else {\n                for (keyOrIndex in iterableItem) {\n                    if (Object.prototype.hasOwnProperty.call(iterableItem, keyOrIndex)) {\n                        retVal = callback(keyOrIndex, iterableItem[keyOrIndex]);\n                        if (retVal === false) {\n                            break;\n                        }\n                    }\n                }\n            }\n        }\n    };\n\n    //include any args that should be passed to the new function after the context arg\n    qq.bind = function(oldFunc, context) {\n        if (qq.isFunction(oldFunc)) {\n            var args =  Array.prototype.slice.call(arguments, 2);\n\n            return function() {\n                var newArgs = qq.extend([], args);\n                if (arguments.length) {\n                    newArgs = newArgs.concat(Array.prototype.slice.call(arguments));\n                }\n                return oldFunc.apply(context, newArgs);\n            };\n        }\n\n        throw new Error(\"first parameter must be a function!\");\n    };\n\n    /**\n     * obj2url() takes a json-object as argument and generates\n     * a querystring. pretty much like jQuery.param()\n     *\n     * how to use:\n     *\n     *    `qq.obj2url({a:'b',c:'d'},'http://any.url/upload?otherParam=value');`\n     *\n     * will result in:\n     *\n     *    `http://any.url/upload?otherParam=value&a=b&c=d`\n     *\n     * @param  Object JSON-Object\n     * @param  String current querystring-part\n     * @return String encoded querystring\n     */\n    qq.obj2url = function(obj, temp, prefixDone) {\n        /*jshint laxbreak: true*/\n        var uristrings = [],\n            prefix = \"&\",\n            add = function(nextObj, i) {\n                var nextTemp = temp\n                    ? (/\\[\\]$/.test(temp)) // prevent double-encoding\n                    ? temp\n                    : temp + \"[\" + i + \"]\"\n                    : i;\n                if ((nextTemp !== \"undefined\") && (i !== \"undefined\")) {\n                    uristrings.push(\n                        (typeof nextObj === \"object\")\n                            ? qq.obj2url(nextObj, nextTemp, true)\n                            : (Object.prototype.toString.call(nextObj) === \"[object Function]\")\n                            ? encodeURIComponent(nextTemp) + \"=\" + encodeURIComponent(nextObj())\n                            : encodeURIComponent(nextTemp) + \"=\" + encodeURIComponent(nextObj)\n                    );\n                }\n            };\n\n        if (!prefixDone && temp) {\n            prefix = (/\\?/.test(temp)) ? (/\\?$/.test(temp)) ? \"\" : \"&\" : \"?\";\n            uristrings.push(temp);\n            uristrings.push(qq.obj2url(obj));\n        } else if ((Object.prototype.toString.call(obj) === \"[object Array]\") && (typeof obj !== \"undefined\")) {\n            qq.each(obj, function(idx, val) {\n                add(val, idx);\n            });\n        } else if ((typeof obj !== \"undefined\") && (obj !== null) && (typeof obj === \"object\")) {\n            qq.each(obj, function(prop, val) {\n                add(val, prop);\n            });\n        } else {\n            uristrings.push(encodeURIComponent(temp) + \"=\" + encodeURIComponent(obj));\n        }\n\n        if (temp) {\n            return uristrings.join(prefix);\n        } else {\n            return uristrings.join(prefix)\n                .replace(/^&/, \"\")\n                .replace(/%20/g, \"+\");\n        }\n    };\n\n    qq.obj2FormData = function(obj, formData, arrayKeyName) {\n        if (!formData) {\n            formData = new FormData();\n        }\n\n        qq.each(obj, function(key, val) {\n            key = arrayKeyName ? arrayKeyName + \"[\" + key + \"]\" : key;\n\n            if (qq.isObject(val)) {\n                qq.obj2FormData(val, formData, key);\n            }\n            else if (qq.isFunction(val)) {\n                formData.append(key, val());\n            }\n            else {\n                formData.append(key, val);\n            }\n        });\n\n        return formData;\n    };\n\n    qq.obj2Inputs = function(obj, form) {\n        var input;\n\n        if (!form) {\n            form = document.createElement(\"form\");\n        }\n\n        qq.obj2FormData(obj, {\n            append: function(key, val) {\n                input = document.createElement(\"input\");\n                input.setAttribute(\"name\", key);\n                input.setAttribute(\"value\", val);\n                form.appendChild(input);\n            }\n        });\n\n        return form;\n    };\n\n    /**\n     * Not recommended for use outside of Fine Uploader since this falls back to an unchecked eval if JSON.parse is not\n     * implemented.  For a more secure JSON.parse polyfill, use Douglas Crockford's json2.js.\n     */\n    qq.parseJson = function(json) {\n        /*jshint evil: true*/\n        if (window.JSON && qq.isFunction(JSON.parse)) {\n            return JSON.parse(json);\n        } else {\n            return eval(\"(\" + json + \")\");\n        }\n    };\n\n    /**\n     * Retrieve the extension of a file, if it exists.\n     *\n     * @param filename\n     * @returns {string || undefined}\n     */\n    qq.getExtension = function(filename) {\n        var extIdx = filename.lastIndexOf(\".\") + 1;\n\n        if (extIdx > 0) {\n            return filename.substr(extIdx, filename.length - extIdx);\n        }\n    };\n\n    qq.getFilename = function(blobOrFileInput) {\n        /*jslint regexp: true*/\n\n        if (qq.isInput(blobOrFileInput)) {\n            // get input value and remove path to normalize\n            return blobOrFileInput.value.replace(/.*(\\/|\\\\)/, \"\");\n        }\n        else if (qq.isFile(blobOrFileInput)) {\n            if (blobOrFileInput.fileName !== null && blobOrFileInput.fileName !== undefined) {\n                return blobOrFileInput.fileName;\n            }\n        }\n\n        return blobOrFileInput.name;\n    };\n\n    /**\n     * A generic module which supports object disposing in dispose() method.\n     * */\n    qq.DisposeSupport = function() {\n        var disposers = [];\n\n        return {\n            /** Run all registered disposers */\n            dispose: function() {\n                var disposer;\n                do {\n                    disposer = disposers.shift();\n                    if (disposer) {\n                        disposer();\n                    }\n                }\n                while (disposer);\n            },\n\n            /** Attach event handler and register de-attacher as a disposer */\n            attach: function() {\n                var args = arguments;\n                /*jslint undef:true*/\n                this.addDisposer(qq(args[0]).attach.apply(this, Array.prototype.slice.call(arguments, 1)));\n            },\n\n            /** Add disposer to the collection */\n            addDisposer: function(disposeFunction) {\n                disposers.push(disposeFunction);\n            }\n        };\n    };\n}());\n"
  },
  {
    "path": "client/js/version.js",
    "content": "/*global qq */\nqq.version = \"5.16.2\";\n"
  },
  {
    "path": "client/js/window.receive.message.js",
    "content": "/*globals qq */\n/*jshint -W117 */\nqq.WindowReceiveMessage = function(o) {\n    \"use strict\";\n\n    var options = {\n            log: function(message, level) {}\n        },\n        callbackWrapperDetachers = {};\n\n    qq.extend(options, o);\n\n    qq.extend(this, {\n        receiveMessage: function(id, callback) {\n            var onMessageCallbackWrapper = function(event) {\n                    callback(event.data);\n                };\n\n            if (window.postMessage) {\n                callbackWrapperDetachers[id] = qq(window).attach(\"message\", onMessageCallbackWrapper);\n            }\n            else {\n                log(\"iframe message passing not supported in this browser!\", \"error\");\n            }\n        },\n\n        stopReceivingMessages: function(id) {\n            if (window.postMessage) {\n                var detacher = callbackWrapperDetachers[id];\n                if (detacher) {\n                    detacher();\n                }\n            }\n        }\n    });\n};\n"
  },
  {
    "path": "client/typescript/fine-uploader.d.ts",
    "content": "// Type definitions for FineUploader 5.x.x\n// Project: http://fineuploader.com/\n// Definitions by: Sukhdeep Singh <https://github.com/SinghSukhdeep>\n\n\ndeclare module \"fine-uploader/lib/core\" {\n\n\n    export class FineUploaderBasic {\n\n        /**\n         * The FineUploader Core only constructor\n         */\n        constructor(fineuploaderOptions?: CoreOptions);\n\n        /**\n         * FineUploader's Promise implementation\n         */\n        Promise(): void;\n\n        /**\n         * Submit one or more files to the uploader\n         *\n         * @param any[] files : An array of `File`s, `<input>`s, `Blob`s, `BlobWrapper` objects, `<canvas>`es, or `CanvasWrapper` objects. You may also pass in a `FileList`.\n         * @param any params : A set of parameters to send with the file to be added\n         * @param string endpoint : The endpoint to send this file to\n         */\n        addFiles(files: File[]\n            | HTMLInputElement[]\n            | Blob[]\n            | BlobWrapper\n            | HTMLCanvasElement[]\n            | CanvasWrapper\n            | FileList, params?: any, endpoint?: string): void;\n\n        /**\n         * Submit one or more canned/initial files to the uploader\n         *\n         * @param any[] initialFiles : An array of objects that describe files already on the server\n         */\n        addInitialFiles(initialFiles: any[]): void;\n\n        /**\n         * Cancel the queued or currently uploading item which corresponds to the id\n         *\n         * @param number id : The file's id\n         */\n        cancel(id: number): void;\n\n        /**\n         * Cancels all queued or currently uploading items\n         */\n        cancelAll(): void;\n\n        /**\n         * Clears the internal list of stored items. Only applies when autoUpload is false\n         */\n        clearStoredFiles(): void;\n\n        /**\n         * Attempts to continue a paused upload\n         *\n         * @param number id : A file id\n         * @returns boolean : `true` if attempt was successful.\n         */\n        continueUpload(id: number): boolean;\n\n        /**\n         * Send a delete request to the server for the corresponding file id\n         *\n         * @param number id : The file's id\n         */\n        deleteFile(id: number): void;\n\n        /**\n         * Draws a thumbnail\n         *\n         * @param number id : The id of the image file\n         * @param HTMLElement targetContainer : The element where the image preview will be drawn. Must be either an <img> or <canvas> element\n         * @param number maxSize : The maximum dimensions (for width and height) you will allow this image to scale to\n         * @param boolean fromServer : true if the image data will come as a response from the server rather than be generated client-side\n         * @param CustomResizerCallBack customResizer : Ignored if the current browser does not support image previews.\n         *                                              If you want to use an alternate library to resize the image, you must contribute a function for this option that returns a `Promise`.\n         *                                              Once the resize is complete, your promise must be fulfilled.\n         *                                              You may, of course, reject your returned `Promise` is the resize fails in some way.\n         * @returns Promise: Fulfilled by passing the container back into the success callback after the thumbnail has been rendered.\n         *                   If the thumbnail cannot be rendered, failure callbacks will be invoked instead, passing an object with `container` and `error` properties.\n         */\n        drawThumbnail(id: number, targetContainer: HTMLElement, maxSize?: number, fromServer?: boolean, customResizer?: CustomResizerCallBack): PromiseOptions;\n\n        /**\n         * Returns the button container element associated with a file\n         *\n         * @param number id : The file id\n         * @returns HTMLElement : The button container element associated with a file, or `undefined` if the file was not submitted via a Fine Uploader controlled upload button.\n         */\n        getButton(id: number): HTMLElement;\n\n        /**\n         * Returns the file identified by the id. File API browsers only\n         *\n         * @param number id : The file id\n         * @returns File | Blob : A `File` or `Blob` object\n         */\n        getFile(id: number): File | Blob;\n\n        /**\n         * Returns the endpoint associated with a particular file, or the current catch-all endpoint for all files (if no ID is specified).\n         *\n         * @param number id : The ID of the associated file\n         * @return string | string[] : endpoint associated with a particular file, or the current catch-all endpoint for all files (if no ID is specified).\n         */\n        getEndpoint(id?: number): string | string[];\n\n        /**\n         * Returns the number of items that are either currently uploading or waiting for an available connection (`qq.status.QUEUED`).\n         *\n         * If called inside of a cancel event handler, then this method will return a value that includes the upload associated with the cancel event handler.\n         * This is because the upload will not be canceled until the event handler returns.\n         *\n         * @returns number : The number of items that are currently uploading or queued\n         */\n        getInProgress(): number;\n\n        /**\n         * Returns the name of the file with the associated id\n         *\n         * @param number id : The file id\n         * @returns string : Returns the name of the file identified by the id.\n         */\n        getName(id: number): string;\n\n        /**\n         * Get the number of items that have been successfully uploaded and have not been deleted\n         *\n         * @returns number : The number of items that have been successfully uploaded and not deleted\n         */\n        getNetUploads(): number;\n\n        /**\n         * Get the ID of the parent file for this scaled file\n         *\n         * @param number scaledFileId : The ID of a scaled image file\n         * @returns number : Returns the ID of the scaled image's parent file. `null` if this is not a scaled image or a parent cannot be located\n         */\n        getParentId(scaledFileId: number): number;\n\n        /**\n         * Returns the number of remaining allowed items that may be submitted for upload based on `validation.itemLimit`.\n         */\n        getRemainingAllowedItems(): number;\n\n        /**\n         * Returns an array of potentially resumable items\n         *\n         * @returns ResumableFileObject[] : An array of resumable items\n         */\n        getResumableFilesData(): ResumableFileObject[] | ResumableFileObject;\n\n        /**\n         * Returns the size of the item with the associated id\n         *\n         * @param number id : The file id\n         * @returns number : The size of the file with the corresponding id\n         */\n        getSize(id: number): number;\n\n        /**\n         * Return information about all the items that have been submitted to the uploader\n         *\n         * @param UploadFilter filter : An object which indicates which keys and values must be present in an upload to be returned\n         * @return FoundUploadItems | FoundUploadItems [] : A list of items or a single item that has been filtered/found.\n         *                                                  This returns an array only when there is a potential for the operation to return more than one file in the result set.\n         *                                                  This excludes queries for a specific, single ID or UUID. All other queries will return an array\n         */\n        getUploads(filter?: UploadFilter): FoundUploadItems | FoundUploadItems[];\n\n        /**\n         * Returns the UUID of the item with the associated id\n         *\n         * @param number id : The file id\n         * @returns string : A level 4 UUID which identifies the corresponding file\n         */\n        getUuid(id: number): string;\n\n        /**\n         * Returns true if the file can be auto-resumed, false otherwise.\n         *\n         * @param number id : The file id\n         * @returns boolean : True if the file can be resumed and has a resume record, false otherwise\n         */\n        isResumable(id: number): boolean;\n\n        /**\n         * Output a message to the console, if possible\n         *\n         * @param string message : The message to print\n         * @param string level : The level to output the message at\n         */\n        log(message: string, level?: string): void;\n\n        /**\n         * Attempts to pause an in-progress upload\n         *\n         * @param number id : The file id\n         * @returns boolean : `true` if the attempt was successful. `false` otherwise\n         */\n        pauseUpload(id: number): boolean;\n\n        /**\n         * Remove internal reference to the associated Blob/File object.\n         *\n         * For Blobs that are created via JavaScript in the browser, this will free up all consumed memory.\n         */\n        removeFileRef(id: number): void;\n\n        /**\n         * Reset Fine Uploader\n         */\n        reset(): void;\n\n        /**\n         * Attempt to upload a specific item again\n         *\n         * @param number id : The file id\n         */\n        retry(id: number): void;\n\n        /**\n         * Generates a scaled version of a submitted image file\n         *\n         * @param number id : The id of the image file\n         * @param ScaleImageOptions option : Information about the scaled image to generate\n         * @returns PromiseOptions : Fulfilled by passing the scaled image as a `Blob` back into the success callback after the original image has been scaled.\n         *                         If the scaled image cannot be generated, the failure callback will be invoked instead\n         */\n        scaleImage(id: number, options: ScaleImageOptions): PromiseOptions;\n\n        /**\n         * Set custom headers for an upload request. Pass in a file id to make the headers specific to that file\n         *\n         * @param any customHeaders : The custom headers to include in the upload request. Fine Uploader may also send some other required headers\n         * @param number id : The file id\n         */\n        setCustomHeaders(customHeaders: any, id?: number): void;\n\n        /**\n         * Set custom resume data for a potentially resumable file.\n         * This data will be stored with the file's resume record and will be accessible in the `onResume` event handler and via the `getResumableFilesData` API method.\n         *\n         * @param number id : The file id\n         * @param Object customResumeData : The custom resume data to store with the file's resume record\n         */\n        setCustomResumeData(id: number, customResumeData: Object): void;\n\n        /**\n         * Modify the location where upload requests should be directed. Pass in a file id to change the endpoint for that specific item\n         *\n         * @param string path : A valid URI where upload requests will be sent\n         * @param number | HTMLElement identifier : An integer or HTMLElement corresponding to a file\n         */\n        setEndpoint(path: string, identifier?: number | HTMLElement): void;\n\n        /**\n         * Set custom headers for a delete file request. Pass in a file id to make the headers specific to that file\n         *\n         * @param any customHeaders : The custom headers to include in the upload request. Fine Uploader may also send some other required headers\n         * @param number id : The file id\n         */\n        setDeleteFileCustomHeaders(customHeaders: any, id?: number): void;\n\n        /**\n         * Modify the location where delete requests should be directed. Pass in a file id to change the endpoint for that specific item\n         *\n         * @param string path : A valid URI where delete requests will be sent\n         * @param number | HTMLElement identifier : An integer or HTMLElement corresponding to a file\n         */\n        setDeleteFileEndpoint(path: string, identifier?: number | HTMLElement): void;\n\n        /**\n         * Set the parameters for a delete request. Pass in a file id to make the parameters specific to that file\n         *\n         * @param any params : The parameters to include in the delete request\n         * @param number id : The file id\n         */\n        setDeleteFileParams(params: any, id?: number): void;\n\n        /**\n         * Change the `validation.itemLimit` option set during construction/initialization\n         *\n         * @param number newItemLimit : The new file count limit\n         */\n        setItemLimit(newItemLimit: number): void;\n\n        /**\n         * Bind a `<form>` to Fine Uploader dynamically\n         *\n         * @param HTMLFormElement | string formElementOrId : A form element or a form element's ID\n         */\n        setForm(formElementOrId: HTMLFormElement | string): void;\n\n        /**\n         * Change the name of a file\n         *\n         * @param number id: The file id\n         * @param string name : The new file name\n         */\n        setName(id: number, name: string): void;\n\n        /**\n         * Set the parameters for an upload request. Pass in a file id to make the parameters specific to that file\n         *\n         * @param any params : The parameters to include in the upload request\n         * @param number id : The file id\n         */\n        setParams(params: any, id?: number): void;\n\n        /**\n         * Modify the status of an file.\n         * The status values correspond to those found in the qq.status object.\n         * Currently, the following status values may be set via this method:\n         * - qq.status.DELETED\n         * - qq.status.DELETE_FAILED\n         *\n         * @param number id : The file id\n         * @param string newStatus : The new qq.status value.\n         */\n        setStatus(id: number, newStatus: string): void;\n\n        /**\n         * Change the UUID of a file\n         *\n         * @param number id : The file id\n         * @param string uuid : The new file UUID\n         */\n        setUuid(id: number, uuid: string): void;\n\n        /**\n         * Begin uploading all queued items. Throws a `NoFilesError` if there are no items to upload\n         */\n        uploadStoredFiles(): void;\n\n\n        /* ====================================== UTILITY METHODS ======================================= */\n\n\n        /**\n         * Returns an array of all immediate children of this element.\n         *\n         * @param HTMLElement element : An HTMLElement or an already wrapped qq object\n         * @returns HTMLElement[] : An array of HTMLElements who are children of the `element` parameter\n         */\n        children(element: HTMLElement): HTMLElement[];\n\n        /**\n         * Returns true if the element contains the passed element.\n         *\n         * @param HTMLElement element : An HTMLElement or an already wrapped qq object\n         * @returns boolean : The result of the contains test\n         */\n        contains(element: HTMLElement): boolean;\n\n        /**\n         * Returns `true` if the attribute exists on the element and the value of the attribute is not 'false' case-insensitive.\n         *\n         * @param string attributeName : An attribute to test for\n         * @returns boolean : The result of the `hasAttribute` test\n         */\n        hasAttribute(attributeName: string): boolean;\n\n        /**\n         * Clears all text for this element\n         */\n        clearText(): void;\n\n        /**\n         * Inserts the element directly before the passed element in the DOM.\n         *\n         * @param HTMLElement element : the `element` before which an element has to be inserted\n         */\n        insertBefore(element: HTMLElement): void;\n\n        /**\n         * Removes the element from the DOM.\n         */\n        remove(): void;\n\n        /**\n         * Sets the inner text for this element.\n         *\n         * @param string text : The text to set\n         */\n        setText(text: string): void;\n\n        /**\n         * Add a class to this element.\n         *\n         * @param string className : The name of the class to add to the element\n         */\n        addClass(className: string): void;\n\n        /**\n         * Add CSS style(s) to this element.\n         *\n         * @param Object styles : An object with styles to apply to this element\n         * @returns Object : Returns the current context to allow method chaining\n         */\n        css(styles: any): this;\n\n        /**\n         * Returns an array of all descendants of this element that contain a specific class name.\n         *\n         * @param string className : The name of the class to look for in each element\n         * @returns HTMLElement[] : An array of `HTMLElements\n         */\n        getByClass(className: string): HTMLElement[];\n\n        /**\n         * Returns `true` if the element has the class name\n         *\n         * @param string className : The name of the class to look for in each element\n         * @returns boolean : Result of the `hasClass` test\n         */\n        hasClass(className: string): boolean;\n\n        /**\n         * Hide this element.\n         *\n         * @returns Object : Returns the current context to allow method chaining\n         */\n        hide(): this;\n\n        /**\n         * Remove the provided class from the element.\n         *\n         * @param string className : The name of the class to look for in each element\n         * @returns Object : Returns the current context to allow method chaining\n         */\n        removeClass(className: string): this;\n\n        /**\n         * Attach an event handler to this element for a specific DOM event.\n         *\n         * @param string event : A valid `DOM Event`\n         * @param function handler : A function that will be invoked whenever the respective event occurs\n         * @returns function : Call this function to detach the event\n         */\n        attach(event: string, handler: () => any | void): () => any | void;\n\n        /**\n         * Detach an already attached event handler from this element for a specific DOM event\n         *\n         * @param string event : A valid `DOM Event`\n         * @param function originalHandler : A function that will be detached from this event\n         * @returns Object : Call this function to detach the event\n         */\n        detach(event: string, originalHandler: () => any | void): this;\n\n        /**\n         * Shim for `Function.prototype.bind`\n         *\n         * Creates a new function that, when called, has its `this` keyword set to the provided context.\n         * Pass comma-separated values after the `context` parameter for all arguments to be passed into the new function (when invoked).\n         * You can still pass in additional arguments during invocation.\n         *\n         * @param function oldFunc : The function that will be bound to\n         * @param Object context : The context the function will assume\n         * @returns function : A new function, same as the old one, but bound to the passed in `context`\n         */\n        bind(oldFunc: () => any | void, context: any): () => any;\n\n        /**\n         * Iterates through a collection, passing the key and value into the provided callback. `return false;` to stop iteration.\n         *\n         * @param Array or Object :\n         * @param function callback : A function that will be called for each item returned by looping through the iterable. This function takes an index and an item.\n         */\n        each(iterable: any[] | any, callback: (index: number, item: any) => any | void): () => any | void;\n\n        /**\n         * Shallowly copies the parameters of secondobj to firstobj. if extendnested is true then a deep-copy is performed.\n         *\n         * @param Object firstObj : The object to copy parameters to\n         * @param Object secondObj : The object to copy parameters from\n         * @param boolean extendNested : If `true` then a deep-copy is performed, else a shallow copy\n         * @returns Object : The new object created by the extension\n         */\n        extend(firstObj: any, secondObj: any, extendNested?: boolean): any;\n\n        /**\n         * Returns a string, swapping argument values with the associated occurrence of `{}` in the passed string\n         *\n         * @param string message : the string to be formatted\n         * @returns string : the formatted string\n         */\n        format(message: string): string;\n\n        /**\n         * Return the extension for the filename, if any\n         *\n         * @param string filename : The file's name to rip the extension off of\n         * @returns string : The extension name\n         */\n        getExtension(filename: string): string;\n\n        /**\n         * Returns a version4 uuid\n         *\n         * @returns string : A version 4 unique identifier\n         */\n        getUniqueId(): string;\n\n        /**\n         * Returns the index of `item` in the `Array` starting the search from `startingindex`\n         *\n         * @param any[] array : the array to search in\n         * @param Object item : the item to search for\n         * @param number startingIndex : the index to search from\n         * @returns number : The index of `item` in the array\n         */\n        indexOf(array: any[], item: any, startingIndex?: number): number;\n\n        /**\n         * Check if the parameter is function\n         *\n         * @param Object func : The object to test\n         * @returns boolean : Whether the parameter is a function or not\n         */\n        isFunction(func: any): boolean;\n\n        /**\n         * Check if the parameter is object\n         *\n         * @param Object obj : The thing to test\n         * @returns boolean : Whether the parameter is a object or not\n         */\n        isObject(obj: any): boolean;\n\n        /**\n         * Check if the parameter is string\n         *\n         * @param Object str : The object to test\n         * @returns boolean : Whether the parameter is a string or not\n         */\n        isString(str: any): boolean;\n\n        /**\n         * Log a message to the console. no-op if console logging is not supported. shim for `console.log`\n         *\n         * @param string logMessage : The message to log\n         * @param string logLevel : The logging level, such as 'warn' and 'info'. If `null`, then 'info' is assumed\n         */\n        log(logMessage: string, logLevel?: string): void;\n\n        /**\n         * Prevent the browser's default action on an event\n         *\n         * @param string event : The name of the default event to prevent\n         */\n        preventDefault(event: string): void;\n\n        /**\n         * Creates and returns a new <div> element\n         *\n         * @param string str : Valid HTML that can be parsed by a browser.\n         * @returns HTMLElement : An newly created `HTMLElement` from the input\n         */\n        toElement(str: string): HTMLElement;\n\n        /**\n         * Removes whitespace from the ends of a string. Shim for `String.prototype.trim`\n         *\n         * @param string str : The string to remove whitespace from\n         * @returns string : The new string sans whitespace\n         */\n        trimstr(str: string): string;\n\n\n        /* ====================================== END - UTILITY METHODS ================================= */\n\n\n    }\n\n    /* ====================================== Misc Options and Wrappers  ==================================== */\n\n    /**\n     * Callback type for `customResizer` parameter\n     */\n    export interface CustomResizerCallBack {\n        /**\n         * Contribute this function to manually resize images using alternate 3rd party libraries\n         *\n         * @param ResizeInfo resizeInfo : the ResizeInfo object containing all the resize values/options\n         * @returns Promise : Once the resize is complete, the function must return a promise\n         */\n        (resizeInfo: ResizeInfo): PromiseOptions;\n    }\n\n    /**\n     * The FineUploader namespace contains all the methods, options, events and types\n\n     /* ========================================================== CORE & UI ===================================================================== */\n    /**\n     * type for `resizeInfo` object\n     */\n    export interface ResizeInfo {\n        /**\n         * The original `File` or `Blob` object, if available.\n         */\n        blob?: File | Blob;\n        /**\n         * Desired height of the image after the resize operation.\n         */\n        height?: number;\n        /**\n         * The original HTMLImageElement object, if available.\n         */\n        image?: HTMLImageElement;\n        /**\n         * `HTMLCanvasElement` element containing the original image data (not resized).\n         */\n        sourceCanvas?: HTMLCanvasElement;\n        /**\n         * `HTMLCanvasElement` element containing the `HTMLCanvasElement` that should contain the resized image.\n         */\n        targetCanvas?: HTMLCanvasElement;\n        /**\n         * Desired width of the image after the resize operation.\n         */\n        width?: number;\n    }\n\n\n    /**\n     * type for getUploads method's filter parameter\n     */\n    export interface UploadFilter {\n        /**\n         * the id of the file\n         */\n        id?: number | number[];\n        /**\n         * the uuid of the file\n         */\n        uuid?: number | number[];\n        /**\n         * status of the file\n         */\n        status?: string | string[];\n    }\n\n    /**\n     * type for getUploads method's return object\n     */\n    export interface FoundUploadItems extends UploadFilter {\n        /**\n         * the name of the file\n         */\n        name?: string;\n        /**\n         * the size of the file\n         */\n        size?: string;\n    }\n\n    /**\n     * ScaleImageOptions\n     */\n    export interface ScaleImageOptions {\n        /**\n         * required\n         */\n        maxSize: number;\n        /**\n         * @default `true`\n         */\n        orient?: boolean;\n        /**\n         * defaults to the type of the reference image\n         */\n        type?: string;\n        /**\n         * number between `0` and `100`\n         *\n         * @default `80`\n         */\n        quality?: number;\n        /**\n         * @default `false`\n         */\n        includeExif?: boolean;\n        /**\n         * Ignored if the current browser does not support image previews.\n         *\n         * If you want to use an alternate library to resize the image, you must contribute a function for this option that returns a `Promise`.\n         *\n         * Once the resize is complete, your promise must be fulfilled.\n         * You may, of course, reject your returned `Promise` is the resize fails in some way.\n         */\n        customResizer?: CustomResizerCallBack;\n    }\n\n    export interface PromiseOptions {\n        /**\n         * Register callbacks from success and failure.\n         *\n         * The promise instance that then is called on will pass any values into the provided callbacks.\n         * If success or failure have already occurred before these callbacks have been registered, then they will be called immediately after this call has been executed.\n         * Each subsequent call to then registers an additional set of callbacks.\n         *\n         * @param Function successCallback : The function to call when the promise is successfully fulfilled\n         * @param Function failureCallback : The function to call when the promise is unsuccessfully fulfilled\n         * @return PromiseOptions : An instance of a promise\n         */\n        then(successCallback: Function, failureCallback: Function): PromiseOptions;\n\n        /**\n         * Register callbacks for success or failure.\n         *\n         * Invoked when the promise is fulfilled regardless of the result.\n         * The promise instance that done is called on will pass any values into the provided callback.\n         * Each call to done registers an additional set of callbacks\n         *\n         * @param Function callback : The function to call when the promise is fulfilled, successful or not.\n         * @return PromiseOptions : An instance of a promise\n         */\n        done(callback: Function): PromiseOptions;\n\n        /**\n         * Call this on a promise to indicate success.\n         * The parameter values will depend on the situation.\n         *\n         * @param Object param : The value to pass to the promise's success handler.\n         * @return PromiseOptions : An instance of a promise\n         */\n        success(param: any): PromiseOptions;\n\n        /**\n         * Call this on a promise to indicate failure.\n         * The parameter values will depend on the situation.\n         *\n         * @param Object param : The value to pass to the promise's failure handler.\n         * @return PromiseOptions : An instance of a promise\n         */\n        failure(param: any): PromiseOptions;\n    }\n\n    /**\n     * A BlobWrapper object type\n     */\n    export interface BlobWrapper {\n        /**\n         * the bytes of the `Blob` object being uploaded\n         */\n        blob?: Blob;\n        /**\n         * the name of the `Blob`\n         */\n        name?: string;\n    }\n\n    /**\n     * A CanvasWrapper Object type\n     */\n    export interface CanvasWrapper {\n        /**\n         * the `<canvas>` to be converted to a file & then uploaded\n         */\n        canvas?: HTMLCanvasElement;\n        /**\n         * the name to assign to the created file\n         */\n        name?: string;\n        /**\n         * `1`-`100` value indicating the desired quality of the converted file (only for `image/jpeg`)\n         */\n        quality?: number;\n        /**\n         * MIME type of the file to create from this `<canvas>`\n         */\n        type?: MimeType;\n    }\n\n    /**\n     * Resumable file object type\n     */\n    export interface ResumableFileObject {\n        /**\n         * an object containing any custom resume data for the file\n         */\n        customResumeData?: any;\n        /**\n         * filename\n         */\n        name?: string;\n        /**\n         * number of bytes to be uploaded\n         */\n        remaining?: number;\n        /**\n         * the unique id\n         */\n        uuid?: number;\n        /**\n         * total file size in bytes\n         */\n        size?: number;\n    }\n\n\n    /* ====================================== Core Options   ==================================== */\n\n    /**\n     * Contains Core options\n     */\n    export interface CoreOptions {\n        /**\n         * Set to false if you want to be able to upload queued items later by calling the `uploadStoredFiles()` method\n         *\n         * @default `true`\n         */\n        autoUpload?: boolean;\n        /**\n         * Specify an element to use as the 'select files' button. Cannot be a `<button>`\n         *\n         * @default `null`\n         */\n        button?: HTMLElement;\n        /**\n         * This will result in log messages being written to the `window.console` object\n         *\n         * @default `false`\n         */\n        debug?: boolean;\n        /**\n         * When true the cancel link does not appear next to files when the form uploader is used\n         *\n         * @default `false`\n         */\n        disableCancelForFormUploads?: boolean;\n        /**\n         * Provide a function to control the display of file names.\n         *\n         * The raw file name is passed into the function when it is invoked. Your function may return a modified file name.\n         *\n         * Note that this does not affect the actual file name, only the displayed file name\n         */\n        formatFileName?: FormatFileNameFuncton;\n        /**\n         * Maximum allowable concurrent requests\n         *\n         * @default `3`\n         */\n        maxConnections?: number;\n        /**\n         * When false this will prevent the user from simultaneously selecting or dropping more than one item\n         *\n         * @default `true`\n         */\n        multiple?: boolean;\n        /**\n         * When true Fine Uploader will ensure a modal confirmation dialog appears whenever a user tries to navigate away from the page with uploads in progress\n         *\n         * @default `true`\n         */\n        warnBeforeUnload?: boolean;\n\n        /**\n         * blobs options\n         */\n        blobs?: BlobsOptions;\n        /**\n         * camera options\n         */\n        camera?: CameraOptions;\n        /**\n         * ChunkingOptions\n         */\n        chunking?: ChunkingOptions;\n        /**\n         * CorsOptions\n         */\n        cors?: CorsOptions;\n        /**\n         * DeleteFileOptions\n         */\n        deleteFile?: DeleteFileOptions;\n        /**\n         * ExtraButtonsOptions\n         */\n        extraButtons?: ExtraButtonsOptions[];\n        /**\n         * FormOptions\n         */\n        form?: FormOptions;\n        /**\n         * Messages\n         */\n        messages?: Messages;\n        /**\n         * PasteOptions\n         */\n        paste?: PasteOptions;\n        /**\n         * ResumeOptions\n         */\n        resume?: ResumeOptions;\n        /**\n         * RequestOptions\n         */\n        request?: RequestOptions;\n        /**\n         * ScalingOptions\n         */\n        scaling?: ScalingOptions;\n        /**\n         * SessionOptions\n         */\n        session?: SessionOptions;\n        /**\n         * TextOptions\n         */\n        text?: TextOptions;\n        /**\n         * ValidationOptions\n         */\n        validation?: ValidationOptions;\n        /**\n         * WorkArounds\n         */\n        workarounds?: WorkArounds;\n        /**\n         * Core callback functions\n         */\n        callbacks?: CoreEvents;\n\n\n    }\n\n    /**\n     * formatFileName function type\n     */\n    export interface FormatFileNameFuncton {\n        (fileOrBlobName: string): string\n    }\n\n    /**\n     * BlobsOptions\n     */\n    export interface BlobsOptions {\n        /**\n         * The default name to be used for nameless `Blob`s\n         *\n         * @default `Misc data`\n         */\n        defaultName?: string;\n    }\n\n    /**\n     * CameraOptions\n     */\n    export interface CameraOptions {\n        /**\n         * `null` allows camera access on the default button in iOS.\n         *\n         * Otherwise provide an extra button container element to target\n         *\n         * @default `null`\n         */\n        button?: HTMLElement;\n        /**\n         * Enable or disable camera access on iOS (iPod, iPhone, and iPad) devices.\n         *\n         * ###Note:\n         * Enabling this will disable multiple file selection\n         *\n         * @default `false`\n         */\n        ios?: boolean;\n    }\n\n    /**\n     * ConcurrentOptions\n     */\n    export interface ConcurrentOptions {\n        /**\n         * Allow multiple chunks to be uploaded simultaneously per file\n         *\n         * @default `false`\n         */\n        enabled?: boolean;\n    }\n\n    /**\n     * ChunkingOptions\n     */\n    export interface ChunkingOptions {\n        /**\n         * concurrent Chunking options\n         */\n        concurrent?: ConcurrentOptions;\n        /**\n         * Enable or disable splitting the file separate chunks. Each chunks is sent in a separate requested\n         *\n         * @default `false`\n         */\n        enabled?: boolean;\n        /**\n         * Ensure every file is uploaded in chunks, even if the file can only be split up into 1 chunk.\n         *\n         * Does not apply if chunking is not possible in the current browser\n         *\n         * @default `false`\n         */\n        mandatory?: boolean;\n        /**\n         * The maximum size of each chunk, in bytes\n         * If a function value is provided, the file's ID will be passed when invoking the function (which should only be called once per file)\n         *\n         * @default `2000000`\n         */\n        partSize?: number | Function;\n        /**\n         * ParamNamesOptions\n         */\n        paramNames?: ParamNamesOptions;\n        /**\n         * SuccessOptions\n         */\n        success?: SuccessOptions;\n\n    }\n\n    /**\n     * ParamNamesOptions\n     */\n    export interface ParamNamesOptions {\n        /**\n         * Name of the parameter passed with a chunked request that specifies the size in bytes of the associated chunk\n         *\n         * @default `'qqchunksize'`\n         */\n        chunkSize?: string;\n        /**\n         * Name of the parameter passed with a chunked request that specifies the starting byte of the associated chunk\n         *\n         * @default `'qqpartbyteoffset'`\n         */\n        partByteOffset?: string;\n        /**\n         * Name of the parameter passed with a chunked request that specifies the index of the associated partition\n         *\n         * @default `'qqpartindex'`\n         */\n        partIndex?: string;\n        /**\n         * Name of the parameter passed with a chunked request that specifies the total number of chunks associated with the `File` or `Blob`\n         *\n         * @default `'qqtotalparts'`\n         */\n        totalParts?: string;\n        /**\n         * Sent with the first request of the resume with a value of `true`\n         *\n         * @default `'qqresume'`\n         */\n        resuming?: string;\n        /**\n         * totalFileSize\n         *\n         * @default `'qqtotalfilesize'`\n         */\n        totalFileSize?: string;\n    }\n\n    /**\n     * SuccessOptions\n     */\n    export interface SuccessOptions {\n        /**\n         * Endpoint to send a POST after all chunks have been successfully uploaded for each file.\n         *\n         * Required if the `concurrent.enabled` option is set.\n         * \n         * If a function value is provided, the file's ID will be passed when invoking the function\n         *\n         * @default `null`\n         */\n        endpoint?: string | Function;\n        /**\n         * Headers to send to with chunking success request. The file's ID will be passed to your provided function\n         * \n         * @default `function(fileId) { return null }`\n         */\n        headers?: Function;\n        /**\n         * Send all parameters in the request body JSON-encoded. Otherwise params will be sent application/x-www-form-urlencoded\n         * \n         * @default `false`\n         */\n        jsonPayload?: boolean;\n        /**\n         * HTTP method used when sending the success request\n         * \n         * @default `POST`\n         */\n        method?: string;\n        /**\n         * Parameters to send in the message body of the success request.  The file's ID will be passed to your provided function\n         * \n         * @default `function(fileId) { return null }`\n         */\n        params?: Function;\n        /**\n         * Fine Uploader will reset the file such that a retry will start at chunk 0 if the success response status matches any of the provided status codes\n         * \n         * @default `[]`\n         */\n        resetOnStatus?: Array<any>;\n    }\n\n    /**\n     * CorsOptions\n     */\n    export interface CorsOptions {\n        /**\n         * Enable or disable cross-origin requests from IE9 and older where XDomainRequest must be used\n         *\n         * @default `false`\n         */\n        allowXdr?: boolean;\n        /**\n         * Enable or disable cross-domain requests\n         *\n         * @default `false`\n         */\n        expected?: boolean;\n        /**\n         * Enable or disable sending credentials along with each cross-domain request. Ignored if allowXdr is true and IE9 is being used\n         *\n         * @default `false`\n         */\n        sendCredentials?: boolean;\n    }\n\n    /**\n     * DeleteFileOptions\n     */\n    export interface DeleteFileOptions {\n        /**\n         * Any additional headers to attach to all delete file requests\n         *\n         * @default `{}`\n         */\n        customHeaders?: any;\n        /**\n         * Enable or disable deletion of uploaded files\n         *\n         * @default `false`\n         */\n        enabled?: boolean;\n        /**\n         * The endpoint to which delete file requests are sent.\n         *\n         * @default `'/server/upload'`\n         */\n        endpoint?: string;\n        /**\n         * Set the method to use for delete requests.\n         *\n         * Accepts `'POST'` or `'DELETE'`\n         *\n         * @default `'DELETE'`\n         */\n        method?: string;\n        /**\n         * Any additional parameters to attach to delete file requests\n         *\n         * @default `{}`\n         */\n        params?: any;\n    }\n\n    /**\n     * ExtraButtonsOptions\n     */\n    export interface ExtraButtonsOptions {\n        /**\n         * The container element for the upload button\n         *\n         * @default `undefined`\n         */\n        element: HTMLElement;\n        /**\n         * This value will be used when creating the `title` attribute for the underlying `<input type=\"file\">`.\n         *\n         * If not provided, the `text.fileInputTitle` option will be used instead\n         *\n         * @default `'file input'`\n         */\n        fileInputTitle?: string;\n        /**\n         * `true` to allow folders to be selected, `false` to allow files to be selected.\n         *\n         * @default `false`\n         */\n        folders?: boolean;\n        /**\n         * Specify to override the default `multiple` value\n         *\n         * @default `true`\n         */\n        multiple?: boolean;\n        /**\n         * Specify to override the default `validation` option specified.\n         *\n         * Any `validation` properties not specified will be inherited from the default `validation` option\n         *\n         * @default `validation`\n         */\n        validation?: any;\n    }\n\n    /**\n     * FormOptions\n     */\n    export interface FormOptions {\n        /**\n         * This can be the ID of the <form> or a reference to the <form> element\n         *\n         * @default `'qq-form'`\n         */\n        element?: string | HTMLElement;\n        /**\n         * If Fine Uploader is able to attach to a form, this value takes the place of the base `autoUpload` option\n         *\n         * @default `false`\n         */\n        autoUpload?: boolean;\n        /**\n         * Set this to `false` if you do not want Fine Uploader to intercept attempts to submit your form.\n         *\n         * By default, Fine Uploader will intercept submit attempts and instead upload all submitted files, including data from your form in each upload request\n         *\n         * @default `true`\n         */\n        interceptSubmit?: boolean;\n    }\n\n    /**\n     * Messages\n     */\n    export interface Messages {\n        /**\n         * Text passed to the error event handler if a submitted item is zero bits\n         *\n         * @default `'{file} is empty, please select files again without it.'`\n         */\n        emptyError?: string;\n        /**\n         * Text passed to the error event handler if an image is too tall\n         *\n         * @default `'Image is too tall.'`\n         */\n        maxHeightImageError?: string;\n        /**\n         * Text passed to the error event handler if an image is too wide\n         *\n         * @default `'Image is too wide.'`\n         */\n        maxWidthImageError?: string;\n        /**\n         * Text passed to the error event handler if an image is not tall enough\n         *\n         * @default `'Image is not tall enough.'`\n         */\n        minHeightImageError?: string;\n        /**\n         * Text passed to the error event handler if an image is not wide enough\n         *\n         * @default `'Image is not wide enough.'`\n         */\n        minWidthImageError?: string;\n        /**\n         * Text passed to the error event handler if the item is too small\n         *\n         * @default `'{file} is too small, minimum file size is {minSizeLimit}.'`\n         */\n        minSizeError?: string;\n        /**\n         * Text passed to the error event handler if any empty array of items is submitted\n         *\n         * @default `'No files to upload.'`\n         */\n        noFilesError?: string;\n        /**\n         * Text displayed to the user when they attempt to leave the page while uploads are still in progress\n         *\n         * @default `'The files are being uploaded, if you leave now the upload will be canceled.'`\n         */\n        onLeave?: string;\n        /**\n         * Text passed to the error event handler if a retry attempt is declared a failed due to a violation of the `validation.itemLimit` rule\n         *\n         * @default `'Retry failed - you have reached your file limit.'`\n         */\n        retryFailTooManyItemsError?: string;\n        /**\n         * Text passed to the error event handler if a submitted item is too large.\n         *\n         * @default `'{file} is too large, maximum file size is {sizeLimit}.'`\n         */\n        sizeError?: string;\n        /**\n         * Text passed to the error event handler if a submit is ignored due to a violation of the `validation.itemLimit` rules\n         *\n         * @default `'Too many items ({netItems}) would be uploaded. Item limit is {itemLimit}.'`\n         */\n        tooManyItemsError?: string;\n        /**\n         * Text passed to the error event handler if an invalid file type is detected\n         *\n         * @default `'{file} has an invalid extension. Valid extension(s): {extensions}.'`\n         */\n        typeError?: string;\n        /**\n         * Message displayed if the browser is iOS8 Safari and the corresponding workarounds option is not disabled\n         *\n         * @default `'Unrecoverable error - this browser does not permit file uploading of any kind due to serious bugs in iOS8 Safari. Please use iOS8 Chrome until Apple fixes these issues.'`\n         */\n        unsupportedBrowserIos8Safari?: string;\n    }\n\n    /**\n     * PasteOptions\n     */\n    export interface PasteOptions {\n        /**\n         * The default name given to pasted images\n         *\n         * @default `'pasted_image'`\n         */\n        defaultName?: string;\n        /**\n         * Enable this feature by providing any HTMLElement here\n         *\n         * @default `null`\n         */\n        targetElement?: HTMLElement;\n    }\n\n    /**\n     * ResumeOptions\n     */\n    export interface ResumeOptions {\n        /**\n         * Define custom keys used to identify this file among other resume records.\n         * \n         * The file's ID will be passed to your provided function\n         * \n         * @default `function(fileId) { return [] }`\n         */\n        customKeys?: Function;\n        /**\n         * The number of days before a persistent resume record will expire\n         *\n         * @default `7`\n         */\n        recordsExpireIn?: number;\n        /**\n         * Enable or disable the ability to resume failed or stopped chunked uploads\n         *\n         * @default `false`\n         */\n        enabled?: boolean;\n        /**\n         * paramNames.resuming - Sent with the first request of the resume with a value of `true`.\n         *\n         * @default `'qqresume'`\n         */\n        paramNames?: ParamNamesOptions;\n    }\n\n    /**\n     * RetryOptions\n     */\n    export interface RetryOptions {\n        /**\n         * The number of seconds to wait between auto retry attempts\n         *\n         * @default `5`\n         */\n        autoAttemptDelay?: number;\n        /**\n         * Enable or disable retrying uploads that receive any error response\n         *\n         * @default `false`\n         */\n        enableAuto?: boolean;\n        /**\n         * The maximum number of times to attempt to retry a failed upload\n         *\n         * @default `3`\n         */\n        maxAutoAttempts?: number;\n        /**\n         * This property will be looked for in the server response and, if found and `true`, will indicate that no more retries should be attempted for this item\n         *\n         * @default `'preventRetry'`\n         */\n        preventRetryResponseProperty?: string;\n    }\n\n    /**\n     * RequestOptions\n     */\n    export interface RequestOptions {\n        /**\n         * Additional headers sent along with each upload request\n         */\n        customHeaders?: any;\n        /**\n         * The endpoint to send upload requests to\n         *\n         * @default `'/server/upload'`\n         */\n        endpoint?: string;\n        /**\n         * The name of the parameter passed if the original filename has been edited or a `Blob` is being sent\n         *\n         * @default `'qqfilename'`\n         */\n        filenameParam?: string;\n        /**\n         * Force all uploads to use multipart encoding\n         *\n         * @default `true`\n         */\n        forceMultipart?: boolean;\n        /**\n         * The attribute of the input element which will contain the file name.\n         *\n         * For non-multipart-encoded upload requests, this will be included as a parameter in the query string of the URI with a value equal to the file name\n         *\n         * @default `'qqfile'`\n         */\n        inputName?: string;\n        /**\n         * Specify a method to use when sending files to a traditional endpoint. This option is ignored in older browsers (such as IE 9 and older)\n         *\n         * @default `'POST'`\n         */\n        method?: string;\n        /**\n         * If set to true, any Fine Uploader created parameters (qq*) will not be sent with the upload request\n         * \n         * @default `false`\n         */\n        omitDefaultParams?: boolean;\n        /**\n         * The parameters that shall be sent with each upload request\n         */\n        params?: any;\n        /**\n         * Enable or disable sending parameters in the request body.\n         *\n         * If `false`, parameters are sent in the URL.\n         * Otherwise, parameters are sent in the request body\n         *\n         * @default `true`\n         */\n        paramsInBody?: boolean;\n        /**\n         * If set to true, each upload response MUST contain a JSON message-body with `{success: true}` in order to be considered a success.\n         * \n         * If set to false, the success of the request is determined by examining the response status code\n         * \n         * @default `true`\n         */\n        requireSuccessJson?: boolean;\n        /**\n         * The name of the parameter the uniquely identifies each associated item. The value is a Level 4 UUID\n         *\n         * @default `'qquuid'`\n         */\n        uuidName?: string;\n        /**\n         * The name of the parameter passed that specifies the total file size in bytes\n         *\n         * @default `'qqtotalfilesize'`\n         */\n        totalFileSizeName?: string;\n    }\n\n    /**\n     * SizeOptions\n     */\n    export interface SizeOptions {\n        /**\n         * name property will be appended to the file name of the scaled file\n         */\n        name?: string;\n        /**\n         * maximum size\n         */\n        maxSize?: number;\n        /**\n         * MIME type\n         */\n        type?: string;\n    }\n\n    /**\n     * ScalingOptions\n     */\n    export interface ScalingOptions {\n        /**\n         * Ignored if the current browser does not support image previews.\n         *\n         * If you want to use an alternate scaling library, you must contribute a function for this option that returns a Promise.\n         * Once the resize is complete, your promise must be fulfilled. You may, of course, reject your returned Promise is the resize fails in some way\n         *\n         * @default `undefined`\n         */\n        customResizer?: CustomResizerCallBack;\n        /**\n         * A value between `1` and `100` that describes the requested quality of scaled images.\n         *\n         * Ignored unless the scaled image type target is `image/jpeg`\n         *\n         * @default `80`\n         */\n        defaultQuality?: number\n        /**\n         * Scaled images will assume this image type if you don't specify a specific type in your size object, or if the type specified in the size object is not valid.\n         *\n         * You generally should not use any value other than `image/jpeg` or `image/png` here.\n         *\n         * The default value of null will ensure the scaled image type is `PNG`, unless the original file is a `JPEG`, in which case the scaled file will also be a `JPEG`.\n         * The default is probably the safest option.\n         *\n         * @default `null`\n         */\n        defaultType?: string;\n        /**\n         * Text sent to your `complete` event handler as an `error` property of the `response` param if a scaled image could not be generated\n         *\n         * @default `'failed to scale'`\n         */\n        failureText?: string;\n        /**\n         * Ensure the `EXIF` data from the reference image is inserted into the scaled image. Only applicable when both the reference and the target are type `image/jpeg`\n         *\n         * @default `false`\n         */\n        includeExif?: boolean;\n        /**\n         * Set this to `false` if you do not want scaled images to be re-oriented based on parsed `EXIF` data before they are uploaded\n         *\n         * @default `true`\n         */\n        orient?: boolean;\n        /**\n         * Set this to `false` if you don't want to original file to be uploaded as well\n         *\n         * @default `true`\n         */\n        sendOriginal?: boolean;\n        /**\n         * An array containing size objects that describe scaled versions of each submitted image that should be generated and uploaded\n         *\n         * @default `[]`\n         */\n        sizes?: SizeOptions;\n    }\n\n    /**\n     * SessionOptions\n     */\n    export interface SessionOptions {\n        /**\n         * Any additional headers you would like included with the `GET` request sent to your server. Ignored in `IE9` and `IE8` if the endpoint is cross-origin\n         *\n         * @default `{}`\n         */\n        customHeaders?: any;\n        /**\n         * If non-null, Fine Uploader will send a `GET` request on startup to this endpoint, expecting a `JSON` response containing data about the initial file list to populate\n         *\n         * @default `null`\n         */\n        endpoint?: string;\n        /**\n         * Any parameters you would like passed with the associated `GET` request to your server\n         *\n         * @default `{}`\n         */\n        params?: any;\n        /**\n         * Set this to `false` if you do not want the file list to be retrieved from the server as part of a reset.\n         *\n         * @default `true`\n         */\n        refreshOnReset?: boolean\n    }\n\n    /**\n     * TextOptions\n     */\n    export interface TextOptions {\n        /**\n         * In the event of non-200 response from the server sans the 'error' property, this message will be passed to the 'error' event handler\n         *\n         * @default `'Upload failure reason unknown'`\n         */\n        defaultResponseError?: string;\n        /**\n         * The value for the `title` attribute attached to the `<input type=\"file\">` maintained by Fine Uploader for each upload button.\n         *\n         * This is used as hover text, among other things.\n         *\n         * @default `'file input'`\n         */\n        fileInputTitle?: string;\n        /**\n         * Symbols used to represent file size, in ascending order\n         *\n         * @default `['kB', 'MB', 'GB', 'TB', 'PB', 'EB']`\n         */\n        sizeSymbols?: string[];\n    }\n\n    /**\n     * ImageOptions\n     */\n    export interface ImageOptions {\n        /**\n         * Restrict images to a maximum height in pixels (wherever possible)\n         *\n         * @default `0`\n         */\n        maxHeight?: number;\n        /**\n         * Restrict images to a maximum width in pixels (wherever possible)\n         *\n         * @default `0`\n         */\n        maxWidth?: number;\n        /**\n         * Restrict images to a minimum height in pixels (wherever possible)\n         *\n         * @default `0`\n         */\n        minHeight?: number;\n        /**\n         * Restrict images to a minimum width in pixels (wherever possible)\n         *\n         * @default `0`\n         */\n        minWidth?: number;\n    }\n\n    /**\n     * ValidationOptions\n     */\n    export interface ValidationOptions {\n        /**\n         * Used by the file selection dialog.\n         *\n         * Restrict the valid file types that appear in the selection dialog by listing valid content-type specifiers\n         *\n         * @default `null`\n         */\n        acceptFiles?: any;\n        /**\n         * Specify file valid file extensions here to restrict uploads to specific types\n         *\n         * @default `[]`\n         */\n        allowedExtensions?: string[];\n        /**\n         * Maximum number of items that can be potentially uploaded in this session.\n         *\n         * Will reject all items that are added or retried after this limit is reached\n         *\n         * @default `0`\n         */\n        itemLimit?: number;\n        /**\n         * The minimum allowable size, in bytes, for an item\n         *\n         * @default `0`\n         */\n        minSizeLimit?: number;\n        /**\n         * The maximum allowable size, in bytes, for an item\n         *\n         * @default `0`\n         */\n        sizeLimit?: number;\n        /**\n         * When `true` the first invalid item will stop processing further files\n         *\n         * @default `true`\n         */\n        stopOnFirstInvalidFile?: boolean;\n        /**\n         * ImageOptions\n         */\n        image?: ImageOptions;\n    }\n\n    /**\n     * WorkArounds options\n     */\n    export interface WorkArounds {\n        /**\n         * Ensures all `<input type='file'>` elements tracked by Fine Uploader do NOT contain a `multiple` attribute to work around an issue present in iOS7 & 8 that otherwise results in 0-sized uploaded videos\n         *\n         * @default `true`\n         */\n        iosEmptyVideos?: boolean;\n        /**\n         * Ensures all `<input type='file'>` elements tracked by Fine Uploader always have a `multiple` attribute present.\n         *\n         * This only applies to iOS8 Chrome and iOS8 UIWebView, and is put in place to work around an issue that causes the browser to crash when a file input element does not contain a `multiple` attribute inside of a `UIWebView` container created by an iOS8 app compiled with and iOS7 SDK\n         *\n         * @default `false`\n         */\n        ios8BrowserCrash?: boolean;\n        /**\n         * Disables Fine Uploader and displays a message to the user in iOS 8.0.0 Safari.\n         *\n         * Due to serious bugs in iOS 8.0.0 Safari, uploading is not possible.\n         * This was apparently fixed in subsequent builds of iOS8, so this workaround only targets 8.0.0\n         *\n         * @default `true`\n         */\n        ios8SafariUploads?: boolean;\n    }\n\n\n    /* ====================================== Core Callback functions ==================================== */\n\n    /**\n     * Core callback functions\n     */\n    export interface CoreEvents {\n        /**\n         * Called before each automatic retry attempt for a failed item\n         */\n        onAutoRetry?: OnAutoRetry;\n        /**\n         * Called when the item has been canceled. Return `false` to prevent the upload from being canceled.\n         *\n         * Also can return a promise if non-blocking work is required here. Calling `failure()` on the promise is equivalent to returning `false`.\n         *\n         * If using a Promise, then processing of the cancel request will be deferred until the promise is fullfilled.\n         *\n         * Since there is no way to 'pause' the upload in progress while waiting for the promise to be fullfilled the upload may actually complete until the promise has actually be fullfilled\n         */\n        onCancel?: OnCancel;\n        /**\n         * Called when the item has finished uploading.\n         *\n         * The `responseJSON` will contain the raw response from the server including the 'success' property which indicates whether the upload succeeded.\n         */\n        onComplete?: OnComplete;\n        /**\n         * Called when all submitted items have reached a point of termination.\n         *\n         * A file has reached a point of termination if it has been cancelled, rejected, or uploaded (failed or successful).\n         *\n         * For example, if a file in the group is paused, and all other files in the group have uploaded successfully the allComplete event will not be invoked for the group until that paused file is either continued and completes the uploading process, or canceled.\n         *\n         * This event will not be called if all files in the group have been cancelled or rejected (i.e. if none of the files have reached a status of `qq.status.UPLOAD_SUCCESSFUL` or `qq.status.UPLOAD_FAILED`)\n         */\n        onAllComplete?: OnAllComplete;\n        /**\n         * Called just before a delete request is sent for the associated item.\n         *\n         * ###Note:\n         * This is not the correct callback to influence the delete request.\n         * To do that, use the `onSubmitDelete` callback instead\n         */\n        onDelete?: OnDelete;\n        /**\n         * Called just after receiving a response from the server for a delete file request\n         */\n        onDeleteComplete?: OnDeleteComplete;\n        /**\n         * Called whenever an exceptional condition occurs\n         */\n        onError?: OnError;\n        /**\n         * Called before each manual retry attempt.\n         *\n         * Return `false` to prevent this and all future retry attempts on the associated item\n         */\n        onManualRetry?: OnManualRetry;\n        /**\n         * Called when a pasted image has been received (before upload).\n         *\n         * The pasted image is represented as a `Blob`. Also can return a `Promise` if non-blocking work is required here.\n         *\n         * If using a `Promise` the value of the success parameter must be the name to associate with the pasted image.\n         *\n         * If the associated attempt is marked a failure then you should include a string explaining the reason in your failure callback for the `Promise`\n         *\n         * ###NOTE:\n         * The `promptForName` option, if `true`, will effectively wipe away any custom implementation of this callback.\n         *\n         * The two are not meant to be used together. This callback is meant to provide an alternative means to provide a name for a pasted image.\n         *\n         * If you are using Fine Uploader Core mode then you can display your own prompt for the name by overriding the default implementation of this callback\n         */\n        onPasteReceived?: OnPasteReceived;\n        /**\n         * Called during the upload, as it progresses, but only for the AJAX uploader.\n         *\n         * For chunked uploads, this will be called for each chunk.\n         * Useful for implementing a progress bar\n         */\n        onProgress?: OnProgress;\n        /**\n         * Called just before an upload is resumed.\n         *\n         * See the `uploadChunk` event for more info on the `chunkData` object\n         */\n        onResume?: OnResume;\n        /**\n         * Invoked when a session request has completed.\n         *\n         * The `response` will be either an `Array` containing the response data or `null` if the response did not contain valid `JSON`.\n         *\n         * The `success` parameter will be `false` if ANY of the file items represented in the response could not be parsed (due to bad syntax, missing name/UUID property, etc)\n         */\n        onSessionRequestComplete?: OnSessionRequestComplete;\n        /**\n         * Invoked whenever the status changes for any item submitted by the uploader.\n         *\n         * The status values correspond to those found in the `qq.status` object.\n         *\n         * For reference:\n         * * `SUBMITTED`\n         * * `QUEUED`\n         * * `UPLOADING`\n         * * `UPLOAD_RETRYING`\n         * * `UPLOAD_FAILED`\n         * * `UPLOAD_SUCCESSFUL`\n         * * `CANCELED`\n         * * `REJECTED`\n         * * `DELETED`\n         * * `DELETING`\n         * * `DELETE_FAILED`\n         * * `PAUSED`\n         */\n        onStatusChange?: OnStatusChange;\n        /**\n         * Called when the item has been selected and is a candidate for uploading\n         *\n         * This does not mean the item is going to be uploaded. Return `false` to prevent submission to the uploader.\n         *\n         * A promise can be used if non-blocking work is required. Processing of this item is deferred until the promise is fullfilled.\n         *\n         * If a promise is returned, a call to failure is the same as returning `false`\n         */\n        onSubmit?: OnSubmit;\n        /**\n         * Called before an item has been marked for deletion has been submitted to the uploader\n         *\n         * A promise can be used if non-blocking work is required.\n         * Processing of this item is deferred until the promise is fullfilled.\n         * If a promise is returned, a call to failure is the same as returning `false`.\n         *\n         * Use this callback to influence the delete request.\n         * For example, you can change the custom parameters sent with the underlying delete request using the `setDeleteParams` API method\n         */\n        onSubmitDelete?: OnSubmitDelete;\n        /**\n         * Called when the item has been successfully submitted to the uploader.\n         *\n         * The file will upload immediately if there is:\n         * * a) at least one free connection (see: maxConnections option) and\n         * * b) autoUpload is set to true (see autoUpload option)\n         *\n         * The callback is invoked after the 'submit' event is handled without returning a false value.\n         *\n         * In Fine Uploader Core mode it is usually safe to assume that the associated elements in the UI representing the associated file have already been added to the DOM immediately before this callback is invoked\n         */\n        onSubmitted?: OnSubmitted;\n        /**\n         * Called during a batch of uploads, as they progress, but only for the AJAX uploader.\n         *\n         * This represents the total progress of all files in the batch. Useful for implementing an aggregate progress bar.\n         */\n        onTotalProgress?: OnTotalProgress;\n        /**\n         * Called just before an item begins uploading to the server.\n         */\n        onUpload?: OnUpload;\n        /**\n         * Called just before a chunk request is sent.\n         */\n        onUploadChunk?: OnUploadChunk;\n        /**\n         * This is similar to the `complete` event, except it is invoked after each chunk has been successfully uploaded.\n         *\n         * See the `uploadChunk` event for more information on the `chunkData` object\n         */\n        onUploadChunkSuccess?: OnUploadChunkSuccess;\n        /**\n         * Called once for each selected, dropped, or `addFiles` submitted file.\n         *\n         * This callback is always invoked before the default Fine Uploader validators execute.\n         *\n         * This event will not be called if you return `false` in your `validateBatch` event handler, or if the `stopOnFirstInvalidFile` validation option is `true` and the `validate` event handler has returned `false` for an item.\n         *\n         * A promise can be used if non-blocking work is required. Processing of this item is deferred until the promise is fullfilled.\n         * If a promise is returned, a call to `failure` is the same as returning `false`.\n         *\n         * A buttonContainer element will be passed as the last argument, provided the file was submitted using a Fine Uploader tracked button.\n         *\n         * The `blobData` object has two properties: `name` and `size`. The `size` property will be undefined for browsers without File API support.\n         */\n        onValidate?: OnValidate;\n        /**\n         * This callback is always invoked before the default Fine Uploader validators execute.\n         *\n         * This event will not be called if you return `false` in your `validateBatch` event handler, or if the `stopOnFirstInvalidFile` validation option is `true` and the `validate` event handler has returned `false` for an item.\n         *\n         * A promise can be used if non-blocking work is required. Processing of this item is deferred until the promise is fullfilled. If a promise is returned, a call to `failure` is the same as returning `false`.\n         *\n         * A buttonContainer element will be passed as the last argument, provided the file was submitted using a Fine Uploader tracked button.\n         *\n         * The `fileOrBlobDataArray` object has two properties: `name` and `size`. The `size` property will be undefined for browsers without File API support.\n         */\n        onValidateBatch?: OnValidateBatch;\n    }\n\n    /**\n     * onAutoRetry function type\n     */\n    export interface OnAutoRetry {\n        /**\n         * @param number id : The current file's id\n         * @param string name : The current file's name\n         * @param number attemptNumber : The number of retry attempts for the current file so far\n         */\n        (id: number, name: string, attemptNumber: number): void;\n    }\n\n    /**\n     * onCancel function type\n     */\n    export interface OnCancel {\n        /**\n         * @param number id : The current file's id\n         * @param string name : The current file's name\n         */\n        (id: number, name: string): boolean | PromiseOptions | void;\n    }\n\n    /**\n     * onComplete function type\n     */\n    export interface OnComplete {\n        /**\n         * @param number id : The current file's id\n         * @param string name : The current file's name\n         * @param Object responseJSON : The raw response from the server\n         * @param XMLHttpRequest xhr : The object used to make the request\n         */\n        (id: number, name: string, responseJSON: any, xhr: XMLHttpRequest): void;\n    }\n\n    /**\n     * onAllComplete function type\n     */\n    export interface OnAllComplete {\n        /**\n         * @param number[] succeeded : IDs of all files in the group that have uploaded successfully (status = `qq.status.UPLOAD_SUCCESSFUL`)\n         * @param number[] failed : IDs of all files in the group that have failed (status = `qq.status.UPLOAD_FAILED`)\n         */\n        (succeeded: number[], failed: number[]): void;\n    }\n\n    /**\n     * onDelete function type\n     */\n    export interface OnDelete {\n        /**\n         * @param number id : The current file's id\n         */\n        (id: number): void;\n    }\n\n    /**\n     * onDeleteComplete function type\n     */\n    export interface OnDeleteComplete {\n        /**\n         * @param number id : The current file's id\n         * @param XMLHttpRequest xhr : The object used to make the request\n         * @param boolean isError : `true` if there has been an error, `false` otherwise\n         */\n        (id: number, xhr: XMLHttpRequest, isError: boolean): void;\n    }\n\n    /**\n     * onError function type\n     */\n    export interface OnError {\n        /**\n         * @param number id : The current file's id\n         * @param string name : The current file's name\n         * @param string errorReason : The reason for the current error\n         * @param XMLHttpRequest xhr : The object used to make the request\n         */\n        (id: number, name: string, errorReason: string, xhr: XMLHttpRequest): void;\n    }\n\n    /**\n     * onManualRetry function type\n     */\n    export interface OnManualRetry {\n        /**\n         * @param number id : The current file's id\n         * @param string name : The current file's name\n         */\n        (id: number, name: string): boolean | void;\n    }\n\n    /**\n     * onPasteReceived function type\n     */\n    export interface OnPasteReceived {\n        /**\n         * @param Blob blob : An object encapsulating the image pasted from the clipboard\n         */\n        (blob: Blob): PromiseOptions | void;\n    }\n\n    /**\n     * onProgress function type\n     */\n    export interface OnProgress {\n        /**\n         * @param number id : The current file's id\n         * @param string name : The current file's name\n         * @param number uploadedBytes : The number of bytes that have been uploaded so far\n         * @param number totalBytes : The total number of bytes that comprise this file\n         */\n        (id: number, name: string, uploadedBytes: number, totalBytes: number): void;\n    }\n\n    /**\n     * onResume function type\n     */\n    export interface OnResume {\n        /**\n         * @param number id : The current file's id\n         * @param string name : The current file's name\n         * @param Object chunkData : The chunk that will be sent next when file upload resumes\n         * @param Object customResumeData : Any custom resume data provided for this resumable file\n         */\n        (id: number, name: string, chunkData: any, customResumeData: any): void | Promise<any>;\n    }\n\n    /**\n     * onSessionRequestComplete function type\n     */\n    export interface OnSessionRequestComplete {\n        /**\n         * @param any[] response : The raw response data\n         * @param boolean success : Indicates whether success has been achieved or not\n         * @param XMLHttpRequest xhrOrXdr : The raw request\n         */\n        (response: any[], success: boolean, xhrOrXdr: XMLHttpRequest): void;\n    }\n\n    /**\n     * onStatusChange function type\n     */\n    export interface OnStatusChange {\n        /**\n         * @param number id : The current file's id\n         * @param string oldStatus : The previous item status\n         * @param string newStatus : The new status of the item\n         */\n        (id: number, oldStatus: string, newStatus: string): void;\n    }\n\n    export interface OnSubmit {\n        /**\n         * @param number id : The current file's id\n         * @param string name : The current file's name\n         */\n        (id: number, name: string): boolean | PromiseOptions | void;\n    }\n\n    /**\n     * onSubmitDelete function type\n     */\n    export interface OnSubmitDelete {\n        /**\n         * @param number id : The current file's id\n         */\n        (id: number): PromiseOptions | void;\n    }\n\n    /**\n     * onSubmitted function type\n     */\n    export interface OnSubmitted {\n        /**\n         * @param number id : The current file's id\n         * @param string name : The current file's name\n         */\n        (id: number, name: string): void;\n    }\n\n    /**\n     * onTotalProgress function type\n     */\n    export interface OnTotalProgress {\n        /**\n         * @param number totalUploadedBytes : The number of bytes that have been uploaded so far in this batch\n         * @param number totalBytes : The total number of bytes that comprise all files in the batch\n         */\n        (totalUploadedBytes: number, totalBytes: number): void;\n    }\n\n    /**\n     * onUpload function type\n     */\n    export interface OnUpload {\n        /**\n         * @param number id : The current file's id\n         * @param string name : The current file's name\n         */\n        (id: number, name: string): void;\n    }\n\n\n    /**\n     * properties for chunkData object\n     */\n    export interface ChunkData {\n        /**\n         * the 0-based index of the associated partition\n         */\n        partIndex: number;\n        /**\n         * the byte offset of the current chunk\n         */\n        startByte: number;\n        /**\n         * the last byte of the current chunk\n         */\n        endByte: number;\n        /**\n         * the total number of partitions associated with the `File` or `Blob`\n         */\n        totalParts: number;\n    }\n\n    /**\n     * onUploadChunk function type\n     */\n    export interface OnUploadChunk {\n        /**\n         * @param number id : The current file's id\n         * @param string name : The current file's name\n         * @param ChunkData chunkData : An object encapsulating the current chunk of data about to be uploaded\n         */\n        (id: number, name: string, chunkData: ChunkData): void | Promise<any>;\n    }\n\n    /**\n     * onUploadChunkSuccess function type\n     */\n    export interface OnUploadChunkSuccess {\n        /**\n         * @param number id : The current file's id\n         * @param ChunkData chunkData : An object encapsulating the current chunk of data about to be uploaded\n         * @param Object responseJSON : The raw response from the server\n         * @param XMLHttpRequest xhr : The object used to make the request\n         */\n        (id: number, chunkData: ChunkData, responseJSON: any, xhr: XMLHttpRequest): void;\n    }\n\n    /**\n     * blobData object\n     */\n    export interface BlobDataObject {\n        /**\n         * the name of the file\n         */\n        name: string;\n        /**\n         * the size of the file\n         */\n        size: number;\n    }\n\n    /**\n     * onValidate function type\n     */\n    export interface OnValidate {\n        /**\n         * @param BlobDataObject data : An object with a name and size property\n         * @param HTMLElement buttonContainer : The button corresponding to the respective file if the file was submitted to Fine Uploader using a tracked button\n         */\n        (data: BlobDataObject, buttonContainer?: HTMLElement): PromiseOptions | void;\n    }\n\n    /**\n     * onValidateBatch function type\n     */\n    export interface OnValidateBatch {\n        /**\n         * @param BlobDataObject[] fileOrBlobDataArray : An array of Objects with name and size properties\n         * @param HTMLElement buttonContainer : The button corresponding to the respective file if the file was submitted to Fine Uploader using a tracked button\n         */\n        (fileOrBlobDataArray: BlobDataObject[], buttonContainer: HTMLElement): PromiseOptions | void;\n    }\n\n}\n\n\n\n\n\ndeclare module \"fine-uploader\" {\n\n    import {\n        FineUploaderBasic,\n        CoreOptions,\n        DeleteFileOptions,\n        Messages,\n        RetryOptions,\n        PasteOptions,\n        ScalingOptions,\n        TextOptions,\n        PromiseOptions,\n        CustomResizerCallBack\n    } from 'fine-uploader/lib/core';\n\n    export class FineUploader extends FineUploaderBasic {\n\n        /**\n         * The FineUploader Core + UI constructor\n         */\n        constructor(fineuploaderOptions?: UIOptions);\n\n        /**\n         * Mark `element` as a drop zone\n         *\n         * @param HTMLElement element : The element to mark as a drop zone\n         */\n        addExtraDropzone(element: HTMLElement): void;\n\n        /**\n         * Returns the (drop zone) element where the file was dropped. Undefined if drop event was not involved\n         *\n         * @param number id : The file id\n         * @returns HTMLElement : The drop zone element where the file was dropped\n         */\n        getDropTarget(id: number): HTMLElement;\n\n        /**\n         * Returns the file `id` associated with an `HTMLElement`\n         *\n         * @param HTMLElement element : Returns the ID of the associated file, given a file container element or a child of a file container element\n         * @returns number : the id of the file\n         */\n        getId(element: HTMLElement): number;\n\n        /**\n         * Returns the `HTMLElement` associated with the file id\n         *\n         * @param number id : The file id\n         * @returns HTMLElement : The `HTMLElement` that is associated with the file id\n         */\n        getItemByFileId(id: number): HTMLElement;\n\n        /**\n         * Used to un-mark an `element` as a drop zone\n         *\n         * @param HTMLElement element : The element to un-mark as a drop zone\n         */\n        removeExtraDropzone(element: HTMLElement): void;\n\n    }\n\n\n    /* ====================================== UI Options ======================================== */\n\n    /**\n     * Contains UIOptions\n     */\n    export interface UIOptions extends CoreOptions {\n        /**\n         * Container element for the default drop zone\n         *\n         * @default `null`\n         */\n        element?: HTMLElement;\n        /**\n         * Container element for the item list\n         *\n         * @default `null`\n         */\n        listElement?: HTMLElement;\n        /**\n         * When false this will prevent the user from simultaneously selecting or dropping more than one item.\n         *\n         * Dropping or selecting another item will clear the upload list. If another is already uploading, it will be canceled.\n         *\n         * To ignore rather than cancel, simply return false in the 'validate' or 'submit' event handlers\n         *\n         * @default `true`\n         */\n        multiple?: boolean\n        /**\n         * Provide a function here to display a message to the user when the uploader receives an error or the user attempts to leave the page.\n         *\n         * The provided function may return a promise if one wishes to do asynchronous work whilst waiting for user input\n         *\n         * @default `function(message) { window.alert(message); }`\n         */\n        showMessage?: ShowMessageFunction;\n        /**\n         * Provide a function here to prompt the user to confirm deletion of a file.\n         *\n         * The provided function may return a promise if one wishes to do asynchronous work whilst waiting for user input\n         *\n         * @default `function(message) { window.confirm(message); }`\n         */\n        showConfirm?: ShowConfirmFunction;\n        /**\n         * Provide a function here to prompt the user for a filename when pasting file(s).\n         *\n         * The provided function may return a promise if one wishes to do asynchronous work whilst waiting for user input\n         *\n         * @default `function(message, defaultValue) { window.prompt(message, defaultValue); }`\n         */\n        showPrompt?: ShowPromptFunction;\n        /**\n         * This points to the container element that contains the template to use for one or more Fine Uploader UI instances.\n         *\n         * You can either specify a string, which is the element ID (the ID of the container element on the page) or an `Element` that points to the container element\n         *\n         * @default `'qq-template'`\n         */\n        template?: string | HTMLElement;\n        /**\n         * UIDeleteFileOptions\n         */\n        deleteFile?: UIDeleteFileOptions;\n        /**\n         * display options\n         */\n        display?: UIDisplayOptions;\n        /**\n         * dragAndDrop options\n         */\n        dragAndDrop?: UIDragAndDropOptions;\n        /**\n         * failedUploadTextDisplay options\n         */\n        failedUploadTextDisplay?: UIFailedUploadTextDisplay;\n        /**\n         * messages\n         */\n        messages?: UIMessages;\n        /**\n         * retry options\n         */\n        retry?: UIRetryOptions;\n        /**\n         * thumbnail options\n         */\n        thumbnails?: UIThumbnailsOptions;\n        /**\n         * paste UI options\n         */\n        paste?: UIPasteOptions;\n        /**\n         * UI scaling options\n         */\n        scaling?: UIScalingOptions;\n        /**\n         * UI text options\n         */\n        text?: UITextOptions;\n    }\n\n    /**\n     * function for `showMessage` option\n     */\n    export interface ShowMessageFunction {\n        (message: string): PromiseOptions | void;\n    }\n\n    /**\n     * function for `showConfirm` option\n     */\n    export interface ShowConfirmFunction {\n        (message: string): PromiseOptions | void;\n    }\n\n    /**\n     * function for `showPrompt` option\n     */\n    export interface ShowPromptFunction {\n        (message: string, defaultValue: string): PromiseOptions | void;\n    }\n\n    /**\n     * This export  class defines UI specific options for the core `DeleteFileOptions`\n     */\n    export interface UIDeleteFileOptions extends DeleteFileOptions {\n        /**\n         * The message displayed in the confirm delete dialog\n         *\n         * @default `'Are you sure you want to delete {filename}?'`\n         */\n        confirmMessage?: string;\n        /**\n         * The status message to appear next to a file that has failed to delete\n         *\n         * @default `'Delete failed'`\n         */\n        deletingFailedText?: string;\n        /**\n         * The status message to appear next to a file that is pending deletion\n         *\n         * @default `'Deleting...'`\n         */\n        deletingStatusText?: string;\n        /**\n         * If this value is set to `true`, the user will be required to confirm the file delete request via a confirmation dialog\n         *\n         * @default `false`\n         */\n        forceConfirm?: boolean;\n    }\n\n    /**\n     * UIDisplayOptions\n     */\n    export interface UIDisplayOptions {\n        /**\n         * Enable or disable the display of the file size next to the file after it has been submitted\n         *\n         * @default `false`\n         */\n        fileSizeOnSubmit?: boolean;\n        /**\n         * When `true` batches of files are added to the top of the UI's file list. The default is to append file(s) to the bottom of the list\n         *\n         * @default `false`\n         */\n        prependFiles?: boolean;\n    }\n\n    /**\n     * dragAndDrop options\n     */\n    export interface UIDragAndDropOptions {\n        /**\n         * Designate additional drop zones for file input\n         *\n         * @default `[]`\n         */\n        extraDropzones?: any[];\n        /**\n         * Include the path of dropped files (starting with the top-level dropped directory). This value will be sent along with the request as a qqpath parameter\n         *\n         * @default `false`\n         */\n        reportDirectoryPaths?: boolean;\n    }\n\n    /**\n     * failedUploadTextDisplay options\n     */\n    export interface UIFailedUploadTextDisplay {\n        /**\n         * Enable or disable a tooltip that will display the full contents of the error message when the mouse pointer hovers over the failed item.\n         *\n         * @default `true`\n         */\n        enableTooltip?: boolean;\n        /**\n         * Set the message to display next to each failed file.\n         *\n         * One of: 'default' which displays the failedUploadText, 'custom' which displays the error response from the server, or 'none' which displays no text\n         *\n         * @default `'default'`\n         */\n        mode?: string;\n        /**\n         * The property from the server response that contains the error text to display next to a failed item. Ignored unless `mode` is `'custom'`\n         *\n         * @default `'error'`\n         */\n        responseProperty?: string;\n    }\n\n    /**\n     * UIMessages\n     */\n    export interface UIMessages extends Messages {\n        /**\n         * Text sent to `showMessage` when `multiple` is `false` and more than one file is dropped at once\n         *\n         * @default `'You may only drop one file.'`\n         */\n        tooManyFilesError?: string;\n        /**\n         * Text displayed to users who have ancient browsers\n         *\n         * @default `'Unrecoverable error - the browser does not permit uploading of any kind.'`\n         */\n        unsupportedBrowser?: string;\n    }\n\n    /**\n     * UIRetryOptions\n     */\n    export interface UIRetryOptions extends RetryOptions {\n        /**\n         * The text of the note that will optionally appear next to the item during automatic retry attempts.\n         *\n         * Ignored if `showAutoRetryNote` is false.\n         *\n         * @default `'Retrying {retryNum}/{maxAuto} ...'`\n         */\n        autoRetryNote?: string;\n        /**\n         * Enable or disable the showing of a button/link next to the failed item after all retry attempts have been exhausted.\n         *\n         * Clicking the button/link will force the uploader to make another attempt\n         *\n         * @default `false`\n         */\n        showButton?: boolean;\n        /**\n         * Enable or disable a status message appearing next to the item during auto retry attempts\n         *\n         * @default `true`\n         */\n        showAutoRetryNote?: boolean;\n    }\n\n    /**\n     * thumbnails options\n     */\n    export interface UIThumbnailsOptions {\n        /**\n         * Ignored if the current browser does not support image previews.\n         *\n         * If you want to use an alternate library to resize the image, you must contribute a function for this option that returns a Promise.\n         * Once the resize is complete, your promise must be fulfilled.\n         *\n         * You may, of course, reject your returned Promise is the resize fails in some way\n         *\n         * @default `undefined`\n         */\n        customResizer?: CustomResizerCallBack;\n        /**\n         * Maximum number of previews to render per Fine Uploader instance.\n         *\n         * A call to the reset method resets this value as well\n         *\n         * @default `0`\n         */\n        maxCount?: number;\n        /**\n         * The amount of time, in milliseconds, to pause between each preview generation process.\n         *\n         * This is in place to prevent the UI thread from locking up for a continuously long period of time, as preview generation can be a resource-intensive process\n         *\n         * @default `750`\n         */\n        timeBetweenThumbs?: number;\n        /**\n         *\n         */\n        placeholders?: UIThumbnailsPlaceholderOptions;\n    }\n\n    /**\n     * UIThumbnailsPlaceholderOptions\n     */\n    export interface UIThumbnailsPlaceholderOptions {\n        /**\n         * Absolute URL or relative path to the image to display if the preview/thumbnail could not be generated/displayed\n         *\n         * @default `null`\n         */\n        notAvailablePath?: string;\n        /**\n         * Absolute URL or relative path to the image to display during preview generation (modern browsers) or until the server response has been parsed (older browsers)\n         *\n         * @default `null`\n         */\n        waitingPath?: string;\n        /**\n         * Set this to true if you want the 'waiting' placeholder image to remain in place until the server response has been parsed.\n         *\n         * This is useful if you expect to return thumbnail URLs in your upload responses for files types that cannot be previewed.\n         * This option is ignored in older browsers where client-side previews cannot be generated\n         *\n         * @default `false`\n         */\n        waitUntilResponse?: boolean;\n    }\n\n    /**\n     * UIPasteOptions\n     */\n    export interface UIPasteOptions extends PasteOptions {\n        /**\n         * Text that will appear in the `showPrompt` dialog.\n         *\n         * @default `Please name this image`\n         */\n        namePromptMessage?: string;\n        /**\n         * Enable or disable the usage of `showPrompt` by Fine Uploader to prompt the user for a filename for a pasted file\n         *\n         * @default `false`\n         */\n        promptForName?: boolean;\n    }\n\n    /**\n     * UIScalingOptions\n     */\n    export interface UIScalingOptions extends ScalingOptions {\n        /**\n         * Text that will appear next to a scaled image that could not be generated.\n         *\n         * This is in addition to the behavior associated with this property provided by Fine Uploader Core\n         *\n         * @default `'Failed to scale'`\n         */\n        failureText?: string;\n        /**\n         * Set this to true if you do not want any scaled images to be displayed in the file list\n         *\n         * @default `false`\n         */\n        hideScaled?: boolean;\n    }\n\n    /**\n     * UITextOptions\n     */\n    export interface UITextOptions extends TextOptions {\n        /**\n         * Text that appears next to a failed item\n         *\n         * @default `'Upload failed'`\n         */\n        failUpload?: string;\n        /**\n         * Appears next to a currently uploading item\n         *\n         * @default `'{percent}% of {total_size}'`\n         */\n        formatProgress?: string;\n        /**\n         * Appears next to a paused item\n         *\n         * @default `'paused'`\n         */\n        paused?: string;\n        /**\n         * Appears next to item once the last bytes have been sent (differs on the user-agent)\n         *\n         * @default `'Processing...'`\n         */\n        waitingForResponse?: string;\n    }\n\n\n}\n\n\ndeclare module \"fine-uploader/lib/azure\" {\n\n    import {\n        FineUploader as FineUploaderCore,\n        UIOptions,\n        UIDeleteFileOptions,\n        UIMessages,\n        UIPasteOptions,\n        UIScalingOptions,\n        UITextOptions\n    } from 'fine-uploader';\n\n    import {\n        FineUploaderBasic as FineUploaderBasicCore,\n        ChunkingOptions,\n        CorsOptions,\n        RequestOptions,\n        CoreOptions,\n        ResumableFileObject,\n        PromiseOptions\n    } from 'fine-uploader/lib/core';\n\n\n\n    export namespace azure {\n\n\n        export class FineUploader extends FineUploaderCore {\n\n            /**\n             * The FineUploader Azure Core + UI constructor\n             */\n            constructor(fineuploaderOptions?: AzureUIOptions);\n\n            /**\n             * Retrieve the blob name with the associated ID\n             *\n             * @param number : An ID corresponding to a file\n             * @returns string : The blob name associated with the file ID\n             */\n            getBlobName(fileId: number): string;\n\n            /**\n             * Returns an array of potentially resumable items\n             *\n             * @returns AzureResumableFileObject : An array of resumable items\n             */\n            getResumableFilesData(): AzureResumableFileObject[] | AzureResumableFileObject;\n\n            /**\n             * Modify the container URL where upload requests should be directed.\n             *\n             * The endpoint for a specific file or blob can be changed by passing in an optional `id` parameter.\n             * An `id` will always be `a number and refers to a single file.\n             *\n             * @param string containerUrl : The new Azure Blob Storage container URL\n             * @param number id : An ID corresponding to a file\n             */\n            setEndpoint(containerUrl: string, id?: number): void;\n\n            /**\n             * Modify the endpoint that Fine Uploader should POST to when a file has been successfully uploaded to Azure Blob Storage.\n             *\n             * @param string endpoint : An endpoint that Fine Uploader should POST to when a file has been successfully uploaded to Azure Blob Storage\n             * @param number id : An ID corresponding to a file\n             */\n            setUploadSuccessEndpoint(endpoint: string, id?: number): void;\n\n            /**\n             * Set additional parameters for the upload success request.\n             * ###Note:\n             * Fine Uploader will still send the `container URL`, `blob name`, `filename`, and `UUID` as well\n             *\n             * @param object newParams : The additional parameters set for the upload request\n             * @param number id : A file id to apply these upload success parameters to\n             */\n            setUploadSuccessParams(newParams: any, id?: number): void;\n        }\n\n        export class FineUploaderBasic extends FineUploaderBasicCore {\n\n            /**\n             * The FineUploader Azure Core only constructor\n             */\n            constructor(fineuploaderOptions?: AzureCoreOptions);\n\n            /**\n             * Retrieve the blob name with the associated ID\n             *\n             * @param number : An ID corresponding to a file\n             * @returns string : The blob name associated with the file ID\n             */\n            getBlobName(fileId: number): string;\n\n            /**\n             * Returns an array of potentially resumable items\n             *\n             * @returns AzureResumableFileObject : An array of resumable items\n             */\n            getResumableFilesData(): AzureResumableFileObject[] | AzureResumableFileObject;\n\n            /**\n             * Modify the container URL where upload requests should be directed.\n             *\n             * The endpoint for a specific file or blob can be changed by passing in an optional `id` parameter.\n             * An `id` will always be `a number and refers to a single file.\n             *\n             * @param string containerUrl : The new Azure Blob Storage container URL\n             * @param number id : An ID corresponding to a file\n             */\n            setEndpoint(containerUrl: string, id?: number): void;\n\n            /**\n             * Modify the endpoint that Fine Uploader should POST to when a file has been successfully uploaded to Azure Blob Storage.\n             *\n             * @param string endpoint : An endpoint that Fine Uploader should POST to when a file has been successfully uploaded to Azure Blob Storage\n             * @param number id : An ID corresponding to a file\n             */\n            setUploadSuccessEndpoint(endpoint: string, id?: number): void;\n\n            /**\n             * Set additional parameters for the upload success request.\n             * ###Note:\n             * Fine Uploader will still send the `container URL`, `blob name`, `filename`, and `UUID` as well\n             *\n             * @param object newParams : The additional parameters set for the upload request\n             * @param number id : A file id to apply these upload success parameters to\n             */\n            setUploadSuccessParams(newParams: any, id?: number): void;\n\n        }\n\n\n        /**\n         * AzureChunkingOptions\n         */\n        export interface AzureChunkingOptions extends ChunkingOptions {\n            /**\n             * The maximum size of each part, in bytes\n             *\n             * @default `5242880`\n             */\n            partSize?: number;\n            /**\n             * Files smaller than this value will not be chunked.\n             *\n             * @default `4000001`\n             */\n            minFileSize?: number\n        }\n\n        /**\n         * AzureCorsOptions\n         */\n        export interface AzureCorsOptions extends CorsOptions {\n            /**\n             * Enables or disables cross-domain ajax calls (if the `expected` property is true) in IE9 and older.\n             *\n             * @default `true`\n             */\n            allowXdr?: boolean;\n        }\n\n        /**\n         * AzureBlobPropertyNameFunction\n         */\n        export interface AzureBlobPropertyNameFunction {\n            (id: number): PromiseOptions | string;\n        }\n\n        /**\n         * AzureBlobPropertyOptions\n         */\n        export interface AzureBlobPropertyOptions {\n            /**\n             * Describes the blob name used to identify the file in your Azure Blob Storage container.\n             *\n             * Possible values are\n             * * `'uuid'`\n             * * `'filename'`\n             * * `function`\n             *\n             * If the value is a function, Fine Uploader Azure will pass the associated file ID as a parameter when invoking your function.\n             * If the value is a function it may return one of a `qq.Promise` or a `String`\n             *\n             * @default `'uuid'`\n             */\n            name?: string | AzureBlobPropertyNameFunction;\n        }\n\n        /**\n         * AzureRequestOptions\n         */\n        export interface AzureRequestOptions extends RequestOptions {\n            /**\n             * URL for your Azure Blob Storage container\n             *\n             * @default `null`\n             */\n            containerUrl?: string;\n            /**\n             * Parameters passed along with each upload request.\n             *\n             * @default `{}`\n             */\n            params?: any;\n            /**\n             * Part of the parameter name that contains the name of the associated file which may differ from the blob name.\n             * Prefixed with 'x-ms-meta-' by Fine Uploader\n             *\n             * @default `'qqfilename'`\n             */\n            filenameParam?: string;\n        }\n\n        /**\n         * type for Azure's customHeaders function\n         */\n        export interface AzureCustomHeaderFunction {\n            (id: number): void;\n        }\n\n        /**\n         * AzureSignatureOptions\n         */\n        export interface AzureSignatureOptions {\n            /**\n             * Additional headers sent along with each signature request.\n             *\n             * If you declare a function as the value, the associated file's ID will be passed to your function when it is invoked\n             *\n             * @default `{}`\n             */\n            customHeaders?: any | AzureCustomHeaderFunction;\n            /**\n             * The endpoint that Fine Uploader can use to send GET for a SAS before sending requests off to Azure.\n             *\n             * The blob URL and underlying method type associated with the underlying REST request will be included in the query string\n             *\n             * @default `null`\n             */\n            endpoint?: string;\n        }\n\n        /**\n         * AzureUploadSuccessOptions\n         */\n        export interface AzureUploadSuccessOptions {\n            /**\n             * Additional headers sent along with each signature request\n             *\n             * @default `{}`\n             */\n            customHeaders?: any;\n            /**\n             * An endpoint that Fine Uploader should POST to when a file has been successfully uploaded to Azure Blob Storage.\n             *\n             * @default `null`\n             */\n            endpoint?: string;\n            /**\n             * The request method (i.e. POST/PUT)\n             *\n             * @default `POST`\n             */\n            method?: string;\n            /**\n             * Any additional parameters to attach to upload success file requests.\n             *\n             * ###Note:\n             * Fine Uploader will still send the `bucket`, `key`, `filename`, `UUID`, and `etag` (if available) as well\n             *\n             * @default `{}`\n             */\n            params?: any;\n        }\n\n        /**\n         * AzureFailedUploadTextDisplayOptions\n         */\n        export interface AzureFailedUploadTextDisplayOptions {\n            /**\n             * You will most likely want to keep this at the default value of 'custom'. See the UI options documentation for more info on this option.\n             *\n             * @default `'custom'`\n             */\n            mode?: string;\n        }\n\n        /**\n         * Resumable file object type for Azure\n         */\n        export interface AzureResumableFileObject extends ResumableFileObject {\n            /**\n             * The associated file's blob name in Azure Blob Storage\n             */\n            key?: string;\n        }\n\n        /**\n         * Azure Core Options\n         */\n        export interface AzureCoreOptions extends CoreOptions {\n            /**\n             * chunking options\n             */\n            chunking?: AzureChunkingOptions;\n            /**\n             * cors options\n             */\n            cors?: AzureCorsOptions;\n            /**\n             * blobProperties\n             */\n            blobProperties?: AzureBlobPropertyOptions;\n            /**\n             * RequestOptions\n             */\n            request?: AzureRequestOptions;\n            /**\n             * AzureSignatureOptions\n             */\n            signature?: AzureSignatureOptions;\n            /**\n             * AzureUploadSuccessOptions\n             */\n            uploadSuccess?: AzureUploadSuccessOptions;\n        }\n\n        /**\n         * AzureUIOptions\n         */\n        export interface AzureUIOptions extends UIOptions, AzureCoreOptions {\n            /**\n             * failedUploadText options\n             */\n            failedUploadTextDisplay?: AzureFailedUploadTextDisplayOptions;\n\n            /**\n             * chunking options\n             */\n            chunking?: AzureChunkingOptions;\n            /**\n             * cors options\n             */\n            cors?: AzureCorsOptions;\n            /**\n             * deleteFile options\n             */\n            deleteFile?: UIDeleteFileOptions;\n            /**\n             * messages\n             */\n            messages?: UIMessages;\n            /**\n             * paste UI options\n             */\n            paste?: UIPasteOptions;\n            /**\n             * UI scaling options\n             */\n            scaling?: UIScalingOptions;\n            /**\n             * UI text options\n             */\n            text?: UITextOptions;\n            /**\n             * RequestOptions\n             */\n            request?: AzureRequestOptions;\n\n        }\n\n\n    }\n\n}\n\n\ndeclare module \"fine-uploader/lib/s3\" {\n\n    import {\n        FineUploader as FineUploaderCore,\n        UIOptions,\n        UIDeleteFileOptions,\n        UIMessages,\n        UIPasteOptions,\n        UIScalingOptions,\n        UITextOptions\n    } from 'fine-uploader';\n\n    import {\n        FineUploaderBasic as FineUploaderBasicCore,\n        ChunkingOptions,\n        CorsOptions,\n        RequestOptions,\n        CoreOptions,\n        ResumableFileObject,\n        CoreEvents,\n        PromiseOptions\n    } from 'fine-uploader/lib/core';\n\n\n\n    export namespace s3 {\n\n\n        export class FineUploader extends FineUploaderCore {\n\n            constructor(fineuploaderOptions?: S3UIOptions);\n\n            /**\n             * Retrieve the S3 bucket name associated with the passed file (id). Note that the bucket name is not available before the file has started uploading\n             *\n             * @param number fileId : An ID corresponding to a file\n             * @returns string : The S3 bucket name associated with the passed file (id)\n             */\n            getBucket(fileId: number): string;\n\n            /**\n             * Retrieve the S3 object key associated with the passed file (id). Note that the key is not available before the file has started uploading.\n             *\n             * @param number fileId : An ID corresponding to a file\n             * @returns string : The S3 object key associated with the passed file (id)\n             */\n            getKey(fileId: number): string;\n\n            /**\n             * Returns an array of potentially resumable items\n             *\n             * @returns S3ResumableFileObject : An array of Resumable file items\n             */\n            getResumableFilesData(): S3ResumableFileObject[] | S3ResumableFileObject;\n\n            /**\n             * Set/update the ACL to be used for one or all file uploads. If the ID is omitted, the new ACL targets all future files that have not yet been uploaded\n             *\n             * @param any newAcl : Canned ACL value to be sent with the upload request. Used by S3\n             * @param number id : File ID to target the ACL\n             */\n            setAcl(newAcl: any, id?: number): void;\n\n            /**\n             * Pass new or initial credentials. This is used to support the no-server workflow\n             *\n             * @param any newCredentials : The new or initial credentials to set for server-less uploads\n             */\n            setCredentials(newCredentials: any): void;\n\n            /**\n             * Modify the endpoint URL where upload requests should be directed.\n             *\n             * The endpoint for a specific file or blob can be changed by passing in an optional `id` parameter.\n             * An `id` will always be a number and refers to a single file.\n             *\n             * All valid bucket URLs documented by Amazon are supported, including custom domains.\n             * SSL is also supported. If you specify a CDN endpoint URL, be sure that you are specifying a bucket as well via the `objectProperties.bucket` option.\n             *\n             * @param string endpoint : A URL for the S3 bucket or a CDN that forwards the request on to S3\n             * @param number id : An ID corresponding to a file\n             */\n            setEndpoint(endpoint: string, id?: number): void;\n\n            /**\n             * Modify the endpoint that Fine Uploader should POST to when a file has been successfully uploaded to S3\n             *\n             * @param string endpoint : An endpoint that Fine Uploader should POST to when a file has been successfully uploaded to S3\n             * @param number id : An ID corresponding to a file\n             */\n            setUploadSuccessEndpoint(endpoint: string, id?: number): void;\n\n            /**\n             * Set additional parameters for the upload success request.\n             *\n             * ###Note:\n             * Fine Uploader will still send the `bucket`, `name`, `key`, `filename`, `UUID`, and `etag` (if available) as well\n             *\n             * @param object newParams : The additional parameters set for the upload request\n             * @param number id : A file id to apply these upload success parameters to\n             */\n            setUploadSuccessParams(newParams: any, id?: number): void;\n        }\n\n        export class FineUploaderBasic extends FineUploaderBasicCore {\n\n            constructor(fineuploaderOptions?: S3CoreOptions);\n\n            /**\n             * Retrieve the S3 bucket name associated with the passed file (id). Note that the bucket name is not available before the file has started uploading\n             *\n             * @param number fileId : An ID corresponding to a file\n             * @returns string : The S3 bucket name associated with the passed file (id)\n             */\n            getBucket(fileId: number): string;\n\n            /**\n             * Retrieve the S3 object key associated with the passed file (id). Note that the key is not available before the file has started uploading.\n             *\n             * @param number fileId : An ID corresponding to a file\n             * @returns string : The S3 object key associated with the passed file (id)\n             */\n            getKey(fileId: number): string;\n\n            /**\n             * Returns an array of potentially resumable items\n             *\n             * @returns S3ResumableFileObject : An array of Resumable file items\n             */\n            getResumableFilesData(): S3ResumableFileObject[] | S3ResumableFileObject;\n\n            /**\n             * Set/update the ACL to be used for one or all file uploads. If the ID is omitted, the new ACL targets all future files that have not yet been uploaded\n             *\n             * @param any newAcl : Canned ACL value to be sent with the upload request. Used by S3\n             * @param number id : File ID to target the ACL\n             */\n            setAcl(newAcl: any, id?: number): void;\n\n            /**\n             * Pass new or initial credentials. This is used to support the no-server workflow\n             *\n             * @param any newCredentials : The new or initial credentials to set for server-less uploads\n             */\n            setCredentials(newCredentials: any): void;\n\n            /**\n             * Modify the endpoint URL where upload requests should be directed.\n             *\n             * The endpoint for a specific file or blob can be changed by passing in an optional `id` parameter.\n             * An `id` will always be a number and refers to a single file.\n             *\n             * All valid bucket URLs documented by Amazon are supported, including custom domains.\n             * SSL is also supported. If you specify a CDN endpoint URL, be sure that you are specifying a bucket as well via the `objectProperties.bucket` option.\n             *\n             * @param string endpoint : A URL for the S3 bucket or a CDN that forwards the request on to S3\n             * @param number id : An ID corresponding to a file\n             */\n            setEndpoint(endpoint: string, id?: number): void;\n\n            /**\n             * Modify the endpoint that Fine Uploader should POST to when a file has been successfully uploaded to S3\n             *\n             * @param string endpoint : An endpoint that Fine Uploader should POST to when a file has been successfully uploaded to S3\n             * @param number id : An ID corresponding to a file\n             */\n            setUploadSuccessEndpoint(endpoint: string, id?: number): void;\n\n            /**\n             * Set additional parameters for the upload success request.\n             *\n             * ###Note:\n             * Fine Uploader will still send the `bucket`, `name`, `key`, `filename`, `UUID`, and `etag` (if available) as well\n             *\n             * @param object newParams : The additional parameters set for the upload request\n             * @param number id : A file id to apply these upload success parameters to\n             */\n            setUploadSuccessParams(newParams: any, id?: number): void;\n\n        }\n\n\n        /**\n         * S3CredentialsOptions\n         */\n        export interface S3CredentialsOptions {\n            /**\n             * Temporary public AWS key\n             *\n             * @default `null`\n             */\n            accessKey?: string;\n            /**\n             * Expiration date for temporary credentials. May be an ISO 8601 String or a `Date` object.\n             *\n             * @default `null`\n             */\n            expiration?: string | Date;\n            /**\n             * Temporary secret AWS key\n             *\n             * @default `null`\n             */\n            secretKey?: string;\n            /**\n             * Session token associated with the temporary credentials\n             *\n             * @default `null`\n             */\n            sessionToken?: string;\n        }\n\n        /**\n         * S3ChunkingOptions\n         */\n        export interface S3ChunkingOptions extends ChunkingOptions {\n            /**\n             * The maximum size of each part, in bytes\n             *\n             * @default `5242880`\n             */\n            partSize?: number;\n        }\n\n        /**\n         * S3CorsOptions\n         */\n        export interface S3CorsOptions extends CorsOptions {\n            /**\n             * Enables or disables cross-domain ajax calls (if the `expected` property is true) in IE9 and older.\n             *\n             * @default `true`\n             */\n            allowXdr?: boolean;\n        }\n\n        /**\n         * S3iFrameSupportOptions\n         */\n        export interface S3iFrameSupportOptions {\n            /**\n             * This is required if you plan on supporting browsers that do not implement the File API, such as IE9 and older.\n             * This must point to a blank page on the same origin/domain as the page hosting Fine Uploader\n             *\n             * @default `null`\n             */\n            localBlankPagePath?: string;\n        }\n\n        /**\n         * type for S3's bucket object property\n         */\n        export interface BucketFunction {\n            (id: number): PromiseOptions | string;\n        }\n\n        /**\n         * type for S3's host object property\n         */\n        export interface HostFunction {\n            (id: number): PromiseOptions | string;\n        }\n\n        /**\n         * type for S3's key object property\n         */\n        export interface KeyFunction {\n            (id: number): PromiseOptions | string;\n        }\n\n        /**\n         * S3ObjectPropertyOptions\n         */\n        export interface S3ObjectPropertyOptions {\n            /**\n             * This value corresponds to a canned ACL\n             *\n             * @default `'private'`\n             */\n            acl?: string;\n            /**\n             * Describes the name of the bucket used to house the file in S3.\n             *\n             * This is required if the bucket cannot be determined by examining the endpoint (such as if you are using a CDN as an endpoint).\n             * Possible values are a string representing the bucket name, or a function.\n             *\n             * If the value is a function, Fine Uploader S3 will pass the associated file ID as a parameter when invoking your function.\n             * If the value is a function it may return a `promise` or a `String`\n             *\n             * @default `(assumes the bucket can be determined by parsing the endpoint string)`\n             */\n            bucket?: string | BucketFunction;\n            /**\n             * The hostname of your S3 bucket.\n             *\n             * This is required if you are using version 4 signatures and sending files through a CDN.\n             * Possible values are a string representing the host name, or a function.\n             *\n             * If the value is a function, Fine Uploader S3 will pass the associated file ID as a parameter when invoking your function.\n             * If the value is a function it may return a `promise` or a `String`.\n             *\n             * @default `(uses the request endpoint to determine the hostname)`\n             */\n            host?: string | HostFunction;\n            /**\n             * Describes the object key used to identify the file in your S3 bucket.\n             *\n             * Possible values are 'uuid', 'filename' or a function.\n             *\n             * If the value is a function, Fine Uploader S3 will pass the associated file ID as a parameter when invoking your function.\n             * If the value is a function it may return one of a `promise` or a `String`.\n             *\n             * @default `'uuid'`\n             */\n            key?: string | KeyFunction;\n            /**\n             * Set this to true if you would like to use the reduced redundancy storage class for all objects uploaded to S3\n             *\n             * @default `false`\n             */\n            reducedRedundancy?: boolean;\n            /**\n             * Version 4 signatures only: The S3 region identifier for the target bucket\n             *\n             * @default `'us-east-1'`\n             */\n            region?: string;\n            /**\n             * Set this to true if you would like all uploaded files to be encrypted by AWS\n             *\n             * @default `false`\n             */\n            serverSideEncryption?: boolean;\n        }\n\n        /**\n         * S3RequestOptions\n         */\n        export interface S3RequestOptions extends RequestOptions {\n            /**\n             * Your AWS public key. NOT YOUR SECRET KEY. Ignored if `credentials` have been set\n             *\n             * @default `null`\n             */\n            accessKey?: string;\n            /**\n             * Number of milliseconds to add to the `x-amz-date` header and the policy expiration date to account for clock drift on the browser/client machine\n             *\n             * @default `0`\n             */\n            clockDrift?: number;\n            /**\n             * URL for your S3 bucket or the URL of a CDN that forwards the request to S3.\n             *\n             * All valid bucket URLs documented by Amazon are supported, including custom domains. SSL is also supported.\n             * If you use a CDN address, be sure to specify the bucket via the objectProperties.bucket option\n             *\n             * @default `null`\n             */\n            endpoint?: string;\n            /**\n             * Part of the parameter name that contains the name of the associated file which may differ from the key name.\n             *\n             * Prefixed with 'x-amz-meta-' by Fine Uploader\n             *\n             * @default `'qqfilename'`\n             */\n            filenameParam?: string;\n            /**\n             * Parameters passed along with each upload request\n             *\n             * @default `{}`\n             */\n            params?: any;\n        }\n\n        /**\n         * type for S3's customHeaders function\n         */\n        export interface S3CustomHeaderFunction {\n            (id: number): void;\n        }\n\n        /**\n         * S3SignatureOptions\n         */\n        export interface S3SignatureOptions {\n            /**\n             * Additional headers sent along with each signature request.\n             *\n             * If you declare a function as the value, the associated file's ID will be passed to your function when it is invoked\n             *\n             * @default `{}`\n             */\n            customHeaders?: any | S3CustomHeaderFunction;\n            /**\n             * The endpoint that Fine Uploader can use to send policy documents (HTML form uploads) or other strings to sign (REST requests) before sending requests off to S3\n             *\n             * @default `null`\n             */\n            endpoint?: string;\n            /**\n             * The AWS/S3 signature version to use. Currently supported values are `2` and `4`. Directly related to `objectProperties.region`\n             *\n             * @default `2`\n             */\n            version?: number;\n        }\n\n        /**\n         * S3UploadSuccessOptions\n         */\n        export interface S3UploadSuccessOptions {\n            /**\n             * Additional headers sent along with each signature request\n             *\n             * @default `{}`\n             */\n            customHeaders?: any;\n            /**\n             * An endpoint that Fine Uploader should POST to when a file has been successfully uploaded to S3\n             *\n             * @default `null`\n             */\n            endpoint?: string;\n            /**\n             * The request method (i.e. POST/PUT)\n             *\n             * @default `POST`\n             */\n            method?: string;\n            /**\n             * Any additional parameters to attach to upload success file requests.\n             *\n             * ###Note:\n             * Fine Uploader will still send the `bucket`, `key`, `filename`, `UUID`, and `etag` (if available) as well\n             *\n             * @default `{}`\n             */\n            params?: any;\n        }\n\n        /**\n         * Contains S3's Core options\n         */\n        export interface S3CoreOptions extends CoreOptions {\n            /**\n             * credentials\n             */\n            credentials?: S3CredentialsOptions;\n            /**\n             * chunking options\n             */\n            chunking?: S3ChunkingOptions;\n            /**\n             * cors options\n             */\n            cors?: S3CorsOptions;\n            /**\n             * iframeSupport options\n             */\n            iframeSupport?: S3iFrameSupportOptions;\n            /**\n             * objectProperties\n             */\n            objectProperties?: S3ObjectPropertyOptions;\n            /**\n             * request options\n             */\n            request?: S3RequestOptions;\n            /**\n             * signature options\n             */\n            signature?: S3SignatureOptions;\n            /**\n             * upload success options\n             */\n            uploadSuccess?: S3UploadSuccessOptions;\n        }\n\n        /**\n         * S3FailedUploadTextDisplayOptions\n         */\n        export interface S3FailedUploadTextDisplayOptions {\n            /**\n             * You will most likely want to keep this at the default value of 'custom'. See the UI options documentation for more info on this option.\n             *\n             * @default `'custom'`\n             */\n            mode?: string;\n        }\n\n        /**\n         * onCredentialsExpired function type\n         */\n        export interface OnCredentialsExpired {\n            (): PromiseOptions;\n        }\n\n        /**\n         * S3 Callback functions\n         */\n        export interface S3Events extends CoreEvents {\n            /**\n             * Called before a request is sent to S3 if the temporary credentials have expired.\n             *\n             * You must return a promise. If your attempt to refresh the temporary credentials is successful, you must fulfill the promise via the success method, passing the new credentials object.\n             * Otherwise, call failure with a descriptive reason.\n             */\n            onCredentialsExpired?: OnCredentialsExpired;\n        }\n\n        /**\n         * S3UIOptions\n         */\n        export interface S3UIOptions extends UIOptions, S3CoreOptions {\n            /**\n             * failedUploadText options\n             */\n            failedUploadTextDisplay?: S3FailedUploadTextDisplayOptions;\n            /**\n             * chunking options\n             */\n            chunking?: S3ChunkingOptions;\n            /**\n             * cors options\n             */\n            cors?: S3CorsOptions;\n            /**\n             * request options\n             */\n            request?: S3RequestOptions;\n            /**\n             * deleteFile options\n             */\n            deleteFile?: UIDeleteFileOptions;\n            /**\n             * messages\n             */\n            messages?: UIMessages;\n            /**\n             * paste UI options\n             */\n            paste?: UIPasteOptions;\n            /**\n             * UI scaling options\n             */\n            scaling?: UIScalingOptions;\n            /**\n             * UI text options\n             */\n            text?: UITextOptions;\n        }\n\n        /**\n         * Resumable file object type for S3\n         */\n        export interface S3ResumableFileObject extends ResumableFileObject {\n            /**\n             * The associated object's S3 key\n             */\n            key?: string;\n        }\n    }\n\n}"
  },
  {
    "path": "client/typescript/fine-uploader.test.ts",
    "content": "import { FineUploader, UIOptions } from 'fine-uploader';\nimport { s3 } from 'fine-uploader/lib/s3';\nimport { azure } from 'fine-uploader/lib/azure';\nimport { PromiseOptions } from 'fine-uploader/lib/core';\n\n/**\n * Prepare/set options for the core + UI FineUploader\n */\nlet uiOptions: UIOptions = {\n    debug: false,\n    autoUpload: false,\n    element: document.getElementById('fine-uploader-manual-trigger'),\n    template: \"qq-template-manual-trigger\",\n    request: {\n        endpoint: \"/server/upload\"\n    },\n    deleteFile: {\n        enabled: true,\n        endpoint: '/uploads'\n    },\n    retry: {\n        enableAuto: true\n    }\n};\n\n/**\n * Instantiate the FineUploader and pass in the uiOptions\n */\nlet uploader = new FineUploader(uiOptions);\n\n\n/**\n * Prepare/set options for the Amazon S3 FineUploader\n */\nlet s3UIOptions: s3.S3UIOptions = {\n    debug: true,\n    element: document.getElementById('fine-uploader'),\n    request: {\n        endpoint: '{ YOUR_BUCKET_NAME }.s3.amazonaws.com',\n        accessKey: '{ YOUR_ACCESS_KEY }'\n    },\n    signature: {\n        endpoint: '/s3/signature'\n    },\n    uploadSuccess: {\n        endpoint: '/s3/success'\n    },\n    iframeSupport: {\n        localBlankPagePath: '/success.html'\n    },\n    retry: {\n        enableAuto: true // defaults to false\n    },\n    deleteFile: {\n        enabled: true,\n        endpoint: '/s3handler'\n    }\n}\nlet s3Uploader = new s3.FineUploader(s3UIOptions);\n\n\n\n/**\n * Prepare/set options for the Amazon S3 FineUploader\n */\nlet azureUIOptions: azure.AzureUIOptions = {\n    element: document.getElementById('fine-uploader'),\n    request: {\n        endpoint: 'https://{ YOUR_STORAGE_ACCOUNT_NAME }.blob.core.windows.net/{ YOUR_CONTAINER_NAME }'\n    },\n    signature: {\n        endpoint: '/signature'\n    },\n    uploadSuccess: {\n        endpoint: '/success'\n    },\n    retry: {\n        enableAuto: true\n    },\n    deleteFile: {\n        enabled: true\n    }\n}\nlet azureUploader = new azure.FineUploader(azureUIOptions);\n\n/**\n * Manually upload files to the server. This method should be called on some button click event\n */\nuploader.uploadStoredFiles();\ns3Uploader.uploadStoredFiles();\nazureUploader.uploadStoredFiles();\n\n//FineUploader's Promise Implementation\nlet promise: PromiseOptions = new uploader.Promise();\nlet result = {};\npromise.failure(result);\npromise.success(result);\npromise.then(() => {\n    //promise is successfully fulfilled, do something here\n}, () => {\n    //promise is un-successfully fulfilled, do something here\n});\npromise.done(() => {\n    //promise is fulfilled whether successful or not, do something here\n});"
  },
  {
    "path": "config/karma.conf.js",
    "content": "/* jshint node: true */\nvar path = require(\"path\");\n\nmodule.exports = function(config, options) {\n    \"use strict\";\n\n    return config.set({\n        browsers: [\"Firefox\"],\n        captureTimeout: 60000,\n        files: [\n            path.resolve(\"_build/all.fine-uploader.js\"),\n            path.resolve(\"test/static/third-party/assert/assert.js\"),\n            path.resolve(\"test/static/third-party/jquery/jquery.js\"),\n            path.resolve(\"test/static/third-party/jquery.simulate/jquery.simulate.js\"),\n            path.resolve(\"test/static/third-party/purl/purl.js\"),\n            path.resolve(\"test/static/third-party/sinon/sinon.js\"),\n            path.resolve(\"test/static/third-party/sinon/event.js\"),\n            path.resolve(\"test/static/third-party/sinon/fake_xml_http_request.js\"),\n            path.resolve(\"test/static/local/formdata.js\"),\n            path.resolve(\"test/static/local/karma-runner.js\"),\n            path.resolve(\"test/static/local/blob-maker.js\"),\n            path.resolve(\"test/static/third-party/q/q-1.0.1.js\"),\n            path.resolve(\"node_modules/pica/dist/pica.js\"),\n            path.resolve(\"test/static/local/helpme.js\"),\n            path.resolve(\"test/unit/**/*.js\")\n        ],\n        logLevel: config.LOG_INFO,\n        logColors: true,\n        frameworks: [\"mocha\"],\n        reporters: [\"spec\"],\n        singleRun: true\n    });\n};\n"
  },
  {
    "path": "docs/_static/css/main.css",
    "content": "\n/* ==========================================================================\n   Author's custom styles\n   ========================================================================== */\nbody\n{\n    padding-top: 60px;\n    padding-bottom: 40px;\n    color: #525252;\n    background-color: #FCFCFA;\n    font-family:'Maven Pro', sans-serif !important;\n}\n\n@media (max-width: 979px)\n{\n    body\n    {\n        padding-top: 0px;\n    }\n}\ncode, pre { color: #f87436; }\npre\n{\n    overflow: -moz-scrollbars-vertical;\n    overflow: scroll;\n    word-wrap: normal !important;\n    white-space: pre !important;\n}\n.nav > .brand > img\n{\n    margin-top: -13px;\n}\n.dropdown-menu\n{\n    min-width: 220px;\n}\n.dropdown-menu.api\n{\n    padding-left: 20px;\n}\nh1, h2, h3\n{\n    margin: 20px 0;\n}\nh1 {\n    font-size: 30px;\n    color: #00abc7;\n}\nh2\n{\n    size: 24px;\n    color: #00abc7;\n}\nh3\n{\n    color: #00ABC7;\n}\nh2, h3, h4, h5\n{\n    margin-top: -40px;\n    padding-top: 60px;\n}\n\n.alert .alert-heading\n{\n    padding-top: 0px;\n}\n\n.navbar-inverse .brand, .navbar-inverse .nav > li > a,\n.navbar-inverse .brand, .navbar-inverse .nav > li > button,\n.navbar-inverse .nav-collapse .nav > li > a,\n.navbar-inverse .nav-collapse .nav > li > button,\n.navbar-inverse .nav-collapse .dropdown-menu a\n{\n    color: #6C6C6C;\n    text-shadow:none;\n    font-size:13px;\n    font-weight: normal;\n    text-transform:uppercase;\n    text-decoration: none;\n}\n.navbar-inverse .brand, .navbar-inverse .nav > li > a:hover,\n.navbar-inverse .brand, .navbar-inverse .nav > li > button:hover\n{\n    color: #F87436 !important;\n}\n.navbar-inverse .nav li.dropdown.open > .dropdown-toggle,\n.navbar-inverse .nav li.dropdown.active > .dropdown-toggle,\n.navbar-inverse .nav li.dropdown.open.active > .dropdown-toggle\n{\n    background-color:#525252;\n}\n.dropdown-menu > .active > a,\n.dropdown-menu > .active > a:hover,\n.dropdown-menu > .active > a:focus\n{\n    background-color: #66ccdd;\n    background-image:linear-gradient(to bottom, #66ccdd, #66ccdd);\n}\n.navbar-inverse .btn-navbar\n{\n    background-color:#F87436;\n    background-image:linear-gradient(to bottom, #F87436, #F7631E)\n}\n.navbar-inverse .btn-navbar:hover\n{\n    background-color:#F7631E;\n}\n.navbar-inverse .nav-collapse .nav > li > a:hover,\n.navbar-inverse .nav-collapse .nav > li > button:hover\n{\n    background-color:#FFFfff;\n}\n.navbar-inverse .nav-collapse .dropdown-menu a:hover\n{\n    background:none;\n    color:#F87436;\n}\n.navbar-inverse .nav .active > a:hover,\n.navbar-inverse .nav .active > a:focus,\n.navbar-inverse .nav .active > button:focus\n{\n    color:#005580;\n    text-decoration:underline;\n}\n.navbar-inverse .navbar-inner\n{\n    background: #66ccdd;\n    /* fallback for non-supporting browsers */\n    background-image: -webkit-gradient(radial,33% 25px,0,center center,141,from(#ddf3f7),to(#00abc7));\n    /* old WebKit Syntax */\n    background-image: -webkit-radial-gradient(33% 25px,circle contain,#ddf3f7 0%,#00abc7 5000%);\n    /* New WebKit syntax */\n    background-image: -moz-radial-gradient(33% 25px,circle contain,#ddf3f7 0%,#00abc7 5000%);\n    background-image: -ms-radial-gradient(33% 25px,circle contain,#ddf3f7 0%,#00abc7 5000%);\n    /* IE10+ */\n    background-image: -o-radial-gradient(33% 25px,circle contain,#ddf3f7 0%,#00abc7 5000%);\n    /* Opera (13?) */\n    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#D1F8FF',endColorstr='#00859b',GradientType=0);\n    -webkit-box-shadow: inset 0 1px 5px rgba(100,100,100,.2), inset 0 -1px 5px rgba(100,100,100,.2);\n    -moz-box-shadow: inset 0 1px 5px rgba(0,100,100,.2), inset 0 -1px 5px rgba(100,100,100,.2);\n    box-shadow: inset 0 1px 5px rgba(100,100,100,.2), inset 0 -1px 7px rgba(100,100,100,.2);\n    border:none;\n}\n\n.navbar .nav > li > button.btn.btn-link.dropdown-toggle\n{\n    padding-top: 10px;\n    padding-bottom: 8px;\n    margin-top: 0px;\n    font-family:'Maven Pro', sans-serif !important;\n}\n\n.content { width: 80%; max-width: 960px; }\n\na:hover,\nbutton.btn-link\n{\n    -webkit-transition: all .15s ease-in-out;\n    -moz-transition: all .15s ease-in-out;\n    transition: all .15s ease-in-out;\n}\na:visited\n{\n    color: #0088CC;\n}\n.reference-table thead { font-weight: bold; }\n.dropdown-menu .divider { margin-right: 20px; }\n\n.dropdown-backdrop\n{\n    position: static;\n}\n\ndiv .accordion-heading { padding: 8px; }\ndt { font-weight: normal; }\n\n.accordion-inner { padding: 9px 0px; }\n.accordion-inner .nav-list { padding-right: 2px; padding-left: 10px; }\n\nh3 a, a:hover, a:focus, :visited {\n    text-decoration: none;\n}\n\nh4 a, a:hover, a:focus, :visited {\n    text-decoration: none;\n}\n\n.label-object {\n    background-color: #00abc7;\n}\n.label-array {\n    background-color: #C71B00;\n}\n.label-function {\n    background-color: #00C71B;\n}\n.label-integer {\n    background-color: #FF5B42;\n}\n.label-string {\n    background-color: #00abc7;\n}\n.label-boolean {\n    background-color: #00859b;\n}\n.label-htmlelement {\n    background-color: #f87436;\n}\n.label-qq.promise {\n    background-color: #C700AC;\n}\n.label-xmlhttprequest {\n    background-color: #66ccdd;\n}\n.label-xmldomainrequest {\n    background-color: #66ccdd;\n}\n.label-undefined {\n    background-color: #525252;\n}\n.label-null {\n    background-color: #525252;\n}\n.label-other {\n    background-color: #7c858c;\n}\n\n.event-params > hr {\n    margin: 2px;\n}\n.method-return > hr {\n    margin: 2px;\n}\n.method-params > hr {\n    margin: 2px;\n}\n\n.version-number {\n    padding: 1px;\n    margin-right: 5px;\n    font-size: 18px;\n}\n\n.tag-chooser {\n    margin-bottom: 0;\n    padding: 3px 20px;\n}\n\n@media (max-width: 979px) {\n   .tag-chooser {\n      padding: 3px 15px;\n   }\n}\n\n.tag-chooser button {\n    margin-top: 0 !important;\n    padding: 2px 5px;\n}\n\n.tag-input {\n    border: 1px solid rgba(0,0,0,0.2);\n    border-radius: 3px;\n    padding-left: 5px;\n    width: 75px;\n}\n"
  },
  {
    "path": "docs/_static/css/pygments.css",
    "content": ".hll { background-color: #ffffcc }\n.c { color: #8f5902; font-style: italic } /* Comment */\n.err { color: #a40000; border: 0px solid #ef2929 } /* Error */\n.g { color: #000000 } /* Generic */\n.k { color: #204a87; font-weight: bold } /* Keyword */\n.l { color: #000000 } /* Literal */\n.n { color: #000000 } /* Name */\n.o { color: #ce5c00; font-weight: bold } /* Operator */\n.x { color: #000000 } /* Other */\n.p { color: #000000; font-weight: bold } /* Punctuation */\n.cm { color: #8f5902; font-style: italic } /* Comment.Multiline */\n.cp { color: #8f5902; font-style: italic } /* Comment.Preproc */\n.c1 { color: #8f5902; font-style: italic } /* Comment.Single */\n.cs { color: #8f5902; font-style: italic } /* Comment.Special */\n.gd { color: #a40000 } /* Generic.Deleted */\n.ge { color: #000000; font-style: italic } /* Generic.Emph */\n.gr { color: #ef2929 } /* Generic.Error */\n.gh { color: #000080; font-weight: bold } /* Generic.Heading */\n.gi { color: #00A000 } /* Generic.Inserted */\n.go { color: #000000; font-style: italic } /* Generic.Output */\n.gp { color: #8f5902 } /* Generic.Prompt */\n.gs { color: #000000; font-weight: bold } /* Generic.Strong */\n.gu { color: #800080; font-weight: bold } /* Generic.Subheading */\n.gt { color: #a40000; font-weight: bold } /* Generic.Traceback */\n.kc { color: #204a87; font-weight: bold } /* Keyword.Constant */\n.kd { color: #204a87; font-weight: bold } /* Keyword.Declaration */\n.kn { color: #204a87; font-weight: bold } /* Keyword.Namespace */\n.kp { color: #204a87; font-weight: bold } /* Keyword.Pseudo */\n.kr { color: #204a87; font-weight: bold } /* Keyword.Reserved */\n.kt { color: #204a87; font-weight: bold } /* Keyword.Type */\n.ld { color: #000000 } /* Literal.Date */\n.m { color: #0000cf; font-weight: bold } /* Literal.Number */\n.s { color: #4e9a06 } /* Literal.String */\n.na { color: #c4a000 } /* Name.Attribute */\n.nb { color: #204a87 } /* Name.Builtin */\n.nc { color: #000000 } /* Name.Class */\n.no { color: #000000 } /* Name.Constant */\n.nd { color: #5c35cc; font-weight: bold } /* Name.Decorator */\n.ni { color: #ce5c00 } /* Name.Entity */\n.ne { color: #cc0000; font-weight: bold } /* Name.Exception */\n.nf { color: #000000 } /* Name.Function */\n.nl { color: #f57900 } /* Name.Label */\n.nn { color: #000000 } /* Name.Namespace */\n.nx { color: #000000 } /* Name.Other */\n.py { color: #000000 } /* Name.Property */\n.nt { color: #204a87; font-weight: bold } /* Name.Tag */\n.nv { color: #000000 } /* Name.Variable */\n.ow { color: #204a87; font-weight: bold } /* Operator.Word */\n.w { color: #f8f8f8; text-decoration: underline } /* Text.Whitespace */\n.mf { color: #0000cf; font-weight: bold } /* Literal.Number.Float */\n.mh { color: #0000cf; font-weight: bold } /* Literal.Number.Hex */\n.mi { color: #0000cf; font-weight: bold } /* Literal.Number.Integer */\n.mo { color: #0000cf; font-weight: bold } /* Literal.Number.Oct */\n.sb { color: #4e9a06 } /* Literal.String.Backtick */\n.sc { color: #4e9a06 } /* Literal.String.Char */\n.sd { color: #8f5902; font-style: italic } /* Literal.String.Doc */\n.s2 { color: #4e9a06 } /* Literal.String.Double */\n.se { color: #4e9a06 } /* Literal.String.Escape */\n.sh { color: #4e9a06 } /* Literal.String.Heredoc */\n.si { color: #4e9a06 } /* Literal.String.Interpol */\n.sx { color: #4e9a06 } /* Literal.String.Other */\n.sr { color: #4e9a06 } /* Literal.String.Regex */\n.s1 { color: #4e9a06 } /* Literal.String.Single */\n.ss { color: #4e9a06 } /* Literal.String.Symbol */\n.bp { color: #3465a4 } /* Name.Builtin.Pseudo */\n.vc { color: #000000 } /* Name.Variable.Class */\n.vg { color: #000000 } /* Name.Variable.Global */\n.vi { color: #000000 } /* Name.Variable.Instance */\n.il { color: #0000cf; font-weight: bold } /* Literal.Number.Integer.Long */\n"
  },
  {
    "path": "docs/_static/js/main.js",
    "content": "/**\n * Resize the dropdown-menu's to be 80% of the window height, and\n * add an overflow-y property so that all the elements are shown.\n */\n$(function() {\n    'use strict';\n\n    var setDropdownHeight = function(){\n\n        var dropdownHeight = $(window.top).height() * .80; // take 80% of the current window height\n        $('.dropdown-menu').css('max-height', dropdownHeight);\n        $('.dropdown-menu').css('overflow-y',  'auto');\n\n    };\n\n    $(window).resize(setDropdownHeight);\n    setDropdownHeight();\n});\n"
  },
  {
    "path": "docs/_static/js/navbar.js",
    "content": "(function() {\n    document.querySelector('.tag-chooser').addEventListener('submit', function(event) {\n        event.preventDefault()\n        var version = event.target.elements['tag'].value\n        if (version.trim()) {\n            window.location.href = '/tag/' + version\n        }\n    })\n}())\n"
  },
  {
    "path": "docs/_static/js/sidebar.js",
    "content": "var renderSidebarNav = function(type, headers) {\n    var $sidebar = $(\".sidebar .accordion\"),\n        sidebarContents = '';\n\n    $.each(headers, function (index, header) {\n        var $typeEl = $(\".\" + type + \"s-\" + header),\n            typeTitle = $typeEl.find('h2').text(),\n            typeLink = \"#\"+$typeEl.find('h2').attr('id');\n\n        sidebarContents += \"<div class='accordion-group'>\" +\n                                \"<div class='accordion-heading'>\" +\n                                    \"<a href='#' data-target='.\" + type + \"-\" + header + \"-accordion' data-parent='.sidebar-accordion' data-toggle='collapse'>\" +\n                                        \"<b>\" + typeTitle + \"</b>\" +\n                                    \"</a>\" +\n                                \"</div>\" +\n                                \"<div class='\" + type + \"-\" + header + \"-accordion accordion-body collapse in'>\" +\n                                    \"<div class='accordion-inner'>\" +\n                                        \"<ul class='nav nav-list'>\";\n        $typeEl.children('.' + type).each(function (idx, sub_type) {\n            var el = \"h4\",\n                $type = $(sub_type).find(el),\n                title = type === 'event' ? $type.text() : $type.attr('id'),\n                link = '#' + $type.attr('id');\n\n            sidebarContents +=  \"<li><a href='\" + link + \"'>\" + title + \"</a></li>\";\n        });\n\n        $typeEl.children('.' + type + '-parent').each(function (idx, sub_type) {\n            var el = \"h3\",\n                $type = $(sub_type).find(el),\n                title = type === 'event' ? $type.text() : $type.attr('id'),\n                link = '#' + $type.attr('id');\n\n            sidebarContents +=  \"<li><a href='\" + link + \"'>\" + title + \"</a></li>\";\n        });\n\n        sidebarContents +=              \"</ul>\" +\n                                    \"</div>\" +\n                                \"</div>\" +\n                            \"</div>\";\n    });\n    $sidebar.append(sidebarContents);\n}\n\nvar renderOptionsSidebarNav = function(option_types) {\n    renderSidebarNav('option', option_types);\n}\n\nvar renderMethodsSidebarNav = function(method_types) {\n    renderSidebarNav('method', method_types);\n}\n\nvar renderEventsSidebarNav = function(event_types) {\n    renderSidebarNav('event', event_types);\n}\n"
  },
  {
    "path": "docs/_templates/api.html",
    "content": "{% from \"_templates/macros/code.html\" import options_table, events_table, methods_table, code_table %}\n{% from \"_templates/macros/alerts.html\" import alert, label %}\n{% extends \"_templates/layout.html\" %}\n{% block content %}{% endblock %}\n"
  },
  {
    "path": "docs/_templates/base.html",
    "content": "{% from \"_templates/macros/code.html\" import options_table, events_table, methods_table, code_table, api_method, api_parent_option, api_option, api_event, api_links, label %}\n{% from \"_templates/macros/alerts.html\" import alert, label %}\n{% from \"_templates/macros/github.html\" import issue %}\n{% extends \"_templates/layout.html\" %}\n{% block content %}{% endblock %}\n"
  },
  {
    "path": "docs/_templates/feature.html",
    "content": "{% from \"_templates/macros/code.html\" import options_table, events_table, methods_table, code_table %}\n{% from \"_templates/macros/alerts.html\" import alert, label %}\n{% extends \"_templates/layout.html\" %}\n\n{% block content %}\n{% endblock %}\n"
  },
  {
    "path": "docs/_templates/footer.html",
    "content": ""
  },
  {
    "path": "docs/_templates/layout.html",
    "content": "<!DOCTYPE html>\n<!--[if IE 8]>         <html class=\"no-js lt-ie9\" lang=\"en\"> <![endif]-->\n<!--[if gt IE 8]><!--> <html class=\"no-js\" lang=\"en\"> <!--<![endif]-->\n{% block head %}\n    <head>\n        <title>{% block page_title %}{{ page_title }}{% if page_title %} | {% endif %}Fine Uploader Documentation{% endblock %}</title>\n        {% block meta %}\n        <meta charset=\"utf-8\">\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n        <meta name=\"description\" content=\"{% block description %}{% endblock %}\">\n        <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge,chrome=1\">\n        <link rel=\"icon\" href=\"/favicon.ico\">\n        {% endblock %}\n\n        {% block css_bootstrap %}\n        <link href=\"//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/2.3.2/css/bootstrap.min.css\" rel=\"stylesheet\">\n        <link href=\"//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/2.3.2/css/bootstrap-responsive.min.css\" rel=\"stylesheet\">\n        {% endblock %}\n        {% block css_font %}\n        <link href='https://fonts.googleapis.com/css?family=Maven+Pro:700,400' rel='stylesheet' type='text/css'>\n        {% endblock %}\n        {% block css_site %}\n        <link rel=\"stylesheet\" href=\"{{ ASSETS }}/css/main.css\" />\n        <link rel=\"stylesheet\" href=\"{{ ASSETS }}/css/pygments.css\" />\n        {% endblock %}\n\n        {% block css_extra %}{% endblock %}\n        \n        {% block js_modernizr %}\n        <script src=\"//cdnjs.cloudflare.com/ajax/libs/modernizr/2.6.2/modernizr.min.js\"></script>\n        {% endblock %}\n\n        {% block js_head %}{% endblock %}\n{% endblock %}\n    </head>\n    <body>\n        <!--[if lt IE 7]>\n            <p class=\"chromeframe\">You are using an <strong>outdated</strong> browser. Please <a href=\"http://browsehappy.com/\">upgrade your browser</a> or <a href=\"http://www.google.com/chromeframe/?redirect=true\">activate Google Chrome Frame</a> to improve your experience.</p>\n        <![endif]-->\n{% block body %}\n\n        {% include \"_templates/navbar.html\" %}\n\n\n        {% block pre_container %}{% endblock %}\n        <div class=\"container-fluid\">\n            <div class='row-fluid'>\n            {% block container %}\n\n                <div class=\"span2 sidebar\">\n                {% block sidebar %}{% endblock %}\n                </div>\n\n                <div class=\"span9 content\">\n                    <span class=\"pull-right version-number\"><p>{{ PKG['version'] }}</p></span>\n\n                {% block pre_content %}{% endblock %}\n\n                {% block content %}{% endblock %}\n\n                {% block post_content %}{% endblock %}\n                </div>\n\n            {% endblock %}\n            </div>\n            {% include \"_templates/footer.html\" %}\n        </div> <!-- /container -->\n        {% block post_container %}{% endblock %}\n\n        {% block js_jquery %}\n            <script src=\"//ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js\"></script>\n        {% endblock %}\n        {% block js_bootstrap %}\n            <!--\n            <script src=\"http://netdna.bootstrapcdn.com/twitter-bootstrap/2.3.2/js/bootstrap.min.js\"></script>\n            -->\n        <script src=\"//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/2.3.2/js/bootstrap.min.js\"></script>\n        {% endblock %}\n\n        {% block js_analytics %}\n            {# Via HTML5BP. Asynchronous Google Analytics snippet. mathiasbynens.be/notes/async-analytics-snippet #}\n            {% block google_analytics %}\n            <script>\n                {% if not DEBUG %}\n                var _gaq=[['_setAccount','{% block google_analytics_id %}UA-XXXXX-X{% endblock %}'],['_trackPageview']];\n                (function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0];\n                    g.src=('https:'==location.protocol?'//ssl':'//www')+'.google-analytics.com/ga.js';\n                    s.parentNode.insertBefore(g,s)}(document,'script'));\n                {% endif %}\n            </script>\n            {% endblock %}\n\n            {% block clicky_analytics %}\n            <script type=\"text/javascript\">\n                var clicky_site_ids = clicky_site_ids || [];\n                clicky_site_ids.push(100643192);\n                (function() {\n                    var s = document.createElement('script');\n                    s.type = 'text/javascript';\n                    s.async = true;\n                    s.src = '//static.getclicky.com/js';\n                    ( document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0] ).appendChild( s );\n                })();\n            </script>\n            <noscript><p><img alt=\"Clicky\" width=\"1\" height=\"1\" src=\"//in.getclicky.com/100643192ns.gif\" /></p></noscript>\n            {% endblock %}\n        {% endblock %}\n\n        <script src=\"{{ ASSETS }}/js/main.js\"></script>\n        <script src=\"{{ ASSETS }}/js/navbar.js\"></script>\n\n        {% block js_footer %}\n        {# Add your javascript here #}\n        {% endblock %}\n\n{% endblock %}\n    </body>\n</html>\n"
  },
  {
    "path": "docs/_templates/macros/alerts.html",
    "content": "{% macro alert(content, type=\"info\", alert_header=\"Note\", close_button=False) -%}\n{# type can be success, error (or danger), info. Defaults to a warning style. #}\n<div class=\"alert\n            {%- if alert_header %} alert-block{% endif -%}\n            {%- if type %} alert-{{ type }}{% endif -%}\n            {%- if close_button %} fade in{% endif %}\">\n{% if close_button -%}\n<button class=\"close btn btn-link\" data-dismiss=\"alert\">&times;</button>\n{%- endif %}\n{% if alert_header -%}\n<h4 class=\"alert-heading\">{{ alert_header|safe }}</h4>\n{%- endif %}\n\n{{ content|markdown|safe }}\n\n</div>\n{%- endmacro %}\n\n{#\n{% macro browsers(ok, not_ok) -%}\n{% for browser in ok %}\n    <span class=\"label label-success\">{{ content|safe }}</span>\n{% endfor %}\n{% for browser in ok %}\n    <span class=\"label label-failure\">{{ content|safe }}</span>\n{% endfor %}\n{%- endmacro %}\n#}\n\n{% macro label(content, type='warning') -%}\n    <span class=\"label label-{{ type }}\">{{ content|safe }}</span>\n{%- endmacro %}\n\n{% macro badge(content, type='warning') -%}\n<span class=\"badge badge-{{ type }}\">{{ content|safe }}</span>\n{%- endmacro %}\n"
  },
  {
    "path": "docs/_templates/macros/code.html",
    "content": "{#\n    Tables\n#}\n{% macro code_table(headings, rows, title=None) -%}\n{% if title %}\n<h2>{{ title }}</h2>\n{% endif %}\n<table class=\"table table-striped table-bordered reference-table\">\n    <thead>\n    <tr>\n        {% for heading in headings %}\n        <td>{{ heading|markdown|safe }}</td>\n        {% endfor %}\n    </tr>\n    </thead>\n\n    <tbody>\n    {% for row in rows %}\n    <tr>\n        {% for col in row %}\n        <td>{{ col|markdown|safe }}</td>\n        {% endfor %}\n    </tr>\n    {% endfor %}\n    </tbody>\n</table>\n{%- endmacro %}\n\n{% macro options_table(rows, title=None) -%}\n{{ code_table((\"Option Name\", \"Type\", \"Default\", \"Description\"), rows, title) }}\n{%- endmacro %}\n{% macro events_table(rows, title=None) -%}\n{{ code_table((\"Event\", \"Parameters\", \"Description\"), rows, title) }}\n{%- endmacro %}\n{% macro methods_table(rows, title=None) -%}\n{{ code_table((\"Name\", \"Parameters\", \"Return\", \"Description\"), rows, title) }}\n{%- endmacro %}\n{#\n{% macro classes_table(rows, title=None) -%}\n{{ code_table((\"Property Name\", \"Default CSS\", \"Description\"), rows, title) }}\n{%- endmacro %}\n#}\n\n{#\n    Labels\n#}\n{% macro code_label(type, val=None) %}\n{% set type_label_mapping = {\n    \"object\": {\n        \"link\": \"\",\n    },\n    \"array\": {\n        \"link\": \"\",\n    },\n    \"function\": {\n        \"link\": \"\"\n    },\n    \"integer\": {\n        \"link\": \"\"\n    },\n    \"string\": {\n        \"link\": \"\"\n    },\n    \"boolean\": {\n        \"link\": \"\"\n    },\n    \"htmlelement\": {\n        \"link\": \"\"\n    },\n    \"qq.promise\": {\n        \"link\": \"\"\n    },\n    \"xmlhttprequest\": {\n        \"link\": \"\"\n    },\n    \"undefined\": {\n        \"link\": \"\"\n    },\n    \"null\": {\n        \"link\": \"\"\n    },\n    \"other\": {\n        \"link\": \"\"\n    }\n} %}\n{% if \",\" in type %}\n    {% set label_types = type.split(\",\") %}\n    {% if val == None %}\n        {% for label_type in label_types %}\n            {% if loop.first %}\n                {{code_label(label_type.strip(\" \"), val)}}\n                or\n            {% else %}\n                {{code_label(label_type)}}\n            {% endif %}\n        {% endfor %}\n    {% else %}\n        {{code_label(label_types[0], val)}}\n    {% endif %}\n{% elif \" or \" in type %}\n    {% set label_types = type.split(\" or \") %}\n    {% if val == None %}\n        {% for label_type in label_types %}\n            {% if loop.first %}\n                {{code_label(label_type.strip(\" \"), val)}}\n                or\n            {% else %}\n                {{code_label(label_type)}}\n            {% endif %}\n        {% endfor %}\n    {% else %}\n        {{code_label(label_types[0], val)}}\n    {% endif %}\n{% else %}\n    {% set label_type = type_label_mapping.get(type.lower(), 'other') %}\n    {% set text = type if val == None else val %}\n    <span class=\"label label-{{type.lower()}}\">{{text|safe}}</span>\n{% endif %}\n{% endmacro %}\n\n{#\n    api_param\n\n    This macro will generate the correct HTML structure to document an API\n    method as found on api/methods.jmd and api/methods-ui.jmd\n#}\n{#\n    api_param\n\n    This macro will generate the correct HTML structure for a parameter in\n    the api_methods documentation.\n#}\n{% macro api_param(name, type, description) -%}\n<span class=\"method-param row-fluid\">\n    <dd class=\"method-param-type span2\">{{code_label(type)}}</dd>\n    <dt class=\"method-param-name span2\"><em>{{name}}</em></dt>\n    <dd class=\"method-param-description span8\">{% markdown %}{{description}}{% endmarkdown %}</dd>\n</span>\n<hr/>\n{%- endmacro %}\n\n{#\n    api_return\n\n    This macro will generate the correct HTML structure for a return in\n    the api_methods documentation.\n#}\n{% macro api_return(type, description) -%}\n<span class=\"method-return row-fluid\">\n    <dt class=\"span2\"><b>Returns:</b></dt>\n    <dd class=\"method-return-type span2\">{{code_label(type)}}</dd>\n    <dd class=\"method-return-description span8\">{{description}}</dd>\n</span>\n<hr/>\n{%- endmacro %}\n\n{% macro api_method_or_event(type, header, title, description, params=None, returns=None) -%}\n<span class=\"{{type}} row-fluid\">\n    <h4 id=\"{{header}}\"><a style=\"color: #f87436;\" href=\"#{{header}}\">{{title}}</a></h4>\n    <span class=\"{{type}}-description\">\n        {% markdown %}{{description}}{% endmarkdown %}\n    </span>\n    {% if params %}\n    <h5>Parameters: </h5>\n    <dl class=\"{{type}}-params row-fluid\">\n        {% for param in params %}\n            {% if param is mapping %}\n                {{ api_param(param.name, param.type, param.description) }}\n            {% else %}\n                {% for sub_params in param %}\n                    {{ api_param(sub_params.name, sub_params.type, sub_params.description) }}\n                    {% if not loop.last %}\n                    <p> or ... </p>\n                    {% endif %}\n                {% endfor %}\n            {% endif %}\n        {% endfor %}\n    {% endif %}\n    </dl>\n    {% if returns %}\n    <dl class=\"{{type}}-return row-fluid\">\n        {% for return in returns %}\n            {{api_return(return.type, return.description)}}\n        {% endfor %}\n    </dl>\n    {% endif %}\n    {% if returns is undefined and params is undefined %}\n    <hr/>\n    {% endif %}\n</span>\n{%- endmacro %}\n\n{% macro api_option(header, title, description, type, default_value) -%}\n<div class=\"option row-fluid\">\n    <h4 id=\"{{header}}\"><a style=\"color: #f87436\" href=\"#{{header}}\">{{title}}</a></h4>\n    <span class=\"option-description row-fluid\">\n        {% markdown %}{{description|safe}}{% endmarkdown %}\n    </span>\n    <dl>\n        <div class=\"row-fluid\">\n            <dt class=\"span2\"><b>Type:</b></dt>\n            <dd class=\"span10 option-type\">{{code_label(type)}}</dd>\n        </div>\n\n        <div class=\"row-fluid\">\n            <dt class=\"span2\"><b>Default Value:</b></dt>\n            <dd class=\"span10 option-default-value\">{{code_label(type, default_value)}}</dd>\n        </div>\n    </dl>\n<hr>\n</div>\n{%- endmacro %}\n\n{% macro api_parent_option(header, title, description, sub_options) -%}\n<div class=\"option-parent row-fluid\">\n    <h3 id=\"{{header}}\"><a style=\"color: #00ABC7;\" href=\"#{{header}}\">{{title}}</a></h3>\n    <span class=\"option-description row-fluid\">\n        {% markdown %}{{description|safe}}{% endmarkdown %}\n    </span>\n    {% for sub_option in sub_options %}\n    {{ api_option(*sub_option) }}\n    {% endfor %}\n</div>\n{% endmacro -%}\n\n{% macro api_event(header, title, description, params, returns) %}\n    {{ api_method_or_event('event', header, title, description, params, returns) }}\n{% endmacro %}\n\n{% macro api_method(header, title, description, params, returns) %}\n    {{ api_method_or_event('method', header, title, description, params, returns) }}\n{% endmacro %}\n\n{% macro api_links(options=None, methods=None, events=None) -%}\n{% if options is not none or methods is not none or events is not none %}\n<div class=\"sidebar\">\n    {% if options is iterable and options is not none %}\n    <div class=\"accordion-group\">\n        <div class=\"accordion-heading\">\n            <a href=\"../api/options.html\"><b>Options</b></a>\n        </div>\n        <div class=\"accordion-body\">\n            <div class=\"accordon-inner\">\n                <ul class=\"nav nav-list\">\n        {% for option in options %}\n            {% if \"-\" in option %}\n                {% set option_type = option.split(\"-\")[1] %}\n                {% set option = option.split(\"-\")[0] %}\n                    {% if option_type == \"ui\" %}\n                    <li><a href=\"../api/options-ui.html#{{option}}\">{{option}} (UI)</a></li>\n                    {% elif option_type == \"s3\" %}\n                    <li><a href=\"../api/options-s3.html#{{option}}\">{{option}} (S3)</a></li>\n                    {% endif %}\n            {% else %}\n                <li><a href=\"../api/options.html#{{option}}\">{{option}}</a></li>\n            {% endif %}\n        {% endfor %}\n                </ul>\n            </div>\n        </div>\n    </div>\n    {% endif %}\n\n    {% if methods is iterable and methods is not none %}\n    <div class=\"accordion-group\">\n        <div class=\"accordion-heading\">\n            <a href=\"../api/methods.html\"><b>Methods</b></a>\n        </div>\n        <div class=\"accordion-body\">\n            <div class=\"accordon-inner\">\n                <ul class=\"nav nav-list\">\n        {% for method in methods %}\n            {% if \"-\" in method %}\n                {% set method_type = method.split(\"-\")[1] %}\n                {% set method = method.split(\"-\")[0] %}\n                    {% if method_type == \"ui\" %}\n                    <li><a href=\"../api/methods.html#{{method}}\">{{method}} (UI)</a></li>\n                    {% elif method_type == \"s3\" %}\n                    <li><a href=\"../api/methods-s3.html#{{method}}\">{{method}} (S3)</a></li>\n                    {% endif %}\n            {% else %}\n                    <li><a href=\"../api/methods.html#{{method}}\">{{method}}</a></li>\n            {% endif %}\n        {% endfor %}\n                </ul>\n            </div>\n        </div>\n    </div>\n    {% endif %}\n\n    {% if events is iterable and events is not none %}\n    <div class=\"accordion-group\">\n        <div class=\"accordion-heading\">\n            <a href=\"../api/events.html\"><b>Events</b></a>\n        </div>\n        <div class=\"accordion-body\">\n            <div class=\"accordon-inner\">\n                <ul class=\"nav nav-list\">\n        {% for event in events %}\n            {% if \"-\" in event %}\n                {% set event_type = event.split(\"-\")[1] %}\n                {% set event = event.split(\"-\")[0] %}\n                    {% if event_type == \"ui\" %}\n                    <li><a href=\"../api/events-ui.html#{{event}}\">{{event}} (UI)</a></li>\n                    {% elif event_type == \"s3\" %}\n                    <li><a href=\"../api/events-s3.html#{{event}}\">{{event}} (S3)</a></li>\n                    {% endif %}\n            {% else %}\n                <li><a href=\"../api/events.html#{{event}}\">{{event}}</a></li>\n            {% endif %}\n        {% endfor %}\n                </ul>\n            </div>\n        </div>\n    </div>\n    {% endif %}\n\n</div>\n{% endif %}\n{% endmacro -%}\n"
  },
  {
    "path": "docs/_templates/macros/github.html",
    "content": "{% macro issue(num) -%}\n<a href=\"https://github.com/FineUploader/fine-uploader/issues/{{ num }}\">#{{ num }}</a>\n{%- endmacro %}\n"
  },
  {
    "path": "docs/_templates/navbar.html",
    "content": "<div class=\"navbar navbar-inverse navbar-fixed-top\">\n    <div class=\"navbar-inner\">\n        <div class=\"container\">\n\n            <button class=\"btn btn-navbar\" data-toggle=\"collapse\" data-target=\".nav-collapse\" aria-label=\"mobile navigation menu\">\n                <span class=\"hide\">mobile navigation menu</span>\n                <span class=\"icon-bar\"></span>\n                <span class=\"icon-bar\"></span>\n                <span class=\"icon-bar\"></span>\n            </button>\n\n            <div class=\"nav-collapse collapse\">\n                <ul class=\"nav\">\n                    <li><a class=\"brand\" href=\"http://fineuploader.com/\"><img style=\"margin-top: -10px; height: 20px\" src=\"https://fineuploader.com/img/FineUploader_logo.png\" alt=\"Fine Uploader logo\"></a></li>\n                    <li><a href=\"{{ URL_ROOT }}\">Docs</a></li>\n                    <li><a href=\"https://blog.fineuploader.com/\">Blog</a></li>\n\n                    <li class=\"dropdown\">\n                        <button id=\"features-menu-trigger\" class=\"dropdown-toggle btn btn-link\" data-toggle=\"dropdown\">Features <b class=\"caret\"></b></button>\n                        <ul class=\"dropdown-menu\" role=\"menu\" aria-labelledby=\"features-menu-trigger\">\n                            <li role=\"presentation\"><a role=\"menuitem\" href=\"{{ URL_ROOT }}/features/async-tasks-and-promises.html\">Async Tasks & Promises</a></li>\n                            <li role=\"presentation\"><a role=\"menuitem\" href=\"{{ URL_ROOT }}/features/azure.html\">Azure Blob Storage Uploads</a></li>\n                            <li role=\"presentation\"><a role=\"menuitem\" href=\"{{ URL_ROOT }}/features/cancellable-uploads.html\">Cancelling</a></li>\n                            <li role=\"presentation\"><a role=\"menuitem\" href=\"{{ URL_ROOT }}/features/chunking.html\">Chunking</a></li>\n                            <li role=\"presentation\"><a role=\"menuitem\" href=\"{{ URL_ROOT }}/features/concurrent-chunking.html\">Concurrent Chunking</a></li>\n                            <li role=\"presentation\"><a role=\"menuitem\" href=\"{{ URL_ROOT }}/features/CORS.html\">CORS</a></li>\n                            <li role=\"presentation\"><a role=\"menuitem\" href=\"{{ URL_ROOT }}/features/delete.html\">Delete</a></li>\n                            <li role=\"presentation\"><a role=\"menuitem\" href=\"{{ URL_ROOT }}/features/drag-and-drop.html\">Drag and Drop</a></li>\n                            <li role=\"presentation\"><a role=\"menuitem\" href=\"{{ URL_ROOT }}/features/handling-errors.html\">Error Handling</a></li>\n                            <li role=\"presentation\"><a role=\"menuitem\" href=\"{{ URL_ROOT }}/features/modules.html\">ES6 & CommonJS Support</a></li>\n                            <li role=\"presentation\"><a role=\"menuitem\" href=\"{{ URL_ROOT }}/features/filename-edit.html\">Filename Editing</a></li>\n                            <li role=\"presentation\"><a role=\"menuitem\" href=\"{{ URL_ROOT }}/features/forms.html\">Form Support</a></li>\n                            <li role=\"presentation\"><a role=\"menuitem\" href=\"{{ URL_ROOT }}/features/session.html\">Initial File Lists</a></li>\n                            <li role=\"presentation\"><a role=\"menuitem\" href=\"{{ URL_ROOT }}/features/extra-buttons.html\">Multiple Upload Buttons</a></li>\n                            <li role=\"presentation\"><a role=\"menuitem\" href=\"{{ URL_ROOT }}/features/no-server-uploads.html\">No-Server Uploads</a></li>\n                            <li role=\"presentation\"><a role=\"menuitem\" href=\"{{ URL_ROOT }}/features/paste-to-upload.html\">Paste</a></li>\n                            <li role=\"presentation\"><a role=\"menuitem\" href=\"{{ URL_ROOT }}/features/pause.html\">Pause In-Progress Uploads</a></li>\n                            <li role=\"presentation\"><a role=\"menuitem\" href=\"{{ URL_ROOT }}/features/progress-bars.html\">Progress Bars</a></li>\n                            <li role=\"presentation\"><a role=\"menuitem\" href=\"{{ URL_ROOT }}/features/thumbnails.html\">Previews & Thumbnails</a></li>\n                            <li role=\"presentation\"><a role=\"menuitem\" href=\"{{ URL_ROOT }}/features/request-parameters.html\">Request Parameters</a></li>\n                            <li role=\"presentation\"><a role=\"menuitem\" href=\"{{ URL_ROOT }}/features/resume.html\">Resuming</a></li>\n                            <li role=\"presentation\"><a role=\"menuitem\" href=\"{{ URL_ROOT }}/features/retry.html\">Retrying</a></li>\n                            <li role=\"presentation\"><a role=\"menuitem\" href=\"{{ URL_ROOT }}/features/s3.html\">S3 Uploads</a></li>\n                            <li role=\"presentation\"><a role=\"menuitem\" href=\"{{ URL_ROOT }}/features/statistics-and-status-updates.html\">Stats & Status</a></li>\n                            <li role=\"presentation\"><a role=\"menuitem\" href=\"{{ URL_ROOT }}/features/styling.html\">Styling</a></li>\n                            <li role=\"presentation\"><a role=\"menuitem\" href=\"{{ URL_ROOT }}/features/upload-files.html\">Uploading</a></li>\n                            <li role=\"presentation\"><a role=\"menuitem\" href=\"{{ URL_ROOT }}/features/scaling.html\">Upload Scaled Images</a></li>\n                            <li role=\"presentation\"><a role=\"menuitem\" href=\"{{ URL_ROOT }}/features/upload-from-mobile-camera.html\">Upload via Mobile Camera</a></li>\n                            <li role=\"presentation\"><a role=\"menuitem\" href=\"{{ URL_ROOT }}/features/validation.html\">Validation</a></li>\n                        </ul>\n                    </li>\n\n                    <li class=\"dropdown\">\n                        <button id=\"api-menu-trigger\" class=\"dropdown-toggle btn btn-link\" data-toggle=\"dropdown\">API <b class=\"caret\"></b></button>\n                        <ul class=\"dropdown-menu api\" role=\"menu\" aria-labelledby=\"api-menu-trigger\">\n                                <li><b>Options</b></li>\n                                <li role=\"presentation\"><a role=\"menuitem\" href=\"{{ URL_ROOT }}/api/options.html\">Core</a></li>\n                                <li role=\"presentation\"><a role=\"menuitem\" href=\"{{ URL_ROOT }}/api/options-ui.html\">UI</a></li>\n                                <li role=\"presentation\"><a role=\"menuitem\" href=\"{{ URL_ROOT }}/api/options-s3.html\">S3</a></li>\n                                <li role=\"presentation\"><a role=\"menuitem\" href=\"{{ URL_ROOT }}/api/options-azure.html\">Azure</a></li>\n                                <li class=\"divider\"></li>\n                                <li><b>Methods</b></li>\n                                <li role=\"presentation\"><a role=\"menuitem\" href=\"{{ URL_ROOT }}/api/methods.html\">Core & UI</a></li>\n                                <li role=\"presentation\"><a role=\"menuitem\" href=\"{{ URL_ROOT }}/api/methods-s3.html\">S3</a></li>\n                                <li role=\"presentation\"><a role=\"menuitem\" href=\"{{ URL_ROOT }}/api/methods-azure.html\">Azure</a></li>\n                                <li class=\"divider\"></li>\n                                <li><b>Events</b></li>\n                                <li role=\"presentation\"><a role=\"menuitem\" href=\"{{ URL_ROOT }}/api/events.html\">Core & UI</a></li>\n                                <li role=\"presentation\"><a role=\"menuitem\" href=\"{{ URL_ROOT }}/api/events-s3.html\">S3</a></li>\n                                <li class=\"divider\"></li>\n                                <li role=\"presentation\"><a role=\"menuitem\" style=\"margin-left: -20px;\" href=\"{{ URL_ROOT }}/api/qq.html\">Utilities</a></li>\n                        </ul>\n                    </li>\n\n                    <li class=\"dropdown\">\n                        <button id=\"servers-dropdown-trigger\" class=\"dropdown-toggle btn btn-link\" data-toggle=\"dropdown\">Servers <b class=\"caret\"></b></button>\n                        <ul class=\"dropdown-menu\" role=\"menu\" aria-labelledby=\"servers-dropdown-trigger\">\n                            <li role=\"presentation\"><a role=\"menuitem\" href=\"{{ URL_ROOT }}/endpoint_handlers/traditional.html\">Traditional</a></li>\n                            <li role=\"presentation\"><a role=\"menuitem\" href=\"{{ URL_ROOT }}/endpoint_handlers/amazon-s3.html\">Amazon S3</a></li>\n                            <li role=\"presentation\"><a role=\"menuitem\" href=\"{{ URL_ROOT }}/endpoint_handlers/azure.html\">Azure Blob Storage</a></li>\n                        </ul>\n                    </li>\n\n                    <li class=\"dropdown\">\n                        <button id=\"support-dropdown-trigger\" class=\"dropdown-toggle btn btn-link\" data-toggle=\"dropdown\">Support <b class=\"caret\"></b></button>\n                        <ul class=\"dropdown-menu support\" role=\"menu\" aria-labelledby=\"support-dropdown-trigger\">\n                            <li role=\"presentation\"><a role=\"menuitem\" href=\"{{ URL_ROOT }}/browser-support.html\">Browsers</a></li>\n                            <li role=\"presentation\"><a role=\"menuitem\" href=\"{{ URL_ROOT }}/faq.html\">FAQ</a></li>\n                            <li role=\"presentation\"><a role=\"menuitem\" href=\"{{ URL_ROOT }}/features/handling-errors.html\">Handling Errors</a></li>\n                        </ul>\n                    </li>\n\n                    {% set version_bar = [\n                        ('/branch/master', 'master', 'Stable'),\n                        ('/branch/develop', 'develop', 'In Progress'),\n                        ] -%}\n\n                    {% if GIT_REF_VAL %}\n                        {% set active_page = GIT_REF_VAL %}\n                    {% else %}\n                        {% set active_page = \"master\" %}\n                    {% endif %}\n                    {% set active_page = GIT_REF_VAL %}\n                    <li class=\"dropdown\">\n                        <button id=\"version-dropdown-trigger\" class=\"dropdown-toggle btn btn-link\" data-toggle=\"dropdown\">Version <b class=\"caret\"></b></button>\n                        <ul class=\"dropdown-menu\" role=\"menu\" aria-labelledby=\"version-dropdown-trigger\">\n                            <li role=\"presentation\"><a role=\"menuitem\" href=\"{{ URL_ROOT }}\">Current: {{ PKG['version'] }}</a></li>\n                            {% for href, id, caption in version_bar %}\n                                <li role=\"presentation\">{% if id == active_page %} class=\"active\"{% endif %}<a role=\"menuitem\" href=\"{{ href|e }}\">{{ caption|e }}</a></li>\n                            {% endfor %}\n                            <li role=\"presentation\">\n                                <form class=\"tag-chooser\">\n                                    <input class=\"tag-input\" name=\"tag\" placeholder=\"5.4.0\">\n                                    <button class=\"btn btn-info\">Go</button>\n                                </form>\n                            </li>\n                        </ul>\n                    </li>\n                </ul>\n\n                <form method=\"get\" id=\"search\" action=\"https://duckduckgo.com/\" class=\"navbar-form pull-right\">\n                    <input type=\"hidden\" name=\"sites\" value=\"docs.fineuploader.com{{ URL_ROOT }}\"/>\n                    <!-- Header -->\n                    <input type=\"hidden\" name=\"kj\" value=\"#0088CC\"/>\n                    <!-- Background -->\n                    <input type=\"hidden\" name=\"k7\" value=\"#FFFFFF\"/>\n                    <!-- Text -->\n                    <input type=\"hidden\" name=\"k8\" value=\"#444444\"/>\n                    <!-- Links -->\n                    <input type=\"hidden\" name=\"k9\" value=\"#0088CC\"/>\n                    <!-- Visited Links -->\n                    <input type=\"hidden\" name=\"kaa\" value=\"#0088CC\"/>\n                    <!-- URLS -->\n                    <input type=\"hidden\" name=\"kx\" value=\"#F87436\"/>\n                    <!-- Font -->\n                    <input type=\"hidden\" name=\"kt\" value=\"h\"/>\n                    <input type=\"text\" name=\"q\" maxlength=\"255\" placeholder=\"Search&hellip;\" class=\"span2\" title=\"search documentation\" style=\"margin-top: 4px;\"/>\n                    <input type=\"submit\" value=\"DuckDuckGo Search\" style=\"visibility: hidden; width: 0;\" />\n                </form>\n            </div>\n        </div>\n    </div>\n</div>\n\n\n"
  },
  {
    "path": "docs/api/events-s3.jmd",
    "content": "{% extends \"_templates/base.html\" %}\n{% set page_title = \"S3 Methods\" %}\n\n{% block sidebar %}\n<div id=\"sidebar-accordion\" class=\"accordion\"></div>\n{% endblock %}\n\n{% block js_head %}\n<script src=\"{{ ASSETS }}/js/sidebar.js\"></script>\n{% endblock %}\n\n{% block js_footer %}\n<script type=\"text/javascript\">\n$(document).ready(function() {\n    renderEventsSidebarNav(['all']);\n});\n</script>\n{% endblock %}\n\n{% block content %}\n{% markdown %}\n\n# Events <small>S3</small> {: .page-header }\n\nThe S3 uploader provides many of the same events as the traditional\nuploader, but there are some additional events _exclusive_ to S3 endpoints.\nIf you plan to use the S3 uploader, then use the following table as your event\nreference.  **All methods outlined in the core/traditional documentation are also available\nin Fine Uploader S3, unless otherwise noted here.**\n\n### Syntax\n\nThe following syntax is the correct way to define event handlers:\n\n```javascript\ncallbacks: {\n    onDelete: function(id) {\n        //...\n    },\n    onDeleteComplete: function(id, xhr, isError) {\n        //...\n    }\n}\n```\n\n{% endmarkdown %}\n<div class=\"events-all\">\n\n{{ api_event(\"credentialsExpired\", \"onCredentialsExpired\",\n\"Called before a request is sent to S3 if the temporary credentials have expired.  You must return a promise.  If your attempt to refresh the temporary credentials is successful, you must fulfill the promise via the `success` method, passing the new `credentials` object.  Otherwise, call `failure` with a descriptive reason.\", [])}}\n\n</div>\n\n{% endblock %}\n"
  },
  {
    "path": "docs/api/events.jmd",
    "content": "{% extends \"_templates/base.html\" %}\n{% set page_title = \"Events\" %}\n{% block title %}Events - Fine Uploader Documentation{% endblock %}\n\n{% block sidebar %}\n<div id=\"sidebar-accordion\" class=\"accordion\"></div>\n{% endblock %}\n\n{% block js_head %}\n<script src=\"{{ ASSETS }}/js/sidebar.js\"></script>\n{% endblock %}\n\n{% block js_footer %}\n<script type=\"text/javascript\">\n$(document).ready(function() {\n    renderEventsSidebarNav(['all']);\n});\n</script>\n{% endblock %}\n\n{% block content %}\n{% markdown %}\n\n[status]: ../features/statistics-and-status-updates.html\n\n# Events {: .page-header }\n\nFine Uploader's event system enables integrators to execute any operations at\nalmost any point in the upload process. Knowing how these callbacks work, and\nwhen they are called, is crucial to unlocking the full potential of\nFine Uploader.\n\n### Syntax\n\nThe following syntax is the correct way to define event handlers:\n\n```\ncallbacks: {\n    onDelete: function(id) {\n        // ...\n    },\n    onDeleteComplete: function(id, xhr, isError) {\n        //...\n    }\n}\n```\n\nwhere `callbacks` is specified as a FineUploader option.\n\n{% endmarkdown %}\n<div class=\"events-all\">\n\n{{ api_event(\"autoRetry\", \"onAutoRetry\", \"Called before each automatic retry attempt for a failed item.\",\n    [\n        {\n            \"name\": \"id\",\n            \"type\": \"Integer\",\n            \"description\": \"The current file's id.\"\n        },\n        {\n            \"name\": \"name\",\n            \"type\": \"String\",\n            \"description\": \"The current file's name.\"\n        },\n        {\n            \"name\": \"attemptNumber\",\n            \"type\": \"Integer\",\n            \"description\": \"The number of retry attempts for the current file so far.\"\n        }\n    ])\n}}\n\n{{ api_event(\"cancel\", \"onCancel\",\n\"\"\"Called when the item has been canceled. Return `false` to prevent the\nupload from being canceled. Also can return a promise if non-blocking\nwork is required here. Calling `failure()` on the promise is equivalent to\nreturning `false`. If using a Promise, then processing of the cancel request\nwill be deferred until the promise is fullfilled. Since there is no way to\n'pause' the upload in progress while waiting for the promise to be fullfilled\nthe upload may actually complete until the promise has actually be fullfilled.\n\"\"\",\n    [\n        {\n            \"name\": \"id\",\n            \"type\": \"Integer\",\n            \"description\": \"The current file's id.\"\n        },\n        {\n            \"name\": \"name\",\n            \"type\": \"String\",\n            \"description\": \"The current file's name.\"\n        },\n    ])\n}}\n\n{{ api_event(\"complete\", \"onComplete\",\n\"\"\"Called when the item has finished uploading.\n\nThe `responseJSON` will contain the raw response from the server including the\n'success' property which indicates whether the upload succeeded.\"\"\",\n    [\n        {\n            \"name\": \"id\",\n            \"type\": \"Integer\",\n            \"description\": \"The current file's id.\"\n        },\n        {\n            \"name\": \"name\",\n            \"type\": \"String\",\n            \"description\": \"The current file's name.\"\n        },\n        {\n            \"name\": \"responseJSON\",\n            \"type\": \"Object\",\n            \"description\": \"The raw response from the server.\"\n        },\n        {\n            \"name\": \"xhr\",\n            \"type\": \"XMLHttpRequest or XDomainRequest\",\n            \"description\": \"The object used to make the request.\"\n        }\n    ])\n}}\n\n{{ api_event(\"allComplete\", \"onAllComplete\",\n\"Called when all submitted items have reached a point of termination.  A file has reached a point of termination\nif it has been cancelled, rejected, or uploaded (failed or successful).  For example, if a file in the group is paused,\nand all other files in the group have uploaded successfully the allComplete event will not be invoked for the group\nuntil that paused file is either continued and completes the uploading process, or canceled.  This event will not be\ncalled if all files in the group have been cancelled or rejected (i.e. if none of the files have reached a\n[status][status] of `qq.status.UPLOAD_SUCCESSFUL` or `qq.status.UPLOAD_FAILED`).\",\n    [\n        {\n            \"name\": \"succeeded\",\n            \"type\": \"Array\",\n            \"description\": \"IDs of all files in the group that have uploaded successfully ([status][status] = `qq.status.UPLOAD_SUCCESSFUL`).\"\n        },\n        {\n            \"name\": \"failed\",\n            \"type\": \"Array\",\n            \"description\": \"IDs of all files in the group that have failed ([status][status] = `qq.status.UPLOAD_FAILED`).\"\n        }\n    ])\n}}\n\n{{ api_event(\"delete\", \"onDelete\", \"Called just before a delete request is sent for the associated item. Note that this is _not_ the correct callback to influence the delete request. To do that, use the [onSubmitDelete callback](#submitDelete) instead.\",\n    [\n        {\n            \"name\": \"id\",\n            \"type\": \"Integer\",\n            \"description\": \"The current file's id.\"\n        }\n    ])\n}}\n\n{{ api_event(\"deleteComplete\", \"onDeleteComplete\", \"Called just after receiving a response from the server for a delete file request.\",\n    [\n        {\n            \"name\": \"id\",\n            \"type\": \"Integer\",\n            \"description\": \"The current file's id.\"\n        },\n        {\n            \"name\": \"xhr\",\n            \"type\": \"XMLHttpRequest or XDomainRequest\",\n            \"description\": \"The object used to make the request.\"\n        },\n        {\n            \"name\": \"isError\",\n            \"type\": \"Boolean\",\n            \"description\": \"`true` if there has been an error, `false` otherwise.\"\n        },\n    ])\n}}\n\n{{ api_event(\"error\", \"onError\", \"Called whenever an exceptional condition occurs (see [Handling Errors](../features/handling-errors.html))\",\n    [\n        {\n            \"name\": \"id\",\n            \"type\": \"Integer\",\n            \"description\": \"The current file's id.\"\n        },\n        {\n            \"name\": \"name\",\n            \"type\": \"String\",\n            \"description\": \"The current file's name.\"\n        },\n        {\n            \"name\": \"errorReason\",\n            \"type\": \"String\",\n            \"description\": \"The reason for the current error.\"\n        },\n        {\n            \"name\": \"xhr\",\n            \"type\": \"XMLHttpRequest or XDomainRequest\",\n            \"description\": \"The object used to make the request.\"\n        }\n    ])\n}}\n\n{{ api_event(\"manualRetry\", \"onManualRetry\",\n\"\"\"Called before each manual retry attempt.\n\nReturn `false` to prevent this and all future retry attempts on the associated item.\"\"\",\n    [\n        {\n            \"name\": \"id\",\n            \"type\": \"Integer\",\n            \"description\": \"The current file's id.\"\n        },\n        {\n            \"name\": \"name\",\n            \"type\": \"String\",\n            \"description\": \"The current file's name.\"\n        },\n    ])\n}}\n\n{{ api_event(\"pasteReceived\", \"onPasteReceived\",\n\"\"\"Called when a pasted image has been received (before upload).\n\nThe pasted image is represented as a `Blob`. Also can return a promise if non-blocking work is required here. If using a Promise the value of the success parameter must be the name to associate with the pasted image. If the associated attempt is marked a failure then you should include a string explaining the reason in your failure callback for the Promise.\n\n**Note:**\n\nThe `promptForName` option, if `true`, will effectively wipe away any custom implementation of this callback. The two are not meant to be used together. This callback is meant to provide an alternative means to provide a name for a pasted image. If you are using Fine Uploader Core mode then you can display your own prompt for the name by overriding the default implementation of this callback.\n\"\"\",\n    [\n        {\n            \"name\": \"blob\",\n            \"type\": \"Blob\",\n            \"description\": \"An object encapsulating the image pasted from the clipboard.\"\n        },\n    ])\n}}\n\n{{ api_event(\"progress\", \"onProgress\",\n\"\"\" Called during the upload, as it progresses, but only for the AJAX uploader.\n\nFor chunked uploads, this will be called for each chunk.\n\nUseful for implementing a progress bar.\n\"\"\",\n    [\n        {\n            \"name\": \"id\",\n            \"type\": \"Integer\",\n            \"description\": \"The current file's id.\"\n        },\n        {\n            \"name\": \"name\",\n            \"type\": \"String\",\n            \"description\": \"The current file's name\"\n        },\n        {\n            \"name\": \"uploadedBytes\",\n            \"type\": \"Integer\",\n            \"description\": \"The number of bytes that have been uploaded so far.\"\n        },\n        {\n            \"name\": \"totalBytes\",\n            \"type\": \"Integer\",\n            \"description\": \"The total number of bytes that comprise this file.\"\n        }\n    ])\n}}\n\n{{ api_event(\"resume\", \"onResume\",\n\"\"\" Called just before an upload is resumed. See the [`uploadChunk`](#uploadChunk)\n event for more info on the `chunkData` object.\n\"\"\",\n    [\n        {\n            \"name\": \"id\",\n            \"type\": \"Integer\",\n            \"description\": \"The current file's id.\"\n        },\n        {\n            \"name\": \"name\",\n            \"type\": \"String\",\n            \"description\": \"The current file's name\"\n        },\n        {\n            \"name\": \"chunkData\",\n            \"type\": \"Object\",\n            \"description\": \"The chunk that will be sent next when file upload resumes.\"\n        },\n        {\n            \"name\": \"customResumeData\",\n            \"type\": \"Object\",\n            \"description\": \"Any custom resume data provided for this resumable file.\"\n        },\n    ])\n}}\n\n{{ api_event(\"sessionRequestComplete\", \"onSessionRequestComplete\",\n\"\"\" Invoked when a session request has completed.  The `response` will be either an `Array` containing the response data\nor `null` if the response did not contain valid JSON.  The `success` parameter will be `false` if ANY of the file items\nrepresented in the response could not be parsed (due to bad syntax, missing name/UUID property, etc).\n\nSee the [Initial File List feature page](../features/session.html) for more details.\n\"\"\",\n    [\n        {\n            \"name\": \"response\",\n            \"type\": \"Array\",\n            \"description\": \"The raw response data.\"\n        },\n        {\n            \"name\": \"success\",\n            \"type\": \"Boolean\",\n            \"description\": \"Indicates whether success has been achieved or not.\"\n        },\n        {\n            \"name\": \"xhrOrXdr\",\n            \"type\": \"XMLHttpRequest or XDomainRequest\",\n            \"description\": \"The raw request.\"\n        },\n    ])\n}}\n\n{{ api_event(\"statusChange\", \"onStatusChange\",\n\"\"\" Invoked whenever the status changes for any item submitted by the uploader.\n\nThe status values correspond to those found in the `qq.status` object. For reference:\n\n* `SUBMITTED`\n* `QUEUED`\n* `UPLOADING`\n* `UPLOAD_RETRYING`\n* `UPLOAD_FAILED`\n* `UPLOAD_SUCCESSFUL`\n* `CANCELED`\n* `REJECTED`\n* `DELETED`\n* `DELETING`\n* `DELETE_FAILED`\n* `PAUSED`\n\n\"\"\",\n    [\n        {\n            \"name\": \"id\",\n            \"type\": \"Integer\",\n            \"description\": \"The current file's id.\"\n        },\n        {\n            \"name\": \"oldStatus\",\n            \"type\": \"String\",\n            \"description\": \"The previous item status.\"\n        },\n        {\n            \"name\": \"newStatus\",\n            \"type\": \"String\",\n            \"description\": \"The new status of the item.\"\n        }\n    ])\n}}\n\n{{ api_event(\"submit\", \"onSubmit\",\n\"\"\" Called when the item has been selected and is a candidate for uploading.\n\nThis does not mean the item is going to be uploaded. Return `false` to prevent\nsubmission to the uploader. A promise can be used if non-blocking work is\nrequired. Processing of this item is deferred until the promise is fullfilled.\nIf a promise is returned, a call to failure is the same as returning `false`.\n\"\"\",\n    [\n        {\n            \"name\": \"id\",\n            \"type\": \"Integer\",\n            \"description\": \"The current file's id.\"\n        },\n        {\n            \"name\": \"name\",\n            \"type\": \"String\",\n            \"description\": \"The current file's name.\"\n        },\n    ])\n}}\n\n{{ api_event(\"submitDelete\", \"onSubmitDelete\",\n\"\"\" Called before an item has been marked for deletion has been submitted to\nthe uploader.\n\nA promise can be used if non-blocking work is required. Processing of\nthis item is deferred until the promise is fullfilled. If a promise is\nreturned, a call to failure is the same as returning `false`.\n\nUse this callback to influence the delete request. For example, you can change the\ncustom parameters sent with the underlying delete request using the [`setDeleteParams` API method](methods.html#setDeleteFileParams).\n\"\"\",\n    [\n        {\n            \"name\": \"id\",\n            \"type\": \"Integer\",\n            \"description\": \"The current file's id.\"\n        },\n    ])\n}}\n\n{{ api_event(\"submitted\", \"onSubmitted\",\n\"\"\"Called when the item has been successfully submitted to the uploader.\n\nThe file will upload immediately if there is a) at least one free connection\n(see: [`maxConnections` option](options.html#maxconnections)) and b) `autoUpload`\nis set to `true` (see [`autoUpload` option](options.html#autoupload)).\n\nThe callback is invoked after the 'submit' event is handled without returning a\n`false` value. In Fine Uploader Core mode it is usually safe to assume that\nthe associated elements in the UI representing the associated file have already\nbeen added to the DOM immediately before this callback is invoked.\n\n\"\"\",\n    [\n        {\n            \"name\": \"id\",\n            \"type\": \"Integer\",\n            \"description\": \"The current file's id.\"\n        },\n        {\n            \"name\": \"name\",\n            \"type\": \"String\",\n            \"description\": \"THe current file's name.\"\n        },\n    ])\n}}\n\n{{ api_event(\"totalProgress\", \"onTotalProgress\",\n\"\"\" Called during a batch of uploads, as they progress, but only for the AJAX uploader.  This represents the total\nprogress of all files in the batch.\n\nUseful for implementing an aggregate progress bar.\n\"\"\",\n    [\n        {\n            \"name\": \"totalUploadedBytes\",\n            \"type\": \"Integer\",\n            \"description\": \"The number of bytes that have been uploaded so far in this batch.\"\n        },\n        {\n            \"name\": \"totalBytes\",\n            \"type\": \"Integer\",\n            \"description\": \"The total number of bytes that comprise all files in the batch.\"\n        }\n    ])\n}}\n\n{{ api_event(\"upload\", \"onUpload\",\n\"\"\"\nCalled just before an item begins uploading to the server. If asynchronous work must be performed,\nyou may return a Promise. To fail the upload, reject the Promise. Otherwise, resolve it.\nAdditionally, you can pause the upload when resolving the Promise by passing an object\nwith `{ pause: true }` to the resolve function.\n\"\"\",\n    [\n        {\n            \"name\": \"id\",\n            \"type\": \"Integer\",\n            \"description\": \"The current file's id.\"\n        },\n        {\n            \"name\": \"name\",\n            \"type\": \"String\",\n            \"description\": \"The current file's name.\"\n        },\n    ])\n}}\n\n{{ api_event(\"uploadChunk\", \"onUploadChunk\",\n\"\"\" Called just before a chunk request is sent.\n\nThe `chunkData` object, which is passed into the 'uploadChunk' event handler, has 4 properties:\n\n* `partIndex` - the 0-based index of the associated partition.\n* `startByte` - the byte offset of the current chunk.\n* `endByte` - the last byte of the current chunk.\n* `totalParts` - the total number of partitions associated with the `File` or `Blob`.\n\nNote that you can also return a Promise from this handler. To fail the upload, simply reject the Promise.\nOtherwise, resolve the Promise. Additionally, you can order Fine Uploader to use a specific\nendpoint, parameters, method, and headers for this specific chunk upload request simply\nby passing an object to the resolve function that contains values for those four request attributes.\nThe following object properties may be passed to your resolve function to influence the chunk upload request:\n\n* `endpoint` - The endpoint to send the upload chunk request.\n* `headers` - Any headers to be included with the upload chunk request.\n* `method` - The HTTP method to use for the upload chunk request.\n* `params` - Any parameters to send in either the body or the URL of the upload chunk request (depending on if this is a multipart encoded request).\n\"\"\",\n    [\n        {\n            \"name\": \"id\",\n            \"type\": \"Integer\",\n            \"description\": \"The current file's id.\"\n        },\n        {\n            \"name\": \"name\",\n            \"type\": \"String\",\n            \"description\": \"The current file's name.\"\n        },\n        {\n            \"name\": \"chunkData\",\n            \"type\": \"Object\",\n            \"description\": \"An object encapsulating the current chunk of data about to be uploaded.\"\n        },\n    ])\n}}\n\n{{ api_event(\"uploadChunkSuccess\", \"onUploadChunkSuccess\",\n\"\"\" This is similar to the [`complete` event](#complete), except it is invoked after each chunk\nhas been **successfully** uploaded.\n\nSee the [`uploadChunk` event](#uploadChunk) for more information on the `chunkData` object.\n\"\"\",\n    [\n        {\n            \"name\": \"id\",\n            \"type\": \"Integer\",\n            \"description\": \"The current file's id.\"\n        },\n        {\n            \"name\": \"chunkData\",\n            \"type\": \"Object\",\n            \"description\": \"An object encapsulating the current chunk of data about to be uploaded.\"\n        },\n        {\n            \"name\": \"responseJSON\",\n            \"type\": \"Object\",\n            \"description\": \"The raw response from the server.\"\n        },\n        {\n            \"name\": \"xhr\",\n            \"type\": \"XMLHttpRequest or XDomainRequest\",\n            \"description\": \"The object used to make the request.\"\n        }\n    ])\n}}\n\n{{ api_event(\"validate\", \"onValidate\",\n\"\"\" Called once for each selected, dropped, or `addFiles` submitted file.\n\nThis callback is always invoked before the default Fine Uploader validators\nexecute.\n\nThis event will not be called if you return `false` in your `validateBatch` event\nhandler, or if the `stopOnFirstInvalidFile` validation option is `true` and\nthe `validate` event handler has returned `false` for an item.\n\nA promise can be used if non-blocking work is required.\nProcessing of this item is deferred until the promise is fullfilled.\nIf a promise is returned, a call to `failure` is the same as returning `false`.\n\nA buttonContainer element will be passed as the last argument, provided the file was\nsubmitted using a Fine Uploader tracked button.\n\nThe `blobData` object has two properties: `name` and `size`. The `size` property will be undefined for browsers without File API support.\n\n\"\"\",\n    [\n        {\n            \"name\": \"data\",\n            \"type\": \"Object\",\n            \"description\": \"An object with a `name` and `size` property.\"\n        },\n        {\n            \"name\": \"buttonContainer\",\n            \"type\": \"HTMLElement\",\n            \"description\": \"The button corresponding to the respective file if the file was submitted to Fine Uploader using a tracked button.\"\n        },\n    ])\n}}\n\n{{ api_event(\"validateBatch\", \"onValidateBatch\",\n\"\"\" This callback is always invoked before the default Fine Uploader validators\nexecute.\n\nThis event will not be called if you return `false` in your `validateBatch` event\nhandler, or if the `stopOnFirstInvalidFile` validation option is `true` and\nthe `validate` event handler has returned `false` for an item.\n\nA promise can be used if non-blocking work is required.\nProcessing of this item is deferred until the promise is fullfilled.\nIf a promise is returned, a call to `failure` is the same as returning `false`.\n\nA buttonContainer element will be passed as the last argument, provided the file was\nsubmitted using a Fine Uploader tracked button.\n\nThe `fileOrBlobDataArray` object has two properties: `name` and `size`. The `size` property will be undefined for browsers without File API support.\n\n\"\"\",\n    [\n        {\n            \"name\": \"fileOrBlobDataArray\",\n            \"type\": \"Array\",\n            \"description\": \"An array of `Objects` with `name` and `size` properties.\"\n        },\n        {\n            \"name\": \"buttonContainer\",\n            \"type\": \"HTMLElement\",\n            \"description\": \"The button corresponding to the respective file if the file was submitted to Fine Uploader using a tracked button.\"\n        },\n    ])\n}}\n\n</div>\n\n{% endblock %}\n"
  },
  {
    "path": "docs/api/methods-azure.jmd",
    "content": "{% extends \"_templates/base.html\" %}\n{% set page_title = \"Azure Methods\" %}\n\n{% block sidebar %}\n<div id=\"sidebar-accordion\" class=\"accordion\">\n</div>\n{% endblock %}\n\n{% block js_head %}\n<script src=\"{{ ASSETS }}/js/sidebar.js\"></script>\n{% endblock %}\n\n{% block js_footer %}\n<script type=\"text/javascript\">\n$(document).ready(function() {\n    renderMethodsSidebarNav(['azure']);\n});\n</script>\n{% endblock %}\n\n\n{% block content %}\n{% markdown %}\n# Methods <small>Azure</small> {: .page-header }\n{% endmarkdown %}\n\n<div class=\"all-methods\" data-spy=\"\" data-target=\"\">\n<div class=\"methods-azure\">\n\n{% markdown %}\n## Azure\n\nThe Azure Blob Storage uploader provides many of the same API methods as the traditional\nuploader, but there are a few crucial differences to take into account.\nIf you plan to use the Azure uploader, then use the following table as your API\nreference.  All methods outlined in the core or UI documentation are also available\nin Fine Uploader Azure, unless otherwise noted here.\n{% endmarkdown %}\n\n{{ api_method(\"getBlobName\", \"getBlobName (fileId)\", \"Retrieve the blob name with the associated ID\",\n[\n        {\n            \"name\": \"fileId\",\n            \"type\": \"Integer\",\n            \"description\": \"An ID corresponding to a file.\"\n        },\n],\n[\n    {\n        \"type\": \"String\",\n        \"description\": \"The blob name associated with the file ID.\"\n    }\n]) }}\n\n{{ api_method(\"getResumableFilesData\", \"getResumableFilesData ()\", \"Returns an array of potentially resumable items. Each resumable is represented by an object with the following properties:\n\n| Property | Description |\n| -------- | :---------- |\n| `name`   | filename |\n| `uuid`   | the unique id |\n| `partIdx` | the index of the part where the resume will start from |\n| `key` | The associated file's blob name in Azure Blob Storage |\n\", null,\n[\n    {\n        \"type\": \"Array\",\n        \"description\": \"An array of resumable items. \"\n    }\n\n]) }}\n\n{{ api_method(\"setEndpoint\", \"setEndpoint (containerUrl[, id])\",\n\"Modify the container URL where upload requests should be directed. The endpoint for a specific file or blob can be changed by passing in an optional `id` parameter. An `id` will always be a number and refers to a single file.\",\n[\n        {\n            \"name\": \"containerUrl\",\n            \"type\": \"String\",\n            \"description\": \"The new Azure Blob Storage container URL\"\n        },\n        {\n            \"name\": \"id\",\n            \"type\": \"Integer\",\n            \"description\": \"An ID corresponding to a file.\"\n        },\n]) }}\n\n{{ api_method(\"setUploadSuccessEndpoint\", \"setUploadSuccessEndpoint (endpoint[, id])\",\n\"Modify the endpoint that Fine Uploader should POST to when a file has been successfully uploaded to Azure Blob Storage.\",\n[\n        {\n            \"name\": \"endpoint\",\n            \"type\": \"String\",\n            \"description\": \"An endpoint that Fine Uploader should POST to when a file has been successfully uploaded to Azure Blob Storage.\"\n        },\n        {\n            \"name\": \"id\",\n            \"type\": \"Integer\",\n            \"description\": \"An ID corresponding to a file.\"\n        },\n]) }}\n\n{{ api_method(\"setUploadSuccessParams\", \"setUploadSuccessParams (newParams[, id])\",\n\"Set additional parameters for the upload success request. Note that Fine Uploader will still send the container URL, blob name, filename, and UUID as well.\",\n[\n        {\n            \"name\": \"newParams\",\n            \"type\": \"Object\",\n            \"description\": \"The additional parameters set for the upload request.\"\n        },\n        {\n            \"name\": \"id\",\n            \"type\": \"Integer\",\n            \"description\": \"A file id to apply these upload success parameters to.\"\n        },\n]) }}\n</div>\n</div>\n\n{% endblock %}\n"
  },
  {
    "path": "docs/api/methods-s3.jmd",
    "content": "{% extends \"_templates/base.html\" %}\n{% set page_title = \"S3 Methods\" %}\n\n{% block sidebar %}\n<div id=\"sidebar-accordion\" class=\"accordion\">\n</div>\n{% endblock %}\n\n{% block js_head %}\n<script src=\"{{ ASSETS }}/js/sidebar.js\"></script>\n{% endblock %}\n\n{% block js_footer %}\n<script type=\"text/javascript\">\n$(document).ready(function() {\n    renderMethodsSidebarNav(['s3']);\n});\n</script>\n{% endblock %}\n\n\n{% block content %}\n{% markdown %}\n# Methods <small>S3</small> {: .page-header }\n{% endmarkdown %}\n\n<div class=\"all-methods\" data-spy=\"\" data-target=\"\">\n<div class=\"methods-s3\">\n\n{% markdown %}\n## S3\n\nThe S3 uploader provides many of the same API methods as the traditional\nuploader, but there are a few crucial differences to take into account.\nIf you plan to use the S3 uploader, then use the following table as your API\nreference.  All methods outlined in the core or UI documentation are also available\nin Fine Uploader S3, unless otherwise noted here.\n{% endmarkdown %}\n\n{{ api_method(\"getBucket\", \"getBucket (fileId)\", \"Retrieve the S3 bucket name associated with the passed file (id).  Note that the bucket name is not available before the file has started uploading.\",\n[\n        {\n            \"name\": \"fileId\",\n            \"type\": \"Integer\",\n            \"description\": \"An ID corresponding to a file.\"\n        },\n],\n[\n    {\n        \"type\": \"String\",\n        \"description\": \"The S3 bucket name associated with the passed file (id)\"\n    }\n]) }}\n\n{{ api_method(\"getKey\", \"getKey (fileId)\", \"Retrieve the S3 object key associated with the passed file (id).  Note that the key is not available before the file has started uploading.\",\n[\n        {\n            \"name\": \"fileId\",\n            \"type\": \"Integer\",\n            \"description\": \"An ID corresponding to a file.\"\n        },\n],\n[\n    {\n        \"type\": \"String\",\n        \"description\": \"The S3 object key associated with the passed file (id)\"\n    }\n]) }}\n\n{{ api_method(\"getResumableFilesData\", \"getResumableFilesData ()\", \"Returns an array of potentially resumable items. Each resumable is represented by an object with the following properties:\n\n| Property | Description |\n| -------- | :---------- |\n| `name`   | filename |\n| `uuid`   | the unique id |\n| `partIdx` | the index of the part where the resume will start from |\n| `key` | The associated object's S3 key |\n\", null,\n[\n    {\n        \"type\": \"Array\",\n        \"description\": \"An array of resumable items. \"\n    }\n\n]) }}\n\n{{ api_method(\"setAcl\", \"setAcl (newAcl[, id])\", \"Set/update the ACL to be used for one or all file uploads.  If the ID is omitted, the new ACL targets all future files that have not yet been uploaded.\",\n[\n    {\n        \"name\": \"newAcl\",\n        \"type\": \"String\",\n        \"description\": \"Canned ACL value to be sent with the upload request.  Used by S3.\"\n    },\n    {\n        \"name\": \"id\",\n        \"type\": \"Integer\",\n        \"description\": \"File ID to target the ACL.\"\n    }\n])}}\n\n{{ api_method(\"setCredentials\", \"setCredentials (newCredentials)\", \"Pass new or initial credentials.  This is used to support the no-server workflow.\",\n[\n    {\n        \"name\": \"newCredentials\",\n        \"type\": \"Object\",\n        \"description\": \"The new or initial credentials to set for server-less uploads\"\n    }\n])}}\n\n{{ api_method(\"setEndpoint\", \"setEndpoint (endpoint[, id])\",\n\"Modify the endpoint URL where upload requests should be directed. The endpoint for a specific file or blob can be changed by passing in an optional `id` parameter. An `id` will always be a number and refers to a single file. All valid bucket URLs documented by Amazon are supported, including custom domains. SSL is also supported.  If you specify a CDN endpoint URL, be sure that you are specifying a bucket as well via the `objectProperties.bucket` option.\",\n[\n        {\n            \"name\": \"endpoint\",\n            \"type\": \"String\",\n            \"description\": \"A URL for the S3 bucket or a CDN that forwards the request on to S3.\"\n        },\n        {\n            \"name\": \"id\",\n            \"type\": \"Integer\",\n            \"description\": \"An ID corresponding to a file.\"\n        },\n]) }}\n\n{{ api_method(\"setUploadSuccessEndpoint\", \"setUploadSuccessEndpoint (endpoint[, id])\",\n\"Modify the endpoint that Fine Uploader should POST to when a file has been successfully uploaded to S3.\",\n[\n        {\n            \"name\": \"endpoint\",\n            \"type\": \"String\",\n            \"description\": \"An endpoint that Fine Uploader should POST to when a file has been successfully uploaded to S3.\"\n        },\n        {\n            \"name\": \"id\",\n            \"type\": \"Integer\",\n            \"description\": \"An ID corresponding to a file.\"\n        },\n]) }}\n\n{{ api_method(\"setUploadSuccessParams\", \"setUploadSuccessParams (newParams[, id])\",\n\"Set additional parameters for the upload success request. Note that Fine Uploader will still send the bucket name, key, filename, UUID, and etag (if available) as well.\",\n[\n        {\n            \"name\": \"newParams\",\n            \"type\": \"Object\",\n            \"description\": \"The additional parameters set for the upload request.\"\n        },\n        {\n            \"name\": \"id\",\n            \"type\": \"Integer\",\n            \"description\": \"A file id to apply these upload success parameters to.\"\n        },\n]) }}\n</div>\n</div>\n\n{% endblock %}\n"
  },
  {
    "path": "docs/api/methods.jmd",
    "content": "{% extends \"_templates/base.html\" %}\n{% set page_title = \"Methods\" %}\n\n{% block title %}Methods - Fine Uploader Documentation{% endblock %}\n\n{% block sidebar %}\n<div id=\"sidebar-accordion\" class=\"accordion\">\n</div>\n{% endblock %}\n\n{% block js_head %}\n<script src=\"{{ ASSETS }}/js/sidebar.js\"></script>\n{% endblock %}\n\n{% block js_footer %}\n<script type=\"text/javascript\">\n$(document).ready(function() {\n    renderMethodsSidebarNav(['core', 'ui']);\n});\n</script>\n{% endblock %}\n\n{% block content %}\n{% markdown %}\n# Methods <small>Traditional</small> {: .page-header }\n{% endmarkdown %}\n\n<div class=\"all-methods\" data-spy=\"scroll\" data-target=\"\">\n<div class=\"methods-core\">\n{% markdown %}\n## Core\n{% endmarkdown %}\n\n{{\nalert(\"If you pass a large `Blob` that was created using JavaScript in the browser into `addFiles`, you should consider calling the [`removeFileRef` method](#removeFileRef) after the file has been successfully uploaded to free up any memory consumed by the Blob.\")\n}}\n\n{{ api_method(\"addFiles\", \"addFiles (files[, params[, endpoint]])\", \"Submit one or more files to the uploader.\n\nA `BlobWrapper` object:\n\n| Property | Description |\n| -------- | :---------- |\n| blob     | the bytes of the `Blob` object being uploaded\n| name     | the name of the `Blob` | {: .table }\n\n<br/>\nA `CanvasWrapper` object:\n\n| Property | Description |\n| -------- | :---------- |\n| canvas   | the `<canvas>` to be converted to a file & then uploaded\n| name     | the name to assign to the created file |\n| quality  | 1-100 value indicating the desired quality of the converted file (only for image/jpeg)\n| type     | MIME type of the file to create from this `<canvas>` | {: .table }\n\",\n[\n        {\n            \"name\": \"files\",\n            \"type\": \"Array\",\n            \"description\": \"An array of `File`s, `<input>`s, `Blob`s, `BlobWrapper` objects, `<canvas>`es, or `CanvasWrapper` objects. You may also pass in a `FileList`.\"\n        },\n    {\n        \"name\": \"params\",\n        \"type\": \"Object\",\n        \"description\": \"A set of parameters to send with the file to be added\"\n    },\n    {\n        \"name\": \"endpoint\",\n        \"type\": \"String\",\n        \"description\": \"The endpoint to send this file to\"\n    }\n]) }}\n\n{{ api_method(\"addInitialFiles\", \"addInitialFiles (initialFiles)\", \"Submit one or more canned/initial files to the uploader. See the [Initial File List feature page](../features/session.html) for more details.\",\n[\n    {\n        \"name\": \"initialFiles\",\n        \"type\": \"Array\",\n        \"description\": \"An array of objects that describe files already on the server.\"\n    }\n]) }}\n\n{{ api_method(\"cancel\", \"cancel (id)\", \"Cancel the queued or currently uploading item which corresponds to the `id`.\",\n[\n    {\n        \"name\": \"id\",\n        \"type\": \"Integer\",\n        \"description\": \"The file's id\"\n    }\n]) }}\n\n{{ api_method(\"cancelAll\", \"cancelAll ()\", \"Cancels all queued or currently uploading items.\") }}\n\n{{ api_method(\"clearStoredFiles\", \"clearStoredFiles ()\", \"Clears the internal list of stored items. Only applies when [`autoUpload`](options.html#autoUpload) is `false`\") }}\n\n{{ api_method(\"continueUpload\", \"continueUpload (id)\", \"Attempts to continue a paused upload.\",\n[\n    {\n        \"name\": \"id\",\n        \"type\": \"Integer\",\n        \"description\": \"A file id\"\n    }\n],\n[\n    {\n        \"type\": \"Boolean\",\n        \"description\": \"`true` if attempt was successful.\"\n    }\n]) }}\n\n{{ api_method(\"deleteFile\", \"deleteFile (id)\", \"\",\n[\n    {\n        \"name\": \"id\",\n        \"type\": \"Integer\",\n        \"description\": \"Send a delete request to the server for the corresponding file id.\"\n    }\n]) }}\n\n{{ api_method(\"drawThumbnail\", \"drawThumbnail (id, targetContainer[, maxSize[, fromServer[, customResizer]]])\", \"Draws a thumbnail.\",\n[\n    {\n        \"name\": \"id\",\n        \"type\": \"Integer\",\n        \"description\": \"The id of the image file.\"\n    },\n    {\n        \"name\": \"targetContainer\",\n        \"type\": \"HTMLElement\",\n        \"description\": \"The element where the image preview will be drawn. Must be either an `<img>` or `<canvas>` element.\"\n    },\n    {\n        \"name\": \"maxSize\",\n        \"type\": \"Integer\",\n        \"description\": \"The maximum dimensions (for width and height) you will allow this image to scale to.\"\n    },\n    {\n        \"name\": \"fromServer\",\n        \"type\": \"Boolean\",\n        \"description\": \"`true` if the image data will come as a response from the server rather than be generated client-side.\"\n    },\n    {\n        \"name\": \"customResizer\",\n        \"type\": \"function\",\n        \"description\": \"Ignored if the current browser does not [support image previews](../browser-support.html). If you want to use an alternate library to resize the image, you must contribute a function for this option that returns a `Promise`. Once the resize is complete, your promise must be fulfilled. You may, of course, reject your returned `Promise` is the resize fails in some way.\n\nA `resizeInfo` object, which will be passed to the supplied function, contains the following properties:\n\n* `blob` - The original `File` or `Blob` object, if available.\n* `height` - Desired height of the image after the resize operation.\n* `image` - The original `HTMLImageElement` object, if available.\n* `sourceCanvas` - `HTMLCanvasElement` element containing the original image data (not resized).\n* `targetCanvas` - `HTMLCanvasElement` element containing the `HTMLCanvasElement` that should contain the resized image.\n* `width` - Desired width of the image after the resize operation.\n\"\n    }\n],\n[\n    {\n        \"type\": \"promise\",\n        \"description\": \"Fulfilled by passing the container back into the success callback after the thumbnail has been rendered. If the thumbnail cannot be rendered, failure callbacks will be invoked instead, passing an object with `container` and `error` properties.\"\n    }\n\n]) }}\n{{ api_method(\"getButton\", \"getButton (id)\", \"Returns the button container element associated with a file\",\n[\n    {\n        \"name\": \"id\",\n        \"type\": \"Integer\",\n        \"description\": \"The file id\"\n    }\n],\n[\n    {\n        \"type\": \"HTMLElement\",\n        \"description\": \"The button container element associated with a file, or `undefined` if the file was not submitted via a Fine Uploader controlled upload button.\"\n    }\n\n]) }}\n\n{{ api_method(\"getFile\", \"getFile (id)\", \"Returns the file identified by the id. File API browsers only.\",\n[\n    {\n        \"name\": \"id\",\n        \"type\": \"Integer\",\n        \"description\": \"The file id.\"\n    }\n],\n[\n    {\n        \"type\": \"File or Blob\",\n        \"description\": \"A `File` or `Blob` object.\"\n    }\n\n]) }}\n\n{{ api_method(\"getEndpoint\", \"getEndpoint ([id])\", \"Returns the endpoint associated with a particular file, or the current catch-all endpoint for all files (if no ID is specified).\",\n[\n    {\n        \"name\": \"id\",\n        \"type\": \"Integer\",\n        \"description\": \"The ID of the associated file.\"\n    }\n]) }}\n\n{{ api_method(\"getInProgress\", \"getInProgress ()\", \"Returns the number of items that are either currently uploading or waiting for an available connection (`qq.status.QUEUED`). If called inside of [a `cancel` event](events.html#cancel) handler, then this method will return a value that includes the upload associated with the `cancel` event handler. This is because the upload will not be canceled until the event handler returns.\", none, [\n    {\n        \"type\": \"Integer\",\n        \"description\": \"The number of items that are currently uploading or queued.\"\n    }\n]) }}\n\n{{ api_method(\"getName\", \"getName (id)\", \"Returns the name of the file with the associated id.\",\n[\n    {\n        \"name\": \"id\",\n        \"type\": \"Integer\",\n        \"description\": \"The file id\"\n    }\n],\n[\n    {\n        \"type\": \"String\",\n        \"description\": \"Returns the name of the file identified by the id.\"\n    }\n\n]) }}\n\n{{ api_method(\"getNetUploads\", \"getNetUploads ()\", \"Get the number of items that have been successfully uploaded and have not been deleted.\", null,\n[\n    {\n        \"type\": \"Integer\",\n        \"description\": \"The number of items that have been successfully uploaded and not deleted.\"\n    }\n\n]) }}\n\n{{ api_method(\"getParentId\", \"getParentId (scaledFileId)\", \"Get the ID of the parent file for this scaled file.\",\n[\n    {\n        \"name\": \"id\",\n        \"type\": \"Integer\",\n        \"description\": \"The ID of a scaled image file\"\n    }\n],\n[\n    {\n        \"type\": \"Integer\",\n        \"description\": \"Returns the ID of the scaled image's parent file.  `null` if this is not a scaled image or a parent cannot be located.\"\n    }\n\n]) }}\n\n{{ api_method(\"getRemainingAllowedItems\", \"getRemainingAllowedItems ()\", \"Returns the numer of remaining allowed items that may be submitted for upload based on [`validation.itemLimit`](options.html#validation.itemLimit).\", null,\n[\n    {\n        \"type\": \"Integer\",\n        \"description\": \"The number of items that may be submitted for upload.\"\n    }\n\n]) }}\n\n{{ api_method(\"getResumableFilesData\", \"getResumableFilesData ()\", \"Returns an array of potentially resumable items. Each resumable is represented by an object with the following properties:\n\n| Property             | Description |\n| -------------------- | :---------- |\n| `customResumeData`   | (optional) an object containing any custom resume data for the file |\n| `name`               | filename |\n| `remaining`          | number of bytes to be uploaded |\n| `size`               | total file size in bytes |\n| `uuid`               | the unique id |\n\", null,\n[\n    {\n        \"type\": \"Array\",\n        \"description\": \"An array of resumable items. \"\n    }\n\n]) }}\n\n{{ api_method(\"getSize\", \"getSize (id)\", \"Returns the size of the item with the associated id.\",\n[\n    {\n        \"name\": \"id\",\n        \"type\": \"Integer\",\n        \"description\": \"The file id.\"\n    }\n],\n[\n    {\n        \"type\": \"Integer\",\n        \"description\": \"The size of the file with the corresponding id.\"\n    }\n\n]) }}\n\n{{ api_method(\"getUploads\", \"getUploads (filter)\", \"Return information about all the items that have been submitted to the uploader. The objects being iterated over will have the following properties: `id`, `uuid`, `originalName`, `name`, `status`, and `size`. For information on filtering via the filter parameter, see the [upload status feature page](http://docs.fineuploader.com/branch/master/features/statistics-and-status-updates.html).\",\n[\n    {\n        \"name\": \"filter\",\n        \"type\": \"Object\",\n        \"description\": \"An object which indicates which keys and values must be present in an upload to be returned.\"\n    }\n],\n[\n    {\n        \"type\": \"Array or Object\",\n        \"description\": \"A list of items or a single item that has been filtered/found.  This returns an array only when\n        there is a potential for the operation to return more than one file in the result set. This excludes queries\n        for a specific, single ID or UUID. All other queries will return an array.\"\n    }\n\n]) }}\n\n{{ api_method(\"getUuid\", \"getUuid (id)\", \"Returns the UUID of the item with the associated id.\",\n[\n    {\n        \"name\": \"id\",\n        \"type\": \"Integer\",\n        \"description\": \"The fild id.\"\n    }\n],\n[\n    {\n        \"type\": \"String\",\n        \"description\": \"A level 4 UUID which identifies the corresponding file.\"\n    }\n\n]) }}\n\n{{ api_method(\"isResumable\", \"isResumable (id)\", \"Returns true if the file can be auto-resumed, false otherwise.\",\n[\n    {\n        \"name\": \"id\",\n        \"type\": \"Integer\",\n        \"description\": \"The file id.\"\n    }\n],\n[\n    {\n        \"type\": \"Boolean\",\n        \"description\": \"True if the file can be resumed and has a resume record, false otherwise\"\n    }\n\n]) }}\n\n{{ api_method(\"log\", \"log (message[, level])\", \"Output a message to the console, if possible.\",\n[\n    {\n        \"name\": \"message\",\n        \"type\": \"String\",\n        \"description\": \"The message to print\"\n    },\n    {\n        \"name\": \"level\",\n        \"type\": \"String\",\n        \"description\": \"The level to output the message at.\"\n    }\n]) }}\n\n{{ api_method(\"pauseUpload\", \"pauseUpload (id)\", \"Attempts to pause an in-progress upload.\",\n[\n    {\n        \"name\": \"id\",\n        \"type\": \"Integer\",\n        \"description\": \"The file id.\"\n    }\n],\n[\n    {\n        \"type\": \"Boolean\",\n        \"description\": \"`true` if the attempt was successful. `false` otherwise.\"\n    }\n\n]) }}\n\n{{ api_method(\"removeFileRef\", \"removeFileRef (id)\", \"\",\n[\n    {\n        \"name\": \"id\",\n        \"type\": \"Integer\",\n        \"description\": \"Remove internal reference to the associated Blob/File object. For Blobs that are created via JavaScript in the browser, this will free up all consumed memory.\"\n    }\n]) }}\n\n{{ api_method(\"reset\", \"reset ()\", \"Reset Fine Uploader\", null, null) }}\n\n{{ api_method(\"retry\", \"retry (id)\", \"Attempt to upload a specific item again.\",\n[\n    {\n        \"name\": \"id\",\n        \"type\": \"Integer\",\n        \"description\": \"The file id.\"\n    }\n], null) }}\n\n{{ api_method(\"scaleImage\", \"scaleImage (id, options)\", \"Generates a scaled version of a submitted image file.\",\n[\n    {\n        \"name\": \"id\",\n        \"type\": \"Integer\",\n        \"description\": \"The id of the image file.\"\n    },\n    {\n        \"name\": \"options\",\n        \"type\": \"Object\",\n        \"description\": \"Information about the scaled image to generate.  The following properties are supported:\n\n* `maxSize` (**required**) (integer).\n* `orient` (boolean, defaults to true)\n* `type` (string, defaults to the type of the reference image)\n* `quality` (number between 0 and 100, defaults to 80)\n* `includeExif` (boolean, defaults to `false`).\n* `customResizer` (function) - Ignored if the current browser does not [support image previews](../browser-support.html). If you want to use an alternate library to resize the image, you must contribute a function for this option that returns a `Promise`. Once the resize is complete, your promise must be fulfilled. You may, of course, reject your returned `Promise` is the resize fails in some way.\n\nA `resizeInfo` object, which will be passed to your (optional) `customResizer` function, contains the following properties:\n\n* `blob` - The original `File` or `Blob` object, if available.\n* `height` - Desired height of the image after the resize operation.\n* `image` - The original `HTMLImageElement` object, if available.\n* `sourceCanvas` - `HTMLCanvasElement` element containing the original image data (not resized).\n* `targetCanvas` - `HTMLCanvasElement` element containing the `HTMLCanvasElement` that should contain the resized image.\n* `width` - Desired width of the image after the resize operation.\n\"\n    }\n],\n[\n    {\n        \"type\": \"promise\",\n        \"description\": \"Fulfilled by passing the scaled image as a `Blob` back into the success callback after the original image has been scaled. If the scaled image cannot be generated, the failure callback will be invoked instead.\"\n    }\n\n]) }}\n\n{{ api_method(\"setCustomHeaders\", \"setCustomHeaders (customHeaders[, id])\", \"Set custom headers for an upload request. Pass in a file id to make the headers specific to that file.\",\n[\n    {\n        \"name\": \"customHeaders\",\n        \"type\": \"Object\",\n        \"description\": \"The custom headers to include in the upload request.  Fine Uploader may also send some other required headers.\"\n    },\n    {\n        \"name\": \"id\",\n        \"type\": \"Integer\",\n        \"description\": \"The file id.\"\n    }\n], null) }}\n\n{{ api_method(\"setCustomResumeData\", \"setCustomResumeData (id, customResumeData)\", \"Set custom resume data for a potentially resumable file. This data will be stored with the file's resume record and will be accessible in the onResume event handler and via the getResumableFilesData API method.\",\n[\n    {\n        \"name\": \"id\",\n        \"type\": \"Integer\",\n        \"description\": \"The file id.\"\n    }\n    {,\n        \"name\": \"customResumeData\",\n        \"type\": \"Object\",\n        \"description\": \"The custom resume data to store with the file's resume record.\"\n    }\n], null) }}\n\n{{ api_method(\"setEndpoint\", \"setEndpoint (path[, identifier])\", \"Modify the location where upload requests should be directed. Pass in a file id to change the endpoint for that specific item.\",\n[\n    {\n        \"name\": \"path\",\n        \"type\": \"String\",\n        \"description\": \"A valid URI where upload requests will be sent.\"\n    },\n    {\n        \"name\": \"identifier\",\n        \"type\": \"Integer or HTMLElement\",\n        \"description\": \"An integer corresponding to a file.\"\n    }\n], null) }}\n\n{{ api_method(\"setDeleteFileCustomHeaders\", \"setDeleteFileCustomHeaders (customHeaders[, id])\", \"Set custom headers for a delete file request. Pass in a file id to make the headers specific to that file.\",\n[\n    {\n        \"name\": \"customHeaders\",\n        \"type\": \"Object\",\n        \"description\": \"The custom headers to include in the delete request.  Fine Uploader may also send some other required headers.\"\n    },\n    {\n        \"name\": \"id\",\n        \"type\": \"Integer\",\n        \"description\": \"The file id.\"\n    }\n], null) }}\n\n{{ api_method(\"setDeleteFileEndpoint\", \"setDeleteFileEndpoint (path[, identifier])\", \"Modify the location where [delete requests](../features/delete.html) should be directed. Pass in a file id to change the endpoint for that specific item.\",\n[\n    {\n        \"name\": \"path\",\n        \"type\": \"String\",\n        \"description\": \"A valid URI where delete requests will be sent.\"\n    },\n    {\n        \"name\": \"identifier\",\n        \"type\": \"Integer or HTMLElement\",\n        \"description\": \"An integer corresponding to a file.\"\n    }\n], null) }}\n\n{{ api_method(\"setDeleteFileParams\", \"setDeleteFileParams (params[, id])\", \"Set the parameters for [a delete request](../features/delete.html). Pass in a file id to make the parameters specific to that file.\",\n[\n    {\n        \"name\": \"params\",\n        \"type\": \"Object\",\n        \"description\": \"The parameters to include in the delete request.\"\n    },\n    {\n        \"name\": \"id\",\n        \"type\": \"Integer\",\n        \"description\": \"The file id.\"\n    }\n\n], null) }}\n\n{{ api_method(\"setItemLimit\", \"setItemLimit (newItemLimit)\", \"Change [the `validation.itemLimit` option](options.html#validation.itemLimit) set during construction/initialization.\",\n[\n    {\n        \"name\": \"newItemLimit\",\n        \"type\": \"Integer\",\n        \"description\": \"The new file count limit.\"\n    }\n\n], null) }}\n\n{{ api_method(\"setForm\", \"setForm (formElementOrId)\", \"Bind a `<form>` to Fine Uploader dynamically. See the [form support feature page](../features/forms.html) for more details.\",\n[\n    {\n        \"name\": \"formElementOrId\",\n        \"type\": \"HTMLFormElement or String\",\n        \"description\": \"A form element or a form element's ID.\"\n    }\n\n], null) }}\n\n{{ api_method(\"setName\", \"setName (id, name)\", \"Change the name of a file.\",\n[\n    {\n        \"name\": \"id\",\n        \"type\": \"Integer\",\n        \"description\": \"The file id.\"\n    },\n    {\n        \"name\": \"name\",\n        \"type\": \"String\",\n        \"description\": \"The new file name.\"\n    }\n], null) }}\n\n{{ api_method(\"setParams\", \"setParams (params[, id])\", \"Set the parameters for an upload request. Pass in a file id to make the parameters specific to that file.\",\n[\n    {\n        \"name\": \"params\",\n        \"type\": \"Object\",\n        \"description\": \"The parameters to include in the upload request.\"\n    },\n    {\n        \"name\": \"id\",\n        \"type\": \"Integer\",\n        \"description\": \"The file id.\"\n    }\n], null) }}\n\n{{ api_method(\"setUuid\", \"setUuid (id, uuid)\", \"Change the UUID of a file.\",\n[\n    {\n        \"name\": \"id\",\n        \"type\": \"Integer\",\n        \"description\": \"The file id.\"\n    },\n    {\n        \"name\": \"uuid\",\n        \"type\": \"String\",\n        \"description\": \"The new file UUID.\"\n    }\n], null) }}\n\n{{ api_method(\"setStatus\", \"setStatus (id, newStatus)\",\n\"\"\" Modify the status of an file.\n\nThe status values correspond to those found in the `qq.status` object. Currently, the following status values may be set via this method:\n\n* `qq.status.DELETED`\n* `qq.status.DELETE_FAILED`\n\n\"\"\",\n[\n    {\n        \"name\": \"id\",\n        \"type\": \"Integer\",\n        \"description\": \"The file id.\"\n    },\n    {\n        \"name\": \"newStatus\",\n        \"type\": \"String\",\n        \"description\": \"The new `qq.status` value.\"\n    }\n], null) }}\n\n{{ api_method(\"uploadStoredFiles\", \"uploadStoredFiles ()\", \"Begin uploading all queued items. Throws a `NoFilesError` of there are no items to upload.\", null, null) }}\n</div>\n\n<div class=\"methods-ui\">\n{% markdown %}\n## UI\n{% endmarkdown %}\n\n{{ api_method(\"addExtraDropzone\", \"addExtraDropzone (element)\", \"Mark `element` as a drop zone.\",\n[\n    {\n        \"name\": \"element\",\n        \"type\": \"HTMLElement\",\n        \"description\": \"The element to mark as a drop zone.\"\n    }\n], null) }}\n\n{{ api_method(\"getDropTarget\", \"getDropTarget (id)\", \"Returns the (drop zone) element where the file was dropped. Undefined if drop event was not involved.\",\n[\n    {\n        \"name\": \"id\",\n        \"type\": \"Integer\",\n        \"description\": \"The id of a file.\"\n    }\n],\n[\n    {\n        \"type\": \"HTMLElement\",\n        \"description\": \"The drop zone element where the file as dropped.\"\n    }\n]) }}\n\n{{ api_method(\"getId\", \"getId (element)\", \"Returns the file `id` associated with an `HTMLElement`.\",\n[\n    {\n        \"name\": \"element\",\n        \"type\": \"HTMLElement\",\n        \"description\": \"Returns the ID of the associated file, given a file container element or a child of a file container element.\"\n    }\n],\n[\n    {\n        \"type\": \"Integer\",\n        \"description\": \"The id of the file.\"\n    }\n]) }}\n\n{{ api_method(\"getItemByFileId\", \"getItemByFileId (id)\", \"Returns the `HTMLElement` associated with the file id.\",\n[\n    {\n        \"name\": \"id\",\n        \"type\": \"Integer\",\n        \"description\": \"The file id.\"\n    }\n],\n[\n    {\n        \"type\": \"HTMLElement\",\n        \"description\": \"The `HTMLElement` that is associated with the file id.\"\n    }\n]) }}\n\n{{ api_method(\"removeExtraDropzone\", \"removeExtraDropzone (element)\", \"Used to un-mark an `element` as a drop zone.\",\n[\n    {\n        \"name\": \"element\",\n        \"type\": \"HTMLElement\",\n        \"description\": \"The element to un-mark as a drop zone.\"\n    }\n], null) }}\n\n</div>\n\n</div>\n{% endblock %}\n</div>\n"
  },
  {
    "path": "docs/api/options-azure.jmd",
    "content": "{% extends \"_templates/base.html\" %}\n{% set page_title = \"Azure Options\" %}\n\n{% block sidebar %}\n<div id=\"sidebar-accordion\" class=\"accordion\">\n</div>\n{% endblock %}\n\n{% block js_head %}\n<script src=\"{{ ASSETS }}/js/sidebar.js\"></script>\n{% endblock %}\n\n{% block js_footer %}\n<script>\n$(document).ready(function() {\n    console.log('herro')\n    renderOptionsSidebarNav(['azure-core', 'azure-ui']);\n});\n</script>\n{% endblock %}\n\n{% block content %}\n<div class=\"all-options\">\n<div class=\"options-azure-core\">\n{% markdown %}\n# Options <small>Azure</small> {: .page-header }\n\nIf you are using the Azure uploader, many of Fine Uploader's options are still\npresent, and most of them still function the same way. However, some of the\noptions are slightly different. This page will list any new or different options\nfor the Azure uploader. Any option not listed here can be assumed to have the same\nbehavior for the Traditional or Azure uploader.\n\n## Core\n\n{% endmarkdown %}\n\n{{\nalert(\"The [`chunking.paramNames` option](options.html#chunking) does **not** apply to Azure.\")\n}}\n{{ api_parent_option(\"chunking\", \"chunking\", \"\",\n    (\n        (\"chunking.partSize\", \"partSize\", \"The maximum size of each part, in bytes.  It is not recommenced you increase this value, as Azure will not accept parts larger than 4 MB.\", \"Integer\", \"4000000\",),\n        (\"chunking.minFileSize\", \"minFileSize\", \"Files smaller than this value will not be chunked.\", \"Integer\", \"4000001\",),\n    )\n)}}\n\n{{ api_parent_option(\"cors\", \"cors\", \"\",\n    (\n        (\"cors.allowXdr\", \"allowXdr\", \"Enables or disables cross-domain ajax calls (if [`expected` property](options.html#core.expected) is true) in IE9 and older.\", \"Boolean\", \"true\",),\n    )\n)}}\n\n{{ api_parent_option(\"blobProperties\", \"blobProperties\", \"\",\n    (\n        (\"blobProperties.name\", \"name\", \"Describes the blob name used to identify the file in your Azure Blob Storage container. Possible values are 'uuid', 'filename' or a function.  If the value is a function, Fine Uploader Azure will pass the associated file ID as a parameter when invoking your function. If the value is a function it may return one of a [`qq.Promise`](../features/async-tasks-and-promises.html) or a `String`.\", \"String or Function\", \"uuid\",),\n    )\n)}}\n\n{{\nalert(\"The [`resume.paramNames` option](options.html#resume) does **not** apply to Azure.\")\n}}\n\n{{\nalert(\"The [`request.customHeaders` option](options.html#request.customHeaders) does **not** apply to Azure.\")\n}}\n\n{{ api_parent_option(\"request\", \"request\", \"\",\n    (\n        (\"request.endpoint\", \"containerUrl\", \"URL for your Azure Blob Storage container.\", \"String\", \"null\",),\n        (\"request.params\", \"params\", \"Parameters passed along with each upload request.\", \"Object\", \"{}\",),\n        (\"request.filenameParam\", \"filenameParam\", \"Part of the parameter name that contains the name of the associated file which may differ from the blob name. Prefixed with 'x-ms-meta-' by Fine Uploader.\", \"String\", \"qqfilename\",),\n    )\n)}}\n\n{{ api_parent_option(\"signature\", \"signature\", \"\",\n    (\n        (\"signature.customHeaders\", \"customHeaders\", \"Additional headers sent along with each signature request.  If you declare a function as the value, the associated file's ID will be passed to your function when it is invoked.\", \"Object, Function\", \"{}\",),\n        (\"signature.endpoint\", \"endpoint\", \"The endpoint that Fine Uploader can use to send GET for a SAS before sending requests off to Azure.  The blob URL and underlying method type associated with the underlying REST request will be included in the query string.\", \"String\", \"null\",),\n    )\n)}}\n\n{{ api_parent_option(\"uploadSuccess\", \"uploadSuccess\", \"\",\n    (\n        (\"uploadSuccess.customHeaders\", \"customHeaders\", \"Additional headers sent along with each signature request.\", \"Object\", \"{}\",),\n        (\"uploadSuccess.endpoint\", \"endpoint\", \"An endpoint that Fine Uploader should POST to when a file has been successfully uploaded to Azure Blob Storage.\", \"String\", \"null\",),\n        (\"uploadSuccess.method\", \"method\", \"The request method (i.e. POST/PUT).\", \"String\", \"POST\",),\n        (\"uploadSuccess.params\", \"params\", \"Any additional parameters to attach to upload success file requests. Note that Fine Uploader will still send the blob name, container URL, filename, and UUID as well\", \"Object\", \"{}\",),\n    )\n)}}\n\n</div>\n\n\n<div class=\"options-azure-ui\">\n{% markdown %}\n## UI\n{% endmarkdown %}\n{{ api_parent_option(\"failedUploadTextDisplay\", \"failedUploadTextDisplay\", \"\",\n    (\n        (\"failedUploadTextDisplay.mode\", \"mode\", \"You will most likely want to keep this at the default value of 'custom'. See the UI options documentation for more info on this option.\", \"String\", \"custom\",),\n    )\n)}}\n\n</div>\n</div>\n\n{% endblock %}\n"
  },
  {
    "path": "docs/api/options-s3.jmd",
    "content": "{% extends \"_templates/base.html\" %}\n{% set page_title = \"S3 Options\" %}\n\n{% block sidebar %}\n<div id=\"sidebar-accordion\" class=\"accordion\">\n</div>\n{% endblock %}\n\n{% block js_head %}\n<script src=\"{{ ASSETS }}/js/sidebar.js\"></script>\n{% endblock %}\n\n{% block js_footer %}\n<script>\n$(document).ready(function() {\n    renderOptionsSidebarNav(['s3-core', 's3-ui']);\n});\n</script>\n{% endblock %}\n\n{% block content %}\n<div class=\"all-options\">\n<div class=\"options-s3-core\">\n{% markdown %}\n# Options <small>S3</small> {: .page-header }\n\nIf you are using the S3 uploader, all of Fine Uploader's options are still\npresent, and most of them still function the same way. However, some of the\noptions are slightly different. This page will list any new or different options\nfor the S3 uploader. Any option not listed here can be assumed to have the same\nbehavior as the traditional endpoint uploader.\n\n## Core\n\n{% endmarkdown %}\n\n{{ api_parent_option(\"credentials\", \"credentials\", \"\",\n    (\n        (\"credentials.accessKey\", \"accessKey\", \"Temporary public AWS key\", \"String\", \"null\",),\n        (\"credentials.expiration\", \"expiration\", \"Expiration date for temporary credentials.  May be an ISO 8601 String or a `Date` object.\", \"String or Date\", \"null\",),\n        (\"credentials.secretKey\", \"secretKey\", \"Temporary secret AWS key\", \"String\", \"null\",),\n        (\"credentials.sessionToken\", \"sessionToken\", \"Session token associated with the temporary credentials.\", \"String\", \"null\",),\n    )\n)}}\n\n{{\nalert(\"The [`chunking.paramNames` option](options.html#chunking) does **not** apply to S3.\")\n}}\n{{ api_parent_option(\"chunking\", \"chunking\", \"\",\n    (\n        (\"chunking.partSize\", \"partSize\", \"The maximum size of each part, in bytes.\", \"Integer\", \"5242880\",),\n    )\n)}}\n\n{{ api_parent_option(\"cors\", \"cors\", \"\",\n    (\n        (\"cors.allowXdr\", \"allowXdr\", \"Enables or disables cross-domain ajax calls (if [the `expected` property](options.html#cors.expected) is true) in IE9 and older.\", \"Boolean\", \"true\",),\n    )\n)}}\n\n{{ api_parent_option(\"iframeSupport\", \"iframeSupport\", \"\",\n    (\n        (\"iframeSupport.localBlankPagePath\", \"localBlankPagePath\", \"This is required if you plan on supporting browsers that do not implement the File API, such as IE9 and older.  This must point to a blank page on the same origin/domain as the page hosting Fine Uploader.\", \"String\", \"null\",),\n    )\n)}}\n\n{{ api_parent_option(\"objectProperties\", \"objectProperties\", \"\",\n    (\n        (\"objectProperties.acl\", \"acl\", \"This value corresponds to a [canned ACL](http://docs.aws.amazon.com/AmazonS3/latest/dev/ACLOverview.html#CannedACL)\", \"String\", \"private\",),\n        (\"objectProperties.bucket\", \"bucket\", \"Describes the name of the bucket used to house the file in S3.  This is required if the bucket cannot be determined by examining the endpoint (such as if you are using a CDN as an endpoint).  Possible values are a string representing the bucket name, or a function.  If the value is a function, Fine Uploader S3 will pass the associated file ID as a parameter when invoking your function. If the value is a function it may return a promise or a `String`.\", \"String or Function\", \"(assumes the bucket can be determined by parsing the endpoint string)\",),\n        (\"objectProperties.host\", \"host\", \"The hostname of your S3 bucket.  This is required if you are using version 4 signatures and sending files through a CDN. Possible values are a string representing the host name, or a function.  If the value is a function, Fine Uploader S3 will pass the associated file ID as a parameter when invoking your function. If the value is a function it may return a promise or a `String`.\", \"String or Function\", \"(uses the request endpoint to determine the hostname)\"),\n        (\"objectProperties.key\", \"key\", \"Describes the object key used to identify the file in your S3 bucket. Possible values are 'uuid', 'filename' or a function.  If the value is a function, Fine Uploader S3 will pass the associated file ID as a parameter when invoking your function. If the value is a function it may return one of a promise or a `String`.\", \"String or Function\", \"uuid\",),\n        (\"objectProperties.reducedRedundancy\", \"reducedRedundancy\", \"Set this to true if you would like to use the reduced redundancy storage class for all objects uploaded to S3.\", \"Boolean\", \"false\",),\n        (\"objectProperties.region\", \"region\", \"Version 4 signatures only: The [S3 region identifier](http://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region) for the target bucket.\", \"String\", \"us-east-1\",),\n        (\"objectProperties.serverSideEncryption\", \"serverSideEncryption\", \"Set this to true if you would like all uploaded files to be encrypted by AWS.\", \"Boolean\", \"false\",),\n    )\n)}}\n\n{{\nalert(\"The [`resume.paramNames` option](options.html#resume) does **not** apply to S3.\")\n}}\n\n{{\nalert(\"The [`request.customHeaders` option](options.html#request.customHeaders) does **not** apply to S3.\")\n}}\n\n{{ api_parent_option(\"request\", \"request\", \"\",\n    (\n        (\"request.accessKey\", \"accessKey\", \"Your AWS public key. **NOT YOUR SECRET KEY**. Ignored if [`credentials`](#credentials) have been set.\", \"String\", \"null\",),\n        (\"request.clockDrift\", \"clockDrift\", \"Number of milliseconds to add to the `x-amz-date` header and the policy expiration date to account for clock drift on the browser/client machine.\", \"Long\", \"0\",),\n        (\"request.endpoint\", \"endpoint\", \"URL for your S3 bucket or the URL of a CDN that forwards the request to S3.  All valid bucket URLs documented by Amazon are supported, including custom domains.  SSL is also supported.  If you use a CDN address, be sure to specify the bucket via [the `objectProperties.bucket` option](#objectProperties.bucket).\", \"String\", \"null\",),\n        (\"request.filenameParam\", \"filenameParam\", \"Part of the parameter name that contains the name of the associated file which may differ from the key name. Prefixed with 'x-amz-meta-' by Fine Uploader.\", \"String\", \"qqfilename\",),\n        (\"request.params\", \"params\", \"Parameters passed along with each upload request.\", \"Object\", \"{}\",),\n    )\n)}}\n\n{{ api_parent_option(\"signature\", \"signature\", \"\",\n    (\n        (\"signature.customHeaders\", \"customHeaders\", \"Additional headers sent along with each signature request.  If you declare a function as the value, the associated file's ID will be passed to your function when it is invoked.\", \"Object, Function\", \"{}\",),\n        (\"signature.endpoint\", \"endpoint\", \"The endpoint that Fine Uploader can use to send policy documents (HTML form uploads) or other strings to sign (REST requests) before sending requests off to S3.\", \"String\", \"null\",),\n        (\"signature.version\", \"version\", \"The AWS/S3 signature version to use. Currently supported values are `2` and `4`. Directly related to [`objectProperties.region`](#objectProperties.region).\", \"Integer\", \"2\",),\n    )\n)}}\n\n{{ api_parent_option(\"uploadSuccess\", \"uploadSuccess\", \"\",\n    (\n        (\"uploadSuccess.customHeaders\", \"customHeaders\", \"Additional headers sent along with each signature request.\", \"Object\", \"{}\",),\n        (\"uploadSuccess.endpoint\", \"endpoint\", \"An endpoint that Fine Uploader should POST to when a file has been successfully uploaded to S3.\", \"String\", \"null\",),\n        (\"uploadSuccess.method\", \"method\", \"The request method (i.e. POST/PUT).\", \"String\", \"POST\",),\n        (\"uploadSuccess.params\", \"params\", \"Any additional parameters to attach to upload success file requests. Note that Fine Uploader will still send the bucket, key, filename, UUID, and etag (if available) as well\", \"Object\", \"{}\",),\n    )\n)}}\n</div>\n\n\n<div class=\"options-s3-ui\">\n{% markdown %}\n## UI\n{% endmarkdown %}\n\n{{ api_parent_option(\"failedUploadTextDisplay\", \"failedUploadTextDisplay\", \"\",\n    (\n        (\"failedUploadTextDisplay.mode\", \"mode\", \"You will most likely want to keep this at the default value of 'custom'. See the UI options documentation for more info on this option.\", \"String\", \"custom\",),\n    )\n)}}\n</div>\n</div>\n\n{% endblock %}\n"
  },
  {
    "path": "docs/api/options-ui.jmd",
    "content": "{% extends \"_templates/base.html\" %}\n{% set page_title = \"UI Options\" %}\n\n{% block sidebar %}\n<div id=\"sidebar-accordion\" class=\"accordion\">\n</div>\n{% endblock %}\n\n{% block js_head %}\n<script src=\"{{ ASSETS }}/js/sidebar.js\"></script>\n{% endblock %}\n\n{% block js_footer %}\n<script type=\"text/javascript\">\n$(document).ready(function() {\n    renderOptionsSidebarNav(['ui']);\n});\n</script>\n{% endblock %}\n\n{% block content %}\n<div class=\"all-options\">\n<div class=\"options-ui\">\n{% markdown %}\n# Options <small>UI</small> {: .page-header }\n\n## UI\n\nFine Uploader [UI mode](../modes/ui.html) has a few different options as well as some options\npertaining specifically to the UI which [core mode](../modes/core.html) does not have.\n\nAny options that exist in Core mode also exist in UI mode, and, in most cases,\ncan be overridden.\n\n{% endmarkdown %}\n\n{{ api_option(\"element\", \"element\", \"Container element for the default drop zone\", \"HTMLElement\", \"null\") }}\n\n{{ api_option(\"listElement\", \"listElement\", \"Container element for the item list.\", \"HTMLElement\", \"null\") }}\n\n{{ api_option(\"multiple\", \"multiple\", \"When `false` this will prevent the user from simultaneously selecting or dropping more than one item. Dropping or selecting another item will clear the upload list. If another is already uploading, it will be canceled. To ignore rather than cancel, simply return `false` in the 'validate' or 'submit' event handlers.\", \"Boolean\", \"true\") }}\n\n{{ api_option(\"showMessage\", \"showMessage\", \"Provide a function here to display a message to the user when the uploader receives an error or the user attempts to leave the page. The provided function may return a promise if one wishes to do asynchronous work whilst waiting for user input.\", \"Function\", \"function(message) { window.alert(message); }\") }}\n\n{{ api_option(\"showConfirm\", \"showConfirm\", \"Provide a function here to prompt the user to confirm deletion of a file. The provided function may return a promise if one wishes to do asynchronous work whilst waiting for user input.\", \"Function\", \"function(message) { window.confirm(message); }\") }}\n\n{{ api_option(\"showPrompt\", \"showPrompt\", \"Provide a function here to prompt the user for a filename when pasting file(s). The provided function may return a promise if one wishes to do asynchronous work whilst waiting for user input.\", \"Function\", \"function(message, defaultValue) { window.prompt(message, defaultValue); }\") }}\n\n\n{{ api_option(\"template\", \"template\", \"This points to the container element that contains the template to use for one or more Fine Uploader UI instances.  You can either specify a string, which is the element ID (the ID of the container element on the page) or an `Element` that points to the container element.\", \"String or Element\", \"qq-template\") }}\n\n{{ api_parent_option(\"deleteFile\", \"deleteFile\",\n\"This section defines UI specific options for [the core `deleteFile` option](options.html#deleteFile).\",\n    (\n        (\"deleteFile.confirmMessage\", \"confirmMessage\", \"The message displayed in the confirm delete dialog.\", \"String\", \"Are you sure you want to delete {filename}?\",),\n        (\"deleteFile.deletingFailedText\", \"deletingFailedText\", \"The status message to appear next to a file that has failed to delete.\", \"String\", \"Delete failed\",),\n        (\"deleteFile.deletingStatusText\", \"deletingStatusText\", \"The status message to appear next to a file that is pending deletion.\", \"String\", \"Deleting...\",),\n        (\"deleteFile.forceConfirm\", \"forceConfirm\", \"If this value is set to `true`, the user will be required to confirm the file delete request via a confirmation dialog.\", \"Boolean\", \"false\",),\n    )\n)}}\n\n{{ api_parent_option(\"display\", \"display\", \"\",\n    (\n        (\"display.fileSizeOnSubmit\", \"fileSizeOnSubmit\", \"Enable or disable the display of the file size next to the file after it has been submitted.\", \"Boolean\", \"false\",),\n        (\"display.prependFiles\", \"prependFiles\", \"When `true` batches of files are added to the top of the UI's file list. The default is to append file(s) to the bottom of the list.\", \"Boolean\", \"false\",),\n    )\n)}}\n\n{{ api_parent_option(\"dragAndDrop\", \"dragAndDrop\", \"\",\n    (\n        (\"dragAndDrop.extraDropzones\", \"extraDropzones\", \"Designate additional drop zones for file input.\", \"Array\", \"[]\",),\n        (\"dragAndDrop.reportDirectoryPaths\", \"reportDirectoryPaths\", \"Include the path of dropped files (starting with the top-level dropped directory).  This value will be sent along with the request as a qqpath parameter.\", \"Boolean\", \"false\",),\n    )\n)}}\n\n{{ api_parent_option(\"failedUploadTextDisplay\", \"failedUploadTextDisplay\", \"\",\n    (\n        (\"failedUploadTextDisplay.enableTooltip\", \"enableTooltip\", \"Enable or disable a tooltip that will display the full contents of the error message when the mouse pointer hovers over the failed item.\", \"Boolean\", \"true\",),\n        (\"failedUploadTextDisplay.mode\", \"mode\", \"Set the message to display next to each failed file. One of: `'default'` which displays the `failedUploadText`, `'custom'` which displays the error response from the server, or `'none'` which displays no text.\", \"String\", \"default\",),\n        (\"failedUploadTextDisplay.responseProperty\", \"responseProperty\", \"The property from the server response that contains the error text to display next to a failed item. Ignored unless `mode` is `'custom'`\", \"String\", \"error\",),\n    )\n)}}\n\n{{ api_parent_option(\"messages\", \"messages\", \"\",\n    (\n        (\"messages.tooManyFilesError\", \"tooManyFilesError\", \"Text sent to [`showMessage`](#showMessage) when `multiple` is `false` and more than one file is dropped at once.\", \"String\", \"You may only drop one file.\",),\n        (\"messages.unsupportedBrowser\", \"unsupportedBrowser\", \"Text displayed to users who have ancient browsers.\", \"String\", \"Unrecoverable error - the browser does not permit uploading of any kind.\",),\n    )\n)}}\n\n{{ alert(\n\"\"\"`messages` is also in the Core mode options. This section defines UI specific\noptions for `messages`\"\"\", \"info\", \"Note:\") }}\n\n{{ api_parent_option(\"retry\", \"retry\", \"This section defines UI specific options for [the core `retry` option](options.html#retry)\",\n    (\n        (\"retry.autoRetryNote\", \"autoRetryNote\", \"The text of the note that will optionally appear next to the item during automatic retry attempts. Ignored if `showAutoRetryNote` is false.\", \"String\", \"Retrying {retryNum}/{maxAuto} ...\",),\n        (\"retry.showButton\", \"showButton\", \"Enable or disable the showing of a button/link next to the failed item after all retry attempts have been exhausted. Clicking the button/link will force the uploader to make another attempt.\", \"Boolean\", \"false\",),\n        (\"retry.showAutoRetryNote\", \"showAutoRetryNote\", \"Enable or disable a status message appearing next to the item during auto retry attempts.\", \"Boolean\", \"true\",),\n    )\n)}}\n\n{{ api_parent_option(\"thumbnails\", \"thumbnails\", \"\",\n    (\n        (\"thumbnails.customResizer\", \"customResizer\", \"\"\"Ignored if the current browser does not [support image previews](../browser-support.html). If you want to use an alternate library to resize the image, you must contribute a function for this option that returns a `Promise`. Once the resize is complete, your promise must be fulfilled. You may, of course, reject your returned `Promise` is the resize fails in some way.\n\nA `resizeInfo` object, which will be passed to the supplied function, contains the following properties:\n\n* `blob` - The original `File` or `Blob` object, if available.\n* `height` - Desired height of the image after the resize operation.\n* `image` - The original `HTMLImageElement` object, if available.\n* `sourceCanvas` - `HTMLCanvasElement` element containing the original image data (not resized).\n* `targetCanvas` - `HTMLCanvasElement` element containing the `HTMLCanvasElement` that should contain the resized image.\n* `width` - Desired width of the image after the resize operation.\n\",\n        \"Function\", \"undefined\"),\n        (\"thumbnails.maxCount\", \"maxCount\", \"Maximum number of previews to render per Fine Uploader instance.  A call to the reset method resets this value as well.\", \"Integer\", \"0\",),\n        (\"thumbnails.timeBetweenThumbs\", \"timeBetweenThumbs\", \"The amount of time, in milliseconds, to pause between each preview generation process.  This is in place to prevent the UI thread from locking up for a continuously long period of time, as preview generation can be a resource-intensive process.\", \"Integer\", \"750\",),\n    )\n)}}\n\n{{ api_parent_option(\"thumbnails.placeholders\", \"thumbnails.placeholders\", \"\",\n    (\n        (\"thumbnails.placeholders.notAvailablePath\", \"notAvailablePath\", \"Absolute URL or relative path to the image to display if the preview/thumbnail could not be generated/displayed.\", \"String\", \"null\",),\n        (\"thumbnails.placeholders.waitingPath\", \"waitingPath\", \"Absolute URL or relative path to the image to display during preview generation (modern browsers) or until the server response has been parsed (older browsers).\", \"String\", \"null\",),\n        (\"thumbnails.placeholders.waitUntilResponse\", \"waitUntilResponse\", \"Set this to `true` if you want the 'waiting' placeholder image to remain in place until the server response has been parsed.  This is useful if you expect to return thumbnail URLs in your upload responses for files types that cannot be previewed.  This option is ignored in older browsers where client-side previews cannot be generated.\", \"Boolean\", \"false\",),\n    )\n)}}\n\n{{ api_parent_option(\"paste\", \"paste\", \"\",\n    (\n        (\"paste.namePromptMessage\", \"namePromptMessage\", \"Text that will appear in [the `showPrompt` dialog](#showPrompt).\", \"String\", \"Please name this image\",),\n        (\"paste.promptForName\", \"promptForName\", \"Enable or disable the usage of [`showPrompt`](#showPrompt) by Fine Uploader to prompt the user for a filename for a pasted file.\", \"Boolean\", \"false\",),\n    )\n)}}\n\n{{ api_parent_option(\"scaling\", \"scaling\", \"See the [Upload Scaled Images feature page](../features/scaling.html) for more details.\",\n    (\n        (\"scaling.failureText\", \"failureText\", \"Text that will appear next to a scaled image that could not be generated.  This is in addition to the behavior associated with this property provided by Fine Uploader Core.\", \"String\", \"Failed to scale\",),\n        (\"scaling.hideScaled\", \"hideScaled\", \"Set this to true if you do not want any scaled images to be displayed in the file list.\", \"Boolean\", \"false\",),\n    )\n)\n}}\n\n{{ api_parent_option(\"text\", \"text\", \"\",\n    (\n        (\"text.failUpload\", \"failUpload\", \"Text that appears next to a failed item\", \"String\", \"Upload failed\",),\n        (\"text.formatProgress\", \"formatProgress\", \"Appears next to a currently uploading item\", \"String\", \"{percent}% of {total_size}\",),\n        (\"text.paused\", \"paused\", \"Appears next to a paused item\", \"String\", \"Paused\",),\n        (\"text.waitingForResponse\", \"waitingForResponse\", \"Appears next to item once the last bytes have been sent (differs on the user-agent)\", \"String\", \"Processing...\",),\n    )\n)}}\n\n{% endblock %}\n"
  },
  {
    "path": "docs/api/options.jmd",
    "content": "{% extends \"_templates/base.html\" %}\n{% set page_title = \"Core Options\" %}\n\n{% block sidebar %}\n<div id=\"sidebar-accordion\" class=\"accordion\">\n</div>\n{% endblock %}\n\n{% block js_head %}\n<script src=\"{{ ASSETS }}/js/sidebar.js\"></script>\n{% endblock %}\n\n{% block js_footer %}\n<script>\n$(document).ready(function() {\n    renderOptionsSidebarNav(['core']);\n});\n</script>\n{% endblock %}\n\n{% block content %}\n<div class=\"all-options\">\n<div class=\"options-core\">\n{% markdown %}\n[forms]: ../features/forms.html\n\n# Options <small>Core</small> {: .page-header }\n\nFine Uploader has a **plethora** of options. Many of these options change meaning\ndepending on what sort of uploader you are using. Pay close attention to the\nmode you are in and keep that in mind when determining the meaning of a particular option.\n\n## Core\n{% endmarkdown %}\n\n{{ api_option(\"autoUpload\", \"autoUpload\", \"Set to `false` if you want to be able to upload queued items later by calling the [`uploadStoredFiles()` method](methods.html#uploadStoredFiles).\", \"Boolean\", \"true\") }}\n\n{{ api_option(\"button\", \"button\", \"Specify an element to use as the 'select files' button. [Cannot be a `<button>`](https://github.com/FineUploader/fine-uploader/issues/33).\", \"HTMLElement\", \"null\") }}\n\n{{ api_option(\"debug\", \"debug\", \"This will result in log messages being written to the [`window.console`](https://developer.mozilla.org/en-USDOM/console.log) object\", \"Boolean\", \"false\") }}\n\n{{ api_option(\"disableCancelForFormUploads\", \"disableCancelForFormUploads\", \"When true the cancel link does not appear next to files when the form uploader is used.\", \"Boolean\", \"false\") }}\n\n{{ api_option(\"formatFileName\", \"formatFileName\", \"Provide a function to control the display of file names. The raw file name is passed into the function when it is invoked. Your function may return a modified file name. Note that this does not affect the *actual* file name, only the _displayed_ file name.\", \"Function\", \"\") }}\n\n{{ api_option(\"maxConnections\", \"maxConnections\", \"Maximum allowable concurrent requests\", \"Integer\", \"3\") }}\n\n{{ api_option(\"multiple\", \"multiple\", \"When false this will prevent the user from simultaneously selecting or dropping more than one item.\", \"Boolean\", \"true\") }}\n\n{{ api_option(\"warnBeforeUnload\", \"warnBeforeUnload\", \"When true Fine Uploader will ensure a modal confirmation dialog appears whenever a user tries to navigate away from the page with uploads in progress.\", \"Boolean\", \"true\") }}\n\n{{ api_parent_option(\"blobs\", \"blobs\", \"\",\n    (\n        (\"blobs.defaultName\", \"defaultName\", \"The default name to be used for nameless `Blob`s.\", \"String\", \"Misc data\",),\n    )\n)\n}}\n\n{{ api_parent_option(\"callbacks\", \"callbacks\", \"Provide event handlers for almost any point in the upload process in order to unlock the full potential of Fine Uploader. See the [Events page](./events.html) for more details.\",\n    ()\n)\n}}\n\n{{ api_parent_option(\"camera\", \"camera\", \"\",\n    (\n        (\"camera.button\", \"button\", \"`null` allows camera access on the default button in iOS. Otherwise provide an extra button container element to target.\", \"HTMLElement\", \"null\",),\n        (\"camera.ios\", \"ios\", \"Enable or disable camera access on iOS (iPod, iPhone, and iPad) devices. Note: Enabling this will disable multiple file selection.\", \"Boolean\", \"false\",),\n    )\n)\n}}\n\n{{\nalert(\"The `chunking.success.endpoint` option **only** applies to traditional upload endpoints (not S3, Azure, etc).\")\n}}\n{{ api_parent_option(\"chunking\", \"chunking\", \"\",\n    (\n        (\"chunking.concurrent.enabled\", \"concurrent.enabled\", \"Allow multiple chunks to be uploaded simultaneously per file\", \"Boolean\", \"false\",),\n\n        (\"chunking.enabled\", \"enabled\", \"Enable or disable splitting the file separate chunks. Each chunks is sent in a separate request.\", \"Boolean\", \"false\",),\n        (\"chunking.mandatory\", \"mandatory\", \"Ensure _every_ file is uploaded in chunks, even if the file can only be split up into 1 chunk.  Does not apply if chunking is not possible in the current browser.\", \"Boolean\", \"false\",),\n        (\"chunking.partSize\", \"partSize\", \"The maximum size of each chunk, in bytes. If a function value is provided, the file's ID will be passed when invoking the function (which should only be called once per file).\", \"Integer or Function\", \"2000000\",),\n\n        (\"chunking.paramNames.chunkSize\", \"paramNames.chunkSize\", \"Name of the parameter passed with a chunked request that specifies the size in bytes of the associated chunk.\", \"String\", \"qqchunksize\",),\n        (\"chunking.paramNames.partByteOffset\", \"paramNames.partByteOffset\", \"Name of the parameter passed with a chunked request that specifies the starting byte of the associated chunk.\", \"String\", \"qqpartbyteoffset\",),\n        (\"chunking.paramNames.partIndex\", \"paramNames.partIndex\", \"Name of the parameter passed with a chunked request that specifies the index of the associated partition.\", \"String\", \"qqpartindex\",),\n        (\"chunking.paramNames.totalParts\", \"paramNames.totalParts\", \"Name of the parameter passed with a chunked request that specifies the total number of chunks associated with the `File` or `Blob`.\", \"String\", \"qqtotalparts\",),\n\n        (\"chunking.success.endpoint\", \"success.endpoint\", \"Endpoint to send a POST after all chunks have been successfully uploaded for each file.  Required if the [`concurrent.enabled` option](#chunking.concurrent.enabled) is set. If a function value is provided, the file's ID will be passed when invoking the function.\", \"String or Function\", \"null\",),\n        (\"chunking.success.headers\", \"success.headers\", \"Headers to send to with chunking success request. The file's ID will be passed to your provided function.\", \"Function\", \"function(fileId) { return null }\",),\n        (\"chunking.success.jsonPayload\", \"success.jsonPayload\", \"Send all parameters in the request body JSON-encoded. Otherwise params will be sent application/x-www-form-urlencoded.\", \"Boolean\", \"false\",),\n        (\"chunking.success.method\", \"success.method\", \"HTTP method used when sending the success request.\", \"String\", \"POST\",),\n        (\"chunking.success.params\", \"success.params\", \"Parameters to send in the message body of the success request.  The file's ID will be passed to your provided function.\", \"Function\", \"function(fileId) { return null }\",),\n        (\"chunking.success.resetOnStatus\", \"success.resetOnStatus\", \"Fine Uploader will reset the file such that a retry will start at chunk 0 if the success response status matches any of the provided status codes.\", \"Array\", \"[]\",),\n    )\n)\n}}\n\n{{ api_parent_option(\"cors\", \"cors\", \"\",\n    (\n        (\"cors.allowXdr\", \"allowXdr\", \"Enable or disable cross-origin requests from IE9 and older where [XDomainRequest](https://developer.mozilla.org/en-US/docs/HTTP/Access_control_CORS#Note) must be used.\", \"Boolean\", \"false\",),\n        (\"cors.expected\", \"expected\", \"Enable or disable cross-domain requests.\", \"Boolean\", \"false\",),\n        (\"cors.sendCredentials\", \"sendCredentials\", \"Enable or disable sending credentials along with each cross-domain request. Ignored if `allowXdr` is `true` and IE9 is being used.\", \"Boolean\", \"false\",),\n    )\n)\n}}\n\n{{ api_parent_option(\"deleteFile\", \"deleteFile\", \"\",\n    (\n        (\"deleteFile.customHeaders\", \"customHeaders\", \"Any additional headers to attach to all delete file requests.\", \"Object\", \"{}\",),\n        (\"deleteFile.enabled\", \"enabled\", \"Enable or disable deletion of uploaded files.\", \"Boolean\", \"false\",),\n        (\"deleteFile.endpoint\", \"endpoint\", \"The endpoint to which delete file requests are sent.\", \"String\", \"/server/upload\",),\n        (\"deleteFile.method\", \"method\", \"Set the method to use for delete requests. Accepts 'POST' or 'DELETE'\", \"String\", \"DELETE\",),\n        (\"deleteFile.params\", \"params\", \"Any additional parameters to attach to delete file requests.\", \"Object\", \"{}\",),\n    )\n)\n}}\n\n{{ api_parent_option(\"extraButtons\", \"extraButtons\", \"This must contain an array of objects. Each object will describe an “extra” button that Fine Uploader will track along with the default upload button. Each button object can contain the following properties:\",\n    (\n        (\"extraButtons.element\", \"element\", \"REQUIRED: The container element for the upload button.\", \"HTMLElement\", \"undefined\",),\n        (\"extraButtons.fileInputTitle\", \"fileInputTitle\", \"This value will be used when creating the `title` attribute for the underlying `<input type=\\\"file\\\">`. If not provided, the [`text.fileInputTitle` option](#text.fileInputTitle) will be used instead.\", \"String\", \"file input\",),\n        (\"extraButtons.folders\", \"folders\", \"`true` to allow folders to be selected, `false` to allow files to be selected. See the browser support page for details regarding the limited user agent support for this feature.\", \"Boolean\", \"false\",),\n        (\"extraButtons.multiple\", \"multiple\", \"Specify to override the default `multiple` value.\", \"Boolean\", \"true\",),\n        (\"extraButtons.validation\", \"validation\", \"Specify to override the default `validation` option specified. Any `validation` properties not specified will be inherited from the default `validation` option.\", \"Object\", \"`validation`\",),\n    )\n)\n}}\n\n{{ api_parent_option(\"form\", \"form\", \"Override default values for form support.  See the [form support][forms] feature page for more details.\",\n    (\n        (\"form.element\", \"element\", \"This can be the ID of the `<form>` or a reference to the `<form>` element.\", \"String, HTMLElement\", \"qq-form\",),\n        (\"form.autoUpload\", \"autoUpload\", \"If Fine Uploader is able to attach to a form, this value takes the place of the base `autoUpload` option.\", \"Boolean\", \"false\",),\n        (\"form.interceptSubmit\", \"interceptSubmit\", \"Set this to `false` if you do not want Fine Uploader to intercept attempts to submit your form.  By default, Fine Uploader will intercept submit attempts and instead upload all submitted files, including data from your form in each upload request.\", \"Boolean\", \"true\",)\n    )\n)\n}}\n\n{{ api_parent_option(\"messages\", \"messages\", \"\",\n    (\n        (\"messages.emptyError\", \"emptyError\", \"Text passed to the error event handler if a submitted item is zero bits.\", \"String\", \"{file} is empty, please select files again without it.\",),\n        (\"messages.maxHeightImageError\", \"maxHeightImageError\", \"Text passed to the error event handler if an image is too tall.\", \"String\", \"Image is too tall.\",),\n        (\"messages.maxWidthImageError\", \"maxWidthImageError\", \"Text passed to the error event handler if an image is too wide.\", \"String\", \"Image is too wide.\",),\n        (\"messages.minHeightImageError\", \"minHeightImageError\", \"Text passed to the error event handler if an image is not tall enough.\", \"String\", \"Image is not tall enough.\",),\n        (\"messages.minWidthImageError\", \"minWidthImageError\", \"Text passed to the error event handler if an image is not wide enough.\", \"String\", \"Image is not wide enough.\",),\n        (\"messages.minSizeError\", \"minSizeError\", \"Text passed to the error event handler if the item is too small.\", \"String\", \"{file} is too small, minimum file size is {minSizeLimit}.\",),\n        (\"messages.noFilesError\", \"noFilesError\", \"Text passed to the error event handler if any empty array of items is submitted.\", \"String\", \"No files to upload.\",),\n        (\"messages.onLeave\", \"onLeave\", \"Text displayed to the user when they attempt to leave the page while uploads are still in progress.\", \"String\", \"The files are being uploaded, if you leave now the upload will be canceled.\",),\n        (\"messages.retryFailTooManyItemsError\", \"retryFailTooManyItemsError\", \"Text passed to the error event handler if a retry attempt is declared a failed due to a violation of [the `validation.itemLimit` rule](#validation.itemLimit).\", \"String\", \"Retry failed - you have reached your file limit.\",),\n        (\"messages.sizeError\", \"sizeError\", \"Text passed to the error event handler if a submitted item is too large.\", \"String\", \"{file} is too large, maximum file size is {sizeLimit}.\",),\n        (\"messages.tooManyItemsError\", \"tooManyItemsError\", \"Text passed to the error event handler if a submit is ignored due to a violation of [the `validation.itemLimit` rules](#validation.itemLimit).\", \"String\", \"Too many items ({netItems}) would be uploaded.  Item limit is {itemLimit}.\",),\n        (\"messages.typeError\", \"typeError\", \"Text passed to the error event handler if an invalid file type is detected.\", \"String\", \"{file} has an invalid extension. Valid extension(s): {extensions}.\",),\n        (\"messages.unsupportedBrowserIos8Safari\", \"unsupportedBrowserIos8Safari\", \"Message displayed if the browser is iOS8 Safari and the corresponding workarounds option is not disabled.\", \"String\", \"Unrecoverable error - this browser does not permit file uploading of any kind due to serious bugs in iOS8 Safari.  Please use iOS8 Chrome until Apple fixes these issues.\",),\n    )\n)\n}}\n\n{{ api_parent_option(\"paste\", \"paste\", \"\",\n    (\n        (\"paste.defaultName\", \"defaultName\", \"The default name given to pasted images.\", \"String\", \"pasted_image\",),\n        (\"paste.targetElement\", \"targetElement\", \"Enable this feature by providing any `HTMLElement` here.\", \"HTMLElement\", \"null\",),\n    )\n)\n}}\n\n{{ api_parent_option(\"resume\", \"resume\", \"\",\n    (\n        (\"resume.customKeys\", \"customKeys\", \"Define custom keys used to identify this file among other resume records. The file's ID will be passed to your provided function.\", \"Function\", \"function(fileId) { return [] }\",),\n        (\"resume.enabled\", \"enabled\", \"Enable or disable the ability to resume failed or stopped chunked uploads.\", \"Boolean\", \"false\",),\n        (\"resume.paramNames.resuming\", \"paramNames.resuming\", \"Sent with the first request of the resume with a value of `true`.\", \"String\", \"qqresume\",),\n        (\"resume.recordsExpireIn\", \"recordsExpireIn\", \"The number of days before a persistent resume record will expire.\", \"Integer\", \"7\",),\n    )\n)\n}}\n\n{{ api_parent_option(\"retry\", \"retry\", \"\",\n    (\n        (\"retry.autoAttemptDelay\", \"autoAttemptDelay\", \"The number of seconds to wait between auto retry attempts.\", \"Integer\", \"5\",),\n        (\"retry.enableAuto\", \"enableAuto\", \"Enable or disable retrying uploads that receive any error response.\", \"Boolean\", \"false\",),\n        (\"retry.maxAutoAttempts\", \"maxAutoAttempts\", \"The maximum number of times to attempt to retry a failed upload.\", \"Integer\", \"3\",),\n        (\"retry.preventRetryResponseProperty\", \"preventRetryResponseProperty\", \"This property will be looked for in the server response and, if found and `true`, will indicate that no more retries should be attempted for this item.\", \"String\", \"preventRetry\",),\n    )\n)\n}}\n\n{{ api_parent_option(\"request\", \"request\", \"\",\n    (\n        (\"request.customHeaders\", \"customHeaders\", \"Additional headers sent along with each upload request.\", \"Object\", \"{}\",),\n        (\"request.endpoint\", \"endpoint\", \"The endpoint to send upload requests to.\", \"String\", \"/server/upload\",),\n        (\"request.filenameParam\", \"filenameParam\", \"The name of the parameter passed if the original filename has been edited or a `Blob` is being sent.\", \"String\", \"qqfilename\",),\n        (\"request.forceMultipart\", \"forceMultipart\", \"Force all uploads to use multipart encoding\", \"Boolean\", \"true\",),\n        (\"request.inputName\", \"inputName\", \"The attribute of the input element which will contain the file name. For non-multipart-encoded upload requests, this will be included as a parameter in the query string of the URI with a value equal to the file name.\", \"String\", \"qqfile\",),\n        (\"request.method\", \"method\", \"Specify a method to use when sending files to a traditional endpoint.  This option is ignored in older browsers (such as IE 9 and older).\", \"String\", \"POST\",),\n        (\"request.omitDefaultParams\", \"omitDefaultParams\", \"If set to true, any Fine Uploader created parameters (qq*) will not be sent with the upload request.\", \"Boolean\", \"false\",),\n        (\"request.params\", \"params\", \"The parameters that shall be sent with each upload request.\", \"Object\", \"{}\",),\n        (\"request.paramsInBody\", \"paramsInBody\", \"Enable or disable sending parameters in the request body. If `false`, parameters are sent in the URL. Otherwise, parameters are sent in the request body.\", \"Boolean\", \"true\",),\n        (\"request.requireSuccessJson\", \"requireSuccessJson\", \"If set to true, each upload response MUST contain a JSON message-body with {success: true} in order to be considered a success. If set to false, the success of the request is determined by examining the response status code.\", \"Boolean\", \"true\",),\n        (\"request.uuidName\", \"uuidName\", \"The name of the parameter the uniquely identifies each associated item. The value is a [Level 4 UUID](http://en.wikipedia.org/wiki/Universally_unique_identifier#Version_4_.28random.29).\", \"String\", \"qquuid\",),\n        (\"request.totalFileSizeName\", \"totalFileSizeName\", \"The name of the parameter passed that specifies the total file size in bytes.\", \"String\", \"qqtotalfilesize\",),\n    )\n)\n}}\n\n{{ api_parent_option(\"scaling\", \"scaling\", \"See the [Upload Scaled Images feature page](../features/scaling.html) for more details.\",\n    (\n        (\"scaling.customResizer\", \"customResizer\", \"Ignored if the current browser does not [support image previews](../browser-support.html). If you want to use an alternate scaling library, you must contribute a function for this option that returns a `Promise`. Once the resize is complete, your promise must be fulfilled. You may, of course, reject your returned `Promise` is the resize fails in some way.\n\n    A `resizeInfo` object, which will be passed to the supplied function, contains the following properties:\n\n* `blob` - The original `File` or `Blob` object, if available.\n* `height` - Desired height of the image after the resize operation.\n* `image` - The original `HTMLImageElement` object, if available.\n* `sourceCanvas` - `HTMLCanvasElement` element containing the original image data (not resized).\n* `targetCanvas` - `HTMLCanvasElement` element containing the `HTMLCanvasElement` that should contain the resized image.\n* `width` - Desired width of the image after the resize operation.\n\",\n        \"Function\", \"undefined\"),\n        (\"scaling.defaultQuality\", \"defaultQuality\", \"A value between 1 and 100 that describes the requested quality of scaled images.  Ignored unless the scaled image type target is image/jpeg.\", \"Integer\", \"80\",),\n        (\"scaling.defaultType\", \"defaultType\", \"Scaled images will assume this image type if you don't specify a specific type in your size object, or if the type specified in the size object is not valid.  You generally should not use any value other than image/jpeg or image/png here.  The default value of `null` will ensure the scaled image type is PNG, unless the original file is a JPEG, in which case the scaled file will also be a JPEG.  The default is probably the safest option.\", \"String\", \"null\",),\n        (\"scaling.failureText\", \"failureText\", \"Text sent to your `complete` event handler as an `error` property of the `response` param if a scaled image could not be generated.\", \"String\", \"Failed to scale\",),\n        (\"scaling.includeExif\", \"includeExif\", \"Ensure the EXIF data from the reference image is inserted into the scaled image.  Only applicable when both the reference and the target are type image/jpeg.\", \"Boolean\", \"false\",),\n        (\"scaling.orient\", \"orient\", \"Set this to `false` if you do not want scaled images to be re-oriented based on parsed EXIF data before they are uploaded.\", \"Boolean\", \"true\",),\n        (\"scaling.sendOriginal\", \"sendOriginal\", \"Set this to `false` if you don't want to original file to be uploaded as well.\", \"Boolean\", \"true\",),\n        (\"scaling.sizes\", \"sizes\", \"An array containing size objects that describe scaled versions of each submitted image that should be generated and uploaded.  A size object should usually contain a `name` String property (which will be appended to the file name of the scaled file), and must always contain a `maxSize` integer property.  A `type` MIME string property is optional.\", \"Array\", \"[]\",),\n    )\n)\n}}\n\n{{ api_parent_option(\"session\", \"session\", \"See the [Initial File List feature page](../features/session.html) for more details.\",\n    (\n        (\"session.customHeaders\", \"customHeaders\", \"Any additional headers you would like included with the GET request sent to your server. Ignored in IE9 and IE8 if the endpoint is cross-origin.\", \"Object\", \"{}\",),\n        (\"session.endpoint\", \"endpoint\", \"If non-null, Fine Uploader will send a GET request on startup to this endpoint, expecting a JSON response containing data about the initial file list to populate.\", \"String\", \"null\",),\n        (\"session.params\", \"params\", \"Any parameters you would like passed with the associated GET request to your server.\", \"Object\", \"{}\",),\n        (\"session.refreshOnReset\", \"refreshOnReset\", \"Set this to `false` if you do not want the file list to be retrieved from the server as part of a reset.\", \"Boolean\", \"true\",),\n    )\n)\n}}\n\n{{ api_parent_option(\"text\", \"text\", \"\",\n    (\n        (\"text.defaultResponseError\", \"defaultResponseError\", \"In the event of non-200 response from the server sans the 'error' property, this message will be passed to the 'error' event handler.\", \"String\", \"Upload failure reason unknown\",),\n        (\"text.fileInputTitle\", \"fileInputTitle\", \"The value for the `title` attribute attached to the `<input type=\\\"file\\\">` maintained by Fine Uploader for each upload button. This is used as hover text, among other things.\", \"String\", \"file input\",),\n        (\"text.sizeSymbols\", \"sizeSymbols\", \"Symbols used to represent file size, in ascending order.\", \"Array\", \"['kB', 'MB', 'GB', 'TB', 'PB', 'EB']\",),\n    )\n)\n}}\n\n{{ api_parent_option(\"validation\", \"validation\", \"\",\n    (\n        (\"validation.acceptFiles\", \"acceptFiles\", \"Used by the file selection dialog. Restrict the valid file types that appear in the selection dialog by listing valid content-type specifiers here. See docs on the [accept attribute of the `<input>` element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Input)\", \"Comma-delimited list of valid MIMEtypes\", \"null\",),\n        (\"validation.allowedExtensions\", \"allowedExtensions\", \"Specify file valid file extensions here to restrict uploads to specific types.\", \"Array\", \"[]\",),\n        (\"validation.allowEmpty\", \"allowEmpty\", \"Allow file size of 0 bytes\", \"Boolean\", \"false\"),\n        (\"validation.itemLimit\", \"itemLimit\", \"Maximum number of items that can be potentially uploaded in this session. Will reject all items that are added or retried after this limit is reached.\", \"Integer\", \"0\",),\n        (\"validation.minSizeLimit\", \"minSizeLimit\", \"The minimum allowable size, in bytes, for an item.\", \"Integer\", \"0\",),\n        (\"validation.sizeLimit\", \"sizeLimit\", \"The maximum allowable size, in bytes, for an item.\", \"Integer\", \"0\",),\n        (\"validation.stopOnFirstInvalidFile\", \"stopOnFirstInvalidFile\", \"When `true` the first invalid item will stop processing further files.\", \"Boolean\", \"true\",),\n\n        (\"validation.image.maxHeight\", \"image.maxHeight\", \"Restrict images to a maximum height in pixels (wherever possible).\", \"Integer\", \"0\",),\n        (\"validation.image.maxWidth\", \"image.maxWidth\", \"Restrict images to a maximum width in pixels (wherever possible).\", \"Integer\", \"0\",),\n        (\"validation.image.minHeight\", \"image.minHeight\", \"Restrict images to a minimum height in pixels (wherever possible).\", \"Integer\", \"0\",),\n        (\"validation.image.minWidth\", \"image.minWidth\", \"Restrict images to a minimum width in pixels (wherever possible).\", \"Integer\", \"0\",)\n    )\n)\n}}\n\n{{ api_parent_option(\"workarounds\", \"workarounds\", \"Flags that enable or disable workarounds for browser-specific bugs.\",\n    (\n        (\"workarounds.iosEmptyVideos\", \"iosEmptyVideos\", \"Ensures all `<input type='file'>` elements tracked by Fine Uploader do NOT contain a `multiple` attribute to work around an issue present in iOS7 & 8 that otherwise results in 0-sized uploaded videos.\", \"Boolean\", \"true\",),\n        (\"workarounds.ios8BrowserCrash\", \"ios8BrowserCrash\", \"Ensures all `<input type='file'>` elements tracked by Fine Uploader always have a `multiple` attribute present.  This only applies to iOS8 Chrome and iOS8 UIWebView, and is put in place to work around an issue that causes the browser to crash when a file input element does not contain a `multiple` attribute inside of a UIWebView container created by an iOS8 app compiled with and iOS7 SDK.\", \"Boolean\", \"false\",),\n        (\"workarounds.ios8SafariUploads\", \"ios8SafariUploads\", \"Disables Fine Uploader and displays a message to the user in iOS 8.0.0 Safari.  Due to serious bugs in iOS 8.0.0 Safari, uploading is not possible.  This was apparently fixed in subsequent builds of iOS8, so this workaround only targets 8.0.0.\", \"Boolean\", \"true\",)\n    )\n)\n}}\n\n</div>\n</div>\n\n{% endblock %}\n"
  },
  {
    "path": "docs/api/qq.jmd",
    "content": "{% extends \"_templates/base.html\" %}\n{% set page_title = \"qq API\" %}\n{% block content %}\n{% markdown %}\n\n# Utilities {: .page-header }\n\nFine Uploader contains a number of utility functions and shims to keep\nit dependency free and lightweight. These functions are found in the same\nnamespace as Fine Uploader, `qq`.\n\n## qQuery\n\nThe qQuery object is much like a poor man's jQuery. If you cannot or do not\nwant to import a 3rd-party library such as jQuery or Zepto to do cross-browser\nDOM manipulation, then qQuery will be your friend.\n\n{% endmarkdown %}\n{{\napi_method(\"qq\", \"qq (element)\",\n\"\"\"Selects an `HTMLElement` and returns a `qq` 'wrapped object.'\n\"\"\",\n[\n    {\n        \"name\": \"element\",\n        \"type\": \"HTMLElement\",\n        \"description\": \"A HTML element.\"\n    },\n],\n[\n    {\n        \"type\": \"qq instance\",\n        \"description\": \"A wrapped DOM object with a variety of cross-browser shims as methods.\"\n    },\n])\n}}\n{% markdown %}\n\n\n`qq` functions similar to the `jQuery` function in terms of the operations\nit can perform. For now, though, `qq` only accepts `HTMLElements` as input.\nTo be able to use the `qq` methods, first one must wrap some `HTMLElement`\nin the `qq` function as such:\n\nFor example, if you wanted to hide an element with the id of \"myDiv\":\n\n```javascript\nvar myDiv, qqMyDiv;\nmyDiv = document.getElementById(\"myDiv\");\nqqMyDiv = qq(myDiv);\n\n// Now we can call other qq methods:\nqqMyDiv.hide();\nvar children = qqMyDiv.children();\n\n// etc ...\n```\n\nOnce you've wrapped an element, you have access to a wealth of cross-browser\nshims that will let you manipulate the DOM and CSS as you please. Just make\nsure to wrap any elements before calling these methods on them.\n\n### DOM Selection and Traversal\n\n{% endmarkdown %}\n{{ api_method(\"qq.children\", \"children (element)\", \"Returns an array of all immediate children of this element.\",\n[\n    {\n        \"name\": \"element\",\n        \"type\": \"HTMLElement\",\n        \"description\": \"An HTMLElement or an already wrapped `qq` object.\"\n    }\n],\n[\n    {\n        \"type\": \"Array\",\n        \"description\": \"An array of HTMLElements who are children of the `element` parameter.\"\n\n    }\n]) }}\n\n{{ api_method(\"qq.contains\", \"contains (element)\", \"Returns `true` if the element contains the passed element.\",\n[\n    {\n        \"name\": \"element\",\n        \"type\": \"HTMLElement\",\n        \"description\": \"An HTMLElement or an already wrapped `qq` object.\"\n    }\n],\n[\n    {\n        \"type\": \"Boolean\",\n        \"description\": \"The result of the contains test.\"\n    }\n]\n) }}\n\n{{ api_method(\"qq.hasAttribute\", \"hasAttribute (attributeName)\", \"Returns `true` if the attribute exists on the element and the value of the attribute is not 'false' case-insensitive.\",\n[\n    {\n        \"name\": \"attributeName\",\n        \"type\": \"String\",\n        \"description\": \"An attribute to test for.\"\n    }\n],\n[\n    {\n        \"type\": \"Boolean\",\n        \"description\": \"The result of the `hasAttribute` test.\"\n    }\n]) }}\n\n{% markdown %}\n\n### DOM Manipulation\n\n{% endmarkdown %}\n\n{{ api_method(\"qq.clearText\", \"clearText ()\", \"Clears all text for this element\") }}\n{{ api_method(\"qq.insertBefore\", \"insertBefore (element)\", \"Inserts the element directly before the passed element in the DOM.\",\n[\n    {\n        \"name\": \"element\",\n        \"type\": \"HTMLElement\",\n        \"description:\": \"The element that will be inserted.\"\n    }\n]) }}\n{{ api_method(\"qq.remove\", \"remove ()\", \"Removes the element from the DOM.\") }}\n{{ api_method(\"qq.setText\", \"setText (text)\", \"Sets the inner text for this element.\",\n[\n    {\n        \"name\": \"text\",\n        \"type\": \"String\",\n        \"description\": \"The text to set.\"\n    }\n]) }}\n\n{% markdown %}\n\n### CSS Operations\n\n{% endmarkdown %}\n{{ api_method(\"qq.addClass\", \"addClass (className)\", \"Add a class to this element.\",\n[\n    {\n        \"name\": \"className\",\n        \"type\": \"String\",\n        \"description\": \"The name of the class to add to the element.\"\n    }\n]) }}\n\n{{ api_method(\"qq.css\", \"css (styles)\", \"Add CSS style(s) to this element.\",\n[\n    {\n        \"name\": \"styles\",\n        \"type\": \"Object\",\n        \"description\": \"An object with styles to apply to this element.\"\n    }\n],\n[\n    {\n        \"type\": \"Object\",\n        \"description\": \"Returns the current context to allow method chaining.\"\n    }\n]\n) }}\n\n{{ api_method(\"qq.getByClass\", \"getByClass (className)\", \"Returns an array of all descendants of this element that contain a specific class name.\",\n[\n    {\n        \"name\": \"className\",\n        \"type\": \"String\",\n        \"description\": \"The name of the class to look for in each element.\"\n    }\n],\n[\n    {\n        \"type\": \"Array\",\n        \"description\": \"An array of `HTMLElements`\"\n    }\n]) }}\n\n{{ api_method(\"qq.hasClass\", \"hasClass (className)\", \"Returns `true` if the element has the class name\",\n[\n    {\n        \"name\": \"className\",\n        \"type\": \"String\",\n        \"description\": \"The name of the class to look for in each element.\"\n    }\n],\n[\n    {\n        \"type\": \"Boolean\",\n        \"description\": \"Result of the `hasClass` test\"\n    }\n]) }}\n\n{{ api_method(\"qq.hide\", \"hide ()\", \"Hide this element.\",\n[],\n[\n    {\n        \"type\": \"Object\",\n        \"description\": \"Returns the current context to allow method chaining.\"\n    }\n]\n) }}\n\n{{ api_method(\"qq.removeClass\", \"removeClass (className)\", \"Remove the provided class from the element\",\n[\n    {\n        \"name\": \"className\",\n        \"type\": \"String\",\n        \"description\": \"The name of the class to look for in each element.\"\n    }\n],\n[\n    {\n        \"type\": \"Object\",\n        \"description\": \"Returns the current context to allow method chaining.\"\n    }\n]\n)\n}}\n\n{% markdown %}\n### Event Attaching and Detaching\n{% endmarkdown %}\n\n{{ api_method(\"qq.attach\", \"attach (event, handler)\", \"Attach an event handler to this element for a specific DOM event.\",\n[\n    {\n        \"name\": \"event\",\n        \"type\": \"String\",\n        \"description\": \"A valid `DOM Event`\"\n    },\n    {\n        \"name\": \"handler\",\n        \"type\": \"Function\",\n        \"description\": \"A function that will be invoked whenever the respective event occurs\"\n    },\n],\n[\n    {\n        \"type\": \"Function\",\n        \"description\": \"Call this function to detach the event.\"\n    }\n])}}\n\n\n{{ api_method(\"qq.detach\", \"detach (event, originalHandler)\", \"Detach an already attached event handler from this element for a specific DOM event.\",\n[\n    {\n        \"name\": \"event\",\n        \"type\": \"String\",\n        \"description\": \"A valid `DOM Event`\"\n    },\n    {\n        \"name\": \"originalHandler\",\n        \"type\": \"Function\",\n        \"description\": \"A function that will be detached from this event.\"\n    },\n],\n[\n    {\n        \"type\": \"Object\",\n        \"description\": \"Returns the current context to allow method chaining.\"\n    }\n]\n)}}\n\n\n{% markdown %}\n## Utility Functions\n{% endmarkdown %}\n\n{{ api_method(\"qq.bind\", \"bind (oldFunc, context)\", \"Shim for `Function.prototype.bind`. Creates a new function that, when called, has its `this` keyword set to the provided `context`. Pass comma-separated values after the `context parameter for all arguments to be passed into the new function (when invoked). you can still pass in additional arguments during invocation.\",\n[\n    {\n        \"name\": \"oldFunc\",\n        \"type\": \"Function\",\n        \"description\": \"The function that will be bound to.\"\n    },\n    {\n        \"name\": \"context\",\n        \"type\": \"Object\",\n        \"description\": \"The context the function will assume.\"\n    },\n],\n[\n    {\n        \"type\": \"Function\",\n        \"description\": \"A new function, same as the old one, but bound to the passed in`context`.\"\n    }\n]) }}\n\n{{ api_method(\"qq.each\", \"each (iterable, callback)\", \"Iterates through a collection, passing the key and value into the provided callback. `return false;` to stop iteration.\",\n[\n    {\n        \"name\": \"iterable\",\n        \"type\": \"Array or Object\",\n        \"description\": \"The array or object containing items/properties to loop through.\"\n    },\n    {\n        \"name\": \"callback\",\n        \"type\": \"Function\",\n        \"description\": \"A function that will be called for each item returned by looping through the iterable. This function takes an index and an item.\"\n    }\n]) }}\n\n{{ api_method(\"qq.extend\", \"extend (firstObj, secondObj[, extendNested])\", \"Shallowly copies the parameters of `secondobj` to `firstobj`. if `extendnested` is true then a deep-copy is performed.\",\n[\n    {\n        \"name\": \"firstObj\",\n        \"type\": \"Object\",\n        \"description\": \"The object to copy parameters to.\"\n    },\n    {\n        \"name\": \"secondObj\",\n        \"type\": \"Object\",\n        \"description\": \"The object to copy parameters from.\"\n    },\n    {\n        \"name\": \"extendNested\",\n        \"type\": \"Boolean\",\n        \"description\": \"If `true` then a deep-copy is performed, else a shallow copy.\"\n    },\n],\n[\n    {\n        \"type\": \"Object\",\n        \"description\": \"The new object created by the extension.\"\n    }\n]) }}\n\n{{ api_method(\"qq.format\", \"format (message)\", \"Returns a string, swapping argument values with the associated occurrence of {} in the passed string.\",\n[\n    {\n        \"name\": \"message\",\n        \"type\": \"String\",\n        \"description\": \"\"\n    }\n],\n[\n    {\n       \"type\": \"String\",\n       \"description\": \"The formatted string\"\n    }\n]) }}\n\n{{ api_method(\"qq.getextension\", \"getExtension (filename)\", \"Return the extension for the filename, if any.\", [ { \"name\": \"filename\", \"type\": \"String\", \"description\": \"The file's name to rip the extension off of.\" } ],\n[ { \"type\": \"String\", \"description\": \"The new filename\" }] )}}\n\n{{ api_method(\"qq.getUniqueId ()\", \"getUniqueId\", \"Returns a version4 uuid\", [],\n[ { \"type\": \"String\", \"description\": \"A version 4 unique identifier\" } ]) }}\n\n{{ api_method(\"qq.indexof\", \"indexOf (array, item[, startingIndex])\", \"Returns the index of `item` in the `Array` starting the search from `startingindex`.\",\n[\n    {\n        \"name\": \"array\",\n        \"type\": \"Array\",\n        \"description\": \"\"\n    },\n    {\n        \"name\": \"item\",\n        \"type\": \"Object\",\n        \"description\": \"\"\n    },\n    {\n        \"name\": \"startingIndex\",\n        \"type\": \"Integer\",\n        \"description\": \"\"\n    }\n],\n[\n    {\n        \"type\": \"Integer\",\n        \"description\": \"The index of `item` in the array.\"\n    }\n]) }}\n\n{{ api_method(\"qq.isfunction\", \"isFunction (func)\", \"`true` if the parameter is a function.\",\n[ { \"name\": \"func\", \"type\": \"Object\", \"description\": \"The Object to test.\" } ],\n[ { \"type\": \"Boolean\", \"description\": \"The result of the `isfunction` test.\" } ]) }}\n\n{{ api_method(\"qq.isobject\", \"isObject (obj)\", \"`true` if the parameter is a 'simple' object.\",\n[ { \"name\": \"obj\", \"type\": \"Object\", \"description\": \"The 'thing' to test.\"}],\n[ { \"type\": \"Boolean\", \"description\": \"Result of the `isobject` test\" }]) }}\n\n{{ api_method(\"qq.isstring\", \"isString (str)\", \"`true` if the parameter is a string.\",\n[ { \"name\": \"str\", \"type\": \"Object\", \"description\": \"The Object to test\" } ],\n[ { \"type\": \"Boolean\", \"description\": \"The result of the `isstring` test.\" } ]) }}\n\n{{ api_method(\"qq.log\", \"log (logMessage[, logLevel])\", \"Log a message to the console. no-op if console logging is not supported. shim for `console.log`.\",\n[ { \"name\": \"logmessage\", \"type\": \"String\", \"description\": \"The message to log.\" },\n  { \"name\": \"logLevel\", \"type\": \"String\", \"description\": \"The logging level, such as 'warn' and 'info'. If `null`, then 'info' is assumed.\" } ]) }}\n\n{{ api_method(\"qq.preventdefault\", \"preventDefault (event)\", \"Prevent the browser's default action on an event.\",\n[\n    {\n        \"name\": \"event\",\n        \"type\": \"String\",\n        \"description\": \"The name of the default event to prevent.\"\n    }\n]) }}\n\n{{ api_method(\"qq.toelement\", \"toElement (str)\", \"Creates and returns a new `<div>` element\",\n[ { \"name\": \"str\", \"type\": \"String\", \"description\": \"Valid HTML that can be parsed by a browser.\" }],\n[ { \"type\": \"HTMLElement\", \"description\": \"An newly created `HTMLElement` from the input.\" } ]) }}\n\n{{ api_method(\"qq.trimstr\", \"trimstr (str)\", \"Removes whitespace from the ends of a string. Shim for `String.prototype.trim`.\",\n[ { \"name\": \"str\", \"type\": \"String\", \"description\": \"The string to remove whitespace from.\" } ],\n[ { \"type\" : \"String\", \"description\": \"The new string sans whitespace.\" } ]) }}\n\n{% endblock %}\n"
  },
  {
    "path": "docs/browser-support.jmd",
    "content": "{% extends \"_templates/base.html\" %}\n{% block content %}\n{% markdown %}\n\n# Browser Support {: .page-header }\n\nCurrently, Fine Uploader supports the following browsers:\n\n* Chrome\n* Firefox\n* Microsoft Edge 13.10586+\n* Opera 15+\n* Android 2.3.x+\n* iOS 6+\n* Safari 5+ (OS X)\n* Chrome mobile (iOS & Android)\n* Internet Explorer 8+\n\n## Feature Support Matrix\n\nNote: Any features not listed here are supported in all browsers.\n\n<table class='table table-condensed table-hover table-striped'>\n    <thead>\n        <tr>\n            <th><strong>Browser</strong></th>\n            <th>XHR2</th>\n            <th>Multiple File Selection</th>\n            <th>File Drop</th>\n            <th>Folder Drop</th>\n            <th>Folder Selection</th>\n            <th>Chunking / Resume</th>\n            <th>Non-MPE Requests</th>\n            <th>Upload via Paste</th>\n            <th>CORS</th>\n            <th>Size Validation</th>\n            <th>Progress reporting</th>\n            <th>Image Previews</th>\n            <th>Image Scaling</th>\n            <th>Image Validation</th>\n            <th>Pause Uploads</th>\n            <th>S3 Uploads</th>\n            <th>Azure Uploads</th>\n        </tr>\n    </thead>\n    <tbody>\n        <tr>\n            <td>Chrome</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n        </tr>\n        <tr>\n            <td>Chrome iOS 6+</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}<a style=\"font-size: 24px\" href=\"#ios-multiple\">*</a></td>\n            <td>{{ label('-', \"\") }}</td>\n            <td>{{ label('-', \"\") }}</td>\n            <td>{{ label('-', \"\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('-', \"\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n        </tr>\n        <tr>\n            <td>Chrome Android 4+</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('-', \"\") }}</td>\n            <td>{{ label('-', \"\") }}</td>\n            <td>{{ label('-', \"\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('-', \"\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n        </tr>\n        <tr>\n            <td>Opera 15+</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n        </tr>\n        <tr>\n            <td>Firefox</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"important\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n        </tr>\n        <tr>\n            <td>Edge</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"important\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n        </tr>\n        <tr>\n            <td>IE 11</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n        </tr>\n        <tr>\n            <td>IE 10</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n        </tr>\n        <tr>\n            <td>IE 9</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n        </tr>\n        <tr>\n            <td>IE 8</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n        </tr>\n        <tr>\n            <td>Android\n                4+ stock</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('-', \"\") }}</td>\n            <td>{{ label('-', \"\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n        </tr>\n        <tr>\n            <td>Android\n                2.3.x stock</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('-', \"\") }}</td>\n            <td>{{ label('-', \"\") }}</td>\n            <td>{{ label('-', \"\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('y', \"success\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n        </tr>\n        <tr>\n            <td>iOS\n                6+</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}<a style=\"font-size: 24px\" href=\"#ios-multiple\">*</a></td>\n            <td>{{ label('-', \"\") }}</td>\n            <td>{{ label('-', \"\") }}</td>\n            <td>{{ label('-', \"\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n        </tr>\n        <tr>\n            <td>Safari\n                6+</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n        </tr>\n        <tr>\n            <td>Safari\n                5.1</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('x', \"important\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n            <td>{{ label('√', \"success\") }}</td>\n        </tr>\n    </tbody>\n</table>\n\n<small><a style=\"font-size: 24px\" name=\"ios-multiple\">*</a> - Some iOS browsers are unable to upload multiple files when video files are allowed to be uploaded due to a long-standing iOS bug. See case <a href=\"https://github.com/FineUploader/fine-uploader/issues/990\">#990</a> on our bug tracker for more details.</small>\n\n\n### Upload size Limitations\n\nUpload size limitations are present in all browsers, but are most likely to affect the users of older browsers\nwhich do not support the [File API](http://caniuse.com/fileapi). Fine Uploader uses [chunking]({{ URL_ROOT }}/features/chunking.html) to work around\nthe maximum upload size limit in browsers that do support the File API. When you are designing your upload form\nfor users of these older browsers, or if you are not using chunking, make sure you keep the imposed file size limits in mind:\n\n<table class='table table-condensed table-hover table-striped'>\n    <thead>\n        <tr>\n            <th>Browser</th>\n            <th>Upload Limit</th>\n        </tr>\n    </thead>\n\n    <tbody>\n        <tr>\n            <td>IE8</td>\n            <td>2GB</td>\n        </tr>\n        <tr>\n            <td>IE9</td>\n            <td>4GB</td>\n        </tr>\n    </tbody>\n\n</table>\n\n[Source](http://blogs.msdn.com/b/ieinternals/archive/2011/03/10/wininet-internet-explorer-file-download-and-upload-maximum-size-limits.aspx)\n\n# Feature Detection\n\nYou can programmatically detect support for all of Fine Uploader's features at runtime by checking the bundled `qq.supportedFeatures` object.\n\n## Feature Flags\n\nFine Uploader provides a set of flags that can be used to determine which\nfeatures are supported in the current browser. These flags are set during\ninitialization of the uploader.\n\nReading the feature flags in the feature detection module is easy.  Each flag\nhas a boolean value. Simply call `qq.supportedFeatures.{featureFlagName}`\nand examine the return value.  For example, if you'd like to check if the\ncurrent user agent supports dropping folders, call `qq.supportedFeatures.folderDrop`.\n\nFeature     | Description\n------------|------------\n`ajaxUploading` | Is uploading via XHR2 supported? Indicates File API support\n`blobUploading` | Is it possible to upload `Blob` objects in this browser?\n`canDetermineSize` | Can file size be determined client-side?\n`chunking` | Is chunking supported?\n`dialogElement` | Does this browser support the `<dialog>` element?\n`fileDrop` | Is dragging and dropping files supported?\n`folderDrop` | Is drag and dropping folders supported?\n`folderSelection` | Can folders be selected via a file dialog?\n`imagePreviews` | Is client-side image preview generation possible?\n`imageValidation` | Can we validate image properties client-side?\n`limitedScaledImageSize` | Does the browser have an upper limit on the size of a `<canvas>`?  Directly related to the maximum allowable dimensions of a scaled image.\n`itemSizeValidation` | Is client-side file size validation supported?\n`pause` | Can chunked file uploads be paused?\n`progressBar` | Are progress bars supported?\n`resume` | Is the auto-resume feature supported?\n`scaling` | Is client-side image scaling possible?\n`tiffPreviews` | Can TIFFs be rendered natively in the current browser?\n`unlimitedScaledImageSize` | `true` if there is no known upper limit for `<canvas>` (used to scale images).  Will be `false` for iOS and scaled images may be further downsampled by Fine Uploader to work around this limit.\n`uploadCors` | Are cross-domain uploads supported?\n`uploading` | Is uploading of any sort supported? (Note: This will return true for browsers that are not explicitly supported such as IE6)\n`uploadCustomHeaders` | Are custom headers allowed to be sent along with the upload request?\n`uploadNonMultipart` | Are non-multipart-encoded requests supported?\n`uploadViaPaste` | Is uploading via paste supported?\n\n\n## iOS (iPhone, iPad, iPod, etc) Browsers\n\nFine Uploader supports mobile Safari and mobile Chrome found on iOS 6+. One caveat to take into\naccount is that iOS saves all image files on the camera roll is \"image.jpg\".\nEnsure that your server is saving files with some sort of unique identifier appended to the path\nbecause otherwise your iOS users will be overwriting each others' images.\nFine Uploader automatically generates a Level 4 UUID per file and sends that\nUUID along with the request to the server in the `qquuid` parameter.\n\n## Internet Explorer\n\nIE10 finally competes with existing modern browsers. For those of you with customers suffering with IE9 and older, here are some of the limitations\nyou may want to be aware of when using Fine Uploader. The limitations that are overcome\nby upgrading to IE10 are listed as well.\n\n<table class=\"table table-bordered table-striped\">\n    <thead>\n        <tr>\n            <th>Limitation</th>\n            <th>Reason</th>\n            <th>Supported/Fixed in IE10+</th>\n        </tr>\n    </thead>\n    <tbody>\n        <tr>\n            <td>No progress indicator\n            </td>\n            <td>Lack of File API support\n            </td>\n            <td>\n                <span class=\"label label-success\">Yes</span>\n            </td>\n        </tr>\n        <tr>\n            <td>Lack of File API support\n            </td>\n            <td>Only multipart form requests may be used to send files via form submission\n            </td>\n            <td>\n                <span class=\"label label-success\">Yes</span>\n            </td>\n        </tr>\n        <tr>\n            <td>Size restriction options not enforced\n            </td>\n            <td>Lack of File API Support\n            </td>\n            <td>\n                <span class=\"label label-success\">Yes</span>\n            </td>\n        </tr>\n        <tr>\n            <td>Cannot select multiple files in the file selection dialog\n            </td>\n            <td>The <code>&lt;input&gt;</code> element does not support the <code>multiple</code> attribute</td>\n            <td>\n                <span class=\"label label-success\">Yes</span>\n            </td>\n        </tr>\n        <tr>\n            <td>No logging\n            </td>\n            <td><code>console.log</code> is not available unless developer tools are open (IE9 and older).\n            </td>\n            <td>\n                <span class=\"label label-success\">Yes</span>\n            </td>\n        </tr>\n        <tr>\n            <td>Unable to cancel uploads\n            </td>\n            <td>Not really an IE problem, but since we are forced to upload files via\n                form submission, I thought I’d include it. There may be a way to\n                make this work, but I haven’t spent time playing around with the\n                available options yet. In the meantime, you should probably set the <code>disableCancelForFormUploads</code> option\n                to true. If I can’t find a way to properly allow cancel to work when\n                using the form uploader, I’ll probably remove the cancel link when\n                the form uploader is in use.\n            </td>\n            <td>\n                <span class=\"label label-success\">Yes</span>\n            </td>\n        </tr>\n       <tr>\n           <td>Requires response of type <code>text/plain</code>\n           </td>\n           <td>IE does strange things with the response when the content-type is, for\n               example, “application/json” or “text/html”. The latter is only a\n               problem if you return HTML in your JSON response.\n           </td>\n           <td>\n               <span class=\"label label-success\">Yes</span>\n           </td>\n       </tr>\n       <tr>\n           <td>Cannot determine response code\n           </td>\n           <td>Not really an IE problem, but since we are forced to upload files via\n               form submission, I thought I’d include it. This is really a side-effect\n               of using a form submission to upload files.\n           </td>\n            <td>\n                <span class=\"label label-success\">Yes</span>\n            </td>\n       </tr>\n        <tr>\n            <td>Content-size header field value does not match the actual file size\n            </td>\n            <td>This isn’t technically an IE issue, but I’m going to call it one since\n                we are forced to use multipart request in IE. The content-size for\n                multipart data requests does not refer only to the file. Rather,\n                it refers to the the total size of all sections in the request.\n            </td>\n            <td>\n                <span class=\"label label-default\">N/A</span>\n            </td>\n        </tr>\n        <tr>\n            <td>Cannot parse JSON response if response code is not 200\n            </td>\n            <td>If the response code is not 200, and the size of the response is less\n                than 512, or, apparently, sometimes, less than 256 bytes, IE replaces\n                the response with a “friendly” error message. If you insist on returning\n                responses with a status code other than 200, you can work around\n                this by instructing IE users to uncheck the “show friendly HTTP error\n                messages” setting or by padding the response JSON with whitespace\n                as described\n            </td>\n            <td>\n                <span class=\"label label-default\">N/A</span>\n            </td>\n        </tr>\n        <tr>\n            <td>Cannot use a <code>&lt;button&gt;</code> element in the <code>button</code> option</td>\n            <td>The button receives the click event instead of the child <code>&lt;input&gt;</code> element</td>\n            <td>\n                <span class=\"label label-default\">N/A</span>\n            </td>\n        </tr>\n    </tbody>\n</table>\n\n\n\n{% endmarkdown %}\n{% endblock %}\n"
  },
  {
    "path": "docs/endpoint_handlers/amazon-s3.jmd",
    "content": "{% extends \"_templates/base.html\" %}\n{% set page_title = \"S3 Endpoint\" %}\n{% block content %}\n{% markdown %}\n\n[s3-canonical-request]: http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html\n[s3-php]: https://github.com/FineUploader/php-s3-server\n[s3-rest]: http://docs.aws.amazon.com/AmazonS3/latest/API/APIRest.html\n[s3-version4-auth]: http://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html\n[scaling]: ../features/scaling.html\n[v4-post]: http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-authentication-HTTPPOST.html\n\n# S3 Server-Side Notes & Requirements {: .page-header }\n\nUsing the direct-to-S3 uploader module means that most of the server-side work required to parse upload requests is\nhandled by Amazon for you.  However, there is some minimal communication required between Fine Uploader and your local\nserver.  This document will outline both required and optional server-side tasks.  Note that, at the writing of this\ndocument, Fine Uploader developers have provided fully functional [server-side examples](https://github.com/FineUploader/fine-uploader-server)\nin PHP, node.js, Python, and Java.  Other examples will be created in the future.\n\n{{ alert(\n\"\"\"[A comprehensive, step-by-step guide on implementing your origin server to\nhandle S3 uploads can be found on the Fine Uploader blog](http://blog.fineuploader.com/2013/08/16/fine-uploader-s3-upload-directly-to-amazon-s3-from-your-browser)\"\"\")}}\n\nYour signature endpoint must, at the very least:\n\n- Sign non-chunked requests\n- Sign chunked requests\n\n\n## Creating your S3 bucket's CORS configuration\nIn order to use the upload-to-S3 feature, you MUST properly set the CORS configuration in your S3 bucket(s).  Fine Uploader\nmust make cross-origin requests to S3 whenever it communicates with AWS.  You can [read more about CORS configuration on\nthe AWS developer site](http://docs.aws.amazon.com/AmazonS3/latest/dev/cors.html).  A simple and typical CORS configuration\nwould look like this:\n\n```xml\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<CORSConfiguration xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">\n    <CORSRule>\n        <AllowedOrigin>*</AllowedOrigin>\n        <AllowedMethod>POST</AllowedMethod>\n        <AllowedMethod>PUT</AllowedMethod>\n        <AllowedMethod>DELETE</AllowedMethod>\n        <MaxAgeSeconds>3000</MaxAgeSeconds>\n        <ExposeHeader>ETag</ExposeHeader>\n        <AllowedHeader>*</AllowedHeader>\n    </CORSRule>\n</CORSConfiguration>\n```\n\nSome notes on the above configuration:\n\n* The POST method is required to allow HTML Form uploads by Fine Uploader, as well as other various REST API calls.  You\nwill always need to include this.\n* The PUT method is required only if you enable the chunking feature in Fine Uploader.\n* The DELETE method is if you plan on deleting files from your bucket server-side.  This would be critical\nif you enable Fine Uploader's delete file feature.  It is also required if you enable the chunking feature, as Fine Uploader\nsends a DELETE request to S3 when a user cancels an in-progress upload in order to ensure S3 removes all chunks associated\nwith that upload present in your bucket.\n* You can (and probably should) make the AllowedOrigin condition(s) a bit more restrictive.  If you know specifically what\ndomains will host your Fine Uploader instance, include these in the AllowedOrigin tag value.\n* You can (and probably should) make the AllowedHeader condition(s) a bit more restrictive.  If you do, you will always\nneed to allow the following headers: \"origin\" and \"content-type\".  If you have the chunking feature enabled, you will also\nneed to include: \"x-amz-acl\", \"x-amz-meta-qqfilename\", \"x-amz-date\", and \"authorization\".  If you have chunking enabled,\nyou will also need to authorize headers for any custom parameters (user metadata) you want to attach to the object in S3.\nAll parameters are sent as headers in the \"Initiate Multipart Upload\" request sent by Fine Uploader with a prefix of\n\"x-amz-meta-\".\n* If you are using the [scaling feature][scaling], you will need to account for these headers in your CORS configuration as well:\n\"x-amz-meta-qquuid\", \"x-amz-meta-qqparentuuid\", and \"x-amz-meta-qqparentsize\".\n* If you are using version 4 signature support, you will _also_ need to account for a header of \"x-amz-content-sha256\" for all chunked uploads.\n\n\n## Signing non-chunked upload requests (required: all browsers)\nThe only required server-side task for all browsers is an endpoint that provides a version 2 _or_ version 4 signature for the policy document.  This endpoint\ncorresponds to the `signature.endpoint` property. Be sure you set the [`signature.version` configuration option](../api/options-s3.html#signature.version) to `4` if you are sending files to a bucket in a region that only supports version 4 signatures.\n\nFine Uploader will construct a policy document (required for securely uploading the file to your S3 bucket)\nwhich then must be signed using your AWS secret key.  When Fine Uploader requires this signature, it will send a\nPOST request to the endpoint specified in your `signature.endpoint` option, passing the JSON policy document in the\nrequest payload with a Content-Type of \"application/json\".  You can read more about [policy documents on Amazon's developer site](http://docs.aws.amazon.com/AmazonS3/latest/dev/HTTPPOSTForms.html#HTTPPOSTConstructPolicy).\n\nFor version 4 signatures, a query parameter of \"v4\" will be appended to the signature endpoint's URI. This will make it easy for your signature server to determine the correct signing algorithm to use.\n\n#### Verifying the policy\nWhen your server receives this policy document, it should first verify that the policy contains expected properties. Some common\nproperties to validate on a policy include the \"bucket\" and the \"content-length-range\". The [PHP server-side example][s3-php] maintained\nby Fine Uploader developers validates these two properties.\n\nIf the policy document appears incorrect (due to client-side tampering), your server should immediately return a response\nwith a status code of 500, a Content-Type of \"application/json\" and the following payload:\n\n```javascript\n{\n    \"invalid\": true\n}\n```\n\nThe above response will let Fine Uploader know that the policy document may have been tampered with client-side, and it\nwill not send the file to S3 until the issue is addressed.\n\nIf there is any other error generating the signature, you may return a response with a status code of 500, a\nContent-Type of \"application/json\", and a payload including an error message that will be displayed to the user:\n\n```javascript\n{\n    \"error\": \"There was an error generating the AWS signature\"\n}\n```\n\nIf your server returns an error response with no \"error\" attribute, a default message will be used.\n\n\n#### Signing the policy - version 2 signatures\nIf your server determines that the policy document is accurate, it should then base-64 encode the policy document,\ngenerate a base-64 encoded HMAC SHA1 of the policy document using your AWS secret key, and then return the base-64\nencoded policy document and the base-64 encoded signature in the response payload with a status of 200-204 and a Content-Type\nof \"application/json\".  Your response payload should follow this format:\n\n```javascript\n{\n    \"policy\": \"INSERT BASE-64 ENCODED POLICY HERE\",\n    \"signature\": \"INSERT BASE-64 ENCODED SIGNED POLICY HERE\"\n}\n```\n\nIt's quite simple to sign the policy document server-side.  Amazon's developer site also provides some\n[code in several languages illustrating how to do this](http://aws.amazon.com/articles/1434/#signyours3postform).\n\n\n#### Signing the policy - version 4 signatures\nAmazon has made the version 4 signature algorithm quite complicated to implement for unknown reasons. If you'd like to see\nan example of signing a policy document supplied by Fine Uploader S3 using the version 4 signing process, have a look at the\n[Fine Uploader PHP S3 signature server example][s3-php]. The general process is as follows:\n\n1. Handle the policy signature request from Fine Uploader S3. Look at the query string to determine if this is a version 4 signature, or not.\n2. Locate the policy document in the request body. Note that the entire contents of the request body represent this JSON policy.\n3. Locate the `conditions` array in the policy.\n4. Locate the `x-amz-credential` item in the conditions array.\n5. Generate the signature as outlined in the [AWS version 4 signature calculate guide][v4-post]. The relevant section is at the bottom of the linked page. All of the values you need for the \"Signing Key\" section are included in the value of the `x-amz-credential` element in the policy document.\n6. Return a JSON response with two properties: `\"policy\"` which includes the base-64-encoded policy document, and `\"signature\"` which includes the generated signature.\n\nNote that you will have to base64-encode the policy document to generate the \"SigningKey\", which is needed for the last step\nin the signature generation process.\n\n\n## Signing chunked uploads (required: modern browsers)\nFine Uploader S3 uses [Amazon S3's REST API][s3-rest]\nto initiate, upload, complete, and\nabort multipart uploads. The REST API handles authentication by signing\ncanonically formatted headers. This signing is something you need to implement\nserver-side.\n\n\n### Providing a chunked upload signature - version 2 signatures\nAll your server needs to do to authenticate and supported chunked\nuploads direct to Amazon S3 is sign a string representing the headers of the\nrequest that Fine Uploader sends to S3. This string is found in the payload of\nthe signature request:\n\n    { \"headers\": /* string to sign */ }\n\nThe presence of this property indicates to your sever that this is, in fact,\na request to sign a REST/multipart request and not a policy document.\n\nThis signature for the headers string differs slightly from the policy document\nsignature. You should **NOT** base64 encode the headers string before signing it.\nAll you must do, server-side, is generate an HMAC SHA1 signature of the string\nusing your AWS secret key and _then_ base64 encode the result. Your server\nshould respond with the following in the body of an 'application/json' response:\n\n    { \"signature\": /* signed headers string */ }\n\n\n### Providing a chunked upload signature - version 4 signatures\nIf you'd like to see an example of generating a signature for a chunked upload request created by Fine Uploader S3 using the\nversion 4 signing process, have a look at the [Fine Uploader PHP S3 signature server example][s3-php]. The general process is as\nfollows:\n\n1. Handle a chunked upload signature request from Fine Uploader. Look at the query string to determine if this is a version 4 request, or not. The presence of a `\"headers\"` property in the JSON-encoded message body indicates that this is indeed related to a chunked upload request.  \n2. Follow the signing process outlined in the diagram on [Amazon's version 4 header-based auth documentation page][s3-version4-auth].  \n3. The `\"headers\"` string provided by Fine Uploader corresponds to the \"StringToSign\" portion of the diagram on the page referenced in the previous step, with one notable difference. Instead of a hashed \"canonical request\" at the end of the \"StringToSign\", Fine Uploader S3 will include the raw [newline-delimited canonical request][s3-canonical-request]. This allows you to properly inspect the request before signing it.  \n    1. Examine the raw canonical request string at the end of the headers string to verify that the request is valid before signing it.  \n    2. Replace the raw canonical request at the end of the \"StringToSign\" sent by Fine Uploader S3 with a SHA256 hashed version.  \n    3. Now generate the signature using this constructed \"StringToSign\" by following the logic in the third step (titled \"Signature\") of the above-mentioned AWS document.  \n4. Return a JSON response with the generated signature as the value of a `\"signature\"` property.  \n\n\n## Supporting IE9 (and older) and Android 2.3.x (and older)\nFor browsers that do not support the File API, Fine Uploader must submit selected files inside of form, targeting a specific\ndynamically-generated iframe.  When the response comes in, Fine Uploader will not be able to determine if the upload was\nsuccessful since the response originated from a domain other than the one hosting the uploader.  To get around this,\nwe can ask Amazon to redirect the response, on success, to an endpoint of our choice.  This is where the `iframeSupport`\noption comes into play.\n\nYour `iframeSupport.localBlankPagePath` value must point to a page on your server.  It can (and probably should) be an empty HTML page,\nbut it MUST reside on the same origin/domain as the one hosting your upload page.\n\n\n## Supporting the upload success POST to your server (optional)\nIf you would like Fine Uploader to notify your server when any file has been successfully uploaded to S3, you should\nset the `uploadSuccess.endpoint` property.  If this is set, Fine Uploader will send a POST request\nto your server with a Content-Type of \"application/x-www-form-urlencoded\".  The payload of this request, by default,\nwill contain the following information:\n\n* S3 bucket\n* Key name of the associated file in S3\n* UUID of the file\n* Name of the file\n* The ETag of the object in S3 (for non-chunked uploads only)\n* Any parameters/form fields you have associated with the file\n\nAn example of the payload for this request sent by Fine Uploader would look like this:\n\n`key=f9a922bd-3007-4393-a76e-925fc009639c.txt&uuid=f9a922bd-3007-4393-a76e-925fc009639c&name=rubycsv.txt&bucket=fineuploadertest&etag=123`\n\nParsing url-encoded payloads should be trivial and handled by most web application frameworks.\n\nIf you need to perform some specific task to verify the file server-side at this point, you can do so when\nhandling this request and let Fine Uploader know if there is a problem with this file by returning a response with an\nappropriate (anything other than 200-204) status code.  Furthermore, you can include a message to be displayed (FineUploader/default-UI mode)\nand passed to your `onError` callback handler via an `error` property in the payload of your response.  In this case,\nthe response payload must be valid JSON.\n\nYou can also pass any data to your Fine Uploader [`complete` event handler](../api/events.html#complete), client-side,\nby including it in a valid JSON response to the `uploadSuccess.endpoint` POST request.  In fact, the S3 demo server-side\ncode on FineUploader.com is passing a signed URL to the `complete` handler which allows you to view the file you've\nuploaded.\n\n\n## Delete file feature support (optional)\nSupport for the delete file feature when using the S3 uploader is mostly the same as when using the traditional upload\nmode.  The S3 uploader does add \"key\" and \"bucket\" parameters with the request.  Otherwise, the request and the server-side\ncode required to handle these requests is the same as when using the traditional uploader.  Fine Uploader expects your\nserver-side code to delete the associated file in S3 via Amazon's S3 API, and then return a response to Fine Uploader's\ndelete request when this has task has been handled.  See the [server-side documentation for the traditional uploader](../endpoint_handlers/traditional.html)\nfor additional information on handling delete file requests.\n\n\n## CORS support (optional)\nSupport for CORS exists for the requests sent to the `signature.endpoint` `uploadSuccess.endpoint` paths.  You will need\nto set the `expected` property of the `cors` option when setting up your Fine Uploader instance.  You must also include\nappropriate headers in your server-response, and possibly handle OPTIONS (pre-flight) requests sent by the browser.  Please\nread the [blog post on CORS support](http://blog.fineuploader.com/2013/01/31/cors-support-in-3-3/) for details.  Note that\nyou can ignore the \"Handling iframe CORS upload requests server-side\" section.\n\n## Thumbnail generation support (optional)\nIf you would like to override the client-side generated preview (where supported) or provide a thumbnail for a\nnon-previewable file that you have generated server-side, you can do so by providing an absolute or relative path (URL)\nto this thumbnail in your response to the `uploadSuccess` request via a `thumbnailUrl` property in your JSON response.\nThe URL may be cross-origin as well.  See the [previews/thumbnails feature page](../features/thumbnails.html)\nfor more information on this feature.\n\n{% endmarkdown %}\n{% endblock %}\n\n"
  },
  {
    "path": "docs/endpoint_handlers/azure.jmd",
    "content": "{% extends \"_templates/base.html\" %}\n{% set page_title = \"Azure Endpoint\" %}\n{% block content %}\n{% markdown %}\n\n[scaling]: ../features/scaling.html\n\n# Azure Server-Side Notes & Requirements {: .page-header }\n\nUsing the direct-to-Azure uploader module means that most of the server-side work required to parse upload requests is\nhandled by Microsoft for you.  However, there is some minimal communication required between Fine Uploader and your local\nserver.  This document will outline both required and optional server-side tasks.\n\n{{ alert(\"Fine Uploader Azure does not support IE9 and older.  This is due to the fact that Azure's API does\nnot allow files to be uploaded via multipart encoded POST requests, which is critical for IE9 and older support.\nIf you need to support IE9 and older, you will need to load/use Fine Uploader with its traditional endpoint\nhandler if the value of `qq.supportedFeatures.ajaxUploading` is `false`.\") }}\n\n#### Creating your Azure Blob Storage container's CORS configuration\nYou MUST properly set the CORS configuration in your container(s).  Fine Uploader\nmust make cross-origin requests to Azure.  You can [read more about CORS configuration on\nthe Azure developer site][azurecors].\n\nUnfortunately, CORS can only be configured via the Azure SDK/API.  The management console does not allow you\nto configure your container in this regard at this point.  The [Fine Uploader Azure C# example][csharp] covers CORS\nconfiguration.\n\nA simple and typical CORS configuration would look like this:\n\n```xml\n<Cors>\n    <CorsRule>\n        <AllowedOrigins>http://yourdomain.com</AllowedOrigins>\n        <AllowedMethods>PUT,DELETE</AllowedMethods>\n        <AllowedHeaders>x-ms-meta-qqfilename,x-ms-blob-type,x-ms-blob-content-type,Content-Type</AllowedHeaders>\n        <MaxAgeInSeconds>200</MaxAgeInSeconds>\n    </CorsRule>\n<Cors>\n```\n\nSome notes on the above configuration:\n\n* The PUT method is required to upload blobs, blocks, and combine blocks into a blob.  The latter two apply if chunking is enabled/used.\n* The DELETE method is required.  It is used by the delete file feature (optional) but is also used by Fine Uploader Azure\nto clean up uncommitted blocks if an in-progress chunked upload is cancelled.\n* You will also need to include entries in the AllowedHeaders tag for any custom parameters (user metadata) you want\nto attach to the blob.  All parameters are sent as headers with a prefix of \"x-ms-meta-\".\n* If you are using the [scaling feature][scaling], you will need to account for these headers in your CORS configuration as well:\n\"x-ms-meta-qquuid\", \"x-ms-meta-qqparentuuid\", and \"x-ms-meta-qqparentsize\".\n\n### Required server-side tasks\nThe only required server-side task for all browsers is an endpoint the returns a Shared Access Signature URI\nfor each request Fine Uploader Azure makes against the Azure REST API.  This endpoint\ncorresponds to the `signature.endpoint` property.\n\nFine Uploader will send a GET request to your signature endpoint.  The following parameters will be included in the\nquery string of this request URI:\n\n* `_method`: The verb that will be used by Fine Uploader when it sends the associated request to Azure.\nPossible values are \"DELETE\" and \"PUT\" at this time.\n* `bloburi`: The fully-qualified URI for the blob associated with the request that Fine Uploader will send to Azure.\n* `qqtimestamp`: You can ignore this parameter.  It is simply used to ensure that the browser requests a fresh\nSAS URI from your server every time.\n\n#### Verification before returning a SAS URI\nBefore you return a SAS URI, you might want to verify the `bloburi` and `_method` to ensure that the associated\nuser is allowed to perform the requested action on the associated blob.  If there is an issue, and your server\ndoes not want the requested operation to occur, your server should respond with a [403 status code][403].  If your\nserver returns a 403, Fine Uploader Azure will not send the underlying request, and will not attempt an auto-retry\neither.\n\n#### Returning a SAS URI\nAt this point, the simplest way to generate a SAS URI is to use the Microsoft Windows Azure Storage SDK server-side.\nSee [our C# server-side example][csharp] for details.\n\nOnce the SAS URI has been generated, simply return it in your response to Fine Uploader's signature request.\n\n### Optional server-side tasks\nIf you would like Fine Uploader to notify your server when any file has been successfully uploaded to Azure, you should\nset the `uploadSuccess.endpoint` property.  If this is set, Fine Uploader will send a POST request\nto your server with a Content-Type of \"application/x-www-form-urlencoded\".  The payload of this request, by default,\nwill contain the following information:\n\n* Blob name\n* Container URL\n* UUID of the file\n* Name of the file\n* Any parameters/form fields you have associated with the file\n\nAn example of the payload for this request sent by Fine Uploader would look like this:\n\n`blob=f9a922bd-3007-4393-a76e-925fc009639c.txt&uuid=f9a922bd-3007-4393-a76e-925fc009639c&name=rubycsv.txt&container=http%3A%2F%2Ffineuploaderdev.blob.core.windows.net%2Fdev`\n\nIf you need to perform some specific task to verify the file server-side at this point, you can do so when\nhandling this request and let Fine Uploader know if there is a problem with this file by returning a response with an\nappropriate (anything other than 200-204) status code.  Furthermore, you can include a message to be displayed (FineUploader/default-UI mode)\nand passed to your `onError` callback handler via an `error` property in the payload of your response.  In this case,\nthe response payload must be valid JSON.  Furthermore, you can tell Fine Uploader to prevent any retries by including\na `preventRetry` property in the payload of your JSON response with a value of \"true\".\n\nYou can also pass any data to your Fine Uploader [`complete` event handler][completeevent], client-side,\nby including it in a valid JSON response to the `uploadSuccess.endpoint` POST request.\n\n## Delete File support\nSupport for the delete file feature when using the Azure uploader is a bit different than the traditional endpoint uploader.\nInstead of proxying the delete request through your local server, Fine Uploader Azure will send delete requests directly\nto Azure.  As usual, Fine Uploader will ask your server for a SAS URI before sending the request.\n\n## CORS support\nSupport for CORS exists for the requests sent to the `signature.endpoint` `uploadSuccess.endpoint` paths.  You will need\nto set the `expected` property of the `cors` option when setting up your Fine Uploader instance.  You must also include\nappropriate headers in your server-response, and possibly handle OPTIONS (pre-flight) requests sent by the browser.  Please\nread the [blog post on CORS support][corsblog] for details.  Note that\nyou can ignore the \"Handling iframe CORS upload requests server-side\" section.\n\n## Thumbnails\nIf you would like to override the client-side generated preview (where supported) or provide a thumbnail for a\nnon-previewable file that you have generated server-side, you can do so by providing an absolute or relative path (URL)\nto this thumbnail in your response to the `uploadSuccess` request via a `thumbnailUrl` property in your JSON response.\nThe URL may be cross-origin as well.  See the [previews/thumbnails feature page][thumbnails]\nfor more information on this feature.\n\n[csharp]: https://github.com/FineUploader/fine-uploader-server/tree/master/C%23/azure\n[azurecors]: http://msdn.microsoft.com/en-us/library/windowsazure/dn535601.aspx\n[403]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.4\n[completeevent]: ../api/events.html#complete\n[corsblog]: http://blog.fineuploader.com/2013/01/31/cors-support-in-3-3/\n[thumbnails]: ../features/thumbnails.html\n\n{% endmarkdown %}\n{% endblock %}\n"
  },
  {
    "path": "docs/endpoint_handlers/traditional.jmd",
    "content": "{% extends \"_templates/base.html\" %}\n{% set page_title = \"Traditional Endpoint\" %}\n{% block content %}\n{% markdown %}\n\n[chunking-success]: ../features/concurrent-chunking.html#server-side-implications\n[examples]: https://github.com/FineUploader/server-examples\n\n\n# Traditional Uploader Server-Side Notes & Requirements {: .page-header }\n\nIn the [FineUploader/server-examples][examples] repository you will find some code examples for different server platforms. If\nyou can't find the one you need, please read the following on the server-side guidelines for handling multipart form requests and XHR upload requests in your\nlanguage of choice.\n\n\n## Handling the upload requests\n\nBy default Fine Uploader will send the file in the body of a multipart encoded\nPOST request. Each request contains a UUID parameter.  By default, the name of this\nparameter is `qquuid`, but [this is configurable in the `request` option section](../api/options.html#request.uuidName).\nThis parameter value should be used to uniquely identify the file, and the\nassociation between this UUID and the file should be maintained\nsever-side if you want to handle [DELETE requests](../features/delete.html), the [resume feature](../features/resume.html),\nor [chunking](../features/chunking.html).\n\n{{ alert(\n\"\"\"If chunking is enabled, the filename in the content-disposition header of the multipart file boundary will have a\nvalue of 'blob' so you will need to parse the value of the 'qqfilename' parameter in this case to determine the name\nof the associated file.\"\"\", \"info\", \"Note:\") }}\n\n\n{{ alert(\n\"\"\"iOS devices use 'image.jpg' for **all** image file names. Ensure to save your\nfiles with the UUID somewhere in that file's path to ensure users are not\noverwriting each others' files.\"\"\", \"warning\", \"Note:\") }}\n\n\n## Response\nYour server should return a [valid JSON](http://jsonlint.com/) response to _all_ requests.  The content-type must be \"text/plain\"\nfor older versions of Internet Explorer to work around an undesired behavior in the browser's handling of JSON responses.\n\n### Example values\n* `{\"success\":true}` when upload was successful.\n* `{\"success\": false}` if not successful, no specific reason.\n* `{\"error\": \"error message to display\"}` if not successful, with a specific reason.\n* `{\"success\": false, \"error\": \"error message to display\", \"preventRetry\": true}` to prevent Fine Uploader from making\nany further attempts to retry uploading the file\n* `{\"success\": false, \"error\": \"error message to display\", \"reset\": true}` to fail this attempt and restart with the first chunk on the next attempt.  Only applies if chunking is enabled.\nNote that, if resume is also enabled, and this is the first chunk of a resume attempt, this will result in the upload starting with the first chunk immediately.\n* `{\"success\":true, \"newUuid\": \"abc-def-ghi\"}` When you would like to override the UUID for this file provided by Fine Uploader.\n\n{{ alert(\n\"\"\"You can have additional custom properties in the response, but ensure that you include the 'success': true property\nfor a successful response, or it will trigger the onError callback.\"\"\", \"info\", \"Note:\") }}\n\n\n## File Chunking/Partitioning\n\nIf you have [file chunking](../features/chunking.html) turned on, each file will be split up into chunks that are sent,\nin order, in separate requests. Note that [this feature is only supported in modern browsers](../browser-support.html#feature-support-matrix).\n\nOn the server-side, you must acknowledge each chunked request just as you would a non-chunked request.  If your response does\nnot indicate success, Fine Uploader will declare the entire file a failure.  If you have auto and/or manual retry enabled,\nFine Uploader will retry beginning with the last failed chunk.\n\nYou must temporarily store each partition server-side and then concatenate all parts (to arrive at the complete file) after\nthe last part is sent.  See [the `paramNames` chunking sub-option](../api/options.html#chunking) to see what specific\nparameters are sent by Fine Uploader along with each chunked request.  These parameters will be necessary to ensure you\nproperly parse each chunked request.  You may order Fine Uploader to restart with the first chunk on a failed attempt\nby returning a `reset` property in your server response (with a value of `true`).  This is only applicable if `autoRetry`\nor `manualRetry` is enabled.\n\nYou should make use of the UUID parameter, passed with each request, that uniquely identifies each file.  This may make it easier for you\nto avoid collisions during accumulation of chunks between files with the same name. Furthermore, you should _also_ set the\n[chunking.success.endpoint option](../api/options.html#chunking.success.endpoint). Fine Uploader will send a POST to your\nendpoint when all chunks for a file have been _successfully_ uploaded. This is the perfect opportunity to combine all chunks.\n\n\n## Resuming Uploads From A Previous Session\n\nThere isn't much you need to do, server-side, to support file resume, other than what has been discussed in the file chunking\nsection above.  You can determine if a resume has been ordered by looking for a [\"qqresume\" param](../api/options.html#resume.paramNames.resuming)\nwith a value of true.  This parameter will be sent with the first request of the resume attempt.\n\nIt is important that you keep chunks around on the server until either the entire file has been uploaded\nand all chunks have been merged, or until the number of days specified in [the `recordsExpireIn` property of the resume option](../api/options.html#resume.recordsExpireIn)\nhave passed.  If, for some reason, you receive a request that indicates a resume has been ordered, and one or more of the previously uploaded\nchunks is missing or invalid, you can return a valid JSON response containing a \"reset\" property with a value of \"true\".  This will\nlet Fine Uploader know that it should start the file upload from the first chunk instead of the last failed chunk.\n\n\n## Deleting Files\n\nIf you have enabled [this feature](../features/delete.html), you will need to handle the corresponding `DELETE` or `POST` requests server-side.\nThe method is configurable via the [`method` property of the `deleteFile` option](../api/options.html#deleteFile.method).\n\nFor DELETE requests, the UUID of the file to delete will be specified as the last element of the URI path.  Any custom parameters\nspecified will be added to the query string.  For POST requests, the UUID is sent as a \"qquuid\" parameter, and a \"_method\"\nparameter is sent with a value of \"DELETE\".  All POST request parameters are sent in the request payload.\n\nSuccess of the request will depend solely on the response code.  Acceptable response codes that indicate success are 200,\n202, and 204 for DELETE requests and 200-204 for POST requests.\n\nIf you would like to enable the delete file feature for cross-origin environments in IE9 or older, you will need to set\n[the `allowXdr` property of the `cors` client-side option](../api/options.html#cors.allowXdr) and adjust your server-side\ncode appropriately.  Keep in mind that the Content-Type will be absent from the request header, and credentials (cookies)\nand [non-simple headers](http://www.w3.org/TR/cors/#simple-header) cannot be sent.\n\n\n## CORS Support\n\nAs of version 3.3, **C**ross **O**rigin **R**esource **S**haring is supported.  For more details on how this works,\nlimitations, and how to properly configure your server, please see the [blog post on CORS support](http://blog.fineuploader.com/2013/01/31/cors-support-in-3-3/),\nor the [CORS feature page](../features/CORS.html).\n\n\n## Providing your own UUID for files\n\nIf you would like to track files with your own generated UUID, you can return the new UUID for the file at any time in\nyour server's response.  If chunking is enabled, it generally would be most prudent to return this new UUID in the response\nto the first or last chunk.  Once you return the new UUID in your response, Fine Uploader will update its client-side\nrecords and begin to use that UUID from that point forward.  New UUIDs must be returned as the value of a `newUuid` property.\nSee the [values](#values) section above for an example.\n\n## Handling an Overridden Filename\n\nThis may happen, for example, if [your user edits the file name](../features/filename-edit.html). Simply look for a\n\"qqfilename\" parameter.  If this exists in the request, be sure to use this when naming your file server-side.\n\n\n## Thumbnails\n\nIf you would like to override the client-side generated preview [(where supported)](../browser-support.html#feature-support-matrix)\nor provide a thumbnail for a non-previewable file that you have generated server-side, you can do so by providing an absolute\nor relative path (URL) to this thumbnail in your response to the upload request via a `thumbnailUrl` property in your JSON response.  The\nURL may be cross-origin as well.  See the [previews/thumbnails feature page](../features/thumbnails.html) for more\ninformation on this feature.\n\n## POST when all chunks have successfully uploaded\n\nYou may specify a [`chunking.success.endpoint`](../api/options.html#chunking.success.endpoint) if you'd like your server to be called when all chunks\nhave been successfully uploaded. See the section on the [chunking success call][chunking-success] for more details.\n\n{% endmarkdown %}\n{% endblock %}\n"
  },
  {
    "path": "docs/faq.jmd",
    "content": "{% extends \"_templates/base.html\" %}\n{% block content %}\n{% markdown %}\n## Frequently Asked Questions (FAQ)\n\n**Q**: Are multiple instances of Fine Uploader on a single page supported?\n\n**A**: Absolutely! In fact, our internal development page has 4 different instances of Fine Uploader running at all times. [The demo page](http://fineuploader.com/demos) is another example where multiple instances of the library are running side-by-side.\n\n<br/>\n**Q**: Why can't I select multiple files at once in Android?\n\n**A**: Android doesn't officially support the multiple attribute on file input elements.  I believe\nsome builds of Android unofficially supported this, but I can't recall which specific versions.\nSee case [#840](https://github.com/FineUploader/fine-uploader/issues/840) for more details.\n\n<br/>\n**Q**: I'm using core mode, but I don't want to write my own code to handle dropped files and folders.  Can\nI use the DnD module used by UI mode?\n\n**A**: Certainly, and it's quite easy to do so!  Head on over to the [DnD Module Documentation](features/drag-and-drop.html) for more information.\n\n<br/>\n**Q**: In IE, when my server returns its response to an upload request, I see a \"Save As...\" dialog box on the client.  What am I doing wrong?\n\n**A**: Your server's response content-type MUST be \"text/plain\".  IE does not handle the \"application/json\" mime-type.  You have\nprobably read advice from others that claim \"text/html\" is also safe.  This is not always true.  You will run into problems with a content-type\nof \"text/html\" if your JSON response contains HTML.\n\n<br/>\n**Q:** I like UI mode, but I don't want to allow my users to utilize the drag & drop feature.  How can I do this?\n\n**A:** Simply remove the drop zone elements from the template.\n\n<br/>\n**Q:** Using the jQuery plug-in in core mode, I can't seem to get my upload button to appear.\n\n**A:** It is important to understand that the target of your plug-in should be an existing container element for your upload\ncomponent, _not_ the button element.  Your button element must be specified separately via the `button` option.\n\n<br/>\n**Q:** Why am I seeing an \"Access Denied\" error in IE's javascript console?\n\n**A:** There are two common causes.  One cause is triggering the \"select files\" dialog via javascript.  IE does not permit this\nand will throw a security error when Fine Uploader attempts to submit the underlying form.  Another cause is returning a\nresponse code that is not 200.  The error occurs when Fine Uploader attempts to parse the response in the hidden iframe.\nSee the [Internet Explorer Limitations](browser-support.html#internet-explorer) for more details.\n\n<br/>\n**Q:** Why can't I use a progress bar, drag and drop, multiple file selection, chunking, or auto-resume in some browsers?\n\n**A:** Some browsers (IE9 and older, along with Android 2.3.x and older) do not support the File API the `multiple` attribute on file input elements.\nThese are all required to give you the best possible experience.\n\n<br/>\n**Q:** Why isn't Safari for Windows supported?\n\n**A:** There is really no reason to use Safari for Windows.   Webkit is better represented on that platform in Chrome.\nApple doesn't appear to be interested in maintaining Safari for Windows anymore either.  Switch to Chrome.\n\n<br/>\n**Q:** I have created a `<button>` element for my uploader button, but this doesn't seem to work in IE.  Why?\n\n**A:** In IE, the button element receives the click event, instead of the child input element.  Use a `<div>` or a `<span>` or an `<a>` instead. Be sure to add a `role` attribute with a value of \"button\" to this element for accessibility reasons.\n\n<br/>\n**Q:** Why do I only see a \"Processing...\" message next to a file (in UI mode for the traditional uploader)\nin Chrome, Safari, & Opera after the last byte has been sent but the server has yet to respond?\n\n**A:** The implementation of the onProgress notification that tells us the status of the bytes sent to the server varies from browser to browser, unfortunately.\nWebkit browsers have elected to follow the \"spirit\" of the W3C spec, while Firefox, and (I believe) IE10, obey the spec in the most strict sense.  I have discussed\nthis in some detail [in the \"processing\" status message feature case](https://github.com/FineUploader/fine-uploader/issues/404#issuecomment-10124160).\n\n<br/>\n**Q:** When chunking and multipart encoded are both enabled, why must I determine the original file's name by parsing the qqfilename parameter?\n\n**A:** The file data is stored in one of the multipart boundaries contained in the request payload.  Normally, the\ncontent-disposition header for this boundary contains the actual file name.  However, when the file is split up into parts\nclient-side, we are sending a [Blob](http://www.w3.org/TR/FileAPI/#dfn-Blob) to represent a different part of the\n[File](http://www.w3.org/TR/FileAPI/#dfn-file) in each request.  A [FormData](http://www.w3.org/TR/2010/WD-XMLHttpRequest2-20100907/#the-fo.htmlata-interface) object is used to construct these\nrequests.  When a Blob is added to a Fo.htmlata object, the user agent sets the content-disposition header for the associated\nmultipart boundary in the request to \"blob\" (or sometimes an empty or random string).  As a result, we must\npass the original file name in a parameter.\n\n<br/>\n**Q:** Why are `<input type=\"file\">` elements I pass into the `addFiles` method removed from the DOM in IE9 and older?\n\n**A:** Internally, Fine Uploader must move these input elements to a `<form>` that targets a hidden `<iframe>`.  This form is then\nsubmitted to simulate a non-page-reloading upload.  The act of moving the input to the form removes it from its original\nposition in the DOM.  This is unavoidable as it is not possible to clone a file input element.  You should  clone\nor re-create the `<input type=\"file\">` after submitting it via addFiles, or simply attach the input element to\nFine Uploader via the [button](api/options.html#button) or [extraButtons](api/options.html#extraButtons) options.\n\n<br/>\n{% endmarkdown %}\n{% endblock %}\n"
  },
  {
    "path": "docs/features/CORS.jmd",
    "content": "{% extends \"_templates/base.html\" %}\n{% set page_title = \"CORS\" %}\n{% block sidebar %}\n{{ api_links(options=['cors']) }}\n{% endblock %}\n{% block content %}\n{% markdown %}\n\n# Cross-Origin Resource Sharing (CORS) {: .page-header }\n\n[CORS](https://developer.mozilla.org/en-US/docs/HTTP/Access_control_CORS?redirectlocale=en-US&redirectslug=HTTP_access_control)\nor Cross-origin resource sharing allows client-side JavaScript to make AJAX requests to another domain.\n\nFine Uploader has support for uploading and sending requests via CORS, but there are a few\ncaveats depending on which browser is being used.\n\n\n## Support exceptions\n\n* In IE8 and IE9, the response from the request iframe is passed to the uploader window via window.postMessage.\n* Delete file CORS requests in IE9 and earlier are not supported. This is due to the fact that DELETE requests must be preflighted.\nIE9 and earlier require `XDomainRequest` to be used when sending CORS requests, and `XDomainRequest` does not support preflighting.\nThe delete files feature will be automatically disabled in IE9 and earlier if Fine Uploader CORS support is enabled.\n\n\n## Enabling CORS support\n\nIf you set the `core.expected` property to `true`, it is assumed that all requests will be cross-domain requests.\nIf you set this value to `true` and do not respond using the cross-domain response convention outlined in the next section for non-XHR requests,\neven if the request is NOT cross-domain, it will be considered a failure.\n\n```javascript\nvar uploader = new qq.FineUploader({\n    element: document.getElementById('myUploader'),\n    request: {\n        endpoint: 'server/upload'\n    }\n    cors: {\n        //all requests are expected to be cross-domain requests\n        expected: true,\n\n        //if you want cookies to be sent along with the request\n        sendCredentials: true\n    }\n});\n```\n\n\n## Handling XHR CORS requests server-side\n\nYou will know if an incoming upload request was sent via `XMLHttpRequest` if the request has an `X-Requested-With` header\nwith a value of “XMLHttpRequest”. If CORS support is enabled in Fine Uploader, you must properly handle the associated\nrequests and format your responses accordingly. Note that, since non-standards headers are sent with all Fine Uploader requests,\nALL XHR requests are preflighted. This applies to both delete file requests (which are all XHR requests) and upload requests\non browsers that support the File API. This means that you will need to handle an OPTIONS request as well. Your responses\nmust include the appropriate Access-Control headers. If you set the `cors.sendCredentials` property to `true`, you should be aware\nthat your responses must NOT include wildcard `Access-Control-Allow-Origin` headers, and you must also include the\n`Access-Control-Allow-Credentials` header. The actual response text for CORS XHR requests will not differ at all from\nresponse text for non-CORS requests.\n\n\n## Handling iframe CORS upload requests server-side\n\nAn incoming upload request has been sent sent by Fine Uploader via a form submission inside of a hidden iframe if the request does not have an\n`X-Requested-With` header, or if the `X-Requested-With` header has a value other than “XMLHttpRequest”. Handling cross-domain\niframe/form-submit-initiated uploads (in browsers that do not support the File API) is a bit tricky for Fine Uploader.\nYour response for iframe based request (such as required when using IE8 or IE9), will look very similar to your normal response,\nexcept it must have a `Content-Type` header value of “text/html”, and your response must import a helper javascript file\nvia a `<script>` tag immediately preceding your valid JSON response. For example:\n\n```json\n\"{\\\"success\\\": true, \\\"uuid\\\": \\\"9da17ad5-ad6a-40cd-81b5-226e837db45b\\\"}<script src=\\\"http://<YOUR_SERVER_DOMAIN>/iframe.xss.response-<VERSION>.js/\"></script>\"\n```\n\nAll cross-domain iframe-initiated responses must end with the script tag above, and the valid JSON portion _must_ include\nthe UUID of the associated file. Note that you must host the iframe.xss.response.js file on an accessible server and\nreference it in the `<script>` tag accordingly. When the contents of your response is returned to the iframe tracked by\nFine Uploader, the associated javascript file imported by the script tag above will be executed, and will pass the JSON\nportion of your response to the uploader via `window.postMessage`.\n\n\n{% endmarkdown %}\n{% endblock %}\n"
  },
  {
    "path": "docs/features/async-tasks-and-promises.jmd",
    "content": "{% extends \"_templates/base.html\" %}\n{% set page_title = \"Async Tasks and Promises\" %}\n{% block content %}\n{% markdown %}\n\n[q]: https://github.com/kriskowal/q\n[rsvp]: https://github.com/tildeio/rsvp.js/\n[showconfirm]: ../api/options-ui.html#showConfirm\n[showmessage]: ../api/options-ui.html#showMessage\n[showprompt]: ../api/options-ui.html#showPrompt\n\n# Async Tasks and Promises {: .page-header }\n\nIn some cases, Fine Uploader uses \"promises\", both internally and via the API.\nPromises are a design pattern taken from the functional programming style and\nare extremely helpful when dealing with asynchronous operations.\n\nFine Uploader's support for promises is encapsulated in the `qq.Promise` module.  Starting with version 5.0, you\ncan use any A+ certified promise implementation to communicate with Fine Uploader as well.  That is, you are\nno longer bound to `qq.Promise`.  Instead, you can use [Q][q], or [RSVP][rsvp], for example, to return promises from any\npromissory callback handlers.\n\nFor more information on promises in JavaScript, have a look at\n[Promises -- an alternative way to approach asynchronous JavaScript](http://12devs.co.uk/articles/promises-an-alternative-way-to-approach-asynchronous-javascript/).\n\n## Promissory Callbacks\n\nPromises are acceptable return values in the following [event handlers](../api/events.html).\nMany of these callbacks can also prevent an associated action from being executed\nwith a false return value (non-promise) or a call to `failure()` on a returned\npromise instance (see individual event docs for more details):\n\n* `onCancel`\n* `onCredentialsExpired`\n* `onPasteReceived`\n* `onSubmit`\n* `onSubmitDelete`\n* `onUpload`\n* `onUploadChunk`\n* `onValidate`\n* `onValidateBatch`\n\nYou are not required to return a promise -- you can simply return `false` (or nothing).\nHowever, there are some instances where you may want to perform\nsome asynchronous work in your callback handler. A promise can be returned from\nany of the above callbacks if you need to execute  some non-blocking task,\nsuch as an AJAX request, or asking the user for input via a modal window that\ndoes not block the UI thread (such as a Bootstrap modal, or a Bootbox.js dialog box).\n\n## qq.Promise API\n\n{{ alert(\n\"\"\"Fine Uploader's internal `qq.Promise` object is _not_ compliant with the A+ specification. If this is a problem for you,\nfeel free to use any compliant promise library of your choice instead.\"\"\", \"info\", \"Note:\") }}\n\n{% endmarkdown %}\n{{ api_method(\"then\", \"then (successCallback, failureCallback)\", \"Register callbacks from success *and* failure. The promise instance that `then` is called on will pass any values into the provided callbacks. If success or failure have already occurred before these callbacks have been registered, then they will be called immediately after this call has been executed. Each subsequent call to `then` registers an additional set of callbacks.\",\n[\n        {\n            \"name\": \"successCallback\",\n            \"type\": \"Function\",\n            \"description\": \"The function to call when the promise is successfully fulfilled.\"\n        },\n        {\n            \"name\": \"failureCallback\",\n            \"type\": \"Function\",\n            \"description\": \"The function to call when the promise is unsuccessfully fulfilled.\"\n        }\n],\n[\n    {\n        \"type\": \"qq.Promise\",\n        \"description\": \"An instance of a promise.\"\n    }\n]) }}\n\n{{ api_method(\"done\", \"done (callback)\", \"Register callbacks for success *or* failure. Invoked when the promise is fulfilled regardless of the result. The promise instance that `done` is called on will pass any values into the provided callback. Each call to `done` registers an additional set of callbacks\",\n[\n        {\n            \"name\": \"callback\",\n            \"type\": \"Function\",\n            \"description\": \"The function to call when the promise is fulfilled, successful or not.\"\n        },\n],\n[\n    {\n        \"type\": \"qq.Promise\",\n        \"description\": \"An instance of a promise.\"\n    }\n]) }}\n{{ api_method(\"success\", \"success (param)\", \"Call this on a promise to indicate success. The parameter value will depend on the situation.\",\n[\n        {\n            \"name\": \"param\",\n            \"type\": \"Object\",\n            \"description\": \"The value to pass to the promise's success handler.\"\n        },\n],\n[\n    {\n        \"type\": \"qq.Promise\",\n        \"description\": \"An instance of a promise.\"\n    }\n]) }}\n{{ api_method(\"failure\", \"failure (param)\", \"Call this on a promise to indicate failure. The parameter value will depend on the situation.\",\n[\n        {\n            \"name\": \"param\",\n            \"type\": \"Object\",\n            \"description\": \"The value to pass to the promise's failure handler.\"\n        },\n],\n[\n    {\n        \"type\": \"qq.Promise\",\n        \"description\": \"An instance of a promise.\"\n    }\n]) }}\n{% markdown %}\n\n## Promissory Options\n\nThe [`showConfirm`][showconfirm], [`showMessage`][showmessage], and [`showPrompt`][showprompt] UI options in Fine Uploader accept promissory return values.\nThis makes it trivial to do work (such as launching a confirmation window and waiting for user input)\nwithout blocking the rest of the UI thread (and thus stopping downloads).\n\n### Example\n\nSuppose you are overriding a promissory function or callback.  For example,\nsuppose you want to override the `showPrompt` option (function).  The default\nimplementation of this function uses `window.prompt`, which isn't very stylish.\nYou may want to provide a more appropriate prompt dialog for your application.\nYour implementation, using [bootbox](http://bootboxjs.com/)\nfor your prompt dialog, might look something like this:\n\n```javascript\nshowPrompt: function(message, defaultValue) {\n    var promise = new qq.Promise();\n\n    bootbox.prompt(\"Enter a value\", \"Cancel\", \"Confirm\", function(result) {\n        if (result === null || qq.trimStr(result).length === 0) {\n            promise.failure(\"User canceled prompt dialog or entered an empty string.\");\n        }\n        else {\n            promise.success(result);\n        }\n    }, defaultValue);\n\n    return promise;\n}\n```\n\nThe bootbox prompt dialog is not blocking, meaning the `showPrompt` call will\nreturn before the user has entered any information.  So, you must return a\npromise that will be updated with the user's response after it is submitted.\nFine Uploader will wait for a callback indicating either success or\nfailure, and will then continue executing the original task\nbased on this result.  One use of the `showPrompt` function is to grab a name,\nfrom the user, of a pasted image.  In this case\nonce the result has been updated on the promise instance, Fine Uploader will\nproceed to upload the pasted image using the\nname supplied by the user.\n\n{% endmarkdown %}\n{% endblock %}\n"
  },
  {
    "path": "docs/features/azure.jmd",
    "content": "{% extends \"_templates/base.html\" %}\n{% set page_title = \"Uploading Directly to Azure Blob Storage\" %}\n{% block sidebar %}\n{% endblock %}\n{% block content %}\n{% markdown %}\n# Uploading Directly to Azure Blob Storage {: .page-header }\n\n### Summary\n\nFine Uploader Azure allows your users to upload files directly to your [Azure Blob Storage][blobstorage] container without the need\nto send the file through one of your servers first.  This will, among other things, allow your application to be a bit\nmore efficient and scalable.\n\n\n### How do uploads work?\n\nWhen a user submits a file to Fine Uploader Azure, the following occurs:\n\n1. Fine Uploader constructs a URL that will represent the file as a blob once it is in the blob storage container.\n2. Fine Uploader sends a GET request to your [signature server][azureserver].  Your server must then create a [Shared Access Signature][sas]\nURI for that specific operation and blob.  This URI must be returned in your server's response.\n3. Fine Uploader sends a [Put Blob][putblob] request to Azure and includes the file bytes in the request payload.  The URL for this\nrequest is the SAS URI returned by your server in step 2.\n4. If the file uploaded successfully, Fine Uploader will send a POST request to your server with the blob details if\nyou have specified an [`uploadSuccess.endpoint`][uploadsuccess] in your [Fine Uploader Azure options configuration][azureoptions].\nIf the upload failed, start over at step 2 automatically if the [`retry.enableAuto` option][retryoption] is set to `true`.\n\n**The above steps are slightly different if Fine Uploader is [chunking][chunkingfeat] the file.**  Instead of a [Put Blob][putblob]\nrequest, Fine Uploader will send a [Put Block][putblock] request to Azure for each piece of the file.\nOnce all pieces have been successfully uploaded, Fine Uploader will then send a [Put Block List][putblocklist] request,\nwhich asks Azure to combine all the blocks into a blob. Fine Uploader will call your [signature server][azureserver]\nbefore each and every request, asking for a [SAS][sas] URI.\n\n\n### How do delete file requests work?\n\nIf this feature is enabled, Fine Uploader will send a [Delete Blob][deleteblob] request directly to Azure when\nasked to delete a file.  As usual, your [signature server][azureserver] will be called on to provide a [SAS][sas] URI\nbefore this request is sent to Azure.  Note that even if this feature is disabled, Fine Uploader will issue a [Delete Blob][deleteblob]\nrequest to Azure if a user cancels an in-progress [chunked][chunkingfeat] upload.  This is necessary to ensure that the\nuncommitted blocks are cleaned up by Azure.\n\n\n### Which features are available?\n\n**All features present in the traditional endpoint uploader are also available in Fine Uploader Azure**, with one notable\nomission.  **Fine Uploader Azure does not support IE9 and older**.  This is due to the fact that Azure's API does\nnot allow files to be uploaded via multipart encoded POST requests, which is critical for IE9 and older support.\nIf you need to support IE9 and older, you will need to load/use Fine Uploader with its traditional endpoint\nhandler if the value of [`qq.supportedFeatures.ajaxUploading`][supportedfeatures] is `false`.\n\n\n### Is this secure?\n\nThere is nothing inherently insecure about this workflow, provided you take appropriate precautions. For example:\n\n* Ensure your [SAS][sas] URIs are short-lived.  An expiration date of a few minutes or seconds is appropriate.\n* When constructing your [SAS][sas] URI, only attach the required permission(s) to the URI.\n* Examine the blob URI before generating a [SAS][sas].  It is potentially unwise to blindly generate signatures.\n* Keep your Azure storage account key a secret.  Do not commit it to any public repository.\nEnsure it is never sent to the client in a response or cookie.\n* When creating CORS rules for your container, only authorize your domain(s), the expected request headers, and\nthe expected request methods.  Avoid using wildcards as values whenever possible.  Also, keep in mind that CORS\nis NOT a security mechanism.\n\n\n### How do I use this?\n\nPlease see the getting started guides on [the home page of this documentation site](../quickstart/01-getting-started.html).\n\n\n[blobstorage]: http://www.windowsazure.com/en-us/documentation/articles/storage-dotnet-how-to-use-blobs-20/\n[azureserver]: ../endpoint_handlers/azure.html\n[sas]: http://www.windowsazure.com/en-us/documentation/articles/storage-dotnet-shared-access-signature-part-1/\n[putblob]: http://msdn.microsoft.com/en-us/library/windowsazure/dd179451.aspx\n[putblock]: http://msdn.microsoft.com/en-us/library/windowsazure/dd135726.aspx\n[putblocklist]: http://msdn.microsoft.com/en-us/library/windowsazure/dd179467.aspx\n[deleteblob]: http://msdn.microsoft.com/en-us/library/windowsazure/dd179413.aspx\n[uploadsuccess]: ../api/options-azure.html#uploadSuccess\n[azureoptions]: ../api/options-azure.html\n[retryoption]: ../api/options.html#retry\n[chunkingfeat]: ../features/chunking.html\n[supportedfeatures]: ../browser-support.html#feature-detection\n\n{% endmarkdown %}\n{% endblock %}\n"
  },
  {
    "path": "docs/features/cancellable-uploads.jmd",
    "content": "{% extends \"_templates/base.html\" %}\n{% set page_title = \"Cancellable Uploads\" %}\n{% block sidebar %}\n{{ api_links(None, ['cancel', 'cancelAll'], ['cancel']) }}\n{% endblock %}\n\n{% block content %}\n{% markdown %}\n\n# Cancellable Uploads {: .page-header }\n\nFine Uploader allows file uploads to be cancelled via the API or the UI.\n\nUploads can be canceled programmatically by calling the [`cancel`](../api/methods.html#cancel) or [`cancelAll`](../api/methods.html#cancelAll) API methods.\nThe ['cancel'](../api/events.html#cancel) event will be fired and handled by a callback whenever an item is canceled.\n\nIn [UI mode](../modes/ui.html), The cancel button will appear if [included in the template](../features/styling.html)\nIn Fine Uploader UI mode, cancelling an upload will remove that element from the DOM as well.\n\n\n{% endmarkdown %}\n{% endblock %}\n"
  },
  {
    "path": "docs/features/chunking.jmd",
    "content": "{% extends \"_templates/base.html\" %}\n{% set page_title = \"Chunking\" %}\n{% block sidebar %}\n{{ api_links(['chunking'], None, ['uploadChunk', 'uploadChunkSuccess']) }}\n{% endblock %}\n\n{% block content %}\n{% markdown %}\n\n[chunking-success]: concurrent-chunking.html#server-side-implications\n[postmaxsize[: http://php.net/manual/en/ini.core.php#ini.post-max-size\n\n\n# Chunking & Partitioning {: .page-header }\n\n{{ alert(\"[Concurrent chunking](concurrent-chunking.html) is also supported!\", \"default\", \"Note:\") }}\n\nWith the advent of the File API, modern browsers now are capable of dividing\nfiles into \"chunks\". This feature is a crucial\ncomponent to the [resume](resume.html) feature, and makes the [retry](retry.html) feature\nmore useful.  File chunking also provides a workaround for request size limits\nput in place by browsers (e.g., Firefox and Chrome limit upload request\nsizes to about 4GB) and servers (e.g. [PHP's `post_max_size`][postmaxsize]).\n\n{{ alert(\"The default chunk size for Fine Uploader S3 is 5 MiB. If your file is smaller than that size,\nthe upload will be _not_ be chunked.\", \"default\", \"Amazon S3 Specific Note:\") }}\n\nChunking is made possible by the File API, and is supported in Chrome, Firefox,\nSafari (iOS 6+ and OS X), Internet Explorer 10+, Opera 15+, and Microsoft Edge 13.10586+. Chunking is disabled in\nAndroid's stock browser due to a bug in the browser's ability to upload Blobs.\n\nEach chunk is sent as a separate request, and each chunked request\nshould be acknowledged just as you would for a non-chunked request.\nThe response to each chunked upload request is checked to determine success. If the\nserver responds with a failure response, the uploader will attempt to retry,\nsending the file starting with the last failed chunk (if the `autoRetry` option\nis enabled).\n\nThe details of chunking are largely invisible to your servers when using Fine Uploader S3 or Fine Uploader Azure. However\nif sending files to an endpoint you control, there are some details you must be aware of.\n\n\n## Traditional endpoints/servers (not S3 or Azure)\n\nThe parameters on a chunked request will contain _at least_ the following information. Consider a sample file\nwith named \"NSA_PRISM.ppt\" with a total file size of about 15 MB and a configured chunk size of 2 MB for the following example:\n\n```json\n{\n    \"qquuid\": \"380d6893-b98b-4189-9938-93d265dfab5d\",\n    \"qqpartindex\": 1,\n    \"qqpartbyteoffset\": 2000000,\n    \"qqtotalfilesize\": 15092995,\n    \"qqtotalparts\": 8,\n    \"qqfilename\": \"NSA_PRISM.ppt\",\n    \"qqchunksize\": 2000000\n}\n```\n\n{{ alert(\"`qqpartindex` is 0-indexed!\", \"info\", \"Note:\") }}\n\nThe `qqfilename` parameter is only sent along with chunked requests that are\nmultipart encoded. This is because multipart encoded uploads report the filename\nin the Content-Disposition header as \"blob\" or the empty string when a `Blob`\nis included in the request. When a file is chunked, it is split up into `Blob` pieces.\nSo, to determine the original filename when dealing with a multipart encoded request, you must read the `qqfilename` property.\n\nSince each chunk comes in as a separate request, the server should save the chunk\nto a temporary location. The filename should be saved using the `qquuid` parameter\nto guarantee there are no collisions with other chunks. After all chunks have\nbeen received for a file (i.e., `qqpartindex == qqtotalparts - 1`) then you should\ncombine all the chunks (in order) to recreate the original file.\n\nFine Uploader will also keep an eye out for a `reset` JSON property in the\nserver's response. When this property is present Fine Uploader will\nfail the upload and then restart the upload with the first chunk again on the\nnext attempt (assuming auto or manual retry has been enabled).\n\n\n### POST when all chunks have successfully uploaded\n\nYou may specify a `chunking.success.endpoint` if you'd like your server to be called when all chunks\nhave been successfully uploaded.  Note that this only applies to traditional endpoint.  See the section on\nthe [chunking success call][chunking-success] for more details.\n\n{% endmarkdown %}\n{% endblock %}\n"
  },
  {
    "path": "docs/features/concurrent-chunking.jmd",
    "content": "{% extends \"_templates/base.html\" %}\n{% set page_title = \"Concurrent Chunking\" %}\n{% block sidebar %}\n{{ api_links(['chunking'], None, ['uploadChunk', 'uploadChunkSuccess']) }}\n{% endblock %}\n\n{% block content %}\n{% markdown %}\n\n# Concurrent Chunking {: .page-header }\n\n{{ alert(\"For details on the concept of chunking, start with the [chunking feature page](chunking.html).\", \"default\", \"Note:\") }}\n\nThe concurrent chunking feature allows multiple chunks for each file to be uploaded simultaneously.  The\ntraditional chunking feature only sends one chunk at a time per file.  The number of chunks sent at once\nwill always equal the maximum number of available connections (see the [`maxConnections` option](../api/options.html#maxConnections)).\n\nFor example, if one file is submitted, and it can be broken up into 3 chunks, and `maxConnections` is set\nto 3, all 3 chunks of the file will be uploaded simultaneously.  With this feature disabled, the first\nchunk will be uploaded, the second chunk will be submitted only after the first chunk completes, and the\nthird chunk will be uploaded only after the second chunk completes.\n\n\n### Benefits\n\nThere is a clear benefit in terms of upload speed when sending multiple chunks at once.  The concurrent\nchunks feature is primarily in place to maximize bandwidth usage for single large file uploads.\n\nOur internal tests that involved uploading multiple chunks for a specific file concurrently\nshowed significant improvements in bandwidth utilization.  For example, on our internal network,\nsending a 110 MB file to S3 with chunk sizes of 5 MB took about 22 seconds when chunks were\nuploaded one-at-a-time (with concurrent chunking disabled). When maxing out the default maxConnections\nfor that file (3 chunks at once, concurrent chunking enabled) the same file uploaded in about 12 seconds.\n\n\n### Server-side Implications (traditional endpoints only)\n\n**There are only server-side implications for traditional endpoints.  If you using Fine Uploader S3, or\nFine Uploader Azure, for example, you can skip this section entirely.**\n\nIf you specify a path for the `chunking.success.endpoint` option, Fine Uploader (traditional) will send\na POST request, giving your server an opportunity to combine all parts into a single file.  This option\nis required when concurrent chunking is enabled, and you may also take advantage of this request if\nyou are not using the concurrent chunking feature.\n\nThe POST request described above is necessary to ensure your server does not have to maintain state information\nabout an in-progress upload.  Without this POST, your server would have to determine, with each chunk\nupload, if all chunks have been successfully uploaded and persisted.  This can be tricky with multiple\nchunks uploading simultaneously for a single file.  To avoid this complexity, the `chunking.success.endpoint`\noption is required when making use of the concurrent chunking feature.\n\n#### Headers for the chunking success POST\nAny custom headers you have specified via the `request.customHeaders` option will also be included\nin this request.\n\n#### Payload (body) for the chunking success POST\nThe request body will be a URL-encoded string of parameters.\n\nAny parameters you have specified for the upload request (either via options or the API) will also be passed\nalong with this request.\n\nFine Uploader will also include the following parameters that describe the underlying file:\n\n- qquuid: the UUID of the underlying file.\n- qqfilename: the name of the underlying file.\n- qqtotalfilesize: the size, in bytes, of the underlying file.\n- qqtotalparts: the total number of parts that make up the underlying file.\n\n#### Expected response for the chunking success POST\nFor successful responses, you may return an empty body with a status of 200-204.  Any\nother status code is determined to be a failure.  You can also return a JSON response with\nany data your would like passed to your `onComplete` handler.  Furthermore, you can include\nan `error` property in your response with the error message you would like to have displayed\nnext to the failed file (if you are using Fine Uploader UI).\n\n\n### Endpoint Support\n\nAll endpoint types are supported by this feature.  Note that the `chunking.success.endpoint` option is required\nfor traditional endpoints when this feature is enabled.\n\n\n### Enabling\n\nYou simply must set the `chunking.concurrent.enabled` flag.  If using a traditional upload endpoint\n(i.e. not S3 or Azure) you must also specify a `chunking.success.endpoint` path, where Fine Uploader will\nsend a POST after all chunks have been successfully uploaded for each file.\n\nFor example:\n```javascript\nvar uploader = new qq.FineUploader({\n    element: document.getElementById(\"myUploader\"),\n    request: {\n        endpoint: \"/upload/receiver\"\n    },\n    chunking: {\n        enabled: true,\n        concurrent: {\n            enabled: true\n        },\n        success: {\n            endpoint: \"/chunksdone\"\n        }\n    }\n});\n```\n\n\n{% endmarkdown %}\n{% endblock %}\n"
  },
  {
    "path": "docs/features/delete.jmd",
    "content": "{% extends \"_templates/base.html\" %}\n{% set page_title = \"Deleting Files\" %}\n{% block sidebar %}\n{{ api_links(['deleteFile', 'deleteFile-ui'], ['deleteFile', 'setDeleteFileEndpoint', 'setDeleteFileParams'], ['delete', 'deleteComplete', 'submitDelete']) }}\n{% endblock %}\n{% block content %}\n{% markdown %}\n\n[session]: session.html\n\n# Deleting Files {: .page-header }\n\nEnabling the deletion of files means your users can delete files after they have been uploaded.\nIf you upload a file, leave the page, and come back then you cannot delete that file\nthrough Fine Uploader _unless_ you have enabled the [initial file list feature](../features/session.html).\n\n\n## Overview\n\nA file is eligible for deletion only after it has been successfully uploaded. In UI mode, if this feature is enabled,\na customizable delete link will appear next to the file after it has successfully uploaded.\n\nWhen the user clicks on this link, first, your [`onSubmitDelete` callback](../api/events.html#submitDelete) will be invoked.\nYou may return `false` in your callback handler if you want to abort the request (or [a promise](async-tasks-and-promises.html)\nthat you may resolve later). If not the user will see a (customizable) confirm dialog (if the `deleteFile.forceConfirm` property has been set).\nIf they click “ok” (or if the `deleteFile.forceConfirm` option has been disabled), a DELETE request will be sent to the server\nafter Fine Uploader invokes [your onDelete callback](../api/events.html#delete).\n\nIn UI mode, a status message, along with a spinner, will appear next to the file while Fine Uploader is waiting for the server response.\nOnce the response is received, Fine Uploader will invoke [your onDeleteComplete callback](../api/events#deleteComplete).\nIf the server indicates success in its response, the file will be removed from the UI and [its status](statistics-and-status-updates.html)\nwill be updated. If the server indicates failure, a failure status message will be displayed next to the file (in UI mode).\nIf an error has been detected in the response, [your `onError` callback](../api/events.html#error) will also be invoked.\n\nIn core mode, you can order a delete request via the new `deleteFile` API function. All of the callbacks invoked while\nin UI mode will also be invoked in core mode if you order a delete via this API function.\n\n\n## The Request (and server-side handling)\n\nFine Uploader sends a separate DELETE request for each file marked for deletion. The UUID of the file is passed as the\nlast item in the request URI. For example, if the UUID of a file, ordered for deletion, is “6a9955d3-8f1e-4c70-ae47-b01b35dd1562″,\nand the endpoint property specified in the deleteFile option object is “/server/upload” and the domain is http://mysite.com,\nFine Uploader will send a DELETE request to the following endpoint:\n\n>http://mysite.com/server/upload/6a9955d3-8f1e-4c70-ae47-b01b35dd1562.\n\n{{\nalert(\"\"\"Fine Uploader appends a `_method` parameter to the request object with a value of DELETE when the HTTP method is\nPOST instead of DELETE.  This makes it easier to determine the intended semantics of the request server-side.\"\"\")\n}}\n\nNote that you can specify custom parameter and additional headers that are sent along with this request as well.\nIf you specify custom parameters, they will be sent as part of the query string, NOT the request payload.\nThis is due to the fact that many servers will either rip out any request body associated with a DELETE request, or simply\nthrow an error/reject the request. In other words, a DELETE request is much like a GET request.\n\n\n## The (Expected) Response\n\nYou may return any response you like, as the `XMLHttpRequest` object will be passed to your `onDeleteComplete` handler\nfor further examination. However, the response code you return is important to Fine Uploader, as it uses this to determine\nif the delete has succeeded or not. The following response codes indicate success for this specific request: 200, 202, and 204.\nAny other response code indicates a failure.\n\n\n## Enabling the Feature\n\nThe following is an example of a VERY basic setup that enables you to use the new delete files feature.\n\n```javascript\nvar uploader = new qq.FineUploader({\n    element: document.getElementById('myUploader'),\n    request: {\n        endpoint: 'server/upload'\n    }\n    deleteFile: {\n        enabled: true,\n        forceConfirm: true,\n        endpoint: 'server/file'\n    }\n});\n```\n\n{% endmarkdown %}\n{% endblock %}\n"
  },
  {
    "path": "docs/features/drag-and-drop.jmd",
    "content": "{% extends \"_templates/base.html\" %}\n{% set page_title = \"Drag and Drop\" %}\n{% block sidebar %}\n{{ api_links(options=['dragAndDrop-ui',], methods=['addExtraDropzone', 'removeExtraDropzone', 'getDropTarget-ui']) }}\n{% endblock %}\n{% block content %}\n{% markdown %}\n\n# Drag and Drop {: .page-header }\n\n{{ alert(\"If you want the contents of any drop zone to be invisible until an item enters the drop zone,\nsimply ensure a `qq-hide-dropzone` attribute is present on the drop zone container.\") }}\n\nFine Uploader's UI mode has drag & drop support built in.  The following contains information about the standalone\nsupport.  Here, you can find instructions that will allow you to easily integrate Fine Uploader's\nstandalone drag & drop module into your project if you are using Fine Uploader's core mode only. For more information\nabout the integrated support, see the options and methods in the side nav bar of this page.\n\n{{ alert(\"If using the integrated drag & drop feature of Fine Uploader UI, a `qqDropTarget` property is added to all `File`\nobjects dropped into any drop zone. The value of this property points to the actual drop zone element.\", \"default\", \"Note\") }}\n\n# Drag and Drop Standalone Module\n\nIf you are integrating Fine Uploader and utilizing core mode,\nyou are likely building you own UI entirely. In case you want to support drag\nand drop of folders and files, you probably don't want to re-invent the wheel, since\nFine Uploader already has code to handle this in Fine Uploader mode.\nFine Uploader's drag & drop code has been moved into a standalone module, so\nit can be easily integrated into your Fine Uploader Core mode app\n(or even any non-Fine Uploader app). This following will explain how to utilize\nthis module.\n\n{{\nalert(\"This module is aimed at Fine Uploader Core mode integrators only. If\nyou are running this in UI mode, then take a look at the `dragAndDrop` Fine Uploader UI option.\", \"info\")\n}}\n\n## Options\n\n{% endmarkdown %}\n\n{{ api_option(\"allowMultipleItems\", \"allowMultipleItems\", \"Set to `false` if you want to prevent the user from dropping more than one item at once.\", \"boolean\", \"true\",)}}\n{{ api_option(\"dropZoneElements\", \"dropZoneElements\", \"Specify all container elements that should be treated as drop zones.\", \"Array\", \"[ ]\",)}}\n\n{{api_parent_option(\"classes\", \"classes\", \"\",\n    (\n        (\"classes.dropActive\", \"dropActive\", \"Specify a CSS class to apply to drop zone(s) when a file has entered it.\", \"String\", \"''\",),\n    )\n)}}\n\n{% markdown %}\n## Events\n\n{% endmarkdown %}\n\n{{\napi_event(\"processingDroppedFiles\", \"processingDroppedFiles\",\n\"Invoked when the module has started processing the set of dropped files.\", [], [])\n}}\n\n{{\napi_event(\"processingDroppedFilesComplete\", \"processingDroppedFilesComplete\",\n\"\"\"Invoked when the module has finished processing.\n\"\"\",\n[\n    {\n        \"name\": \"files\",\n        \"type\": \"Array\",\n        \"description\": \"An array of processed files.  Note that a `qqPath` property will be added\n        to each `File` object that was dropped as part of a directory drop.  The value of this property\n        will be the path of the file starting with the dropped top-level directory.\"\n    },\n    {\n        \"name\": \"dropTarget\",\n        \"type\": \"HTMLElement\",\n        \"description\": \"The target where this event originated from.\"\n    },\n])\n}}\n\n{{\napi_event(\"dropError\", \"dropError\",\n\"\"\"Called when processing the drop fails for any reason.\n\"\"\",\n[\n    {\n        \"name\": \"errorCode\",\n        \"type\": \"String\",\n        \"description\": \"The specific error code\"\n    },\n    {\n        \"name\": \"errorRelatedData\",\n        \"type\": \"String\",\n        \"description\": \"Data related to the error.\"\n    },\n])\n}}\n\n{{\napi_event(\"dropLog\", \"dropLog\",\n\"\"\"Invoked when a message is logged.\n\"\"\",\n[\n    {\n        \"name\": \"message\",\n        \"type\": \"String\",\n        \"description\": \"The message to log.\"\n    },\n])\n}}\n\n{{\napi_event(\"dragEnter\", \"dragEnter\",\n\"Invoked when dragged element enters drop zone.\", [], [])\n}}\n{{\napi_event(\"dragLeave\", \"dragLeave\",\n\"Invoked when dragged element leaves drop zone.\", [], [])\n}}\n\n{% markdown %}\n### Methods\n{% endmarkdown %}\n\n{{ api_method(\"dispose\", \"dispose\", \"Tears down all drop zones associated with the dnd instance.\") }}\n{{ api_method(\"setupExtraDropzone\", \"setupExtraDropzone\", \"Call this to add an additional drop zone to the DnD instance. Not available if using the jQuery plug-in wrapper.\",\n[\n    {\n        \"name\": \"element\",\n        \"type\": \"HTMLElement\",\n        \"description\": \"THe HTMLElement where the dropzone will be rendered inside of.\"\n    }\n])}}\n{{ api_method(\"removeDropzone\", \"removeDropzone\", \"Call this to remove a drop zone from the DnD instance. Not available if using the jQuery plug-in wrapper.\",\n[\n    {\n        \"name\": \"element\",\n        \"type\": \"HTMLElement\",\n        \"description\": \"THe HTMLElement where the dropzone will be remove from.\"\n    }\n\n]) }}\n\n{% markdown %}\n\n## Simple Example\n\n```javascript\nvar dragAndDropModule = new qq.DragAndDrop({\n        dropZoneElements: [document.getElementById('myDropZone')],\n        classes: {\n          dropActive: \"cssClassToAddToDropZoneOnEnter\"\n        },\n        callbacks: {\n          processingDroppedFiles: function() {\n            //TODO: display some sort of a \"processing\" or spinner graphic\n          },\n          processingDroppedFilesComplete: function(files, dropTarget) {\n            //TODO: hide spinner/processing graphic\n\n            fineUploaderBasicInstance.addFiles(files); //this submits the dropped files to Fine Uploader\n          }\n        }\n      }),\n\n    fineUploaderBasicInstance = new qq.FineUploaderBasic({\n    request: {\n        endpoint: \"server/uploadHandler\"\n    }\n  });\n```\n\n{% endmarkdown %}\n{% endblock %}\n"
  },
  {
    "path": "docs/features/extra-buttons.jmd",
    "content": "{% extends \"_templates/base.html\" %}\n{% set page_title = \"Multiple Upload Buttons\" %}\n{% block sidebar %}\n{{ api_links(options=['extraButtons', 'multiple', 'validation'], events=['validate', 'validateBatch']) }}\n{% endblock %}\n{% block content %}\n{% markdown %}\n\n# Multiple Upload Buttons {: .page-header }\n\nYou have always been able to customize the default upload button.  As of Fine Uploader 3.9,\nyou can easily create multiple upload buttons with varying validation rules, styles, and behaviors.\n\n## Adding additional upload buttons\n\nThe new [`extraButtons` option](../api/options.html#extraButtons) allows you to point Fine Uploader\nat styled containers that you would like to use as upload buttons.  This option takes an array of objects, and\neach object must, at least, define an [`element` property](../api/options-ui.html#element) with a value of your styled upload button container.\nFine Uploader will then attach a file input element to this container and track it just as it tracks the default\nupload button it creates.  You can specify an `HTMLElement` as the value for the `element` property.\n\nBy default, each extra button will inherit the root [`multiple`](../api/options.html#multiple) and\n[`validation` option](../api/options.html#validation) values from the root option\nset.  You can override these values for each button by simply contributing alternate `multiple` and `validation`\nproperties for a specific extra button object.  For example, say your default upload button allows jpegs,\nto be selected, but you have an extra button elsewhere on your page that deals specifically with PDFs.  You\nalso want to enforce a 5 MiB limit (where supported) for all types of accepted files\nselected by both buttons. Your HTML and javascript might look something like this:\n\n```html\n<div id=\"pdfButton\" role=\"button\" class=\"myCustomCssClassForStyling\">Select a PDF</div>\n<div id=\"uploader\"></div>\n```\n\n```javascript\nvar uploader = new qq.FineUploader({\n    element: document.getElementById(\"myUploader\"),\n    validation: {\n        allowedExtensions: [\"jpeg\", \"jpg\"],\n        sizeLimit: 5000000 // 5 MiB\n    }\n    extraButtons: [\n        {\n            element: document.getElementById(\"pdfButton\"),\n            validation: {\n                allowedExtensions: [\"pdf\"]\n            }\n        }\n    ]\n});\n```\n\nAs always, Fine Uploader creates a default upload button (in Fine Uploader UI) for you,\nand attaches the limitations specified via the `validation` option to that button and any drop zones.\nFine Uploader will also attach an invisible file input element to the `#pdfButton` element and track it.\nNote that, in the above code, the extra button inherits the [`sizeLimit`](api/options.html#validation.sizeLimit)\nfrom the base `validation` option, but overrides the [`allowedExtensions` property](api/options.html#validation.allowedExtensions).\n\n\n## Custom per-button validation rules\n\nSuppose you want to apply custom validation rules to a specific button.  No problem!  The [`validate`](../api/events.html#validate) and\n[`validateBatch`](../api/events.html#validateBatch) events contain a `button` as the last parameter passed to your handler.\nThis `button` parameter will be the container element for the button that was used to select/submit the associated file\nto the uploader. If a drop zone was used to submit the file, or if an input element or source outside of Fine Uploader was used\nto submit the file (such as via the [`addFiles` method](../api/methods.html#addFiles)), the `button` parameter will be undefined.\n\n\n## Associate file ID to a button (per-button endpoints, params, etc)\n\nYou can also, at any time, associate any file with the button that was used to select/submit the file.\nFor example, if you want to attach specific parameters to a file, or send it to a different endpoint based\non the button that selected it, you can do so in an appropriate event handler, [such as `onUpload`](..api/events.html#upload).\nFor example:\n\n```javascript\nvar uploader = new qq.FineUploader({\n    /* other required config options left out for brevity */\n\n    callbacks: {\n        onUpload: function(id, name) {\n            var button = this.getButton(id);\n\n            if (button.id === \"pdfButton\") {\n                this.setParams({type: \"pdf\"}, id);\n            }\n            else {\n                this.setParams({type: \"jpeg\"}, id);\n            }\n        }\n    }\n});\n```\n\n\n## Allow an extra upload button to be used to select folders\n\nFine Uploader already allows you to drop folders that you would like to upload.\nWhat if you want to allow users to select folders via the file chooser dialog?  Unfortunately, due\nto the restrictions imposed by this browser feature, you can either select files OR folders, not both.\nIf you'd like to allow users to select files and folders, you can use the default upload button for file\nselection, and contribute an extra button marked as \"folder selection\" (Chrome & Opera 15+).  Here's an example,\nalso assuming you only want this button to appear if folder selection is possible in the current browser:\n\n```html\n<div id=\"foldersButton\" role=\"button\">Select Folders</div>\n<div id=\"uploader\"></div>\n```\n\n```javascript\n// only show the folders button if folder selection is possible in the current browser\nif (!qq.supportedFeatures.folderSelection) {\n    document.getElementById(\"foldersButton\").style.display = \"none\";\n}\n\nvar uploader = new qq.FineUploader({\n    /* other required config options left out for brevity */\n\n    extraButtons: [\n        {\n            element: document.getElementById(\"foldersButton\"),\n            folders: true\n        }\n    ]\n});\n```\n\n\n## Allow an extra upload button to target the camera in iOS\n\nAs pointed out in [the blog](http://blog.fineuploader.com/2013/05/09/upload-directly-via-a-camera-on-mobile-devices/),\niOS has an odd restriction that prohibits you from accessing the camera and the photo/video gallery simultaneously\nwith a file input element.  The blog details a way to get around this, but it involves a bit of complexity.  Now,\nwith the addition of the extra buttons feature, it's very easy!  Here is a simplified version of the code listed in the\nblog, now using the extra buttons feature:\n\n```html\n<div id=\"cameraButtonContainer\">Upload from the camera</div>\n<div id=\"myFineUploader\"></div>\n```\n\n```javascript\n// only show this button in iOS\nif (!qq.ios()) {\n    document.getElementById(\"cameraButtonContainer\").style.display = \"none\";\n}\n\nvar uploader = new qq.FineUploader({\n    /* other required config options left out for brevity */\n\n    camera: {\n        ios: true,\n        button: document.getElementById(\"cameraButtonContainer\")\n    }\n    extraButtons: [\n        {\n            element: document.getElementById(\"cameraButtonContainer\")\n        }\n    ]\n});\n```\n\n{% endmarkdown %}\n{% endblock %}\n"
  },
  {
    "path": "docs/features/filename-edit.jmd",
    "content": "{% extends \"_templates/base.html\" %}\n{% set page_title = \"Editing Filenames\" %}\n{% block sidebar %}\n{{ api_links(['getName', 'setName'], None) }}\n{% endblock %}\n{% block content %}\n{% markdown %}\n\n# Filename Editing {: .page-header }\n\nFine Uploader provides methods and options to enable client-side editing\nof filenames before upload. In core mode, integrators can use the API methods `getName` and `setName` for\nthis. In UI mode, if the feature is enabled via the template, then when a user\nclicks a file that they have submitted to the uploader the text will become\nan input element that they can use to change the name.\n\n{{\nalert(\"You will need to have the `autoUpload` option disabled when using the edit filename feature.\")\n}}\n\n## Turning on the edit filename feature in UI mode\n\nIn order to enable this feature, you must disabled `autoUpload` and include the following elements in your template:\n\n```html\n<span class=\"qq-edit-filename-icon-selector qq-edit-filename-icon\" aria-label=\"Edit filename\"></span>\n<input class=\"qq-edit-filename-selector qq-edit-filename\" tabindex=\"0\" type=\"text\">\n```\n\nYou can read more about templates in the [styling](styling.html) section of the documentation. Also, a live demo of the\nedit feature, along with the needed code, can be found in [the demo section of the home page](http://fineuploader.com/demos#manually-trigger-uploads).\n\n\n## Editing a file name\n\nWhen this feature is properly enabled, you will notice an edit icon next to each submitted file in the UI, assuming the\nfilename is editable at the time. Also, notice the cursor will be set to “pointer” in this case. A filename is editable\nonly while the upload status is SUBMITTED. This means that you may edit your file after submitting it, before calling the\n[`uploadStoredFiles` API method](../api/methods.html#uploadStoredFiles).\n\nWhen an editable file name is clicked/touched, a text input will appear. Here, the filename can be changed. Note that the\nextension is not modifiable. The original extension will always be appended to the filename after the name edit is complete.\nThe extension is not editable to ensure that any validation rules surrounding file type are not violated.\n\nAfter editing a filename, you have a few options:\n\n* Press ENTER to save the change.\n* Click or touch some other area on the page to save the change.\n* Hit TAB to edit the name of the next file in the UI (and save the name entered into the current input box).\n\nNote that an empty file name is not an acceptable value. In this case, the original or previously overridden file name will be used instead.\n\n\n## Things to be aware of\n\nWhen a file name has been modified, please keep the following in mind:\n\n* Fine Uploader cannot change the filename listed in the header of the associated file’s multipart boundary. The new file name is sent as a parameter along with the request. See the server-side section below for more details.\n* The [`getUploads` API method](statistics-and-status-updates.html) can be used to obtain the original file name.\nAll objects returned by this method will include an `originalName` property. The name property will be set to the\ncurrent file name.\n* The [`getName` API method](../api/methods.html#getName) will return the current file name, not the original file name.\n* All name parameters passed to callback handlers will equal the new file name, not the original file name.\n* The resume feature will key on the most current (not necessarily the original) file name when deciding if a submitted\nfile is resumable. This determination is made by Fine Uploader just before the first upload request for the file request is sent. If you submit a previously interrupted file with an overridden name, you must ensure the file name is again changed to ensure the file is properly resumed.\n\n\n## Handling an overridden / new file name server-side\n\nThe filename will be sent with the request as the `qqfilename` parameter. Be sure to read this parameter when naming\nyour file server-side.\n\n{% endmarkdown %}\n{% endblock %}\n"
  },
  {
    "path": "docs/features/forms.jmd",
    "content": "{% extends \"_templates/base.html\" %}\n{% set page_title = \"Support for Forms\" %}\n{% block sidebar %}\n{{ api_links(options=['form'], methods=['setForm']) }}\n{% endblock %}\n{% block content %}\n{% markdown %}\n\n[form]: ../api/options.html#form\n[validationattrs]: https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/HTML5/Constraint_validation\n[constraintsapi]: https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Forms_in_HTML?redirectlocale=en-US&redirectslug=Web%2FHTML%2FForms_in_HTML#Constraint_Validation_API\n[allcomplete]: ../api/events.html#allComplete\n\n# Integrating with Existing HTML Forms {: .page-header }\n\n### Overview\nSay you have an existing HTML form.  Say you want to simply allow your users to send files along with the form data.\nFine Uploader form support will allow you to do this quite easily.  A number of conventions are in place to allow\nincredibly easy integration of Fine Uploader into your existing form.  If you follow all of these conventions, and only\nrequire the most basic features, you can initialize and attach Fine Uploader to your form with only one simple line of\nJavaScript!    But don't worry, any of these conventions can be overridden via the [form option][form].  Fine Uploader\nwill even respect any HTML5 validation attributes set on your form elements.  Furthermore, you can include a traditional\nsubmit button in your form, and Fine Uploader will intercept this and upload all submitted files with the associated\nform data for you.\n\n### Features\n#### Convention over configuration\nA set of conventions are in place that allow you to simply include a form in your document and allow Fine Uploader\nto discover it and send all form data along with each upload request without explicitly setting any form-related\nconfiguration options.  This can allow you to focus more on your form, and less on JavaScript.  By default, Fine Uploader\nwill:\n\n* Find your form automatically if it has an `id` attribute with a value of \"qq-form\".  Once it finds your form, all\nfile upload requests will include any data associated with that form, automatically!\n* Ensure any HTML5 validation attributes on your form elements are respected.  If any form values are invalid, the user\nwill be notified and the uploads will not occur.\n* Set the `request.endpoint` option to the value of your form's `action` attribute.  This means that you don't even\nneed to provide a `request.endpoint` configuration value when creating your Fine Uploader instance.\n* Intercept any attempt to submit your form and instead upload all user-selected files (along with associated form data).\n* Set Fine Uploader to \"manual\" upload mode.  This means that uploads will not occur until you (or your user) indicate\nthat they are done filling out the form and choosing files.  You can allow your user to \"submit\" the form and trigger\nall files to be uploaded by simply providing a submit button in your form.\n\nIf you don't want to rely on some of these default behaviors, you can override them.  See the [form option][form]\nfor details. Note that, as of Fine Uploader 5.3, you can _also_ attach a form to Fine Uploader at any time using the new\n[setForm API method](../api/methods.html#setForm)!\n\n#### Validation\nFine Uploader will respect any [validation attributes on your form elements][validationattrs], provided the current browser\nsupports the [HTML5 Constraints Validation API][constraintsapi].  When an attempt is made to submit the associated form,\nif there are validation issues, Fine Uploader will prevent the files from being uploaded until the issues are resolved\nby the user.  Furthermore, the specific issues will be highlighted in the UI.\n\n{{ alert(\"Validation will only occur if you attempt to submit your form via a submit button, or by triggering the  form's submit\nfunction, or by triggering a submit event on the form.  If you trigger an upload via the [uploadStoredFiles API method](../api/methods.html#uploadStoredFiles),\nthe form will not be validated.\", \"info\", \"Note:\") }}\n\n#### Submit support\nSubmit attempts that target your attached form will be intercepted by default.  When Fine Uploader intercepts a submit\nattempt, it will first attempt to validate the form.  If the form is valid, or if the current browser does not have\nnative support for form validation, the user-selected files will be uploaded along with the form data.  Fine Uploader\nwill intercept submit attempts triggered by a form submit button, invocation of the form element's `submit` function,\nor via a submit event.\n\nYou can prevent Fine Uploader from intercepting submit events by adjusting the [form.interceptSubmit option][form].\n\n\n#### When are all uploads done?\nA new callback/event has been added: [`onAllComplete`][allcomplete].  This will be triggered when all selected\nfiles have been uploaded (successfully or not).  Fine Uploader will pass an array of all successfully uploaded file\nIDs, and an array of all failed file IDs.  If you need to notify your server when all associated files are done,\nyou can safely implement this logic in a contributed `onAllComplete` event handler.\n\n#### Cross-browser\nThe form support feature, like most other Fine Uploader features, works in all supported browsers.\n\n\n### Simple example\nThe simplest use-case results in a very simple example, with only one line of JavaScript!  Here is a complete HTML\nfile that integrates Fine Uploader into an existing form:\n\n{{ alert(\n\"\"\"There are many ways to load Fine Uploader into your project. The code below creates a global `qq` variable, but there are\nother, better options. Please see [modules feature page](modules.html) for more details.\"\"\") }}\n\n\n```html\n<html>\n    <head>\n        <script src=\"fine-uploader.js\"></script>\n        <link href=\"fine-uploader-new.css\">\n        <script type=\"text/template\" id=\"qq-template\">\n            <div class=\"qq-uploader-selector qq-uploader\" qq-drop-area-text=\"Drop files here\">\n                <div class=\"qq-total-progress-bar-container-selector qq-total-progress-bar-container\">\n                    <div role=\"progressbar\" aria-valuenow=\"0\" aria-valuemin=\"0\" aria-valuemax=\"100\" class=\"qq-total-progress-bar-selector qq-progress-bar qq-total-progress-bar\"></div>\n                </div>\n                <div class=\"qq-upload-drop-area-selector qq-upload-drop-area\" qq-hide-dropzone>\n                    <span class=\"qq-upload-drop-area-text-selector\"></span>\n                </div>\n                <div class=\"qq-upload-button-selector qq-upload-button\">\n                    <div>Upload a file</div>\n                </div>\n                <span class=\"qq-drop-processing-selector qq-drop-processing\">\n                    <span>Processing dropped files...</span>\n                    <span class=\"qq-drop-processing-spinner-selector qq-drop-processing-spinner\"></span>\n                </span>\n                <ul class=\"qq-upload-list-selector qq-upload-list\" aria-live=\"polite\" aria-relevant=\"additions removals\">\n                    <li>\n                        <div class=\"qq-progress-bar-container-selector\">\n                            <div role=\"progressbar\" aria-valuenow=\"0\" aria-valuemin=\"0\" aria-valuemax=\"100\" class=\"qq-progress-bar-selector qq-progress-bar\"></div>\n                        </div>\n                        <span class=\"qq-upload-spinner-selector qq-upload-spinner\"></span>\n                        <img class=\"qq-thumbnail-selector\" qq-max-size=\"100\" qq-server-scale>\n                        <span class=\"qq-upload-file-selector qq-upload-file\"></span>\n                        <span class=\"qq-edit-filename-icon-selector qq-edit-filename-icon\" aria-label=\"Edit filename\"></span>\n                        <input class=\"qq-edit-filename-selector qq-edit-filename\" tabindex=\"0\" type=\"text\">\n                        <span class=\"qq-upload-size-selector qq-upload-size\"></span>\n                        <button type=\"button\" class=\"qq-btn qq-upload-cancel-selector qq-upload-cancel\">Cancel</button>\n                        <button type=\"button\" class=\"qq-btn qq-upload-retry-selector qq-upload-retry\">Retry</button>\n                        <button type=\"button\" class=\"qq-btn qq-upload-delete-selector qq-upload-delete\">Delete</button>\n                        <span role=\"status\" class=\"qq-upload-status-text-selector qq-upload-status-text\"></span>\n                    </li>\n                </ul>\n\n                <dialog class=\"qq-alert-dialog-selector\">\n                    <div class=\"qq-dialog-message-selector\"></div>\n                    <div class=\"qq-dialog-buttons\">\n                        <button type=\"button\" class=\"qq-cancel-button-selector\">Close</button>\n                    </div>\n                </dialog>\n\n                <dialog class=\"qq-confirm-dialog-selector\">\n                    <div class=\"qq-dialog-message-selector\"></div>\n                    <div class=\"qq-dialog-buttons\">\n                        <button type=\"button\" class=\"qq-cancel-button-selector\">No</button>\n                        <button type=\"button\" class=\"qq-ok-button-selector\">Yes</button>\n                    </div>\n                </dialog>\n\n                <dialog class=\"qq-prompt-dialog-selector\">\n                    <div class=\"qq-dialog-message-selector\"></div>\n                    <input type=\"text\">\n                    <div class=\"qq-dialog-buttons\">\n                        <button type=\"button\" class=\"qq-cancel-button-selector\">Cancel</button>\n                        <button type=\"button\" class=\"qq-ok-button-selector\">Ok</button>\n                    </div>\n                </dialog>\n            </div>\n        </script>\n    </head>\n\n    <body>\n        <form action=\"server/uploads.php\" id=\"qq-form\">\n            <label>Enter your name</label>\n            <input type=\"text\" name=\"user_name\" required>\n            <label>Enter your email</label>\n            <input type=\"email\" name=\"user_email\" required>\n            <input type=\"submit\" value=\"Done\">\n        </form>\n\n        <div id=\"my-uploader\"></div>\n\n        <script>\n            var uploader = new qq.FineUploader({\n                element: document.getElementById('my-uploader')\n            });\n        </script>\n    </body>\n</html>\n```\n\n{% endmarkdown %}\n{% endblock %}\n"
  },
  {
    "path": "docs/features/handling-errors.jmd",
    "content": "{% extends \"_templates/base.html\" %}\n{% set page_title = \"Handling Errors\" %}\n{% block sidebar %}\n{{ api_links(['messages'], ['log'], ['error']) }}\n{% endblock %}\n{% block content %}\n{% markdown %}\n\n# Handling Errors {: .page-header }\n\nFine Uploader has a wealth of ways to respond to error messages. By default, Fine Uploader assumes an error when the\nserver response does not have a key of `success` set to `true`. If any error is thrown, then the `onError` callback will be invoked.\n\n\n## Debugging Errors\n\nSometimes errors are not intended, or unexpected. Fine Uploader provides the\n`debug` option which will make the plugin spit out logging messages to the\nbrowser's developer tools console.\n\nTo enable debug mode, set the `debug` option of Fine Uploader to `true`\nand open the developer console in your browser. Interact with Fine Uploader\nand notice that it is logging specifically what is happening. These messages\ncan be crucial in determining why something is not working as expected.\n\n\n## Handling Errors\n\nHandling error messages and errors can take place inside of the `onError`\ncallback. `onError` is triggered whenever Fine Uploader encounters an error.\nThe text to be displayed in the case of an error is passed in as the `errorReason`\nparameter.\n\nThe following code snippet shows how one might display a custom alert on the\nevent of an error.\n\n```javascript\nvar uploader = new qq.FineUploader({\n    /* other required config options left out for brevity */\n\n    callbacks: {\n        onError: function(id, name, errorReason, xhrOrXdr) {\n            alert(qq.format(\"Error on file number {} - {}.  Reason: {}\", id, name, errorReason));\n        }\n    }\n});\n```\n\n\n## The Error Response Property\n\nBy default, if Fine Uploader does _not_ receive a successful server response,\nit will look for the `error` property of the response body and will pass\nthat value to the `onError` callback.\n\nIf the server indicates failure in the response, but does not include an\n`error` property and the response status indicates failure, then the\n`defaultResponseError` property will be used as the error message.\n\n```javascript\nvar uploader = new qq.FineUploader({\n    /* other required config options left out for brevity */\n\n    text: {\n        defaultResponseError: 'An unknown upload error occurred.'\n    }\n});\n```\n\n## Changing Error Messages\n\nThe [`messages` property in the core Fine Uploader options](../api/options.html#messages) provides a\nhandful of properties one can override to display custom error messages.\n\n```javascript\nvar uploader = new qq.FineUploader({\n    /* other required config options left out for brevity */\n\n    messages: {\n        typeError: '{file} has an invalid extension. Valid extension(s): {extensions}.'\n        // other messages can go here as well ...\n    }\n});\n```\n\n\n### Error Messages\n\nError messages include primitive `String` interpolation that allows integrators\nto inject variable data into error messages. For example, the `typeError`\nmessage could be modified:\n\n```javascript\nmessages: {\n    typeError: 'Invalid extension detected in file, {file}.'\n}\n```\n\nThe filename of the file which the error was thrown on will be injected\ninstead of the `\"{file}\"` `String`.\n\n\n## Modifying the Text for Failed Uploads\n\nThe `failedUploadTextDisplay` option defines properties used for displaying\ntext when an upload fails.\n\n```javascript\nvar uploader = new qq.FineUploader({\n    /* other required config options left out for brevity */\n\n    // These are all the DEFAULT values...\n    failedUploadTextDisplay: {\n        mode: 'default',\n        responseProperty: 'error',\n    }\n});\n```\n\nThe `mode` property has three settings, each of which determine how failed\nupload errors are displayed.\n\n* `'default'` will display the `failUploadText` defined in the `text` option\nproperties next to each item.\n* `'none'` will **not** display any text next to a failed item.\n* `'custom'` will display an error response text from the server next to each\nfailed item).\n    * `'custom'` mode will use the provided `responseProperty` (which defaults\n    to `'error'`) as its text\n\n{% endmarkdown %}\n{% endblock %}\n"
  },
  {
    "path": "docs/features/modules.jmd",
    "content": "{% extends \"_templates/base.html\" %}\n{% set page_title = \"Importing Fine Uploader\" %}\n{% block content %}\n{% markdown %}\n\n# Importing Fine Uploader {: .page-header }\n\nYou have the following options when pulling Fine Uploader into your project:\n\n1. CommonJS (`require`).\n2. ES6 Modules (`import`).\n3. Globally scoped (`<script src=\"...\">`)\n\nThe first two options assume you are downloading [Fine Uploader via npm][npm]. However, all three options are available\nshould you choose the download the library with npm. The _preferred_ way to download Fine Uploader is in fact through npm.\n\nA new \"lib\" directory is available in the npm package. Among the JavaScript and placeholder image files, the three CSS files\nare available as well, but with different names. The legacy css file is named \"legacy.css\". Generally speaking, you should\navoid using this stylesheet unless you have old code that depends on these styles. The \"fine-uploader-new.css\" stylsheet that contains\na more modern set of styles for a row-based uploader has been appropriately named \"rows.css\". Finally, the \"fine-uploader-gallery.css\"\nstylesheet which supports a gallery view of submitted files is named \"gallery.css\". For more information about styling\nyour uploader, please see [the styling feature page](styling.html).\n\nThe following sections cover, in detail, the three options you have for importing Fine Uploader, once you have downloaded\nthe library. They are ordered by preference.\n\n\n## ES6 Modules\n\nAt the writing of this feature page, the JavaScript modules specification is completed and part of the ECMAScript-262\n6th edition standard. However, at this time, no browser implements this specification. But there are still many options\navailable to you should you choose to make use of ES6 modules when loading Fine Uploader into your project.\nWithout native browser support, you must make use of a tool that compiles ES6 module syntax into a format that is\nparsable by all browsers. There are many such tools, but the more popular options are:\n\n- [Babel][babel] + [Webpack][webpack] v1\n- [Webpack][webpack] v2+\n- [Rollup][rollup]\n\nImporting your desired Fine Uploader resources is as simple as using `import` statements. For example:\n\n```javascript\n// use Fine Uploader core (non-ui) for traditional endpoints\nimport qq from 'fine-uploader/lib/core'\n\nconst uploader = new qq.FineUploaderBasic({...})\n```\n\nYou will find core builds for S3, Azure, and \"all\" in `lib/core/s3.js`, `lib/core/azure.js`,\nand `lib/core/all.js` (respectively)\n\n\n```javascript\n// use Fine Uploader UI for traditional endpoints\nimport qq from 'fine-uploader'\n\n// You may replace \"rows\" w/ \"legacy\" or \"gallery\" depending on your needs\n// This assumes you have a loader to handle importing css files, such as Webpack css-loader\nimport 'fine-uploader/lib/rows.css'\n\nconst uploader = new qq.FineUploader({...})\n```\n\n```javascript\n// use Fine Uploader S3\nimport qq from 'fine-uploader/lib/s3'\n\n// You may replace \"rows\" w/ \"legacy\" or \"gallery\" depending on your needs\n// This assumes you have a loader to handle importing css files, such as Webpack css-loader\nimport 'fine-uploader/lib/rows.css'\n\nconst uploader = new qq.s3.FineUploader({...})\n```\n\n```javascript\n// use Fine Uploader Azure\nimport qq from 'fine-uploader/lib/azure'\n\n// You may replace \"rows\" w/ \"legacy\" or \"gallery\" depending on your needs\n// This assumes you have a loader to handle importing css files, such as Webpack css-loader\nimport 'fine-uploader/lib/rows.css'\n\nconst uploader = new qq.azure.FineUploader({...})\n```\n\n```javascript\n// allow use of Fine Uploader S3, Azure, and/or traditional endpoint code\nimport qq from 'fine-uploader/lib/all'\n\n// You may replace \"rows\" w/ \"legacy\" or \"gallery\" depending on your needs\n// This assumes you have a loader to handle importing css files, such as Webpack css-loader\nimport 'fine-uploader/lib/rows.css'\n\nconst traditionalUploader = new qq.FineUploader({...})\nconst s3Uploader = new qq.s3.FineUploader({...})\nconst azureUploader = new qq.azure.FineUploader({...})\n```\n\n## TypeScript + Angular version 2 and higher\n\nImporting Fine Uploader in TypeScript is very similar to ES6 import. The only difference being the global namespace `qq` is not available for TypeScript users.\nSo the ES6 import syntax changes for TypeScript users as following:\n\n```javascript\n// use Fine Uploader core (non-ui) for traditional endpoints\nimport { FineUploaderBasic } from 'fine-uploader/lib/core';\n\nlet uploader = new FineUploaderBasic({...})\n```\n\n```javascript\n// use Fine Uploader UI for traditional endpoints\nimport { FineUploader } from 'fine-uploader';\n\nlet uploader = new FineUploader({...});\n```\n\n```javascript\n// use Fine Uploader S3\nimport { s3 } from 'fine-uploader/lib/s3';\n\nlet uploader = new s3.FineUploader({...});\n```\n\n```javascript\n// use Fine Uploader Azure\nimport { azure } from 'fine-uploader/lib/azure';\n\nlet uploader = new azure.FineUploader({...});\n```\n\nGenerating production bundles using Rollup?\nIn your `rollup-config.js` (or whatever name your are using for rollup's config file) file, add the following under plugins property\n\n```javascript\ncommonjs({\n    include: [\n        'node_modules/rxjs/**',\n        'node_modules/fine-uploader/**'\n    ],\n    namedExports: {\n        'node_modules/fine-uploader/lib/traditional.js': ['FineUploader']\n    }\n})\n```\n\nIf you are not using traditional Fine Uploader flavour, remember to change `node_modules/fine-uploader/lib/traditional.js` accordingly. \nFor example: for s3 Fine Uploader this should be `node_modules/fine-uploader/lib/s3.js`\n\n\n## CommonJS\n\nIf you would like to use [RequireJS][requirejs] or [Babel][babel] + [Webpack] v1, or even [Bublé][buble], you will want\nto import Fine Uploader using the `require` function. Importing your desired Fine Uploader resources is as simple\nas using `require` statements. For example:\n\n```javascript\n// use Fine Uploader core (non-ui) for traditional endpoints\nvar qq = require('fine-uploader/lib/core')\n\nvar uploader = new qq.FineUploaderBasic({...})\n```\n\nYou will find core builds for S3, Azure, and \"all\" in `lib/core/s3.js`, `lib/core/azure.js`,\nand `lib/core/all.js` (respectively)\n\n\n```javascript\n// use Fine Uploader UI for traditional endpoints\nvar qq = require('fine-uploader'_\n\n// You may replace \"rows\" w/ \"legacy\" or \"gallery\" depending on your needs\n// This assumes you have a loader to handle importing css files, such as Webpack css-loader\nrequire('fine-uploader/lib/rows.css')\n\nvar uploader = new qq.FineUploader({...})\n```\n\n```javascript\n// use Fine Uploader S3\nvar qq = require('fine-uploader/lib/s3')\n\n// You may replace \"rows\" w/ \"legacy\" or \"gallery\" depending on your needs\n// This assumes you have a loader to handle importing css files, such as Webpack css-loader\nrequire('fine-uploader/lib/rows.css')\n\nvar uploader = new qq.s3.FineUploader({...})\n```\n\n```javascript\n// use Fine Uploader Azure\nvar qq = require('fine-uploader/lib/azure')\n\n// You may replace \"rows\" w/ \"legacy\" or \"gallery\" depending on your needs\n// This assumes you have a loader to handle importing css files, such as Webpack css-loader\nrequire('fine-uploader/lib/rows.css')\n\nvar uploader = new qq.azure.FineUploader({...})\n```\n\n```javascript\n// allow use of Fine Uploader S3, Azure, and/or traditional endpoint code\nvar qq = require('fine-uploader/lib/all')\n\n// You may replace \"rows\" w/ \"legacy\" or \"gallery\" depending on your needs\n// This assumes you have a loader to handle importing css files, such as Webpack css-loader\nrequire('fine-uploader/lib/rows.css')\n\nvar traditionalUploader = new qq.FineUploader({...})\nvar s3Uploader = new qq.s3.FineUploader({...})\nvar azureUploader = new qq.azure.FineUploader({...})\n```\n\n\n## Globally Scoped\n\nThe traditional way to pull Fine Uploader onto a page is to do so using `<script>` and `<link>` tags. Before version\n5.8.0, this was the _only_ way to properly import Fine Uploader. The downside of this approach is that you are left with\na global variable attached to `window` - `qq`. Importing Fine Uploader as a globally scoped object can be accomplished\nas follows:\n\n```html\n<!-- use Fine Uploader core (non-ui) for traditional endpoints -->\n<script src=\"node_modules/fine-uploader/fine-uploader/fine-uploader.core.js\" type=\"text/javascript\"></script>\n\n<script>\n    (function() {\n        var uploader = new qq.FineUploaderBasic({...})\n    }())\n</script>\n```\n\nYou will find similarly named core builds for S3, Azure, and \"all\" inside of each respective\nendpoint folder as well.\n\n\n```html\n<!-- use Fine Uploader UI for traditional endpoints -->\n<script src=\"node_modules/fine-uploader/fine-uploader/fine-uploader.js\" type=\"text/javascript\"></script>\n\n<link href=\"node_modules/fine-uploader/fine-uploader/fine-uploader-new.css\" rel=\"stylesheet\" type=\"text/css\"/>\n\n<script>\n    (function() {\n        var uploader = new qq.FineUploader({...})\n    }())\n</script>\n```\n\n```javascript\n<!-- use Fine Uploader S3 -->\n<script src=\"node_modules/fine-uploader/s3.fine-uploader/s3.fine-uploader.js\" type=\"text/javascript\"></script>\n\n<link href=\"node_modules/fine-uploader/s3.fine-uploader/fine-uploader-new.css\" rel=\"stylesheet\" type=\"text/css\"/>\n\n<script>\n    (function() {\n        var uploader = new qq.s3.FineUploader({...})\n    }())\n</script>\n```\n\n```javascript\n<!-- use Fine Uploader Azure -->\n<script src=\"node_modules/fine-uploader/azure.fine-uploader/azure.fine-uploader.js\" type=\"text/javascript\"></script>\n\n<link href=\"node_modules/fine-uploader/azure.fine-uploader/fine-uploader-new.css\" rel=\"stylesheet\" type=\"text/css\"/>\n\n<script>\n    (function() {\n        var uploader = new qq.azure.FineUploader({...})\n    }())\n</script>\n```\n\n```javascript\n<!-- use Fine Uploader S3, Azure, or traditional -->\n<script src=\"node_modules/fine-uploader/all.fine-uploader/all.fine-uploader.js\" type=\"text/javascript\"></script>\n\n<link href=\"node_modules/fine-uploader/all.fine-uploader/fine-uploader-new.css\" rel=\"stylesheet\" type=\"text/css\"/>\n\n<script>\n    (function() {\n        var traditionalUploader = new qq.FineUploader({...})\n        var s3Uploader = new qq.s3.FineUploader({...})\n        var azureUploader = new qq.azure.FineUploader({...})\n    }())\n</script>\n```\n\n\n## AMD\n\nSupport for loading Fine Uploader via the Asynchronous Module Definition API is technically possible, and the code\n_should_ be in place to support this, but it is not _officially_ supported at this time. Demand for this type of loading\nis small, and we don't have a use for this in any of our projects. If you do find any issues with AMD support, please\nfile an issue. We would also love to have this section of the documentation expanded to cover AMD support.\n\n\n\n[babel]: https://babeljs.io/\n[buble]: https://gitlab.com/Rich-Harris/buble\n[npm]: https://www.npmjs.com/package/fine-uploader\n[requirejs]: http://requirejs.org/\n[rollup]: http://rollupjs.org/\n[typescript]: https://www.typescriptlang.org/\n[webpack]: http://webpack.github.io/\n\n{% endmarkdown %}\n{% endblock %}\n"
  },
  {
    "path": "docs/features/no-server-uploads.jmd",
    "content": "{% extends \"_templates/base.html\" %}\n{% set page_title = \"No-Server Uploads\" %}\n{% block sidebar %}\n{{ api_links(options=['credentials-s3'], events=['credentialsExpired-s3'], methods=['setCredentials-s3']) }}\n{% endblock %}\n{% block content %}\n{% markdown %}\n# Upload Without a Server {: .page-header }\n\n### Summary\nEnabling file uploads for your web application is easier than ever with Fine Uploader S3's client-side\nrequest signing support.  With this feature, you don't need to worry about server-side languages.  In fact,\nyou only need a server to host your HTML and JavaScript files.  You can even delegate this task to an Amazon\nS3 bucket, such as in the [official Fine Uploader S3 no-server example][1].\n\n### API\nA [`credentials` option][2] has been created for Fine Uploader S3 to support this workflow.  It allows you to\npass temporary credentials (secret & public AWS keys) to Fine Uploader S3 during initialization.  An `expiration` date\nfor the credentials must also be provided.  Additionally, if you are using temporary credentials from the\nAWS Security Token Service, you will also need to provide a `sessionToken`. If you would like to use version 4\nsignatures, instead the default version 2, be sure to set [the `signature.version` option][6] to `4`.\n\nWhen your credentials expire, your [`credentialsExpired` event handler][3] will be invoked.  You must then return a\n[promise][4] and fulfill it by passing the new credentials via the `success` method on the promise.  The\nvalidity of credentials is checked internally by Fine Uploader S3 just before a request is sent to S3.  If the\ncredentials have expired, the request will be held up until your event handler specifies a new set of credentials or\nfails its attempt to do so.\n\nAn [API method, `setCredentials`][5], also exists if you would like to update initial credentials after\nFine Uploader S3 has been initialized.  You can also utilize this method to tell Fine Uploader S3 about\ntemporary credentials for the first time at some point after the uploader instance has been created.\n\n### Best Practices\nIn light of the fact that this workflow requires you expose an AWS secret key client-side (making it easy for\nmalicious users to gain access to your secret key), there are some precautionary measure you should take:\n\n* Do not hard-code your credentials client-side.  Your credentials should be **temporary**, with a reasonable expiration date.\n* Communication between your page and the entity that provides temporary credentials should be secured in some fashion (i.e. SSL).\n* Communication between your Fine Uploader S3 instance and your S3 bucket should be secured via SSL.\n* Consider use of an established identity provider along with AWS STS to obtain temporary credentials.  An example of this can be seen in the [official Fine Uploader S3 no-server upload example][1].\n\n[1]: https://fineuploader-s3-client-demo.s3.amazonaws.com/index.html\n[2]: ../api/options-s3.html#credentials-option\n[3]: ../api/events-s3.html#credentialsexpired\n[4]: async-tasks-and-promises.html\n[5]: ../api/methods-s3.html\n[6]: ../api/options-s3.html#signature.version\n\n{% endmarkdown %}\n{% endblock %}\n"
  },
  {
    "path": "docs/features/paste-to-upload.jmd",
    "content": "{% extends \"_templates/base.html\" %}\n{% set page_title = \"Paste to Upload\" %}\n{% block sidebar %}\n{{ api_links(options=['paste'], events=['pasteReceived']) }}\n{% endblock %}\n{% block content %}\n{% markdown %}\n\n# Paste to Upload {: .page-header }\n\nBefore some of the latest HTML5 features came out, in order to upload an image\nfrom another web page, you must save the image to your local file system and\nthen either drop it into an uploader instance or select it from the\n“choose files” dialog. Certainly copying and pasting is a much more efficient\nway to do this. Note that you can also use this feature to upload any image\ncurrently present in your clipboard. For example, suppose you take a screenshot\nand add it directly to your clipboard. You can then paste it into your Fine\nUploader instance.\n\n\n## Handling pasted images in Core mode\n\nIn order to enable this feature, you must provide a `paste.targetElement` option value. This is the element that should\nreceive pasted images. This target element must have focus before the paste event can be handled. This is normally\naccomplished by clicking on the element before pasting the image from the clipboard, or by programmatically giving it focus.\n\nOnce the image has been pasted, the `onPasteReceived` callback will be invoked with the associated `Blob` as a parameter.\nThis is a promissory callback, and a `qq.Promise` MUST be returned if you provide your own implementation of this callback.\nVia the promise object, you can return a string that determines the name of the associated image. This will be passed along\nwith the upload request. Fine Uploader will also append an extension to the name, based on the image type. This callback\ncan be used if you need to make an ajax call to determine the name of the pasted image, or if you need to display a\n(blocking or non-blocking) modal dialog to ask the user for this information. If you do not contribute a custom implementation\nfor this callback, the `paste.defaultName` configuration property, along with an appropriate extension, will be used to\nname the pasted image.\n\n\n## Handling pasted images in UI mode\n\nEverything in the above previous section also applies to UI mode, but a small nicety in the form of an overridable name\nprompt is available. In other words, if you want a basic prompt dialog to ask the user for a name after an image has been pasted,\nyou can do so simply by setting the `paste.promptForName` configuration option. If you want to ask the user to name a\npasted image, you don’t have to worry about the `onPasteReceived` callback at all. You can also use a more appropriate\nprompt dialog by overriding the `paste.showPrompt` option function. A simple example of this, using a bootbox modal prompt,\nis described [at the end of the promises documentation](async-tasks-and-promises.html#example).\n\n\n{{\nalert(\n\"\"\"The filename will be sent with the request as the `qqfilename` parameter.\n   Be sure to read this parameter when naming your file server-side.\", \"info\", \"Note:\")\n}}\n\n{% endmarkdown %}\n{% endblock %}\n"
  },
  {
    "path": "docs/features/pause.jmd",
    "content": "{% extends \"_templates/base.html\" %}\n{% set page_title = \"Pause In-Progress Uploads\" %}\n{% block sidebar %}\n{{ api_links(methods=['continueUpload', 'pauseUpload']) }}\n{% endblock %}\n{% block content %}\n{% markdown %}\n# Pause In-Progress Uploads {: .page-header }\n\n### Summary\nVersion 4.1 brings the ability to pause (and then continue) in-progress chunked uploads.  A paused upload will free\nup a slot in the [connections](../api/options.html#core_1) queue, allowing the next queued file to begin uploading as well.\nThis feature includes new [API methods](../api/methods.html), built-in Fine Uploader UI support (via optional\n[template](styling.html) elements), and a [feature support](../browser-support.html) flag.  Note that this feature\ndepends on the chunking feature, so chunking must be possible and enabled in the current browser.  When a paused upload\nis continued, it will start at the beginning of the chunk that was in progress when the upload was paused.\n\n### Use Cases\nThis feature mainly compliments the [chunking](chunking.html) and [auto-resume](resume.html) features.\nFor example, while it is not necessary to \"pause\" an in progress upload to resume it later, it is probably much more\nintuitive for end users to do so.  This feature also provides the ability to upload queued items immediately (or sooner)\nsimply by pausing in-progress uploads that are lower priority.\n\n### Fine Uploader UI Support\nIf you want \"pause\" and/or \"continue\" buttons to appear next to your files at the appropriate times, you must\nadd the following to your template:\n\n```html\n<button type=\"button\" class=\"qq-upload-pause-selector qq-upload-pause\">Pause</button>\n<button type=\"button\" class=\"qq-upload-continue-selector qq-upload-continue\">Continue</button>\n```\nThese elements must be added under the qq-upload-list-selector container in your template in order for Fine Uploader\nUI to find them.\n\nYou can remove the second CSS class in each element, if you choose.  The first CSS class is used internally\nby Fine Uploader to select the buttons, and the second is used to apply default styles defined in Fine Uploader's\nCSS file.  You can also change the text, omit one or both of these elements, add attributes, or add CSS classes\nin your template definition.\n\nThe pause button will appear next to a file that is in progress if chunking is enabled and at least one chunk has been\nsuccessfully uploaded.  By default, the progress bar will remain visible, the spinner will be hidden, and a \"Paused\"\nmessage will appear to the right of the file.  This text can be customized by overriding the default value of the\n[`text.paused` UI option](../api/options-ui.html#text-option).\n\nThe continue button will appear (and the pause button will be hidden) after a file upload has been paused.  The status\ntext will be cleared and the spinner will re-appear at this point.  The file will be added to the end of the connections\nqueue, meaning that it will not actually continue until all other files ahead of it in the queue have completed\n(or have been paused).\n\n### Fine Uploader Core Support (API methods)\nThere are two new [API methods](../api/methods.html): `pauseUpload` and `continueUpload`.  As with all API methods,\nthese are also available to Fine Uploader UI instances as well.  With these, you can programmatically pause an in-progress\nupload, or continue a paused upload.   These methods will return `true` if the operation succeeds.  Note that pausing\nan upload is only possible if the current browser supports chunking, and if chunking is enabled.\n\n{% endmarkdown %}\n{% endblock %}\n"
  },
  {
    "path": "docs/features/progress-bars.jmd",
    "content": "{% extends \"_templates/base.html\" %}\n{% set page_title = \"Progress Bars\" %}\n{% block sidebar %}\n{{ api_links(events=['progress', 'totalProgress']) }}\n{% endblock %}\n{% block content %}\n{% markdown %}\n\n# Progress Bars {: .page-header }\n\n{{ alert(\"This feature is available in Chrome (except in iOS), Firefox, Safari, IE10+, & Opera 15+ due to their support for the\n[`ProgressEvent` interface](https://dvcs.w3.org/hg/progress/raw-file/tip/Overview.html#interface-progressevent).  Note that\nprogress reporting is not supported in Android's stock browser, or Chrome in iOS.\") }}\n\nSeeing your uploads progress can be a satisfying feeling, especially when they\nfinish. With Fine Uploader UI mode, a progress bar is included in the UI, and\n-- just like any other Fine Uploader UI feature -- it can be completely\nrebuilt and rebranded in Core mode.\n\nFine Uploader provides two concepts of progress: per-file progress, and total progress.\n\n### Per-File Progress\nIf using Fine Uploader UI, a progress bar will be drawn and updated for each file as the upload progresses.  Note that\nyou must have the progress element in your template.  Here's what the progress element may look like in your template:\n\n```html\n<div class=\"qq-progress-bar-container-selector\">\n    <div role=\"progressbar\" aria-valuenow=\"0\" aria-valuemin=\"0\" aria-valuemax=\"100\" class=\"qq-progress-bar-selector qq-progress-bar\"></div>\n</div>\n```\n\nYou can remove or change the non-selector CSS classes, tags, etc to suit your application.\n\n\nThere is also a `progress` event, that can be used to feed your own custom progress bar if you are using\nFine Uploader Core.\n\n\n### Total Progress\nVersion 4.4 brings built-in total progress tracking.  If using Fine Uploader UI, a total progress bar will be rendered\nand updated automatically for you, provided you have the total progress module included in the source and the total\nprogress element is present in your template.  Here's what the total progress element may look like in your\ntemplate:\n\n```html\n<div class=\"qq-total-progress-bar-container-selector qq-total-progress-bar-container\">\n    <div role=\"progressbar\" aria-valuenow=\"0\" aria-valuemin=\"0\" aria-valuemax=\"100\" class=\"qq-total-progress-bar-selector qq-progress-bar qq-total-progress-bar\"></div>\n</div>\n```\n\nAs with all other template elements, you can remove or change the non-selector CSS classes, tags, etc to suit\nyour application.\n\n\nFine Uploader core users can easily feed their own custom total progress bar by listening to the `totalProgress`\nevents that Fine Uploader will trigger.\n\nTotal progress represents the upload progress of all files in the current batch of files.  Once all files have\nfinished uploading (failed or successful), total progress calculations are complete and will restart once\nnew files are submitted.\n\nTotal progress will be updated, and the `totalProgress` event will be triggered whenever:\n* An individual file's progress changes.\n* The total size of an individual file changes (such as for scaled images, as the sizes of these are not known\nuntil they are generated just before the upload begins).\n* Whenever a file is added.\n* Whenever a file is canceled.\n\nIn Fine Uploader UI, the total progress bar will be hidden once it reaches 100%, or once there are no longer\nany files in progress.\n\n{% endmarkdown %}\n{% endblock %}\n"
  },
  {
    "path": "docs/features/request-parameters.jmd",
    "content": "{% extends \"_templates/base.html\" %}\n{% set page_title = \"Request Parameters\" %}\n{% block sidebar %}\n{{ api_links(options=['request', 'request-s3'], methods=['setParams', 'setDeleteFileParams'],\nevents=['upload', 'manualRetry', 'autoRetry']) }}\n{% endblock %}\n\n{% block content %}\n{% markdown %}\n\n# Request Parameters {: .page-header }\n\nThe requests that Fine Uploader makes to your server are arguably the most\n crucial part of the uploading process. Fortunately, modifying the request\nis a fairly trivial task thanks to a few helpful options and methods\nprovided by Fine Uploader.\n\nRequest parameters can be modified foremost by changing the values of the\nfollowing options found in the `request` option:\n\n### Fine Uploader S3's `request` option\nThe request option properties are a bit different if you are using Fine Uploader S3.  Here are the only properties of\nthe `request` option used by Fine Uploader S3.\n\n----\n\nThe `params` option is used to define request parameters when you instantiate a\nnew Fine Uploader instance. Parameters defined this way are set on the request\nat the very last minute before it is sent to the server. Parameters can also\nbe set for delete file requests. Simply modify the `params` property of the\n`deleteFile` option. Either of these properties are capable of handling a\nvariety of JavaScript objects.\n\n#### Simple Objects\n\n```javascript\nparams: {\n    username: \"mark\",\n    date: new Date()\n}\n```\n\n#### Nested Objects\n\n```javascript\nparams: {\n    user: {\n        username: 'mark',\n        firstName: 'Mark',\n        lastName: 'Twain'\n    }\n}\n```\n\n#### Functions\n\nYes, even functions! (They are first-class citizens in JS world)\n\nEach function will be evaluated **for each** file that is submitted. The following code snippet\ndemonstrates how one would track the current number of uploaded files.\n\n\n```javascript\nvar fileNum = 0;\n\n// ...\n\n    params: {\n        fileNum: function () {\n            return fileNum++;\n        }\n    }\n```\n\n{{ alert(\"Any params with a function value **must** return either a number or a string.\", \"info\", \"Note\") }}\n\n[For more information, see the associated blog post](http://blog.fineuploader.com/2012/12/09/setparams-is-now-much-more-useful-in-3-1/)\n\n## Methods\n\nThe `setParams` and `setDeleteFileParams` methods can be used to change parameters\npassed at any point in the upload process (well, at any point up until the file\nis uploaded). For the current file, you can adjust the passed parameters along\nwith the request as late as when the `upload` event is fired. Parameters can\nalso be modified for a file in the `manualRetry` and `autoRetry` events.\n\n`setParams` can change the parameters for a all files, or for a single file by\nproviding an optional `id` parameter.\n\n{{ alert(\n\"\"\"Once you change parameters for a specific file, subsequent calls to `setParams` without an `id` will **not** affect this file.\nTo change parameters again, call `setParams` again with the associated `id`.\"\"\", \"info\", \"Note\") }}\n\n## URL Query String Parameters\n\nSometimes it may prove useful to send request parameters in the URL\nquery string, and fortunately Fine Uploader supports this right out of the\nbox. By disabling the `request.paramsInBody` option, you can pass parameters\nthrough the query string rather than the request body.\n\n{{ alert(\n\"\"\"All parameters in the query string are [URI Component Encoded](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/encodeURIComponent).\nThis means that you must decode the parameter keys and values server-side. JavaScript's `encodeURIComponent` function\nuses the UTF-8 encoding scheme so make sure that your server is using the same encoding. Lastly,\nbe sure to the meta tag in your HTML: `<meta charset='utf-8'>`\"\"\", \"info\", \"Note\") }}\n\n\n[For more information, see the associated blog post](http://blog.fineuploader.com/2012/12/05/include-params-in-the-request-body-or-the-query-string/)\n\n{% endmarkdown %}\n{% endblock %}\n"
  },
  {
    "path": "docs/features/resume.jmd",
    "content": "{% extends \"_templates/base.html\" %}\n{% set page_title = \"Resume\" %}\n{% block sidebar %}\n{{ api_links(options=['resume'], methods=['getResumableFilesData'], events=['resume']) }}\n{% endblock %}\n{% block content %}\n{% markdown %}\n\n# Resuming Uploads {: .page-header }\n\nSuppose you’re sitting in a coffee shop, slowly uploading a very large file. Your lunch break is over and you have to\nhead back to the office, but your upload is no where near complete. If you're using Fine Uploader, you can simply close\nyour browser, head back to the office, and re-select or drop the file back into the uploader. It will pick up where you left off.\nPerhaps you are uploading another large file, but your PC blue-screens in the middle of the upload. Once you get your\nbrowser back up and running again, simply drop or select the file again and Fine Uploader will resume your file upload.\n\n\n## High-level summary\n\nThe ability to resume an upload is dependent [the chunking feature](chunking.html). Before each chunk is sent to the server,\nFine Uploader creates a `localStorage` entry with all of the information required to resume the upload. This is done to\ncover termination of the browser session before the chunk has been successfully received by the server. After Fine Uploader\nhas confirmed that the chunk has been successfully processed, the storage entry is either deleted (if there are no more\nchunks left for this file) or updated with the metadata for the next chunk.\n\n\n## Configuring\n\nWe have provided the ability to enable or disable the resume feature (it’s disabled by default). Also, the number of\ndays a resume storage entry can live is configurable, but defaults to 7 days. Finally, you may specify an ID property\nthat will be used to further distinguish resume cookies stored by the uploader. You may find this useful, if, for instance,\nyou would like to tie resumable files to a specific user.\n\nNote that you _must_ also enable the chunking feature if you want to use resume.\n\n\n## Callbacks\n\nWe've also provided a callback, `onResume`, with some useful parameters. The callback is invoked _before_ a resume begins.\nThe file ID, along with the name of the file and some data specific to the chunk to be sent are passed to the callback.\nIf you want to abort the resume attempt browser-side and simply start uploading from the first chunk, you can return `false`\nin your callback handler.\n\n\n## API\n\nWe have also added a new method to the API: `getResumableFilesData`. This allows you to obtain a list of files that are\nresumable in the current session. You may find this useful if you want to display a message to the user after the uploader\nis initialized.\n\n\n## Server-side support\n\nOn the server side, there is very little you have to do if you are already accounting for chunked uploads. You can\ndetermine when a resume has been ordered by looking for a “qqresume” parameter in the request with a value of `true`.\nThis parameter will be sent with the first request of the resume attempt.\n\nIt is important that you keep chunks around on the server until either the entire file has been uploaded and all chunks\nhave been merged, or until the number of days specified in the `resume.recordsExpireIn` option have passed.\nIf, for some reason, you receive a request that indicates a resume has been ordered, and one or more of the previously\nuploaded chunks is missing or invalid, you can return a valid JSON response containing a “reset” property with a value of “true”.\nThis will let Fine Uploader know that it should start the file upload from the first chunk instead of the last failed chunk.\n\n\n## Example\n\nIt’s really quite simple to start using the resume feature:\n\n```javascript\nvar uploader = new qq.FineUploader({\n    element: document.getElementById('myUploader'),\n    request: {\n        endpoint: '/my/endpoint'\n    },\n    chunking: {\n        enabled: true\n    },\n    resume: {\n        enabled: true\n    }\n});\n```\n\n{% endmarkdown %}\n{% endblock %}\n"
  },
  {
    "path": "docs/features/retry.jmd",
    "content": "{% extends \"_templates/base.html\" %}\n{% set page_title = \"Retrying Uploads\" %}\n{% block sidebar %}\n{{ api_links(options=['retry', 'retry-ui'], methods=['retry'], events=['autoRetry', 'manualRetry']) }}\n{% endblock %}\n{% block content %}\n{% markdown %}\n\n# Retrying Uploads {: .page-header }\n\nFine Uploader will automatically attempt to retry an upload if the `enableAuto` option is set to `true`:\n\n```javascript\nvar uploader = new qq.FineUploader({\n    /* other required config options left out for brevity */\n\n    retry: {\n        enableAuto: true\n    },\n    callbacks: {\n        onAutoRetry: function(id, name, attemptNumber) {\n            ...\n        }\n    }\n});\n```\n\n{% endmarkdown %}\n{% endblock %}\n"
  },
  {
    "path": "docs/features/s3.jmd",
    "content": "{% extends \"_templates/base.html\" %}\n{% set page_title = \"Uploading Directly to Amazon S3\" %}\n{% block sidebar %}\n{% endblock %}\n{% block content %}\n{% markdown %}\n# Uploading Directly to Amazon S3 {: .page-header }\n\n### General Uploads to S3\nPlease see [the blog post on Fine Uploader S3][s3blog].\n\n\n### Uploading to S3 Through a CDN\nSupport for uploads to an S3 bucket via a CDN was added in Fine Uploader 5.1.0. You can upload files to\nany S3 bucket through any CDN, provided the CDN forwards all headers and does not append any additional\nheaders to the request that is sent on to S3. An example of a CDN that should work fine is [fastly][fastly].\n\n#### S3 Transfer Acceleration\nAmazon's [S3 Transfer Acceleration option][s3transferacceleration] is by far the easiest way to have your\nuploads to S3 go through a CDN (Amazon Cloudfront). To use this feature, simply enable it in your AWS console\nand specify `https://[bucket-name].s3-accelerate.amazonaws.com` instead of `https://[bucket-name].s3.amazonaws.com`\nfor the [`request.endpoint` option][requestendpoint].\n\n#### Custom CDN\nWhen uploading to S3 via a CDN, you must specify the name of the bucket. This is not required when directly\nuploading to S3 or when using the Transfer Acceleration option, since Fine Uploader is able to determine the\nbucket name by examining the S3 endpoint URL. This is obviously not the case for a CDN endpoint. So, in\naddition to specifying the CDN endpoint via the [`request.endpoint` option][requestendpoint], you must also\nspecify a bucket name via the [`objectProperties.bucket` option][objectpropertiesbucket].\n\nFurthermore, if you are using version 4 signatures, you _also_ must specify the hostname of the S3 bucket via the\n[`objectProperties.host` option][objectpropertieshost].\n\nPlease see the documentation for these options for more details.\n\nThe simplest set up consists of:\n\n```javascript\nvar uploader = new qq.s3.FineUploader({\n    request: {\n        endpoint: '{ YOUR_CDN_ENDPOINT_URL }'\n        accessKey: '{ YOUR_ACCESS_KEY }'\n    },\n    objectProperties: {\n        bucket: '{ YOUR_S3_BUCKET_NAME }'\n        host: '{ YOUR_S3_BUCKET_HOST_NAME }' // only needed for version 4 signatures\n    },\n    signature: {\n        endpoint: '/s3/signature'\n    },\n    uploadSuccess: {\n        endpoint: '/s3/success'\n    },\n    iframeSupport: {\n        localBlankPagePath: '/success.html'\n    }\n});\n```\n\n### Accounting for browser/client clock drift\nIf the clock on the machine running Fine Uploader is too far off of the current date, S3 may reject any requests\nsent from this machine. To overcome this situation, you can include a [clock drift value][drift-option], in milliseconds, when\ncreating a new Fine Uploader instance. One way to set this value is to subtract the current time according to the\nbrowser from the current unix time according to your server. For example:\n\n```javascript\nvar uploader = new s3.FineUploader({\n    request: {\n        clockDrift: SERVER_UNIX_TIME_IN_MS - Date.now()\n    }\n})\n```\n\nIf this value is non-zero, Fine Uploader S3 will use it to pad the `x-amz-date` header and the policy expiration\ndate sent to S3.\n\n\n### Headers\n\nIf you would like to attach HTTP headers to objects uploaded to your S3 bucket via Fine Uploader S3,\nyou must specify them as parameters, either via [the `setParams` API method][setparams], or [the `request.params` option][params].\nAll parameter/header names will be prefixes with \"x-amz-meta-\", _and_ values will be URI encoded, _except_ for the\nfollowing \"special\" headers:\n\n* Cache-Control\n* Content-Disposition\n* Content-Encoding\n* Content-MD5\n* x-amz-server-side-encryption-aws-kms-key-id\n* x-amz-server-side-encryption\n* x-amz-server-side-encryption-customer-algorithm\n* x-amz-server-side-encryption-customer-key\n* x-amz-server-side-encryption-customer-key-MD5\n\nThe above header names and values will be untouched. If any of these values needs to be encoded in some way,\nyou are responsible for doing so _before_ passing the header to Fine Uploader S3.\n\n\n[drift-option]: ../api/options-s3.html#request.clockDrift\n[fastly]: http://www.fastly.com/\n[issue1016]: https://github.com/FineUploader/fine-uploader/issues/1016\n[objectpropertiesbucket]: ../api/options-s3.html#objectProperties.bucket\n[objectpropertieshost]: ../api/options-s3.html#objectProperties.host\n[params]: ../api/options.html#request.params\n[requestendpoint]: ../api/options-s3.html#request.endpoint\n[s3blog]: http://blog.fineuploader.com/2013/08/16/fine-uploader-s3-upload-directly-to-amazon-s3-from-your-browser/\n[s3transferacceleration]: http://docs.aws.amazon.com/AmazonS3/latest/dev/transfer-acceleration.html\n[setparams]: ../api/methods.html#setParams\n\n{% endmarkdown %}\n{% endblock %}\n"
  },
  {
    "path": "docs/features/scaling.jmd",
    "content": "{% extends \"_templates/base.html\" %}\n{% set page_title = \"Upload Scaled Images\" %}\n{% block sidebar %}\n{{ api_links(options=['scaling', 'scaling-ui', ], methods=['getParentId', 'scaleImage']) }}\n{% endblock %}\n{% block content %}\n{% markdown %}\n\n[s3]: s3.html\n[azure]: azure.html\n[customResizer]: ../api/options.html#scaling.customResizer\n[pica]: https://github.com/nodeca/pica\n[scaling]: ../api/options.html#scaling\n[sizes]: ../api/options.html#scaling.sizes\n[defaulttype]: ../api/options.html#scaling.defaultType\n[sendOriginal]: ../api/options.html#scaling.sendOriginal\n[exif]: ../api/options.html#scaling.includeExif\n[orient]: ../api/options.html#scaling.orient\n[hidescaled]: ../api/options-ui.html#scaling.hideScaled\n[getfile]: ../api/methods.html#getFile\n[limby-resize]: https://github.com/danschumann/limby-resize\n[promise]: async-tasks-and-promises.html\n[data]: statistics-and-status-updates.html\n[api]: ../api/methods.html\n[itemlimit]: ../api/options.html#validation.itemLimit\n[webworkers]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers\n\n\n# Generate and Upload Scaled Images {: .page-header }\n\n### Overview\nThe scaling feature allows your server to breathe a bit easier as image scaling efforts are handled client-side\nby Fine Uploader.  If your users submit any image files that can be rendered natively by the current browser,\nFine Uploader will create scaled versions and upload those as well.  You can tell Fine Uploader which sizes you would\nlike it to generate and upload.  You can specify size-specific text to append to each scaled image name.  The scaled\nimage can be correctly oriented by Fine Uploader before it is sent to your server.  You can also ensure only scaled\nimages, and not the original image, are uploaded.  Finally, you can ask Fine Uploader to magically insert the\nEXIF data from the original image into each scaled image.\n\nGenerating and uploading scaled versions of each submitted\nimage can be as simple as specifying an array of objects with `name` and `maxSize` properties to indicate the scaled image\nfile's name and maximum size, respectively.\n\n\n### Basic Operation\nBy default, Fine Uploader will send the original image file, along with any scaled versions you have requested via\nthe [`scaling.sizes` option][sizes].  Also, by default, all scaled versions will be correctly oriented before they\nare uploaded.\n\nYou must provide a `sizes` array though.  For example:\n\n```javascript\nvar uploader = new qq.FineUploader({\n    ...\n    scaling: {\n        sizes: [\n            {name: \"small\", maxSize: 100},\n            {name: \"medium\", maxSize: 300}\n        ]\n    }\n});\n```\n\nThe code above will result in Fine Uploader generating and uploading 2 scaled versions of each user-submitted\nimage file.  The original, along with the 2 scaled versions, will appear in the UI's file list as well.  If the\nuser submits a PNG named \"image.png\", that will be submitted, along with another file named \"image (small).png\",\nand \"image (medium).png\".\n\nThe scaled versions will be scaled proportional to the original image.  In this case, the \"small\" version will\nbe no more than 100px high or wide, and the \"medium\" version will be no more than 300px wide or high.\n\nNotice that the two scaled images do not initially have file sizes displayed in the UI.  This is due to the\nfact that Fine Uploader defers generation of these scaled versions until just before the images are going to be\nactually uploaded.  After a scaled image has been uploaded, the client-side generated version of this image is\ndiscarded to conserve browser memory.  Calls to the [`getFile` API method][getfile] will return the associated\noriginal file, not the scaled version.  This is due to the fact that, again, the scaled version is not kept around\nany longer than necessary to conserve memory.  Also, generating a scaled version is an asynchronous operation, which\nwould require the [`getFile` API method][getfile] return a [promise][promise], introducing a breaking\nchange to the API.  Once a scaled image has been generated, the UI (and [upload data store][data]) will be updated\nwith the generated size.\n\nFine Uploader ensures that a group of scaled images is always uploaded in order, smallest file first.\nThe original file is always sent last.\n\n\n### Only Upload Scaled Files\nTo conserve bandwidth, you may only want to upload smaller, scaled versions of an original image.  Simply set the\n[`scaling.sendOriginal` option][sendoriginal] to `false`.  The above example would be changed to:\n\n```javascript\nvar uploader = new qq.FineUploader({\n    ...\n    scaling: {\n        sendOriginal: false,\n\n        sizes: [\n            {name: \"small\", maxSize: 100},\n            {name: \"medium\", maxSize: 300}\n        ]\n    }\n});\n```\n\nIf you instruct Fine Uploader to NOT send the original file, it will not be uploaded, represented in the\nUI, or represented in the [upload data store][data].  Effectively, the original file will be transformed into\nthe specified number of scaled versions.  However, it will still be accessible via any [API methods][api]\nthat require access to the original file for your convenience.  For example, calling [`getFile`][getfile]\non a scaled image will still return the original file.\n\n\n### Hide Scaled Files in UI Mode\nIf you do not want scaled versions to be represented in Fine Uploader UI's file list, simply set the Fine Uploader\nUI [`scaling.hideScaled` option][hidescaled] to `true`.  Note that this will _only_ ensure the scaled versions\nare not displayed in the UI.  They will still be uploaded and represented in the [upload data store][data].\nSince they will not be represented in the UI, this means that they cannot be deleted, canceled, or manually retried\nvia Fine Uploader's default UI.  Failures of these scaled versions will obviously not be apparent to users by\ndefault if you elect to hide them from the UI.\n\n\n### Include EXIF Headers in Scaled Images\nIf you set the [`scaling.includeExif` option][exif] to `true`, Fine Uploader will insert the EXIF header from the\noriginal image into each scaled image.  This option is ignored unless the original image AND the target (scaled)\nimage are both of type \"image/jpeg\".\n\n```javascript\nvar uploader = new qq.FineUploader({\n    ...\n    scaling: {\n        includeExif: true,\n\n        sizes: [\n            {name: \"small\", maxSize: 100},\n            {name: \"medium\", maxSize: 300}\n        ]\n    }\n});\n```\n\nIn the above example, EXIF data from the original image will be inserted into both of the scaled images.\nNote that, by default, the scaled images will also be re-oriented according to the parsed Orientation tag in the\noriginal image.  If you don't want Fine Uploader to do this, you must set the [`scaling.orient` option][orient]\nto `false`.\n\n\n### Supported File Types\nScaling can occur on JPEGs, BMPs, GIFs, and PNGs.  TIFFs are also supported, but only in Safari.  The output\ntype is generally limited to JPEG and PNG.  If the original file type happens to be anything other than a JPEG,\nthe output type will default to PNG.  JPEG original files will be converted to scaled JPEGs by default.\nYou can override this via the [`scaling` option][scaling], but you should probably not, unless you know\nwhat you are doing.\n\n\n### Tracking Scaled Files Server-Side\nFine Uploader will send request parameters along with some scaled files to make it easy for you to connect them to\nthe original/parent file server-side and group them together.  \"qqparentuuid\" and \"qqparentsize\" parameters will be sent\nwith each scaled image upload request.  These parameters link the scaled image to the parent/original image.  Note that\nthe parent images will include a \"qquuid\" request parameter instead.  If you are using [Fine Uploader S3][s3] or\n[Azure][azure], these parameters will be associated with the file in your bucket or blob container.\n\n\n### Using a third-party library to resize images\n\nFine Uploader's internal image resize code delegates to the `drawImage` method on the browser's native `CanvasRenderingContext2D` object.\nThis object is used to manipulate a `<canvas>` element, which represents a submitted image `File` or `Blob`.\nMost browsers use linear interpolation when resizing images. This can lead to extreme aliasing and moire patterns\nwhich is a deal breaker for anyone resizing images for art/photo galleries, albums, etc.\nThese kinds of artifacts are impossible to remove after the fact.\n\nIf speed is most important, and precise scaled image generation is _not_ paramount, you should continue to use Fine Uploader's\ninternal scaling implementation. However, if you want to generate higher quality scaled images for upload, you should\ninstead use a third-party library to resize submitted image files, such as [pica] or [limby-resize]. As of version 5.10 of\nFine Uploader, it is extremely easy to integrate such a plug-in into this library. In fact, Fine Uploader will continue\nto properly orient the submitted image file and then pass a properly sized `<canvas>` to the image scaling library of\nyour choice to receive the resized image file, along with the original full-sized image file drawn onto a `<canvas>` for reference.\nThe only caveat is that, due to issues with scaling larger images in iOS, you may need to continue to use\nFine Uploader's internal scaling algorithm for that particular OS, as other third-party scaling libraries\nmost likely do _not_ contain logic to handle this complex case. Luckily, that is easy to account for as well.\n\nIf you'd like to, for example, use pica to generate higher-quality scaled images, simply pull pica into your project,\nand contribute a [`scaling.customResizer` function][customResizer], like so:\n\n```javascript\nscaling: {\n    customResizer: !qq.ios() && function(resizeInfo) {\n        return new Promise(function(resolve, reject) {\n            pica.resizeCanvas(resizeInfo.sourceCanvas, resizeInfo.targetCanvas, {}, resolve)\n        })\n    },\n    ...\n}\n```\n\nThat's it! The above code will result in a higher-quality scaled image, and pica even pushes resizing logic off to a\n[web worker][webworkers] to reduce strain on the UI thread.\n\n\n### Notices\n* Do not set the [`scaling.hideScaled` option][hidescaled] to `true` AND the [`scaling.sendOriginal` option][sendoriginal]\nto `false` at the same time.  This will result in no files being represented in the UI for images that are scalable.\n\n* Scaled files hidden from the UI when the [`scaling.hideScaled` option][hidescaled] is set to `true` cannot be\ncanceled, manually retried, or deleted via the UI, since they are not represented in the UI.\n\n* Be very careful when specifying a value for [`scaling.defaultType`][defaulttype], or a `type` property for one of your\n[`scaling.sizes` objects][sizes].  The only safe values are `null` (the default), \"image/jpeg\", and \"image/png\".  Other\ntarget types may not be supported by all browsers.\n\n* Be careful if you set the [`validation.itemLimit` option][itemlimit].  Each Scaled image file will count towards\nthis item limit.\n\n* Client-side scaling and EXIF header re-insertion operations can potentially be very resource-intensive.\nPlease keep this in mind before you enable this feature, taking into account your user base and application goals.\n\n* iOS limits the size of a `<canvas>` to  about 5 megapixels.  Any image larger than this will be rendered as a\nblank `<canvas>`.  This affects scaling as Fine Uploader uses `<canvas>` in part of the scaling process.  To\novercome this issue, Fine Uploader will further downsample scaled images that exceed this limit.  You can check to\nsee if such a limitation is known by examining the `qq.supportedFeatures.unlimitedScaledImageSize` flag.\n\n* The scaled file names will be sent with the upload request as the `qqfilename` parameter.\nBe sure to read this parameter when naming your file server-side.\n\n\n### Supported Browsers\nThis feature is supported on all browsers other than IE9 and older, Android 2.4 or older, and Safari 5.1 or older.\nNote that Android's stock browser is also not supported, due to multiple long-standing bugs in that browser with\nBlobs and data URIs.  Chrome on Android is supported, though.\n\n{% endmarkdown %}\n{% endblock %}\n"
  },
  {
    "path": "docs/features/session.jmd",
    "content": "{% extends \"_templates/base.html\" %}\n{% set page_title = \"Initial File List\" %}\n{% block sidebar %}\n{{ api_links(options=['deleteFile.endpoint', 'deleteFile.params', 'session'], events=['sessionRequestComplete'], methods=['addInitialFiles']) }}\n{% endblock %}\n{% block content %}\n{% markdown %}\n# Initial File List {: .page-header }\n\n\n### Summary\n\nThis feature is targeted at integrators with an application that requires a list of files from a previous session to be\nfed into the uploader during initialization.  If enabled, Fine Uploader will ask your server for metadata about file items\npreviously uploaded.  All items returned from your server will be loaded into Fine Uploader, and you will be able to\noptionally delete these files.  All relevant API methods can be used on these pre-loaded files.  If you are using\nFine Uploader UI, they will be displayed as successfully uploaded files as well.\n\nThe API includes a [`session` option](../api/options.html#session)\nand a [`sessionRequestComplete` event](../api/events.html#sessionRequestComplete), and an [`addInitialFiles` method](../api/methods.html#addInitialFiles).\n\n\n### Fine Uploader Core Support\n\nAfter the array of file items has been retrieved from the server, all valid items will be loaded into the uploader\ninstance.  Internal data structures will be updated with this data, making all relevant API methods possible, such as\n`getName`, `getUuid`, `getUploads`, `deleteFile`, etc.\n\nAll loaded file items will include a [status of `qq.status.UPLOAD_SUCCESSFUL`](statistics-and-status-updates.html).\nFine Uploader will assign IDs to each file item as well.  Also note that file items loaded into Fine Uploader will be\ncounted when determining if you have reached your [`validation.itemLimit`](validation.html).\n\n\n### Fine Uploader UI Support\n\nIn addition to everything described in the above core session, all files loaded into Fine Uploader on startup\n(or after a reset) will be displayed in Fine Uploader UI as successfully uploaded files.  If the [delete file feature](delete.html)\nis enabled, a delete button will appear next to each file as well.\n\n\n### File Uploader S3 Support\n\nSupport for S3 with or without UI mode is the same, except an additional property for each file item is required: `s3Key`.\nThe value of this property must be the current key of the file in your S3 bucket.\n\n### File Uploader Azure Support\n\nSupport for Azure with or without UI mode is the same, except an additional property for each file item is required: `blobName`.\nThe value of this property must be the current name of the Azure blob.\n\n\n### Server\n\nIf you have specified a value for the `session.endpoint` option, Fine Uploader will send a GET request to your endpoint\non startup (and optionally when the instance is reset).  The response to this request must be valid JSON.  More\nspecifically, it must be a JSON array containing `Object`s for each file to be fed into the uploader.\n\nHere are the following properties of each `Object` that Fine Uploader recognizes (* = required):\n\n* *name: `String` - Name of the file.\n* *uuid: `String` - UUID of the file.\n* size: `Number` - Size of the file, in bytes.\n* deleteFileEndpoint: `String` - Endpoint for the associated delete file request.  If omitted, the `deleteFile.endpoint` is used.\n* deleteFileParams: `Object` - Parameters to send along with the associated delete file request.  If omitted, `deleteFile.params` is used.\n* thumbnailUrl: `String` - URL of an image to display next to the file.\n* *s3Key: `String` - Key of the file in your S3 bucket.  Only required if using Fine Uploader S3.\n* *s3Bucket: `String` - Name of the bucket where the file is stored in S3.  Only required if using Fine Uploader S3 and if the bucket cannot be determined by examining the endpoint URL (such as when routing through a CDN).\n* *blobName: `String` - Name of the file in your Azure Blob Storage container.  Only required if using Fine Uploader Azure.\n\nThe response will be converted into a JavaScript `Array` and passed to your `sessionRequestComplete` event handler.\nSo, any non-standard object properties passed with your server response will also be passed to your event handler.\n\nAdditionally:\n\n* Cross-origin endpoints are supported in all browsers.\n* Your server must respond with a status of 200.\n\n{% endmarkdown %}\n{% endblock %}\n"
  },
  {
    "path": "docs/features/statistics-and-status-updates.jmd",
    "content": "{% extends \"_templates/base.html\" %}\n{% set page_title = \"Statistics and Status Updates\" %}\n{% block sidebar %}\n{{ api_links(methods=['getUploads', 'setStatus'], events=['statusChange']) }}\n{% endblock %}\n{% block content %}\n{% markdown %}\n\n# Statistics and Status Updates {: .page-header }\n\nFine Uploader provides API methods and other options to facilitate statistics\nand status updates on your uploads.\n\nWith this, we can register a callback that allows us to be notified\nwhen any submitted item changes status.  The concept is simple: this will be\ninvoked whenever any submitted file changes state. The ID, old status, and the\nnew status will be included in the callback parameters.\n\nThe status values correspond to those found in the `qq.status` object.\nFor reference, here are valid status values:\n\n* `SUBMITTING` - Selected file is in the process of being submitted to the uploader.  Validation checks occur during this phase.\n* `SUBMITTED` - Selected file has been successfully submitted to the uploader.  In UI mode, it is also now represented in the DOM.\n* `QUEUED` - Uploads are in progress, but this one has not yet started due to lack of available connections.   It is waiting in line for an available connection before an attempt is made to upload it.\n* `UPLOADING` - File is currently uploading (in progress).\n* `UPLOAD_FINALIZING` - All bytes have been sent for all chunks and we are waiting for a final response from the server (such as a response to the chunking.success request).\n* `UPLOAD_RETRYING` - The state when an upload retry is about to occur, just before the auto retry waiting period starts.\n* `UPLOAD_FAILED` - The upload has officially failed to upload, after all auto-retry attempts have been exhausted.\n* `UPLOAD_SUCCESSFUL` - The upload has officially succeeded.\n* `CANCELED` - The upload has been canceled.\n* `REJECTED` - The submitted file has failed validation, either via the internal validation checks, or via a `validate`, `validateBatch`, or `submit` event callback.\n* `DELETED` - The file has been successfully deleted from the server.\n* `DELETING` - A delete attempt is in progress.\n* `DELETE_FAILED` - The last delete attempt failed.\n* `PAUSED` - The file was in progress, but is now paused.\n\n----\n\n### Examples\n\n#### Simple status query\n\nThe API method allows you the retrieve information about all items submitted to\nthe uploader. If you invoke this method without any arguments, information on\nall items will be returned.  For example, suppose we have submitted only two\nfiles to the uploader, and both have uploaded successfully. If we wanted to retrieve\ninformation about these files, the following code should be executed.  Note that I\nhave illustrated the expected return value as well:\n\n```javascript\nvar uploads = myUploader.getUploads();\n\n//the uploads return value will look like this:\n[\n  {\n    id: 0,\n    name: \"some_name\",\n    size: 12345,\n    uuid: \"some_uuid\",\n    status: \"upload successful\"\n  },\n  {\n    id: 1,\n    name: \"some_other_name\",\n    size: 67890,\n    uuid: \"some_other_uuid\",\n    status: \"upload successful\"\n  }\n]\n\n```\n\nNow, suppose we delete the second files via Fine Uploader. If we invoke a new-argument getUploads() again,\nwe receive the following in return:\n\n```javascript\nvar uploads = myUploader.getUploads();\n\n//the uploads return value will look like this:\n[\n  {\n    id: 0,\n    name: \"some_name\",\n    size: 12345,\n    uuid: \"some_uuid\",\n    status: \"upload successful\"\n  },\n  {\n    id: 1,\n    name: \"some_other_name\",\n    size: 67890,\n    uuid: \"some_other_uuid\",\n    status: \"deleted\"\n  }\n]\n```\n\n\n#### Filtering results\n\nNote that you can filter by ID(s) OR UUID(s) OR status.  You can include multiple\nID, UUID, or status values as an array as well. All valid status values\nare defined in the `qq.status` object. Also, please note that the `size` property\nwill only be included if the user agent supports the File API (that is, if\nqq.supportedFeatures.canDetermineSize evaluates to true).\n\nSuppose we only want to compile a list of all deleted files for this instance, we can do this:\n\n```javascript\nvar uploads = myUploader.getUploads({\n  status: qq.status.DELETED\n});\n\n//the uploads return value will look like this:\n[\n  {\n    id: 1,\n    name: \"some_other_name\",\n    size: 67890,\n    uuid: \"some_other_uuid\",\n    status: \"deleted\"\n  }\n]\n```\n\nNow, suppose we have submitted another file, but we have canceled it.  Maybe we only want\ninformation about canceled OR deleted items:\n\n```javascript\nvar uploads = myUploader.getUploads({\n  status: [qq.status.DELETED, qq.status.CANCELED]\n});\n\n//the uploads return value will look like this:\n[\n  {\n    id: 1,\n    name: \"some_other_name\",\n    size: 67890,\n    uuid: \"some_other_uuid\",\n    status: \"deleted\"\n  },\n    {\n    id: 2,\n    name: \"some_other_name_2\",\n    size: 11111,\n    uuid: \"some_other_uuid2\",\n    status: \"canceled\"\n  }\n]\n```\n\n{{ alert(\"getUploads returns an array only when there is a potential for the operation to return more than\none file in the result set. This excludes queries for a specific, single ID or UUID. All other queries will\nreturn an array.\", \"info\", \"Note:\") }}\n\n\n#### Determine if all submitted files are \"done\"\n\nHow can I determine if all of my uploads are complete?  This is very much a frequently asked question.\nLuckily, is quite simple to make this determination by contributing [an `onAllComplete` event handler][allcomplete].\n\n[allcomplete]: ../api/events.html#allComplete\n\n{% endmarkdown %}\n{% endblock %}\n"
  },
  {
    "path": "docs/features/styling.jmd",
    "content": "{% extends \"_templates/base.html\" %}\n{% set page_title = \"Styling\" %}\n{% block sidebar %}\n{{ api_links(options=['messages', 'messages-ui', 'showConfirm-ui', 'showMessage-ui', 'showPrompt-ui']) }}\n{% endblock %}\n{% block content %}\n{% markdown %}\n# Styling {: .page-header }\n\n{{ alert(\n\"\"\"Templating changed drastically in version 4.0.  If you are upgrading from a 3.x version to a 4.x version\nyou will want to read the [upgrading to 4.x document](../upgrading-to-4.html) as well.\"\"\", \"warn\") }}\n\n{{ alert(\n\"\"\"If you are making use of ES6 modules, CommonJS, or AMD to import Fine Uploader into your project, please see\nthe [modules feature page](modules.html) for information on the name changes to the stylesheets in the distributed\nlib directory.\"\"\") }}\n\n\nThis document applies to Fine Uploader UI mode only, and aims to help you customize the default UI using\nthe built-in templating feature.\n\n### Templates\n\nSome defaults templates are bundled with each version in the `templates` directory.  In the future,\nalternate templates may be included as well.  Note that you must include a template in your markup/document\nwhen using Fine Uploader UI.  Everything should work just fine if you simply use the provided templates as is,\nwithout changing anything.  However, you can certainly customize any of the default templates to match your\nweb application's look and feel.\n\nThe templates are, by default, included in a `<script>` tag with the `type` attribute set to \"text/template\".\nThe template does not have to be included in a `<script>` tag.  For example, you may instead include the template\nin a hidden `<div>`.  Fine Uploader simple needs to be able to locate the template element container in the document.\n\n{{ alert(\n\"\"\"The CSS classes included in template elements that end with '-selector' are used internally by Fine Uploader UI\nfor the purposes of element selection only.  No styles are attached to these classes.  Other CSS classes that DO NOT\nend with '-selector' are used to style these elements.  You can certainly remove the non-selector class if you\nwant to contribute your own styles, but you should only remove the selector class if you no longer want Fine\nUploader UI to track/address the associated template element.\"\"\"\n)}}\n\n#### default.html\n\nThe \"default\" template bundled with Fine Uploader contains, more or less, a very basic UI for Fine Uploader. There are\nno thumbnails/previews generated when using this template. You may use the legacy fine-uploader.css file or the new/modern\nfine-uploader-new.css file.\n\nThis template also renders a visible drop zone, but only with the fine-uploader-new.css file. The fine-uploader-new.css\nfile will include a customizable \"drop files\" message in the background of the drop zone if the current browser supports\nfile dropping.\n\n#### simple-thumbnails.html\n\nThis template is similar to \"default\", except it will render previews of images and placeholders for non-previewable images\n(provided you make preview images accessible via the `template.placeholders` option). There are several instances of this\ntemplate in use (both customized and non-customized) at http://fineuploader.com/demos. You may use the legacy fine-uploader.css\nfile or the new/modern fine-uploader-new.css file.\n\nThis template also renders a visible drop zone, but only with the fine-uploader-new.css file. The fine-uploader-new.css\nfile will include a customizable \"drop files\" message in the background of the drop zone if the current browser supports\nfile dropping.\n\n#### gallery.html\n\nThis is a template that represents each submitted file as a \"tile\". You can see a couple examples of this template in action\non http://fineuploader.com/demo. The S3 example notably uses a modified version of the gallery template. You *must* use the\nfine-uploader-gallery.css file, and you also must be sure to include all \".gif\" files packaged with the uploader.\nThe gallery view uses icons to represent buttons (such as retry/pause/continue/delete/cancel) in almost all cases.\n\nLike the fine-uploader-new.css file, the fine-uploader-gallery.css file will include a \"drop files\" message in the background\nof the visible drop zone if the current browser supports file dropping.\n\nWhen using the fine-uploader-gallery.css file, you *must* ensure that the container element for your template contains a\n\"qq-gallery\" class. This is already included in the proper location in the gallery.html template bundled with the library.\n\n\n#### Re-styling the progress bar\n\nDon't like [the default progress bar](progress-bars.html#per-file-progress) included with Fine Uploader's CSS file?\nNo problem!  You can easily use, for example, a Bootstrap-styled progress bar.\nJust [include Bootstrap](http://getbootstrap.com/) in your project and change this:\n\n```html\n<div class=\"qq-progress-bar-container-selector\">\n    <div class=\"qq-progress-bar-selector qq-progress-bar\" role=\"progressbar\" aria-valuenow=\"0\" aria-valuemin=\"0\" aria-valuemax=\"100\"></div>\n</div>\n```\n\nTo this:\n\n```html\n<div class=\"progress qq-progress-bar-container-selector\">\n    <div class=\"bar qq-progress-bar-selector\" role=\"progressbar\" aria-valuenow=\"0\" aria-valuemin=\"0\" aria-valuemax=\"100\"></div>\n</div>\n```\n\nYou can do the same for the [total progress bar](progress-bars.html#total-progress).\n\n\n#### Omitting a portion of the template that does not apply to your app\n\nYou can omit any element of the template that does not apply to your uploader except for:\n\n* The parent div that contains the contents of the template (qq-uploader-selector element)\n* The file list container element (qq-upload-list-selector element)\n* The immediate child container of the file list container element.  You can certainly change the type of tag here though.\n\nOtherwise, any portion of the template can simply be omitted.  For example, if you don't want to utilize the edit\nfilename feature, simply omit any elements with class names that start with qq-edit-filename.  If you don't want\nto have the delete button appear after a successful upload, omit the qq-upload-delete-selector element.  If you don't\nwant the progress bar at all, omit the qq-progress-bar elements, etc, etc.\n\n\n#### Changing any default text\n\nAny of the text nodes in the template can be changed to match your language or preferences.  In some cases, Fine\nUploader UI dynamically sets text, such as with the qq-upload-status-text-selector and qq-upload-size-selector\nelements.  See the [`text` option](../api/options-ui.html#text-option) for adjusting these types of strings.\n\n\n#### Re-arranging the order of the template elements\n\nYou may also change the order of the elements in the template.  Note that you should not move file-related elements\nout of the qq-upload-list-selector container or it's immediate child container though.\n\n\n#### Moving the file list to an alternate location in the DOM\n\nIf you would like to locate the file list (contents of the qq-upload-list-selector element) elsewhere in your document,\nyou may do so via the [`listElement` option](../api/options-ui.html) which allows you to specify an existing\ncontainer element where Fine Uploader UI will render the file list item elements.\n\n\n#### Customizing the buttons/links\n\nIt is trivial to re-style the delete, cancel, retry, and upload buttons.  For example, you can utilize Bootstrap's\nbutton styling to re-skin the upload button by changing this:\n\n```html\n<div class=\"qq-upload-button-selector qq-upload-button\">\n    <div>Upload a file</div>\n</div>\n```\n\nto this:\n\n```html\n<div class=\"qq-upload-button-selector btn\">\n    <div>Upload a file</div>\n</div>\n```\n\nNote that you cannot use a `<button>` element if you plan on supporting Internet Explorer, as this will not trigger\nthe file chooser dialog.\n\n\n#### Including any other custom elements in your template\n\nSuppose you want to include a link to the uploaded file next to each file item element.  You can modify the template\nto include a styled anchor link, hidden initially, and then show the anchor and set its `href` attribute\nin your `complete` event handler, once you know the path of the file on your server.\n\nTo do this, the file list portion of the template can be modified to include this link as the last child:\n\n```html\n<ul class=\"qq-upload-list-selector qq-upload-list\" aria-live=\"polite\" aria-relevant=\"additions removals\">\n    <li>\n        ...\n        <button type=\"button\" class=\"view-btn hide btn\">View</button>\n    </li>\n</ul>\n```\n\nYour `complete` event handler would look something like this:\n\n```javascript\nvar uploader = new qq.FineUploader({\n    /* other required config options left out for brevity */\n\n    callbacks: {\n        onComplete: function(id, name, response) {\n            var serverPathToFile = response.filePath,\n                fileItem = this.getItemByFileId(id);\n\n            if (response.success) {\n                var viewBtn = qq(fileItem).getByClass(\"view-btn\")[0];\n\n                viewBtn.setAttribute(\"href\", serverPathToFile);\n                qq(viewBtn).removeClass(\"hide\");\n            }\n        }\n    }\n});\n```\n\n{{ alert(\n\"\"\"You cannot specify an `Element` as an option value if it only exists in a template.  This is due to the\nfact that the options are evaluated before the template is rendered.  For example, you cannot specify an\n[extra button](extra-buttons.html) that is declared in a template.\"\"\"\n)}}\n\n\n\n### `classes` option\n\nA `classes` option allows you to change some default class names that Fine Uploader may add to template\nelements dynamically/on-demand.  The following `classes` option properties exist in Fine Uploader UI mode:\n\n{% macro classes_table(rows, title=None) -%}\n{{ code_table((\"Property Name\", \"Default CSS\", \"Description\"), rows, title) }}\n{%- endmacro %}\n\n{{ classes_table(\n    (\n        (\"buttonFocus\", \"qq-upload-button-focus\", \"Added to any upload button tracked by Fine Uploader when the\n        button receives focus.\"),\n        (\"buttonHover\", \"qq-upload-button-hover\", \"Added to any upload button tracked by Fine Uploader when a\n        mouse cursor hovers over the button.\"),\n        (\"dropActive\", \"qq-upload-drop-area-active\", \"Added to the drop area container when an item\n        has entered the drop zone.\"),\n        (\"editable\", \"qq-editable\", \"Added to the file name element when the file name may be edited.\"),\n        (\"fail\", \"qq-upload-fail\", \"Added to the file item container after a completely failed upload.\"),\n        (\"hide\", \"qq-hide\", \"Added whenever an item should no longer be visible.\"),\n        (\"retryable\", \"qq-upload-retryable\", \"Added to the file item container after a failed upload attempt\n        if the item is eligible for a retry.\"),\n        (\"retrying\", \"qq-upload-retrying\", \"Added to the file item container during a retry attempt.\"),\n        (\"success\", \"qq-upload-success\", \"Added to the file item container after a successful upload.\"),\n    )\n) }}\n\n\n### Dialogs\n\nFine Uploader UI mode -- by default -- uses the native browser implementations\nof alerts, confirms, and messages to show user notifications when necessary.\nMore than likely, you are going to want to override these. To provide your own\ndialogs just provide your own functions for the `showMessage`,  `showConfirm`,\nand/or `showPrompt` options.\n\n{{ alert(\n\"\"\"For a tutorial on integrating the third-party modal dialog library [alertify.js](http://fabien-d.github.io/alertify.js/)\nwith Fine Uploader's dialogs check out: [Alertify your Notifications and Dialogs](http://wp.me/p3FpYP-5I).\"\"\"\n)}}\n\n* `showMessage: function(message) {...}` - You may want to change the default alert dialog implementation and messages\nas you see fit.  This is possible by overriding the `showMessage` function option.  The default `showMessage` function\nsimply invokes `alert` with the message text.  One instance in which this is used is when the user attempts to select an\ninvalid file for upload.  There are general message types with default text that can be overriden as well.\n\n* `showConfirm: function(message) {...}` - This function is used to display a confirm dialog.  One\nsuch feature that optionally uses this is the `deleteFile` feature. Note that **this\nis a promissory callback**, meaning it requires a [promise](async-tasks-and-promises.html) to\nbe returned.The default implementation uses `window.confirm`, but you\nmay override this with something a bit nicer, if you choose.  The okCallback will be executed if the user clicks \"ok\" and the\n`cancelCallback` if the user clicks \"cancel\".  The `cancelCallback` is optional, but the `okCallback` is required.\n\n* `showPrompt: function(message, defaultValue) {...}` - This function is used to prompt the user for a value.  Note that **this\nis a promissory callback**, meaning it requires a [promise](async-tasks-and-promises.html) to be returned.  The [promise documentation](features/async-tasks-and-promises.html)\nincludes a simple example that overrides this default implementation using bootbox.  The default implementation here simply\nuses `window.prompt`.\n\n\n#### Support for the HTML 5.1 `<dialog>` element\nFine Uploader UI v5.2 will include support for [the `<dialog>` element, which is part of the HTML 5.1 specification](http://blog.teamtreehouse.com/a-preview-of-the-new-dialog-element).\nFor any browsers that support this element, you may replace all message, confirm, and prompt alerts with\n`<dialog>` elements.  Fine Uploader will look in your template for the presence of a `<dialog>` element for\neach type of message and render it at the appropriate time instead of the `alert` dialog if it finds a match\nAND if the current browser has support for the `<dialog>` element.\n\nPlease see [one of the template file examples](https://github.com/FineUploader/fine-uploader/tree/master/client/html/templates)\nfor an example of the `<dialog>` elements that must be present in your template if you'd like to make use of\nthis new native dialog in your uploader instance.  Including the proper markup in your template is all that is\nrequired of you - Fine Uploader will take care of the showing, hiding, and all other logic required to\ndetermine if a `<dialog>` should be used.  Fine Uploader will default back to an `alert` if the browser does\nnot support the `<dialog>` element.  If a `<dialog>` element is rendered, it will be displayed as a modal dialog.\n\nYou may also continue to override the `showMessage`, `showConfirm`, and/or `showPrompt` methods to display your\nown dialog instead.\n\n{% endmarkdown %}\n{% endblock %}\n"
  },
  {
    "path": "docs/features/thumbnails.jmd",
    "content": "{% extends \"_templates/base.html\" %}\n{% set page_title = \"Previews & Thumbnails\" %}\n{% block sidebar %}\n{{ api_links(options=['thumbnails-ui', 'template-ui', ], methods=['drawThumbnail']) }}\n{% endblock %}\n{% block content %}\n{% markdown %}\n\n[customResizer]: ../api/options-ui.html#thumbnails.customResizer\n[webworkers]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers\n\n\n# Previews & Thumbnails {: .page-header }\n\n## Summary\nVersion 4.0 brings native support for thumbnail generation for UI and core mode integrators.\nIn modern browsers, Fine Uploader has the ability to generate image previews for dropped or selected files\nbefore the upload even begins.  For older browsers (IE9 and older) Fine Uploader will display a thumbnail\nassociated with an uploaded file, provided your server response contains a URL for the thumbnail.  You also have\nthe option to provide alternate thumbnails or thumbnails for non-images, such as videos, in your server response.\n\nFine Uploader will even go the extra mile, whenever possible, and parse the EXIF data for dropped or selected\nJPEGs in order to ensure any generated previews are oriented correctly when displayed.  You can also ask Fine Uploader\nto enforce a maximum height/width for thumbnails in all browsers.\n\nFinally, you may elect to have Fine Uploader display a placeholder image before the thumbnail is displayed, along\nwith a placeholder if the thumbnail cannot be displayed for any reason.  The library ships with some default\nplaceholders, but you can easily replace these with your own.\n\n\n## Getting Started\nThe `templates` folder bundled with Fine Uploader contains a `simple-thumbnails.html` template document\nthat you may use to get up and running with the thumbnail feature.  If you are a Fine Uploader UI user,\nsimply including using this document or including the template `<script>` in your existing document\nin place of the `default.html` template is all that is required to turn on the thumbnails feature.\n\nThe simple thumbnails template bundled with the library will display thumbnails between the spinner and the\nfile name.  You, of course, can customize the location, along with any other aspect of the template.  Notice\nthat the thumbnail will always be house in an `<img>` element.  This `<img>` tag has a `qq-max-size` attribute\nwhich specifies a default maximum size to enforce for all thumbnails.  A `qq-server-scale` attribute is also\npresent by default.  This signals Fine Uploader UI to always ensure any thumbnails display\nfrom server responses are scaled as well.  You can remove this attribute or set the value to \"false\" if any thumbnails\nreturned in your upload responses are already appropriately scaled.\n\nIf you are not a Fine Uploader UI user, and instead are building your own highly-customized UI utilizing\nFine Uploader Core, you can still easily display thumbnails/previews via the new `drawThumbnail` API method.\nThis method supports rendering optionally scaled thumbnails/previews on `<canvas>` elements (where supported)\nand of course `<img>` elements.\n\n\n## Supporting Modern Browsers\nModern browsers include Chrome, Firefox, IE10+, Safari 6+, iOS 6+, Android 4+, & Opera 15+.  In these browsers,\nimage previews can be generated immediately after the file has been selected or dropped.  The images that can be\npreviewed depend on the browser.  All of these browsers can render previews for JPEG, GIF, and PNGs.  Safari\ncan also render TIFF previews.\n\n### UI mode\n\n#### Previews\nAs mentioned previously Fine Uploader can generate previews for popular image file formats in modern browsers.\nIn addition to this, JPEGs will be displayed with the correct orientation as Fine Uploader includes an file parser\nthat will read the Orientation tag in the EXIF header and render the image appropriately based on this tag's value.\nFurthermore, the preview will be scaled according to the value (in the template) of the `<img>` tag's\n`qq-max-size` attribute.  When scaling previews, Fine Uploader will draw the preview onto a `<canvas>` internally\nin order to scale the preview without resorting to CSS or inline `style` attribute adjustments.  Once the preview\nis scaled on the `<canvas>`, it is then transferred to the `<img>` tag mentioned in the template.\n\n#### Thumbnails from server response\nIf the server's response to Fine Uploader's upload request (or `uploadSuccess` for S3 endpoints) contains a\n`thumbnailUrl` property in the JSON response, Fine Uploader will attempt to render the path in the template's `<img>`\nelement for the associated successfully uploaded file.  If a preview already exists for the file, it will be replaced\nwith the server-returned thumbnail, but only if the thumbnail can be successfully displayed in the browser.  This\nthumbnail will be scaled according to the value of the `<img>` tag's `qq-max-size` attribute, but only if the `<img>`\ntag also contains a `qq-server-scale` attribute.  The provided simple thumbnails template\nincludes both of these attributes, but you can remove the latter one (or set it's value to false) if your server\nis going to generate an appropriately scaled thumbnail at the returned `thumbnailUrl`.\n\nNote that JPEGs grabbed from the URL returned in your server's upload response will not be re-oriented, so your server\nmust either be sure to create thumbnails that are oriented correctly.  Since modern browsers already generate correctly\noriented JPEG previews client-side, there is generally no need to worry about this.  However, you should take care to\nnot blindly return a `thumbnailUrl` for images that are already previewable in modern browsers unless your server\nintends to orient them correctly first.\n\n{{ alert(\n\"\"\"You can determine if the current browser is capable of previewing images by checking the value of\n`qq.supportedFeatures.imagePreviews`.\"\"\"\n)}}\n\n#### Performance considerations\nFor browsers that support client-generated image previews (`qq.supportedFeatures.imagePreviews === true`), a configurable\npause between template-generated previews is in effect.  This is to prevent the complex process of generating previews\nfrom overwhelming the client machine's CPU for a lengthy amount of time.  Without this limit in place, the browser's\nUI thread runs the risk of blocking, preventing any user interaction (scrolling, etc) until all previews have been\ngenerated.\n\nAlso, you may limit the number of previews generated/scaled via a `thumbnails` option property.  This may be especially\nimportant in Chrome, which seems to have memory management issues when generating thumbnails using `<canvas>`.  In Chrome,\nif enough previews are generated and rendered, the browser will eventually crash.  You can avoid this situation by\nlimiting the number of previews to be generated (say, 50).  This is probably a good limit to set for all browsers, to\nconserve memory.  When the limit has been reached, the \"not available\" placeholder image will be used for all\nsubsequently submitted files.\n\nThe limits described above only apply to modern browsers that are capable of rendering previews via `<canvas>`.  See the\n`thumbnails` option for defaults and further configuration of these limits.\n\n##### Cross-origin thumbnails\nThumbnails returned in the server response may be cross-origin as well.  In that case, Fine Uploader will make a\nbest-effort attempt to scale the thumbnail (if required) using a `<canvas>` internally.  In some browsers, this is\nnot possible as support for image CORS is required in order to transfer the scaled cross-origin image from the `<canvas>`\nto the `<img>` tag.  In browsers where image CORS is not supported (such as IE10) the thumbnail URL will be placed\ndirectly on the template's `<img>` tag and scaled (if required) via inline `style` attribute properties (using\n`max-width` and `max-height`).  If your server returns a cross-origin thumbnail URL, your server must also return\nthe appropriate `Access-Control-Allow-Origin` header in the response, with a value equal to the domain hosting\nthe uploader page.  You can determine the domain server-side by looking at the `Origin` header for the GET request\nthat the browser will send when Fine Uploader attempts to render the thumbnail in the document.\n\n#### Placeholder images\nIf you specify URLs/paths for the [`thumbnails.placeholders.notAvailablePath` and/or `thumbnails.placeholders.waitingPath`](../api/options-ui.html#thumbnailsplaceholders-option)\noptions, Fine Uploader will render a \"waiting for thumbnail/preview\" image until the preview generation attempt has\ncompleted, and a \"image/preview not available\" image if the preview generation attempt has failed.  Placeholders are\nbundled with Fine Uploader.  You can use the bundled placeholders or use your own, but be sure to specify the correct\npath for these placeholders in the Fine Uploader UI options if you want them to be displayed.  It is valid to leave\neither of these options `null` if you don't want a placeholder image to be displayed.\n\nPlaceholders will also be scaled, if the `qq-max-size` attribute on the template's `<img>` tag is specified.  Cross-origin\nplaceholders will be treated the same way as cross-origin server-generated thumbnails.  See the \"Cross-origin thumbnails\"\nsection above for details.  Re-orienting placeholder images is not supported, so, if you provide your own placeholder\nimages, ensure they are already oriented correctly.\n\n#### Using a third-party library to resize images\n\nFine Uploader's internal image resize code delegates to the `drawImage` method on the browser's native `CanvasRenderingContext2D` object.\nThis object is used to manipulate a `<canvas>` element, which represents a submitted image `File` or `Blob`.\nMost browsers use linear interpolation when resizing images. This can lead to extreme aliasing and moire patterns\nwhich may result in lower quality displayed thumbnails.\n\nIf speed is most important, and precise scaled thumbnail generation is _not_ paramount, you should continue to use Fine Uploader's\ninternal scaling implementation. However, if you want to generate higher quality thumbnail images for display, you should\ninstead use a third-party library to resize submitted image files, such as [pica] or [limby-resize]. As of version 5.10 of\nFine Uploader, it is extremely easy to integrate such a plug-in into this library. In fact, Fine Uploader will continue\nto properly orient the submitted image file and then pass a properly sized `<canvas>` to the image scaling library of\nyour choice to receive the resized image file, along with the original full-sized image file drawn onto a `<canvas>` for reference.\nThe only caveat is that, due to issues with scaling larger images in iOS, you will likely need to continue to use\nFine Uploader's internal scaling algorithm for that particular OS, as other third-party scaling libraries\nmost likely do _not_ contain logic to handle this complex case. Luckily, that is easy to account for as well.\n\nIf you'd like to, for example, use pica to generate higher-quality scaled images, simply pull pica into your project,\nand contribute a [`thumbnails.customResizer` function][customResizer], like so:\n\n```javascript\nthumbnails: {\n    customResizer: !qq.ios() && function(resizeInfo) {\n        return new Promise(function(resolve, reject) {\n            pica.resizeCanvas(resizeInfo.sourceCanvas, resizeInfo.targetCanvas, {}, resolve)\n        })\n    },\n    ...\n}\n```\n\nThat's it! The above code will result in a higher-quality scaled thumbnail, and pica even pushes resizing logic off to a\n[web worker][webworkers] to reduce strain on the UI thread.\n\n\n### Core mode\nFor Core mode users that need to create their own highly-customized UI, there is a [`drawThumbnail` API method](../api/methods.html)\nthat will allow you to effortlessly render either a submitted image file's preview or a server-returned thumbnail\non either a `<canvas>` or and `<img>`.  See the [method documentation section](../api/methods.html)\nfor `drawThumbnail` for more details.\n\n{{ alert(\n\"\"\"The `drawThumbnail` method is also, of course, available to UI mode users if you want to, for example, render a\npreview/thumbnail on a `<canvas>` instead of the `<img>` tag in the template.\"\"\"\n)}}\n\n\n## Supporting Older Browsers (IE9 and older)\n\"Older\" browsers include IE9 and older, Safari 5, and Android older than version 4.  In these browsers, client-side\nimage previews are not possible.  However, Fine Uploader provides a way to display thumbnails returned in the server's\nresponse to the upload request.  If you want thumbnails to be displayed in these browsers, your server will have to\neither generate the thumbnail or simply return a public URL for the uploaded image.  Fine Uploader will display\nthe thumbnail if the URL can result in a natively rendered `<img>`.\n\n### UI mode\nIf you want to display thumbnails (after the upload has completed) next to the associated files, your server must\nreturn a `thumbnailUrl` property in its JSON response to the upload (or `uploadSuccess` request for S3 endpoints).\nFine Uploader will scale the thumbnail using inline `style` attribute properties (via `max-width` and `max-height`) if\nthe `qq-max-size` attribute is present on the template's `<img>` template tag.  Re-orientation is not possible\nclient-side.  If you do not want to display thumbnails for these browsers, you can contribute an alternate template\nfor browsers that do not support client-side previews and set the [`template` option](../api/options-ui.html)\naccordingly, based on the capabilities of the current browser.\n\n#### Placeholder images\nPlaceholder images are supported in older browsers as well.  As with thumbnails, placeholder images will be scaled,\nif required, via incline style attribute properties.  If specified, the \"waiting for thumbnail\" placeholder will be\nvisible until the server response is parsed.  If the server response contains a `thumbnailUrl` property in its response,\nand the URL can be rendered in an `<img>` tag, this placeholder will be replaced with the thumbnail.  Otherwise, if\nspecified, the \"not available\" placeholder will be displayed.  See the \"Placeholder images\" section above for more\ndetails.\n\n### Core mode\nFor Core mode users that need to create their own highly-customized UI, there is a [`drawThumbnail` API method](../api/methods.html)\nthat will allow you to effortlessly render either a submitted image file's server-returned thumbnail on an `<img>`.\nNote that you are limited to drawing the thumbnail on an `<img>` in these browsers.  Also, since client-side previews\nare not possible in these browsers, the thumbnail will need to be provided in your server's upload response.\nFine Uploader will scale the thumbnail using inline `style` attribute properties (via `max-width` and `max-height`) if\nyou specify a `maxSize` parameter.  See the [method documentation section](../api/methods.html) for `drawThumbnail`\nfor more details.\n\n{{ alert(\n\"\"\"The `drawThumbnail` method is also, of course, available to UI mode users.\"\"\"\n)}}\n\n\n{% endmarkdown %}\n{% endblock %}\n"
  },
  {
    "path": "docs/features/upload-files.jmd",
    "content": "{% extends \"_templates/base.html\" %}\n{% set page_title = \"Upload Files\" %}\n{% block sidebar %}\n{{ api_links(options=['request', 'request-s3', 'credentials-s3'], methods=['addFiles', 'uploadStoredFiles'], events=['submit', 'submitted', 'complete', 'upload']) }}\n{% endblock %}\n{% block content %}\n{% markdown %}\n\n# Uploading Files {: .page-header }\n\nAssuming you already have a copy of Fine Uploader, first and foremost, you are\ngoing to want to make sure you've read the [Getting Started guide](../quickstart/01-getting-started.html).\n\nIf you feel comfortable from there, we can begin explaining how Fine Uploader\nuploads files, as well as some of the basic options one can set to customize\ntheir uploads.\n\n{{ alert(\n\"\"\"Before you delve any further, note that this section assumes you have a\n(working!) server to handle uploads. If not, then make sure to check out\nthe server-side guidelines for either [traditional](../endpoint_handlers/traditional.html) or [Amazon S3](../endpoint_handlers/amazon-s3.html) upload\nendpoints.\"\"\", \"warn\") }}\n\n### Successfully Uploading\n\nUpload success will occur when the server's response status is 200-204 and the\nrequest body is a JSON string `{ \"success\": \"true\" }`\n\n### Auto/Manual Uploading\n\nFine Uploader can automatically upload files that are added to it, or wait until\nthe user triggers an action to upload. Automatic uploads are turned on or off\nvia the `autoUpload` property of the main options object.\n\nBy default `autoUpload` is enabled.\n\n```javascript\n// Upload automatically\nvar uploader = new qq.FineUploader({\n    /* other required config options left out for brevity */\n\n    request: {\n        endpoint: '/server/upload'\n    }\n});\n```\n\nIf `autoUpload` is set to `false`, then items added to Fine Uploader will be queued until you explicitly trigger\nthe uploads via the API time. Uploads can be triggered by calling the `uploadStoredFiles` method on an instance of Fine Uploader.\n\n<!-- http://stackoverflow.com/questions/16471503/fineuploader-uploads-the-same-file-multiple-times-at-the-same-time -->\n```javascript\n// Manually upload\nvar uploader = new qq.FineUploader({\n    /* other required config options left out for brevity */\n\n    autoUpload: false,\n    request: {\n        endpoint: '/server/upload'\n    }\n});\n\nqq(document.getElementById(\"upload-button\")).attach('click', function() {\n    uploader.uploadStoredFiles();\n});\n```\n\n{% endmarkdown %}\n{% endblock %}\n"
  },
  {
    "path": "docs/features/upload-from-mobile-camera.jmd",
    "content": "{% extends \"_templates/base.html\" %}\n{% set page_title = \"Upload From Mobile Camera\" %}\n{% block sidebar %}\n{{ api_links(options=['camera']) }}\n{% endblock %}\n{% block content %}\n{% markdown %}\n\n# Upload from Mobile Device Cameras {: .page-header }\n\n{{\nalert(\"While uploads are possible from all mobile cameras, this option is relevant for iOS devices only!\nNote that due to limitations related to camera access in iOS, setting this to true will prevent you from selecting\nmultiple files at once.\")\n}}\n\nIf you’d like to easily ensure camera access is available in iOS, your Fine Uploader instance would look something like this:\n\n```javascript\nvar uploader = new qq.FineUploader({\n  element: document.getElementById(\"myFineUploader\"),\n  camera: {\n    ios: true\n  }\n});\n```\n\nThe downside to setting the `camera.ios` property to `true` is that you will not be able to select multiple files at once.\nThis is a limitation of iOS. If you really want to allow users to select multiple files and have camera access,\nyou can create your own `<input type=\"file\" accept=\"image/*;capture=camera\">` element, register an `onchange` handler,\nand pass the input element to Fine Uploader’s [addFiles API method](../api/methods.html#addFiles) (in your onchange handler).\nThis button will be used exclusively for camera access and the button provided by Fine Uploader will be used for multiple file selection.\nYou would, of course, not set the `camera.ios` to `true` in this case. Here’s an example:\n\n```css\n#cameraButtonContainer\n{\n    position: relative;\n    overflow: hidden;\n    direction: ltr;\n    display: none;\n}\n\n#cameraButtonContainer .ios\n{\n    display: block;\n}\n\n#cameraButton\n{\n    position: absolute;\n    right: 0px;\n    top: 0px;\n    font-family: Arial;\n    font-size: 118px;\n    margin: 0px;\n    padding: 0px;\n    cursor: pointer;\n    opacity: 0;\n}\n```\n\n```html\n<div id=\"cameraButtonContainer\" class=\"qq-upload-button\">\n  <div>Camera</div>\n  <input id=\"cameraButton\" type=\"file\" name=\"camera\" accept=\"image/*;capture=camera\">\n</div>\n<div id=\"myFineUploader\"></div>\n```\n\n```javascript\nvar uploader = new qq.FineUploader({\n  element: document.getElementById(\"myFineUploader\");\n});\n\nif (qq.ios()) {\n  qq(document.getElementById(\"cameraButtonContainer\")).addClass('ios');\n\n  qq(document.getElementById(\"cameraButton\")).attach(\"change\", function() {\n    uploader.addFiles(this);\n  });\n}\n```\n\n{% endmarkdown %}\n{% endblock %}\n"
  },
  {
    "path": "docs/features/validation.jmd",
    "content": "{% extends \"_templates/base.html\" %}\n{% set page_title = \"Validation\" %}\n{% block sidebar %}\n{{ api_links(options=['validation'], events=['submit', 'validate', 'validateBatch'], methods=['setItemLimit']) }}\n{% endblock %}\n{% block content %}\n{% markdown %}\n\n# Validation {: .page-header }\n\nFine Uploader's validation abilities allow integrators to strictly define\nwhat sorts of files can and can not be uploaded.\n\nFor each file, the `validate` event callback is invoked, allowing you to enforce your own custom rules.  Then, Fine\nUploader's built-in validators (defined in the options) execute.  If you need access to more information\n(such as the actual file), you can declare a `submit` event callback handler instead. When `submit` is called,\nan ID is available for the file, and you can address it using any of the other API methods that require a file ID.\n\nIf the `validation.stopOnFirstInvalidFile` option is enabled, Fine Uploader will stop processing files once an invalid\nfile has been encountered.\n\nFine Uploader's built-in validators allow you to limit based on file extension, number of files, file size, and\nimage dimensions.  See the `validation` option for more details about these built-in validators.\n\n## Validation on File Size\n\nSize validation is available in File API browsers only. Size can be restricted\nvia the `sizeLimit` or `minSizeLimit` options in the `validation` settings.\n\n## Image Validation\n\nAs of 4.1, the ability to restrict submitted images based on width and height is possible.  See the `validation.image`\noption for specifics.  Keep in mind that this feature was created with processed images in mind, and not images\nsent directly from a camera.  Some browsers may natively report width and height swapped for JPEGs (from cameras) based\non the orientation of the camera.  Those uploading pictures directly from cameras have no control over the\ndimensions of the image, so it doesn't seem to make much sense to impose these restrictions if you expect your users\nto upload images directly via a camera.  In that case, it likely makes more sense to allow the user to scale the image\nfirst (before submitting the file) or simply impose a file size restriction.\n\nAlso, this feature is dependant on the preview generation feature, and therefore is only supported in modern browsers.\nCheck the browser support matrix or the `qq.supportedFeatures.imageValidation` flag for more specific information.\nIf image dimension validation is important to your application, you will also need a server-side fallback to account\nfor older browsers and TIFFs (if allowed, since TIFFs are only previewable in Safari).\n\n## Custom Validation Rules\nYou can further limit files using your own custom rules by including validation logic in a `validate` or `validateBatch`\ncallback event handler.  Your handlers can return `false` to reject the file or files outright.  If you need to perform an\nasync operation to determine if the file is valid, you can return a promise, and either resolve or reject the promise\nonce the validity of the file is known.  See the `validate` and `validateBatch` documentation for more details.\n\n{% endmarkdown %}\n{% endblock %}\n"
  },
  {
    "path": "docs/index.jmd",
    "content": "{% extends \"_templates/base.html\" %}\n{% set page_title = \"Overview\" %}\n{% block content %}\n{% markdown %}\n\n{{ alert(\n\"\"\"Version 5.0 brings some breaking changes.  See the [upgrading to 5.x page](upgrading-to-5.html)\nfor help on upgrading from a 4.x version.\"\"\"\n)}}\n\n# Documentation Overview {: .page-header }\n\nWelcome to Fine Uploader's documentation. This documentation is divided into\ndifferent parts. It's recommended that you first check out the\n[quickstart](quickstart/01-getting-started.html) for Fine Uploader. If you’d rather dive into the\ninternals of Fine Uploader, check out the exhaustive\nFeatures reference or API documentation (see the navigation menu bar at the top).\nFor assistance in getting your upload server set up, check out the docs in\nthe Servers section (also available via the navigation menu).\n\n## Getting Started\n\nThis area of the documentation is mostly prose and examples. It is meant to be\nhuman-readable and accessible to those looking to get started with Fine Uploader.\nFor experts, or developers looking to view more technical documentation, refer\nto the API documentation section.\n\n### Quick-start\n\n#### Traditional / Generic Endpoint Uploader\n\n1. [Getting Started with Fine Uploader](quickstart/01-getting-started.html)\n2. [Setting Fine Uploader options](quickstart/02-setting_options.html)\n3. [Creating an upload server](quickstart/03-setting_up_server.html)\n\n#### Amazon S3 Uploader\n\n1. [Getting Started with Fine Uploader](quickstart/01-getting-started.html)\n2. [Setting Fine Uploader options](quickstart/02-setting_options-s3.html)\n3. [Creating an upload server](quickstart/03-setting_up_server-s3.html)\n\n#### Azure Blob Storage Uploader\n\n1. [Getting Started with Fine Uploader](quickstart/01-getting-started.html)\n2. [Setting Fine Uploader options](quickstart/02-setting_options-azure.html)\n3. [Creating an upload server](quickstart/03-setting_up_server-azure.html)\n\n\n### Fine Uploader Modes\n\n* [Core](modes/core.html) - Most API methods and options available, build your own UI from scratch\n* [UI](modes/ui.html) - Inherits from core, provides additional UI-specific options and API methods. Default customizable UI.\n\n\n### Plug-ins\n\n* [jQuery](integrating/jquery.html) - _Not_ required to use _any_ feature of Fine Uploader. Simply wraps the library as a jQuery plug-in.\n\n\n### Endpoint Handlers (server-side guidelines)\n\n* [Traditional Server](endpoint_handlers/traditional.html) - For upload servers that you control and build.\n* [Amazon S3](endpoint_handlers/amazon-s3.html) - For uploads directly to S3 from the browser.\n* [Azure Blob Storage](endpoint_handlers/azure.html) - For uploads directly to Azure from the browser.\n* [Examples](https://github.com/FineUploader/fine-uploader-server) - GitHub repo of server-side examples to handle all types of Fine Uploader requests.\n\n\n### Additional Information\n\n* [Browser Support](browser-support.html) - Matrix of support for all major features. Also contains information on how to determine features support via JavaScript at runtime using Fine Uploader's API.\n* [Contributing](https://github.com/FineUploader/fine-uploader#contributing) - Guidelines and options for contributing back to this free open source library.\n* [Changelog](http://blog.fineuploader.com/category/changelog/) - High-level summary of all releases.\n* [FAQ](faq.html)\n\n\n### Links\n\n* [Official Site](http://fineuploader.com)\n* [Blog](http://blog.fineuploader.com/)\n* [Support](http://fineuploader.com/support)\n* [GitHub repository](https://github.com/FineUploader/fine-uploader)\n\n{% endmarkdown %}\n{% endblock %}\n"
  },
  {
    "path": "docs/integrating/jquery.jmd",
    "content": "{% extends \"_templates/base.html\" %}\n{% set page_title = \"jQuery Plugin\" %}\n{% block content %}\n{% markdown %}\n\n[button]: ../api/options.html#button\n[callbacks]: ../api/events.html\n[element]: ../api/options-ui.html#element\n[extradropzones]: ../api/options-ui.html#dragAndDrop.extraDropzones\n\n\n# jQuery Plugin Wrapper {: .page-header }\n\n**While Fine Uploader provides wrapper code that integrates with jQuery while still allowing the integrator to choose\nbetween Core and UI modes, [there is no common benefit to using jQuery in the context of Fine Uploader](https://github.com/FineUploader/fine-uploader/issues/1310).**\nHowever, if you prefer to use the jQuery wrapper, this page will serve as a guide, explaining the syntax differences between\nthe jQuery wrapper and the core library (sans jQuery).\n\n{{\n    alert(\"You must include jQuery and the jQuery wrapped version of Fine Uploader on your page if you wish to use jQuery-wrapped Fine Uploader.\")\n}}\n\nThe Fine Uploader jQuery wrapper has a couple of syntactical differences compared to the the unwrapped library.\nAlso, some of the options are defined differently.  Note that all conventions commonly associated with jQuery\nplug-ins are followed faithfully here, so if you have worked with jQuery plug-ins before, this should all be quite intuitive.\n\n\n## Working With HTML elements\n\nWhen using the Fine Uploader jQuery plugin there is no need to specify [the `element` option][element]. Instead,\nusing the jQuery plugin syntax, you simply provide the element (or elements) you would like to associate with your\nFine Uploader instance, like so:\n\n```javascript\n// This will instantiate an instance of Fine Uploader on the page element\n// that has an `id` of \"fine-uploader\".\n$(\"#fine-uploader\").fineUploader({\n    /* options go here */\n});\n```\n\nFine Uploader's jQuery wrapper is smart enough to know you are using jQuery and allows you to\npass in `jQuery` objects instead of `HTMLElement`s or `Node`s. In fact, the wrapper intercepts these jQuery objects\nand converts them to HTML `Element`s for use by the library.\n\nFor example, if you specify [the `button` option][button], the value can be `$(\"#myButton\")`\nrather than `$(\"#myButton\")[0]` or `document.getElementById(\"button\")`.\n\nIf the option takes an array of `HTMLElements`, any item in the array that is a jQuery object will be evaluated and\nall `HTMLElements` associated with that jQuery object will be added to the array when it is passed to Fine Uploader.\nFor instance, if you specify a value for [`extraDropzones`][extradropzones] which is `[$(\".myExtraDropzone\")]`, and the element\ncontains three child elements, then the plugin will pass all three elements to Fine Uploader.\n\n```javascript\n$(\"#fine-uploader\").fineUploader({\n    /* ... */\n    button: $(\"#button\"),\n    dragAndDrop: {\n        extraDropzones: [$(\".dropzones\")]\n    }\n});\n```\n\n\n## Uploader Type\n\nThe Fine Uploader jQuery wrapper still provides integrators with a way to specify whether they are using core or\nUI mode by specifying an `uploaderType` option on instantiation. When the type is `'basic'` then core/basic mode\nis used, otherwise UI mode is assumed.\n\n```javascript\n$(\"#fine-uploader\").fineUploader({\n    uploaderType: 'basic'\n});\n```\n\n\n## Callbacks and Events\n\nThe biggest syntactical difference between the jQuery and non-jQuery plugin is the callback syntax.\nAll callbacks defined in Fine Uploader are available to jQuery plugin users.\n\n{{ alert(\n\"\"\"When using the jQuery plugin, **all** event function handlers are passed an `event` parameter\n as the <strong>first</strong> argument to your callback function.\"\"\", \"info\", \"Note:\") }}\n\n```javascript\n$(\"#fine-uploader\").fineUploader({\n    request: {\n        endpoint: '/upload/endpoint'\n    }\n}).on('error', function (event, id, name, reason) {\n    // do something\n}).on('complete', function (event, id, name, responseJSON) {\n    // do something\n});\n```\n\nYou may use traditional callback handler functions with the jQuery plug-in if you wish.\nIf you do contribute traditional callback functions via [the `callbacks` option][callbacks], you should disregard everything on this\npage when writing code inside of your callback functions as this will bypass the jQuery plug-in entirely.\nAlso, while it is possible to contribute two callbacks of the same type via a jQuery event handler and a `callbacks`\noption, you should be aware that the `callbacks` function will always be evaluated first, and the return value from\nthe `callbacks` function will be accepted instead of the return value from the jQuery event handler if the return\nvalue of the `callbacks` function is non-null.\n\n{{ alert(\n\"\"\"When passing a jQuery object as the value for an option or API method parameter, all elements represented by the selector will\nbe resolved IFF the expected data type is an array. Otherwise, only the first element matched by the selector will be used.\nFor an example of the latter situation please see [case #1375 in the issue tracker](https://github.com/FineUploader/fine-uploader/issues/1375).\n\"\"\", \"info\", \"Note:\") }}\n\n## API Methods\n\nAll public API methods are also accessible when using the jQuery plugin, but, as with callbacks,\nthere are a few syntactical differences.\n\n```javascript\n// To call a method on Fine Uploader, refer to it's jQuery object:\nvar files = $(\"#fine-uploader\").fineUploader('getStoredFiles'):\n\n$(\"#cancel-button\").click(function (event) {\n    // Trivial example to check if the 1st file registered with the uploader\n    var exists = $(\"#fine-uploader\").fineUploader('doesExist', 0);\n});\n```\n\n\n## Endpoint Handlers\n\nThe jQuery plugin cares not about what your endpoint is, but if you plan to use\nthe S3 uploader rather than the traditional uploader, ensure that you are using\nthe right plugin name:\n\n```javascript\n// Fine Uploader\n$(\"#fine-uploader\").fineUploader({ /* options ... */ });\n\n// Fine Uploader S3\n$(\"#fine-uploader\").fineUploaderS3({ /* options ... */ });\n\n// Fine Uploader Azure\n$(\"#fine-uploader\").fineUploaderAzure({ /* options ... */ });\n```\n\n{% endmarkdown %}\n{% endblock %}\n"
  },
  {
    "path": "docs/modes/core.jmd",
    "content": "{% extends \"_templates/base.html\" %}\n{% set page_title = \"Core Mode\" %}\n{% block content %}\n{% markdown %}\n# Fine Uploader Core Mode {: .page-header }\n\n[solo-dnd]: ../features/drag-and-drop.html#drag-and-drop-standalone-module\n[ui]: ui.html\n\nIf you need to design an exceptionally unique upload experience for you users, _and_ require the\npower that Fine Uploader provides in its feature set, core mode is most appropriate. While [UI mode][ui]\nprovides a customizable default UI, core mode is designed to allow you to develop your own UI that builds on\nFine Uploader's rich API and configuration options. There is _also_ a [standalone drag and drop module][solo-dnd]\navailable for you to integrate into your custom UI.\n\nFine Uploader Core mode is defined in the `qq.FineUploaderBasic` module in the code.\nThis is the base module for Fine Uploader, and provides all functions that do not \ninvolve UI components.\n\nFor more information on the events, API methods, and configuration options available to UI mode,\nhave a look at the menu at the top of the page.\n\n{% endmarkdown %}\n{% endblock %}\n"
  },
  {
    "path": "docs/modes/ui.jmd",
    "content": "{% extends \"_templates/base.html\" %}\n{% set page_title = \"UI Mode\" %}\n{% block content %}\n{% markdown %}\n# Fine Uploader UI Mode {: .page-header }\n\n[getstarted]: ../quickstart/01-getting-started.html\n[dnd]: ../features/drag-and-drop.html\n[progress]: ../features/progress-bars.html\n[styling]: ../features/styling.html\n\nFine Uploader \"UI\" mode is the easiest way to [get started][getstarted] with a dependency-free\nFine Uploader. This mode provides a [customizable UI][styling], [drag and drop support][dnd],\n[progress bars][progress], status messages, a file list with color-coded status indicators,\nand other UI niceties. Most developers will decide to use UI mode.\n\nFine Uploader UI mode is defined in the `qq.FineUploader` module in the code.\nIt inherits everything from `qq.FineUploaderBasic`. For more information on the events,\nAPI methods, and configuration options available to UI mode, have a look at the menu at the\ntop of the page.\n\n{% endmarkdown %}\n{% endblock %}\n"
  },
  {
    "path": "docs/quickstart/01-getting-started.jmd",
    "content": "{% extends \"_templates/base.html\" %}\n{% set page_title = \"Getting Started\" %}\n{% block content %}\n{% markdown %}\n# Getting Started with Fine Uploader {: .page-header }\n\nFine Uploader is a pure-JavaScript browser-based file upload library with a long list of features that is unmatched by any other library. The power of Fine Uploader comes from its comprehensive set of options, API methods, and callbacks/events. The menu at the top of this page will give you access to more details regarding these three critical pieces of Fine Uploader. You can also read more about many of the features in the top navigation menu as well.\n\nThis tutorial will serve as a step-by-step guide that you can follow to get Fine Uploader up and running in your project. The tutorial is not comprehensive or meant to cover all aspects of Fine Uploader. For more information, make sure to read through the list of features as well\nas the API documentation. Both of these items (and more) are available in the top nav menu anywhere on this site. Please also see the live demos at [http://fineuploader.com/demos](http://fineuploader.com/demos) and detailed write-ups and integration ideas at [https://blog.fineuploader.com](https://blog.fineuploader.com/).\n\n### 1. Download (or build)\n\nFirst, you will need to download or build Fine Uploader's JS, CSS, image, and template files. You have exactly three different supported options to achieve this. **Choose _one_ of the following options:**\n\n#### Download [Fine Uploader from npm](https://www.npmjs.com/package/fine-uploader)\n\nThe simplest option is to run `npm install fine-uploader` in your project directory. But this may not be appropriate for all situations.\n\nIf your project has a package.json file, you should do this instead:\n\n1. Add a \"fine-uploader\" entry to the \"dependencies\" section of your project's package.json file. The safest option is to explicitly specify the version number you need. For example, as of 16 August 2016, your entry would be `\"fine-uploader\": \"5.11.5\"`. For more information about package.json files, read [the official npm documentation on the subject](https://docs.npmjs.com/files/package.json).\n2. Run `npm install` in the same directory as your project's package.json file.\n\nIn either case, fine-uploader will be installed in a directory of the same name under the node_modules directory.\n\n#### Download Fine Uploader from FineUploader.com\n\nNavigate to [http://fineuploader.com/customize](http://fineuploader.com/customize) and select the desired build type. See \"step 2\" below for a detailed explanation of the different Fine Uploader build types. For any version since (and including) 5.11.0, you can download the individual builds at [https://github.com/FineUploader/fine-uploader/releases](https://github.com/FineUploader/fine-uploader/releases).\n\n\n#### Build Fine Uploader yourself from the GitHub repo\n\n1. `git clone https://github.com/FineUploader/fine-uploader.git`\n2. `cd fine-uploader`\n3. `npm install`\n4. `make build`\n\nThis will result in a \"_build\" directory with CSS, JS, image, and template files for all possible builds of Fine Uploader. For information on how to further process these build files, if needed, read the [\"Generating build artifacts\" section the project's README.md file](https://github.com/FineUploader/fine-uploader#generating-build-artifacts).\n\n### 2. Decide which build of Fine Uploader to use\n\nFine Uploader builds are categorized by \"endpoint type\" _and_ by feature set. Each of the four endpoint type builds are available with each of the two supported feature sets. **Choose one of the following endpoint types _and_ one of the \"feature sets\" before moving on to step 3**.\n\n#### Endpoint types\n\nA Fine Uploader build that is tied to a specific \"endpoint type\" is designed to send requests to match a specific type of server. There are three endpoint types supported:\n\n##### Traditional endpoints\n\nTraditional endpoints reside on a server or servers that you control. Fine Uploader \"Traditional\" will send all HTTP requests (such as \"upload\" and \"delete file\", to name a couple) to _your_ server. It is expected that you maintain and control the server that handles these requests. The [traditional server endpoints documentation page](../endpoint_handlers/traditional.html) describes how Fine Uploader \"Traditional\" will send requests to your server, and how your server must respond to these requests.\n\n##### S3 endpoints\n\nFine Uploader S3 sends all requests (with the exception of the delete file request) directly to one or more S3 buckets. You may [utilize a server you control to sign these requests](../endpoint_handlers/amazon-s3.html) (the safest option), or you may elect to [omit a server entirely and sign the requests client side](../features/no-server-uploads.html) using Amazon's Web Identity Federation.\n\n##### Microsoft Azure endpoints\n\nFine Uploader Azure sends _all_ requests directly to one or more Azure Blob Storage containers. You must utilize [a server you control to sign each request](../endpoint_handlers/azure.html).\n\n{{ alert(\"Fine Uploader Azure does not support IE9 and older.  This is due to the fact that Azure's API does not allow files to be uploaded via multipart encoded POST requests, which is critical for IE9 and older support. If you need to support IE9 and older, you will need to load/use Fine Uploader with its traditional endpoint handler if the value of `qq.supportedFeatures.ajaxUploading` is `false`.\") }}\n\n##### \"All\" endpoints\n\nIf you would like to be able to send files to various endpoint types from a single running instance of Fine Uploader, a build of the library exists that includes support for traditional endpoint, S3 endpoints, _and_ Azure endpoints.\n\n#### Feature sets\n\nIn addition to endpoint types Fine Uploader builds are classified according to what we call \"feature sets\". The two feature sets with associated builds are \"UI\" and \"core\".\n\n##### Core feature set\n\nThe most basic Fine Uploader mode.  [The core feature set](../modes/core.html) assumes that you will design and code your own UI but still make use of Fine Uploader's API. This feature set is suggested for advanced developers who wish to heavily customize the uploader's user interface.\n\n\n##### UI feature set\n\nInherits everything from the core feature set. [The UI feature set](../modes/ui.html) also comes with a fully functional user interface which includes, but is not limited to: a default upload button, progress bars, retry/cancel/delete buttons, proper display of error messages, and more. This feature set is recommended for those who are pleased with the default Fine Uploader interface or can sufficiently customize the default Fine Uploader UI using CSS overrides. **In most cases, the UI feature set will be the most appropriate choice.**\n\n### 3. Gather the appropriate Fine Uploader files\n\nAfter picking and endpoint type and a set of features from step 2, you can now gather the Fine Uploader files you will need to bring advanced file uploading capabilities to your project.\n\n#### Traditional endpoint, core feature set\n\nYou will need the following JavaScript files:\n\n- fine-uploader/fine-uploader.core.js\n- fine-uploader/fine-uploader.core.map.js (optional, but useful for live-debugging)\n\n#### Traditional endpoint, UI feature set\n\nYou will need the following JavaScript files:\n\n- fine-uploader/fine-uploader.js\n- fine-uploader/fine-uploader.map.js (optional, but useful for live-debugging)\n\nNext, decide if you'd like to adopt the \"gallery layout\" or the \"row-based layout\" for submitted files. See the [demos page](http://fineuploader.com/demos.html) for examples of both. The \"Gallery View for Images\" demo shows the gallery layout, and the \"Manually Trigger Uploads & Edit File Names\" demo illustrates a row-based layout.\n\nIf you choose the gallery layout, you'll need this css file:\n\n- fine-uploader/fine-uploader-gallery.css\n\n...and this template file:\n\n- fine-uploader/templates/gallery.html\n\nIf you've elected for a row-based UI layout, you'll need _these_ CSS files instead of the gallery files listed previously:\n\n- fine-uploader/fine-uploader-new.css\n\n\n...and this template file:\n\n- fine-uploader/templates/simple-thumbnails.html\n\nYou'll also need these image files, which should be located in the same directory as the Fine Uploader CSS file:\n\n- fine-uploader/loading.gif\n- fine-uploader/processing.gif\n- fine-uploader/continue.gif (optional - only if using the pause upload feature)\n- fine-uploader/edit.gif (optional - only if using the edit filename feature)\n- fine-uploader/retry.gif (optional - only if using the retry failed upload feature)\n- fine-uploader/trash.gif (optional - only if using the delete file feature)\n\nThe path to these placeholder images must be specified in the set of options you pass Fine Uploader. These are only needed if you make use of the [thumbnail preview feature](../features/thumbnails.html):\n\n- fine-uploader/placeholders/not_available-generic.png\n- fine-uploader/placeholders/waiting-generic.png\n\n#### S3 endpoint, core feature set\n\nYou will need the following JavaScript files:\n\n- s3.fine-uploader/s3.fine-uploader.core.js\n- s3.fine-uploader/s3.fine-uploader.core.map.js (optional, but useful for live-debugging)\n\n#### S3 endpoint, UI feature set\n\nYou will need the following JavaScript files:\n\n- s3.fine-uploader/s3.fine-uploader.js\n- s3.fine-uploader/s3.fine-uploader.map.js (optional, but useful for live-debugging)\n\nNext, decide if you'd like to adopt the \"gallery layout\" or the \"row-based layout\" for submitted files. See the [demos page](http://fineuploader.com/demos.html) for examples of both. The \"Gallery View for Images\" demo shows the gallery layout, and the \"Manually Trigger Uploads & Edit File Names\" demo illustrates a row-based layout.\n\nIf you choose the gallery layout, you'll need this css file:\n\n- s3.fine-uploader/fine-uploader-gallery.css\n\n\n...and this template file:\n\n- s3.fine-uploader/templates/gallery.html\n\nIf you've elected for a row-based UI layout, you'll need _these_ CSS files instead of the gallery files listed previously:\n\n- s3.fine-uploader/fine-uploader-new.css\n\n\n...and this template file:\n\n- s3.fine-uploader/templates/simple-thumbnails.html\n\nYou'll also need these image files, which should be located in the same directory as the Fine Uploader CSS file:\n\n- s3.fine-uploader/loading.gif\n- s3.fine-uploader/processing.gif\n- s3.fine-uploader/continue.gif (optional - only if using the pause upload feature)\n- s3.fine-uploader/edit.gif (optional - only if using the edit filename feature)\n- s3.fine-uploader/retry.gif (optional - only if using the retry failed upload feature)\n- s3.fine-uploader/trash.gif (optional - only if using the delete file feature)\n\nThe path to these placeholder images must be specified in the set of options you pass Fine Uploader. These are only needed if you make use of the [thumbnail preview feature](../features/thumbnails.html):\n\n- s3.fine-uploader/placeholders/not_available-generic.png\n- s3.fine-uploader/placeholders/waiting-generic.png\n\n#### Azure endpoint, core feature set\n\nYou will need the following JavaScript files:\n\n- azure.fine-uploader/azure.fine-uploader.core.js\n- azure.fine-uploader/azure.fine-uploader.core.map.js (optional, but useful for live-debugging)\n\n#### Azure endpoint, UI feature set\n\nYou will need the following JavaScript files:\n\n- azure.fine-uploader/azure.fine-uploader.js\n- azure.fine-uploader/azure.fine-uploader.map.js (optional, but useful for live-debugging)\n\nNext, decide if you'd like to adopt the \"gallery layout\" or the \"row-based layout\" for submitted files. See the [demos page](http://fineuploader.com/demos.html) for examples of both. The \"Gallery View for Images\" demo shows the gallery layout, and the \"Manually Trigger Uploads & Edit File Names\" demo illustrates a row-based layout.\n\nIf you choose the gallery layout, you'll need this css file:\n\n- azure.fine-uploader/fine-uploader-gallery.css\n\n...and this template file:\n\n- azure.fine-uploader/templates/gallery.html\n\nIf you've elected for a row-based UI layout, you'll need _these_ CSS files instead of the gallery files listed previously:\n\n- azure.fine-uploader/fine-uploader-new.css\n\n...and this template file:\n\n- azure.fine-uploader/templates/simple-thumbnails.html\n\nYou'll also need these image files, which should be located in the same directory as the Fine Uploader CSS file:\n\n- azure.fine-uploader/loading.gif\n- azure.fine-uploader/processing.gif\n- azure.fine-uploader/continue.gif (optional - only if using the pause upload feature)\n- azure.fine-uploader/edit.gif (optional - only if using the edit filename feature)\n- azure.fine-uploader/retry.gif (optional - only if using the retry failed upload feature)\n- azure.fine-uploader/trash.gif (optional - only if using the delete file feature)\n\nThe path to these placeholder images must be specified in the set of options you pass Fine Uploader. These are only needed if you make use of the [thumbnail preview feature](../features/thumbnails.html):\n\n- azure.fine-uploader/placeholders/not_available-generic.png\n- azure.fine-uploader/placeholders/waiting-generic.png\n\n#### All endpoint types, core feature set\n\nYou will need the following JavaScript files:\n\n- all.fine-uploader/all.fine-uploader.core.js\n- all.fine-uploader/all.fine-uploader.core.map.js (optional, but useful for live-debugging)\n\n#### All endpoint types, UI feature set\n\nYou will need the following JavaScript files:\n\n- all.fine-uploader/all.fine-uploader.js\n- all.fine-uploader/all.fine-uploader.map.js (optional, but useful for live-debugging)\n\nNext, decide if you'd like to adopt the \"gallery layout\" or the \"row-based layout\" for submitted files. See the [demos page](http://fineuploader.com/demos.html) for examples of both. The \"Gallery View for Images\" demo shows the gallery layout, and the \"Manually Trigger Uploads & Edit File Names\" demo illustrates a row-based layout.\n\nIf you choose the gallery layout, you'll need this css file:\n\n- all.fine-uploader/fine-uploader-gallery.css\n\n...and this template file:\n\n- all.fine-uploader/templates/gallery.html\n\nIf you've elected for a row-based UI layout, you'll need _these_ CSS files instead of the gallery files listed previously:\n\n- all.fine-uploader/fine-uploader-new.css\n\n...and this template file:\n\n- all.fine-uploader/templates/simple-thumbnails.html\n\nYou'll also need these image files, which should be located in the same directory as the Fine Uploader CSS file:\n\n- all.fine-uploader/loading.gif\n- all.fine-uploader/processing.gif\n- all.fine-uploader/continue.gif (optional - only if using the pause upload feature)\n- all.fine-uploader/edit.gif (optional - only if using the edit filename feature)\n- all.fine-uploader/retry.gif (optional - only if using the retry failed upload feature)\n- all.fine-uploader/trash.gif (optional - only if using the delete file feature)\n\nThe path to these placeholder images must be specified in the set of options you pass Fine Uploader. These are only needed if you make use of the [thumbnail preview feature](../features/thumbnails.html):\n\n- all.fine-uploader/placeholders/not_available-generic.png\n- all.fine-uploader/placeholders/waiting-generic.png\n\n### 4. Integrating Fine Uploader into your project\n\nThe three most common environments for web projects are:\n\n1. Globally scoped (`<script src=\"...\">` for JS files and `<link href=\"...\"> for CSS files`).\n2. ES6 Modules (using `import` for JS/CSS files).\n3. CommonJS (using `require()` for JS/CSS files).\n\nContinue reading the section for the steps needed to integrate Fine Uploader into your project for each of the three environments listed above.\n\n#### Globally scoped environments\n\nIn a \"globally scoped\" environment, you simply reference the required Fine Uploader JavaScript and CSS files (along with other JS and CSS files specific to your prohect) directly via appropriate HTML tags. The sections below will show you how to use Fine Uploader in a globally scoped environment, depending on the endpoint type and feature set you chose in step 2, using the files you gathered in step 3.\n\n##### Traditional endpoint, core feature set\n\n```html\n<!DOCTYPE html>\n<html>\n<head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <script src=\"fine-uploader/fine-uploader.core.min.js\"></script>\n    <title>Upload your files</title>\n</head>\n<body>\n    <script>\n        // Some options to pass to the uploader are discussed on the next page\n        var uploader = new qq.FineUploaderBasic({...})\n    </script>\n</body>\n</html>\n```\n\nRemember, when using the \"core\" build, you will need to build your own UI and update it as needed by observing Fine Uploader callbacks/events. See the API menu at the top of this page for more information on events.\n\n##### Traditional endpoint, UI feature set\n\n```html\n<!DOCTYPE html>\n<html>\n<head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <link href=\"fine-uploader/fine-uploader-gallery.min.css\" rel=\"stylesheet\">\n    <script src=\"fine-uploader/fine-uploader.min.js\"></script>\n    <script type=\"text/template\" id=\"qq-template\">\n        <div class=\"qq-uploader-selector qq-uploader qq-gallery\" qq-drop-area-text=\"Drop files here\">\n            <div class=\"qq-total-progress-bar-container-selector qq-total-progress-bar-container\">\n                <div role=\"progressbar\" aria-valuenow=\"0\" aria-valuemin=\"0\" aria-valuemax=\"100\" class=\"qq-total-progress-bar-selector qq-progress-bar qq-total-progress-bar\"></div>\n            </div>\n            <div class=\"qq-upload-drop-area-selector qq-upload-drop-area\" qq-hide-dropzone>\n                <span class=\"qq-upload-drop-area-text-selector\"></span>\n            </div>\n            <div class=\"qq-upload-button-selector qq-upload-button\">\n                <div>Upload a file</div>\n            </div>\n            <span class=\"qq-drop-processing-selector qq-drop-processing\">\n                <span>Processing dropped files...</span>\n                <span class=\"qq-drop-processing-spinner-selector qq-drop-processing-spinner\"></span>\n            </span>\n            <ul class=\"qq-upload-list-selector qq-upload-list\" role=\"region\" aria-live=\"polite\" aria-relevant=\"additions removals\">\n                <li>\n                    <span role=\"status\" class=\"qq-upload-status-text-selector qq-upload-status-text\"></span>\n                    <div class=\"qq-progress-bar-container-selector qq-progress-bar-container\">\n                        <div role=\"progressbar\" aria-valuenow=\"0\" aria-valuemin=\"0\" aria-valuemax=\"100\" class=\"qq-progress-bar-selector qq-progress-bar\"></div>\n                    </div>\n                    <span class=\"qq-upload-spinner-selector qq-upload-spinner\"></span>\n                    <div class=\"qq-thumbnail-wrapper\">\n                        <img class=\"qq-thumbnail-selector\" qq-max-size=\"120\" qq-server-scale>\n                    </div>\n                    <button type=\"button\" class=\"qq-upload-cancel-selector qq-upload-cancel\">X</button>\n                    <button type=\"button\" class=\"qq-upload-retry-selector qq-upload-retry\">\n                        <span class=\"qq-btn qq-retry-icon\" aria-label=\"Retry\"></span>\n                        Retry\n                    </button>\n\n                    <div class=\"qq-file-info\">\n                        <div class=\"qq-file-name\">\n                            <span class=\"qq-upload-file-selector qq-upload-file\"></span>\n                            <span class=\"qq-edit-filename-icon-selector qq-btn qq-edit-filename-icon\" aria-label=\"Edit filename\"></span>\n                        </div>\n                        <input class=\"qq-edit-filename-selector qq-edit-filename\" tabindex=\"0\" type=\"text\">\n                        <span class=\"qq-upload-size-selector qq-upload-size\"></span>\n                        <button type=\"button\" class=\"qq-btn qq-upload-delete-selector qq-upload-delete\">\n                            <span class=\"qq-btn qq-delete-icon\" aria-label=\"Delete\"></span>\n                        </button>\n                        <button type=\"button\" class=\"qq-btn qq-upload-pause-selector qq-upload-pause\">\n                            <span class=\"qq-btn qq-pause-icon\" aria-label=\"Pause\"></span>\n                        </button>\n                        <button type=\"button\" class=\"qq-btn qq-upload-continue-selector qq-upload-continue\">\n                            <span class=\"qq-btn qq-continue-icon\" aria-label=\"Continue\"></span>\n                        </button>\n                    </div>\n                </li>\n            </ul>\n\n            <dialog class=\"qq-alert-dialog-selector\">\n                <div class=\"qq-dialog-message-selector\"></div>\n                <div class=\"qq-dialog-buttons\">\n                    <button type=\"button\" class=\"qq-cancel-button-selector\">Close</button>\n                </div>\n            </dialog>\n\n            <dialog class=\"qq-confirm-dialog-selector\">\n                <div class=\"qq-dialog-message-selector\"></div>\n                <div class=\"qq-dialog-buttons\">\n                    <button type=\"button\" class=\"qq-cancel-button-selector\">No</button>\n                    <button type=\"button\" class=\"qq-ok-button-selector\">Yes</button>\n                </div>\n            </dialog>\n\n            <dialog class=\"qq-prompt-dialog-selector\">\n                <div class=\"qq-dialog-message-selector\"></div>\n                <input type=\"text\">\n                <div class=\"qq-dialog-buttons\">\n                    <button type=\"button\" class=\"qq-cancel-button-selector\">Cancel</button>\n                    <button type=\"button\" class=\"qq-ok-button-selector\">Ok</button>\n                </div>\n            </dialog>\n        </div>\n    </script>\n\n    <title>Fine Uploader Gallery UI</title>\n</head>\n<body>\n    <div id=\"uploader\"></div>\n    <script>\n        // Some options to pass to the uploader are discussed on the next page\n        var uploader = new qq.FineUploader({\n            element: document.getElementById(\"uploader\")\n        })\n    </script>\n</body>\n</html>\n```\n\nThe above examples assumes you are using the gallery layout. Simply replace the above `<script>` tag containing the template with the row layout template if you prefer that look instead. For information on customizing template, see the [styling feature page](../features/styling.html).\n\n##### S3 endpoint, core feature set\n\n```html\n<!DOCTYPE html>\n<html>\n<head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <script src=\"s3.fine-uploader/s3.fine-uploader.core.min.js\"></script>\n    <title>Upload your files</title>\n</head>\n<body>\n    <script>\n        // Some options to pass to the uploader are discussed on the next page\n        var uploader = new qq.s3.FineUploaderBasic({...})\n    </script>\n</body>\n</html>\n```\n\nRemember, when using the \"core\" build, you will need to build your own UI and update it as needed by observing Fine Uploader callbacks/events. See the API menu at the top of this page for more information on events.\n\n##### S3 endpoint, UI feature set\n\n```html\n<!DOCTYPE html>\n<html>\n<head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <link href=\"s3.fine-uploader/fine-uploader-gallery.min.css\" rel=\"stylesheet\">\n    <script src=\"s3.fine-uploader/s3.fine-uploader.min.js\"\"></script>\n    <script type=\"text/template\" id=\"qq-template\">\n        <div class=\"qq-uploader-selector qq-uploader qq-gallery\" qq-drop-area-text=\"Drop files here\">\n            <div class=\"qq-total-progress-bar-container-selector qq-total-progress-bar-container\">\n                <div role=\"progressbar\" aria-valuenow=\"0\" aria-valuemin=\"0\" aria-valuemax=\"100\" class=\"qq-total-progress-bar-selector qq-progress-bar qq-total-progress-bar\"></div>\n            </div>\n            <div class=\"qq-upload-drop-area-selector qq-upload-drop-area\" qq-hide-dropzone>\n                <span class=\"qq-upload-drop-area-text-selector\"></span>\n            </div>\n            <div class=\"qq-upload-button-selector qq-upload-button\">\n                <div>Upload a file</div>\n            </div>\n            <span class=\"qq-drop-processing-selector qq-drop-processing\">\n                <span>Processing dropped files...</span>\n                <span class=\"qq-drop-processing-spinner-selector qq-drop-processing-spinner\"></span>\n            </span>\n            <ul class=\"qq-upload-list-selector qq-upload-list\" role=\"region\" aria-live=\"polite\" aria-relevant=\"additions removals\">\n                <li>\n                    <span role=\"status\" class=\"qq-upload-status-text-selector qq-upload-status-text\"></span>\n                    <div class=\"qq-progress-bar-container-selector qq-progress-bar-container\">\n                        <div role=\"progressbar\" aria-valuenow=\"0\" aria-valuemin=\"0\" aria-valuemax=\"100\" class=\"qq-progress-bar-selector qq-progress-bar\"></div>\n                    </div>\n                    <span class=\"qq-upload-spinner-selector qq-upload-spinner\"></span>\n                    <div class=\"qq-thumbnail-wrapper\">\n                        <img class=\"qq-thumbnail-selector\" qq-max-size=\"120\" qq-server-scale>\n                    </div>\n                    <button type=\"button\" class=\"qq-upload-cancel-selector qq-upload-cancel\">X</button>\n                    <button type=\"button\" class=\"qq-upload-retry-selector qq-upload-retry\">\n                        <span class=\"qq-btn qq-retry-icon\" aria-label=\"Retry\"></span>\n                        Retry\n                    </button>\n\n                    <div class=\"qq-file-info\">\n                        <div class=\"qq-file-name\">\n                            <span class=\"qq-upload-file-selector qq-upload-file\"></span>\n                            <span class=\"qq-edit-filename-icon-selector qq-btn qq-edit-filename-icon\" aria-label=\"Edit filename\"></span>\n                        </div>\n                        <input class=\"qq-edit-filename-selector qq-edit-filename\" tabindex=\"0\" type=\"text\">\n                        <span class=\"qq-upload-size-selector qq-upload-size\"></span>\n                        <button type=\"button\" class=\"qq-btn qq-upload-delete-selector qq-upload-delete\">\n                            <span class=\"qq-btn qq-delete-icon\" aria-label=\"Delete\"></span>\n                        </button>\n                        <button type=\"button\" class=\"qq-btn qq-upload-pause-selector qq-upload-pause\">\n                            <span class=\"qq-btn qq-pause-icon\" aria-label=\"Pause\"></span>\n                        </button>\n                        <button type=\"button\" class=\"qq-btn qq-upload-continue-selector qq-upload-continue\">\n                            <span class=\"qq-btn qq-continue-icon\" aria-label=\"Continue\"></span>\n                        </button>\n                    </div>\n                </li>\n            </ul>\n\n            <dialog class=\"qq-alert-dialog-selector\">\n                <div class=\"qq-dialog-message-selector\"></div>\n                <div class=\"qq-dialog-buttons\">\n                    <button type=\"button\" class=\"qq-cancel-button-selector\">Close</button>\n                </div>\n            </dialog>\n\n            <dialog class=\"qq-confirm-dialog-selector\">\n                <div class=\"qq-dialog-message-selector\"></div>\n                <div class=\"qq-dialog-buttons\">\n                    <button type=\"button\" class=\"qq-cancel-button-selector\">No</button>\n                    <button type=\"button\" class=\"qq-ok-button-selector\">Yes</button>\n                </div>\n            </dialog>\n\n            <dialog class=\"qq-prompt-dialog-selector\">\n                <div class=\"qq-dialog-message-selector\"></div>\n                <input type=\"text\">\n                <div class=\"qq-dialog-buttons\">\n                    <button type=\"button\" class=\"qq-cancel-button-selector\">Cancel</button>\n                    <button type=\"button\" class=\"qq-ok-button-selector\">Ok</button>\n                </div>\n            </dialog>\n        </div>\n    </script>\n\n    <title>Fine Uploader Gallery UI</title>\n</head>\n<body>\n    <div id=\"uploader\"></div>\n    <script>\n        // Some options to pass to the uploader are discussed on the next page\n        var uploader = new qq.s3.FineUploader({\n            element: document.getElementById(\"uploader\")\n        })\n    </script>\n</body>\n</html>\n```\n\nThe above examples assumes you are using the gallery layout. Simply replace the above `<script>` tag containing the template with the row layout template if you prefer that look instead. For information on customizing template, see the [styling feature page](../features/styling.html).\n\n##### Azure endpoint, core feature set\n\n```html\n<!DOCTYPE html>\n<html>\n<head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <script src=\"azure.fine-uploader/s3.fine-uploader.core.min.js\"></script>\n    <title>Upload your files</title>\n</head>\n<body>\n    <script>\n        // Some options to pass to the uploader are discussed on the next page\n        var uploader = new qq.azure.FineUploaderBasic({...})\n    </script>\n</body>\n</html>\n```\n\nRemember, when using the \"core\" build, you will need to build your own UI and update it as needed by observing Fine Uploader callbacks/events. See the API menu at the top of this page for more information on events.\n\n##### Azure endpoint, UI feature set\n\n```html\n<!DOCTYPE html>\n<html>\n<head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <link href=\"azure.fine-uploader/fine-uploader-gallery.min.css\" rel=\"stylesheet\">\n    <script src=\"azure.fine-uploader/azure.fine-uploader.min.js\"\"></script>\n    <script type=\"text/template\" id=\"qq-template\">\n        <div class=\"qq-uploader-selector qq-uploader qq-gallery\" qq-drop-area-text=\"Drop files here\">\n            <div class=\"qq-total-progress-bar-container-selector qq-total-progress-bar-container\">\n                <div role=\"progressbar\" aria-valuenow=\"0\" aria-valuemin=\"0\" aria-valuemax=\"100\" class=\"qq-total-progress-bar-selector qq-progress-bar qq-total-progress-bar\"></div>\n            </div>\n            <div class=\"qq-upload-drop-area-selector qq-upload-drop-area\" qq-hide-dropzone>\n                <span class=\"qq-upload-drop-area-text-selector\"></span>\n            </div>\n            <div class=\"qq-upload-button-selector qq-upload-button\">\n                <div>Upload a file</div>\n            </div>\n            <span class=\"qq-drop-processing-selector qq-drop-processing\">\n                <span>Processing dropped files...</span>\n                <span class=\"qq-drop-processing-spinner-selector qq-drop-processing-spinner\"></span>\n            </span>\n            <ul class=\"qq-upload-list-selector qq-upload-list\" role=\"region\" aria-live=\"polite\" aria-relevant=\"additions removals\">\n                <li>\n                    <span role=\"status\" class=\"qq-upload-status-text-selector qq-upload-status-text\"></span>\n                    <div class=\"qq-progress-bar-container-selector qq-progress-bar-container\">\n                        <div role=\"progressbar\" aria-valuenow=\"0\" aria-valuemin=\"0\" aria-valuemax=\"100\" class=\"qq-progress-bar-selector qq-progress-bar\"></div>\n                    </div>\n                    <span class=\"qq-upload-spinner-selector qq-upload-spinner\"></span>\n                    <div class=\"qq-thumbnail-wrapper\">\n                        <img class=\"qq-thumbnail-selector\" qq-max-size=\"120\" qq-server-scale>\n                    </div>\n                    <button type=\"button\" class=\"qq-upload-cancel-selector qq-upload-cancel\">X</button>\n                    <button type=\"button\" class=\"qq-upload-retry-selector qq-upload-retry\">\n                        <span class=\"qq-btn qq-retry-icon\" aria-label=\"Retry\"></span>\n                        Retry\n                    </button>\n\n                    <div class=\"qq-file-info\">\n                        <div class=\"qq-file-name\">\n                            <span class=\"qq-upload-file-selector qq-upload-file\"></span>\n                            <span class=\"qq-edit-filename-icon-selector qq-btn qq-edit-filename-icon\" aria-label=\"Edit filename\"></span>\n                        </div>\n                        <input class=\"qq-edit-filename-selector qq-edit-filename\" tabindex=\"0\" type=\"text\">\n                        <span class=\"qq-upload-size-selector qq-upload-size\"></span>\n                        <button type=\"button\" class=\"qq-btn qq-upload-delete-selector qq-upload-delete\">\n                            <span class=\"qq-btn qq-delete-icon\" aria-label=\"Delete\"></span>\n                        </button>\n                        <button type=\"button\" class=\"qq-btn qq-upload-pause-selector qq-upload-pause\">\n                            <span class=\"qq-btn qq-pause-icon\" aria-label=\"Pause\"></span>\n                        </button>\n                        <button type=\"button\" class=\"qq-btn qq-upload-continue-selector qq-upload-continue\">\n                            <span class=\"qq-btn qq-continue-icon\" aria-label=\"Continue\"></span>\n                        </button>\n                    </div>\n                </li>\n            </ul>\n\n            <dialog class=\"qq-alert-dialog-selector\">\n                <div class=\"qq-dialog-message-selector\"></div>\n                <div class=\"qq-dialog-buttons\">\n                    <button type=\"button\" class=\"qq-cancel-button-selector\">Close</button>\n                </div>\n            </dialog>\n\n            <dialog class=\"qq-confirm-dialog-selector\">\n                <div class=\"qq-dialog-message-selector\"></div>\n                <div class=\"qq-dialog-buttons\">\n                    <button type=\"button\" class=\"qq-cancel-button-selector\">No</button>\n                    <button type=\"button\" class=\"qq-ok-button-selector\">Yes</button>\n                </div>\n            </dialog>\n\n            <dialog class=\"qq-prompt-dialog-selector\">\n                <div class=\"qq-dialog-message-selector\"></div>\n                <input type=\"text\">\n                <div class=\"qq-dialog-buttons\">\n                    <button type=\"button\" class=\"qq-cancel-button-selector\">Cancel</button>\n                    <button type=\"button\" class=\"qq-ok-button-selector\">Ok</button>\n                </div>\n            </dialog>\n        </div>\n    </script>\n\n    <title>Fine Uploader Gallery UI</title>\n</head>\n<body>\n    <div id=\"uploader\"></div>\n    <script>\n        // Some options to pass to the uploader are discussed on the next page\n        var uploader = new qq.azure.FineUploader({\n            element: document.getElementById(\"uploader\")\n        })\n    </script>\n</body>\n</html>\n```\n\nThe above examples assumes you are using the gallery layout. Simply replace the above `<script>` tag containing the template with the row layout template if you prefer that look instead. For information on customizing template, see the [styling feature page](../features/styling.html).\n\n##### All endpoint types, core feature set\n\n```html\n<!DOCTYPE html>\n<html>\n<head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <script src=\"all.fine-uploader/all.fine-uploader.core.min.js\"></script>\n    <title>Upload your files</title>\n</head>\n<body>\n    <script>\n        // For a traditional endpoint instance:\n        // Some options to pass to the uploader are discussed on the next page\n        var traditionalUploader = new qq.FineUploaderBasic({...})\n\n        // For an S3 endpoint instance:\n        // Some options to pass to the uploader are discussed on the next page\n        var s3Uploader = new qq.s3.FineUploaderBasic({...})\n\n        // For an Azure endpoint instance:\n        // Some options to pass to the uploader are discussed on the next page\n        var azureUploader = new qq.azure.FineUploaderBasic({...})\n    </script>\n</body>\n</html>\n```\n\nRemember, when using the \"core\" build, you will need to build your own UI and update it as needed by observing Fine Uploader callbacks/events. See the API menu at the top of this page for more information on events.\n\n##### All endpoint types, UI feature set\n\n```html\n<!DOCTYPE html>\n<html>\n<head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <link href=\"all.fine-uploader/fine-uploader-gallery.min.css\" rel=\"stylesheet\">\n    <script src=\"all.fine-uploader/all.fine-uploader.min.js\"\"></script>\n    <script type=\"text/template\" id=\"qq-template\">\n        <div class=\"qq-uploader-selector qq-uploader qq-gallery\" qq-drop-area-text=\"Drop files here\">\n            <div class=\"qq-total-progress-bar-container-selector qq-total-progress-bar-container\">\n                <div role=\"progressbar\" aria-valuenow=\"0\" aria-valuemin=\"0\" aria-valuemax=\"100\" class=\"qq-total-progress-bar-selector qq-progress-bar qq-total-progress-bar\"></div>\n            </div>\n            <div class=\"qq-upload-drop-area-selector qq-upload-drop-area\" qq-hide-dropzone>\n                <span class=\"qq-upload-drop-area-text-selector\"></span>\n            </div>\n            <div class=\"qq-upload-button-selector qq-upload-button\">\n                <div>Upload a file</div>\n            </div>\n            <span class=\"qq-drop-processing-selector qq-drop-processing\">\n                <span>Processing dropped files...</span>\n                <span class=\"qq-drop-processing-spinner-selector qq-drop-processing-spinner\"></span>\n            </span>\n            <ul class=\"qq-upload-list-selector qq-upload-list\" role=\"region\" aria-live=\"polite\" aria-relevant=\"additions removals\">\n                <li>\n                    <span role=\"status\" class=\"qq-upload-status-text-selector qq-upload-status-text\"></span>\n                    <div class=\"qq-progress-bar-container-selector qq-progress-bar-container\">\n                        <div role=\"progressbar\" aria-valuenow=\"0\" aria-valuemin=\"0\" aria-valuemax=\"100\" class=\"qq-progress-bar-selector qq-progress-bar\"></div>\n                    </div>\n                    <span class=\"qq-upload-spinner-selector qq-upload-spinner\"></span>\n                    <div class=\"qq-thumbnail-wrapper\">\n                        <img class=\"qq-thumbnail-selector\" qq-max-size=\"120\" qq-server-scale>\n                    </div>\n                    <button type=\"button\" class=\"qq-upload-cancel-selector qq-upload-cancel\">X</button>\n                    <button type=\"button\" class=\"qq-upload-retry-selector qq-upload-retry\">\n                        <span class=\"qq-btn qq-retry-icon\" aria-label=\"Retry\"></span>\n                        Retry\n                    </button>\n\n                    <div class=\"qq-file-info\">\n                        <div class=\"qq-file-name\">\n                            <span class=\"qq-upload-file-selector qq-upload-file\"></span>\n                            <span class=\"qq-edit-filename-icon-selector qq-btn qq-edit-filename-icon\" aria-label=\"Edit filename\"></span>\n                        </div>\n                        <input class=\"qq-edit-filename-selector qq-edit-filename\" tabindex=\"0\" type=\"text\">\n                        <span class=\"qq-upload-size-selector qq-upload-size\"></span>\n                        <button type=\"button\" class=\"qq-btn qq-upload-delete-selector qq-upload-delete\">\n                            <span class=\"qq-btn qq-delete-icon\" aria-label=\"Delete\"></span>\n                        </button>\n                        <button type=\"button\" class=\"qq-btn qq-upload-pause-selector qq-upload-pause\">\n                            <span class=\"qq-btn qq-pause-icon\" aria-label=\"Pause\"></span>\n                        </button>\n                        <button type=\"button\" class=\"qq-btn qq-upload-continue-selector qq-upload-continue\">\n                            <span class=\"qq-btn qq-continue-icon\" aria-label=\"Continue\"></span>\n                        </button>\n                    </div>\n                </li>\n            </ul>\n\n            <dialog class=\"qq-alert-dialog-selector\">\n                <div class=\"qq-dialog-message-selector\"></div>\n                <div class=\"qq-dialog-buttons\">\n                    <button type=\"button\" class=\"qq-cancel-button-selector\">Close</button>\n                </div>\n            </dialog>\n\n            <dialog class=\"qq-confirm-dialog-selector\">\n                <div class=\"qq-dialog-message-selector\"></div>\n                <div class=\"qq-dialog-buttons\">\n                    <button type=\"button\" class=\"qq-cancel-button-selector\">No</button>\n                    <button type=\"button\" class=\"qq-ok-button-selector\">Yes</button>\n                </div>\n            </dialog>\n\n            <dialog class=\"qq-prompt-dialog-selector\">\n                <div class=\"qq-dialog-message-selector\"></div>\n                <input type=\"text\">\n                <div class=\"qq-dialog-buttons\">\n                    <button type=\"button\" class=\"qq-cancel-button-selector\">Cancel</button>\n                    <button type=\"button\" class=\"qq-ok-button-selector\">Ok</button>\n                </div>\n            </dialog>\n        </div>\n    </script>\n\n    <title>Fine Uploader Gallery UI</title>\n</head>\n<body>\n    <div id=\"traditional-uploader\"></div>\n    <div id=\"s3-uploader\"></div>\n    <div id=\"azure-uploader\"></div>\n\n    <script>\n        // For a traditional endpoint instance:\n        // Some options to pass to the uploader are discussed on the next page\n        var traditionalUploader = new qq.FineUploader({\n            element: document.getElementById(\"traditional-uploader\")\n        })\n\n        // For an S3 endpoint instance:\n        // Some options to pass to the uploader are discussed on the next page\n        var s3Uploader = new qq.s3.FineUploader({\n            element: document.getElementById(\"s3-uploader\")\n        })\n\n        // For an Azure endpoint instance:\n        // Some options to pass to the uploader are discussed on the next page\n        var azureUploader = new qq.azure.FineUploader({\n            element: document.getElementById(\"azure-uploader\")\n        })\n    </script>\n</body>\n</html>\n```\n\nThe above examples assumes you are using the gallery layout. Simply replace the above `<script>` tag containing the template with the row layout template if you prefer that look instead. For information on customizing template, see the [styling feature page](../features/styling.html).\n\n#### ES6 module & CommonJS environments\n\nThe JavaScript and CSS files mentioned in the \"globally scoped environments\" examples are also needed if you are working in an ES6 or CommonJS environment. If you are working in either of these two environments, you may be using tools like Webpack, Rollup, Babel, and Bublé to compile and combine JavaScript and CSS files into bundles for production use in a wide array of browsers. For more information on how you can load Fine Uploader into your project using ES6 or CommonJS, please see the [modules feature page](../features/modules.html).\n\n### 5. Next, let's check out some of Fine Uploader's options.\n\nProceed by selecting either:\n\n* [Traditional endpoint](02-setting_options.html)\n* [Amazon S3 endpoint](02-setting_options-s3.html)\n* [Azure Blob Storage endpoint](02-setting_options-azure.html)\n\n{% endmarkdown %}\n{% endblock %}\n"
  },
  {
    "path": "docs/quickstart/02-setting_options-azure.jmd",
    "content": "{% extends \"_templates/base.html\" %}\n{% set page_title = \"Setting Options Azure\" %}\n{% block content %}\n{% markdown %}\n# Setting Fine Uploader Options <small>Azure Blob Storage</small> {: .page-header }\n\nAt this point in the tutorial you should have an very simple HTML page with a\nFine Uploader Azure instance. This section will get you started with setting some of\nthe more common options of Fine Uploader. To see _all_ supported options, open the \"API\" menu at the top of this page.\n\n{{ alert(\n\"\"\"If you are using Fine Uploader UI, you MUST include a template in your document/markup.  You can\nuse the `default.html` file  in the `templates` directory bundled with the library and customize it as desired.  See the\n[styling documentation page](../features/styling.html) for more details.\"\"\") }}\n\n## Basic Options\n\nFine Uploader comes with a wealth of options. Most importantly, Fine Uploader\nrequires you to specify the URL where it should send uploads and server\nrequests to. This endpoint can be an absolute path or a relative path.  The meaning of this\nendpoint varies depending on the endpoint handler. If you are using\nFine Uploader Azure, the endpoint should point to your Azure Blob Storage container.\n\nMore information on the [`request` option here](../api/options-azure.html#request-option).\n\n{{ alert(\"Fine Uploader Azure does not support IE9 and older.  This is due to the fact that Azure's API does\nnot allow files to be uploaded via multipart encoded POST requests, which is critical for IE9 and older support.\nIf you need to support IE9 and older, you will need to load/use Fine Uploader with its traditional endpoint\nhandler if the value of `qq.supportedFeatures.ajaxUploading` is `false`.\") }}\n\n```javascript\nif (qq.supportedFeatures.ajaxUploading) {\n    var uploader = new qq.azure.FineUploader\n        request: {\n            endpoint: 'https://{ YOUR_STORAGE_ACCOUNT_NAME }.blob.core.windows.net/{ YOUR_CONTAINER_NAME }'\n        },\n        signature: {\n            endpoint: '/signature'\n        },\n        uploadSuccess: {\n            endpoint: '/success'\n        }\n    });\n}\n```\n\n### Debug mode\n\nDebug mode is used to begin diagnosing any application errors. Debug mode can\nbe enabled in the options of Fine Uploader:\n\n```javascript\nvar uploader = new qq.azure.FineUploader\n    debug: true,\n    /* ...other options... */\n});\n```\n\nWith debug mode on, Fine Uploader will spit out log messages to the console.\nThis allows you to diagnose any application errors and it is recommended to\nhave this mode on during development.\n\nMore information on [debugging errors here](../features/handling-errors.html).\n\n### Retrying Uploads\n\nNetworks can be unreliable, and if your upload fails, Fine Uploader offers the\nability to retry. The `retry` option can be set to enable this:\n\n```javascript\nvar uploader = new qq.azure.FineUploader\n    /* ...other options... */\n    retry: {\n       enableAuto: true // defaults to false\n    }\n});\n```\n\nMore information on [retrying uploads here](../features/retry.html).\n\n### Deleting Files\n\nIf a user has uploaded a file, but realized it was a mistake before aborting, then Fine Uploader can also help them delete files.\n\nA file is eligible for deletion only after it has been successfully uploaded.\nIn Fine Uploader UI mode, if this feature is enabled, a customizable delete\nlink will appear next to the file after it has successfully uploaded.  File deletion can be enabled in the Fine Uploader\noptions.  You can also send a delete request via the `deleteFile` [API method](../api/methods.html).\n\nNote that Fine Uploader Azure will send delete file requests directly to Azure via the REST API.  This means that the\n`endpoint` property of the `deleteFile` feature is irrelevant.\n\n```javascript\nvar uploader = new qq.azure.FineUploader\n    /* ...other options... */\n    deleteFile: {\n        enabled: true // default is false\n    }\n});\n```\n\nMore information on [deleting files here](../features/delete.html).\n\n## Putting it together\n\nNow our Fine Uploader page should look something like this:\n\n{{ alert(\n\"\"\"There are many ways to load Fine Uploader into your project. The code below creates a global `qq` variable, but there are\nother, better options. Please see [modules feature page](../features/modules.html) for more details.\"\"\") }}\n\n```html\n<html>\n  <head>\n      <link href=\"fine-uploader-{{ PKG['version']|e }}-new.css\" rel=\"stylesheet\" type=\"text/css\"/>\n  </head>\n  <body>\n\n    <!-- The element where Fine Uploader will exist. -->\n    <div id=\"fine-uploader\">\n    </div>\n\n    <!-- Fine Uploader -->\n    <script src=\"azure.fine-uploader.min.js\" type=\"text/javascript\"></script>\n\n    <script type=\"text/template\" id=\"qq-template\">\n        <div class=\"qq-uploader-selector qq-uploader\" qq-drop-area-text=\"Drop files here\">\n            <div class=\"qq-total-progress-bar-container-selector qq-total-progress-bar-container\">\n                <div role=\"progressbar\" aria-valuenow=\"0\" aria-valuemin=\"0\" aria-valuemax=\"100\" class=\"qq-total-progress-bar-selector qq-progress-bar qq-total-progress-bar\"></div>\n            </div>\n            <div class=\"qq-upload-drop-area-selector qq-upload-drop-area\" qq-hide-dropzone>\n                <span class=\"qq-upload-drop-area-text-selector\"></span>\n            </div>\n            <div class=\"qq-upload-button-selector qq-upload-button\">\n                <div>Upload a file</div>\n            </div>\n            <span class=\"qq-drop-processing-selector qq-drop-processing\">\n                <span>Processing dropped files...</span>\n                <span class=\"qq-drop-processing-spinner-selector qq-drop-processing-spinner\"></span>\n            </span>\n            <ul class=\"qq-upload-list-selector qq-upload-list\" aria-live=\"polite\" aria-relevant=\"additions removals\">\n                <li>\n                    <div class=\"qq-progress-bar-container-selector\">\n                        <div role=\"progressbar\" aria-valuenow=\"0\" aria-valuemin=\"0\" aria-valuemax=\"100\" class=\"qq-progress-bar-selector qq-progress-bar\"></div>\n                    </div>\n                    <span class=\"qq-upload-spinner-selector qq-upload-spinner\"></span>\n                    <img class=\"qq-thumbnail-selector\" qq-max-size=\"100\" qq-server-scale>\n                    <span class=\"qq-upload-file-selector qq-upload-file\"></span>\n                    <span class=\"qq-edit-filename-icon-selector qq-edit-filename-icon\" aria-label=\"Edit filename\"></span>\n                    <input class=\"qq-edit-filename-selector qq-edit-filename\" tabindex=\"0\" type=\"text\">\n                    <span class=\"qq-upload-size-selector qq-upload-size\"></span>\n                    <button type=\"button\" class=\"qq-btn qq-upload-cancel-selector qq-upload-cancel\">Cancel</button>\n                    <button type=\"button\" class=\"qq-btn qq-upload-retry-selector qq-upload-retry\">Retry</button>\n                    <button type=\"button\" class=\"qq-btn qq-upload-delete-selector qq-upload-delete\">Delete</button>\n                    <span role=\"status\" class=\"qq-upload-status-text-selector qq-upload-status-text\"></span>\n                </li>\n            </ul>\n\n            <dialog class=\"qq-alert-dialog-selector\">\n                <div class=\"qq-dialog-message-selector\"></div>\n                <div class=\"qq-dialog-buttons\">\n                    <button type=\"button\" class=\"qq-cancel-button-selector\">Close</button>\n                </div>\n            </dialog>\n\n            <dialog class=\"qq-confirm-dialog-selector\">\n                <div class=\"qq-dialog-message-selector\"></div>\n                <div class=\"qq-dialog-buttons\">\n                    <button type=\"button\" class=\"qq-cancel-button-selector\">No</button>\n                    <button type=\"button\" class=\"qq-ok-button-selector\">Yes</button>\n                </div>\n            </dialog>\n\n            <dialog class=\"qq-prompt-dialog-selector\">\n                <div class=\"qq-dialog-message-selector\"></div>\n                <input type=\"text\">\n                <div class=\"qq-dialog-buttons\">\n                    <button type=\"button\" class=\"qq-cancel-button-selector\">Cancel</button>\n                    <button type=\"button\" class=\"qq-ok-button-selector\">Ok</button>\n                </div>\n            </dialog>\n        </div>\n    </script>\n\n    <script>\n        var uploader = new qq.azure.FineUploader\n            element: document.getElementById('fine-uploader'),\n            request: {\n                endpoint: 'https://{ YOUR_STORAGE_ACCOUNT_NAME }.blob.core.windows.net/{ YOUR_CONTAINER_NAME }'\n            },\n            signature: {\n                endpoint: '/signature'\n            },\n            uploadSuccess: {\n                endpoint: '/success'\n            },\n            retry: {\n               enableAuto: true\n            },\n            deleteFile: {\n                enabled: true\n            }\n        });\n    </script>\n  </body>\n</html>\n```\n\n[Next](03-setting_up_server-azure.html), you'll learn to use Fine Uploader Azure to handle uploads.\n{% endmarkdown %}\n{% endblock %}\n"
  },
  {
    "path": "docs/quickstart/02-setting_options-s3.jmd",
    "content": "{% extends \"_templates/base.html\" %}\n{% set page_title = \"Setting Options S3\" %}\n{% block content %}\n{% markdown %}\n# Setting Fine Uploader Options <small>Amazon S3</small> {: .page-header }\n\nAt this point in the tutorial you should have an very simple HTML page with a\nFine Uploader instance. This section will get you started with setting some of\nthe more common options of Fine Uploader. To see _all_ supported options, open the \"API\" menu at the top of this page.\n\n{{ alert(\n\"\"\"If you are using Fine Uploader UI, you MUST include a template in your document/markup.  You can\nuse the `default.html` file  in the `templates` directory bundled with the library and customize it as desired.  See the\n[styling documentation page](../features/styling.html) for more details.\"\"\") }}\n\n## Basic Options\n\nFine Uploader comes with a wealth of options. Most importantly, Fine Uploader\nrequires you to specify the URL where it should send uploads and server\nrequests to. This endpoint can be an absolute path or a relative path.  The meaning of this\nendpoint varies depending on the endpoint handler. If you are using\nFine Uploader S3, the endpoint should point to your S3 bucket.\n\nMore information on the [`request` option here](../api/options-s3.html#request-option).\n\n```javascript\nvar uploader = new qq.s3.FineUploader({\n    request: {\n        endpoint: '{ YOUR_BUCKET_NAME }.s3.amazonaws.com'\n        accessKey: '{ YOUR_ACCESS_KEY }'\n    },\n    signature: {\n        endpoint: '/s3/signature'\n    },\n    uploadSuccess: {\n        endpoint: '/s3/success'\n    },\n    iframeSupport: {\n        localBlankPagePath: '/success.html'\n    }\n});\n```\n\n{{ alert(\n\"\"\"If you wish to send files to an S3 bucket in a region that only supports version 4 signatures, you will\nhave to set [the `signature.version` option](../api/options-s3.html#signature.version) to `4`.\"\"\") }}\n\n\n### Debug mode\n\nDebug mode is used to begin diagnosing any application errors. Debug mode can\nbe enabled in the options of Fine Uploader:\n\n```javascript\nvar uploader = new qq.s3.FineUploader({\n    debug: true,\n    /* ...other options... */\n});\n```\n\nWith debug mode on, Fine Uploader will spit out log messages to the console.\nThis allows you to diagnose any application errors and it is recommended to\nhave this mode on during development.\n\nMore information on [debugging errors here](../features/handling-errors.html).\n\n### Retrying Uploads\n\nNetworks can be unreliable, and if your upload fails, Fine Uploader offers the\nability to retry. The `retry` option can be set to enable this:\n\n```javascript\nvar uploader = new qq.s3.FineUploader({\n    /* ...other options... */\n    retry: {\n       enableAuto: true // defaults to false\n    }\n});\n```\n\nMore information on [retrying uploads here](../features/retry.html).\n\n### Deleting Files\n\nIf a user has uploaded a file, but realized it was a mistake before aborting, then Fine Uploader can also help them delete files.\n\nA file is eligible for deletion only after it has been successfully uploaded.\nIn Fine Uploader UI mode, if this feature is enabled, a customizable delete\nlink will appear next to the file after it has successfully uploaded.  File deletion can be enabled in the Fine Uploader\noptions.\n\nYou can also send a delete request via the `deleteFile` [API method](../api/methods.html).\n\n```javascript\nvar uploader = new qq.s3.FineUploader({\n    /* ...other options... */\n    deleteFile: {\n        enabled: true, // defaults to false\n        endpoint: '/s3handler'\n    }\n});\n```\n\nMore information on [deleting files here](../features/delete.html).\n\n## Putting it together\n\nNow our Fine Uploader page should look something like this:\n\n{{ alert(\n\"\"\"There are many ways to load Fine Uploader into your project. The code below creates a global `qq` variable, but there are\nother, better options. Please see [modules feature page](../features/modules.html) for more details.\"\"\") }}\n\n```html\n<html>\n  <head>\n      <link href=\"fine-uploader-{{ PKG['version']|e }}-new.css\" rel=\"stylesheet\" type=\"text/css\"/>\n  </head>\n  <body>\n\n    <!-- The element where Fine Uploader will exist. -->\n    <div id=\"fine-uploader\">\n    </div>\n\n    <!-- Fine Uploader -->\n    <script src=\"s3.fine-uploader.min.js\" type=\"text/javascript\"></script>\n\n    <script type=\"text/template\" id=\"qq-template\">\n        <div class=\"qq-uploader-selector qq-uploader\" qq-drop-area-text=\"Drop files here\">\n            <div class=\"qq-total-progress-bar-container-selector qq-total-progress-bar-container\">\n                <div role=\"progressbar\" aria-valuenow=\"0\" aria-valuemin=\"0\" aria-valuemax=\"100\" class=\"qq-total-progress-bar-selector qq-progress-bar qq-total-progress-bar\"></div>\n            </div>\n            <div class=\"qq-upload-drop-area-selector qq-upload-drop-area\" qq-hide-dropzone>\n                <span class=\"qq-upload-drop-area-text-selector\"></span>\n            </div>\n            <div class=\"qq-upload-button-selector qq-upload-button\">\n                <div>Upload a file</div>\n            </div>\n            <span class=\"qq-drop-processing-selector qq-drop-processing\">\n                <span>Processing dropped files...</span>\n                <span class=\"qq-drop-processing-spinner-selector qq-drop-processing-spinner\"></span>\n            </span>\n            <ul class=\"qq-upload-list-selector qq-upload-list\" aria-live=\"polite\" aria-relevant=\"additions removals\">\n                <li>\n                    <div class=\"qq-progress-bar-container-selector\">\n                        <div role=\"progressbar\" aria-valuenow=\"0\" aria-valuemin=\"0\" aria-valuemax=\"100\" class=\"qq-progress-bar-selector qq-progress-bar\"></div>\n                    </div>\n                    <span class=\"qq-upload-spinner-selector qq-upload-spinner\"></span>\n                    <img class=\"qq-thumbnail-selector\" qq-max-size=\"100\" qq-server-scale>\n                    <span class=\"qq-upload-file-selector qq-upload-file\"></span>\n                    <span class=\"qq-edit-filename-icon-selector qq-edit-filename-icon\" aria-label=\"Edit filename\"></span>\n                    <input class=\"qq-edit-filename-selector qq-edit-filename\" tabindex=\"0\" type=\"text\">\n                    <span class=\"qq-upload-size-selector qq-upload-size\"></span>\n                    <button type=\"button\" class=\"qq-btn qq-upload-cancel-selector qq-upload-cancel\">Cancel</button>\n                    <button type=\"button\" class=\"qq-btn qq-upload-retry-selector qq-upload-retry\">Retry</button>\n                    <button type=\"button\" class=\"qq-btn qq-upload-delete-selector qq-upload-delete\">Delete</button>\n                    <span role=\"status\" class=\"qq-upload-status-text-selector qq-upload-status-text\"></span>\n                </li>\n            </ul>\n\n            <dialog class=\"qq-alert-dialog-selector\">\n                <div class=\"qq-dialog-message-selector\"></div>\n                <div class=\"qq-dialog-buttons\">\n                    <button type=\"button\" class=\"qq-cancel-button-selector\">Close</button>\n                </div>\n            </dialog>\n\n            <dialog class=\"qq-confirm-dialog-selector\">\n                <div class=\"qq-dialog-message-selector\"></div>\n                <div class=\"qq-dialog-buttons\">\n                    <button type=\"button\" class=\"qq-cancel-button-selector\">No</button>\n                    <button type=\"button\" class=\"qq-ok-button-selector\">Yes</button>\n                </div>\n            </dialog>\n\n            <dialog class=\"qq-prompt-dialog-selector\">\n                <div class=\"qq-dialog-message-selector\"></div>\n                <input type=\"text\">\n                <div class=\"qq-dialog-buttons\">\n                    <button type=\"button\" class=\"qq-cancel-button-selector\">Cancel</button>\n                    <button type=\"button\" class=\"qq-ok-button-selector\">Ok</button>\n                </div>\n            </dialog>\n        </div>\n    </script>\n\n    <script>\n        var uploader = new qq.s3.FineUploader({\n            debug: true,\n            element: document.getElementById('fine-uploader'),\n            request: {\n                endpoint: '{ YOUR_BUCKET_NAME }.s3.amazonaws.com',\n                accessKey: '{ YOUR_ACCESS_KEY }'\n            },\n            signature: {\n                endpoint: '/s3/signature'\n            },\n            uploadSuccess: {\n                endpoint: '/s3/success'\n            },\n            iframeSupport: {\n                localBlankPagePath: '/success.html'\n            },\n            retry: {\n               enableAuto: true // defaults to false\n            },\n            deleteFile: {\n                enabled: true,\n                endpoint: '/s3handler'\n            }\n        });\n    </script>\n  </body>\n</html>\n```\n\n[Next](03-setting_up_server-s3.html), you'll learn to use Amazon S3 to handle uploads.\n{% endmarkdown %}\n{% endblock %}\n"
  },
  {
    "path": "docs/quickstart/02-setting_options.jmd",
    "content": "{% extends \"_templates/base.html\" %}\n{% set page_title = \"Setting Options\" %}\n{% block content %}\n{% markdown %}\n# Setting Fine Uploader Options <small>Traditional</small> {: .page-header }\n\nAt this point in the tutorial you should have an very simple HTML page with a\nFine Uploader instance. This section will get you started with setting some of\nthe more common options of Fine Uploader. To see _all_ supported options, open the \"API\" menu at the top of this page.\n\n{{ alert(\n\"\"\"If you are using Fine Uploader UI, you MUST include a template in your document/markup.  You can\nuse the `default.html` file in the `templates` directory bundled with the library and customize it as desired.  See the\n[styling documentation page](../features/styling.html) for more details.\"\"\") }}\n\n## Basic Options\n\nFine Uploader comes with a wealth of options. Most importantly, Fine Uploader\nrequires you to specify the URL where it should send uploads and server\nrequests to. This endpoint can be an absolute path or a relative path.  The meaning of this\nendpoint varies depending on the endpoint handler.  If you are using the traditional uploader,\nthis is the path endpoint on your server that will handle upload requests.\n\nMore information on the [`request` option here](../api/options.html).\n\n```javascript\nvar uploader = new qq.FineUploader({\n    request: {\n        endpoint: '/uploads'\n    }\n});\n```\n\n### Debug mode\n\nDebug mode is used to begin diagnosing any application errors. Debug mode can\nbe enabled in the options of Fine Uploader:\n\n```javascript\nvar uploader = new qq.FineUploader({\n    debug: true,\n    request: {\n        endpoint: '/uploads'\n    }\n});\n```\n\nWith debug mode on, Fine Uploader will spit out log messages to the console.\nThis allows you to diagnose any application errors and it is recommended to\nhave this mode on during development.\n\nMore information on [debugging errors here](../features/handling-errors.html).\n\n### Retrying Uploads\n\nNetworks can be unreliable, and if your upload fails, Fine Uploader offers the\nability to retry. The `retry` option can be set to enable this:\n\n```javascript\nvar uploader = new qq.FineUploader({\n    request: {\n        endpoint: '/uploads'\n    },\n    retry: {\n       enableAuto: true // defaults to false\n    }\n});\n```\n\nMore information on [retrying uploads here](../features/retry.html).\n\n### Deleting Files\n\nIf a user has uploaded a file, but realized it was a mistake before aborting, then FineUploder can also help them delete files.\nFine Uploader supports deleting files via POST, DELETE, and across origins/domains.\n\nA file is eligible for deletion only after it has been successfully uploaded.\nIn Fine Uploader UI mode, if this feature is enabled, a customizable delete\nlink will appear next to the file after it has successfully uploaded.  File deletion can be enabled in the Fine Uploader\noptions.\n\nYou can also send a delete request via the `deleteFile` [API method](../api/methods.html).\n\n```javascript\nvar uploader = new qq.FineUploader({\n    request: {\n        endpoint: '/uploads'\n    },\n    deleteFile: {\n        enabled: true, // defaults to false\n        endpoint: '/my/delete/endpoint'\n    }\n});\n```\n\nMore information on [deleting files here](../features/delete.html).\n\n## Putting it together\n\nNow our Fine Uploader page should look something like this:\n\n{{ alert(\n\"\"\"There are many ways to load Fine Uploader into your project. The code below creates a global `qq` variable, but there are\nother, better options. Please see [modules feature page](../features/modules.html) for more details.\"\"\") }}\n\n```html\n<html>\n  <head>\n      <link href=\"fine-uploader-{{ PKG['version']|e }}-new.css\" rel=\"stylesheet\" type=\"text/css\"/>\n  </head>\n  <body>\n\n    <!-- The element where Fine Uploader will exist. -->\n    <div id=\"fine-uploader\">\n    </div>\n\n    <!-- Fine Uploader -->\n    <script src=\"fine-uploader.min.js\" type=\"text/javascript\"></script>\n\n    <script type=\"text/template\" id=\"qq-template\">\n        <div class=\"qq-uploader-selector qq-uploader\" qq-drop-area-text=\"Drop files here\">\n            <div class=\"qq-total-progress-bar-container-selector qq-total-progress-bar-container\">\n                <div role=\"progressbar\" aria-valuenow=\"0\" aria-valuemin=\"0\" aria-valuemax=\"100\" class=\"qq-total-progress-bar-selector qq-progress-bar qq-total-progress-bar\"></div>\n            </div>\n            <div class=\"qq-upload-drop-area-selector qq-upload-drop-area\" qq-hide-dropzone>\n                <span class=\"qq-upload-drop-area-text-selector\"></span>\n            </div>\n            <div class=\"qq-upload-button-selector qq-upload-button\">\n                <div>Upload a file</div>\n            </div>\n            <span class=\"qq-drop-processing-selector qq-drop-processing\">\n                <span>Processing dropped files...</span>\n                <span class=\"qq-drop-processing-spinner-selector qq-drop-processing-spinner\"></span>\n            </span>\n            <ul class=\"qq-upload-list-selector qq-upload-list\" aria-live=\"polite\" aria-relevant=\"additions removals\">\n                <li>\n                    <div class=\"qq-progress-bar-container-selector\">\n                        <div role=\"progressbar\" aria-valuenow=\"0\" aria-valuemin=\"0\" aria-valuemax=\"100\" class=\"qq-progress-bar-selector qq-progress-bar\"></div>\n                    </div>\n                    <span class=\"qq-upload-spinner-selector qq-upload-spinner\"></span>\n                    <img class=\"qq-thumbnail-selector\" qq-max-size=\"100\" qq-server-scale>\n                    <span class=\"qq-upload-file-selector qq-upload-file\"></span>\n                    <span class=\"qq-edit-filename-icon-selector qq-edit-filename-icon\" aria-label=\"Edit filename\"></span>\n                    <input class=\"qq-edit-filename-selector qq-edit-filename\" tabindex=\"0\" type=\"text\">\n                    <span class=\"qq-upload-size-selector qq-upload-size\"></span>\n                    <button type=\"button\" class=\"qq-btn qq-upload-cancel-selector qq-upload-cancel\">Cancel</button>\n                    <button type=\"button\" class=\"qq-btn qq-upload-retry-selector qq-upload-retry\">Retry</button>\n                    <button type=\"button\" class=\"qq-btn qq-upload-delete-selector qq-upload-delete\">Delete</button>\n                    <span role=\"status\" class=\"qq-upload-status-text-selector qq-upload-status-text\"></span>\n                </li>\n            </ul>\n\n            <dialog class=\"qq-alert-dialog-selector\">\n                <div class=\"qq-dialog-message-selector\"></div>\n                <div class=\"qq-dialog-buttons\">\n                    <button type=\"button\" class=\"qq-cancel-button-selector\">Close</button>\n                </div>\n            </dialog>\n\n            <dialog class=\"qq-confirm-dialog-selector\">\n                <div class=\"qq-dialog-message-selector\"></div>\n                <div class=\"qq-dialog-buttons\">\n                    <button type=\"button\" class=\"qq-cancel-button-selector\">No</button>\n                    <button type=\"button\" class=\"qq-ok-button-selector\">Yes</button>\n                </div>\n            </dialog>\n\n            <dialog class=\"qq-prompt-dialog-selector\">\n                <div class=\"qq-dialog-message-selector\"></div>\n                <input type=\"text\">\n                <div class=\"qq-dialog-buttons\">\n                    <button type=\"button\" class=\"qq-cancel-button-selector\">Cancel</button>\n                    <button type=\"button\" class=\"qq-ok-button-selector\">Ok</button>\n                </div>\n            </dialog>\n        </div>\n    </script>\n\n    <script>\n        var uploader = new qq.FineUploader({\n            debug: true,\n            element: document.getElementById('fine-uploader'),\n            request: {\n                endpoint: '/uploads'\n            },\n            deleteFile: {\n                enabled: true,\n                endpoint: '/uploads'\n            },\n            retry: {\n               enableAuto: true\n            }\n        });\n    </script>\n  </body>\n</html>\n```\n\n[Next](03-setting_up_server.html), you'll learn to set up a simple server to handle uploads.\n{% endmarkdown %}\n{% endblock %}\n"
  },
  {
    "path": "docs/quickstart/03-setting_up_server-azure.jmd",
    "content": "{% extends \"_templates/base.html\" %}\n{% set page_title = \"Server Set-Up Azure\" %}\n\n{% block content %}\n{% markdown %}\n# Server Set-Up <small>Azure Blob Storage</small> {: .page-header}\n\nWe have provided a [sample C# endpoint handler for Fine Uploader Azure][csharp].  If you would like\nto develop your own endpoint handler, or modify the example, you should read about [handling\nFine Uploader Azure requests][azureserver] first.\n\n[csharp]: https://github.com/FineUploader/server-examples/tree/master/C%23/azure\n[azureserver]: ../endpoint_handlers/azure.html\n\n{% endmarkdown %}\n{% endblock %}\n"
  },
  {
    "path": "docs/quickstart/03-setting_up_server-s3.jmd",
    "content": "{% extends \"_templates/base.html\" %}\n{% set page_title = \"Server Set-Up S3\" %}\n\n{% block content %}\n{% markdown %}\n\n[blog]: http://blog.fineuploader.com/2013/08/16/fine-uploader-s3-upload-directly-to-amazon-s3-from-your-browser/\n[packagist-url]: https://packagist.org/packages/fineuploader/php-s3-server\n[repo]: https://github.com/FineUploader/php-s3-server\n\n\n# Server Set-Up <small>Amazon S3</small> {: .page-header}\n\nFor this tutorial we are going to use PHP to develop a simple server which will sign requests bound for S3.\n\nIf PHP is not your style, then feel free to browse the other\n[server-side examples repository](https://github.com/FineUploader/server-examples).  **It's quite important for you to read\nthe [Amazon S3 server-side integration instructions](../endpoint_handlers/amazon-s3.html) before you do any of this,\nthough**.\n\n\n### Getting Started\n\n#### Step 1: Setup your S3 bucket & IAM user\n\nPlease read [our blog post on Fine Uploader S3][blog] for more information regarding setup of your buckets and IAM users.\n\n\n#### Step 2: Download the sample PHP S3 signature server\n\nWe've [published an S3 endpoint PHP server for Fine Uploader to Packagist][packagist-url], which means you can easily download\nit to your project using [composer][composer]!\n\nFollow these simple steps to get a copy of the PHP traditional endpoint using Composer:\n\n1. Download Composer: `curl -sS https://getcomposer.org/installer | php`.\n2. Create a composer.json file and place it in the root of your project. See below for an example file.\n3. Install the Fine Uploader S3 endpoint code: `php composer.phar install`.\n\nAn example composer.json file may look like this:\n\n```json\n{\n  \"require\": {\n    \"fineuploader/php-s3-server\": \"1.1.0\"\n  }\n}\n```\n\nBe sure to update the version with the appropriate version of Fine Uploader's S3 PHP server.\n\n\n#### Step 3: Prepare your environment\n\nYou will need to set 4 environment variables before starting the server. These variables will store your AWS client-side\nsecret key, AWS server-side public key, AWS server-side secret key, and the name of your S3 bucket. You set up\nyour bucket, along with all of your keys, in step 1 above.\n\nTo set your environment variables on a Unix or Linux system, you may execute 4 lines, similar to this:\n\n```bash\nexport AWS_CLIENT_SECRET_KEY=fake/client-private-key\nexport AWS_SERVER_PUBLIC_KEY=fake-server-public-key\nexport AWS_SERVER_PRIVATE_KEY=fake/server-private-key\nexport S3_BUCKET_NAME=mybucket\n```\n\n\n#### Step 4: Start the PHP server\n\nYou can easily start up a simple HTTP server using PHP. Run the following command in the root of your web server:\n\n```bash\nphp -S 0.0.0.0:8080\n```\n\nThe above command will spawn a server on port 8080.\n\n\n#### Step 5: Verify your Fine Uploader client-side configuration\n\nEnsure all relevant configuration options are pointing to the path of the downloaded S3 signature endpoint server.\nFor example, if you placed composer.json in the root of your web server, the path to your PHP server will be\n`/vendor/fineuploader/php-s3-server/endpoint.php`. You'll need to be aware of this endpoint when setting\nthe [`request.endpoint`](../api/options-s3.html#request.endpoint), [`signature.endpoint`](../api/options-s3.html#signature.endpoint),\nand [`uploadSuccess.endpoint`](../api/options-s3.html#uploadSuccess.endpoint), and\n[`deleteFile.endpoint`](../api/options.html#deleteFile.endpoint) configuration options.\n\nA typical configuration may look like this:\n\n```javascript\nvar s3Uploader = new qq.s3.FineUploader({\n        element: document.getElementById(\"fineuploader-container\"),\n        debug: true,\n        request: {\n            endpoint: \"http://mybucket.s3.amazonaws.com\",\n            accessKey: \"my-client-public-key\"\n        },\n        signature: {\n            endpoint: \"/vendor/fineuploader/php-s3-server/endpoint.php\"\n        },\n        uploadSuccess: {\n            endpoint: \"/vendor/fineuploader/php-s3-server/endpoint.php?success\"\n        },\n        iframeSupport: {\n            localBlankPagePath: \"success.html\"\n        },\n        chunking: {\n            enabled: true,\n            concurrent: {\n                enabled: true\n            }\n        },\n        resume: {\n            enabled: true\n        },\n        retry: {\n            enableAuto: true,\n            showButton: true\n        },\n        deleteFile: {\n            enabled: true,\n            endpoint: \"/vendor/fineuploader/php-s3-server/endpoint.php\"\n        }\n    });\n```\n\n\n#### Step 6: Start uploading!\n\nYou're all set! If you do run into any problems you can ask a question on [Stack Overflow under the fine-uploader tag][so].\nServer example bugs should be reported in the [php-s3-server GitHub repository][repo].\n\n{% endmarkdown %}\n{% endblock %}\n"
  },
  {
    "path": "docs/quickstart/03-setting_up_server.jmd",
    "content": "{% extends \"_templates/base.html\" %}\n{% set page_title = \"Server Set-Up\" %}\n\n{% block content %}\n{% markdown %}\n\n[composer]: https://getcomposer.org/\n[packagist-url]: https://packagist.org/packages/fineuploader/php-traditional-server\n[phpini]: http://php.net/manual/en/configuration.file.php\n[repo]: https://github.com/FineUploader/php-traditional-server\n[so]: http://stackoverflow.com/questions/tagged/fine-uploader\n\n\n# Server Set-Up {: .page-header}\n\n{{ alert(\n\"\"\"This section of the tutorial is aimed at beginners looking to create their\nown upload server. Note that this example does not apply to S3 uploads. For\nmore information on uploading to Amazon's Simple Storage Service,\nread the [Fine Uploader S3 server documentation](../endpoint_handlers/amazon-s3.html).\"\"\",\n\"warn\") }}\n\nFor this tutorial we are going to use PHP to develop a simple server which will accept uploads from our Fine Uploader instance.\n\nIf PHP is not your style, then feel free to browse the other\n[server-side examples repository](https://github.com/FineUploader/server-examples).  **It's quite important for you to read\nthe [traditional server-side integration instructions](../endpoint_handlers/traditional.html) before you do any of this,\nthough**.\n\n### Getting Started\n\n#### Step 1: Download the example PHP server\n\nWe've [published a traditional endpoint PHP server for Fine Uploader to Packagist][packagist-url], which means you can easily download\nit to your project using [composer][composer]!\n\nFollow these simple steps to get a copy of the PHP traditional endpoint using Composer:\n\n1. Download Composer: `curl -sS https://getcomposer.org/installer | php`.\n2. Create a composer.json file and place it in the root of your project. See below for an example file.\n3. Install the Fine Uploader endpoint code: `php composer.phar install`.\n\nAn example composer.json file may look like this:\n\n```json\n{\n  \"require\": {\n    \"fineuploader/php-traditional-server\": \"1.1.0\"\n  }\n}\n```\n\nBe sure to update the version with the appropriate version of Fine Uploader's traditional PHP server.\n\n\n#### Step 2: Prepare your environment\n\nBe sure to include [a php.ini file][phpini] in the root of your web server with sensible request limits.\nAn example php.ini file looks like this:\n\n\n```php\nupload_max_filesize = 10M\npost_max_size = 10M\n```\n\n\n#### Step 3: Start the PHP server\n\nYou can easily start up a simple HTTP server using PHP. Run the following command in the root of your web server:\n\n```bash\nphp -S 0.0.0.0:8080 -t . -c php.ini\n```\n\nThe above command will spawn a server on port 8080.\n\n\n#### Step 4: Verify your Fine Uploader client-side configuration\n\nEnsure all configuration options are pointing to the path of the downloaded traditional endpoint server. For example,\nif you placed composer.json in the root of your web server, the path to your PHP server will be\n`/vendor/fineuploader/php-traditional-server/endpoint.php`. You'll need to be aware of this endpoint when setting\nthe [`request.endpoint`](../api/options.html#request.endpoint), [`deleteFile.endpoint`](../api/options.html#deleteFile.endpoint),\nand [`chunking.success.endpoint`](../api/options.html#chunking.success.endpoint) configuration options.\n\n\nA typical configuration may look like this:\n\n```javascript\nvar manualUploader = new qq.FineUploader({\n        element: document.getElementById(\"fineuploader-container\"),\n        request: {\n            endpoint: \"/vendor/fineuploader/php-traditional-server/endpoint.php\"\n        },\n        deleteFile: {\n            enabled: true,\n            endpoint: \"/vendor/fineuploader/php-traditional-server/endpoint.php\"\n        },\n        chunking: {\n            enabled: true,\n            concurrent: {\n                enabled: true\n            },\n            success: {\n                endpoint: \"/vendor/fineuploader/php-traditional-server/endpoint.php?done\"\n            }\n        },\n        resume: {\n            enabled: true\n        },\n        retry: {\n            enableAuto: true,\n            showButton: true\n        }\n    });\n```\n\n#### Step 5: Start uploading!\n\nYou're all set! If you do run into any problems you can ask a question on [Stack Overflow under the fine-uploader tag][so].\nServer example bugs should be reported in the [php-traditional-server GitHub repository][repo].\n\n\n{% endmarkdown %}\n{% endblock %}\n"
  },
  {
    "path": "docs/upgrading-to-4.jmd",
    "content": "{% extends \"_templates/base.html\" %}\n{% set page_title = \"Upgrading to 4.x\" %}\n{% block content %}\n{% markdown %}\n# Upgrading to 4.x {: .page-header }\n\nThe 4.0 version brings some breaking changes, mostly surrounding [templating](features/styling.html)\nin [Fine Uploader UI](modes/ui.html).  If you are not using Fine Uploader UI or the [standalone drag & drop module](features/drag-and-drop.html#drag-and-drop-standalone-module),\nyou will not be affected by these changes.\n\nBe sure to read about templating in the [styling guide](features/styling.html).\n\n\n## Fine Uploader UI breaking changes & upgrade instructions\n\nTemplating has been a mess in Fine Uploader UI for quite some time, arguably since day 1.  The goal was to clean up\nthis mess in 4.0 so that integrators/designers could more easily customize the look and feel of UI mode without\nresorting to core mode and developing a custom UI from scratch.  There will likely still be some cases where it makes\nmore sense to drop back to Fine Uploader Core and develop a new UI from scratch, but the improvements to templating\nin UI mode should make that scenario much less likely.\n\nAt a high level, the template markup was moved out of the\njavascript source and into the document.  You will need to, at least, include the default template bundled with Fine\nUploader in your document.  This step made many `classes` and `text` option properties redundant as well, so appropriate\nproperties were removed.  What follows is a more detailed description of these breaking changes, along with instructions\nthat should make the upgrade process from a 3.x build a bit easier.\n\n\n### Changed `template` option to take an ID (string) or element (pointing to script tag containing template)\n\nPreviously, the `template` option contained the top-level template markup (sans the file list markup) as a string.\nIf you wanted to change this, you needed to copy and paste this unwieldy string into your glue code and make appropriate\nchanges.  You also had to be careful about changing any of the CSS classes assigned to these template items.  If any\nCSS class was removed/renamed, you had to be sure to update the associated `classes` option property, since Fine Uploader\nUI used the same CSS classes for selection and styling.\n\nIn version 4.0, the `template` option expects either a string or an `Element` as a value (or a `jQuery` object if using\nthe jQuery plug-in wrapper).  You may either specify the ID of the container element in your document that contains\nthe template markup, or you may select the container element yourself.  The default value is a string: \"qq-template\".\n\nThe default template HTML file that is bundled with Fine Uploader contains a `<script>` with an ID of \"qq-template\" along\nwith all supported template items.  You can drop this into your document without any changes, and Fine Uploader UI\nwill work just as it did before.  If you want to further customize the template, you can make appropriate changes to\nthe markup in the `<script>` template contents.  You can remove almost any item in the template if you don't want it\nto be rendered in the DOM.  Note that each template item has a CSS class that ends in \"-selector\" which is used internally\nfor selecting the item.  A second CSS class may be present on the item, used only for styling.  You can remove this second\nCSS class without fear of breaking the uploader.  If you remove the \"-selector\" CSS class, Fine Uploader will simply\nignore the item, though it will still render in the DOM.\n\n### Removed the `fileTemplate` option\n\nThe contents of the old `template` and `fileTemplate` options have been combined into one template, which must be\nincluded as a `<script>` tag with a type of \"text/template\" in your document (or any hidden container element).\nYou can use the template contained in the included `default.html` file in the `templates` directory as-is,\nor modify it to match your application.\n\n### Template must be included in document script tag for Fine Uploader UI\n\nAs mentioned previously, even if you did not make any adjustments to the default `template` or `fileTemplate` options\nin older versions of Fine Uploader UI, you still must include a template in your document.  A default template is\nnow bundled with Fine Uploader.  You can use the bundled HTML file, or copy and paste the template `<script>` tag\ninto your document.\n\n### Various changes to the bundled CSS file\n\nThere were a few styling-related changes made to the CSS file that ships with the library.\n\nA `.qq-hide` style, which defaults to `display: none;` was also added.  You can override this default style\nvia increased specificity, or you can contribute a new value for this CSS class that Fine Uploader will assign\nto any element that it needs to hide.  See the `classes` option in the [styling guide](features/styling.html)\nfor more details.\n\nThe `.qq-upload-finished` style was removed, as this did not appear to serve a useful purpose anymore.\n\n### Removed `retry.showButton`\n\nIf you do not want the retry button displayed next to each file automatically when a manual retry is possible,\nsimply omit the retry-related element from your template.\n\n\n### Removed `editFilename.enabled`\n\nIf you do not want to utilize the edit filename feature, either ensure the `autoUpload` option is set to true\n(this is the default) or remove the edit-filename related elements from your template.  Currently, this involves removing\nthe \"qq-edit-filename-icon-selector\" and \"qq-edit-filename-selector\" elements.\n\n### Removed all `classes` option properties, other than the ones listed in styling doc\n\nIn most cases, there is no longer any need to define options for each class associated with a UI-mode template item.\nRemember, Fine Uploader UI 4.0's default template includes a \"-selector\" class on each template item specifically for\nselecting the item, and another for styling.  You can remove or rename the styling class without fear of breaking the\nuploader.  In fact, omitting most template items is possible as well.  The only `classes` properties that remain are\nones that are added by Fine Uploader dynamically/on-demand.  You can override any of these default values if you'd like\nto use one of your own custom CSS classes instead.\n\n### Added `classes.hide`\n\nPart of the 4.0 template rework included a push to stop assigning/changing style attributes on template items in the\njavascript code directly.  Previously, if Fine Uploader UI wanted to hide an element, it would add a `display: none;`\nstyle attribute value to the element, and then change this `display` property to a variable appropriate value when\nit was time to display the element again.  As of 4.0, to hide an element, Fine Uploader adds a `qq-hide` CSS class to\nthe element, which is defined with a default style of `display: none;` in the bundled CSS file.  When the element must\nbe visible again, this class is removed.  You can override the default CSS class name for hiding elements via\n`classes.hide`.\n\nNote that the standalone drag & drop module (which is also used internally by Fine Uploader UI) still sets a `display: none;`\nstyle directly on drop zones when they must be hidden, and changes this to `display: block;` to make them visible again.\nThis was left alone, as we didn't want to break all Fine Uploader Core mode apps that do not include Fine Uploader UI's\ndefault CSS file (since there has never been a dependency on this file when using core mode).\n\n\n### Removed all `text` option properties, except for `failUpload`, `formatProgress` and `waitingForResponse`\n\nSimilar to the changes to the `classes` option, there is no longer a need to have options dedicated to text that\nappears with template items in most cases.  You can easily change the default text in your template.  In cases where\nFine Uploader UI dynamically changes text based on conditions, `text` options remain for you to adjust if desired.\n\n### Removed `dragAndDrop.disableDefaultDropzone`\n\nIf you want to disable the default drop zone, simply remove the drop zone related element(s) from your template.\n\n### Removed `dragAndDrop.hideDropzones`\n\nIf you want an extra or default drop zone to remain invisible until an item approaches the drop area, simply include\na `qq-hide-dropzone` attribute on the drop zone container.  The attribute is present on the drop area in the\ndefault template, but it can be removed, of course.\n\n\n## Standalone drag & drop module breaking changes & upgrade instructions\n\n### Removed `hideDropZonesBeforeEnter` option in standalone DnD module (replaced w/ qq-hide-dropzone attr)\n\nThis is the only breaking change to the standalone drag & drop module.  Instead of specifying a value for a\n`hideDropZonesBeforeEnter` option that either hides all drop zones until an item approaches (or not) you can now control\nvisibility in this respect per-dropzone via a `qq-hide-dropzone` attribute on the drop zone container.\n\n\n{% endmarkdown %}\n{% endblock %}\n"
  },
  {
    "path": "docs/upgrading-to-5.jmd",
    "content": "{% extends \"_templates/base.html\" %}\n{% set page_title = \"Upgrading to 5.x\" %}\n{% block content %}\n{% markdown %}\n# Upgrading to 5.x {: .page-header }\n\nThe 5.0 version brings some breaking changes, mostly surrounding upload requests.\n\nIf you are upgrading from a 3.x version, be sure to also read the [Upgrading to 4.x document](upgrading-to-4.html).\n\n### Resume data persisted using localStorage for traditional endpoints\nFine Uploader no longer uses cookies to persist auto-resume data for traditional endpoints.  Now, all auto-resume\ndata is persisted to `localStorage` for all endpoint handlers.\n\nThis means that any existing auto-resume records will be lost when you switch to a 5.x version if you utilize\na traditional endpoint handler.\n\n\n### Persisted resume data format changed for S3 and Azure upload endpoints\nThis means that any existing resume records will be ignored.  This format change was required to\nsupport the new concurrent chunking feature.\n\n\n### Most XHR requests will now contain an Accept header\nThis is probably not technically a breaking change, but it should be mentioned here just in case your\nserver, for some reason, is not expecting this header on requests from Fine Uploader.  This change\naffects all `XMLHttpRequest` originated requests sent to local servers\n(i.e. not requests sent to S3/Azure).\n\nYour can look at this header to programmatically determine the correct Content-Type for your response,\nbut you should **NOT** rely on this value for upload requests in IE9 and older since the Accept header\nis set by the browser and is not reflective of the expected response Content-Type.  Also, ajax requests\nsent in IE9 and IE8 in a cross-origin environment will not contain this header, since\nthe `XDomainRequest` transport is used and does not allow headers to be specified.\n\n\n### Removed all utility functions that deal with cookies\nAs a result of the switch from cookies to `localStorage`, we removed these cookie-related utility\nfunctions as they are no longer needed by Fine Uploader.  If you were utilizing these utility functions for your app,\nconsider importing a cookie library as a replacement, such as [jquery-cookie][jcookie] or [Cookies][Cookies].\n\nThe specific removed util functions were:\n\n* `qq.areCookiesEnabled`\n* `qq.getCookie`\n* `qq.setCookie`\n* `qq.deleteCookie`\n\n\n### Changes to `resume` option\nThe `id` property of the `resume` option was removed.  Very few integrators, if any, were making use of this option.\nIf you were making use of this option property, any existing resume records will be lost when you upgrade to 5.x.\n\nAlso, for traditional endpoints, the `cookiesExpireIn` option has changed to `recordsExpireIn`.  This is related to\nthe switch from cookies to `localStorage`.\n\n\n### onResume callback is no longer promissory\nA promise is no longer an acceptable return type from an `onResume` callback.  It was unclear what benefit this\nprovided, and supporting this made the code more complicated.  Due to a long-standing oversight in the code,\npromises were only accepted from `onResume` callbacks when using the traditional endpoint handler anyway.\n\n\n### Chunked request only sent for traditional endpoints if necessary\nIf the chunking.partSize value is greater than or equal to the size of the\nfile, no chunking will occur. Note that a chunked request is different from\na non-chunked request due to differing headers, and the fact that the\nmessage-body of a chunked request contains a Blob, while a non-chunked request\nmessage-body contains a File (assuming the file is not a scaled version and\nthe original file is not a Blob itself).\n\nThis means that, even if chunking is enabled, a chunked upload request is\nonly sent to traditional endpoints if the associated file can be broken into\nmore than 1 chunk.  This behavior is consistent with the way all other\nendpoints have functioned.  Previously, a chunked request would always be\nsent for traditional endpoints if chunking was supported and enabled.\n\n\n### `drawThumbnail` API method only passes one argument to rejected promise callback\nThis was done to bring the API that Fine Uploader exposes in compliance with the A+ promises spec.  While\n`qq.Promise` is not A+ compliant, use of other promise implementations when communicating with Fine Uploader\nis now supported.  Since the spec indicates that only one argument can be passed to a resolve or reject callback,\nthe promise returned by the [`drawThumbnail` API method][drawthumbnail] had to be modified.  The resolve callback\nalready was only passed one argument (the container element), this breaking change only affects the reject callback.\nPreviously, two arguments were passed, in the event of an error, to the reject callback: the container element, and\nan error message.  Now, an object with `container` and `error` properties is the only argument.\n\n[Cookies]: https://github.com/ScottHamper/Cookies\n[drawthumbnail]: api/methods.html#drawThumbnail\n[jcookie]: https://github.com/carhartl/jquery-cookie\n\n{% endmarkdown %}\n{% endblock %}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"fine-uploader\",\n  \"title\": \"Fine Uploader\",\n  \"main\": \"lib/traditional.js\",\n  \"types\" : \"typescript/fine-uploader.d.ts\",\n  \"version\": \"5.16.2\",\n  \"description\": \"Multiple file upload plugin with progress-bar, drag-and-drop, direct-to-S3 & Azure uploading, client-side image scaling, preview generation, form support, chunking, auto-resume, and tons of other features.\",\n  \"keywords\": [\n    \"amazon\",\n    \"api\",\n    \"aws\",\n    \"azure\",\n    \"chunk\",\n    \"chunking\",\n    \"cross-domain\",\n    \"cross-site\",\n    \"drag\",\n    \"drop\",\n    \"file\",\n    \"file-input\",\n    \"file-uploader\",\n    \"input\",\n    \"jquery\",\n    \"jquery-plugin\",\n    \"multiple\",\n    \"preview\",\n    \"progress\",\n    \"resume\",\n    \"s3\",\n    \"selection\",\n    \"upload\",\n    \"widget\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git://github.com/FineUploader/fine-uploader.git\"\n  },\n  \"author\": \"Ray Nicholus\",\n  \"contributors\": [\n    {\n      \"name\": \"Ray Nicholus\",\n      \"url\": \"http://raynicholus.com\"\n    }\n  ],\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/FineUploader/fine-uploader/issues\"\n  },\n  \"devDependencies\": {\n    \"clean-css-cli\": \"4.0.8\",\n    \"jscs\": \"3.0.7\",\n    \"jshint\": \"2.9.4\",\n    \"karma\": \"1.5.0\",\n    \"karma-firefox-launcher\": \"1.0.0\",\n    \"karma-mocha\": \"1.3.0\",\n    \"karma-spec-reporter\": \"0.0.30\",\n    \"mocha\": \"3.2.0\",\n    \"node-static\": \"0.7.9\",\n    \"pica\": \"2.0.8\",\n    \"uglify-js\": \"2.8.3\"\n  },\n  \"scripts\": {\n    \"build\": \"make clean; make build-all-ui\",\n    \"lint\": \"make lint\",\n    \"start\": \"make start-local-dev\",\n    \"test\": \"make -i lint && make test\"\n  }\n}\n"
  },
  {
    "path": "test/dev/devenv.js",
    "content": "qq(window).attach(\"load\", function() {\n    \"use strict\";\n\n    var errorHandler = function(id, fileName, reason) {\n            return qq.log(\"id: \" + id + \", fileName: \" + fileName + \", reason: \" + reason);\n        },\n        azureUploader, s3Uploader, manualUploader, validatingUploader, failingUploader;\n\n    manualUploader = new qq.FineUploader({\n        element: document.getElementById(\"manual-example\"),\n        autoUpload: false,\n        debug: false,\n        uploadButtonText: \"Select Files\",\n        display: {\n            fileSizeOnSubmit: true\n        },\n        request: {\n            endpoint: \"/test/dev/handlers/vendor/fineuploader/php-traditional-server/endpoint.php\"\n        },\n        deleteFile: {\n            enabled: true,\n            endpoint: \"/test/dev/handlers/vendor/fineuploader/php-traditional-server/endpoint.php\",\n            forceConfirm: true,\n            params: {\n                foo: \"bar\"\n            }\n        },\n        chunking: {\n            enabled: true,\n            concurrent: {\n                enabled: false\n            },\n            success: {\n                endpoint: \"/test/dev/handlers/vendor/fineuploader/php-traditional-server/endpoint.php?done\"\n            }\n        },\n        resume: {\n            enabled: true\n        },\n        retry: {\n            enableAuto: true\n        },\n        thumbnails: {\n            customResizer: !qq.ios() && function(resizeInfo) {\n                var promise = new qq.Promise();\n\n                pica.resizeCanvas(resizeInfo.sourceCanvas, resizeInfo.targetCanvas, {}, function() {\n                    promise.success();\n                })\n\n                return promise;\n            },\n            placeholders: {\n                waitingPath: \"/client/placeholders/waiting-generic.png\",\n                notAvailablePath: \"/client/placeholders/not_available-generic.png\"\n            }\n        },\n        scaling: {\n            customResizer: !qq.ios() && function(resizeInfo) {\n                var promise = new qq.Promise();\n\n                pica.resizeCanvas(resizeInfo.sourceCanvas, resizeInfo.targetCanvas, {}, function() {\n                    promise.success();\n                })\n\n                return promise;\n            },\n            sizes: [{name: \"small\", maxSize: 800}]\n        },\n        session: {\n            //endpoint: \"/test/dev/handlers/vendor/fineuploader/php-traditional-server/endpoint.php?initial\"\n        },\n        callbacks: {\n            onError: errorHandler,\n            onUpload: function (id, filename) {\n                this.setParams({\n                    \"hey\": \"hi ɛ $ hmm \\\\ hi\",\n                    \"ho\": \"foobar\"\n                }, id);\n\n            }\n        }\n    });\n\n    qq(document.getElementById(\"triggerUpload\")).attach(\"click\", function() {\n        manualUploader.uploadStoredFiles();\n    });\n\n\n    s3Uploader = new qq.s3.FineUploader({\n        element: document.getElementById(\"s3-example\"),\n        debug: true,\n        request: {\n            endpoint: \"http://fineuploadertest.s3.amazonaws.com\",\n            accessKey: \"AKIAIXVR6TANOGNBGANQ\"\n        },\n        signature: {\n            endpoint: \"/test/dev/handlers/vendor/fineuploader/php-s3-server/endpoint.php\"\n        },\n        uploadSuccess: {\n            endpoint: \"/test/dev/handlers/vendor/fineuploader/php-s3-server/endpoint.php?success\"\n        },\n        iframeSupport: {\n            localBlankPagePath: \"success.html\"\n        },\n        chunking: {\n            enabled: true,\n            concurrent: {\n                enabled: true\n            }\n        },\n        resume: {\n            enabled: true\n        },\n        retry: {\n            enableAuto: true,\n            showButton: true\n        },\n        deleteFile: {\n            enabled: true,\n            endpoint: \"/test/dev/handlers/vendor/fineuploader/php-s3-server/endpoint.php\",\n            forceConfirm: true,\n            params: {\n                foo: \"bar\"\n            }\n        },\n        failedUploadTextDisplay: {\n            mode: \"custom\"\n        },\n        display: {\n            fileSizeOnSubmit: true\n        },\n        paste: {\n            targetElement: document,\n            promptForName: true\n        },\n        thumbnails: {\n            placeholders: {\n                waitingPath: \"/client/placeholders/waiting-generic.png\",\n                notAvailablePath: \"/client/placeholders/not_available-generic.png\"\n            }\n        },\n        workarounds: {\n            ios8BrowserCrash: false\n        },\n        callbacks: {\n            onError: errorHandler,\n            onUpload: function(id, filename) {\n                this.setParams({\n                    \"hey\": \"hi ɛ $ hmm \\\\ hi\",\n                    \"ho\": \"foobar\"\n                }, id);\n\n            },\n            onStatusChange: function(id, oldS, newS) {\n                qq.log(\"id: \" + id + \" \" + newS);\n            },\n            onComplete: function(id, name, response) {\n                qq.log(response);\n            }\n        }\n    });\n\n    if (qq.supportedFeatures.ajaxUploading) {\n        azureUploader = new qq.azure.FineUploader({\n            element: document.getElementById(\"azure-example\"),\n            debug: true,\n            request: {\n                endpoint: \"http://fineuploaderdev2.blob.core.windows.net/dev\"\n            },\n            cors: {\n                expected: true\n            },\n            signature: {\n                endpoint: \"http://192.168.56.101:8080/sas\"\n            },\n            uploadSuccess: {\n                endpoint: \"http://192.168.56.101:8080/success\"\n            },\n            chunking: {\n                enabled: true,\n                concurrent: {\n                    enabled: true\n                }\n            },\n            resume: {\n                enabled: true\n            },\n            retry: {\n                enableAuto: true,\n                showButton: true\n            },\n            deleteFile: {\n                enabled: true\n            },\n            display: {\n                fileSizeOnSubmit: true\n            },\n            paste: {\n                targetElement: document\n            },\n            thumbnails: {\n                placeholders: {\n                    waitingPath: \"/client/placeholders/waiting-generic.png\",\n                    notAvailablePath: \"/client/placeholders/not_available-generic.png\"\n                }\n            },\n            callbacks: {\n                onError: errorHandler,\n                onUpload: function (id, filename) {\n                    this.setParams({\n                        \"hey\": \"hi ɛ $ hmm \\\\ hi\",\n                        \"ho\": \"foobar\"\n                    }, id);\n\n                },\n                onStatusChange: function (id, oldS, newS) {\n                    qq.log(\"id: \" + id + \" \" + newS);\n                },\n                onComplete: function (id, name, response) {\n                    qq.log(response);\n                }\n            }\n        });\n    }\n\n    failingUploader = new qq.FineUploader({\n        element: document.getElementById(\"failure-example\"),\n        request: {\n            endpoint: \"/test/dev/handlers/traditional/endpoint.php\",\n            params: {\n                generateError: \"true\"\n            }\n        },\n        debug: true,\n        failedUploadTextDisplay: {\n            mode: \"custom\"\n        },\n        retry: {\n            enableAuto: true,\n            showButton: true\n        },\n        thumbnails: {\n            placeholders: {\n                waitingPath: \"/client/placeholders/waiting-generic.png\",\n                notAvailablePath: \"/client/placeholders/not_available-generic.png\"\n            }\n        },\n        callbacks: {\n            onError: errorHandler\n        }\n    });\n\n    validatingUploader = new qq.FineUploader({\n        element: document.getElementById(\"validation-example\"),\n        multiple: false,\n        request: {\n            endpoint: \"/test/dev/handlers/traditional/endpoint.php\"\n        },\n        debug: true,\n        validation: {\n            allowedExtensions: [\"jpeg\", \"jpg\", \"txt\"],\n            sizeLimit: 50000,\n            minSizeLimit: 2000\n        },\n        text: {\n            uploadButton: \"Click Or Drop\"\n        },\n        display: {\n            fileSizeOnSubmit: true\n        },\n        callbacks: {\n            onError: errorHandler\n        }\n    });\n});"
  },
  {
    "path": "test/dev/handlers/composer.json",
    "content": "{\n  \"minimum-stability\": \"alpha\",\n  \"repositories\": [\n    {\n      \"type\": \"vcs\",\n      \"url\": \"https://github.com/FineUploader/php-s3-server\"\n    }\n  ],\n  \"require\": {\n    \"fineuploader/php-traditional-server\": \"1.0.1\",\n    \"fineuploader/php-s3-server\": \"1.1.0\"\n  }\n}\n"
  },
  {
    "path": "test/dev/handlers/php.ini",
    "content": "upload_max_filesize = 10M\npost_max_size = 10M"
  },
  {
    "path": "test/dev/index.html",
    "content": "<!DOCTYPE html>\n<html>\n    <head>\n        <!--GALLERY TEMPLATE-->\n        <script type=\"text/template\" id=\"qq-template\">\n            <div class=\"qq-uploader-selector qq-uploader qq-gallery\" qq-drop-area-text=\"Drop files here\">\n                <div class=\"qq-total-progress-bar-container-selector qq-total-progress-bar-container\">\n                    <div role=\"progressbar\" aria-valuenow=\"0\" aria-valuemin=\"0\" aria-valuemax=\"100\" class=\"qq-total-progress-bar-selector qq-progress-bar qq-total-progress-bar\"></div>\n                </div>\n                <div class=\"qq-upload-drop-area-selector qq-upload-drop-area\" qq-hide-dropzone>\n                    <span class=\"qq-upload-drop-area-text-selector\">Drop files here to upload</span>\n                </div>\n                <div class=\"qq-upload-button-selector qq-upload-button\">\n                    <div>Upload a file</div>\n                </div>\n                <span class=\"qq-drop-processing-selector qq-drop-processing\">\n                    <span>Processing dropped files...</span>\n                    <span class=\"qq-drop-processing-spinner-selector qq-drop-processing-spinner\"></span>\n                </span>\n                <ul class=\"qq-upload-list-selector qq-upload-list\" role=\"region\" aria-live=\"polite\" aria-relevant=\"additions removals\">\n                    <li>\n                        <span role=\"status\" class=\"qq-upload-status-text-selector qq-upload-status-text\"></span>\n                        <div class=\"qq-progress-bar-container-selector qq-progress-bar-container\">\n                            <div role=\"progressbar\" aria-valuenow=\"0\" aria-valuemin=\"0\" aria-valuemax=\"100\" class=\"qq-progress-bar-selector qq-progress-bar\"></div>\n                        </div>\n                        <span class=\"qq-upload-spinner-selector qq-upload-spinner\"></span>\n                        <div class=\"qq-thumbnail-wrapper\">\n                            <img class=\"qq-thumbnail-selector\" qq-max-size=\"120\" qq-server-scale>\n                        </div>\n                        <button type=\"button\" class=\"qq-upload-cancel-selector qq-upload-cancel\">X</button>\n                        <button type=\"button\" class=\"qq-upload-retry-selector qq-upload-retry\">\n                            <span class=\"qq-btn qq-retry-icon\" aria-label=\"Retry\"></span>\n                            Retry\n                        </button>\n\n                        <div class=\"qq-file-info\">\n                            <div class=\"qq-file-name\">\n                                <span class=\"qq-upload-file-selector qq-upload-file\"></span>\n                                <span class=\"qq-edit-filename-icon-selector qq-btn qq-edit-filename-icon\" aria-label=\"Edit filename\"></span>\n                            </div>\n                            <input class=\"qq-edit-filename-selector qq-edit-filename\" tabindex=\"0\" type=\"text\">\n                            <span class=\"qq-upload-size-selector qq-upload-size\"></span>\n                            <button type=\"button\" class=\"qq-btn qq-upload-delete-selector qq-upload-delete\">\n                                <span class=\"qq-btn qq-delete-icon\" aria-label=\"Delete\"></span>\n                            </button>\n                            <button type=\"button\" class=\"qq-btn qq-upload-pause-selector qq-upload-pause\">\n                                <span class=\"qq-btn qq-pause-icon\" aria-label=\"Pause\"></span>\n                            </button>\n                            <button type=\"button\" class=\"qq-btn qq-upload-continue-selector qq-upload-continue\">\n                                <span class=\"qq-btn qq-continue-icon\" aria-label=\"Continue\"></span>\n                            </button>\n                        </div>\n                    </li>\n                </ul>\n\n                <dialog class=\"qq-alert-dialog-selector\">\n                    <div class=\"qq-dialog-message-selector\"></div>\n                    <div class=\"qq-dialog-buttons\">\n                        <button type=\"button\" class=\"qq-cancel-button-selector\">Close</button>\n                    </div>\n                </dialog>\n\n                <dialog class=\"qq-confirm-dialog-selector\">\n                    <div class=\"qq-dialog-message-selector\"></div>\n                    <div class=\"qq-dialog-buttons\">\n                        <button type=\"button\" class=\"qq-cancel-button-selector\">No</button>\n                        <button type=\"button\" class=\"qq-ok-button-selector\">Yes</button>\n                    </div>\n                </dialog>\n\n                <dialog class=\"qq-prompt-dialog-selector\">\n                    <div class=\"qq-dialog-message-selector\"></div>\n                    <input type=\"text\">\n                    <div class=\"qq-dialog-buttons\">\n                        <button type=\"button\" class=\"qq-cancel-button-selector\">Cancel</button>\n                        <button type=\"button\" class=\"qq-ok-button-selector\">Ok</button>\n                    </div>\n                </dialog>\n            </div>\n\n        </script>\n\n        <!--THUMBNAILS TEMPLATE-->\n        <!--<script type=\"text/template\" id=\"qq-template\">-->\n            <!--<div class=\"qq-uploader-selector qq-uploader\" qq-drop-area-text=\"Drop files here\">-->\n                <!--<div class=\"qq-total-progress-bar-container-selector qq-total-progress-bar-container\">-->\n                    <!--<div role=\"progressbar\" aria-valuenow=\"0\" aria-valuemin=\"0\" aria-valuemax=\"100\" class=\"qq-total-progress-bar-selector qq-progress-bar qq-total-progress-bar\"></div>-->\n                <!--</div>-->\n                <!--<div class=\"qq-upload-drop-area-selector qq-upload-drop-area\" qq-hide-dropzone>-->\n                    <!--<span class=\"qq-upload-drop-area-text-selector\">Drop files here to upload</span>-->\n                <!--</div>-->\n                <!--<div class=\"qq-upload-button-selector qq-upload-button\">-->\n                    <!--<div>Upload a file</div>-->\n                <!--</div>-->\n                <!--<span class=\"qq-drop-processing-selector qq-drop-processing\">-->\n                    <!--<span>Processing dropped files...</span>-->\n                    <!--<span class=\"qq-drop-processing-spinner-selector qq-drop-processing-spinner\"></span>-->\n                <!--</span>-->\n                <!--<ul class=\"qq-upload-list-selector qq-upload-list\" aria-live=\"polite\" aria-relevant=\"additions removals\">-->\n                    <!--<li>-->\n                        <!--<div class=\"qq-progress-bar-container-selector\">-->\n                            <!--<div role=\"progressbar\" aria-valuenow=\"0\" aria-valuemin=\"0\" aria-valuemax=\"100\" class=\"qq-progress-bar-selector qq-progress-bar\"></div>-->\n                        <!--</div>-->\n                        <!--<span class=\"qq-upload-spinner-selector qq-upload-spinner\"></span>-->\n                        <!--<img class=\"qq-thumbnail-selector\" qq-max-size=\"100\" qq-server-scale>-->\n                        <!--<span class=\"qq-upload-file-selector qq-upload-file\"></span>-->\n                        <!--<span class=\"qq-edit-filename-icon-selector qq-edit-filename-icon\" aria-label=\"Edit filename\"></span>-->\n                        <!--<input class=\"qq-edit-filename-selector qq-edit-filename\" tabindex=\"0\" type=\"text\">-->\n                        <!--<span class=\"qq-upload-size-selector qq-upload-size\"></span>-->\n                        <!--<button type=\"button\" class=\"qq-btn qq-upload-cancel-selector qq-upload-cancel\">Cancel</button>-->\n                        <!--<button type=\"button\" class=\"qq-btn qq-upload-retry-selector qq-upload-retry\">Retry</button>-->\n                        <!--<button type=\"button\" class=\"qq-btn qq-upload-delete-selector qq-upload-delete\">Delete</button>-->\n                        <!--<span role=\"status\" class=\"qq-upload-status-text-selector qq-upload-status-text\"></span>-->\n                    <!--</li>-->\n                <!--</ul>-->\n\n                <!--<dialog class=\"qq-alert-dialog-selector\">-->\n                    <!--<div class=\"qq-dialog-message-selector\"></div>-->\n                    <!--<div class=\"qq-dialog-buttons\">-->\n                        <!--<button type=\"button\" class=\"qq-cancel-button-selector\">Close</button>-->\n                    <!--</div>-->\n                <!--</dialog>-->\n\n                <!--<dialog class=\"qq-confirm-dialog-selector\">-->\n                    <!--<div class=\"qq-dialog-message-selector\"></div>-->\n                    <!--<div class=\"qq-dialog-buttons\">-->\n                        <!--<button type=\"button\" class=\"qq-cancel-button-selector\">No</button>-->\n                        <!--<button type=\"button\" class=\"qq-ok-button-selector\">Yes</button>-->\n                    <!--</div>-->\n                <!--</dialog>-->\n\n                <!--<dialog class=\"qq-prompt-dialog-selector\">-->\n                    <!--<div class=\"qq-dialog-message-selector\"></div>-->\n                    <!--<input type=\"text\">-->\n                    <!--<div class=\"qq-dialog-buttons\">-->\n                        <!--<button type=\"button\" class=\"qq-cancel-button-selector\">Cancel</button>-->\n                        <!--<button type=\"button\" class=\"qq-ok-button-selector\">Ok</button>-->\n                    <!--</div>-->\n                <!--</dialog>-->\n            <!--</div>-->\n        <!--</script>-->\n\n        <!--SIMPLE TEMPLATE-->\n        <!--<script type=\"text/template\" id=\"qq-template\">-->\n            <!--<div class=\"qq-uploader-selector qq-uploader\" qq-drop-area-text=\"Drop files here\">-->\n                <!--<div class=\"qq-total-progress-bar-container-selector qq-total-progress-bar-container\">-->\n                    <!--<div role=\"progressbar\" aria-valuenow=\"0\" aria-valuemin=\"0\" aria-valuemax=\"100\" class=\"qq-total-progress-bar-selector qq-progress-bar qq-total-progress-bar\"></div>-->\n                <!--</div>-->\n                <!--<div class=\"qq-upload-drop-area-selector qq-upload-drop-area\" qq-hide-dropzone>-->\n                    <!--<span class=\"qq-upload-drop-area-text-selector\">Drop files here to upload</span>-->\n                <!--</div>-->\n                <!--<div class=\"qq-upload-button-selector qq-upload-button\">-->\n                    <!--<div>Upload a file</div>-->\n                <!--</div>-->\n                <!--<span class=\"qq-drop-processing-selector qq-drop-processing\">-->\n                    <!--<span>Processing dropped files...</span>-->\n                    <!--<span class=\"qq-drop-processing-spinner-selector qq-drop-processing-spinner\"></span>-->\n                <!--</span>-->\n                <!--<ul class=\"qq-upload-list-selector qq-upload-list\" aria-live=\"polite\" aria-relevant=\"additions removals\">-->\n                    <!--<li>-->\n                        <!--<div class=\"qq-progress-bar-container-selector\">-->\n                            <!--<div role=\"progressbar\" aria-valuenow=\"0\" aria-valuemin=\"0\" aria-valuemax=\"100\" class=\"qq-progress-bar-selector qq-progress-bar\"></div>-->\n                        <!--</div>-->\n                        <!--<span class=\"qq-upload-spinner-selector qq-upload-spinner\"></span>-->\n                        <!--<span class=\"qq-upload-file-selector qq-upload-file\"></span>-->\n                        <!--<span class=\"qq-edit-filename-icon-selector qq-edit-filename-icon\" aria-label=\"Edit filename\"></span>-->\n                        <!--<input class=\"qq-edit-filename-selector qq-edit-filename\" tabindex=\"0\" type=\"text\">-->\n                        <!--<span class=\"qq-upload-size-selector qq-upload-size\"></span>-->\n                        <!--<button type=\"button\" class=\"qq-btn qq-upload-cancel-selector qq-upload-cancel\">Cancel</button>-->\n                        <!--<button type=\"button\" class=\"qq-btn qq-upload-retry-selector qq-upload-retry\">Retry</button>-->\n                        <!--<button type=\"button\" class=\"qq-btn qq-upload-delete-selector qq-upload-delete\">Delete</button>-->\n                        <!--<span role=\"status\" class=\"qq-upload-status-text-selector qq-upload-status-text\"></span>-->\n                    <!--</li>-->\n                <!--</ul>-->\n\n                <!--<dialog class=\"qq-alert-dialog-selector\">-->\n                    <!--<div class=\"qq-dialog-message-selector\"></div>-->\n                    <!--<div class=\"qq-dialog-buttons\">-->\n                        <!--<button type=\"button\" class=\"qq-cancel-button-selector\">Close</button>-->\n                    <!--</div>-->\n                <!--</dialog>-->\n\n                <!--<dialog class=\"qq-confirm-dialog-selector\">-->\n                    <!--<div class=\"qq-dialog-message-selector\"></div>-->\n                    <!--<div class=\"qq-dialog-buttons\">-->\n                        <!--<button type=\"button\" class=\"qq-cancel-button-selector\">No</button>-->\n                        <!--<button type=\"button\" class=\"qq-ok-button-selector\">Yes</button>-->\n                    <!--</div>-->\n                <!--</dialog>-->\n\n                <!--<dialog class=\"qq-prompt-dialog-selector\">-->\n                    <!--<div class=\"qq-dialog-message-selector\"></div>-->\n                    <!--<input type=\"text\">-->\n                    <!--<div class=\"qq-dialog-buttons\">-->\n                        <!--<button type=\"button\" class=\"qq-cancel-button-selector\">Cancel</button>-->\n                        <!--<button type=\"button\" class=\"qq-ok-button-selector\">Ok</button>-->\n                    <!--</div>-->\n                <!--</dialog>-->\n            <!--</div>-->\n        <!--</script>-->\n\n        <title>Fine Uploader Development</title>\n        <meta charset=\"utf-8\" />\n        <link href=\"https://netdna.bootstrapcdn.com/twitter-bootstrap/2.1.0/css/bootstrap.min.css\" rel=\"stylesheet\" type=\"text/css\"/>\n        <link href=\"../../_build/fine-uploader-gallery.css\" rel=\"stylesheet\" type=\"text/css\"/>\n        <link href=\"../../_build/fine-uploader-new.css\" rel=\"stylesheet\" type=\"text/css\"/>\n        <!--<link href=\"../../_build/fine-uploader.css\" rel=\"stylesheet\" type=\"text/css\"/>-->\n        <link href=\"styles.css\" rel=\"stylesheet\" type=\"text/css\"/>\n        <script src=\"../../_build/all.fine-uploader.js\"></script>\n        <script src=\"../../node_modules/pica/dist/pica.js\"></script>\n        <script src=\"devenv.js\"></script>\n    </head>\n    <body>\n        <ul id=\"foobar\"></ul>\n        <h1>Fine Uploader Development</h1>\n\n        <img id=\"paste-test-img\" src=\"http://fineuploader.smartimage.com/thumbnail/c7DolT/960px/Fine-Uploader-Avatar.png?ref=ce8936f6e\"/>\n\n        <div id=\"examples\">\n            <div class=\"example\">\n                <h3>Manually Trigger Uploads</h3>\n                <ul id=\"manual-example\" class=\"unstyled\"></ul>\n                <button type=\"button\" id=\"triggerUpload\" class=\"btn btn-primary\">Upload Queued Files</button>\n            </div>\n\n            <div class=\"example\">\n                <h3>FU S3</h3>\n                <ul id=\"s3-example\" class=\"unstyled\"></ul>\n            </div>\n\n            <div class=\"example\">\n                <h3>FU Azure</h3>\n                <ul id=\"azure-example\" class=\"unstyled\"></ul>\n            </div>\n\n            <div class=\"example\">\n                <h3>See server failure messages</h3>\n                <ul id=\"failure-example\" class=\"unstyled\"></ul>\n            </div>\n\n            <div class=\"example\">\n                <h3>Various Options</h3>\n                <ul id=\"validation-example\" class=\"unstyled\"></ul>\n            </div>\n\n            <div class=\"example\">\n                <h3>FineUploaderBasic</h3>\n                <div id=\"core-example\"></div>\n                <div id=\"fubUploadButton\" class=\"btn btn-primary\"><div>Select Files</div></div>\n            </div>\n        </div>\n    </body>\n</html>\n\n"
  },
  {
    "path": "test/dev/styles.css",
    "content": "H1 {\n    text-align: center;\n}\n\n#examples {\n    padding-left: 10px;\n}\n\n.endingRemark {\n    margin-top: 30px;\n}\n\nDIV.example {\n    margin-top: 50px;\n}\n\nDIV.example ul {\n    width: 650px;\n}\n\nA {\n    color: green;\n}\n\nA.decorate {\n    text-decoration: underline;\n}\n\n#paste-test-img { width: 125px; }"
  },
  {
    "path": "test/static/local/blob-maker.js",
    "content": "/*globals qq, assert*/\nvar qqtest = qqtest || {};\n$.extend(qqtest, {\n    canDownloadFileAsBlob: !qq.android() && qq.supportedFeatures.ajaxUploading && Boolean(window.FileReader),\n\n    _downloadedFiles: {},\n\n    downloadFileAsBlob: function(key, type) {\n        \"use strict\";\n\n        var BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder ||\n                             window.MozBlobBuilder || window.MSBlobBuilder,\n            xhr = new XMLHttpRequest(),\n            downloadAsync = new qq.Promise(),\n            blobBuilder = BlobBuilder && new BlobBuilder(),\n            self = this;\n\n        if (self._downloadedFiles[key]) {\n            downloadAsync.success(self._downloadedFiles[key]);\n        }\n        else {\n            xhr.open(\"GET\", \"http://\" + window.location.hostname + \":4000/\" + key, true);\n            xhr.responseType = \"arraybuffer\";\n\n            xhr.onerror = function() {\n                assert.fail(null, null, \"Failed to download test file!\");\n                downloadAsync.failure();\n            };\n\n            xhr.onload = function() {\n                if (this.status === 200) {\n                    if (blobBuilder) {\n                        blobBuilder.append(this.response);\n                        self._downloadedFiles[key] = blobBuilder.getBlob(type);\n                    }\n                    else {\n                        self._downloadedFiles[key] = new Blob([this.response], {type: type});\n                    }\n\n                    downloadAsync.success(self._downloadedFiles[key]);\n                }\n                else {\n                    assert.fail(null, null, \"Failed to download test file! Status: \" + this.status);\n                    downloadAsync.failure();\n                }\n            };\n\n            xhr.send();\n        }\n\n        return downloadAsync;\n    }\n}, true);\n"
  },
  {
    "path": "test/static/local/client.js",
    "content": "/* globals mocha */\nfunction FancyJSON(runner) {\n    \"use strict\";\n\n    var root = null;\n    var result = {};\n\n    function recurse(suite, result) {\n\n        result.durationSec = 0;\n        result.passed = true;\n\n        if(suite.title) {\n            result.description = suite.title;\n        }\n\n        if(suite.tests.length) {\n            result.specs = [];\n            var i = 0;\n            for (i; i < suite.tests.length; i++) {\n\n                if (!suite.tests[i].pending) {\n                    result.specs.push({\n                        \"description\": suite.tests[i].title,\n                        \"durationSec\": (suite.tests[i].duration / 1000) || 0, // duration of spec run in seconds\n                        \"passed\": suite.tests[i].state === \"passed\" // did the spec pass?\n                        //\"passedCount\": 1, // passed assertions in spec\n                        //\"failedCount\": 0, // failed assertions in spec\n                        //\"totalCount\": 1 // total assertions in spec\n                    });\n\n                    result.durationSec += (suite.tests[i].duration / 1000) || 0;\n\n                    if(suite.tests[i].state !== \"passed\") {\n                        result.passed = false;\n                    }\n                }\n\n            }\n        }\n\n        if(suite.suites.length) {\n\n            result.suites = [];\n\n            var sub = null;\n            var j = 0;\n            for (j; j < suite.suites.length; j++) {\n\n                sub = {};\n                recurse(suite.suites[j], sub);\n                result.suites.push(sub);\n\n                result.durationSec += sub.durationSec || 0;\n\n                if(!sub.passed) {\n                    result.passed = false;\n                }\n\n            }\n\n        }\n\n    }\n\n    runner.on(\"suite\", function(suite) {\n        if(suite.parent.root) { root = suite.parent; }\n    });\n\n    runner.on(\"end\", function() {\n        recurse(root, result);\n        window.jsonReport = result;\n    });\n\n}\n\n\nfunction mochaSaucePlease(fn) {\n    \"use strict\";\n\n    (function(runner) {\n\n        // execute optional callback to give user access to the runner\n        if(fn) {\n            fn(runner);\n        }\n\n        // in a PhantomJS environment, things are different\n        if(!runner.on) {\n            return;\n        }\n\n        // Generate JSON coverage\n        mocha.reporter(FancyJSON);\n        var m = new mocha._reporter(runner);\n\n        // Generate XUnit coverage\n        //window.xUnitReport = '';\n        //(function() {\n        //    var log = console && console.log;\n        //    console.log = function() {\n        //        window.xUnitReport += arguments[0] + \"\\n\"; // TODO: handle complex console.log\n        //        if(log) log.apply(console, arguments);\n        //    };\n        //})();\n        //mocha.reporter(\"xunit\");\n        //new mocha._reporter(runner);\n\n        // The Grid view needs more info about failures\n        var failed = [];\n        runner.on(\"fail\", function(test, err) {\n            failed.push({\n                title: test.title,\n                fullTitle: test.fullTitle(),\n                error: {\n                    message: err.message,\n                    stack: err.stack\n                }\n            });\n        });\n\n        // implement custom reporter for console to read back from Sauce\n        runner.on(\"end\", function() {\n            runner.stats.failed = failed;\n            //runner.stats.xUnitReport = xUnitReport;\n            runner.stats.jsonReport = window.jsonReport;\n            window.mochaResults = runner.stats;\n            window.chocoReady = true;\n        });\n\n    }(mocha.run()));\n    //})(window.mochaPhantomJS ? mochaPhantomJS.run() : mocha.run());\n\n}\n"
  },
  {
    "path": "test/static/local/formdata.js",
    "content": "/* globals assert, qq */\nfunction mockFormData() {\n    \"use strict\";\n\n    function FormData() {\n        this.fake = true;\n        this.boundary = \"--------FormData\" + Math.random();\n        this.fields = {};\n    }\n\n    FormData.prototype.append = function(key, value) {\n        if (this.fields[key] === undefined) {\n            this.fields[key] = value;\n        }\n        else {\n            assert.ok(false, \"Duplicate field name appended!\");\n        }\n    };\n\n    FormData.prototype.toString = function() {\n        var boundary = this.boundary;\n        var body = \"\";\n        qq.each(this.fields, function(field, value) {\n            body += \"--\" + boundary + \"\\r\\n\";\n            // file upload\n            if (value.name) {\n                var file = field[1];\n                body += \"Content-Disposition: form-data; name=\\\"\"+ field +\"\\\"; filename=\\\"\"+ file.name +\"\\\"\\r\\n\";\n                body += \"Content-Type: \"+ file.type +\"\\r\\n\\r\\n\";\n                body += file.getAsBinary() + \"\\r\\n\";\n            } else {\n                body += \"Content-Disposition: form-data; name=\\\"\"+ field +\"\\\";\\r\\n\\r\\n\";\n                body += value + \"\\r\\n\";\n            }\n        });\n        body += \"--\" + boundary +\"--\";\n        return body;\n    };\n\n    if (window.FormData) {\n        FormData.oldFormData = window.FormData;\n    }\n\n    window.FormData = FormData;\n}\n\nfunction unmockFormData() {\n    \"use strict\";\n\n    if (window.FormData && window.FormData.oldFormData) {\n        window.FormData = window.FormData.oldFormData;\n    }\n}\n"
  },
  {
    "path": "test/static/local/helpme.js",
    "content": "/* global sinon:true */\n// The following is apparently required to mock XHR in some versions of IE\nfunction XMLHttpRequest() {} // jshint ignore:line\nXMLHttpRequest = sinon.xhr.XMLHttpRequest || undefined; // jshint ignore:line\n\n/* globals qq, beforeEach, afterEach, sinon, mockFormData, unmockFormData */\nvar helpme = (function () {\n    \"use strict\";\n\n    var obj = {\n    \n        // create a BLOB object\n        createBlob: function (data) {\n            var blobby;\n            \n            if (!data) {\n                data  = [\"Hello, world!\"];\n            }\n\n            blobby = new Blob(data);\n\n            return blobby;\n        },\n    \n        createUploadData: function (onStatusChange) {\n            return new qq.UploadData({\n                getUuid: function(id) {\n                    return id + \"_uuid\";\n                },\n\n                getName: function(id) {\n                    return id + \"_name\";\n                },\n\n                getSize: function(id) {\n                    return 1980;\n                },\n\n                onStatusChange: function(id, oldStatus, newStatus) {\n                    if (onStatusChange !== undefined) {\n                        onStatusChange(id, oldStatus, newStatus);\n                    }\n                }\n            });\n        },\n\n        createFineUploader: function (options, request, validation) {\n            var defaults = {\n                button: null,\n                multiple: true,\n                maxConnections: 3,\n                disableCancelForFormUploads: false,\n                autoUpload: true\n            };\n            var default_request = {\n                endpoint: \"http://localhost:4000/upload\",\n                params: {},\n                paramsInBody: true,\n                customHeaders: {},\n                forceMultipart: true,\n                inputName: \"qqfile\",\n                uuidName: \"qquuid\",\n                totalFileSizeName: \"qqtotalfilesize\"\n            };\n            var default_validation = {\n                allowedExtensions: [],\n                allowEmpty: false,\n                acceptFiles: null,\n                sizeLimit: 0,\n                minSizeLimit: 0,\n                itemLimit: 0,\n                stopOnFirstInvalidFile: true\n            };\n            var default_retry = {\n            };\n                   \n            var uploader = new qq.FineUploader(options);\n        },\n\n        setupFileTests: function() {\n            var testUploadEndpoint = \"/test/upload\",\n                xhr,\n                requests;\n\n            beforeEach(function() {\n                mockFormData();\n\n                requests = [];\n            });\n\n            afterEach(function() {\n                unmockXhr();\n                unmockFormData();\n            });\n\n            function mockXhr() {\n                xhr = sinon.useFakeXMLHttpRequest();\n                xhr.onCreate = function(req) {\n                    requests.push(req);\n                };\n            }\n\n            function unmockXhr() {\n                xhr && xhr.restore && xhr.restore();\n            }\n\n            return {\n                getRequests: function() {\n                    return requests;\n                },\n\n                mockXhr: mockXhr\n            };\n        }\n    };\n\n    return obj;\n})();\n"
  },
  {
    "path": "test/static/local/karma-runner.js",
    "content": "/* globals assert, qq, before, beforeEach, afterEach */\nvar $fixture;\n\n(function() {\n    \"use strict\";\n\n    qq.override(assert, function(super_) {\n        var expected = -1,\n            hit = 0,\n            done;\n\n        function checkIfDone() {\n            if (expected >= 0) {\n                if (hit === expected) {\n                    done();\n                }\n                else if (hit > expected) {\n                    assert.ok(false, \"Too many assertions!\");\n                }\n            }\n        }\n\n        return {\n            expect: function(assertsExpected, doneCallback) {\n                expected = assertsExpected;\n                done = doneCallback;\n            },\n\n            reset: function() {\n                expected = -1;\n                hit = 0;\n            },\n\n            ok: function() {\n                super_.ok.apply(this, arguments);\n                hit++;\n                checkIfDone();\n            },\n\n            equal: function() {\n                super_.equal.apply(this, arguments);\n                hit++;\n                checkIfDone();\n            },\n\n            deepEqual: function() {\n                super_.deepEqual.apply(this, arguments);\n                hit++;\n                checkIfDone();\n            }\n        };\n    });\n\n    before(function() {\n        // \"Turn off\" the before unload handler since this may cause the tests to stall when run in a browser\n        qq.FineUploaderBasic.prototype._preventLeaveInProgress = function() {};\n        qq.s3.FineUploaderBasic.prototype._preventLeaveInProgress = function() {};\n        qq.azure.FineUploaderBasic.prototype._preventLeaveInProgress = function() {};\n        qq.FineUploaderBasic.prototype._wrapCallbacks = function() {};\n    });\n\n    beforeEach(function() {\n        assert.reset();\n        $fixture = $(\"<div id='mocha-fixture'></div>\");\n        $fixture.appendTo(\"body\");\n        return $fixture;\n    });\n\n    afterEach(function () {\n        $fixture.empty();\n        return $fixture.remove();\n    });\n\n    // `Error.captureStackTrace` may not be supported on all UAs.  Assert.js expects it to be.\n    Error.captureStackTrace = Error.captureStackTrace || function() {};\n}());\n"
  },
  {
    "path": "test/static/third-party/assert/assert.js",
    "content": "// http://wiki.commonjs.org/wiki/Unit_Testing/1.0\n//\n// THIS IS NOT TESTED NOR LIKELY TO WORK OUTSIDE V8!\n//\n// Copyright (c) 2011 Jxck\n//\n// Originally from node.js (http://nodejs.org)\n// Copyright Joyent, Inc.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the 'Software'), to\n// deal in the Software without restriction, including without limitation the\n// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n// sell copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in\n// all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n(function(global) {\n\n// Object.create compatible in IE\nvar create = Object.create || function(p) {\n  if (!p) throw Error('no type');\n  function f() {};\n  f.prototype = p;\n  return new f();\n};\n\n// UTILITY\nvar util = {\n  inherits: function(ctor, superCtor) {\n    ctor.super_ = superCtor;\n    ctor.prototype = create(superCtor.prototype, {\n      constructor: {\n        value: ctor,\n        enumerable: false,\n        writable: true,\n        configurable: true\n      }\n    });\n  },\n  isArray: function(ar) {\n    return Array.isArray(ar);\n  },\n  isBoolean: function(arg) {\n    return typeof arg === 'boolean';\n  },\n  isNull: function(arg) {\n    return arg === null;\n  },\n  isNullOrUndefined: function(arg) {\n    return arg == null;\n  },\n  isNumber: function(arg) {\n    return typeof arg === 'number';\n  },\n  isString: function(arg) {\n    return typeof arg === 'string';\n  },\n  isSymbol: function(arg) {\n    return typeof arg === 'symbol';\n  },\n  isUndefined: function(arg) {\n    return arg === void 0;\n  },\n  isRegExp: function(re) {\n    return util.isObject(re) && util.objectToString(re) === '[object RegExp]';\n  },\n  isObject: function(arg) {\n    return typeof arg === 'object' && arg !== null;\n  },\n  isDate: function(d) {\n    return util.isObject(d) && util.objectToString(d) === '[object Date]';\n  },\n  isError: function(e) {\n    return isObject(e) &&\n      (objectToString(e) === '[object Error]' || e instanceof Error);\n  },\n  isFunction: function(arg) {\n    return typeof arg === 'function';\n  },\n  isPrimitive: function(arg) {\n    return arg === null ||\n      typeof arg === 'boolean' ||\n      typeof arg === 'number' ||\n      typeof arg === 'string' ||\n      typeof arg === 'symbol' ||  // ES6 symbol\n      typeof arg === 'undefined';\n  },\n  objectToString: function(o) {\n    return Object.prototype.toString.call(o);\n  }\n};\n\nvar pSlice = Array.prototype.slice;\n\n// from https://github.com/substack/node-deep-equal\nvar Object_keys = typeof Object.keys === 'function'\n    ? Object.keys\n    : function (obj) {\n        var keys = [];\n        for (var key in obj) keys.push(key);\n        return keys;\n    }\n;\n\n// 1. The assert module provides functions that throw\n// AssertionError's when particular conditions are not met. The\n// assert module must conform to the following interface.\n\nvar assert = ok;\n\nglobal['assert'] = assert;\n\nif (typeof module === 'object' && typeof module.exports === 'object') {\n  module.exports = assert;\n};\n\n// 2. The AssertionError is defined in assert.\n// new assert.AssertionError({ message: message,\n//                             actual: actual,\n//                             expected: expected })\n\nassert.AssertionError = function AssertionError(options) {\n  this.name = 'AssertionError';\n  this.actual = options.actual;\n  this.expected = options.expected;\n  this.operator = options.operator;\n  if (options.message) {\n    this.message = options.message;\n    this.generatedMessage = false;\n  } else {\n    this.message = getMessage(this);\n    this.generatedMessage = true;\n  }\n  var stackStartFunction = options.stackStartFunction || fail;\n\n  if (Error.captureStackTrace) {\n    Error.captureStackTrace(this, stackStartFunction);\n  } else {\n    // try to throw an error now, and from the stack property\n    // work out the line that called in to assert.js.\n    try {\n      this.stack = (new Error).stack.toString();\n    } catch (e) {}\n  }\n};\n\n// assert.AssertionError instanceof Error\nutil.inherits(assert.AssertionError, Error);\n\nfunction replacer(key, value) {\n  if (util.isUndefined(value)) {\n    return '' + value;\n  }\n  if (util.isNumber(value) && (isNaN(value) || !isFinite(value))) {\n    return value.toString();\n  }\n  if (util.isFunction(value) || util.isRegExp(value)) {\n    return value.toString();\n  }\n  return value;\n}\n\nfunction truncate(s, n) {\n  if (util.isString(s)) {\n    return s.length < n ? s : s.slice(0, n);\n  } else {\n    return s;\n  }\n}\n\nfunction getMessage(self) {\n  return truncate(JSON.stringify(self.actual, replacer), 128) + ' ' +\n         self.operator + ' ' +\n         truncate(JSON.stringify(self.expected, replacer), 128);\n}\n\n// At present only the three keys mentioned above are used and\n// understood by the spec. Implementations or sub modules can pass\n// other keys to the AssertionError's constructor - they will be\n// ignored.\n\n// 3. All of the following functions must throw an AssertionError\n// when a corresponding condition is not met, with a message that\n// may be undefined if not provided.  All assertion methods provide\n// both the actual and expected values to the assertion error for\n// display purposes.\n\nfunction fail(actual, expected, message, operator, stackStartFunction) {\n  throw new assert.AssertionError({\n    message: message,\n    actual: actual,\n    expected: expected,\n    operator: operator,\n    stackStartFunction: stackStartFunction\n  });\n}\n\n// EXTENSION! allows for well behaved errors defined elsewhere.\nassert.fail = fail;\n\n// 4. Pure assertion tests whether a value is truthy, as determined\n// by !!guard.\n// assert.ok(guard, message_opt);\n// This statement is equivalent to assert.equal(true, !!guard,\n// message_opt);. To test strictly for the value true, use\n// assert.strictEqual(true, guard, message_opt);.\n\nfunction ok(value, message) {\n  if (!value) fail(value, true, message, '==', assert.ok);\n}\nassert.ok = ok;\n\n// 5. The equality assertion tests shallow, coercive equality with\n// ==.\n// assert.equal(actual, expected, message_opt);\n\nassert.equal = function equal(actual, expected, message) {\n  if (actual != expected) fail(actual, expected, message, '==', assert.equal);\n};\n\n// 6. The non-equality assertion tests for whether two objects are not equal\n// with != assert.notEqual(actual, expected, message_opt);\n\nassert.notEqual = function notEqual(actual, expected, message) {\n  if (actual == expected) {\n    fail(actual, expected, message, '!=', assert.notEqual);\n  }\n};\n\n// 7. The equivalence assertion tests a deep equality relation.\n// assert.deepEqual(actual, expected, message_opt);\n\nassert.deepEqual = function deepEqual(actual, expected, message) {\n  if (!_deepEqual(actual, expected)) {\n    fail(actual, expected, message, 'deepEqual', assert.deepEqual);\n  }\n};\n\nfunction _deepEqual(actual, expected) {\n  // 7.1. All identical values are equivalent, as determined by ===.\n  if (actual === expected) {\n    return true;\n\n  // } else if (util.isBuffer(actual) && util.isBuffer(expected)) {\n  //   if (actual.length != expected.length) return false;\n  //\n  //   for (var i = 0; i < actual.length; i++) {\n  //     if (actual[i] !== expected[i]) return false;\n  //   }\n  //\n  //   return true;\n\n  // 7.2. If the expected value is a Date object, the actual value is\n  // equivalent if it is also a Date object that refers to the same time.\n  } else if (util.isDate(actual) && util.isDate(expected)) {\n    return actual.getTime() === expected.getTime();\n\n  // 7.3 If the expected value is a RegExp object, the actual value is\n  // equivalent if it is also a RegExp object with the same source and\n  // properties (`global`, `multiline`, `lastIndex`, `ignoreCase`).\n  } else if (util.isRegExp(actual) && util.isRegExp(expected)) {\n    return actual.source === expected.source &&\n           actual.global === expected.global &&\n           actual.multiline === expected.multiline &&\n           actual.lastIndex === expected.lastIndex &&\n           actual.ignoreCase === expected.ignoreCase;\n\n  // 7.4. Other pairs that do not both pass typeof value == 'object',\n  // equivalence is determined by ==.\n  } else if (!util.isObject(actual) && !util.isObject(expected)) {\n    return actual == expected;\n\n  // 7.5 For all other Object pairs, including Array objects, equivalence is\n  // determined by having the same number of owned properties (as verified\n  // with Object.prototype.hasOwnProperty.call), the same set of keys\n  // (although not necessarily the same order), equivalent values for every\n  // corresponding key, and an identical 'prototype' property. Note: this\n  // accounts for both named and indexed properties on Arrays.\n  } else {\n    return objEquiv(actual, expected);\n  }\n}\n\nfunction isArguments(object) {\n  return Object.prototype.toString.call(object) == '[object Arguments]';\n}\n\nfunction objEquiv(a, b) {\n  if (util.isNullOrUndefined(a) || util.isNullOrUndefined(b))\n    return false;\n  // an identical 'prototype' property.\n  if (a.prototype !== b.prototype) return false;\n  //~~~I've managed to break Object.keys through screwy arguments passing.\n  //   Converting to array solves the problem.\n  if (isArguments(a)) {\n    if (!isArguments(b)) {\n      return false;\n    }\n    a = pSlice.call(a);\n    b = pSlice.call(b);\n    return _deepEqual(a, b);\n  }\n  try {\n    var ka = Object_keys(a),\n        kb = Object_keys(b),\n        key, i;\n  } catch (e) {//happens when one is a string literal and the other isn't\n    return false;\n  }\n  // having the same number of owned properties (keys incorporates\n  // hasOwnProperty)\n  if (ka.length != kb.length)\n    return false;\n  //the same set of keys (although not necessarily the same order),\n  ka.sort();\n  kb.sort();\n  //~~~cheap key test\n  for (i = ka.length - 1; i >= 0; i--) {\n    if (ka[i] != kb[i])\n      return false;\n  }\n  //equivalent values for every corresponding key, and\n  //~~~possibly expensive deep test\n  for (i = ka.length - 1; i >= 0; i--) {\n    key = ka[i];\n    if (!_deepEqual(a[key], b[key])) return false;\n  }\n  return true;\n}\n\n// 8. The non-equivalence assertion tests for any deep inequality.\n// assert.notDeepEqual(actual, expected, message_opt);\n\nassert.notDeepEqual = function notDeepEqual(actual, expected, message) {\n  if (_deepEqual(actual, expected)) {\n    fail(actual, expected, message, 'notDeepEqual', assert.notDeepEqual);\n  }\n};\n\n// 9. The strict equality assertion tests strict equality, as determined by ===.\n// assert.strictEqual(actual, expected, message_opt);\n\nassert.strictEqual = function strictEqual(actual, expected, message) {\n  if (actual !== expected) {\n    fail(actual, expected, message, '===', assert.strictEqual);\n  }\n};\n\n// 10. The strict non-equality assertion tests for strict inequality, as\n// determined by !==.  assert.notStrictEqual(actual, expected, message_opt);\n\nassert.notStrictEqual = function notStrictEqual(actual, expected, message) {\n  if (actual === expected) {\n    fail(actual, expected, message, '!==', assert.notStrictEqual);\n  }\n};\n\nfunction expectedException(actual, expected) {\n  if (!actual || !expected) {\n    return false;\n  }\n\n  if (Object.prototype.toString.call(expected) == '[object RegExp]') {\n    return expected.test(actual);\n  } else if (actual instanceof expected) {\n    return true;\n  } else if (expected.call({}, actual) === true) {\n    return true;\n  }\n\n  return false;\n}\n\nfunction _throws(shouldThrow, block, expected, message) {\n  var actual;\n\n  if (util.isString(expected)) {\n    message = expected;\n    expected = null;\n  }\n\n  try {\n    block();\n  } catch (e) {\n    actual = e;\n  }\n\n  message = (expected && expected.name ? ' (' + expected.name + ').' : '.') +\n            (message ? ' ' + message : '.');\n\n  if (shouldThrow && !actual) {\n    fail(actual, expected, 'Missing expected exception' + message);\n  }\n\n  if (!shouldThrow && expectedException(actual, expected)) {\n    fail(actual, expected, 'Got unwanted exception' + message);\n  }\n\n  if ((shouldThrow && actual && expected &&\n      !expectedException(actual, expected)) || (!shouldThrow && actual)) {\n    throw actual;\n  }\n}\n\n// 11. Expected to throw an error:\n// assert.throws(block, Error_opt, message_opt);\n\nassert.throws = function(block, /*optional*/error, /*optional*/message) {\n  _throws.apply(this, [true].concat(pSlice.call(arguments)));\n};\n\n// EXTENSION! This is annoying to write outside this module.\nassert.doesNotThrow = function(block, /*optional*/message) {\n  _throws.apply(this, [false].concat(pSlice.call(arguments)));\n};\n\nassert.ifError = function(err) { if (err) {throw err;}};\n\nif (typeof define === 'function' && define.amd) {\n  define('assert', function () {\n    return assert;\n  });\n}\n\n})(this);\n\n"
  },
  {
    "path": "test/static/third-party/jquery/jquery.js",
    "content": "/*!\n * jQuery JavaScript Library v1.10.0\n * http://jquery.com/\n *\n * Includes Sizzle.js\n * http://sizzlejs.com/\n *\n * Copyright 2005, 2013 jQuery Foundation, Inc. and other contributors\n * Released under the MIT license\n * http://jquery.org/license\n *\n * Date: 2013-05-24T18:39Z\n */\n(function( window, undefined ) {\n\n// Can't do this because several apps including ASP.NET trace\n// the stack via arguments.caller.callee and Firefox dies if\n// you try to trace through \"use strict\" call chains. (#13335)\n// Support: Firefox 18+\n//\"use strict\";\nvar\n\t// The deferred used on DOM ready\n\treadyList,\n\n\t// A central reference to the root jQuery(document)\n\trootjQuery,\n\n\t// Support: IE<10\n\t// For `typeof xmlNode.method` instead of `xmlNode.method !== undefined`\n\tcore_strundefined = typeof undefined,\n\n\t// Use the correct document accordingly with window argument (sandbox)\n\tlocation = window.location,\n\tdocument = window.document,\n\tdocElem = document.documentElement,\n\n\t// Map over jQuery in case of overwrite\n\t_jQuery = window.jQuery,\n\n\t// Map over the $ in case of overwrite\n\t_$ = window.$,\n\n\t// [[Class]] -> type pairs\n\tclass2type = {},\n\n\t// List of deleted data cache ids, so we can reuse them\n\tcore_deletedIds = [],\n\n\tcore_version = \"1.10.0\",\n\n\t// Save a reference to some core methods\n\tcore_concat = core_deletedIds.concat,\n\tcore_push = core_deletedIds.push,\n\tcore_slice = core_deletedIds.slice,\n\tcore_indexOf = core_deletedIds.indexOf,\n\tcore_toString = class2type.toString,\n\tcore_hasOwn = class2type.hasOwnProperty,\n\tcore_trim = core_version.trim,\n\n\t// Define a local copy of jQuery\n\tjQuery = function( selector, context ) {\n\t\t// The jQuery object is actually just the init constructor 'enhanced'\n\t\treturn new jQuery.fn.init( selector, context, rootjQuery );\n\t},\n\n\t// Used for matching numbers\n\tcore_pnum = /[+-]?(?:\\d*\\.|)\\d+(?:[eE][+-]?\\d+|)/.source,\n\n\t// Used for splitting on whitespace\n\tcore_rnotwhite = /\\S+/g,\n\n\t// Make sure we trim BOM and NBSP (here's looking at you, Safari 5.0 and IE)\n\trtrim = /^[\\s\\uFEFF\\xA0]+|[\\s\\uFEFF\\xA0]+$/g,\n\n\t// A simple way to check for HTML strings\n\t// Prioritize #id over <tag> to avoid XSS via location.hash (#9521)\n\t// Strict HTML recognition (#11290: must start with <)\n\trquickExpr = /^(?:\\s*(<[\\w\\W]+>)[^>]*|#([\\w-]*))$/,\n\n\t// Match a standalone tag\n\trsingleTag = /^<(\\w+)\\s*\\/?>(?:<\\/\\1>|)$/,\n\n\t// JSON RegExp\n\trvalidchars = /^[\\],:{}\\s]*$/,\n\trvalidbraces = /(?:^|:|,)(?:\\s*\\[)+/g,\n\trvalidescape = /\\\\(?:[\"\\\\\\/bfnrt]|u[\\da-fA-F]{4})/g,\n\trvalidtokens = /\"[^\"\\\\\\r\\n]*\"|true|false|null|-?(?:\\d+\\.|)\\d+(?:[eE][+-]?\\d+|)/g,\n\n\t// Matches dashed string for camelizing\n\trmsPrefix = /^-ms-/,\n\trdashAlpha = /-([\\da-z])/gi,\n\n\t// Used by jQuery.camelCase as callback to replace()\n\tfcamelCase = function( all, letter ) {\n\t\treturn letter.toUpperCase();\n\t},\n\n\t// The ready event handler\n\tcompleted = function( event ) {\n\n\t\t// readyState === \"complete\" is good enough for us to call the dom ready in oldIE\n\t\tif ( document.addEventListener || event.type === \"load\" || document.readyState === \"complete\" ) {\n\t\t\tdetach();\n\t\t\tjQuery.ready();\n\t\t}\n\t},\n\t// Clean-up method for dom ready events\n\tdetach = function() {\n\t\tif ( document.addEventListener ) {\n\t\t\tdocument.removeEventListener( \"DOMContentLoaded\", completed, false );\n\t\t\twindow.removeEventListener( \"load\", completed, false );\n\n\t\t} else {\n\t\t\tdocument.detachEvent( \"onreadystatechange\", completed );\n\t\t\twindow.detachEvent( \"onload\", completed );\n\t\t}\n\t};\n\njQuery.fn = jQuery.prototype = {\n\t// The current version of jQuery being used\n\tjquery: core_version,\n\n\tconstructor: jQuery,\n\tinit: function( selector, context, rootjQuery ) {\n\t\tvar match, elem;\n\n\t\t// HANDLE: $(\"\"), $(null), $(undefined), $(false)\n\t\tif ( !selector ) {\n\t\t\treturn this;\n\t\t}\n\n\t\t// Handle HTML strings\n\t\tif ( typeof selector === \"string\" ) {\n\t\t\tif ( selector.charAt(0) === \"<\" && selector.charAt( selector.length - 1 ) === \">\" && selector.length >= 3 ) {\n\t\t\t\t// Assume that strings that start and end with <> are HTML and skip the regex check\n\t\t\t\tmatch = [ null, selector, null ];\n\n\t\t\t} else {\n\t\t\t\tmatch = rquickExpr.exec( selector );\n\t\t\t}\n\n\t\t\t// Match html or make sure no context is specified for #id\n\t\t\tif ( match && (match[1] || !context) ) {\n\n\t\t\t\t// HANDLE: $(html) -> $(array)\n\t\t\t\tif ( match[1] ) {\n\t\t\t\t\tcontext = context instanceof jQuery ? context[0] : context;\n\n\t\t\t\t\t// scripts is true for back-compat\n\t\t\t\t\tjQuery.merge( this, jQuery.parseHTML(\n\t\t\t\t\t\tmatch[1],\n\t\t\t\t\t\tcontext && context.nodeType ? context.ownerDocument || context : document,\n\t\t\t\t\t\ttrue\n\t\t\t\t\t) );\n\n\t\t\t\t\t// HANDLE: $(html, props)\n\t\t\t\t\tif ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) {\n\t\t\t\t\t\tfor ( match in context ) {\n\t\t\t\t\t\t\t// Properties of context are called as methods if possible\n\t\t\t\t\t\t\tif ( jQuery.isFunction( this[ match ] ) ) {\n\t\t\t\t\t\t\t\tthis[ match ]( context[ match ] );\n\n\t\t\t\t\t\t\t// ...and otherwise set as attributes\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis.attr( match, context[ match ] );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\treturn this;\n\n\t\t\t\t// HANDLE: $(#id)\n\t\t\t\t} else {\n\t\t\t\t\telem = document.getElementById( match[2] );\n\n\t\t\t\t\t// Check parentNode to catch when Blackberry 4.6 returns\n\t\t\t\t\t// nodes that are no longer in the document #6963\n\t\t\t\t\tif ( elem && elem.parentNode ) {\n\t\t\t\t\t\t// Handle the case where IE and Opera return items\n\t\t\t\t\t\t// by name instead of ID\n\t\t\t\t\t\tif ( elem.id !== match[2] ) {\n\t\t\t\t\t\t\treturn rootjQuery.find( selector );\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Otherwise, we inject the element directly into the jQuery object\n\t\t\t\t\t\tthis.length = 1;\n\t\t\t\t\t\tthis[0] = elem;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.context = document;\n\t\t\t\t\tthis.selector = selector;\n\t\t\t\t\treturn this;\n\t\t\t\t}\n\n\t\t\t// HANDLE: $(expr, $(...))\n\t\t\t} else if ( !context || context.jquery ) {\n\t\t\t\treturn ( context || rootjQuery ).find( selector );\n\n\t\t\t// HANDLE: $(expr, context)\n\t\t\t// (which is just equivalent to: $(context).find(expr)\n\t\t\t} else {\n\t\t\t\treturn this.constructor( context ).find( selector );\n\t\t\t}\n\n\t\t// HANDLE: $(DOMElement)\n\t\t} else if ( selector.nodeType ) {\n\t\t\tthis.context = this[0] = selector;\n\t\t\tthis.length = 1;\n\t\t\treturn this;\n\n\t\t// HANDLE: $(function)\n\t\t// Shortcut for document ready\n\t\t} else if ( jQuery.isFunction( selector ) ) {\n\t\t\treturn rootjQuery.ready( selector );\n\t\t}\n\n\t\tif ( selector.selector !== undefined ) {\n\t\t\tthis.selector = selector.selector;\n\t\t\tthis.context = selector.context;\n\t\t}\n\n\t\treturn jQuery.makeArray( selector, this );\n\t},\n\n\t// Start with an empty selector\n\tselector: \"\",\n\n\t// The default length of a jQuery object is 0\n\tlength: 0,\n\n\ttoArray: function() {\n\t\treturn core_slice.call( this );\n\t},\n\n\t// Get the Nth element in the matched element set OR\n\t// Get the whole matched element set as a clean array\n\tget: function( num ) {\n\t\treturn num == null ?\n\n\t\t\t// Return a 'clean' array\n\t\t\tthis.toArray() :\n\n\t\t\t// Return just the object\n\t\t\t( num < 0 ? this[ this.length + num ] : this[ num ] );\n\t},\n\n\t// Take an array of elements and push it onto the stack\n\t// (returning the new matched element set)\n\tpushStack: function( elems ) {\n\n\t\t// Build a new jQuery matched element set\n\t\tvar ret = jQuery.merge( this.constructor(), elems );\n\n\t\t// Add the old object onto the stack (as a reference)\n\t\tret.prevObject = this;\n\t\tret.context = this.context;\n\n\t\t// Return the newly-formed element set\n\t\treturn ret;\n\t},\n\n\t// Execute a callback for every element in the matched set.\n\t// (You can seed the arguments with an array of args, but this is\n\t// only used internally.)\n\teach: function( callback, args ) {\n\t\treturn jQuery.each( this, callback, args );\n\t},\n\n\tready: function( fn ) {\n\t\t// Add the callback\n\t\tjQuery.ready.promise().done( fn );\n\n\t\treturn this;\n\t},\n\n\tslice: function() {\n\t\treturn this.pushStack( core_slice.apply( this, arguments ) );\n\t},\n\n\tfirst: function() {\n\t\treturn this.eq( 0 );\n\t},\n\n\tlast: function() {\n\t\treturn this.eq( -1 );\n\t},\n\n\teq: function( i ) {\n\t\tvar len = this.length,\n\t\t\tj = +i + ( i < 0 ? len : 0 );\n\t\treturn this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] );\n\t},\n\n\tmap: function( callback ) {\n\t\treturn this.pushStack( jQuery.map(this, function( elem, i ) {\n\t\t\treturn callback.call( elem, i, elem );\n\t\t}));\n\t},\n\n\tend: function() {\n\t\treturn this.prevObject || this.constructor(null);\n\t},\n\n\t// For internal use only.\n\t// Behaves like an Array's method, not like a jQuery method.\n\tpush: core_push,\n\tsort: [].sort,\n\tsplice: [].splice\n};\n\n// Give the init function the jQuery prototype for later instantiation\njQuery.fn.init.prototype = jQuery.fn;\n\njQuery.extend = jQuery.fn.extend = function() {\n\tvar src, copyIsArray, copy, name, options, clone,\n\t\ttarget = arguments[0] || {},\n\t\ti = 1,\n\t\tlength = arguments.length,\n\t\tdeep = false;\n\n\t// Handle a deep copy situation\n\tif ( typeof target === \"boolean\" ) {\n\t\tdeep = target;\n\t\ttarget = arguments[1] || {};\n\t\t// skip the boolean and the target\n\t\ti = 2;\n\t}\n\n\t// Handle case when target is a string or something (possible in deep copy)\n\tif ( typeof target !== \"object\" && !jQuery.isFunction(target) ) {\n\t\ttarget = {};\n\t}\n\n\t// extend jQuery itself if only one argument is passed\n\tif ( length === i ) {\n\t\ttarget = this;\n\t\t--i;\n\t}\n\n\tfor ( ; i < length; i++ ) {\n\t\t// Only deal with non-null/undefined values\n\t\tif ( (options = arguments[ i ]) != null ) {\n\t\t\t// Extend the base object\n\t\t\tfor ( name in options ) {\n\t\t\t\tsrc = target[ name ];\n\t\t\t\tcopy = options[ name ];\n\n\t\t\t\t// Prevent never-ending loop\n\t\t\t\tif ( target === copy ) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Recurse if we're merging plain objects or arrays\n\t\t\t\tif ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {\n\t\t\t\t\tif ( copyIsArray ) {\n\t\t\t\t\t\tcopyIsArray = false;\n\t\t\t\t\t\tclone = src && jQuery.isArray(src) ? src : [];\n\n\t\t\t\t\t} else {\n\t\t\t\t\t\tclone = src && jQuery.isPlainObject(src) ? src : {};\n\t\t\t\t\t}\n\n\t\t\t\t\t// Never move original objects, clone them\n\t\t\t\t\ttarget[ name ] = jQuery.extend( deep, clone, copy );\n\n\t\t\t\t// Don't bring in undefined values\n\t\t\t\t} else if ( copy !== undefined ) {\n\t\t\t\t\ttarget[ name ] = copy;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Return the modified object\n\treturn target;\n};\n\njQuery.extend({\n\t// Unique for each copy of jQuery on the page\n\t// Non-digits removed to match rinlinejQuery\n\texpando: \"jQuery\" + ( core_version + Math.random() ).replace( /\\D/g, \"\" ),\n\n\tnoConflict: function( deep ) {\n\t\tif ( window.$ === jQuery ) {\n\t\t\twindow.$ = _$;\n\t\t}\n\n\t\tif ( deep && window.jQuery === jQuery ) {\n\t\t\twindow.jQuery = _jQuery;\n\t\t}\n\n\t\treturn jQuery;\n\t},\n\n\t// Is the DOM ready to be used? Set to true once it occurs.\n\tisReady: false,\n\n\t// A counter to track how many items to wait for before\n\t// the ready event fires. See #6781\n\treadyWait: 1,\n\n\t// Hold (or release) the ready event\n\tholdReady: function( hold ) {\n\t\tif ( hold ) {\n\t\t\tjQuery.readyWait++;\n\t\t} else {\n\t\t\tjQuery.ready( true );\n\t\t}\n\t},\n\n\t// Handle when the DOM is ready\n\tready: function( wait ) {\n\n\t\t// Abort if there are pending holds or we're already ready\n\t\tif ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).\n\t\tif ( !document.body ) {\n\t\t\treturn setTimeout( jQuery.ready );\n\t\t}\n\n\t\t// Remember that the DOM is ready\n\t\tjQuery.isReady = true;\n\n\t\t// If a normal DOM Ready event fired, decrement, and wait if need be\n\t\tif ( wait !== true && --jQuery.readyWait > 0 ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// If there are functions bound, to execute\n\t\treadyList.resolveWith( document, [ jQuery ] );\n\n\t\t// Trigger any bound ready events\n\t\tif ( jQuery.fn.trigger ) {\n\t\t\tjQuery( document ).trigger(\"ready\").off(\"ready\");\n\t\t}\n\t},\n\n\t// See test/unit/core.js for details concerning isFunction.\n\t// Since version 1.3, DOM methods and functions like alert\n\t// aren't supported. They return false on IE (#2968).\n\tisFunction: function( obj ) {\n\t\treturn jQuery.type(obj) === \"function\";\n\t},\n\n\tisArray: Array.isArray || function( obj ) {\n\t\treturn jQuery.type(obj) === \"array\";\n\t},\n\n\tisWindow: function( obj ) {\n\t\t/* jshint eqeqeq: false */\n\t\treturn obj != null && obj == obj.window;\n\t},\n\n\tisNumeric: function( obj ) {\n\t\treturn !isNaN( parseFloat(obj) ) && isFinite( obj );\n\t},\n\n\ttype: function( obj ) {\n\t\tif ( obj == null ) {\n\t\t\treturn String( obj );\n\t\t}\n\t\treturn typeof obj === \"object\" || typeof obj === \"function\" ?\n\t\t\tclass2type[ core_toString.call(obj) ] || \"object\" :\n\t\t\ttypeof obj;\n\t},\n\n\tisPlainObject: function( obj ) {\n\t\tvar key;\n\n\t\t// Must be an Object.\n\t\t// Because of IE, we also have to check the presence of the constructor property.\n\t\t// Make sure that DOM nodes and window objects don't pass through, as well\n\t\tif ( !obj || jQuery.type(obj) !== \"object\" || obj.nodeType || jQuery.isWindow( obj ) ) {\n\t\t\treturn false;\n\t\t}\n\n\t\ttry {\n\t\t\t// Not own constructor property must be Object\n\t\t\tif ( obj.constructor &&\n\t\t\t\t!core_hasOwn.call(obj, \"constructor\") &&\n\t\t\t\t!core_hasOwn.call(obj.constructor.prototype, \"isPrototypeOf\") ) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t} catch ( e ) {\n\t\t\t// IE8,9 Will throw exceptions on certain host objects #9897\n\t\t\treturn false;\n\t\t}\n\n\t\t// Support: IE<9\n\t\t// Handle iteration over inherited properties before own properties.\n\t\tif ( jQuery.support.ownLast ) {\n\t\t\tfor ( key in obj ) {\n\t\t\t\treturn core_hasOwn.call( obj, key );\n\t\t\t}\n\t\t}\n\n\t\t// Own properties are enumerated firstly, so to speed up,\n\t\t// if last one is own, then all properties are own.\n\t\tfor ( key in obj ) {}\n\n\t\treturn key === undefined || core_hasOwn.call( obj, key );\n\t},\n\n\tisEmptyObject: function( obj ) {\n\t\tvar name;\n\t\tfor ( name in obj ) {\n\t\t\treturn false;\n\t\t}\n\t\treturn true;\n\t},\n\n\terror: function( msg ) {\n\t\tthrow new Error( msg );\n\t},\n\n\t// data: string of html\n\t// context (optional): If specified, the fragment will be created in this context, defaults to document\n\t// keepScripts (optional): If true, will include scripts passed in the html string\n\tparseHTML: function( data, context, keepScripts ) {\n\t\tif ( !data || typeof data !== \"string\" ) {\n\t\t\treturn null;\n\t\t}\n\t\tif ( typeof context === \"boolean\" ) {\n\t\t\tkeepScripts = context;\n\t\t\tcontext = false;\n\t\t}\n\t\tcontext = context || document;\n\n\t\tvar parsed = rsingleTag.exec( data ),\n\t\t\tscripts = !keepScripts && [];\n\n\t\t// Single tag\n\t\tif ( parsed ) {\n\t\t\treturn [ context.createElement( parsed[1] ) ];\n\t\t}\n\n\t\tparsed = jQuery.buildFragment( [ data ], context, scripts );\n\t\tif ( scripts ) {\n\t\t\tjQuery( scripts ).remove();\n\t\t}\n\t\treturn jQuery.merge( [], parsed.childNodes );\n\t},\n\n\tparseJSON: function( data ) {\n\t\t// Attempt to parse using the native JSON parser first\n\t\tif ( window.JSON && window.JSON.parse ) {\n\t\t\treturn window.JSON.parse( data );\n\t\t}\n\n\t\tif ( data === null ) {\n\t\t\treturn data;\n\t\t}\n\n\t\tif ( typeof data === \"string\" ) {\n\n\t\t\t// Make sure leading/trailing whitespace is removed (IE can't handle it)\n\t\t\tdata = jQuery.trim( data );\n\n\t\t\tif ( data ) {\n\t\t\t\t// Make sure the incoming data is actual JSON\n\t\t\t\t// Logic borrowed from http://json.org/json2.js\n\t\t\t\tif ( rvalidchars.test( data.replace( rvalidescape, \"@\" )\n\t\t\t\t\t.replace( rvalidtokens, \"]\" )\n\t\t\t\t\t.replace( rvalidbraces, \"\")) ) {\n\n\t\t\t\t\treturn ( new Function( \"return \" + data ) )();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tjQuery.error( \"Invalid JSON: \" + data );\n\t},\n\n\t// Cross-browser xml parsing\n\tparseXML: function( data ) {\n\t\tvar xml, tmp;\n\t\tif ( !data || typeof data !== \"string\" ) {\n\t\t\treturn null;\n\t\t}\n\t\ttry {\n\t\t\tif ( window.DOMParser ) { // Standard\n\t\t\t\ttmp = new DOMParser();\n\t\t\t\txml = tmp.parseFromString( data , \"text/xml\" );\n\t\t\t} else { // IE\n\t\t\t\txml = new ActiveXObject( \"Microsoft.XMLDOM\" );\n\t\t\t\txml.async = \"false\";\n\t\t\t\txml.loadXML( data );\n\t\t\t}\n\t\t} catch( e ) {\n\t\t\txml = undefined;\n\t\t}\n\t\tif ( !xml || !xml.documentElement || xml.getElementsByTagName( \"parsererror\" ).length ) {\n\t\t\tjQuery.error( \"Invalid XML: \" + data );\n\t\t}\n\t\treturn xml;\n\t},\n\n\tnoop: function() {},\n\n\t// Evaluates a script in a global context\n\t// Workarounds based on findings by Jim Driscoll\n\t// http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context\n\tglobalEval: function( data ) {\n\t\tif ( data && jQuery.trim( data ) ) {\n\t\t\t// We use execScript on Internet Explorer\n\t\t\t// We use an anonymous function so that context is window\n\t\t\t// rather than jQuery in Firefox\n\t\t\t( window.execScript || function( data ) {\n\t\t\t\twindow[ \"eval\" ].call( window, data );\n\t\t\t} )( data );\n\t\t}\n\t},\n\n\t// Convert dashed to camelCase; used by the css and data modules\n\t// Microsoft forgot to hump their vendor prefix (#9572)\n\tcamelCase: function( string ) {\n\t\treturn string.replace( rmsPrefix, \"ms-\" ).replace( rdashAlpha, fcamelCase );\n\t},\n\n\tnodeName: function( elem, name ) {\n\t\treturn elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();\n\t},\n\n\t// args is for internal usage only\n\teach: function( obj, callback, args ) {\n\t\tvar value,\n\t\t\ti = 0,\n\t\t\tlength = obj.length,\n\t\t\tisArray = isArraylike( obj );\n\n\t\tif ( args ) {\n\t\t\tif ( isArray ) {\n\t\t\t\tfor ( ; i < length; i++ ) {\n\t\t\t\t\tvalue = callback.apply( obj[ i ], args );\n\n\t\t\t\t\tif ( value === false ) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tfor ( i in obj ) {\n\t\t\t\t\tvalue = callback.apply( obj[ i ], args );\n\n\t\t\t\t\tif ( value === false ) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t// A special, fast, case for the most common use of each\n\t\t} else {\n\t\t\tif ( isArray ) {\n\t\t\t\tfor ( ; i < length; i++ ) {\n\t\t\t\t\tvalue = callback.call( obj[ i ], i, obj[ i ] );\n\n\t\t\t\t\tif ( value === false ) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tfor ( i in obj ) {\n\t\t\t\t\tvalue = callback.call( obj[ i ], i, obj[ i ] );\n\n\t\t\t\t\tif ( value === false ) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn obj;\n\t},\n\n\t// Use native String.trim function wherever possible\n\ttrim: core_trim && !core_trim.call(\"\\uFEFF\\xA0\") ?\n\t\tfunction( text ) {\n\t\t\treturn text == null ?\n\t\t\t\t\"\" :\n\t\t\t\tcore_trim.call( text );\n\t\t} :\n\n\t\t// Otherwise use our own trimming functionality\n\t\tfunction( text ) {\n\t\t\treturn text == null ?\n\t\t\t\t\"\" :\n\t\t\t\t( text + \"\" ).replace( rtrim, \"\" );\n\t\t},\n\n\t// results is for internal usage only\n\tmakeArray: function( arr, results ) {\n\t\tvar ret = results || [];\n\n\t\tif ( arr != null ) {\n\t\t\tif ( isArraylike( Object(arr) ) ) {\n\t\t\t\tjQuery.merge( ret,\n\t\t\t\t\ttypeof arr === \"string\" ?\n\t\t\t\t\t[ arr ] : arr\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tcore_push.call( ret, arr );\n\t\t\t}\n\t\t}\n\n\t\treturn ret;\n\t},\n\n\tinArray: function( elem, arr, i ) {\n\t\tvar len;\n\n\t\tif ( arr ) {\n\t\t\tif ( core_indexOf ) {\n\t\t\t\treturn core_indexOf.call( arr, elem, i );\n\t\t\t}\n\n\t\t\tlen = arr.length;\n\t\t\ti = i ? i < 0 ? Math.max( 0, len + i ) : i : 0;\n\n\t\t\tfor ( ; i < len; i++ ) {\n\t\t\t\t// Skip accessing in sparse arrays\n\t\t\t\tif ( i in arr && arr[ i ] === elem ) {\n\t\t\t\t\treturn i;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn -1;\n\t},\n\n\tmerge: function( first, second ) {\n\t\tvar l = second.length,\n\t\t\ti = first.length,\n\t\t\tj = 0;\n\n\t\tif ( typeof l === \"number\" ) {\n\t\t\tfor ( ; j < l; j++ ) {\n\t\t\t\tfirst[ i++ ] = second[ j ];\n\t\t\t}\n\t\t} else {\n\t\t\twhile ( second[j] !== undefined ) {\n\t\t\t\tfirst[ i++ ] = second[ j++ ];\n\t\t\t}\n\t\t}\n\n\t\tfirst.length = i;\n\n\t\treturn first;\n\t},\n\n\tgrep: function( elems, callback, inv ) {\n\t\tvar retVal,\n\t\t\tret = [],\n\t\t\ti = 0,\n\t\t\tlength = elems.length;\n\t\tinv = !!inv;\n\n\t\t// Go through the array, only saving the items\n\t\t// that pass the validator function\n\t\tfor ( ; i < length; i++ ) {\n\t\t\tretVal = !!callback( elems[ i ], i );\n\t\t\tif ( inv !== retVal ) {\n\t\t\t\tret.push( elems[ i ] );\n\t\t\t}\n\t\t}\n\n\t\treturn ret;\n\t},\n\n\t// arg is for internal usage only\n\tmap: function( elems, callback, arg ) {\n\t\tvar value,\n\t\t\ti = 0,\n\t\t\tlength = elems.length,\n\t\t\tisArray = isArraylike( elems ),\n\t\t\tret = [];\n\n\t\t// Go through the array, translating each of the items to their\n\t\tif ( isArray ) {\n\t\t\tfor ( ; i < length; i++ ) {\n\t\t\t\tvalue = callback( elems[ i ], i, arg );\n\n\t\t\t\tif ( value != null ) {\n\t\t\t\t\tret[ ret.length ] = value;\n\t\t\t\t}\n\t\t\t}\n\n\t\t// Go through every key on the object,\n\t\t} else {\n\t\t\tfor ( i in elems ) {\n\t\t\t\tvalue = callback( elems[ i ], i, arg );\n\n\t\t\t\tif ( value != null ) {\n\t\t\t\t\tret[ ret.length ] = value;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Flatten any nested arrays\n\t\treturn core_concat.apply( [], ret );\n\t},\n\n\t// A global GUID counter for objects\n\tguid: 1,\n\n\t// Bind a function to a context, optionally partially applying any\n\t// arguments.\n\tproxy: function( fn, context ) {\n\t\tvar args, proxy, tmp;\n\n\t\tif ( typeof context === \"string\" ) {\n\t\t\ttmp = fn[ context ];\n\t\t\tcontext = fn;\n\t\t\tfn = tmp;\n\t\t}\n\n\t\t// Quick check to determine if target is callable, in the spec\n\t\t// this throws a TypeError, but we will just return undefined.\n\t\tif ( !jQuery.isFunction( fn ) ) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\t// Simulated bind\n\t\targs = core_slice.call( arguments, 2 );\n\t\tproxy = function() {\n\t\t\treturn fn.apply( context || this, args.concat( core_slice.call( arguments ) ) );\n\t\t};\n\n\t\t// Set the guid of unique handler to the same of original handler, so it can be removed\n\t\tproxy.guid = fn.guid = fn.guid || jQuery.guid++;\n\n\t\treturn proxy;\n\t},\n\n\t// Multifunctional method to get and set values of a collection\n\t// The value/s can optionally be executed if it's a function\n\taccess: function( elems, fn, key, value, chainable, emptyGet, raw ) {\n\t\tvar i = 0,\n\t\t\tlength = elems.length,\n\t\t\tbulk = key == null;\n\n\t\t// Sets many values\n\t\tif ( jQuery.type( key ) === \"object\" ) {\n\t\t\tchainable = true;\n\t\t\tfor ( i in key ) {\n\t\t\t\tjQuery.access( elems, fn, i, key[i], true, emptyGet, raw );\n\t\t\t}\n\n\t\t// Sets one value\n\t\t} else if ( value !== undefined ) {\n\t\t\tchainable = true;\n\n\t\t\tif ( !jQuery.isFunction( value ) ) {\n\t\t\t\traw = true;\n\t\t\t}\n\n\t\t\tif ( bulk ) {\n\t\t\t\t// Bulk operations run against the entire set\n\t\t\t\tif ( raw ) {\n\t\t\t\t\tfn.call( elems, value );\n\t\t\t\t\tfn = null;\n\n\t\t\t\t// ...except when executing function values\n\t\t\t\t} else {\n\t\t\t\t\tbulk = fn;\n\t\t\t\t\tfn = function( elem, key, value ) {\n\t\t\t\t\t\treturn bulk.call( jQuery( elem ), value );\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif ( fn ) {\n\t\t\t\tfor ( ; i < length; i++ ) {\n\t\t\t\t\tfn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn chainable ?\n\t\t\telems :\n\n\t\t\t// Gets\n\t\t\tbulk ?\n\t\t\t\tfn.call( elems ) :\n\t\t\t\tlength ? fn( elems[0], key ) : emptyGet;\n\t},\n\n\tnow: function() {\n\t\treturn ( new Date() ).getTime();\n\t},\n\n\t// A method for quickly swapping in/out CSS properties to get correct calculations.\n\t// Note: this method belongs to the css module but it's needed here for the support module.\n\t// If support gets modularized, this method should be moved back to the css module.\n\tswap: function( elem, options, callback, args ) {\n\t\tvar ret, name,\n\t\t\told = {};\n\n\t\t// Remember the old values, and insert the new ones\n\t\tfor ( name in options ) {\n\t\t\told[ name ] = elem.style[ name ];\n\t\t\telem.style[ name ] = options[ name ];\n\t\t}\n\n\t\tret = callback.apply( elem, args || [] );\n\n\t\t// Revert the old values\n\t\tfor ( name in options ) {\n\t\t\telem.style[ name ] = old[ name ];\n\t\t}\n\n\t\treturn ret;\n\t}\n});\n\njQuery.ready.promise = function( obj ) {\n\tif ( !readyList ) {\n\n\t\treadyList = jQuery.Deferred();\n\n\t\t// Catch cases where $(document).ready() is called after the browser event has already occurred.\n\t\t// we once tried to use readyState \"interactive\" here, but it caused issues like the one\n\t\t// discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15\n\t\tif ( document.readyState === \"complete\" ) {\n\t\t\t// Handle it asynchronously to allow scripts the opportunity to delay ready\n\t\t\tsetTimeout( jQuery.ready );\n\n\t\t// Standards-based browsers support DOMContentLoaded\n\t\t} else if ( document.addEventListener ) {\n\t\t\t// Use the handy event callback\n\t\t\tdocument.addEventListener( \"DOMContentLoaded\", completed, false );\n\n\t\t\t// A fallback to window.onload, that will always work\n\t\t\twindow.addEventListener( \"load\", completed, false );\n\n\t\t// If IE event model is used\n\t\t} else {\n\t\t\t// Ensure firing before onload, maybe late but safe also for iframes\n\t\t\tdocument.attachEvent( \"onreadystatechange\", completed );\n\n\t\t\t// A fallback to window.onload, that will always work\n\t\t\twindow.attachEvent( \"onload\", completed );\n\n\t\t\t// If IE and not a frame\n\t\t\t// continually check to see if the document is ready\n\t\t\tvar top = false;\n\n\t\t\ttry {\n\t\t\t\ttop = window.frameElement == null && document.documentElement;\n\t\t\t} catch(e) {}\n\n\t\t\tif ( top && top.doScroll ) {\n\t\t\t\t(function doScrollCheck() {\n\t\t\t\t\tif ( !jQuery.isReady ) {\n\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t// Use the trick by Diego Perini\n\t\t\t\t\t\t\t// http://javascript.nwbox.com/IEContentLoaded/\n\t\t\t\t\t\t\ttop.doScroll(\"left\");\n\t\t\t\t\t\t} catch(e) {\n\t\t\t\t\t\t\treturn setTimeout( doScrollCheck, 50 );\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// detach all dom ready events\n\t\t\t\t\t\tdetach();\n\n\t\t\t\t\t\t// and execute any waiting functions\n\t\t\t\t\t\tjQuery.ready();\n\t\t\t\t\t}\n\t\t\t\t})();\n\t\t\t}\n\t\t}\n\t}\n\treturn readyList.promise( obj );\n};\n\n// Populate the class2type map\njQuery.each(\"Boolean Number String Function Array Date RegExp Object Error\".split(\" \"), function(i, name) {\n\tclass2type[ \"[object \" + name + \"]\" ] = name.toLowerCase();\n});\n\nfunction isArraylike( obj ) {\n\tvar length = obj.length,\n\t\ttype = jQuery.type( obj );\n\n\tif ( jQuery.isWindow( obj ) ) {\n\t\treturn false;\n\t}\n\n\tif ( obj.nodeType === 1 && length ) {\n\t\treturn true;\n\t}\n\n\treturn type === \"array\" || type !== \"function\" &&\n\t\t( length === 0 ||\n\t\ttypeof length === \"number\" && length > 0 && ( length - 1 ) in obj );\n}\n\n// All jQuery objects should point back to these\nrootjQuery = jQuery(document);\n/*!\n * Sizzle CSS Selector Engine v1.9.4-pre\n * http://sizzlejs.com/\n *\n * Copyright 2013 jQuery Foundation, Inc. and other contributors\n * Released under the MIT license\n * http://jquery.org/license\n *\n * Date: 2013-05-15\n */\n(function( window, undefined ) {\n\nvar i,\n\tsupport,\n\tcachedruns,\n\tExpr,\n\tgetText,\n\tisXML,\n\tcompile,\n\toutermostContext,\n\tsortInput,\n\n\t// Local document vars\n\tsetDocument,\n\tdocument,\n\tdocElem,\n\tdocumentIsHTML,\n\trbuggyQSA,\n\trbuggyMatches,\n\tmatches,\n\tcontains,\n\n\t// Instance-specific data\n\texpando = \"sizzle\" + -(new Date()),\n\tpreferredDoc = window.document,\n\tdirruns = 0,\n\tdone = 0,\n\tclassCache = createCache(),\n\ttokenCache = createCache(),\n\tcompilerCache = createCache(),\n\thasDuplicate = false,\n\tsortOrder = function() { return 0; },\n\n\t// General-purpose constants\n\tstrundefined = typeof undefined,\n\tMAX_NEGATIVE = 1 << 31,\n\n\t// Instance methods\n\thasOwn = ({}).hasOwnProperty,\n\tarr = [],\n\tpop = arr.pop,\n\tpush_native = arr.push,\n\tpush = arr.push,\n\tslice = arr.slice,\n\t// Use a stripped-down indexOf if we can't use a native one\n\tindexOf = arr.indexOf || function( elem ) {\n\t\tvar i = 0,\n\t\t\tlen = this.length;\n\t\tfor ( ; i < len; i++ ) {\n\t\t\tif ( this[i] === elem ) {\n\t\t\t\treturn i;\n\t\t\t}\n\t\t}\n\t\treturn -1;\n\t},\n\n\tbooleans = \"checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped\",\n\n\t// Regular expressions\n\n\t// Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace\n\twhitespace = \"[\\\\x20\\\\t\\\\r\\\\n\\\\f]\",\n\t// http://www.w3.org/TR/css3-syntax/#characters\n\tcharacterEncoding = \"(?:\\\\\\\\.|[\\\\w-]|[^\\\\x00-\\\\xa0])+\",\n\n\t// Loosely modeled on CSS identifier characters\n\t// An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors\n\t// Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier\n\tidentifier = characterEncoding.replace( \"w\", \"w#\" ),\n\n\t// Acceptable operators http://www.w3.org/TR/selectors/#attribute-selectors\n\tattributes = \"\\\\[\" + whitespace + \"*(\" + characterEncoding + \")\" + whitespace +\n\t\t\"*(?:([*^$|!~]?=)\" + whitespace + \"*(?:(['\\\"])((?:\\\\\\\\.|[^\\\\\\\\])*?)\\\\3|(\" + identifier + \")|)|)\" + whitespace + \"*\\\\]\",\n\n\t// Prefer arguments quoted,\n\t//   then not containing pseudos/brackets,\n\t//   then attribute selectors/non-parenthetical expressions,\n\t//   then anything else\n\t// These preferences are here to reduce the number of selectors\n\t//   needing tokenize in the PSEUDO preFilter\n\tpseudos = \":(\" + characterEncoding + \")(?:\\\\(((['\\\"])((?:\\\\\\\\.|[^\\\\\\\\])*?)\\\\3|((?:\\\\\\\\.|[^\\\\\\\\()[\\\\]]|\" + attributes.replace( 3, 8 ) + \")*)|.*)\\\\)|)\",\n\n\t// Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter\n\trtrim = new RegExp( \"^\" + whitespace + \"+|((?:^|[^\\\\\\\\])(?:\\\\\\\\.)*)\" + whitespace + \"+$\", \"g\" ),\n\n\trcomma = new RegExp( \"^\" + whitespace + \"*,\" + whitespace + \"*\" ),\n\trcombinators = new RegExp( \"^\" + whitespace + \"*([>+~]|\" + whitespace + \")\" + whitespace + \"*\" ),\n\n\trsibling = new RegExp( whitespace + \"*[+~]\" ),\n\trattributeQuotes = new RegExp( \"=\" + whitespace + \"*([^\\\\]'\\\"]*)\" + whitespace + \"*\\\\]\", \"g\" ),\n\n\trpseudo = new RegExp( pseudos ),\n\tridentifier = new RegExp( \"^\" + identifier + \"$\" ),\n\n\tmatchExpr = {\n\t\t\"ID\": new RegExp( \"^#(\" + characterEncoding + \")\" ),\n\t\t\"CLASS\": new RegExp( \"^\\\\.(\" + characterEncoding + \")\" ),\n\t\t\"TAG\": new RegExp( \"^(\" + characterEncoding.replace( \"w\", \"w*\" ) + \")\" ),\n\t\t\"ATTR\": new RegExp( \"^\" + attributes ),\n\t\t\"PSEUDO\": new RegExp( \"^\" + pseudos ),\n\t\t\"CHILD\": new RegExp( \"^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\\\(\" + whitespace +\n\t\t\t\"*(even|odd|(([+-]|)(\\\\d*)n|)\" + whitespace + \"*(?:([+-]|)\" + whitespace +\n\t\t\t\"*(\\\\d+)|))\" + whitespace + \"*\\\\)|)\", \"i\" ),\n\t\t\"bool\": new RegExp( \"^(?:\" + booleans + \")$\", \"i\" ),\n\t\t// For use in libraries implementing .is()\n\t\t// We use this for POS matching in `select`\n\t\t\"needsContext\": new RegExp( \"^\" + whitespace + \"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\\\(\" +\n\t\t\twhitespace + \"*((?:-\\\\d)?\\\\d*)\" + whitespace + \"*\\\\)|)(?=[^-]|$)\", \"i\" )\n\t},\n\n\trnative = /^[^{]+\\{\\s*\\[native \\w/,\n\n\t// Easily-parseable/retrievable ID or TAG or CLASS selectors\n\trquickExpr = /^(?:#([\\w-]+)|(\\w+)|\\.([\\w-]+))$/,\n\n\trinputs = /^(?:input|select|textarea|button)$/i,\n\trheader = /^h\\d$/i,\n\n\trescape = /'|\\\\/g,\n\n\t// CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters\n\trunescape = new RegExp( \"\\\\\\\\([\\\\da-f]{1,6}\" + whitespace + \"?|(\" + whitespace + \")|.)\", \"ig\" ),\n\tfunescape = function( _, escaped, escapedWhitespace ) {\n\t\tvar high = \"0x\" + escaped - 0x10000;\n\t\t// NaN means non-codepoint\n\t\t// Support: Firefox\n\t\t// Workaround erroneous numeric interpretation of +\"0x\"\n\t\treturn high !== high || escapedWhitespace ?\n\t\t\tescaped :\n\t\t\t// BMP codepoint\n\t\t\thigh < 0 ?\n\t\t\t\tString.fromCharCode( high + 0x10000 ) :\n\t\t\t\t// Supplemental Plane codepoint (surrogate pair)\n\t\t\t\tString.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );\n\t};\n\n// Optimize for push.apply( _, NodeList )\ntry {\n\tpush.apply(\n\t\t(arr = slice.call( preferredDoc.childNodes )),\n\t\tpreferredDoc.childNodes\n\t);\n\t// Support: Android<4.0\n\t// Detect silently failing push.apply\n\tarr[ preferredDoc.childNodes.length ].nodeType;\n} catch ( e ) {\n\tpush = { apply: arr.length ?\n\n\t\t// Leverage slice if possible\n\t\tfunction( target, els ) {\n\t\t\tpush_native.apply( target, slice.call(els) );\n\t\t} :\n\n\t\t// Support: IE<9\n\t\t// Otherwise append directly\n\t\tfunction( target, els ) {\n\t\t\tvar j = target.length,\n\t\t\t\ti = 0;\n\t\t\t// Can't trust NodeList.length\n\t\t\twhile ( (target[j++] = els[i++]) ) {}\n\t\t\ttarget.length = j - 1;\n\t\t}\n\t};\n}\n\nfunction Sizzle( selector, context, results, seed ) {\n\tvar match, elem, m, nodeType,\n\t\t// QSA vars\n\t\ti, groups, old, nid, newContext, newSelector;\n\n\tif ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) {\n\t\tsetDocument( context );\n\t}\n\n\tcontext = context || document;\n\tresults = results || [];\n\n\tif ( !selector || typeof selector !== \"string\" ) {\n\t\treturn results;\n\t}\n\n\tif ( (nodeType = context.nodeType) !== 1 && nodeType !== 9 ) {\n\t\treturn [];\n\t}\n\n\tif ( documentIsHTML && !seed ) {\n\n\t\t// Shortcuts\n\t\tif ( (match = rquickExpr.exec( selector )) ) {\n\t\t\t// Speed-up: Sizzle(\"#ID\")\n\t\t\tif ( (m = match[1]) ) {\n\t\t\t\tif ( nodeType === 9 ) {\n\t\t\t\t\telem = context.getElementById( m );\n\t\t\t\t\t// Check parentNode to catch when Blackberry 4.6 returns\n\t\t\t\t\t// nodes that are no longer in the document #6963\n\t\t\t\t\tif ( elem && elem.parentNode ) {\n\t\t\t\t\t\t// Handle the case where IE, Opera, and Webkit return items\n\t\t\t\t\t\t// by name instead of ID\n\t\t\t\t\t\tif ( elem.id === m ) {\n\t\t\t\t\t\t\tresults.push( elem );\n\t\t\t\t\t\t\treturn results;\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn results;\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// Context is not a document\n\t\t\t\t\tif ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) &&\n\t\t\t\t\t\tcontains( context, elem ) && elem.id === m ) {\n\t\t\t\t\t\tresults.push( elem );\n\t\t\t\t\t\treturn results;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t// Speed-up: Sizzle(\"TAG\")\n\t\t\t} else if ( match[2] ) {\n\t\t\t\tpush.apply( results, context.getElementsByTagName( selector ) );\n\t\t\t\treturn results;\n\n\t\t\t// Speed-up: Sizzle(\".CLASS\")\n\t\t\t} else if ( (m = match[3]) && support.getElementsByClassName && context.getElementsByClassName ) {\n\t\t\t\tpush.apply( results, context.getElementsByClassName( m ) );\n\t\t\t\treturn results;\n\t\t\t}\n\t\t}\n\n\t\t// QSA path\n\t\tif ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) {\n\t\t\tnid = old = expando;\n\t\t\tnewContext = context;\n\t\t\tnewSelector = nodeType === 9 && selector;\n\n\t\t\t// qSA works strangely on Element-rooted queries\n\t\t\t// We can work around this by specifying an extra ID on the root\n\t\t\t// and working up from there (Thanks to Andrew Dupont for the technique)\n\t\t\t// IE 8 doesn't work on object elements\n\t\t\tif ( nodeType === 1 && context.nodeName.toLowerCase() !== \"object\" ) {\n\t\t\t\tgroups = tokenize( selector );\n\n\t\t\t\tif ( (old = context.getAttribute(\"id\")) ) {\n\t\t\t\t\tnid = old.replace( rescape, \"\\\\$&\" );\n\t\t\t\t} else {\n\t\t\t\t\tcontext.setAttribute( \"id\", nid );\n\t\t\t\t}\n\t\t\t\tnid = \"[id='\" + nid + \"'] \";\n\n\t\t\t\ti = groups.length;\n\t\t\t\twhile ( i-- ) {\n\t\t\t\t\tgroups[i] = nid + toSelector( groups[i] );\n\t\t\t\t}\n\t\t\t\tnewContext = rsibling.test( selector ) && context.parentNode || context;\n\t\t\t\tnewSelector = groups.join(\",\");\n\t\t\t}\n\n\t\t\tif ( newSelector ) {\n\t\t\t\ttry {\n\t\t\t\t\tpush.apply( results,\n\t\t\t\t\t\tnewContext.querySelectorAll( newSelector )\n\t\t\t\t\t);\n\t\t\t\t\treturn results;\n\t\t\t\t} catch(qsaError) {\n\t\t\t\t} finally {\n\t\t\t\t\tif ( !old ) {\n\t\t\t\t\t\tcontext.removeAttribute(\"id\");\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// All others\n\treturn select( selector.replace( rtrim, \"$1\" ), context, results, seed );\n}\n\n/**\n * For feature detection\n * @param {Function} fn The function to test for native support\n */\nfunction isNative( fn ) {\n\treturn rnative.test( fn + \"\" );\n}\n\n/**\n * Create key-value caches of limited size\n * @returns {Function(string, Object)} Returns the Object data after storing it on itself with\n *\tproperty name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)\n *\tdeleting the oldest entry\n */\nfunction createCache() {\n\tvar keys = [];\n\n\tfunction cache( key, value ) {\n\t\t// Use (key + \" \") to avoid collision with native prototype properties (see Issue #157)\n\t\tif ( keys.push( key += \" \" ) > Expr.cacheLength ) {\n\t\t\t// Only keep the most recent entries\n\t\t\tdelete cache[ keys.shift() ];\n\t\t}\n\t\treturn (cache[ key ] = value);\n\t}\n\treturn cache;\n}\n\n/**\n * Mark a function for special use by Sizzle\n * @param {Function} fn The function to mark\n */\nfunction markFunction( fn ) {\n\tfn[ expando ] = true;\n\treturn fn;\n}\n\n/**\n * Support testing using an element\n * @param {Function} fn Passed the created div and expects a boolean result\n */\nfunction assert( fn ) {\n\tvar div = document.createElement(\"div\");\n\n\ttry {\n\t\treturn !!fn( div );\n\t} catch (e) {\n\t\treturn false;\n\t} finally {\n\t\t// Remove from its parent by default\n\t\tif ( div.parentNode ) {\n\t\t\tdiv.parentNode.removeChild( div );\n\t\t}\n\t\t// release memory in IE\n\t\tdiv = null;\n\t}\n}\n\n/**\n * Adds the same handler for all of the specified attrs\n * @param {String} attrs Pipe-separated list of attributes\n * @param {Function} handler The method that will be applied if the test fails\n * @param {Boolean} test The result of a test. If true, null will be set as the handler in leiu of the specified handler\n */\nfunction addHandle( attrs, handler, test ) {\n\tattrs = attrs.split(\"|\");\n\tvar current,\n\t\ti = attrs.length,\n\t\tsetHandle = test ? null : handler;\n\n\twhile ( i-- ) {\n\t\t// Don't override a user's handler\n\t\tif ( !(current = Expr.attrHandle[ attrs[i] ]) || current === handler ) {\n\t\t\tExpr.attrHandle[ attrs[i] ] = setHandle;\n\t\t}\n\t}\n}\n\n/**\n * Fetches boolean attributes by node\n * @param {Element} elem\n * @param {String} name\n */\nfunction boolHandler( elem, name ) {\n\t// XML does not need to be checked as this will not be assigned for XML documents\n\tvar val = elem.getAttributeNode( name );\n\treturn val && val.specified ?\n\t\tval.value :\n\t\telem[ name ] === true ? name.toLowerCase() : null;\n}\n\n/**\n * Fetches attributes without interpolation\n * http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx\n * @param {Element} elem\n * @param {String} name\n */\nfunction interpolationHandler( elem, name ) {\n\t// XML does not need to be checked as this will not be assigned for XML documents\n\treturn elem.getAttribute( name, name.toLowerCase() === \"type\" ? 1 : 2 );\n}\n\n/**\n * Uses defaultValue to retrieve value in IE6/7\n * @param {Element} elem\n * @param {String} name\n */\nfunction valueHandler( elem ) {\n\t// Ignore the value *property* on inputs by using defaultValue\n\t// Fallback to Sizzle.attr by returning undefined where appropriate\n\t// XML does not need to be checked as this will not be assigned for XML documents\n\tif ( elem.nodeName.toLowerCase() === \"input\" ) {\n\t\treturn elem.defaultValue;\n\t}\n}\n\n/**\n * Checks document order of two siblings\n * @param {Element} a\n * @param {Element} b\n * @returns Returns -1 if a precedes b, 1 if a follows b\n */\nfunction siblingCheck( a, b ) {\n\tvar cur = b && a,\n\t\tdiff = cur && a.nodeType === 1 && b.nodeType === 1 &&\n\t\t\t( ~b.sourceIndex || MAX_NEGATIVE ) -\n\t\t\t( ~a.sourceIndex || MAX_NEGATIVE );\n\n\t// Use IE sourceIndex if available on both nodes\n\tif ( diff ) {\n\t\treturn diff;\n\t}\n\n\t// Check if b follows a\n\tif ( cur ) {\n\t\twhile ( (cur = cur.nextSibling) ) {\n\t\t\tif ( cur === b ) {\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn a ? 1 : -1;\n}\n\n/**\n * Returns a function to use in pseudos for input types\n * @param {String} type\n */\nfunction createInputPseudo( type ) {\n\treturn function( elem ) {\n\t\tvar name = elem.nodeName.toLowerCase();\n\t\treturn name === \"input\" && elem.type === type;\n\t};\n}\n\n/**\n * Returns a function to use in pseudos for buttons\n * @param {String} type\n */\nfunction createButtonPseudo( type ) {\n\treturn function( elem ) {\n\t\tvar name = elem.nodeName.toLowerCase();\n\t\treturn (name === \"input\" || name === \"button\") && elem.type === type;\n\t};\n}\n\n/**\n * Returns a function to use in pseudos for positionals\n * @param {Function} fn\n */\nfunction createPositionalPseudo( fn ) {\n\treturn markFunction(function( argument ) {\n\t\targument = +argument;\n\t\treturn markFunction(function( seed, matches ) {\n\t\t\tvar j,\n\t\t\t\tmatchIndexes = fn( [], seed.length, argument ),\n\t\t\t\ti = matchIndexes.length;\n\n\t\t\t// Match elements found at the specified indexes\n\t\t\twhile ( i-- ) {\n\t\t\t\tif ( seed[ (j = matchIndexes[i]) ] ) {\n\t\t\t\t\tseed[j] = !(matches[j] = seed[j]);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t});\n}\n\n/**\n * Detect xml\n * @param {Element|Object} elem An element or a document\n */\nisXML = Sizzle.isXML = function( elem ) {\n\t// documentElement is verified for cases where it doesn't yet exist\n\t// (such as loading iframes in IE - #4833)\n\tvar documentElement = elem && (elem.ownerDocument || elem).documentElement;\n\treturn documentElement ? documentElement.nodeName !== \"HTML\" : false;\n};\n\n// Expose support vars for convenience\nsupport = Sizzle.support = {};\n\n/**\n * Sets document-related variables once based on the current document\n * @param {Element|Object} [doc] An element or document object to use to set the document\n * @returns {Object} Returns the current document\n */\nsetDocument = Sizzle.setDocument = function( node ) {\n\tvar doc = node ? node.ownerDocument || node : preferredDoc;\n\n\t// If no document and documentElement is available, return\n\tif ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) {\n\t\treturn document;\n\t}\n\n\t// Set our document\n\tdocument = doc;\n\tdocElem = doc.documentElement;\n\n\t// Support tests\n\tdocumentIsHTML = !isXML( doc );\n\n\t/* Attributes\n\t---------------------------------------------------------------------- */\n\n\t// Support: IE<8\n\t// Verify that getAttribute really returns attributes and not properties (excepting IE8 booleans)\n\tsupport.attributes = assert(function( div ) {\n\n\t\t// Support: IE<8\n\t\t// Prevent attribute/property \"interpolation\"\n\t\tdiv.innerHTML = \"<a href='#'></a>\";\n\t\taddHandle( \"type|href|height|width\", interpolationHandler, div.firstChild.getAttribute(\"href\") === \"#\" );\n\n\t\t// Support: IE<9\n\t\t// Use getAttributeNode to fetch booleans when getAttribute lies\n\t\taddHandle( booleans, boolHandler, div.getAttribute(\"disabled\") == null );\n\n\t\tdiv.className = \"i\";\n\t\treturn !div.getAttribute(\"className\");\n\t});\n\n\t// Support: IE<9\n\t// Retrieving value should defer to defaultValue\n\tsupport.input = assert(function( div ) {\n\t\tdiv.innerHTML = \"<input>\";\n\t\tdiv.firstChild.setAttribute( \"value\", \"\" );\n\t\treturn div.firstChild.getAttribute( \"value\" ) === \"\";\n\t});\n\n\t// IE6/7 still return empty string for value,\n\t// but are actually retrieving the property\n\taddHandle( \"value\", valueHandler, support.attributes && support.input );\n\n\t/* getElement(s)By*\n\t---------------------------------------------------------------------- */\n\n\t// Check if getElementsByTagName(\"*\") returns only elements\n\tsupport.getElementsByTagName = assert(function( div ) {\n\t\tdiv.appendChild( doc.createComment(\"\") );\n\t\treturn !div.getElementsByTagName(\"*\").length;\n\t});\n\n\t// Check if getElementsByClassName can be trusted\n\tsupport.getElementsByClassName = assert(function( div ) {\n\t\tdiv.innerHTML = \"<div class='a'></div><div class='a i'></div>\";\n\n\t\t// Support: Safari<4\n\t\t// Catch class over-caching\n\t\tdiv.firstChild.className = \"i\";\n\t\t// Support: Opera<10\n\t\t// Catch gEBCN failure to find non-leading classes\n\t\treturn div.getElementsByClassName(\"i\").length === 2;\n\t});\n\n\t// Support: IE<10\n\t// Check if getElementById returns elements by name\n\t// The broken getElementById methods don't pick up programatically-set names,\n\t// so use a roundabout getElementsByName test\n\tsupport.getById = assert(function( div ) {\n\t\tdocElem.appendChild( div ).id = expando;\n\t\treturn !doc.getElementsByName || !doc.getElementsByName( expando ).length;\n\t});\n\n\t// ID find and filter\n\tif ( support.getById ) {\n\t\tExpr.find[\"ID\"] = function( id, context ) {\n\t\t\tif ( typeof context.getElementById !== strundefined && documentIsHTML ) {\n\t\t\t\tvar m = context.getElementById( id );\n\t\t\t\t// Check parentNode to catch when Blackberry 4.6 returns\n\t\t\t\t// nodes that are no longer in the document #6963\n\t\t\t\treturn m && m.parentNode ? [m] : [];\n\t\t\t}\n\t\t};\n\t\tExpr.filter[\"ID\"] = function( id ) {\n\t\t\tvar attrId = id.replace( runescape, funescape );\n\t\t\treturn function( elem ) {\n\t\t\t\treturn elem.getAttribute(\"id\") === attrId;\n\t\t\t};\n\t\t};\n\t} else {\n\t\t// Support: IE6/7\n\t\t// getElementById is not reliable as a find shortcut\n\t\tdelete Expr.find[\"ID\"];\n\n\t\tExpr.filter[\"ID\"] =  function( id ) {\n\t\t\tvar attrId = id.replace( runescape, funescape );\n\t\t\treturn function( elem ) {\n\t\t\t\tvar node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode(\"id\");\n\t\t\t\treturn node && node.value === attrId;\n\t\t\t};\n\t\t};\n\t}\n\n\t// Tag\n\tExpr.find[\"TAG\"] = support.getElementsByTagName ?\n\t\tfunction( tag, context ) {\n\t\t\tif ( typeof context.getElementsByTagName !== strundefined ) {\n\t\t\t\treturn context.getElementsByTagName( tag );\n\t\t\t}\n\t\t} :\n\t\tfunction( tag, context ) {\n\t\t\tvar elem,\n\t\t\t\ttmp = [],\n\t\t\t\ti = 0,\n\t\t\t\tresults = context.getElementsByTagName( tag );\n\n\t\t\t// Filter out possible comments\n\t\t\tif ( tag === \"*\" ) {\n\t\t\t\twhile ( (elem = results[i++]) ) {\n\t\t\t\t\tif ( elem.nodeType === 1 ) {\n\t\t\t\t\t\ttmp.push( elem );\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn tmp;\n\t\t\t}\n\t\t\treturn results;\n\t\t};\n\n\t// Class\n\tExpr.find[\"CLASS\"] = support.getElementsByClassName && function( className, context ) {\n\t\tif ( typeof context.getElementsByClassName !== strundefined && documentIsHTML ) {\n\t\t\treturn context.getElementsByClassName( className );\n\t\t}\n\t};\n\n\t/* QSA/matchesSelector\n\t---------------------------------------------------------------------- */\n\n\t// QSA and matchesSelector support\n\n\t// matchesSelector(:active) reports false when true (IE9/Opera 11.5)\n\trbuggyMatches = [];\n\n\t// qSa(:focus) reports false when true (Chrome 21)\n\t// We allow this because of a bug in IE8/9 that throws an error\n\t// whenever `document.activeElement` is accessed on an iframe\n\t// So, we allow :focus to pass through QSA all the time to avoid the IE error\n\t// See http://bugs.jquery.com/ticket/13378\n\trbuggyQSA = [];\n\n\tif ( (support.qsa = isNative(doc.querySelectorAll)) ) {\n\t\t// Build QSA regex\n\t\t// Regex strategy adopted from Diego Perini\n\t\tassert(function( div ) {\n\t\t\t// Select is set to empty string on purpose\n\t\t\t// This is to test IE's treatment of not explicitly\n\t\t\t// setting a boolean content attribute,\n\t\t\t// since its presence should be enough\n\t\t\t// http://bugs.jquery.com/ticket/12359\n\t\t\tdiv.innerHTML = \"<select><option selected=''></option></select>\";\n\n\t\t\t// Support: IE8\n\t\t\t// Boolean attributes and \"value\" are not treated correctly\n\t\t\tif ( !div.querySelectorAll(\"[selected]\").length ) {\n\t\t\t\trbuggyQSA.push( \"\\\\[\" + whitespace + \"*(?:value|\" + booleans + \")\" );\n\t\t\t}\n\n\t\t\t// Webkit/Opera - :checked should return selected option elements\n\t\t\t// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked\n\t\t\t// IE8 throws error here and will not see later tests\n\t\t\tif ( !div.querySelectorAll(\":checked\").length ) {\n\t\t\t\trbuggyQSA.push(\":checked\");\n\t\t\t}\n\t\t});\n\n\t\tassert(function( div ) {\n\n\t\t\t// Support: Opera 10-12/IE8\n\t\t\t// ^= $= *= and empty values\n\t\t\t// Should not select anything\n\t\t\t// Support: Windows 8 Native Apps\n\t\t\t// The type attribute is restricted during .innerHTML assignment\n\t\t\tvar input = doc.createElement(\"input\");\n\t\t\tinput.setAttribute( \"type\", \"hidden\" );\n\t\t\tdiv.appendChild( input ).setAttribute( \"t\", \"\" );\n\n\t\t\tif ( div.querySelectorAll(\"[t^='']\").length ) {\n\t\t\t\trbuggyQSA.push( \"[*^$]=\" + whitespace + \"*(?:''|\\\"\\\")\" );\n\t\t\t}\n\n\t\t\t// FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)\n\t\t\t// IE8 throws error here and will not see later tests\n\t\t\tif ( !div.querySelectorAll(\":enabled\").length ) {\n\t\t\t\trbuggyQSA.push( \":enabled\", \":disabled\" );\n\t\t\t}\n\n\t\t\t// Opera 10-11 does not throw on post-comma invalid pseudos\n\t\t\tdiv.querySelectorAll(\"*,:x\");\n\t\t\trbuggyQSA.push(\",.*:\");\n\t\t});\n\t}\n\n\tif ( (support.matchesSelector = isNative( (matches = docElem.webkitMatchesSelector ||\n\t\tdocElem.mozMatchesSelector ||\n\t\tdocElem.oMatchesSelector ||\n\t\tdocElem.msMatchesSelector) )) ) {\n\n\t\tassert(function( div ) {\n\t\t\t// Check to see if it's possible to do matchesSelector\n\t\t\t// on a disconnected node (IE 9)\n\t\t\tsupport.disconnectedMatch = matches.call( div, \"div\" );\n\n\t\t\t// This should fail with an exception\n\t\t\t// Gecko does not error, returns false instead\n\t\t\tmatches.call( div, \"[s!='']:x\" );\n\t\t\trbuggyMatches.push( \"!=\", pseudos );\n\t\t});\n\t}\n\n\trbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join(\"|\") );\n\trbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join(\"|\") );\n\n\t/* Contains\n\t---------------------------------------------------------------------- */\n\n\t// Element contains another\n\t// Purposefully does not implement inclusive descendent\n\t// As in, an element does not contain itself\n\tcontains = isNative(docElem.contains) || docElem.compareDocumentPosition ?\n\t\tfunction( a, b ) {\n\t\t\tvar adown = a.nodeType === 9 ? a.documentElement : a,\n\t\t\t\tbup = b && b.parentNode;\n\t\t\treturn a === bup || !!( bup && bup.nodeType === 1 && (\n\t\t\t\tadown.contains ?\n\t\t\t\t\tadown.contains( bup ) :\n\t\t\t\t\ta.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16\n\t\t\t));\n\t\t} :\n\t\tfunction( a, b ) {\n\t\t\tif ( b ) {\n\t\t\t\twhile ( (b = b.parentNode) ) {\n\t\t\t\t\tif ( b === a ) {\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn false;\n\t\t};\n\n\t/* Sorting\n\t---------------------------------------------------------------------- */\n\n\t// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27)\n\t// Detached nodes confoundingly follow *each other*\n\tsupport.sortDetached = assert(function( div1 ) {\n\t\t// Should return 1, but returns 4 (following)\n\t\treturn div1.compareDocumentPosition( doc.createElement(\"div\") ) & 1;\n\t});\n\n\t// Document order sorting\n\tsortOrder = docElem.compareDocumentPosition ?\n\tfunction( a, b ) {\n\n\t\t// Flag for duplicate removal\n\t\tif ( a === b ) {\n\t\t\thasDuplicate = true;\n\t\t\treturn 0;\n\t\t}\n\n\t\tvar compare = b.compareDocumentPosition && a.compareDocumentPosition && a.compareDocumentPosition( b );\n\n\t\tif ( compare ) {\n\t\t\t// Disconnected nodes\n\t\t\tif ( compare & 1 ||\n\t\t\t\t(!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) {\n\n\t\t\t\t// Choose the first element that is related to our preferred document\n\t\t\t\tif ( a === doc || contains(preferredDoc, a) ) {\n\t\t\t\t\treturn -1;\n\t\t\t\t}\n\t\t\t\tif ( b === doc || contains(preferredDoc, b) ) {\n\t\t\t\t\treturn 1;\n\t\t\t\t}\n\n\t\t\t\t// Maintain original order\n\t\t\t\treturn sortInput ?\n\t\t\t\t\t( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) :\n\t\t\t\t\t0;\n\t\t\t}\n\n\t\t\treturn compare & 4 ? -1 : 1;\n\t\t}\n\n\t\t// Not directly comparable, sort on existence of method\n\t\treturn a.compareDocumentPosition ? -1 : 1;\n\t} :\n\tfunction( a, b ) {\n\t\tvar cur,\n\t\t\ti = 0,\n\t\t\taup = a.parentNode,\n\t\t\tbup = b.parentNode,\n\t\t\tap = [ a ],\n\t\t\tbp = [ b ];\n\n\t\t// Exit early if the nodes are identical\n\t\tif ( a === b ) {\n\t\t\thasDuplicate = true;\n\t\t\treturn 0;\n\n\t\t// Parentless nodes are either documents or disconnected\n\t\t} else if ( !aup || !bup ) {\n\t\t\treturn a === doc ? -1 :\n\t\t\t\tb === doc ? 1 :\n\t\t\t\taup ? -1 :\n\t\t\t\tbup ? 1 :\n\t\t\t\tsortInput ?\n\t\t\t\t( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) :\n\t\t\t\t0;\n\n\t\t// If the nodes are siblings, we can do a quick check\n\t\t} else if ( aup === bup ) {\n\t\t\treturn siblingCheck( a, b );\n\t\t}\n\n\t\t// Otherwise we need full lists of their ancestors for comparison\n\t\tcur = a;\n\t\twhile ( (cur = cur.parentNode) ) {\n\t\t\tap.unshift( cur );\n\t\t}\n\t\tcur = b;\n\t\twhile ( (cur = cur.parentNode) ) {\n\t\t\tbp.unshift( cur );\n\t\t}\n\n\t\t// Walk down the tree looking for a discrepancy\n\t\twhile ( ap[i] === bp[i] ) {\n\t\t\ti++;\n\t\t}\n\n\t\treturn i ?\n\t\t\t// Do a sibling check if the nodes have a common ancestor\n\t\t\tsiblingCheck( ap[i], bp[i] ) :\n\n\t\t\t// Otherwise nodes in our document sort first\n\t\t\tap[i] === preferredDoc ? -1 :\n\t\t\tbp[i] === preferredDoc ? 1 :\n\t\t\t0;\n\t};\n\n\treturn doc;\n};\n\nSizzle.matches = function( expr, elements ) {\n\treturn Sizzle( expr, null, null, elements );\n};\n\nSizzle.matchesSelector = function( elem, expr ) {\n\t// Set document vars if needed\n\tif ( ( elem.ownerDocument || elem ) !== document ) {\n\t\tsetDocument( elem );\n\t}\n\n\t// Make sure that attribute selectors are quoted\n\texpr = expr.replace( rattributeQuotes, \"='$1']\" );\n\n\tif ( support.matchesSelector && documentIsHTML &&\n\t\t( !rbuggyMatches || !rbuggyMatches.test( expr ) ) &&\n\t\t( !rbuggyQSA     || !rbuggyQSA.test( expr ) ) ) {\n\n\t\ttry {\n\t\t\tvar ret = matches.call( elem, expr );\n\n\t\t\t// IE 9's matchesSelector returns false on disconnected nodes\n\t\t\tif ( ret || support.disconnectedMatch ||\n\t\t\t\t\t// As well, disconnected nodes are said to be in a document\n\t\t\t\t\t// fragment in IE 9\n\t\t\t\t\telem.document && elem.document.nodeType !== 11 ) {\n\t\t\t\treturn ret;\n\t\t\t}\n\t\t} catch(e) {}\n\t}\n\n\treturn Sizzle( expr, document, null, [elem] ).length > 0;\n};\n\nSizzle.contains = function( context, elem ) {\n\t// Set document vars if needed\n\tif ( ( context.ownerDocument || context ) !== document ) {\n\t\tsetDocument( context );\n\t}\n\treturn contains( context, elem );\n};\n\nSizzle.attr = function( elem, name ) {\n\t// Set document vars if needed\n\tif ( ( elem.ownerDocument || elem ) !== document ) {\n\t\tsetDocument( elem );\n\t}\n\n\tvar fn = Expr.attrHandle[ name.toLowerCase() ],\n\t\t// Don't get fooled by Object.prototype properties (jQuery #13807)\n\t\tval = ( fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ?\n\t\t\tfn( elem, name, !documentIsHTML ) :\n\t\t\tundefined );\n\n\treturn val === undefined ?\n\t\tsupport.attributes || !documentIsHTML ?\n\t\t\telem.getAttribute( name ) :\n\t\t\t(val = elem.getAttributeNode(name)) && val.specified ?\n\t\t\t\tval.value :\n\t\t\t\tnull :\n\t\tval;\n};\n\nSizzle.error = function( msg ) {\n\tthrow new Error( \"Syntax error, unrecognized expression: \" + msg );\n};\n\n/**\n * Document sorting and removing duplicates\n * @param {ArrayLike} results\n */\nSizzle.uniqueSort = function( results ) {\n\tvar elem,\n\t\tduplicates = [],\n\t\tj = 0,\n\t\ti = 0;\n\n\t// Unless we *know* we can detect duplicates, assume their presence\n\thasDuplicate = !support.detectDuplicates;\n\tsortInput = !support.sortStable && results.slice( 0 );\n\tresults.sort( sortOrder );\n\n\tif ( hasDuplicate ) {\n\t\twhile ( (elem = results[i++]) ) {\n\t\t\tif ( elem === results[ i ] ) {\n\t\t\t\tj = duplicates.push( i );\n\t\t\t}\n\t\t}\n\t\twhile ( j-- ) {\n\t\t\tresults.splice( duplicates[ j ], 1 );\n\t\t}\n\t}\n\n\treturn results;\n};\n\n/**\n * Utility function for retrieving the text value of an array of DOM nodes\n * @param {Array|Element} elem\n */\ngetText = Sizzle.getText = function( elem ) {\n\tvar node,\n\t\tret = \"\",\n\t\ti = 0,\n\t\tnodeType = elem.nodeType;\n\n\tif ( !nodeType ) {\n\t\t// If no nodeType, this is expected to be an array\n\t\tfor ( ; (node = elem[i]); i++ ) {\n\t\t\t// Do not traverse comment nodes\n\t\t\tret += getText( node );\n\t\t}\n\t} else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {\n\t\t// Use textContent for elements\n\t\t// innerText usage removed for consistency of new lines (see #11153)\n\t\tif ( typeof elem.textContent === \"string\" ) {\n\t\t\treturn elem.textContent;\n\t\t} else {\n\t\t\t// Traverse its children\n\t\t\tfor ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {\n\t\t\t\tret += getText( elem );\n\t\t\t}\n\t\t}\n\t} else if ( nodeType === 3 || nodeType === 4 ) {\n\t\treturn elem.nodeValue;\n\t}\n\t// Do not include comment or processing instruction nodes\n\n\treturn ret;\n};\n\nExpr = Sizzle.selectors = {\n\n\t// Can be adjusted by the user\n\tcacheLength: 50,\n\n\tcreatePseudo: markFunction,\n\n\tmatch: matchExpr,\n\n\tattrHandle: {},\n\n\tfind: {},\n\n\trelative: {\n\t\t\">\": { dir: \"parentNode\", first: true },\n\t\t\" \": { dir: \"parentNode\" },\n\t\t\"+\": { dir: \"previousSibling\", first: true },\n\t\t\"~\": { dir: \"previousSibling\" }\n\t},\n\n\tpreFilter: {\n\t\t\"ATTR\": function( match ) {\n\t\t\tmatch[1] = match[1].replace( runescape, funescape );\n\n\t\t\t// Move the given value to match[3] whether quoted or unquoted\n\t\t\tmatch[3] = ( match[4] || match[5] || \"\" ).replace( runescape, funescape );\n\n\t\t\tif ( match[2] === \"~=\" ) {\n\t\t\t\tmatch[3] = \" \" + match[3] + \" \";\n\t\t\t}\n\n\t\t\treturn match.slice( 0, 4 );\n\t\t},\n\n\t\t\"CHILD\": function( match ) {\n\t\t\t/* matches from matchExpr[\"CHILD\"]\n\t\t\t\t1 type (only|nth|...)\n\t\t\t\t2 what (child|of-type)\n\t\t\t\t3 argument (even|odd|\\d*|\\d*n([+-]\\d+)?|...)\n\t\t\t\t4 xn-component of xn+y argument ([+-]?\\d*n|)\n\t\t\t\t5 sign of xn-component\n\t\t\t\t6 x of xn-component\n\t\t\t\t7 sign of y-component\n\t\t\t\t8 y of y-component\n\t\t\t*/\n\t\t\tmatch[1] = match[1].toLowerCase();\n\n\t\t\tif ( match[1].slice( 0, 3 ) === \"nth\" ) {\n\t\t\t\t// nth-* requires argument\n\t\t\t\tif ( !match[3] ) {\n\t\t\t\t\tSizzle.error( match[0] );\n\t\t\t\t}\n\n\t\t\t\t// numeric x and y parameters for Expr.filter.CHILD\n\t\t\t\t// remember that false/true cast respectively to 0/1\n\t\t\t\tmatch[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === \"even\" || match[3] === \"odd\" ) );\n\t\t\t\tmatch[5] = +( ( match[7] + match[8] ) || match[3] === \"odd\" );\n\n\t\t\t// other types prohibit arguments\n\t\t\t} else if ( match[3] ) {\n\t\t\t\tSizzle.error( match[0] );\n\t\t\t}\n\n\t\t\treturn match;\n\t\t},\n\n\t\t\"PSEUDO\": function( match ) {\n\t\t\tvar excess,\n\t\t\t\tunquoted = !match[5] && match[2];\n\n\t\t\tif ( matchExpr[\"CHILD\"].test( match[0] ) ) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\t// Accept quoted arguments as-is\n\t\t\tif ( match[3] && match[4] !== undefined ) {\n\t\t\t\tmatch[2] = match[4];\n\n\t\t\t// Strip excess characters from unquoted arguments\n\t\t\t} else if ( unquoted && rpseudo.test( unquoted ) &&\n\t\t\t\t// Get excess from tokenize (recursively)\n\t\t\t\t(excess = tokenize( unquoted, true )) &&\n\t\t\t\t// advance to the next closing parenthesis\n\t\t\t\t(excess = unquoted.indexOf( \")\", unquoted.length - excess ) - unquoted.length) ) {\n\n\t\t\t\t// excess is a negative index\n\t\t\t\tmatch[0] = match[0].slice( 0, excess );\n\t\t\t\tmatch[2] = unquoted.slice( 0, excess );\n\t\t\t}\n\n\t\t\t// Return only captures needed by the pseudo filter method (type and argument)\n\t\t\treturn match.slice( 0, 3 );\n\t\t}\n\t},\n\n\tfilter: {\n\n\t\t\"TAG\": function( nodeNameSelector ) {\n\t\t\tvar nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase();\n\t\t\treturn nodeNameSelector === \"*\" ?\n\t\t\t\tfunction() { return true; } :\n\t\t\t\tfunction( elem ) {\n\t\t\t\t\treturn elem.nodeName && elem.nodeName.toLowerCase() === nodeName;\n\t\t\t\t};\n\t\t},\n\n\t\t\"CLASS\": function( className ) {\n\t\t\tvar pattern = classCache[ className + \" \" ];\n\n\t\t\treturn pattern ||\n\t\t\t\t(pattern = new RegExp( \"(^|\" + whitespace + \")\" + className + \"(\" + whitespace + \"|$)\" )) &&\n\t\t\t\tclassCache( className, function( elem ) {\n\t\t\t\t\treturn pattern.test( typeof elem.className === \"string\" && elem.className || typeof elem.getAttribute !== strundefined && elem.getAttribute(\"class\") || \"\" );\n\t\t\t\t});\n\t\t},\n\n\t\t\"ATTR\": function( name, operator, check ) {\n\t\t\treturn function( elem ) {\n\t\t\t\tvar result = Sizzle.attr( elem, name );\n\n\t\t\t\tif ( result == null ) {\n\t\t\t\t\treturn operator === \"!=\";\n\t\t\t\t}\n\t\t\t\tif ( !operator ) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\n\t\t\t\tresult += \"\";\n\n\t\t\t\treturn operator === \"=\" ? result === check :\n\t\t\t\t\toperator === \"!=\" ? result !== check :\n\t\t\t\t\toperator === \"^=\" ? check && result.indexOf( check ) === 0 :\n\t\t\t\t\toperator === \"*=\" ? check && result.indexOf( check ) > -1 :\n\t\t\t\t\toperator === \"$=\" ? check && result.slice( -check.length ) === check :\n\t\t\t\t\toperator === \"~=\" ? ( \" \" + result + \" \" ).indexOf( check ) > -1 :\n\t\t\t\t\toperator === \"|=\" ? result === check || result.slice( 0, check.length + 1 ) === check + \"-\" :\n\t\t\t\t\tfalse;\n\t\t\t};\n\t\t},\n\n\t\t\"CHILD\": function( type, what, argument, first, last ) {\n\t\t\tvar simple = type.slice( 0, 3 ) !== \"nth\",\n\t\t\t\tforward = type.slice( -4 ) !== \"last\",\n\t\t\t\tofType = what === \"of-type\";\n\n\t\t\treturn first === 1 && last === 0 ?\n\n\t\t\t\t// Shortcut for :nth-*(n)\n\t\t\t\tfunction( elem ) {\n\t\t\t\t\treturn !!elem.parentNode;\n\t\t\t\t} :\n\n\t\t\t\tfunction( elem, context, xml ) {\n\t\t\t\t\tvar cache, outerCache, node, diff, nodeIndex, start,\n\t\t\t\t\t\tdir = simple !== forward ? \"nextSibling\" : \"previousSibling\",\n\t\t\t\t\t\tparent = elem.parentNode,\n\t\t\t\t\t\tname = ofType && elem.nodeName.toLowerCase(),\n\t\t\t\t\t\tuseCache = !xml && !ofType;\n\n\t\t\t\t\tif ( parent ) {\n\n\t\t\t\t\t\t// :(first|last|only)-(child|of-type)\n\t\t\t\t\t\tif ( simple ) {\n\t\t\t\t\t\t\twhile ( dir ) {\n\t\t\t\t\t\t\t\tnode = elem;\n\t\t\t\t\t\t\t\twhile ( (node = node[ dir ]) ) {\n\t\t\t\t\t\t\t\t\tif ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) {\n\t\t\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t// Reverse direction for :only-* (if we haven't yet done so)\n\t\t\t\t\t\t\t\tstart = dir = type === \"only\" && !start && \"nextSibling\";\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tstart = [ forward ? parent.firstChild : parent.lastChild ];\n\n\t\t\t\t\t\t// non-xml :nth-child(...) stores cache data on `parent`\n\t\t\t\t\t\tif ( forward && useCache ) {\n\t\t\t\t\t\t\t// Seek `elem` from a previously-cached index\n\t\t\t\t\t\t\touterCache = parent[ expando ] || (parent[ expando ] = {});\n\t\t\t\t\t\t\tcache = outerCache[ type ] || [];\n\t\t\t\t\t\t\tnodeIndex = cache[0] === dirruns && cache[1];\n\t\t\t\t\t\t\tdiff = cache[0] === dirruns && cache[2];\n\t\t\t\t\t\t\tnode = nodeIndex && parent.childNodes[ nodeIndex ];\n\n\t\t\t\t\t\t\twhile ( (node = ++nodeIndex && node && node[ dir ] ||\n\n\t\t\t\t\t\t\t\t// Fallback to seeking `elem` from the start\n\t\t\t\t\t\t\t\t(diff = nodeIndex = 0) || start.pop()) ) {\n\n\t\t\t\t\t\t\t\t// When found, cache indexes on `parent` and break\n\t\t\t\t\t\t\t\tif ( node.nodeType === 1 && ++diff && node === elem ) {\n\t\t\t\t\t\t\t\t\touterCache[ type ] = [ dirruns, nodeIndex, diff ];\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Use previously-cached element index if available\n\t\t\t\t\t\t} else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) {\n\t\t\t\t\t\t\tdiff = cache[1];\n\n\t\t\t\t\t\t// xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...)\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// Use the same loop as above to seek `elem` from the start\n\t\t\t\t\t\t\twhile ( (node = ++nodeIndex && node && node[ dir ] ||\n\t\t\t\t\t\t\t\t(diff = nodeIndex = 0) || start.pop()) ) {\n\n\t\t\t\t\t\t\t\tif ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) {\n\t\t\t\t\t\t\t\t\t// Cache the index of each encountered element\n\t\t\t\t\t\t\t\t\tif ( useCache ) {\n\t\t\t\t\t\t\t\t\t\t(node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ];\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\tif ( node === elem ) {\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Incorporate the offset, then check against cycle size\n\t\t\t\t\t\tdiff -= last;\n\t\t\t\t\t\treturn diff === first || ( diff % first === 0 && diff / first >= 0 );\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t},\n\n\t\t\"PSEUDO\": function( pseudo, argument ) {\n\t\t\t// pseudo-class names are case-insensitive\n\t\t\t// http://www.w3.org/TR/selectors/#pseudo-classes\n\t\t\t// Prioritize by case sensitivity in case custom pseudos are added with uppercase letters\n\t\t\t// Remember that setFilters inherits from pseudos\n\t\t\tvar args,\n\t\t\t\tfn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||\n\t\t\t\t\tSizzle.error( \"unsupported pseudo: \" + pseudo );\n\n\t\t\t// The user may use createPseudo to indicate that\n\t\t\t// arguments are needed to create the filter function\n\t\t\t// just as Sizzle does\n\t\t\tif ( fn[ expando ] ) {\n\t\t\t\treturn fn( argument );\n\t\t\t}\n\n\t\t\t// But maintain support for old signatures\n\t\t\tif ( fn.length > 1 ) {\n\t\t\t\targs = [ pseudo, pseudo, \"\", argument ];\n\t\t\t\treturn Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?\n\t\t\t\t\tmarkFunction(function( seed, matches ) {\n\t\t\t\t\t\tvar idx,\n\t\t\t\t\t\t\tmatched = fn( seed, argument ),\n\t\t\t\t\t\t\ti = matched.length;\n\t\t\t\t\t\twhile ( i-- ) {\n\t\t\t\t\t\t\tidx = indexOf.call( seed, matched[i] );\n\t\t\t\t\t\t\tseed[ idx ] = !( matches[ idx ] = matched[i] );\n\t\t\t\t\t\t}\n\t\t\t\t\t}) :\n\t\t\t\t\tfunction( elem ) {\n\t\t\t\t\t\treturn fn( elem, 0, args );\n\t\t\t\t\t};\n\t\t\t}\n\n\t\t\treturn fn;\n\t\t}\n\t},\n\n\tpseudos: {\n\t\t// Potentially complex pseudos\n\t\t\"not\": markFunction(function( selector ) {\n\t\t\t// Trim the selector passed to compile\n\t\t\t// to avoid treating leading and trailing\n\t\t\t// spaces as combinators\n\t\t\tvar input = [],\n\t\t\t\tresults = [],\n\t\t\t\tmatcher = compile( selector.replace( rtrim, \"$1\" ) );\n\n\t\t\treturn matcher[ expando ] ?\n\t\t\t\tmarkFunction(function( seed, matches, context, xml ) {\n\t\t\t\t\tvar elem,\n\t\t\t\t\t\tunmatched = matcher( seed, null, xml, [] ),\n\t\t\t\t\t\ti = seed.length;\n\n\t\t\t\t\t// Match elements unmatched by `matcher`\n\t\t\t\t\twhile ( i-- ) {\n\t\t\t\t\t\tif ( (elem = unmatched[i]) ) {\n\t\t\t\t\t\t\tseed[i] = !(matches[i] = elem);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}) :\n\t\t\t\tfunction( elem, context, xml ) {\n\t\t\t\t\tinput[0] = elem;\n\t\t\t\t\tmatcher( input, null, xml, results );\n\t\t\t\t\treturn !results.pop();\n\t\t\t\t};\n\t\t}),\n\n\t\t\"has\": markFunction(function( selector ) {\n\t\t\treturn function( elem ) {\n\t\t\t\treturn Sizzle( selector, elem ).length > 0;\n\t\t\t};\n\t\t}),\n\n\t\t\"contains\": markFunction(function( text ) {\n\t\t\treturn function( elem ) {\n\t\t\t\treturn ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1;\n\t\t\t};\n\t\t}),\n\n\t\t// \"Whether an element is represented by a :lang() selector\n\t\t// is based solely on the element's language value\n\t\t// being equal to the identifier C,\n\t\t// or beginning with the identifier C immediately followed by \"-\".\n\t\t// The matching of C against the element's language value is performed case-insensitively.\n\t\t// The identifier C does not have to be a valid language name.\"\n\t\t// http://www.w3.org/TR/selectors/#lang-pseudo\n\t\t\"lang\": markFunction( function( lang ) {\n\t\t\t// lang value must be a valid identifier\n\t\t\tif ( !ridentifier.test(lang || \"\") ) {\n\t\t\t\tSizzle.error( \"unsupported lang: \" + lang );\n\t\t\t}\n\t\t\tlang = lang.replace( runescape, funescape ).toLowerCase();\n\t\t\treturn function( elem ) {\n\t\t\t\tvar elemLang;\n\t\t\t\tdo {\n\t\t\t\t\tif ( (elemLang = documentIsHTML ?\n\t\t\t\t\t\telem.lang :\n\t\t\t\t\t\telem.getAttribute(\"xml:lang\") || elem.getAttribute(\"lang\")) ) {\n\n\t\t\t\t\t\telemLang = elemLang.toLowerCase();\n\t\t\t\t\t\treturn elemLang === lang || elemLang.indexOf( lang + \"-\" ) === 0;\n\t\t\t\t\t}\n\t\t\t\t} while ( (elem = elem.parentNode) && elem.nodeType === 1 );\n\t\t\t\treturn false;\n\t\t\t};\n\t\t}),\n\n\t\t// Miscellaneous\n\t\t\"target\": function( elem ) {\n\t\t\tvar hash = window.location && window.location.hash;\n\t\t\treturn hash && hash.slice( 1 ) === elem.id;\n\t\t},\n\n\t\t\"root\": function( elem ) {\n\t\t\treturn elem === docElem;\n\t\t},\n\n\t\t\"focus\": function( elem ) {\n\t\t\treturn elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex);\n\t\t},\n\n\t\t// Boolean properties\n\t\t\"enabled\": function( elem ) {\n\t\t\treturn elem.disabled === false;\n\t\t},\n\n\t\t\"disabled\": function( elem ) {\n\t\t\treturn elem.disabled === true;\n\t\t},\n\n\t\t\"checked\": function( elem ) {\n\t\t\t// In CSS3, :checked should return both checked and selected elements\n\t\t\t// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked\n\t\t\tvar nodeName = elem.nodeName.toLowerCase();\n\t\t\treturn (nodeName === \"input\" && !!elem.checked) || (nodeName === \"option\" && !!elem.selected);\n\t\t},\n\n\t\t\"selected\": function( elem ) {\n\t\t\t// Accessing this property makes selected-by-default\n\t\t\t// options in Safari work properly\n\t\t\tif ( elem.parentNode ) {\n\t\t\t\telem.parentNode.selectedIndex;\n\t\t\t}\n\n\t\t\treturn elem.selected === true;\n\t\t},\n\n\t\t// Contents\n\t\t\"empty\": function( elem ) {\n\t\t\t// http://www.w3.org/TR/selectors/#empty-pseudo\n\t\t\t// :empty is only affected by element nodes and content nodes(including text(3), cdata(4)),\n\t\t\t//   not comment, processing instructions, or others\n\t\t\t// Thanks to Diego Perini for the nodeName shortcut\n\t\t\t//   Greater than \"@\" means alpha characters (specifically not starting with \"#\" or \"?\")\n\t\t\tfor ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {\n\t\t\t\tif ( elem.nodeName > \"@\" || elem.nodeType === 3 || elem.nodeType === 4 ) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true;\n\t\t},\n\n\t\t\"parent\": function( elem ) {\n\t\t\treturn !Expr.pseudos[\"empty\"]( elem );\n\t\t},\n\n\t\t// Element/input types\n\t\t\"header\": function( elem ) {\n\t\t\treturn rheader.test( elem.nodeName );\n\t\t},\n\n\t\t\"input\": function( elem ) {\n\t\t\treturn rinputs.test( elem.nodeName );\n\t\t},\n\n\t\t\"button\": function( elem ) {\n\t\t\tvar name = elem.nodeName.toLowerCase();\n\t\t\treturn name === \"input\" && elem.type === \"button\" || name === \"button\";\n\t\t},\n\n\t\t\"text\": function( elem ) {\n\t\t\tvar attr;\n\t\t\t// IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc)\n\t\t\t// use getAttribute instead to test this case\n\t\t\treturn elem.nodeName.toLowerCase() === \"input\" &&\n\t\t\t\telem.type === \"text\" &&\n\t\t\t\t( (attr = elem.getAttribute(\"type\")) == null || attr.toLowerCase() === elem.type );\n\t\t},\n\n\t\t// Position-in-collection\n\t\t\"first\": createPositionalPseudo(function() {\n\t\t\treturn [ 0 ];\n\t\t}),\n\n\t\t\"last\": createPositionalPseudo(function( matchIndexes, length ) {\n\t\t\treturn [ length - 1 ];\n\t\t}),\n\n\t\t\"eq\": createPositionalPseudo(function( matchIndexes, length, argument ) {\n\t\t\treturn [ argument < 0 ? argument + length : argument ];\n\t\t}),\n\n\t\t\"even\": createPositionalPseudo(function( matchIndexes, length ) {\n\t\t\tvar i = 0;\n\t\t\tfor ( ; i < length; i += 2 ) {\n\t\t\t\tmatchIndexes.push( i );\n\t\t\t}\n\t\t\treturn matchIndexes;\n\t\t}),\n\n\t\t\"odd\": createPositionalPseudo(function( matchIndexes, length ) {\n\t\t\tvar i = 1;\n\t\t\tfor ( ; i < length; i += 2 ) {\n\t\t\t\tmatchIndexes.push( i );\n\t\t\t}\n\t\t\treturn matchIndexes;\n\t\t}),\n\n\t\t\"lt\": createPositionalPseudo(function( matchIndexes, length, argument ) {\n\t\t\tvar i = argument < 0 ? argument + length : argument;\n\t\t\tfor ( ; --i >= 0; ) {\n\t\t\t\tmatchIndexes.push( i );\n\t\t\t}\n\t\t\treturn matchIndexes;\n\t\t}),\n\n\t\t\"gt\": createPositionalPseudo(function( matchIndexes, length, argument ) {\n\t\t\tvar i = argument < 0 ? argument + length : argument;\n\t\t\tfor ( ; ++i < length; ) {\n\t\t\t\tmatchIndexes.push( i );\n\t\t\t}\n\t\t\treturn matchIndexes;\n\t\t})\n\t}\n};\n\n// Add button/input type pseudos\nfor ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) {\n\tExpr.pseudos[ i ] = createInputPseudo( i );\n}\nfor ( i in { submit: true, reset: true } ) {\n\tExpr.pseudos[ i ] = createButtonPseudo( i );\n}\n\nfunction tokenize( selector, parseOnly ) {\n\tvar matched, match, tokens, type,\n\t\tsoFar, groups, preFilters,\n\t\tcached = tokenCache[ selector + \" \" ];\n\n\tif ( cached ) {\n\t\treturn parseOnly ? 0 : cached.slice( 0 );\n\t}\n\n\tsoFar = selector;\n\tgroups = [];\n\tpreFilters = Expr.preFilter;\n\n\twhile ( soFar ) {\n\n\t\t// Comma and first run\n\t\tif ( !matched || (match = rcomma.exec( soFar )) ) {\n\t\t\tif ( match ) {\n\t\t\t\t// Don't consume trailing commas as valid\n\t\t\t\tsoFar = soFar.slice( match[0].length ) || soFar;\n\t\t\t}\n\t\t\tgroups.push( tokens = [] );\n\t\t}\n\n\t\tmatched = false;\n\n\t\t// Combinators\n\t\tif ( (match = rcombinators.exec( soFar )) ) {\n\t\t\tmatched = match.shift();\n\t\t\ttokens.push({\n\t\t\t\tvalue: matched,\n\t\t\t\t// Cast descendant combinators to space\n\t\t\t\ttype: match[0].replace( rtrim, \" \" )\n\t\t\t});\n\t\t\tsoFar = soFar.slice( matched.length );\n\t\t}\n\n\t\t// Filters\n\t\tfor ( type in Expr.filter ) {\n\t\t\tif ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] ||\n\t\t\t\t(match = preFilters[ type ]( match ))) ) {\n\t\t\t\tmatched = match.shift();\n\t\t\t\ttokens.push({\n\t\t\t\t\tvalue: matched,\n\t\t\t\t\ttype: type,\n\t\t\t\t\tmatches: match\n\t\t\t\t});\n\t\t\t\tsoFar = soFar.slice( matched.length );\n\t\t\t}\n\t\t}\n\n\t\tif ( !matched ) {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t// Return the length of the invalid excess\n\t// if we're just parsing\n\t// Otherwise, throw an error or return tokens\n\treturn parseOnly ?\n\t\tsoFar.length :\n\t\tsoFar ?\n\t\t\tSizzle.error( selector ) :\n\t\t\t// Cache the tokens\n\t\t\ttokenCache( selector, groups ).slice( 0 );\n}\n\nfunction toSelector( tokens ) {\n\tvar i = 0,\n\t\tlen = tokens.length,\n\t\tselector = \"\";\n\tfor ( ; i < len; i++ ) {\n\t\tselector += tokens[i].value;\n\t}\n\treturn selector;\n}\n\nfunction addCombinator( matcher, combinator, base ) {\n\tvar dir = combinator.dir,\n\t\tcheckNonElements = base && dir === \"parentNode\",\n\t\tdoneName = done++;\n\n\treturn combinator.first ?\n\t\t// Check against closest ancestor/preceding element\n\t\tfunction( elem, context, xml ) {\n\t\t\twhile ( (elem = elem[ dir ]) ) {\n\t\t\t\tif ( elem.nodeType === 1 || checkNonElements ) {\n\t\t\t\t\treturn matcher( elem, context, xml );\n\t\t\t\t}\n\t\t\t}\n\t\t} :\n\n\t\t// Check against all ancestor/preceding elements\n\t\tfunction( elem, context, xml ) {\n\t\t\tvar data, cache, outerCache,\n\t\t\t\tdirkey = dirruns + \" \" + doneName;\n\n\t\t\t// We can't set arbitrary data on XML nodes, so they don't benefit from dir caching\n\t\t\tif ( xml ) {\n\t\t\t\twhile ( (elem = elem[ dir ]) ) {\n\t\t\t\t\tif ( elem.nodeType === 1 || checkNonElements ) {\n\t\t\t\t\t\tif ( matcher( elem, context, xml ) ) {\n\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\twhile ( (elem = elem[ dir ]) ) {\n\t\t\t\t\tif ( elem.nodeType === 1 || checkNonElements ) {\n\t\t\t\t\t\touterCache = elem[ expando ] || (elem[ expando ] = {});\n\t\t\t\t\t\tif ( (cache = outerCache[ dir ]) && cache[0] === dirkey ) {\n\t\t\t\t\t\t\tif ( (data = cache[1]) === true || data === cachedruns ) {\n\t\t\t\t\t\t\t\treturn data === true;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tcache = outerCache[ dir ] = [ dirkey ];\n\t\t\t\t\t\t\tcache[1] = matcher( elem, context, xml ) || cachedruns;\n\t\t\t\t\t\t\tif ( cache[1] === true ) {\n\t\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t};\n}\n\nfunction elementMatcher( matchers ) {\n\treturn matchers.length > 1 ?\n\t\tfunction( elem, context, xml ) {\n\t\t\tvar i = matchers.length;\n\t\t\twhile ( i-- ) {\n\t\t\t\tif ( !matchers[i]( elem, context, xml ) ) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true;\n\t\t} :\n\t\tmatchers[0];\n}\n\nfunction condense( unmatched, map, filter, context, xml ) {\n\tvar elem,\n\t\tnewUnmatched = [],\n\t\ti = 0,\n\t\tlen = unmatched.length,\n\t\tmapped = map != null;\n\n\tfor ( ; i < len; i++ ) {\n\t\tif ( (elem = unmatched[i]) ) {\n\t\t\tif ( !filter || filter( elem, context, xml ) ) {\n\t\t\t\tnewUnmatched.push( elem );\n\t\t\t\tif ( mapped ) {\n\t\t\t\t\tmap.push( i );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn newUnmatched;\n}\n\nfunction setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {\n\tif ( postFilter && !postFilter[ expando ] ) {\n\t\tpostFilter = setMatcher( postFilter );\n\t}\n\tif ( postFinder && !postFinder[ expando ] ) {\n\t\tpostFinder = setMatcher( postFinder, postSelector );\n\t}\n\treturn markFunction(function( seed, results, context, xml ) {\n\t\tvar temp, i, elem,\n\t\t\tpreMap = [],\n\t\t\tpostMap = [],\n\t\t\tpreexisting = results.length,\n\n\t\t\t// Get initial elements from seed or context\n\t\t\telems = seed || multipleContexts( selector || \"*\", context.nodeType ? [ context ] : context, [] ),\n\n\t\t\t// Prefilter to get matcher input, preserving a map for seed-results synchronization\n\t\t\tmatcherIn = preFilter && ( seed || !selector ) ?\n\t\t\t\tcondense( elems, preMap, preFilter, context, xml ) :\n\t\t\t\telems,\n\n\t\t\tmatcherOut = matcher ?\n\t\t\t\t// If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,\n\t\t\t\tpostFinder || ( seed ? preFilter : preexisting || postFilter ) ?\n\n\t\t\t\t\t// ...intermediate processing is necessary\n\t\t\t\t\t[] :\n\n\t\t\t\t\t// ...otherwise use results directly\n\t\t\t\t\tresults :\n\t\t\t\tmatcherIn;\n\n\t\t// Find primary matches\n\t\tif ( matcher ) {\n\t\t\tmatcher( matcherIn, matcherOut, context, xml );\n\t\t}\n\n\t\t// Apply postFilter\n\t\tif ( postFilter ) {\n\t\t\ttemp = condense( matcherOut, postMap );\n\t\t\tpostFilter( temp, [], context, xml );\n\n\t\t\t// Un-match failing elements by moving them back to matcherIn\n\t\t\ti = temp.length;\n\t\t\twhile ( i-- ) {\n\t\t\t\tif ( (elem = temp[i]) ) {\n\t\t\t\t\tmatcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif ( seed ) {\n\t\t\tif ( postFinder || preFilter ) {\n\t\t\t\tif ( postFinder ) {\n\t\t\t\t\t// Get the final matcherOut by condensing this intermediate into postFinder contexts\n\t\t\t\t\ttemp = [];\n\t\t\t\t\ti = matcherOut.length;\n\t\t\t\t\twhile ( i-- ) {\n\t\t\t\t\t\tif ( (elem = matcherOut[i]) ) {\n\t\t\t\t\t\t\t// Restore matcherIn since elem is not yet a final match\n\t\t\t\t\t\t\ttemp.push( (matcherIn[i] = elem) );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tpostFinder( null, (matcherOut = []), temp, xml );\n\t\t\t\t}\n\n\t\t\t\t// Move matched elements from seed to results to keep them synchronized\n\t\t\t\ti = matcherOut.length;\n\t\t\t\twhile ( i-- ) {\n\t\t\t\t\tif ( (elem = matcherOut[i]) &&\n\t\t\t\t\t\t(temp = postFinder ? indexOf.call( seed, elem ) : preMap[i]) > -1 ) {\n\n\t\t\t\t\t\tseed[temp] = !(results[temp] = elem);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t// Add elements to results, through postFinder if defined\n\t\t} else {\n\t\t\tmatcherOut = condense(\n\t\t\t\tmatcherOut === results ?\n\t\t\t\t\tmatcherOut.splice( preexisting, matcherOut.length ) :\n\t\t\t\t\tmatcherOut\n\t\t\t);\n\t\t\tif ( postFinder ) {\n\t\t\t\tpostFinder( null, results, matcherOut, xml );\n\t\t\t} else {\n\t\t\t\tpush.apply( results, matcherOut );\n\t\t\t}\n\t\t}\n\t});\n}\n\nfunction matcherFromTokens( tokens ) {\n\tvar checkContext, matcher, j,\n\t\tlen = tokens.length,\n\t\tleadingRelative = Expr.relative[ tokens[0].type ],\n\t\timplicitRelative = leadingRelative || Expr.relative[\" \"],\n\t\ti = leadingRelative ? 1 : 0,\n\n\t\t// The foundational matcher ensures that elements are reachable from top-level context(s)\n\t\tmatchContext = addCombinator( function( elem ) {\n\t\t\treturn elem === checkContext;\n\t\t}, implicitRelative, true ),\n\t\tmatchAnyContext = addCombinator( function( elem ) {\n\t\t\treturn indexOf.call( checkContext, elem ) > -1;\n\t\t}, implicitRelative, true ),\n\t\tmatchers = [ function( elem, context, xml ) {\n\t\t\treturn ( !leadingRelative && ( xml || context !== outermostContext ) ) || (\n\t\t\t\t(checkContext = context).nodeType ?\n\t\t\t\t\tmatchContext( elem, context, xml ) :\n\t\t\t\t\tmatchAnyContext( elem, context, xml ) );\n\t\t} ];\n\n\tfor ( ; i < len; i++ ) {\n\t\tif ( (matcher = Expr.relative[ tokens[i].type ]) ) {\n\t\t\tmatchers = [ addCombinator(elementMatcher( matchers ), matcher) ];\n\t\t} else {\n\t\t\tmatcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches );\n\n\t\t\t// Return special upon seeing a positional matcher\n\t\t\tif ( matcher[ expando ] ) {\n\t\t\t\t// Find the next relative operator (if any) for proper handling\n\t\t\t\tj = ++i;\n\t\t\t\tfor ( ; j < len; j++ ) {\n\t\t\t\t\tif ( Expr.relative[ tokens[j].type ] ) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn setMatcher(\n\t\t\t\t\ti > 1 && elementMatcher( matchers ),\n\t\t\t\t\ti > 1 && toSelector(\n\t\t\t\t\t\t// If the preceding token was a descendant combinator, insert an implicit any-element `*`\n\t\t\t\t\t\ttokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === \" \" ? \"*\" : \"\" })\n\t\t\t\t\t).replace( rtrim, \"$1\" ),\n\t\t\t\t\tmatcher,\n\t\t\t\t\ti < j && matcherFromTokens( tokens.slice( i, j ) ),\n\t\t\t\t\tj < len && matcherFromTokens( (tokens = tokens.slice( j )) ),\n\t\t\t\t\tj < len && toSelector( tokens )\n\t\t\t\t);\n\t\t\t}\n\t\t\tmatchers.push( matcher );\n\t\t}\n\t}\n\n\treturn elementMatcher( matchers );\n}\n\nfunction matcherFromGroupMatchers( elementMatchers, setMatchers ) {\n\t// A counter to specify which element is currently being matched\n\tvar matcherCachedRuns = 0,\n\t\tbySet = setMatchers.length > 0,\n\t\tbyElement = elementMatchers.length > 0,\n\t\tsuperMatcher = function( seed, context, xml, results, expandContext ) {\n\t\t\tvar elem, j, matcher,\n\t\t\t\tsetMatched = [],\n\t\t\t\tmatchedCount = 0,\n\t\t\t\ti = \"0\",\n\t\t\t\tunmatched = seed && [],\n\t\t\t\toutermost = expandContext != null,\n\t\t\t\tcontextBackup = outermostContext,\n\t\t\t\t// We must always have either seed elements or context\n\t\t\t\telems = seed || byElement && Expr.find[\"TAG\"]( \"*\", expandContext && context.parentNode || context ),\n\t\t\t\t// Use integer dirruns iff this is the outermost matcher\n\t\t\t\tdirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1);\n\n\t\t\tif ( outermost ) {\n\t\t\t\toutermostContext = context !== document && context;\n\t\t\t\tcachedruns = matcherCachedRuns;\n\t\t\t}\n\n\t\t\t// Add elements passing elementMatchers directly to results\n\t\t\t// Keep `i` a string if there are no elements so `matchedCount` will be \"00\" below\n\t\t\tfor ( ; (elem = elems[i]) != null; i++ ) {\n\t\t\t\tif ( byElement && elem ) {\n\t\t\t\t\tj = 0;\n\t\t\t\t\twhile ( (matcher = elementMatchers[j++]) ) {\n\t\t\t\t\t\tif ( matcher( elem, context, xml ) ) {\n\t\t\t\t\t\t\tresults.push( elem );\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif ( outermost ) {\n\t\t\t\t\t\tdirruns = dirrunsUnique;\n\t\t\t\t\t\tcachedruns = ++matcherCachedRuns;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Track unmatched elements for set filters\n\t\t\t\tif ( bySet ) {\n\t\t\t\t\t// They will have gone through all possible matchers\n\t\t\t\t\tif ( (elem = !matcher && elem) ) {\n\t\t\t\t\t\tmatchedCount--;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Lengthen the array for every element, matched or not\n\t\t\t\t\tif ( seed ) {\n\t\t\t\t\t\tunmatched.push( elem );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Apply set filters to unmatched elements\n\t\t\tmatchedCount += i;\n\t\t\tif ( bySet && i !== matchedCount ) {\n\t\t\t\tj = 0;\n\t\t\t\twhile ( (matcher = setMatchers[j++]) ) {\n\t\t\t\t\tmatcher( unmatched, setMatched, context, xml );\n\t\t\t\t}\n\n\t\t\t\tif ( seed ) {\n\t\t\t\t\t// Reintegrate element matches to eliminate the need for sorting\n\t\t\t\t\tif ( matchedCount > 0 ) {\n\t\t\t\t\t\twhile ( i-- ) {\n\t\t\t\t\t\t\tif ( !(unmatched[i] || setMatched[i]) ) {\n\t\t\t\t\t\t\t\tsetMatched[i] = pop.call( results );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Discard index placeholder values to get only actual matches\n\t\t\t\t\tsetMatched = condense( setMatched );\n\t\t\t\t}\n\n\t\t\t\t// Add matches to results\n\t\t\t\tpush.apply( results, setMatched );\n\n\t\t\t\t// Seedless set matches succeeding multiple successful matchers stipulate sorting\n\t\t\t\tif ( outermost && !seed && setMatched.length > 0 &&\n\t\t\t\t\t( matchedCount + setMatchers.length ) > 1 ) {\n\n\t\t\t\t\tSizzle.uniqueSort( results );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Override manipulation of globals by nested matchers\n\t\t\tif ( outermost ) {\n\t\t\t\tdirruns = dirrunsUnique;\n\t\t\t\toutermostContext = contextBackup;\n\t\t\t}\n\n\t\t\treturn unmatched;\n\t\t};\n\n\treturn bySet ?\n\t\tmarkFunction( superMatcher ) :\n\t\tsuperMatcher;\n}\n\ncompile = Sizzle.compile = function( selector, group /* Internal Use Only */ ) {\n\tvar i,\n\t\tsetMatchers = [],\n\t\telementMatchers = [],\n\t\tcached = compilerCache[ selector + \" \" ];\n\n\tif ( !cached ) {\n\t\t// Generate a function of recursive functions that can be used to check each element\n\t\tif ( !group ) {\n\t\t\tgroup = tokenize( selector );\n\t\t}\n\t\ti = group.length;\n\t\twhile ( i-- ) {\n\t\t\tcached = matcherFromTokens( group[i] );\n\t\t\tif ( cached[ expando ] ) {\n\t\t\t\tsetMatchers.push( cached );\n\t\t\t} else {\n\t\t\t\telementMatchers.push( cached );\n\t\t\t}\n\t\t}\n\n\t\t// Cache the compiled function\n\t\tcached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) );\n\t}\n\treturn cached;\n};\n\nfunction multipleContexts( selector, contexts, results ) {\n\tvar i = 0,\n\t\tlen = contexts.length;\n\tfor ( ; i < len; i++ ) {\n\t\tSizzle( selector, contexts[i], results );\n\t}\n\treturn results;\n}\n\nfunction select( selector, context, results, seed ) {\n\tvar i, tokens, token, type, find,\n\t\tmatch = tokenize( selector );\n\n\tif ( !seed ) {\n\t\t// Try to minimize operations if there is only one group\n\t\tif ( match.length === 1 ) {\n\n\t\t\t// Take a shortcut and set the context if the root selector is an ID\n\t\t\ttokens = match[0] = match[0].slice( 0 );\n\t\t\tif ( tokens.length > 2 && (token = tokens[0]).type === \"ID\" &&\n\t\t\t\t\tsupport.getById && context.nodeType === 9 && documentIsHTML &&\n\t\t\t\t\tExpr.relative[ tokens[1].type ] ) {\n\n\t\t\t\tcontext = ( Expr.find[\"ID\"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0];\n\t\t\t\tif ( !context ) {\n\t\t\t\t\treturn results;\n\t\t\t\t}\n\t\t\t\tselector = selector.slice( tokens.shift().value.length );\n\t\t\t}\n\n\t\t\t// Fetch a seed set for right-to-left matching\n\t\t\ti = matchExpr[\"needsContext\"].test( selector ) ? 0 : tokens.length;\n\t\t\twhile ( i-- ) {\n\t\t\t\ttoken = tokens[i];\n\n\t\t\t\t// Abort if we hit a combinator\n\t\t\t\tif ( Expr.relative[ (type = token.type) ] ) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tif ( (find = Expr.find[ type ]) ) {\n\t\t\t\t\t// Search, expanding context for leading sibling combinators\n\t\t\t\t\tif ( (seed = find(\n\t\t\t\t\t\ttoken.matches[0].replace( runescape, funescape ),\n\t\t\t\t\t\trsibling.test( tokens[0].type ) && context.parentNode || context\n\t\t\t\t\t)) ) {\n\n\t\t\t\t\t\t// If seed is empty or no tokens remain, we can return early\n\t\t\t\t\t\ttokens.splice( i, 1 );\n\t\t\t\t\t\tselector = seed.length && toSelector( tokens );\n\t\t\t\t\t\tif ( !selector ) {\n\t\t\t\t\t\t\tpush.apply( results, seed );\n\t\t\t\t\t\t\treturn results;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Compile and execute a filtering function\n\t// Provide `match` to avoid retokenization if we modified the selector above\n\tcompile( selector, match )(\n\t\tseed,\n\t\tcontext,\n\t\t!documentIsHTML,\n\t\tresults,\n\t\trsibling.test( selector )\n\t);\n\treturn results;\n}\n\n// Deprecated\nExpr.pseudos[\"nth\"] = Expr.pseudos[\"eq\"];\n\n// Easy API for creating new setFilters\nfunction setFilters() {}\nsetFilters.prototype = Expr.filters = Expr.pseudos;\nExpr.setFilters = new setFilters();\n\n// One-time assignments\n\n// Sort stability\nsupport.sortStable = expando.split(\"\").sort( sortOrder ).join(\"\") === expando;\n\n// Initialize against the default document\nsetDocument();\n\n// Support: Chrome<<14\n// Always assume duplicates if they aren't passed to the comparison function\n[0, 0].sort( sortOrder );\nsupport.detectDuplicates = hasDuplicate;\n\njQuery.find = Sizzle;\njQuery.expr = Sizzle.selectors;\njQuery.expr[\":\"] = jQuery.expr.pseudos;\njQuery.unique = Sizzle.uniqueSort;\njQuery.text = Sizzle.getText;\njQuery.isXMLDoc = Sizzle.isXML;\njQuery.contains = Sizzle.contains;\n\n\n})( window );\n// String to Object options format cache\nvar optionsCache = {};\n\n// Convert String-formatted options into Object-formatted ones and store in cache\nfunction createOptions( options ) {\n\tvar object = optionsCache[ options ] = {};\n\tjQuery.each( options.match( core_rnotwhite ) || [], function( _, flag ) {\n\t\tobject[ flag ] = true;\n\t});\n\treturn object;\n}\n\n/*\n * Create a callback list using the following parameters:\n *\n *\toptions: an optional list of space-separated options that will change how\n *\t\t\tthe callback list behaves or a more traditional option object\n *\n * By default a callback list will act like an event callback list and can be\n * \"fired\" multiple times.\n *\n * Possible options:\n *\n *\tonce:\t\t\twill ensure the callback list can only be fired once (like a Deferred)\n *\n *\tmemory:\t\t\twill keep track of previous values and will call any callback added\n *\t\t\t\t\tafter the list has been fired right away with the latest \"memorized\"\n *\t\t\t\t\tvalues (like a Deferred)\n *\n *\tunique:\t\t\twill ensure a callback can only be added once (no duplicate in the list)\n *\n *\tstopOnFalse:\tinterrupt callings when a callback returns false\n *\n */\njQuery.Callbacks = function( options ) {\n\n\t// Convert options from String-formatted to Object-formatted if needed\n\t// (we check in cache first)\n\toptions = typeof options === \"string\" ?\n\t\t( optionsCache[ options ] || createOptions( options ) ) :\n\t\tjQuery.extend( {}, options );\n\n\tvar // Flag to know if list is currently firing\n\t\tfiring,\n\t\t// Last fire value (for non-forgettable lists)\n\t\tmemory,\n\t\t// Flag to know if list was already fired\n\t\tfired,\n\t\t// End of the loop when firing\n\t\tfiringLength,\n\t\t// Index of currently firing callback (modified by remove if needed)\n\t\tfiringIndex,\n\t\t// First callback to fire (used internally by add and fireWith)\n\t\tfiringStart,\n\t\t// Actual callback list\n\t\tlist = [],\n\t\t// Stack of fire calls for repeatable lists\n\t\tstack = !options.once && [],\n\t\t// Fire callbacks\n\t\tfire = function( data ) {\n\t\t\tmemory = options.memory && data;\n\t\t\tfired = true;\n\t\t\tfiringIndex = firingStart || 0;\n\t\t\tfiringStart = 0;\n\t\t\tfiringLength = list.length;\n\t\t\tfiring = true;\n\t\t\tfor ( ; list && firingIndex < firingLength; firingIndex++ ) {\n\t\t\t\tif ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {\n\t\t\t\t\tmemory = false; // To prevent further calls using add\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tfiring = false;\n\t\t\tif ( list ) {\n\t\t\t\tif ( stack ) {\n\t\t\t\t\tif ( stack.length ) {\n\t\t\t\t\t\tfire( stack.shift() );\n\t\t\t\t\t}\n\t\t\t\t} else if ( memory ) {\n\t\t\t\t\tlist = [];\n\t\t\t\t} else {\n\t\t\t\t\tself.disable();\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t// Actual Callbacks object\n\t\tself = {\n\t\t\t// Add a callback or a collection of callbacks to the list\n\t\t\tadd: function() {\n\t\t\t\tif ( list ) {\n\t\t\t\t\t// First, we save the current length\n\t\t\t\t\tvar start = list.length;\n\t\t\t\t\t(function add( args ) {\n\t\t\t\t\t\tjQuery.each( args, function( _, arg ) {\n\t\t\t\t\t\t\tvar type = jQuery.type( arg );\n\t\t\t\t\t\t\tif ( type === \"function\" ) {\n\t\t\t\t\t\t\t\tif ( !options.unique || !self.has( arg ) ) {\n\t\t\t\t\t\t\t\t\tlist.push( arg );\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else if ( arg && arg.length && type !== \"string\" ) {\n\t\t\t\t\t\t\t\t// Inspect recursively\n\t\t\t\t\t\t\t\tadd( arg );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t\t})( arguments );\n\t\t\t\t\t// Do we need to add the callbacks to the\n\t\t\t\t\t// current firing batch?\n\t\t\t\t\tif ( firing ) {\n\t\t\t\t\t\tfiringLength = list.length;\n\t\t\t\t\t// With memory, if we're not firing then\n\t\t\t\t\t// we should call right away\n\t\t\t\t\t} else if ( memory ) {\n\t\t\t\t\t\tfiringStart = start;\n\t\t\t\t\t\tfire( memory );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t\t},\n\t\t\t// Remove a callback from the list\n\t\t\tremove: function() {\n\t\t\t\tif ( list ) {\n\t\t\t\t\tjQuery.each( arguments, function( _, arg ) {\n\t\t\t\t\t\tvar index;\n\t\t\t\t\t\twhile( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {\n\t\t\t\t\t\t\tlist.splice( index, 1 );\n\t\t\t\t\t\t\t// Handle firing indexes\n\t\t\t\t\t\t\tif ( firing ) {\n\t\t\t\t\t\t\t\tif ( index <= firingLength ) {\n\t\t\t\t\t\t\t\t\tfiringLength--;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif ( index <= firingIndex ) {\n\t\t\t\t\t\t\t\t\tfiringIndex--;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t\t},\n\t\t\t// Check if a given callback is in the list.\n\t\t\t// If no argument is given, return whether or not list has callbacks attached.\n\t\t\thas: function( fn ) {\n\t\t\t\treturn fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length );\n\t\t\t},\n\t\t\t// Remove all callbacks from the list\n\t\t\tempty: function() {\n\t\t\t\tlist = [];\n\t\t\t\tfiringLength = 0;\n\t\t\t\treturn this;\n\t\t\t},\n\t\t\t// Have the list do nothing anymore\n\t\t\tdisable: function() {\n\t\t\t\tlist = stack = memory = undefined;\n\t\t\t\treturn this;\n\t\t\t},\n\t\t\t// Is it disabled?\n\t\t\tdisabled: function() {\n\t\t\t\treturn !list;\n\t\t\t},\n\t\t\t// Lock the list in its current state\n\t\t\tlock: function() {\n\t\t\t\tstack = undefined;\n\t\t\t\tif ( !memory ) {\n\t\t\t\t\tself.disable();\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t\t},\n\t\t\t// Is it locked?\n\t\t\tlocked: function() {\n\t\t\t\treturn !stack;\n\t\t\t},\n\t\t\t// Call all callbacks with the given context and arguments\n\t\t\tfireWith: function( context, args ) {\n\t\t\t\targs = args || [];\n\t\t\t\targs = [ context, args.slice ? args.slice() : args ];\n\t\t\t\tif ( list && ( !fired || stack ) ) {\n\t\t\t\t\tif ( firing ) {\n\t\t\t\t\t\tstack.push( args );\n\t\t\t\t\t} else {\n\t\t\t\t\t\tfire( args );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t\t},\n\t\t\t// Call all the callbacks with the given arguments\n\t\t\tfire: function() {\n\t\t\t\tself.fireWith( this, arguments );\n\t\t\t\treturn this;\n\t\t\t},\n\t\t\t// To know if the callbacks have already been called at least once\n\t\t\tfired: function() {\n\t\t\t\treturn !!fired;\n\t\t\t}\n\t\t};\n\n\treturn self;\n};\njQuery.extend({\n\n\tDeferred: function( func ) {\n\t\tvar tuples = [\n\t\t\t\t// action, add listener, listener list, final state\n\t\t\t\t[ \"resolve\", \"done\", jQuery.Callbacks(\"once memory\"), \"resolved\" ],\n\t\t\t\t[ \"reject\", \"fail\", jQuery.Callbacks(\"once memory\"), \"rejected\" ],\n\t\t\t\t[ \"notify\", \"progress\", jQuery.Callbacks(\"memory\") ]\n\t\t\t],\n\t\t\tstate = \"pending\",\n\t\t\tpromise = {\n\t\t\t\tstate: function() {\n\t\t\t\t\treturn state;\n\t\t\t\t},\n\t\t\t\talways: function() {\n\t\t\t\t\tdeferred.done( arguments ).fail( arguments );\n\t\t\t\t\treturn this;\n\t\t\t\t},\n\t\t\t\tthen: function( /* fnDone, fnFail, fnProgress */ ) {\n\t\t\t\t\tvar fns = arguments;\n\t\t\t\t\treturn jQuery.Deferred(function( newDefer ) {\n\t\t\t\t\t\tjQuery.each( tuples, function( i, tuple ) {\n\t\t\t\t\t\t\tvar action = tuple[ 0 ],\n\t\t\t\t\t\t\t\tfn = jQuery.isFunction( fns[ i ] ) && fns[ i ];\n\t\t\t\t\t\t\t// deferred[ done | fail | progress ] for forwarding actions to newDefer\n\t\t\t\t\t\t\tdeferred[ tuple[1] ](function() {\n\t\t\t\t\t\t\t\tvar returned = fn && fn.apply( this, arguments );\n\t\t\t\t\t\t\t\tif ( returned && jQuery.isFunction( returned.promise ) ) {\n\t\t\t\t\t\t\t\t\treturned.promise()\n\t\t\t\t\t\t\t\t\t\t.done( newDefer.resolve )\n\t\t\t\t\t\t\t\t\t\t.fail( newDefer.reject )\n\t\t\t\t\t\t\t\t\t\t.progress( newDefer.notify );\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tnewDefer[ action + \"With\" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t});\n\t\t\t\t\t\tfns = null;\n\t\t\t\t\t}).promise();\n\t\t\t\t},\n\t\t\t\t// Get a promise for this deferred\n\t\t\t\t// If obj is provided, the promise aspect is added to the object\n\t\t\t\tpromise: function( obj ) {\n\t\t\t\t\treturn obj != null ? jQuery.extend( obj, promise ) : promise;\n\t\t\t\t}\n\t\t\t},\n\t\t\tdeferred = {};\n\n\t\t// Keep pipe for back-compat\n\t\tpromise.pipe = promise.then;\n\n\t\t// Add list-specific methods\n\t\tjQuery.each( tuples, function( i, tuple ) {\n\t\t\tvar list = tuple[ 2 ],\n\t\t\t\tstateString = tuple[ 3 ];\n\n\t\t\t// promise[ done | fail | progress ] = list.add\n\t\t\tpromise[ tuple[1] ] = list.add;\n\n\t\t\t// Handle state\n\t\t\tif ( stateString ) {\n\t\t\t\tlist.add(function() {\n\t\t\t\t\t// state = [ resolved | rejected ]\n\t\t\t\t\tstate = stateString;\n\n\t\t\t\t// [ reject_list | resolve_list ].disable; progress_list.lock\n\t\t\t\t}, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );\n\t\t\t}\n\n\t\t\t// deferred[ resolve | reject | notify ]\n\t\t\tdeferred[ tuple[0] ] = function() {\n\t\t\t\tdeferred[ tuple[0] + \"With\" ]( this === deferred ? promise : this, arguments );\n\t\t\t\treturn this;\n\t\t\t};\n\t\t\tdeferred[ tuple[0] + \"With\" ] = list.fireWith;\n\t\t});\n\n\t\t// Make the deferred a promise\n\t\tpromise.promise( deferred );\n\n\t\t// Call given func if any\n\t\tif ( func ) {\n\t\t\tfunc.call( deferred, deferred );\n\t\t}\n\n\t\t// All done!\n\t\treturn deferred;\n\t},\n\n\t// Deferred helper\n\twhen: function( subordinate /* , ..., subordinateN */ ) {\n\t\tvar i = 0,\n\t\t\tresolveValues = core_slice.call( arguments ),\n\t\t\tlength = resolveValues.length,\n\n\t\t\t// the count of uncompleted subordinates\n\t\t\tremaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,\n\n\t\t\t// the master Deferred. If resolveValues consist of only a single Deferred, just use that.\n\t\t\tdeferred = remaining === 1 ? subordinate : jQuery.Deferred(),\n\n\t\t\t// Update function for both resolve and progress values\n\t\t\tupdateFunc = function( i, contexts, values ) {\n\t\t\t\treturn function( value ) {\n\t\t\t\t\tcontexts[ i ] = this;\n\t\t\t\t\tvalues[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value;\n\t\t\t\t\tif( values === progressValues ) {\n\t\t\t\t\t\tdeferred.notifyWith( contexts, values );\n\t\t\t\t\t} else if ( !( --remaining ) ) {\n\t\t\t\t\t\tdeferred.resolveWith( contexts, values );\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t},\n\n\t\t\tprogressValues, progressContexts, resolveContexts;\n\n\t\t// add listeners to Deferred subordinates; treat others as resolved\n\t\tif ( length > 1 ) {\n\t\t\tprogressValues = new Array( length );\n\t\t\tprogressContexts = new Array( length );\n\t\t\tresolveContexts = new Array( length );\n\t\t\tfor ( ; i < length; i++ ) {\n\t\t\t\tif ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {\n\t\t\t\t\tresolveValues[ i ].promise()\n\t\t\t\t\t\t.done( updateFunc( i, resolveContexts, resolveValues ) )\n\t\t\t\t\t\t.fail( deferred.reject )\n\t\t\t\t\t\t.progress( updateFunc( i, progressContexts, progressValues ) );\n\t\t\t\t} else {\n\t\t\t\t\t--remaining;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// if we're not waiting on anything, resolve the master\n\t\tif ( !remaining ) {\n\t\t\tdeferred.resolveWith( resolveContexts, resolveValues );\n\t\t}\n\n\t\treturn deferred.promise();\n\t}\n});\njQuery.support = (function( support ) {\n\n\tvar all, a, input, select, fragment, opt, eventName, isSupported, i,\n\t\tdiv = document.createElement(\"div\");\n\n\t// Setup\n\tdiv.setAttribute( \"className\", \"t\" );\n\tdiv.innerHTML = \"  <link/><table></table><a href='/a'>a</a><input type='checkbox'/>\";\n\n\t// Finish early in limited (non-browser) environments\n\tall = div.getElementsByTagName(\"*\") || [];\n\ta = div.getElementsByTagName(\"a\")[ 0 ];\n\tif ( !a || !a.style || !all.length ) {\n\t\treturn support;\n\t}\n\n\t// First batch of tests\n\tselect = document.createElement(\"select\");\n\topt = select.appendChild( document.createElement(\"option\") );\n\tinput = div.getElementsByTagName(\"input\")[ 0 ];\n\n\ta.style.cssText = \"top:1px;float:left;opacity:.5\";\n\n\t// Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7)\n\tsupport.getSetAttribute = div.className !== \"t\";\n\n\t// IE strips leading whitespace when .innerHTML is used\n\tsupport.leadingWhitespace = div.firstChild.nodeType === 3;\n\n\t// Make sure that tbody elements aren't automatically inserted\n\t// IE will insert them into empty tables\n\tsupport.tbody = !div.getElementsByTagName(\"tbody\").length;\n\n\t// Make sure that link elements get serialized correctly by innerHTML\n\t// This requires a wrapper element in IE\n\tsupport.htmlSerialize = !!div.getElementsByTagName(\"link\").length;\n\n\t// Get the style information from getAttribute\n\t// (IE uses .cssText instead)\n\tsupport.style = /top/.test( a.getAttribute(\"style\") );\n\n\t// Make sure that URLs aren't manipulated\n\t// (IE normalizes it by default)\n\tsupport.hrefNormalized = a.getAttribute(\"href\") === \"/a\";\n\n\t// Make sure that element opacity exists\n\t// (IE uses filter instead)\n\t// Use a regex to work around a WebKit issue. See #5145\n\tsupport.opacity = /^0.5/.test( a.style.opacity );\n\n\t// Verify style float existence\n\t// (IE uses styleFloat instead of cssFloat)\n\tsupport.cssFloat = !!a.style.cssFloat;\n\n\t// Check the default checkbox/radio value (\"\" on WebKit; \"on\" elsewhere)\n\tsupport.checkOn = !!input.value;\n\n\t// Make sure that a selected-by-default option has a working selected property.\n\t// (WebKit defaults to false instead of true, IE too, if it's in an optgroup)\n\tsupport.optSelected = opt.selected;\n\n\t// Tests for enctype support on a form (#6743)\n\tsupport.enctype = !!document.createElement(\"form\").enctype;\n\n\t// Makes sure cloning an html5 element does not cause problems\n\t// Where outerHTML is undefined, this still works\n\tsupport.html5Clone = document.createElement(\"nav\").cloneNode( true ).outerHTML !== \"<:nav></:nav>\";\n\n\t// Will be defined later\n\tsupport.inlineBlockNeedsLayout = false;\n\tsupport.shrinkWrapBlocks = false;\n\tsupport.pixelPosition = false;\n\tsupport.deleteExpando = true;\n\tsupport.noCloneEvent = true;\n\tsupport.reliableMarginRight = true;\n\tsupport.boxSizingReliable = true;\n\n\t// Make sure checked status is properly cloned\n\tinput.checked = true;\n\tsupport.noCloneChecked = input.cloneNode( true ).checked;\n\n\t// Make sure that the options inside disabled selects aren't marked as disabled\n\t// (WebKit marks them as disabled)\n\tselect.disabled = true;\n\tsupport.optDisabled = !opt.disabled;\n\n\t// Support: IE<9\n\ttry {\n\t\tdelete div.test;\n\t} catch( e ) {\n\t\tsupport.deleteExpando = false;\n\t}\n\n\t// Check if we can trust getAttribute(\"value\")\n\tinput = document.createElement(\"input\");\n\tinput.setAttribute( \"value\", \"\" );\n\tsupport.input = input.getAttribute( \"value\" ) === \"\";\n\n\t// Check if an input maintains its value after becoming a radio\n\tinput.value = \"t\";\n\tinput.setAttribute( \"type\", \"radio\" );\n\tsupport.radioValue = input.value === \"t\";\n\n\t// #11217 - WebKit loses check when the name is after the checked attribute\n\tinput.setAttribute( \"checked\", \"t\" );\n\tinput.setAttribute( \"name\", \"t\" );\n\n\tfragment = document.createDocumentFragment();\n\tfragment.appendChild( input );\n\n\t// Check if a disconnected checkbox will retain its checked\n\t// value of true after appended to the DOM (IE6/7)\n\tsupport.appendChecked = input.checked;\n\n\t// WebKit doesn't clone checked state correctly in fragments\n\tsupport.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked;\n\n\t// Support: IE<9\n\t// Opera does not clone events (and typeof div.attachEvent === undefined).\n\t// IE9-10 clones events bound via attachEvent, but they don't trigger with .click()\n\tif ( div.attachEvent ) {\n\t\tdiv.attachEvent( \"onclick\", function() {\n\t\t\tsupport.noCloneEvent = false;\n\t\t});\n\n\t\tdiv.cloneNode( true ).click();\n\t}\n\n\t// Support: IE<9 (lack submit/change bubble), Firefox 17+ (lack focusin event)\n\t// Beware of CSP restrictions (https://developer.mozilla.org/en/Security/CSP)\n\tfor ( i in { submit: true, change: true, focusin: true }) {\n\t\tdiv.setAttribute( eventName = \"on\" + i, \"t\" );\n\n\t\tsupport[ i + \"Bubbles\" ] = eventName in window || div.attributes[ eventName ].expando === false;\n\t}\n\n\tdiv.style.backgroundClip = \"content-box\";\n\tdiv.cloneNode( true ).style.backgroundClip = \"\";\n\tsupport.clearCloneStyle = div.style.backgroundClip === \"content-box\";\n\n\t// Support: IE<9\n\t// Iteration over object's inherited properties before its own.\n\tfor ( i in jQuery( support ) ) {\n\t\tbreak;\n\t}\n\tsupport.ownLast = i !== \"0\";\n\n\t// Run tests that need a body at doc ready\n\tjQuery(function() {\n\t\tvar container, marginDiv, tds,\n\t\t\tdivReset = \"padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;\",\n\t\t\tbody = document.getElementsByTagName(\"body\")[0];\n\n\t\tif ( !body ) {\n\t\t\t// Return for frameset docs that don't have a body\n\t\t\treturn;\n\t\t}\n\n\t\tcontainer = document.createElement(\"div\");\n\t\tcontainer.style.cssText = \"border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px\";\n\n\t\tbody.appendChild( container ).appendChild( div );\n\n\t\t// Support: IE8\n\t\t// Check if table cells still have offsetWidth/Height when they are set\n\t\t// to display:none and there are still other visible table cells in a\n\t\t// table row; if so, offsetWidth/Height are not reliable for use when\n\t\t// determining if an element has been hidden directly using\n\t\t// display:none (it is still safe to use offsets if a parent element is\n\t\t// hidden; don safety goggles and see bug #4512 for more information).\n\t\tdiv.innerHTML = \"<table><tr><td></td><td>t</td></tr></table>\";\n\t\ttds = div.getElementsByTagName(\"td\");\n\t\ttds[ 0 ].style.cssText = \"padding:0;margin:0;border:0;display:none\";\n\t\tisSupported = ( tds[ 0 ].offsetHeight === 0 );\n\n\t\ttds[ 0 ].style.display = \"\";\n\t\ttds[ 1 ].style.display = \"none\";\n\n\t\t// Support: IE8\n\t\t// Check if empty table cells still have offsetWidth/Height\n\t\tsupport.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 );\n\n\t\t// Check box-sizing and margin behavior.\n\t\tdiv.innerHTML = \"\";\n\t\tdiv.style.cssText = \"box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;\";\n\n\t\t// Workaround failing boxSizing test due to offsetWidth returning wrong value\n\t\t// with some non-1 values of body zoom, ticket #13543\n\t\tjQuery.swap( body, body.style.zoom != null ? { zoom: 1 } : {}, function() {\n\t\t\tsupport.boxSizing = div.offsetWidth === 4;\n\t\t});\n\n\t\t// Use window.getComputedStyle because jsdom on node.js will break without it.\n\t\tif ( window.getComputedStyle ) {\n\t\t\tsupport.pixelPosition = ( window.getComputedStyle( div, null ) || {} ).top !== \"1%\";\n\t\t\tsupport.boxSizingReliable = ( window.getComputedStyle( div, null ) || { width: \"4px\" } ).width === \"4px\";\n\n\t\t\t// Check if div with explicit width and no margin-right incorrectly\n\t\t\t// gets computed margin-right based on width of container. (#3333)\n\t\t\t// Fails in WebKit before Feb 2011 nightlies\n\t\t\t// WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right\n\t\t\tmarginDiv = div.appendChild( document.createElement(\"div\") );\n\t\t\tmarginDiv.style.cssText = div.style.cssText = divReset;\n\t\t\tmarginDiv.style.marginRight = marginDiv.style.width = \"0\";\n\t\t\tdiv.style.width = \"1px\";\n\n\t\t\tsupport.reliableMarginRight =\n\t\t\t\t!parseFloat( ( window.getComputedStyle( marginDiv, null ) || {} ).marginRight );\n\t\t}\n\n\t\tif ( typeof div.style.zoom !== core_strundefined ) {\n\t\t\t// Support: IE<8\n\t\t\t// Check if natively block-level elements act like inline-block\n\t\t\t// elements when setting their display to 'inline' and giving\n\t\t\t// them layout\n\t\t\tdiv.innerHTML = \"\";\n\t\t\tdiv.style.cssText = divReset + \"width:1px;padding:1px;display:inline;zoom:1\";\n\t\t\tsupport.inlineBlockNeedsLayout = ( div.offsetWidth === 3 );\n\n\t\t\t// Support: IE6\n\t\t\t// Check if elements with layout shrink-wrap their children\n\t\t\tdiv.style.display = \"block\";\n\t\t\tdiv.innerHTML = \"<div></div>\";\n\t\t\tdiv.firstChild.style.width = \"5px\";\n\t\t\tsupport.shrinkWrapBlocks = ( div.offsetWidth !== 3 );\n\n\t\t\tif ( support.inlineBlockNeedsLayout ) {\n\t\t\t\t// Prevent IE 6 from affecting layout for positioned elements #11048\n\t\t\t\t// Prevent IE from shrinking the body in IE 7 mode #12869\n\t\t\t\t// Support: IE<8\n\t\t\t\tbody.style.zoom = 1;\n\t\t\t}\n\t\t}\n\n\t\tbody.removeChild( container );\n\n\t\t// Null elements to avoid leaks in IE\n\t\tcontainer = div = tds = marginDiv = null;\n\t});\n\n\t// Null elements to avoid leaks in IE\n\tall = select = fragment = opt = a = input = null;\n\n\treturn support;\n})({});\n\nvar rbrace = /(?:\\{[\\s\\S]*\\}|\\[[\\s\\S]*\\])$/,\n\trmultiDash = /([A-Z])/g;\n\nfunction internalData( elem, name, data, pvt /* Internal Use Only */ ){\n\tif ( !jQuery.acceptData( elem ) ) {\n\t\treturn;\n\t}\n\n\tvar ret, thisCache,\n\t\tinternalKey = jQuery.expando,\n\n\t\t// We have to handle DOM nodes and JS objects differently because IE6-7\n\t\t// can't GC object references properly across the DOM-JS boundary\n\t\tisNode = elem.nodeType,\n\n\t\t// Only DOM nodes need the global jQuery cache; JS object data is\n\t\t// attached directly to the object so GC can occur automatically\n\t\tcache = isNode ? jQuery.cache : elem,\n\n\t\t// Only defining an ID for JS objects if its cache already exists allows\n\t\t// the code to shortcut on the same path as a DOM node with no cache\n\t\tid = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey;\n\n\t// Avoid doing any more work than we need to when trying to get data on an\n\t// object that has no data at all\n\tif ( (!id || !cache[id] || (!pvt && !cache[id].data)) && data === undefined && typeof name === \"string\" ) {\n\t\treturn;\n\t}\n\n\tif ( !id ) {\n\t\t// Only DOM nodes need a new unique ID for each element since their data\n\t\t// ends up in the global cache\n\t\tif ( isNode ) {\n\t\t\tid = elem[ internalKey ] = core_deletedIds.pop() || jQuery.guid++;\n\t\t} else {\n\t\t\tid = internalKey;\n\t\t}\n\t}\n\n\tif ( !cache[ id ] ) {\n\t\t// Avoid exposing jQuery metadata on plain JS objects when the object\n\t\t// is serialized using JSON.stringify\n\t\tcache[ id ] = isNode ? {} : { toJSON: jQuery.noop };\n\t}\n\n\t// An object can be passed to jQuery.data instead of a key/value pair; this gets\n\t// shallow copied over onto the existing cache\n\tif ( typeof name === \"object\" || typeof name === \"function\" ) {\n\t\tif ( pvt ) {\n\t\t\tcache[ id ] = jQuery.extend( cache[ id ], name );\n\t\t} else {\n\t\t\tcache[ id ].data = jQuery.extend( cache[ id ].data, name );\n\t\t}\n\t}\n\n\tthisCache = cache[ id ];\n\n\t// jQuery data() is stored in a separate object inside the object's internal data\n\t// cache in order to avoid key collisions between internal data and user-defined\n\t// data.\n\tif ( !pvt ) {\n\t\tif ( !thisCache.data ) {\n\t\t\tthisCache.data = {};\n\t\t}\n\n\t\tthisCache = thisCache.data;\n\t}\n\n\tif ( data !== undefined ) {\n\t\tthisCache[ jQuery.camelCase( name ) ] = data;\n\t}\n\n\t// Check for both converted-to-camel and non-converted data property names\n\t// If a data property was specified\n\tif ( typeof name === \"string\" ) {\n\n\t\t// First Try to find as-is property data\n\t\tret = thisCache[ name ];\n\n\t\t// Test for null|undefined property data\n\t\tif ( ret == null ) {\n\n\t\t\t// Try to find the camelCased property\n\t\t\tret = thisCache[ jQuery.camelCase( name ) ];\n\t\t}\n\t} else {\n\t\tret = thisCache;\n\t}\n\n\treturn ret;\n}\n\nfunction internalRemoveData( elem, name, pvt ) {\n\tif ( !jQuery.acceptData( elem ) ) {\n\t\treturn;\n\t}\n\n\tvar thisCache, i,\n\t\tisNode = elem.nodeType,\n\n\t\t// See jQuery.data for more information\n\t\tcache = isNode ? jQuery.cache : elem,\n\t\tid = isNode ? elem[ jQuery.expando ] : jQuery.expando;\n\n\t// If there is already no cache entry for this object, there is no\n\t// purpose in continuing\n\tif ( !cache[ id ] ) {\n\t\treturn;\n\t}\n\n\tif ( name ) {\n\n\t\tthisCache = pvt ? cache[ id ] : cache[ id ].data;\n\n\t\tif ( thisCache ) {\n\n\t\t\t// Support array or space separated string names for data keys\n\t\t\tif ( !jQuery.isArray( name ) ) {\n\n\t\t\t\t// try the string as a key before any manipulation\n\t\t\t\tif ( name in thisCache ) {\n\t\t\t\t\tname = [ name ];\n\t\t\t\t} else {\n\n\t\t\t\t\t// split the camel cased version by spaces unless a key with the spaces exists\n\t\t\t\t\tname = jQuery.camelCase( name );\n\t\t\t\t\tif ( name in thisCache ) {\n\t\t\t\t\t\tname = [ name ];\n\t\t\t\t\t} else {\n\t\t\t\t\t\tname = name.split(\" \");\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// If \"name\" is an array of keys...\n\t\t\t\t// When data is initially created, via (\"key\", \"val\") signature,\n\t\t\t\t// keys will be converted to camelCase.\n\t\t\t\t// Since there is no way to tell _how_ a key was added, remove\n\t\t\t\t// both plain key and camelCase key. #12786\n\t\t\t\t// This will only penalize the array argument path.\n\t\t\t\tname = name.concat( jQuery.map( name, jQuery.camelCase ) );\n\t\t\t}\n\n\t\t\ti = name.length;\n\t\t\twhile ( i-- ) {\n\t\t\t\tdelete thisCache[ name[i] ];\n\t\t\t}\n\n\t\t\t// If there is no data left in the cache, we want to continue\n\t\t\t// and let the cache object itself get destroyed\n\t\t\tif ( pvt ? !isEmptyDataObject(thisCache) : !jQuery.isEmptyObject(thisCache) ) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t}\n\n\t// See jQuery.data for more information\n\tif ( !pvt ) {\n\t\tdelete cache[ id ].data;\n\n\t\t// Don't destroy the parent cache unless the internal data object\n\t\t// had been the only thing left in it\n\t\tif ( !isEmptyDataObject( cache[ id ] ) ) {\n\t\t\treturn;\n\t\t}\n\t}\n\n\t// Destroy the cache\n\tif ( isNode ) {\n\t\tjQuery.cleanData( [ elem ], true );\n\n\t// Use delete when supported for expandos or `cache` is not a window per isWindow (#10080)\n\t/* jshint eqeqeq: false */\n\t} else if ( jQuery.support.deleteExpando || cache != cache.window ) {\n\t\t/* jshint eqeqeq: true */\n\t\tdelete cache[ id ];\n\n\t// When all else fails, null\n\t} else {\n\t\tcache[ id ] = null;\n\t}\n}\n\njQuery.extend({\n\tcache: {},\n\n\t// The following elements throw uncatchable exceptions if you\n\t// attempt to add expando properties to them.\n\tnoData: {\n\t\t\"applet\": true,\n\t\t\"embed\": true,\n\t\t// Ban all objects except for Flash (which handle expandos)\n\t\t\"object\": \"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000\"\n\t},\n\n\thasData: function( elem ) {\n\t\telem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];\n\t\treturn !!elem && !isEmptyDataObject( elem );\n\t},\n\n\tdata: function( elem, name, data ) {\n\t\treturn internalData( elem, name, data );\n\t},\n\n\tremoveData: function( elem, name ) {\n\t\treturn internalRemoveData( elem, name );\n\t},\n\n\t// For internal use only.\n\t_data: function( elem, name, data ) {\n\t\treturn internalData( elem, name, data, true );\n\t},\n\n\t_removeData: function( elem, name ) {\n\t\treturn internalRemoveData( elem, name, true );\n\t},\n\n\t// A method for determining if a DOM node can handle the data expando\n\tacceptData: function( elem ) {\n\t\t// Do not set data on non-element because it will not be cleared (#8335).\n\t\tif ( elem.nodeType && elem.nodeType !== 1 && elem.nodeType !== 9 ) {\n\t\t\treturn false;\n\t\t}\n\n\t\tvar noData = elem.nodeName && jQuery.noData[ elem.nodeName.toLowerCase() ];\n\n\t\t// nodes accept data unless otherwise specified; rejection can be conditional\n\t\treturn !noData || noData !== true && elem.getAttribute(\"classid\") === noData;\n\t}\n});\n\njQuery.fn.extend({\n\tdata: function( key, value ) {\n\t\tvar attrs, name,\n\t\t\tdata = null,\n\t\t\ti = 0,\n\t\t\telem = this[0];\n\n\t\t// Special exceptions of .data basically thwart jQuery.access,\n\t\t// so implement the relevant behavior ourselves\n\n\t\t// Gets all values\n\t\tif ( key === undefined ) {\n\t\t\tif ( this.length ) {\n\t\t\t\tdata = jQuery.data( elem );\n\n\t\t\t\tif ( elem.nodeType === 1 && !jQuery._data( elem, \"parsedAttrs\" ) ) {\n\t\t\t\t\tattrs = elem.attributes;\n\t\t\t\t\tfor ( ; i < attrs.length; i++ ) {\n\t\t\t\t\t\tname = attrs[i].name;\n\n\t\t\t\t\t\tif ( name.indexOf(\"data-\") === 0 ) {\n\t\t\t\t\t\t\tname = jQuery.camelCase( name.slice(5) );\n\n\t\t\t\t\t\t\tdataAttr( elem, name, data[ name ] );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tjQuery._data( elem, \"parsedAttrs\", true );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn data;\n\t\t}\n\n\t\t// Sets multiple values\n\t\tif ( typeof key === \"object\" ) {\n\t\t\treturn this.each(function() {\n\t\t\t\tjQuery.data( this, key );\n\t\t\t});\n\t\t}\n\n\t\treturn arguments.length > 1 ?\n\n\t\t\t// Sets one value\n\t\t\tthis.each(function() {\n\t\t\t\tjQuery.data( this, key, value );\n\t\t\t}) :\n\n\t\t\t// Gets one value\n\t\t\t// Try to fetch any internally stored data first\n\t\t\telem ? dataAttr( elem, key, jQuery.data( elem, key ) ) : null;\n\t},\n\n\tremoveData: function( key ) {\n\t\treturn this.each(function() {\n\t\t\tjQuery.removeData( this, key );\n\t\t});\n\t}\n});\n\nfunction dataAttr( elem, key, data ) {\n\t// If nothing was found internally, try to fetch any\n\t// data from the HTML5 data-* attribute\n\tif ( data === undefined && elem.nodeType === 1 ) {\n\n\t\tvar name = \"data-\" + key.replace( rmultiDash, \"-$1\" ).toLowerCase();\n\n\t\tdata = elem.getAttribute( name );\n\n\t\tif ( typeof data === \"string\" ) {\n\t\t\ttry {\n\t\t\t\tdata = data === \"true\" ? true :\n\t\t\t\t\tdata === \"false\" ? false :\n\t\t\t\t\tdata === \"null\" ? null :\n\t\t\t\t\t// Only convert to a number if it doesn't change the string\n\t\t\t\t\t+data + \"\" === data ? +data :\n\t\t\t\t\trbrace.test( data ) ? jQuery.parseJSON( data ) :\n\t\t\t\t\t\tdata;\n\t\t\t} catch( e ) {}\n\n\t\t\t// Make sure we set the data so it isn't changed later\n\t\t\tjQuery.data( elem, key, data );\n\n\t\t} else {\n\t\t\tdata = undefined;\n\t\t}\n\t}\n\n\treturn data;\n}\n\n// checks a cache object for emptiness\nfunction isEmptyDataObject( obj ) {\n\tvar name;\n\tfor ( name in obj ) {\n\n\t\t// if the public data object is empty, the private is still empty\n\t\tif ( name === \"data\" && jQuery.isEmptyObject( obj[name] ) ) {\n\t\t\tcontinue;\n\t\t}\n\t\tif ( name !== \"toJSON\" ) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\treturn true;\n}\njQuery.extend({\n\tqueue: function( elem, type, data ) {\n\t\tvar queue;\n\n\t\tif ( elem ) {\n\t\t\ttype = ( type || \"fx\" ) + \"queue\";\n\t\t\tqueue = jQuery._data( elem, type );\n\n\t\t\t// Speed up dequeue by getting out quickly if this is just a lookup\n\t\t\tif ( data ) {\n\t\t\t\tif ( !queue || jQuery.isArray(data) ) {\n\t\t\t\t\tqueue = jQuery._data( elem, type, jQuery.makeArray(data) );\n\t\t\t\t} else {\n\t\t\t\t\tqueue.push( data );\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn queue || [];\n\t\t}\n\t},\n\n\tdequeue: function( elem, type ) {\n\t\ttype = type || \"fx\";\n\n\t\tvar queue = jQuery.queue( elem, type ),\n\t\t\tstartLength = queue.length,\n\t\t\tfn = queue.shift(),\n\t\t\thooks = jQuery._queueHooks( elem, type ),\n\t\t\tnext = function() {\n\t\t\t\tjQuery.dequeue( elem, type );\n\t\t\t};\n\n\t\t// If the fx queue is dequeued, always remove the progress sentinel\n\t\tif ( fn === \"inprogress\" ) {\n\t\t\tfn = queue.shift();\n\t\t\tstartLength--;\n\t\t}\n\n\t\thooks.cur = fn;\n\t\tif ( fn ) {\n\n\t\t\t// Add a progress sentinel to prevent the fx queue from being\n\t\t\t// automatically dequeued\n\t\t\tif ( type === \"fx\" ) {\n\t\t\t\tqueue.unshift( \"inprogress\" );\n\t\t\t}\n\n\t\t\t// clear up the last queue stop function\n\t\t\tdelete hooks.stop;\n\t\t\tfn.call( elem, next, hooks );\n\t\t}\n\n\t\tif ( !startLength && hooks ) {\n\t\t\thooks.empty.fire();\n\t\t}\n\t},\n\n\t// not intended for public consumption - generates a queueHooks object, or returns the current one\n\t_queueHooks: function( elem, type ) {\n\t\tvar key = type + \"queueHooks\";\n\t\treturn jQuery._data( elem, key ) || jQuery._data( elem, key, {\n\t\t\tempty: jQuery.Callbacks(\"once memory\").add(function() {\n\t\t\t\tjQuery._removeData( elem, type + \"queue\" );\n\t\t\t\tjQuery._removeData( elem, key );\n\t\t\t})\n\t\t});\n\t}\n});\n\njQuery.fn.extend({\n\tqueue: function( type, data ) {\n\t\tvar setter = 2;\n\n\t\tif ( typeof type !== \"string\" ) {\n\t\t\tdata = type;\n\t\t\ttype = \"fx\";\n\t\t\tsetter--;\n\t\t}\n\n\t\tif ( arguments.length < setter ) {\n\t\t\treturn jQuery.queue( this[0], type );\n\t\t}\n\n\t\treturn data === undefined ?\n\t\t\tthis :\n\t\t\tthis.each(function() {\n\t\t\t\tvar queue = jQuery.queue( this, type, data );\n\n\t\t\t\t// ensure a hooks for this queue\n\t\t\t\tjQuery._queueHooks( this, type );\n\n\t\t\t\tif ( type === \"fx\" && queue[0] !== \"inprogress\" ) {\n\t\t\t\t\tjQuery.dequeue( this, type );\n\t\t\t\t}\n\t\t\t});\n\t},\n\tdequeue: function( type ) {\n\t\treturn this.each(function() {\n\t\t\tjQuery.dequeue( this, type );\n\t\t});\n\t},\n\t// Based off of the plugin by Clint Helfers, with permission.\n\t// http://blindsignals.com/index.php/2009/07/jquery-delay/\n\tdelay: function( time, type ) {\n\t\ttime = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;\n\t\ttype = type || \"fx\";\n\n\t\treturn this.queue( type, function( next, hooks ) {\n\t\t\tvar timeout = setTimeout( next, time );\n\t\t\thooks.stop = function() {\n\t\t\t\tclearTimeout( timeout );\n\t\t\t};\n\t\t});\n\t},\n\tclearQueue: function( type ) {\n\t\treturn this.queue( type || \"fx\", [] );\n\t},\n\t// Get a promise resolved when queues of a certain type\n\t// are emptied (fx is the type by default)\n\tpromise: function( type, obj ) {\n\t\tvar tmp,\n\t\t\tcount = 1,\n\t\t\tdefer = jQuery.Deferred(),\n\t\t\telements = this,\n\t\t\ti = this.length,\n\t\t\tresolve = function() {\n\t\t\t\tif ( !( --count ) ) {\n\t\t\t\t\tdefer.resolveWith( elements, [ elements ] );\n\t\t\t\t}\n\t\t\t};\n\n\t\tif ( typeof type !== \"string\" ) {\n\t\t\tobj = type;\n\t\t\ttype = undefined;\n\t\t}\n\t\ttype = type || \"fx\";\n\n\t\twhile( i-- ) {\n\t\t\ttmp = jQuery._data( elements[ i ], type + \"queueHooks\" );\n\t\t\tif ( tmp && tmp.empty ) {\n\t\t\t\tcount++;\n\t\t\t\ttmp.empty.add( resolve );\n\t\t\t}\n\t\t}\n\t\tresolve();\n\t\treturn defer.promise( obj );\n\t}\n});\nvar nodeHook, boolHook,\n\trclass = /[\\t\\r\\n\\f]/g,\n\trreturn = /\\r/g,\n\trfocusable = /^(?:input|select|textarea|button|object)$/i,\n\trclickable = /^(?:a|area)$/i,\n\truseDefault = /^(?:checked|selected)$/i,\n\tgetSetAttribute = jQuery.support.getSetAttribute,\n\tgetSetInput = jQuery.support.input;\n\njQuery.fn.extend({\n\tattr: function( name, value ) {\n\t\treturn jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 );\n\t},\n\n\tremoveAttr: function( name ) {\n\t\treturn this.each(function() {\n\t\t\tjQuery.removeAttr( this, name );\n\t\t});\n\t},\n\n\tprop: function( name, value ) {\n\t\treturn jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 );\n\t},\n\n\tremoveProp: function( name ) {\n\t\tname = jQuery.propFix[ name ] || name;\n\t\treturn this.each(function() {\n\t\t\t// try/catch handles cases where IE balks (such as removing a property on window)\n\t\t\ttry {\n\t\t\t\tthis[ name ] = undefined;\n\t\t\t\tdelete this[ name ];\n\t\t\t} catch( e ) {}\n\t\t});\n\t},\n\n\taddClass: function( value ) {\n\t\tvar classes, elem, cur, clazz, j,\n\t\t\ti = 0,\n\t\t\tlen = this.length,\n\t\t\tproceed = typeof value === \"string\" && value;\n\n\t\tif ( jQuery.isFunction( value ) ) {\n\t\t\treturn this.each(function( j ) {\n\t\t\t\tjQuery( this ).addClass( value.call( this, j, this.className ) );\n\t\t\t});\n\t\t}\n\n\t\tif ( proceed ) {\n\t\t\t// The disjunction here is for better compressibility (see removeClass)\n\t\t\tclasses = ( value || \"\" ).match( core_rnotwhite ) || [];\n\n\t\t\tfor ( ; i < len; i++ ) {\n\t\t\t\telem = this[ i ];\n\t\t\t\tcur = elem.nodeType === 1 && ( elem.className ?\n\t\t\t\t\t( \" \" + elem.className + \" \" ).replace( rclass, \" \" ) :\n\t\t\t\t\t\" \"\n\t\t\t\t);\n\n\t\t\t\tif ( cur ) {\n\t\t\t\t\tj = 0;\n\t\t\t\t\twhile ( (clazz = classes[j++]) ) {\n\t\t\t\t\t\tif ( cur.indexOf( \" \" + clazz + \" \" ) < 0 ) {\n\t\t\t\t\t\t\tcur += clazz + \" \";\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\telem.className = jQuery.trim( cur );\n\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn this;\n\t},\n\n\tremoveClass: function( value ) {\n\t\tvar classes, elem, cur, clazz, j,\n\t\t\ti = 0,\n\t\t\tlen = this.length,\n\t\t\tproceed = arguments.length === 0 || typeof value === \"string\" && value;\n\n\t\tif ( jQuery.isFunction( value ) ) {\n\t\t\treturn this.each(function( j ) {\n\t\t\t\tjQuery( this ).removeClass( value.call( this, j, this.className ) );\n\t\t\t});\n\t\t}\n\t\tif ( proceed ) {\n\t\t\tclasses = ( value || \"\" ).match( core_rnotwhite ) || [];\n\n\t\t\tfor ( ; i < len; i++ ) {\n\t\t\t\telem = this[ i ];\n\t\t\t\t// This expression is here for better compressibility (see addClass)\n\t\t\t\tcur = elem.nodeType === 1 && ( elem.className ?\n\t\t\t\t\t( \" \" + elem.className + \" \" ).replace( rclass, \" \" ) :\n\t\t\t\t\t\"\"\n\t\t\t\t);\n\n\t\t\t\tif ( cur ) {\n\t\t\t\t\tj = 0;\n\t\t\t\t\twhile ( (clazz = classes[j++]) ) {\n\t\t\t\t\t\t// Remove *all* instances\n\t\t\t\t\t\twhile ( cur.indexOf( \" \" + clazz + \" \" ) >= 0 ) {\n\t\t\t\t\t\t\tcur = cur.replace( \" \" + clazz + \" \", \" \" );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\telem.className = value ? jQuery.trim( cur ) : \"\";\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn this;\n\t},\n\n\ttoggleClass: function( value, stateVal ) {\n\t\tvar type = typeof value,\n\t\t\tisBool = typeof stateVal === \"boolean\";\n\n\t\tif ( jQuery.isFunction( value ) ) {\n\t\t\treturn this.each(function( i ) {\n\t\t\t\tjQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal );\n\t\t\t});\n\t\t}\n\n\t\treturn this.each(function() {\n\t\t\tif ( type === \"string\" ) {\n\t\t\t\t// toggle individual class names\n\t\t\t\tvar className,\n\t\t\t\t\ti = 0,\n\t\t\t\t\tself = jQuery( this ),\n\t\t\t\t\tstate = stateVal,\n\t\t\t\t\tclassNames = value.match( core_rnotwhite ) || [];\n\n\t\t\t\twhile ( (className = classNames[ i++ ]) ) {\n\t\t\t\t\t// check each className given, space separated list\n\t\t\t\t\tstate = isBool ? state : !self.hasClass( className );\n\t\t\t\t\tself[ state ? \"addClass\" : \"removeClass\" ]( className );\n\t\t\t\t}\n\n\t\t\t// Toggle whole class name\n\t\t\t} else if ( type === core_strundefined || type === \"boolean\" ) {\n\t\t\t\tif ( this.className ) {\n\t\t\t\t\t// store className if set\n\t\t\t\t\tjQuery._data( this, \"__className__\", this.className );\n\t\t\t\t}\n\n\t\t\t\t// If the element has a class name or if we're passed \"false\",\n\t\t\t\t// then remove the whole classname (if there was one, the above saved it).\n\t\t\t\t// Otherwise bring back whatever was previously saved (if anything),\n\t\t\t\t// falling back to the empty string if nothing was stored.\n\t\t\t\tthis.className = this.className || value === false ? \"\" : jQuery._data( this, \"__className__\" ) || \"\";\n\t\t\t}\n\t\t});\n\t},\n\n\thasClass: function( selector ) {\n\t\tvar className = \" \" + selector + \" \",\n\t\t\ti = 0,\n\t\t\tl = this.length;\n\t\tfor ( ; i < l; i++ ) {\n\t\t\tif ( this[i].nodeType === 1 && (\" \" + this[i].className + \" \").replace(rclass, \" \").indexOf( className ) >= 0 ) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t},\n\n\tval: function( value ) {\n\t\tvar ret, hooks, isFunction,\n\t\t\telem = this[0];\n\n\t\tif ( !arguments.length ) {\n\t\t\tif ( elem ) {\n\t\t\t\thooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ];\n\n\t\t\t\tif ( hooks && \"get\" in hooks && (ret = hooks.get( elem, \"value\" )) !== undefined ) {\n\t\t\t\t\treturn ret;\n\t\t\t\t}\n\n\t\t\t\tret = elem.value;\n\n\t\t\t\treturn typeof ret === \"string\" ?\n\t\t\t\t\t// handle most common string cases\n\t\t\t\t\tret.replace(rreturn, \"\") :\n\t\t\t\t\t// handle cases where value is null/undef or number\n\t\t\t\t\tret == null ? \"\" : ret;\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\n\t\tisFunction = jQuery.isFunction( value );\n\n\t\treturn this.each(function( i ) {\n\t\t\tvar val;\n\n\t\t\tif ( this.nodeType !== 1 ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif ( isFunction ) {\n\t\t\t\tval = value.call( this, i, jQuery( this ).val() );\n\t\t\t} else {\n\t\t\t\tval = value;\n\t\t\t}\n\n\t\t\t// Treat null/undefined as \"\"; convert numbers to string\n\t\t\tif ( val == null ) {\n\t\t\t\tval = \"\";\n\t\t\t} else if ( typeof val === \"number\" ) {\n\t\t\t\tval += \"\";\n\t\t\t} else if ( jQuery.isArray( val ) ) {\n\t\t\t\tval = jQuery.map(val, function ( value ) {\n\t\t\t\t\treturn value == null ? \"\" : value + \"\";\n\t\t\t\t});\n\t\t\t}\n\n\t\t\thooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];\n\n\t\t\t// If set returns undefined, fall back to normal setting\n\t\t\tif ( !hooks || !(\"set\" in hooks) || hooks.set( this, val, \"value\" ) === undefined ) {\n\t\t\t\tthis.value = val;\n\t\t\t}\n\t\t});\n\t}\n});\n\njQuery.extend({\n\tvalHooks: {\n\t\toption: {\n\t\t\tget: function( elem ) {\n\t\t\t\t// Use proper attribute retrieval(#6932, #12072)\n\t\t\t\tvar val = jQuery.find.attr( elem, \"value\" );\n\t\t\t\treturn val != null ?\n\t\t\t\t\tval :\n\t\t\t\t\telem.text;\n\t\t\t}\n\t\t},\n\t\tselect: {\n\t\t\tget: function( elem ) {\n\t\t\t\tvar value, option,\n\t\t\t\t\toptions = elem.options,\n\t\t\t\t\tindex = elem.selectedIndex,\n\t\t\t\t\tone = elem.type === \"select-one\" || index < 0,\n\t\t\t\t\tvalues = one ? null : [],\n\t\t\t\t\tmax = one ? index + 1 : options.length,\n\t\t\t\t\ti = index < 0 ?\n\t\t\t\t\t\tmax :\n\t\t\t\t\t\tone ? index : 0;\n\n\t\t\t\t// Loop through all the selected options\n\t\t\t\tfor ( ; i < max; i++ ) {\n\t\t\t\t\toption = options[ i ];\n\n\t\t\t\t\t// oldIE doesn't update selected after form reset (#2551)\n\t\t\t\t\tif ( ( option.selected || i === index ) &&\n\t\t\t\t\t\t\t// Don't return options that are disabled or in a disabled optgroup\n\t\t\t\t\t\t\t( jQuery.support.optDisabled ? !option.disabled : option.getAttribute(\"disabled\") === null ) &&\n\t\t\t\t\t\t\t( !option.parentNode.disabled || !jQuery.nodeName( option.parentNode, \"optgroup\" ) ) ) {\n\n\t\t\t\t\t\t// Get the specific value for the option\n\t\t\t\t\t\tvalue = jQuery( option ).val();\n\n\t\t\t\t\t\t// We don't need an array for one selects\n\t\t\t\t\t\tif ( one ) {\n\t\t\t\t\t\t\treturn value;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Multi-Selects return an array\n\t\t\t\t\t\tvalues.push( value );\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn values;\n\t\t\t},\n\n\t\t\tset: function( elem, value ) {\n\t\t\t\tvar optionSet, option,\n\t\t\t\t\toptions = elem.options,\n\t\t\t\t\tvalues = jQuery.makeArray( value ),\n\t\t\t\t\ti = options.length;\n\n\t\t\t\twhile ( i-- ) {\n\t\t\t\t\toption = options[ i ];\n\t\t\t\t\tif ( (option.selected = jQuery.inArray( jQuery(option).val(), values ) >= 0) ) {\n\t\t\t\t\t\toptionSet = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// force browsers to behave consistently when non-matching value is set\n\t\t\t\tif ( !optionSet ) {\n\t\t\t\t\telem.selectedIndex = -1;\n\t\t\t\t}\n\t\t\t\treturn values;\n\t\t\t}\n\t\t}\n\t},\n\n\tattr: function( elem, name, value ) {\n\t\tvar hooks, ret,\n\t\t\tnType = elem.nodeType;\n\n\t\t// don't get/set attributes on text, comment and attribute nodes\n\t\tif ( !elem || nType === 3 || nType === 8 || nType === 2 ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Fallback to prop when attributes are not supported\n\t\tif ( typeof elem.getAttribute === core_strundefined ) {\n\t\t\treturn jQuery.prop( elem, name, value );\n\t\t}\n\n\t\t// All attributes are lowercase\n\t\t// Grab necessary hook if one is defined\n\t\tif ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {\n\t\t\tname = name.toLowerCase();\n\t\t\thooks = jQuery.attrHooks[ name ] ||\n\t\t\t\t( jQuery.expr.match.bool.test( name ) ? boolHook : nodeHook );\n\t\t}\n\n\t\tif ( value !== undefined ) {\n\n\t\t\tif ( value === null ) {\n\t\t\t\tjQuery.removeAttr( elem, name );\n\n\t\t\t} else if ( hooks && \"set\" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {\n\t\t\t\treturn ret;\n\n\t\t\t} else {\n\t\t\t\telem.setAttribute( name, value + \"\" );\n\t\t\t\treturn value;\n\t\t\t}\n\n\t\t} else if ( hooks && \"get\" in hooks && (ret = hooks.get( elem, name )) !== null ) {\n\t\t\treturn ret;\n\n\t\t} else {\n\t\t\tret = jQuery.find.attr( elem, name );\n\n\t\t\t// Non-existent attributes return null, we normalize to undefined\n\t\t\treturn ret == null ?\n\t\t\t\tundefined :\n\t\t\t\tret;\n\t\t}\n\t},\n\n\tremoveAttr: function( elem, value ) {\n\t\tvar name, propName,\n\t\t\ti = 0,\n\t\t\tattrNames = value && value.match( core_rnotwhite );\n\n\t\tif ( attrNames && elem.nodeType === 1 ) {\n\t\t\twhile ( (name = attrNames[i++]) ) {\n\t\t\t\tpropName = jQuery.propFix[ name ] || name;\n\n\t\t\t\t// Boolean attributes get special treatment (#10870)\n\t\t\t\tif ( jQuery.expr.match.bool.test( name ) ) {\n\t\t\t\t\t// Set corresponding property to false\n\t\t\t\t\tif ( getSetInput && getSetAttribute || !ruseDefault.test( name ) ) {\n\t\t\t\t\t\telem[ propName ] = false;\n\t\t\t\t\t// Support: IE<9\n\t\t\t\t\t// Also clear defaultChecked/defaultSelected (if appropriate)\n\t\t\t\t\t} else {\n\t\t\t\t\t\telem[ jQuery.camelCase( \"default-\" + name ) ] =\n\t\t\t\t\t\t\telem[ propName ] = false;\n\t\t\t\t\t}\n\n\t\t\t\t// See #9699 for explanation of this approach (setting first, then removal)\n\t\t\t\t} else {\n\t\t\t\t\tjQuery.attr( elem, name, \"\" );\n\t\t\t\t}\n\n\t\t\t\telem.removeAttribute( getSetAttribute ? name : propName );\n\t\t\t}\n\t\t}\n\t},\n\n\tattrHooks: {\n\t\ttype: {\n\t\t\tset: function( elem, value ) {\n\t\t\t\tif ( !jQuery.support.radioValue && value === \"radio\" && jQuery.nodeName(elem, \"input\") ) {\n\t\t\t\t\t// Setting the type on a radio button after the value resets the value in IE6-9\n\t\t\t\t\t// Reset value to default in case type is set after value during creation\n\t\t\t\t\tvar val = elem.value;\n\t\t\t\t\telem.setAttribute( \"type\", value );\n\t\t\t\t\tif ( val ) {\n\t\t\t\t\t\telem.value = val;\n\t\t\t\t\t}\n\t\t\t\t\treturn value;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\n\tpropFix: {\n\t\t\"for\": \"htmlFor\",\n\t\t\"class\": \"className\"\n\t},\n\n\tprop: function( elem, name, value ) {\n\t\tvar ret, hooks, notxml,\n\t\t\tnType = elem.nodeType;\n\n\t\t// don't get/set properties on text, comment and attribute nodes\n\t\tif ( !elem || nType === 3 || nType === 8 || nType === 2 ) {\n\t\t\treturn;\n\t\t}\n\n\t\tnotxml = nType !== 1 || !jQuery.isXMLDoc( elem );\n\n\t\tif ( notxml ) {\n\t\t\t// Fix name and attach hooks\n\t\t\tname = jQuery.propFix[ name ] || name;\n\t\t\thooks = jQuery.propHooks[ name ];\n\t\t}\n\n\t\tif ( value !== undefined ) {\n\t\t\treturn hooks && \"set\" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ?\n\t\t\t\tret :\n\t\t\t\t( elem[ name ] = value );\n\n\t\t} else {\n\t\t\treturn hooks && \"get\" in hooks && (ret = hooks.get( elem, name )) !== null ?\n\t\t\t\tret :\n\t\t\t\telem[ name ];\n\t\t}\n\t},\n\n\tpropHooks: {\n\t\ttabIndex: {\n\t\t\tget: function( elem ) {\n\t\t\t\t// elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set\n\t\t\t\t// http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/\n\t\t\t\t// Use proper attribute retrieval(#12072)\n\t\t\t\tvar tabindex = jQuery.find.attr( elem, \"tabindex\" );\n\n\t\t\t\treturn tabindex ?\n\t\t\t\t\tparseInt( tabindex, 10 ) :\n\t\t\t\t\trfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ?\n\t\t\t\t\t\t0 :\n\t\t\t\t\t\t-1;\n\t\t\t}\n\t\t}\n\t}\n});\n\n// Hooks for boolean attributes\nboolHook = {\n\tset: function( elem, value, name ) {\n\t\tif ( value === false ) {\n\t\t\t// Remove boolean attributes when set to false\n\t\t\tjQuery.removeAttr( elem, name );\n\t\t} else if ( getSetInput && getSetAttribute || !ruseDefault.test( name ) ) {\n\t\t\t// IE<8 needs the *property* name\n\t\t\telem.setAttribute( !getSetAttribute && jQuery.propFix[ name ] || name, name );\n\n\t\t// Use defaultChecked and defaultSelected for oldIE\n\t\t} else {\n\t\t\telem[ jQuery.camelCase( \"default-\" + name ) ] = elem[ name ] = true;\n\t\t}\n\n\t\treturn name;\n\t}\n};\njQuery.each( jQuery.expr.match.bool.source.match( /\\w+/g ), function( i, name ) {\n\tvar getter = jQuery.expr.attrHandle[ name ] || jQuery.find.attr;\n\n\tjQuery.expr.attrHandle[ name ] = getSetInput && getSetAttribute || !ruseDefault.test( name ) ?\n\t\tfunction( elem, name, isXML ) {\n\t\t\tvar fn = jQuery.expr.attrHandle[ name ],\n\t\t\t\tret = isXML ?\n\t\t\t\t\tundefined :\n\t\t\t\t\t/* jshint eqeqeq: false */\n\t\t\t\t\t(jQuery.expr.attrHandle[ name ] = undefined) !=\n\t\t\t\t\t\tgetter( elem, name, isXML ) ?\n\n\t\t\t\t\t\tname.toLowerCase() :\n\t\t\t\t\t\tnull;\n\t\t\tjQuery.expr.attrHandle[ name ] = fn;\n\t\t\treturn ret;\n\t\t} :\n\t\tfunction( elem, name, isXML ) {\n\t\t\treturn isXML ?\n\t\t\t\tundefined :\n\t\t\t\telem[ jQuery.camelCase( \"default-\" + name ) ] ?\n\t\t\t\t\tname.toLowerCase() :\n\t\t\t\t\tnull;\n\t\t};\n});\n\n// fix oldIE attroperties\nif ( !getSetInput || !getSetAttribute ) {\n\tjQuery.attrHooks.value = {\n\t\tset: function( elem, value, name ) {\n\t\t\tif ( jQuery.nodeName( elem, \"input\" ) ) {\n\t\t\t\t// Does not return so that setAttribute is also used\n\t\t\t\telem.defaultValue = value;\n\t\t\t} else {\n\t\t\t\t// Use nodeHook if defined (#1954); otherwise setAttribute is fine\n\t\t\t\treturn nodeHook && nodeHook.set( elem, value, name );\n\t\t\t}\n\t\t}\n\t};\n}\n\n// IE6/7 do not support getting/setting some attributes with get/setAttribute\nif ( !getSetAttribute ) {\n\n\t// Use this for any attribute in IE6/7\n\t// This fixes almost every IE6/7 issue\n\tnodeHook = {\n\t\tset: function( elem, value, name ) {\n\t\t\t// Set the existing or create a new attribute node\n\t\t\tvar ret = elem.getAttributeNode( name );\n\t\t\tif ( !ret ) {\n\t\t\t\telem.setAttributeNode(\n\t\t\t\t\t(ret = elem.ownerDocument.createAttribute( name ))\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tret.value = value += \"\";\n\n\t\t\t// Break association with cloned elements by also using setAttribute (#9646)\n\t\t\treturn name === \"value\" || value === elem.getAttribute( name ) ?\n\t\t\t\tvalue :\n\t\t\t\tundefined;\n\t\t}\n\t};\n\tjQuery.expr.attrHandle.id = jQuery.expr.attrHandle.name = jQuery.expr.attrHandle.coords =\n\t\t// Some attributes are constructed with empty-string values when not defined\n\t\tfunction( elem, name, isXML ) {\n\t\t\tvar ret;\n\t\t\treturn isXML ?\n\t\t\t\tundefined :\n\t\t\t\t(ret = elem.getAttributeNode( name )) && ret.value !== \"\" ?\n\t\t\t\t\tret.value :\n\t\t\t\t\tnull;\n\t\t};\n\tjQuery.valHooks.button = {\n\t\tget: function( elem, name ) {\n\t\t\tvar ret = elem.getAttributeNode( name );\n\t\t\treturn ret && ret.specified ?\n\t\t\t\tret.value :\n\t\t\t\tundefined;\n\t\t},\n\t\tset: nodeHook.set\n\t};\n\n\t// Set contenteditable to false on removals(#10429)\n\t// Setting to empty string throws an error as an invalid value\n\tjQuery.attrHooks.contenteditable = {\n\t\tset: function( elem, value, name ) {\n\t\t\tnodeHook.set( elem, value === \"\" ? false : value, name );\n\t\t}\n\t};\n\n\t// Set width and height to auto instead of 0 on empty string( Bug #8150 )\n\t// This is for removals\n\tjQuery.each([ \"width\", \"height\" ], function( i, name ) {\n\t\tjQuery.attrHooks[ name ] = {\n\t\t\tset: function( elem, value ) {\n\t\t\t\tif ( value === \"\" ) {\n\t\t\t\t\telem.setAttribute( name, \"auto\" );\n\t\t\t\t\treturn value;\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t});\n}\n\n\n// Some attributes require a special call on IE\n// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx\nif ( !jQuery.support.hrefNormalized ) {\n\t// href/src property should get the full normalized URL (#10299/#12915)\n\tjQuery.each([ \"href\", \"src\" ], function( i, name ) {\n\t\tjQuery.propHooks[ name ] = {\n\t\t\tget: function( elem ) {\n\t\t\t\treturn elem.getAttribute( name, 4 );\n\t\t\t}\n\t\t};\n\t});\n}\n\nif ( !jQuery.support.style ) {\n\tjQuery.attrHooks.style = {\n\t\tget: function( elem ) {\n\t\t\t// Return undefined in the case of empty string\n\t\t\t// Note: IE uppercases css property names, but if we were to .toLowerCase()\n\t\t\t// .cssText, that would destroy case sensitivity in URL's, like in \"background\"\n\t\t\treturn elem.style.cssText || undefined;\n\t\t},\n\t\tset: function( elem, value ) {\n\t\t\treturn ( elem.style.cssText = value + \"\" );\n\t\t}\n\t};\n}\n\n// Safari mis-reports the default selected property of an option\n// Accessing the parent's selectedIndex property fixes it\nif ( !jQuery.support.optSelected ) {\n\tjQuery.propHooks.selected = {\n\t\tget: function( elem ) {\n\t\t\tvar parent = elem.parentNode;\n\n\t\t\tif ( parent ) {\n\t\t\t\tparent.selectedIndex;\n\n\t\t\t\t// Make sure that it also works with optgroups, see #5701\n\t\t\t\tif ( parent.parentNode ) {\n\t\t\t\t\tparent.parentNode.selectedIndex;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn null;\n\t\t}\n\t};\n}\n\njQuery.each([\n\t\"tabIndex\",\n\t\"readOnly\",\n\t\"maxLength\",\n\t\"cellSpacing\",\n\t\"cellPadding\",\n\t\"rowSpan\",\n\t\"colSpan\",\n\t\"useMap\",\n\t\"frameBorder\",\n\t\"contentEditable\"\n], function() {\n\tjQuery.propFix[ this.toLowerCase() ] = this;\n});\n\n// IE6/7 call enctype encoding\nif ( !jQuery.support.enctype ) {\n\tjQuery.propFix.enctype = \"encoding\";\n}\n\n// Radios and checkboxes getter/setter\njQuery.each([ \"radio\", \"checkbox\" ], function() {\n\tjQuery.valHooks[ this ] = {\n\t\tset: function( elem, value ) {\n\t\t\tif ( jQuery.isArray( value ) ) {\n\t\t\t\treturn ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 );\n\t\t\t}\n\t\t}\n\t};\n\tif ( !jQuery.support.checkOn ) {\n\t\tjQuery.valHooks[ this ].get = function( elem ) {\n\t\t\t// Support: Webkit\n\t\t\t// \"\" is returned instead of \"on\" if a value isn't specified\n\t\t\treturn elem.getAttribute(\"value\") === null ? \"on\" : elem.value;\n\t\t};\n\t}\n});\nvar rformElems = /^(?:input|select|textarea)$/i,\n\trkeyEvent = /^key/,\n\trmouseEvent = /^(?:mouse|contextmenu)|click/,\n\trfocusMorph = /^(?:focusinfocus|focusoutblur)$/,\n\trtypenamespace = /^([^.]*)(?:\\.(.+)|)$/;\n\nfunction returnTrue() {\n\treturn true;\n}\n\nfunction returnFalse() {\n\treturn false;\n}\n\nfunction safeActiveElement() {\n\ttry {\n\t\treturn document.activeElement;\n\t} catch ( err ) { }\n}\n\n/*\n * Helper functions for managing events -- not part of the public interface.\n * Props to Dean Edwards' addEvent library for many of the ideas.\n */\njQuery.event = {\n\n\tglobal: {},\n\n\tadd: function( elem, types, handler, data, selector ) {\n\t\tvar tmp, events, t, handleObjIn,\n\t\t\tspecial, eventHandle, handleObj,\n\t\t\thandlers, type, namespaces, origType,\n\t\t\telemData = jQuery._data( elem );\n\n\t\t// Don't attach events to noData or text/comment nodes (but allow plain objects)\n\t\tif ( !elemData ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Caller can pass in an object of custom data in lieu of the handler\n\t\tif ( handler.handler ) {\n\t\t\thandleObjIn = handler;\n\t\t\thandler = handleObjIn.handler;\n\t\t\tselector = handleObjIn.selector;\n\t\t}\n\n\t\t// Make sure that the handler has a unique ID, used to find/remove it later\n\t\tif ( !handler.guid ) {\n\t\t\thandler.guid = jQuery.guid++;\n\t\t}\n\n\t\t// Init the element's event structure and main handler, if this is the first\n\t\tif ( !(events = elemData.events) ) {\n\t\t\tevents = elemData.events = {};\n\t\t}\n\t\tif ( !(eventHandle = elemData.handle) ) {\n\t\t\teventHandle = elemData.handle = function( e ) {\n\t\t\t\t// Discard the second event of a jQuery.event.trigger() and\n\t\t\t\t// when an event is called after a page has unloaded\n\t\t\t\treturn typeof jQuery !== core_strundefined && (!e || jQuery.event.triggered !== e.type) ?\n\t\t\t\t\tjQuery.event.dispatch.apply( eventHandle.elem, arguments ) :\n\t\t\t\t\tundefined;\n\t\t\t};\n\t\t\t// Add elem as a property of the handle fn to prevent a memory leak with IE non-native events\n\t\t\teventHandle.elem = elem;\n\t\t}\n\n\t\t// Handle multiple events separated by a space\n\t\ttypes = ( types || \"\" ).match( core_rnotwhite ) || [\"\"];\n\t\tt = types.length;\n\t\twhile ( t-- ) {\n\t\t\ttmp = rtypenamespace.exec( types[t] ) || [];\n\t\t\ttype = origType = tmp[1];\n\t\t\tnamespaces = ( tmp[2] || \"\" ).split( \".\" ).sort();\n\n\t\t\t// There *must* be a type, no attaching namespace-only handlers\n\t\t\tif ( !type ) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// If event changes its type, use the special event handlers for the changed type\n\t\t\tspecial = jQuery.event.special[ type ] || {};\n\n\t\t\t// If selector defined, determine special event api type, otherwise given type\n\t\t\ttype = ( selector ? special.delegateType : special.bindType ) || type;\n\n\t\t\t// Update special based on newly reset type\n\t\t\tspecial = jQuery.event.special[ type ] || {};\n\n\t\t\t// handleObj is passed to all event handlers\n\t\t\thandleObj = jQuery.extend({\n\t\t\t\ttype: type,\n\t\t\t\torigType: origType,\n\t\t\t\tdata: data,\n\t\t\t\thandler: handler,\n\t\t\t\tguid: handler.guid,\n\t\t\t\tselector: selector,\n\t\t\t\tneedsContext: selector && jQuery.expr.match.needsContext.test( selector ),\n\t\t\t\tnamespace: namespaces.join(\".\")\n\t\t\t}, handleObjIn );\n\n\t\t\t// Init the event handler queue if we're the first\n\t\t\tif ( !(handlers = events[ type ]) ) {\n\t\t\t\thandlers = events[ type ] = [];\n\t\t\t\thandlers.delegateCount = 0;\n\n\t\t\t\t// Only use addEventListener/attachEvent if the special events handler returns false\n\t\t\t\tif ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {\n\t\t\t\t\t// Bind the global event handler to the element\n\t\t\t\t\tif ( elem.addEventListener ) {\n\t\t\t\t\t\telem.addEventListener( type, eventHandle, false );\n\n\t\t\t\t\t} else if ( elem.attachEvent ) {\n\t\t\t\t\t\telem.attachEvent( \"on\" + type, eventHandle );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif ( special.add ) {\n\t\t\t\tspecial.add.call( elem, handleObj );\n\n\t\t\t\tif ( !handleObj.handler.guid ) {\n\t\t\t\t\thandleObj.handler.guid = handler.guid;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Add to the element's handler list, delegates in front\n\t\t\tif ( selector ) {\n\t\t\t\thandlers.splice( handlers.delegateCount++, 0, handleObj );\n\t\t\t} else {\n\t\t\t\thandlers.push( handleObj );\n\t\t\t}\n\n\t\t\t// Keep track of which events have ever been used, for event optimization\n\t\t\tjQuery.event.global[ type ] = true;\n\t\t}\n\n\t\t// Nullify elem to prevent memory leaks in IE\n\t\telem = null;\n\t},\n\n\t// Detach an event or set of events from an element\n\tremove: function( elem, types, handler, selector, mappedTypes ) {\n\t\tvar j, handleObj, tmp,\n\t\t\torigCount, t, events,\n\t\t\tspecial, handlers, type,\n\t\t\tnamespaces, origType,\n\t\t\telemData = jQuery.hasData( elem ) && jQuery._data( elem );\n\n\t\tif ( !elemData || !(events = elemData.events) ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Once for each type.namespace in types; type may be omitted\n\t\ttypes = ( types || \"\" ).match( core_rnotwhite ) || [\"\"];\n\t\tt = types.length;\n\t\twhile ( t-- ) {\n\t\t\ttmp = rtypenamespace.exec( types[t] ) || [];\n\t\t\ttype = origType = tmp[1];\n\t\t\tnamespaces = ( tmp[2] || \"\" ).split( \".\" ).sort();\n\n\t\t\t// Unbind all events (on this namespace, if provided) for the element\n\t\t\tif ( !type ) {\n\t\t\t\tfor ( type in events ) {\n\t\t\t\t\tjQuery.event.remove( elem, type + types[ t ], handler, selector, true );\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tspecial = jQuery.event.special[ type ] || {};\n\t\t\ttype = ( selector ? special.delegateType : special.bindType ) || type;\n\t\t\thandlers = events[ type ] || [];\n\t\t\ttmp = tmp[2] && new RegExp( \"(^|\\\\.)\" + namespaces.join(\"\\\\.(?:.*\\\\.|)\") + \"(\\\\.|$)\" );\n\n\t\t\t// Remove matching events\n\t\t\torigCount = j = handlers.length;\n\t\t\twhile ( j-- ) {\n\t\t\t\thandleObj = handlers[ j ];\n\n\t\t\t\tif ( ( mappedTypes || origType === handleObj.origType ) &&\n\t\t\t\t\t( !handler || handler.guid === handleObj.guid ) &&\n\t\t\t\t\t( !tmp || tmp.test( handleObj.namespace ) ) &&\n\t\t\t\t\t( !selector || selector === handleObj.selector || selector === \"**\" && handleObj.selector ) ) {\n\t\t\t\t\thandlers.splice( j, 1 );\n\n\t\t\t\t\tif ( handleObj.selector ) {\n\t\t\t\t\t\thandlers.delegateCount--;\n\t\t\t\t\t}\n\t\t\t\t\tif ( special.remove ) {\n\t\t\t\t\t\tspecial.remove.call( elem, handleObj );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Remove generic event handler if we removed something and no more handlers exist\n\t\t\t// (avoids potential for endless recursion during removal of special event handlers)\n\t\t\tif ( origCount && !handlers.length ) {\n\t\t\t\tif ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) {\n\t\t\t\t\tjQuery.removeEvent( elem, type, elemData.handle );\n\t\t\t\t}\n\n\t\t\t\tdelete events[ type ];\n\t\t\t}\n\t\t}\n\n\t\t// Remove the expando if it's no longer used\n\t\tif ( jQuery.isEmptyObject( events ) ) {\n\t\t\tdelete elemData.handle;\n\n\t\t\t// removeData also checks for emptiness and clears the expando if empty\n\t\t\t// so use it instead of delete\n\t\t\tjQuery._removeData( elem, \"events\" );\n\t\t}\n\t},\n\n\ttrigger: function( event, data, elem, onlyHandlers ) {\n\t\tvar handle, ontype, cur,\n\t\t\tbubbleType, special, tmp, i,\n\t\t\teventPath = [ elem || document ],\n\t\t\ttype = core_hasOwn.call( event, \"type\" ) ? event.type : event,\n\t\t\tnamespaces = core_hasOwn.call( event, \"namespace\" ) ? event.namespace.split(\".\") : [];\n\n\t\tcur = tmp = elem = elem || document;\n\n\t\t// Don't do events on text and comment nodes\n\t\tif ( elem.nodeType === 3 || elem.nodeType === 8 ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// focus/blur morphs to focusin/out; ensure we're not firing them right now\n\t\tif ( rfocusMorph.test( type + jQuery.event.triggered ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\tif ( type.indexOf(\".\") >= 0 ) {\n\t\t\t// Namespaced trigger; create a regexp to match event type in handle()\n\t\t\tnamespaces = type.split(\".\");\n\t\t\ttype = namespaces.shift();\n\t\t\tnamespaces.sort();\n\t\t}\n\t\tontype = type.indexOf(\":\") < 0 && \"on\" + type;\n\n\t\t// Caller can pass in a jQuery.Event object, Object, or just an event type string\n\t\tevent = event[ jQuery.expando ] ?\n\t\t\tevent :\n\t\t\tnew jQuery.Event( type, typeof event === \"object\" && event );\n\n\t\t// Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true)\n\t\tevent.isTrigger = onlyHandlers ? 2 : 3;\n\t\tevent.namespace = namespaces.join(\".\");\n\t\tevent.namespace_re = event.namespace ?\n\t\t\tnew RegExp( \"(^|\\\\.)\" + namespaces.join(\"\\\\.(?:.*\\\\.|)\") + \"(\\\\.|$)\" ) :\n\t\t\tnull;\n\n\t\t// Clean up the event in case it is being reused\n\t\tevent.result = undefined;\n\t\tif ( !event.target ) {\n\t\t\tevent.target = elem;\n\t\t}\n\n\t\t// Clone any incoming data and prepend the event, creating the handler arg list\n\t\tdata = data == null ?\n\t\t\t[ event ] :\n\t\t\tjQuery.makeArray( data, [ event ] );\n\n\t\t// Allow special events to draw outside the lines\n\t\tspecial = jQuery.event.special[ type ] || {};\n\t\tif ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Determine event propagation path in advance, per W3C events spec (#9951)\n\t\t// Bubble up to document, then to window; watch for a global ownerDocument var (#9724)\n\t\tif ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {\n\n\t\t\tbubbleType = special.delegateType || type;\n\t\t\tif ( !rfocusMorph.test( bubbleType + type ) ) {\n\t\t\t\tcur = cur.parentNode;\n\t\t\t}\n\t\t\tfor ( ; cur; cur = cur.parentNode ) {\n\t\t\t\teventPath.push( cur );\n\t\t\t\ttmp = cur;\n\t\t\t}\n\n\t\t\t// Only add window if we got to document (e.g., not plain obj or detached DOM)\n\t\t\tif ( tmp === (elem.ownerDocument || document) ) {\n\t\t\t\teventPath.push( tmp.defaultView || tmp.parentWindow || window );\n\t\t\t}\n\t\t}\n\n\t\t// Fire handlers on the event path\n\t\ti = 0;\n\t\twhile ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) {\n\n\t\t\tevent.type = i > 1 ?\n\t\t\t\tbubbleType :\n\t\t\t\tspecial.bindType || type;\n\n\t\t\t// jQuery handler\n\t\t\thandle = ( jQuery._data( cur, \"events\" ) || {} )[ event.type ] && jQuery._data( cur, \"handle\" );\n\t\t\tif ( handle ) {\n\t\t\t\thandle.apply( cur, data );\n\t\t\t}\n\n\t\t\t// Native handler\n\t\t\thandle = ontype && cur[ ontype ];\n\t\t\tif ( handle && jQuery.acceptData( cur ) && handle.apply && handle.apply( cur, data ) === false ) {\n\t\t\t\tevent.preventDefault();\n\t\t\t}\n\t\t}\n\t\tevent.type = type;\n\n\t\t// If nobody prevented the default action, do it now\n\t\tif ( !onlyHandlers && !event.isDefaultPrevented() ) {\n\n\t\t\tif ( (!special._default || special._default.apply( eventPath.pop(), data ) === false) &&\n\t\t\t\tjQuery.acceptData( elem ) ) {\n\n\t\t\t\t// Call a native DOM method on the target with the same name name as the event.\n\t\t\t\t// Can't use an .isFunction() check here because IE6/7 fails that test.\n\t\t\t\t// Don't do default actions on window, that's where global variables be (#6170)\n\t\t\t\tif ( ontype && elem[ type ] && !jQuery.isWindow( elem ) ) {\n\n\t\t\t\t\t// Don't re-trigger an onFOO event when we call its FOO() method\n\t\t\t\t\ttmp = elem[ ontype ];\n\n\t\t\t\t\tif ( tmp ) {\n\t\t\t\t\t\telem[ ontype ] = null;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Prevent re-triggering of the same event, since we already bubbled it above\n\t\t\t\t\tjQuery.event.triggered = type;\n\t\t\t\t\ttry {\n\t\t\t\t\t\telem[ type ]();\n\t\t\t\t\t} catch ( e ) {\n\t\t\t\t\t\t// IE<9 dies on focus/blur to hidden element (#1486,#12518)\n\t\t\t\t\t\t// only reproducible on winXP IE8 native, not IE9 in IE8 mode\n\t\t\t\t\t}\n\t\t\t\t\tjQuery.event.triggered = undefined;\n\n\t\t\t\t\tif ( tmp ) {\n\t\t\t\t\t\telem[ ontype ] = tmp;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn event.result;\n\t},\n\n\tdispatch: function( event ) {\n\n\t\t// Make a writable jQuery.Event from the native event object\n\t\tevent = jQuery.event.fix( event );\n\n\t\tvar i, ret, handleObj, matched, j,\n\t\t\thandlerQueue = [],\n\t\t\targs = core_slice.call( arguments ),\n\t\t\thandlers = ( jQuery._data( this, \"events\" ) || {} )[ event.type ] || [],\n\t\t\tspecial = jQuery.event.special[ event.type ] || {};\n\n\t\t// Use the fix-ed jQuery.Event rather than the (read-only) native event\n\t\targs[0] = event;\n\t\tevent.delegateTarget = this;\n\n\t\t// Call the preDispatch hook for the mapped type, and let it bail if desired\n\t\tif ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Determine handlers\n\t\thandlerQueue = jQuery.event.handlers.call( this, event, handlers );\n\n\t\t// Run delegates first; they may want to stop propagation beneath us\n\t\ti = 0;\n\t\twhile ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) {\n\t\t\tevent.currentTarget = matched.elem;\n\n\t\t\tj = 0;\n\t\t\twhile ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) {\n\n\t\t\t\t// Triggered event must either 1) have no namespace, or\n\t\t\t\t// 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace).\n\t\t\t\tif ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) {\n\n\t\t\t\t\tevent.handleObj = handleObj;\n\t\t\t\t\tevent.data = handleObj.data;\n\n\t\t\t\t\tret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )\n\t\t\t\t\t\t\t.apply( matched.elem, args );\n\n\t\t\t\t\tif ( ret !== undefined ) {\n\t\t\t\t\t\tif ( (event.result = ret) === false ) {\n\t\t\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\t\t\tevent.stopPropagation();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Call the postDispatch hook for the mapped type\n\t\tif ( special.postDispatch ) {\n\t\t\tspecial.postDispatch.call( this, event );\n\t\t}\n\n\t\treturn event.result;\n\t},\n\n\thandlers: function( event, handlers ) {\n\t\tvar sel, handleObj, matches, i,\n\t\t\thandlerQueue = [],\n\t\t\tdelegateCount = handlers.delegateCount,\n\t\t\tcur = event.target;\n\n\t\t// Find delegate handlers\n\t\t// Black-hole SVG <use> instance trees (#13180)\n\t\t// Avoid non-left-click bubbling in Firefox (#3861)\n\t\tif ( delegateCount && cur.nodeType && (!event.button || event.type !== \"click\") ) {\n\n\t\t\t/* jshint eqeqeq: false */\n\t\t\tfor ( ; cur != this; cur = cur.parentNode || this ) {\n\t\t\t\t/* jshint eqeqeq: true */\n\n\t\t\t\t// Don't check non-elements (#13208)\n\t\t\t\t// Don't process clicks on disabled elements (#6911, #8165, #11382, #11764)\n\t\t\t\tif ( cur.nodeType === 1 && (cur.disabled !== true || event.type !== \"click\") ) {\n\t\t\t\t\tmatches = [];\n\t\t\t\t\tfor ( i = 0; i < delegateCount; i++ ) {\n\t\t\t\t\t\thandleObj = handlers[ i ];\n\n\t\t\t\t\t\t// Don't conflict with Object.prototype properties (#13203)\n\t\t\t\t\t\tsel = handleObj.selector + \" \";\n\n\t\t\t\t\t\tif ( matches[ sel ] === undefined ) {\n\t\t\t\t\t\t\tmatches[ sel ] = handleObj.needsContext ?\n\t\t\t\t\t\t\t\tjQuery( sel, this ).index( cur ) >= 0 :\n\t\t\t\t\t\t\t\tjQuery.find( sel, this, null, [ cur ] ).length;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif ( matches[ sel ] ) {\n\t\t\t\t\t\t\tmatches.push( handleObj );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif ( matches.length ) {\n\t\t\t\t\t\thandlerQueue.push({ elem: cur, handlers: matches });\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Add the remaining (directly-bound) handlers\n\t\tif ( delegateCount < handlers.length ) {\n\t\t\thandlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) });\n\t\t}\n\n\t\treturn handlerQueue;\n\t},\n\n\tfix: function( event ) {\n\t\tif ( event[ jQuery.expando ] ) {\n\t\t\treturn event;\n\t\t}\n\n\t\t// Create a writable copy of the event object and normalize some properties\n\t\tvar i, prop, copy,\n\t\t\ttype = event.type,\n\t\t\toriginalEvent = event,\n\t\t\tfixHook = this.fixHooks[ type ];\n\n\t\tif ( !fixHook ) {\n\t\t\tthis.fixHooks[ type ] = fixHook =\n\t\t\t\trmouseEvent.test( type ) ? this.mouseHooks :\n\t\t\t\trkeyEvent.test( type ) ? this.keyHooks :\n\t\t\t\t{};\n\t\t}\n\t\tcopy = fixHook.props ? this.props.concat( fixHook.props ) : this.props;\n\n\t\tevent = new jQuery.Event( originalEvent );\n\n\t\ti = copy.length;\n\t\twhile ( i-- ) {\n\t\t\tprop = copy[ i ];\n\t\t\tevent[ prop ] = originalEvent[ prop ];\n\t\t}\n\n\t\t// Support: IE<9\n\t\t// Fix target property (#1925)\n\t\tif ( !event.target ) {\n\t\t\tevent.target = originalEvent.srcElement || document;\n\t\t}\n\n\t\t// Support: Chrome 23+, Safari?\n\t\t// Target should not be a text node (#504, #13143)\n\t\tif ( event.target.nodeType === 3 ) {\n\t\t\tevent.target = event.target.parentNode;\n\t\t}\n\n\t\t// Support: IE<9\n\t\t// For mouse/key events, metaKey==false if it's undefined (#3368, #11328)\n\t\tevent.metaKey = !!event.metaKey;\n\n\t\treturn fixHook.filter ? fixHook.filter( event, originalEvent ) : event;\n\t},\n\n\t// Includes some event props shared by KeyEvent and MouseEvent\n\tprops: \"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which\".split(\" \"),\n\n\tfixHooks: {},\n\n\tkeyHooks: {\n\t\tprops: \"char charCode key keyCode\".split(\" \"),\n\t\tfilter: function( event, original ) {\n\n\t\t\t// Add which for key events\n\t\t\tif ( event.which == null ) {\n\t\t\t\tevent.which = original.charCode != null ? original.charCode : original.keyCode;\n\t\t\t}\n\n\t\t\treturn event;\n\t\t}\n\t},\n\n\tmouseHooks: {\n\t\tprops: \"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement\".split(\" \"),\n\t\tfilter: function( event, original ) {\n\t\t\tvar body, eventDoc, doc,\n\t\t\t\tbutton = original.button,\n\t\t\t\tfromElement = original.fromElement;\n\n\t\t\t// Calculate pageX/Y if missing and clientX/Y available\n\t\t\tif ( event.pageX == null && original.clientX != null ) {\n\t\t\t\teventDoc = event.target.ownerDocument || document;\n\t\t\t\tdoc = eventDoc.documentElement;\n\t\t\t\tbody = eventDoc.body;\n\n\t\t\t\tevent.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 );\n\t\t\t\tevent.pageY = original.clientY + ( doc && doc.scrollTop  || body && body.scrollTop  || 0 ) - ( doc && doc.clientTop  || body && body.clientTop  || 0 );\n\t\t\t}\n\n\t\t\t// Add relatedTarget, if necessary\n\t\t\tif ( !event.relatedTarget && fromElement ) {\n\t\t\t\tevent.relatedTarget = fromElement === event.target ? original.toElement : fromElement;\n\t\t\t}\n\n\t\t\t// Add which for click: 1 === left; 2 === middle; 3 === right\n\t\t\t// Note: button is not normalized, so don't use it\n\t\t\tif ( !event.which && button !== undefined ) {\n\t\t\t\tevent.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) );\n\t\t\t}\n\n\t\t\treturn event;\n\t\t}\n\t},\n\n\tspecial: {\n\t\tload: {\n\t\t\t// Prevent triggered image.load events from bubbling to window.load\n\t\t\tnoBubble: true\n\t\t},\n\t\tfocus: {\n\t\t\t// Fire native event if possible so blur/focus sequence is correct\n\t\t\ttrigger: function() {\n\t\t\t\tif ( this !== safeActiveElement() && this.focus ) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tthis.focus();\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t} catch ( e ) {\n\t\t\t\t\t\t// Support: IE<9\n\t\t\t\t\t\t// If we error on focus to hidden element (#1486, #12518),\n\t\t\t\t\t\t// let .trigger() run the handlers\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t\tdelegateType: \"focusin\"\n\t\t},\n\t\tblur: {\n\t\t\ttrigger: function() {\n\t\t\t\tif ( this === safeActiveElement() && this.blur ) {\n\t\t\t\t\tthis.blur();\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t},\n\t\t\tdelegateType: \"focusout\"\n\t\t},\n\t\tclick: {\n\t\t\t// For checkbox, fire native event so checked state will be right\n\t\t\ttrigger: function() {\n\t\t\t\tif ( jQuery.nodeName( this, \"input\" ) && this.type === \"checkbox\" && this.click ) {\n\t\t\t\t\tthis.click();\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t},\n\n\t\t\t// For cross-browser consistency, don't fire native .click() on links\n\t\t\t_default: function( event ) {\n\t\t\t\treturn jQuery.nodeName( event.target, \"a\" );\n\t\t\t}\n\t\t},\n\n\t\tbeforeunload: {\n\t\t\tpostDispatch: function( event ) {\n\n\t\t\t\t// Even when returnValue equals to undefined Firefox will still show alert\n\t\t\t\tif ( event.result !== undefined ) {\n\t\t\t\t\tevent.originalEvent.returnValue = event.result;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\n\tsimulate: function( type, elem, event, bubble ) {\n\t\t// Piggyback on a donor event to simulate a different one.\n\t\t// Fake originalEvent to avoid donor's stopPropagation, but if the\n\t\t// simulated event prevents default then we do the same on the donor.\n\t\tvar e = jQuery.extend(\n\t\t\tnew jQuery.Event(),\n\t\t\tevent,\n\t\t\t{\n\t\t\t\ttype: type,\n\t\t\t\tisSimulated: true,\n\t\t\t\toriginalEvent: {}\n\t\t\t}\n\t\t);\n\t\tif ( bubble ) {\n\t\t\tjQuery.event.trigger( e, null, elem );\n\t\t} else {\n\t\t\tjQuery.event.dispatch.call( elem, e );\n\t\t}\n\t\tif ( e.isDefaultPrevented() ) {\n\t\t\tevent.preventDefault();\n\t\t}\n\t}\n};\n\njQuery.removeEvent = document.removeEventListener ?\n\tfunction( elem, type, handle ) {\n\t\tif ( elem.removeEventListener ) {\n\t\t\telem.removeEventListener( type, handle, false );\n\t\t}\n\t} :\n\tfunction( elem, type, handle ) {\n\t\tvar name = \"on\" + type;\n\n\t\tif ( elem.detachEvent ) {\n\n\t\t\t// #8545, #7054, preventing memory leaks for custom events in IE6-8\n\t\t\t// detachEvent needed property on element, by name of that event, to properly expose it to GC\n\t\t\tif ( typeof elem[ name ] === core_strundefined ) {\n\t\t\t\telem[ name ] = null;\n\t\t\t}\n\n\t\t\telem.detachEvent( name, handle );\n\t\t}\n\t};\n\njQuery.Event = function( src, props ) {\n\t// Allow instantiation without the 'new' keyword\n\tif ( !(this instanceof jQuery.Event) ) {\n\t\treturn new jQuery.Event( src, props );\n\t}\n\n\t// Event object\n\tif ( src && src.type ) {\n\t\tthis.originalEvent = src;\n\t\tthis.type = src.type;\n\n\t\t// Events bubbling up the document may have been marked as prevented\n\t\t// by a handler lower down the tree; reflect the correct value.\n\t\tthis.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false ||\n\t\t\tsrc.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse;\n\n\t// Event type\n\t} else {\n\t\tthis.type = src;\n\t}\n\n\t// Put explicitly provided properties onto the event object\n\tif ( props ) {\n\t\tjQuery.extend( this, props );\n\t}\n\n\t// Create a timestamp if incoming event doesn't have one\n\tthis.timeStamp = src && src.timeStamp || jQuery.now();\n\n\t// Mark it as fixed\n\tthis[ jQuery.expando ] = true;\n};\n\n// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding\n// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html\njQuery.Event.prototype = {\n\tisDefaultPrevented: returnFalse,\n\tisPropagationStopped: returnFalse,\n\tisImmediatePropagationStopped: returnFalse,\n\n\tpreventDefault: function() {\n\t\tvar e = this.originalEvent;\n\n\t\tthis.isDefaultPrevented = returnTrue;\n\t\tif ( !e ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// If preventDefault exists, run it on the original event\n\t\tif ( e.preventDefault ) {\n\t\t\te.preventDefault();\n\n\t\t// Support: IE\n\t\t// Otherwise set the returnValue property of the original event to false\n\t\t} else {\n\t\t\te.returnValue = false;\n\t\t}\n\t},\n\tstopPropagation: function() {\n\t\tvar e = this.originalEvent;\n\n\t\tthis.isPropagationStopped = returnTrue;\n\t\tif ( !e ) {\n\t\t\treturn;\n\t\t}\n\t\t// If stopPropagation exists, run it on the original event\n\t\tif ( e.stopPropagation ) {\n\t\t\te.stopPropagation();\n\t\t}\n\n\t\t// Support: IE\n\t\t// Set the cancelBubble property of the original event to true\n\t\te.cancelBubble = true;\n\t},\n\tstopImmediatePropagation: function() {\n\t\tthis.isImmediatePropagationStopped = returnTrue;\n\t\tthis.stopPropagation();\n\t}\n};\n\n// Create mouseenter/leave events using mouseover/out and event-time checks\njQuery.each({\n\tmouseenter: \"mouseover\",\n\tmouseleave: \"mouseout\"\n}, function( orig, fix ) {\n\tjQuery.event.special[ orig ] = {\n\t\tdelegateType: fix,\n\t\tbindType: fix,\n\n\t\thandle: function( event ) {\n\t\t\tvar ret,\n\t\t\t\ttarget = this,\n\t\t\t\trelated = event.relatedTarget,\n\t\t\t\thandleObj = event.handleObj;\n\n\t\t\t// For mousenter/leave call the handler if related is outside the target.\n\t\t\t// NB: No relatedTarget if the mouse left/entered the browser window\n\t\t\tif ( !related || (related !== target && !jQuery.contains( target, related )) ) {\n\t\t\t\tevent.type = handleObj.origType;\n\t\t\t\tret = handleObj.handler.apply( this, arguments );\n\t\t\t\tevent.type = fix;\n\t\t\t}\n\t\t\treturn ret;\n\t\t}\n\t};\n});\n\n// IE submit delegation\nif ( !jQuery.support.submitBubbles ) {\n\n\tjQuery.event.special.submit = {\n\t\tsetup: function() {\n\t\t\t// Only need this for delegated form submit events\n\t\t\tif ( jQuery.nodeName( this, \"form\" ) ) {\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\t// Lazy-add a submit handler when a descendant form may potentially be submitted\n\t\t\tjQuery.event.add( this, \"click._submit keypress._submit\", function( e ) {\n\t\t\t\t// Node name check avoids a VML-related crash in IE (#9807)\n\t\t\t\tvar elem = e.target,\n\t\t\t\t\tform = jQuery.nodeName( elem, \"input\" ) || jQuery.nodeName( elem, \"button\" ) ? elem.form : undefined;\n\t\t\t\tif ( form && !jQuery._data( form, \"submitBubbles\" ) ) {\n\t\t\t\t\tjQuery.event.add( form, \"submit._submit\", function( event ) {\n\t\t\t\t\t\tevent._submit_bubble = true;\n\t\t\t\t\t});\n\t\t\t\t\tjQuery._data( form, \"submitBubbles\", true );\n\t\t\t\t}\n\t\t\t});\n\t\t\t// return undefined since we don't need an event listener\n\t\t},\n\n\t\tpostDispatch: function( event ) {\n\t\t\t// If form was submitted by the user, bubble the event up the tree\n\t\t\tif ( event._submit_bubble ) {\n\t\t\t\tdelete event._submit_bubble;\n\t\t\t\tif ( this.parentNode && !event.isTrigger ) {\n\t\t\t\t\tjQuery.event.simulate( \"submit\", this.parentNode, event, true );\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\tteardown: function() {\n\t\t\t// Only need this for delegated form submit events\n\t\t\tif ( jQuery.nodeName( this, \"form\" ) ) {\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\t// Remove delegated handlers; cleanData eventually reaps submit handlers attached above\n\t\t\tjQuery.event.remove( this, \"._submit\" );\n\t\t}\n\t};\n}\n\n// IE change delegation and checkbox/radio fix\nif ( !jQuery.support.changeBubbles ) {\n\n\tjQuery.event.special.change = {\n\n\t\tsetup: function() {\n\n\t\t\tif ( rformElems.test( this.nodeName ) ) {\n\t\t\t\t// IE doesn't fire change on a check/radio until blur; trigger it on click\n\t\t\t\t// after a propertychange. Eat the blur-change in special.change.handle.\n\t\t\t\t// This still fires onchange a second time for check/radio after blur.\n\t\t\t\tif ( this.type === \"checkbox\" || this.type === \"radio\" ) {\n\t\t\t\t\tjQuery.event.add( this, \"propertychange._change\", function( event ) {\n\t\t\t\t\t\tif ( event.originalEvent.propertyName === \"checked\" ) {\n\t\t\t\t\t\t\tthis._just_changed = true;\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t\tjQuery.event.add( this, \"click._change\", function( event ) {\n\t\t\t\t\t\tif ( this._just_changed && !event.isTrigger ) {\n\t\t\t\t\t\t\tthis._just_changed = false;\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Allow triggered, simulated change events (#11500)\n\t\t\t\t\t\tjQuery.event.simulate( \"change\", this, event, true );\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\t// Delegated event; lazy-add a change handler on descendant inputs\n\t\t\tjQuery.event.add( this, \"beforeactivate._change\", function( e ) {\n\t\t\t\tvar elem = e.target;\n\n\t\t\t\tif ( rformElems.test( elem.nodeName ) && !jQuery._data( elem, \"changeBubbles\" ) ) {\n\t\t\t\t\tjQuery.event.add( elem, \"change._change\", function( event ) {\n\t\t\t\t\t\tif ( this.parentNode && !event.isSimulated && !event.isTrigger ) {\n\t\t\t\t\t\t\tjQuery.event.simulate( \"change\", this.parentNode, event, true );\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t\tjQuery._data( elem, \"changeBubbles\", true );\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\n\t\thandle: function( event ) {\n\t\t\tvar elem = event.target;\n\n\t\t\t// Swallow native change events from checkbox/radio, we already triggered them above\n\t\t\tif ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== \"radio\" && elem.type !== \"checkbox\") ) {\n\t\t\t\treturn event.handleObj.handler.apply( this, arguments );\n\t\t\t}\n\t\t},\n\n\t\tteardown: function() {\n\t\t\tjQuery.event.remove( this, \"._change\" );\n\n\t\t\treturn !rformElems.test( this.nodeName );\n\t\t}\n\t};\n}\n\n// Create \"bubbling\" focus and blur events\nif ( !jQuery.support.focusinBubbles ) {\n\tjQuery.each({ focus: \"focusin\", blur: \"focusout\" }, function( orig, fix ) {\n\n\t\t// Attach a single capturing handler while someone wants focusin/focusout\n\t\tvar attaches = 0,\n\t\t\thandler = function( event ) {\n\t\t\t\tjQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true );\n\t\t\t};\n\n\t\tjQuery.event.special[ fix ] = {\n\t\t\tsetup: function() {\n\t\t\t\tif ( attaches++ === 0 ) {\n\t\t\t\t\tdocument.addEventListener( orig, handler, true );\n\t\t\t\t}\n\t\t\t},\n\t\t\tteardown: function() {\n\t\t\t\tif ( --attaches === 0 ) {\n\t\t\t\t\tdocument.removeEventListener( orig, handler, true );\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t});\n}\n\njQuery.fn.extend({\n\n\ton: function( types, selector, data, fn, /*INTERNAL*/ one ) {\n\t\tvar type, origFn;\n\n\t\t// Types can be a map of types/handlers\n\t\tif ( typeof types === \"object\" ) {\n\t\t\t// ( types-Object, selector, data )\n\t\t\tif ( typeof selector !== \"string\" ) {\n\t\t\t\t// ( types-Object, data )\n\t\t\t\tdata = data || selector;\n\t\t\t\tselector = undefined;\n\t\t\t}\n\t\t\tfor ( type in types ) {\n\t\t\t\tthis.on( type, selector, data, types[ type ], one );\n\t\t\t}\n\t\t\treturn this;\n\t\t}\n\n\t\tif ( data == null && fn == null ) {\n\t\t\t// ( types, fn )\n\t\t\tfn = selector;\n\t\t\tdata = selector = undefined;\n\t\t} else if ( fn == null ) {\n\t\t\tif ( typeof selector === \"string\" ) {\n\t\t\t\t// ( types, selector, fn )\n\t\t\t\tfn = data;\n\t\t\t\tdata = undefined;\n\t\t\t} else {\n\t\t\t\t// ( types, data, fn )\n\t\t\t\tfn = data;\n\t\t\t\tdata = selector;\n\t\t\t\tselector = undefined;\n\t\t\t}\n\t\t}\n\t\tif ( fn === false ) {\n\t\t\tfn = returnFalse;\n\t\t} else if ( !fn ) {\n\t\t\treturn this;\n\t\t}\n\n\t\tif ( one === 1 ) {\n\t\t\torigFn = fn;\n\t\t\tfn = function( event ) {\n\t\t\t\t// Can use an empty set, since event contains the info\n\t\t\t\tjQuery().off( event );\n\t\t\t\treturn origFn.apply( this, arguments );\n\t\t\t};\n\t\t\t// Use same guid so caller can remove using origFn\n\t\t\tfn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );\n\t\t}\n\t\treturn this.each( function() {\n\t\t\tjQuery.event.add( this, types, fn, data, selector );\n\t\t});\n\t},\n\tone: function( types, selector, data, fn ) {\n\t\treturn this.on( types, selector, data, fn, 1 );\n\t},\n\toff: function( types, selector, fn ) {\n\t\tvar handleObj, type;\n\t\tif ( types && types.preventDefault && types.handleObj ) {\n\t\t\t// ( event )  dispatched jQuery.Event\n\t\t\thandleObj = types.handleObj;\n\t\t\tjQuery( types.delegateTarget ).off(\n\t\t\t\thandleObj.namespace ? handleObj.origType + \".\" + handleObj.namespace : handleObj.origType,\n\t\t\t\thandleObj.selector,\n\t\t\t\thandleObj.handler\n\t\t\t);\n\t\t\treturn this;\n\t\t}\n\t\tif ( typeof types === \"object\" ) {\n\t\t\t// ( types-object [, selector] )\n\t\t\tfor ( type in types ) {\n\t\t\t\tthis.off( type, selector, types[ type ] );\n\t\t\t}\n\t\t\treturn this;\n\t\t}\n\t\tif ( selector === false || typeof selector === \"function\" ) {\n\t\t\t// ( types [, fn] )\n\t\t\tfn = selector;\n\t\t\tselector = undefined;\n\t\t}\n\t\tif ( fn === false ) {\n\t\t\tfn = returnFalse;\n\t\t}\n\t\treturn this.each(function() {\n\t\t\tjQuery.event.remove( this, types, fn, selector );\n\t\t});\n\t},\n\n\ttrigger: function( type, data ) {\n\t\treturn this.each(function() {\n\t\t\tjQuery.event.trigger( type, data, this );\n\t\t});\n\t},\n\ttriggerHandler: function( type, data ) {\n\t\tvar elem = this[0];\n\t\tif ( elem ) {\n\t\t\treturn jQuery.event.trigger( type, data, elem, true );\n\t\t}\n\t}\n});\nvar isSimple = /^.[^:#\\[\\.,]*$/,\n\trparentsprev = /^(?:parents|prev(?:Until|All))/,\n\trneedsContext = jQuery.expr.match.needsContext,\n\t// methods guaranteed to produce a unique set when starting from a unique set\n\tguaranteedUnique = {\n\t\tchildren: true,\n\t\tcontents: true,\n\t\tnext: true,\n\t\tprev: true\n\t};\n\njQuery.fn.extend({\n\tfind: function( selector ) {\n\t\tvar i,\n\t\t\tret = [],\n\t\t\tself = this,\n\t\t\tlen = self.length;\n\n\t\tif ( typeof selector !== \"string\" ) {\n\t\t\treturn this.pushStack( jQuery( selector ).filter(function() {\n\t\t\t\tfor ( i = 0; i < len; i++ ) {\n\t\t\t\t\tif ( jQuery.contains( self[ i ], this ) ) {\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}) );\n\t\t}\n\n\t\tfor ( i = 0; i < len; i++ ) {\n\t\t\tjQuery.find( selector, self[ i ], ret );\n\t\t}\n\n\t\t// Needed because $( selector, context ) becomes $( context ).find( selector )\n\t\tret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret );\n\t\tret.selector = this.selector ? this.selector + \" \" + selector : selector;\n\t\treturn ret;\n\t},\n\n\thas: function( target ) {\n\t\tvar i,\n\t\t\ttargets = jQuery( target, this ),\n\t\t\tlen = targets.length;\n\n\t\treturn this.filter(function() {\n\t\t\tfor ( i = 0; i < len; i++ ) {\n\t\t\t\tif ( jQuery.contains( this, targets[i] ) ) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t},\n\n\tnot: function( selector ) {\n\t\treturn this.pushStack( winnow(this, selector || [], true) );\n\t},\n\n\tfilter: function( selector ) {\n\t\treturn this.pushStack( winnow(this, selector || [], false) );\n\t},\n\n\tis: function( selector ) {\n\t\treturn !!winnow(\n\t\t\tthis,\n\n\t\t\t// If this is a positional/relative selector, check membership in the returned set\n\t\t\t// so $(\"p:first\").is(\"p:last\") won't return true for a doc with two \"p\".\n\t\t\ttypeof selector === \"string\" && rneedsContext.test( selector ) ?\n\t\t\t\tjQuery( selector ) :\n\t\t\t\tselector || [],\n\t\t\tfalse\n\t\t).length;\n\t},\n\n\tclosest: function( selectors, context ) {\n\t\tvar cur,\n\t\t\ti = 0,\n\t\t\tl = this.length,\n\t\t\tret = [],\n\t\t\tpos = rneedsContext.test( selectors ) || typeof selectors !== \"string\" ?\n\t\t\t\tjQuery( selectors, context || this.context ) :\n\t\t\t\t0;\n\n\t\tfor ( ; i < l; i++ ) {\n\t\t\tfor ( cur = this[i]; cur && cur !== context; cur = cur.parentNode ) {\n\t\t\t\t// Always skip document fragments\n\t\t\t\tif ( cur.nodeType < 11 && (pos ?\n\t\t\t\t\tpos.index(cur) > -1 :\n\n\t\t\t\t\t// Don't pass non-elements to Sizzle\n\t\t\t\t\tcur.nodeType === 1 &&\n\t\t\t\t\t\tjQuery.find.matchesSelector(cur, selectors)) ) {\n\n\t\t\t\t\tcur = ret.push( cur );\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn this.pushStack( ret.length > 1 ? jQuery.unique( ret ) : ret );\n\t},\n\n\t// Determine the position of an element within\n\t// the matched set of elements\n\tindex: function( elem ) {\n\n\t\t// No argument, return index in parent\n\t\tif ( !elem ) {\n\t\t\treturn ( this[0] && this[0].parentNode ) ? this.first().prevAll().length : -1;\n\t\t}\n\n\t\t// index in selector\n\t\tif ( typeof elem === \"string\" ) {\n\t\t\treturn jQuery.inArray( this[0], jQuery( elem ) );\n\t\t}\n\n\t\t// Locate the position of the desired element\n\t\treturn jQuery.inArray(\n\t\t\t// If it receives a jQuery object, the first element is used\n\t\t\telem.jquery ? elem[0] : elem, this );\n\t},\n\n\tadd: function( selector, context ) {\n\t\tvar set = typeof selector === \"string\" ?\n\t\t\t\tjQuery( selector, context ) :\n\t\t\t\tjQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ),\n\t\t\tall = jQuery.merge( this.get(), set );\n\n\t\treturn this.pushStack( jQuery.unique(all) );\n\t},\n\n\taddBack: function( selector ) {\n\t\treturn this.add( selector == null ?\n\t\t\tthis.prevObject : this.prevObject.filter(selector)\n\t\t);\n\t}\n});\n\nfunction sibling( cur, dir ) {\n\tdo {\n\t\tcur = cur[ dir ];\n\t} while ( cur && cur.nodeType !== 1 );\n\n\treturn cur;\n}\n\njQuery.each({\n\tparent: function( elem ) {\n\t\tvar parent = elem.parentNode;\n\t\treturn parent && parent.nodeType !== 11 ? parent : null;\n\t},\n\tparents: function( elem ) {\n\t\treturn jQuery.dir( elem, \"parentNode\" );\n\t},\n\tparentsUntil: function( elem, i, until ) {\n\t\treturn jQuery.dir( elem, \"parentNode\", until );\n\t},\n\tnext: function( elem ) {\n\t\treturn sibling( elem, \"nextSibling\" );\n\t},\n\tprev: function( elem ) {\n\t\treturn sibling( elem, \"previousSibling\" );\n\t},\n\tnextAll: function( elem ) {\n\t\treturn jQuery.dir( elem, \"nextSibling\" );\n\t},\n\tprevAll: function( elem ) {\n\t\treturn jQuery.dir( elem, \"previousSibling\" );\n\t},\n\tnextUntil: function( elem, i, until ) {\n\t\treturn jQuery.dir( elem, \"nextSibling\", until );\n\t},\n\tprevUntil: function( elem, i, until ) {\n\t\treturn jQuery.dir( elem, \"previousSibling\", until );\n\t},\n\tsiblings: function( elem ) {\n\t\treturn jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem );\n\t},\n\tchildren: function( elem ) {\n\t\treturn jQuery.sibling( elem.firstChild );\n\t},\n\tcontents: function( elem ) {\n\t\treturn jQuery.nodeName( elem, \"iframe\" ) ?\n\t\t\telem.contentDocument || elem.contentWindow.document :\n\t\t\tjQuery.merge( [], elem.childNodes );\n\t}\n}, function( name, fn ) {\n\tjQuery.fn[ name ] = function( until, selector ) {\n\t\tvar ret = jQuery.map( this, fn, until );\n\n\t\tif ( name.slice( -5 ) !== \"Until\" ) {\n\t\t\tselector = until;\n\t\t}\n\n\t\tif ( selector && typeof selector === \"string\" ) {\n\t\t\tret = jQuery.filter( selector, ret );\n\t\t}\n\n\t\tif ( this.length > 1 ) {\n\t\t\t// Remove duplicates\n\t\t\tif ( !guaranteedUnique[ name ] ) {\n\t\t\t\tret = jQuery.unique( ret );\n\t\t\t}\n\n\t\t\t// Reverse order for parents* and prev-derivatives\n\t\t\tif ( rparentsprev.test( name ) ) {\n\t\t\t\tret = ret.reverse();\n\t\t\t}\n\t\t}\n\n\t\treturn this.pushStack( ret );\n\t};\n});\n\njQuery.extend({\n\tfilter: function( expr, elems, not ) {\n\t\tvar elem = elems[ 0 ];\n\n\t\tif ( not ) {\n\t\t\texpr = \":not(\" + expr + \")\";\n\t\t}\n\n\t\treturn elems.length === 1 && elem.nodeType === 1 ?\n\t\t\tjQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [] :\n\t\t\tjQuery.find.matches( expr, jQuery.grep( elems, function( elem ) {\n\t\t\t\treturn elem.nodeType === 1;\n\t\t\t}));\n\t},\n\n\tdir: function( elem, dir, until ) {\n\t\tvar matched = [],\n\t\t\tcur = elem[ dir ];\n\n\t\twhile ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) {\n\t\t\tif ( cur.nodeType === 1 ) {\n\t\t\t\tmatched.push( cur );\n\t\t\t}\n\t\t\tcur = cur[dir];\n\t\t}\n\t\treturn matched;\n\t},\n\n\tsibling: function( n, elem ) {\n\t\tvar r = [];\n\n\t\tfor ( ; n; n = n.nextSibling ) {\n\t\t\tif ( n.nodeType === 1 && n !== elem ) {\n\t\t\t\tr.push( n );\n\t\t\t}\n\t\t}\n\n\t\treturn r;\n\t}\n});\n\n// Implement the identical functionality for filter and not\nfunction winnow( elements, qualifier, not ) {\n\tif ( jQuery.isFunction( qualifier ) ) {\n\t\treturn jQuery.grep( elements, function( elem, i ) {\n\t\t\t/* jshint -W018 */\n\t\t\treturn !!qualifier.call( elem, i, elem ) !== not;\n\t\t});\n\n\t}\n\n\tif ( qualifier.nodeType ) {\n\t\treturn jQuery.grep( elements, function( elem ) {\n\t\t\treturn ( elem === qualifier ) !== not;\n\t\t});\n\n\t}\n\n\tif ( typeof qualifier === \"string\" ) {\n\t\tif ( isSimple.test( qualifier ) ) {\n\t\t\treturn jQuery.filter( qualifier, elements, not );\n\t\t}\n\n\t\tqualifier = jQuery.filter( qualifier, elements );\n\t}\n\n\treturn jQuery.grep( elements, function( elem ) {\n\t\treturn ( jQuery.inArray( elem, qualifier ) >= 0 ) !== not;\n\t});\n}\nfunction createSafeFragment( document ) {\n\tvar list = nodeNames.split( \"|\" ),\n\t\tsafeFrag = document.createDocumentFragment();\n\n\tif ( safeFrag.createElement ) {\n\t\twhile ( list.length ) {\n\t\t\tsafeFrag.createElement(\n\t\t\t\tlist.pop()\n\t\t\t);\n\t\t}\n\t}\n\treturn safeFrag;\n}\n\nvar nodeNames = \"abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|\" +\n\t\t\"header|hgroup|mark|meter|nav|output|progress|section|summary|time|video\",\n\trinlinejQuery = / jQuery\\d+=\"(?:null|\\d+)\"/g,\n\trnoshimcache = new RegExp(\"<(?:\" + nodeNames + \")[\\\\s/>]\", \"i\"),\n\trleadingWhitespace = /^\\s+/,\n\trxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\\w:]+)[^>]*)\\/>/gi,\n\trtagName = /<([\\w:]+)/,\n\trtbody = /<tbody/i,\n\trhtml = /<|&#?\\w+;/,\n\trnoInnerhtml = /<(?:script|style|link)/i,\n\tmanipulation_rcheckableType = /^(?:checkbox|radio)$/i,\n\t// checked=\"checked\" or checked\n\trchecked = /checked\\s*(?:[^=]|=\\s*.checked.)/i,\n\trscriptType = /^$|\\/(?:java|ecma)script/i,\n\trscriptTypeMasked = /^true\\/(.*)/,\n\trcleanScript = /^\\s*<!(?:\\[CDATA\\[|--)|(?:\\]\\]|--)>\\s*$/g,\n\n\t// We have to close these tags to support XHTML (#13200)\n\twrapMap = {\n\t\toption: [ 1, \"<select multiple='multiple'>\", \"</select>\" ],\n\t\tlegend: [ 1, \"<fieldset>\", \"</fieldset>\" ],\n\t\tarea: [ 1, \"<map>\", \"</map>\" ],\n\t\tparam: [ 1, \"<object>\", \"</object>\" ],\n\t\tthead: [ 1, \"<table>\", \"</table>\" ],\n\t\ttr: [ 2, \"<table><tbody>\", \"</tbody></table>\" ],\n\t\tcol: [ 2, \"<table><tbody></tbody><colgroup>\", \"</colgroup></table>\" ],\n\t\ttd: [ 3, \"<table><tbody><tr>\", \"</tr></tbody></table>\" ],\n\n\t\t// IE6-8 can't serialize link, script, style, or any html5 (NoScope) tags,\n\t\t// unless wrapped in a div with non-breaking characters in front of it.\n\t\t_default: jQuery.support.htmlSerialize ? [ 0, \"\", \"\" ] : [ 1, \"X<div>\", \"</div>\"  ]\n\t},\n\tsafeFragment = createSafeFragment( document ),\n\tfragmentDiv = safeFragment.appendChild( document.createElement(\"div\") );\n\nwrapMap.optgroup = wrapMap.option;\nwrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;\nwrapMap.th = wrapMap.td;\n\njQuery.fn.extend({\n\ttext: function( value ) {\n\t\treturn jQuery.access( this, function( value ) {\n\t\t\treturn value === undefined ?\n\t\t\t\tjQuery.text( this ) :\n\t\t\t\tthis.empty().append( ( this[0] && this[0].ownerDocument || document ).createTextNode( value ) );\n\t\t}, null, value, arguments.length );\n\t},\n\n\tappend: function() {\n\t\treturn this.domManip( arguments, function( elem ) {\n\t\t\tif ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {\n\t\t\t\tvar target = manipulationTarget( this, elem );\n\t\t\t\ttarget.appendChild( elem );\n\t\t\t}\n\t\t});\n\t},\n\n\tprepend: function() {\n\t\treturn this.domManip( arguments, function( elem ) {\n\t\t\tif ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {\n\t\t\t\tvar target = manipulationTarget( this, elem );\n\t\t\t\ttarget.insertBefore( elem, target.firstChild );\n\t\t\t}\n\t\t});\n\t},\n\n\tbefore: function() {\n\t\treturn this.domManip( arguments, function( elem ) {\n\t\t\tif ( this.parentNode ) {\n\t\t\t\tthis.parentNode.insertBefore( elem, this );\n\t\t\t}\n\t\t});\n\t},\n\n\tafter: function() {\n\t\treturn this.domManip( arguments, function( elem ) {\n\t\t\tif ( this.parentNode ) {\n\t\t\t\tthis.parentNode.insertBefore( elem, this.nextSibling );\n\t\t\t}\n\t\t});\n\t},\n\n\t// keepData is for internal use only--do not document\n\tremove: function( selector, keepData ) {\n\t\tvar elem,\n\t\t\telems = selector ? jQuery.filter( selector, this ) : this,\n\t\t\ti = 0;\n\n\t\tfor ( ; (elem = elems[i]) != null; i++ ) {\n\n\t\t\tif ( !keepData && elem.nodeType === 1 ) {\n\t\t\t\tjQuery.cleanData( getAll( elem ) );\n\t\t\t}\n\n\t\t\tif ( elem.parentNode ) {\n\t\t\t\tif ( keepData && jQuery.contains( elem.ownerDocument, elem ) ) {\n\t\t\t\t\tsetGlobalEval( getAll( elem, \"script\" ) );\n\t\t\t\t}\n\t\t\t\telem.parentNode.removeChild( elem );\n\t\t\t}\n\t\t}\n\n\t\treturn this;\n\t},\n\n\tempty: function() {\n\t\tvar elem,\n\t\t\ti = 0;\n\n\t\tfor ( ; (elem = this[i]) != null; i++ ) {\n\t\t\t// Remove element nodes and prevent memory leaks\n\t\t\tif ( elem.nodeType === 1 ) {\n\t\t\t\tjQuery.cleanData( getAll( elem, false ) );\n\t\t\t}\n\n\t\t\t// Remove any remaining nodes\n\t\t\twhile ( elem.firstChild ) {\n\t\t\t\telem.removeChild( elem.firstChild );\n\t\t\t}\n\n\t\t\t// If this is a select, ensure that it displays empty (#12336)\n\t\t\t// Support: IE<9\n\t\t\tif ( elem.options && jQuery.nodeName( elem, \"select\" ) ) {\n\t\t\t\telem.options.length = 0;\n\t\t\t}\n\t\t}\n\n\t\treturn this;\n\t},\n\n\tclone: function( dataAndEvents, deepDataAndEvents ) {\n\t\tdataAndEvents = dataAndEvents == null ? false : dataAndEvents;\n\t\tdeepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;\n\n\t\treturn this.map( function () {\n\t\t\treturn jQuery.clone( this, dataAndEvents, deepDataAndEvents );\n\t\t});\n\t},\n\n\thtml: function( value ) {\n\t\treturn jQuery.access( this, function( value ) {\n\t\t\tvar elem = this[0] || {},\n\t\t\t\ti = 0,\n\t\t\t\tl = this.length;\n\n\t\t\tif ( value === undefined ) {\n\t\t\t\treturn elem.nodeType === 1 ?\n\t\t\t\t\telem.innerHTML.replace( rinlinejQuery, \"\" ) :\n\t\t\t\t\tundefined;\n\t\t\t}\n\n\t\t\t// See if we can take a shortcut and just use innerHTML\n\t\t\tif ( typeof value === \"string\" && !rnoInnerhtml.test( value ) &&\n\t\t\t\t( jQuery.support.htmlSerialize || !rnoshimcache.test( value )  ) &&\n\t\t\t\t( jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value ) ) &&\n\t\t\t\t!wrapMap[ ( rtagName.exec( value ) || [\"\", \"\"] )[1].toLowerCase() ] ) {\n\n\t\t\t\tvalue = value.replace( rxhtmlTag, \"<$1></$2>\" );\n\n\t\t\t\ttry {\n\t\t\t\t\tfor (; i < l; i++ ) {\n\t\t\t\t\t\t// Remove element nodes and prevent memory leaks\n\t\t\t\t\t\telem = this[i] || {};\n\t\t\t\t\t\tif ( elem.nodeType === 1 ) {\n\t\t\t\t\t\t\tjQuery.cleanData( getAll( elem, false ) );\n\t\t\t\t\t\t\telem.innerHTML = value;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\telem = 0;\n\n\t\t\t\t// If using innerHTML throws an exception, use the fallback method\n\t\t\t\t} catch(e) {}\n\t\t\t}\n\n\t\t\tif ( elem ) {\n\t\t\t\tthis.empty().append( value );\n\t\t\t}\n\t\t}, null, value, arguments.length );\n\t},\n\n\treplaceWith: function() {\n\t\tvar\n\t\t\t// Snapshot the DOM in case .domManip sweeps something relevant into its fragment\n\t\t\targs = jQuery.map( this, function( elem ) {\n\t\t\t\treturn [ elem.nextSibling, elem.parentNode ];\n\t\t\t}),\n\t\t\ti = 0;\n\n\t\t// Make the changes, replacing each context element with the new content\n\t\tthis.domManip( arguments, function( elem ) {\n\t\t\tvar next = args[ i++ ],\n\t\t\t\tparent = args[ i++ ];\n\n\t\t\tif ( parent ) {\n\t\t\t\t// Don't use the snapshot next if it has moved (#13810)\n\t\t\t\tif ( next && next.parentNode !== parent ) {\n\t\t\t\t\tnext = this.nextSibling;\n\t\t\t\t}\n\t\t\t\tjQuery( this ).remove();\n\t\t\t\tparent.insertBefore( elem, next );\n\t\t\t}\n\t\t// Allow new content to include elements from the context set\n\t\t}, true );\n\n\t\t// Force removal if there was no new content (e.g., from empty arguments)\n\t\treturn i ? this : this.remove();\n\t},\n\n\tdetach: function( selector ) {\n\t\treturn this.remove( selector, true );\n\t},\n\n\tdomManip: function( args, callback, allowIntersection ) {\n\n\t\t// Flatten any nested arrays\n\t\targs = core_concat.apply( [], args );\n\n\t\tvar first, node, hasScripts,\n\t\t\tscripts, doc, fragment,\n\t\t\ti = 0,\n\t\t\tl = this.length,\n\t\t\tset = this,\n\t\t\tiNoClone = l - 1,\n\t\t\tvalue = args[0],\n\t\t\tisFunction = jQuery.isFunction( value );\n\n\t\t// We can't cloneNode fragments that contain checked, in WebKit\n\t\tif ( isFunction || !( l <= 1 || typeof value !== \"string\" || jQuery.support.checkClone || !rchecked.test( value ) ) ) {\n\t\t\treturn this.each(function( index ) {\n\t\t\t\tvar self = set.eq( index );\n\t\t\t\tif ( isFunction ) {\n\t\t\t\t\targs[0] = value.call( this, index, self.html() );\n\t\t\t\t}\n\t\t\t\tself.domManip( args, callback, allowIntersection );\n\t\t\t});\n\t\t}\n\n\t\tif ( l ) {\n\t\t\tfragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, !allowIntersection && this );\n\t\t\tfirst = fragment.firstChild;\n\n\t\t\tif ( fragment.childNodes.length === 1 ) {\n\t\t\t\tfragment = first;\n\t\t\t}\n\n\t\t\tif ( first ) {\n\t\t\t\tscripts = jQuery.map( getAll( fragment, \"script\" ), disableScript );\n\t\t\t\thasScripts = scripts.length;\n\n\t\t\t\t// Use the original fragment for the last item instead of the first because it can end up\n\t\t\t\t// being emptied incorrectly in certain situations (#8070).\n\t\t\t\tfor ( ; i < l; i++ ) {\n\t\t\t\t\tnode = fragment;\n\n\t\t\t\t\tif ( i !== iNoClone ) {\n\t\t\t\t\t\tnode = jQuery.clone( node, true, true );\n\n\t\t\t\t\t\t// Keep references to cloned scripts for later restoration\n\t\t\t\t\t\tif ( hasScripts ) {\n\t\t\t\t\t\t\tjQuery.merge( scripts, getAll( node, \"script\" ) );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tcallback.call( this[i], node, i );\n\t\t\t\t}\n\n\t\t\t\tif ( hasScripts ) {\n\t\t\t\t\tdoc = scripts[ scripts.length - 1 ].ownerDocument;\n\n\t\t\t\t\t// Reenable scripts\n\t\t\t\t\tjQuery.map( scripts, restoreScript );\n\n\t\t\t\t\t// Evaluate executable scripts on first document insertion\n\t\t\t\t\tfor ( i = 0; i < hasScripts; i++ ) {\n\t\t\t\t\t\tnode = scripts[ i ];\n\t\t\t\t\t\tif ( rscriptType.test( node.type || \"\" ) &&\n\t\t\t\t\t\t\t!jQuery._data( node, \"globalEval\" ) && jQuery.contains( doc, node ) ) {\n\n\t\t\t\t\t\t\tif ( node.src ) {\n\t\t\t\t\t\t\t\t// Hope ajax is available...\n\t\t\t\t\t\t\t\tjQuery._evalUrl( node.src );\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tjQuery.globalEval( ( node.text || node.textContent || node.innerHTML || \"\" ).replace( rcleanScript, \"\" ) );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Fix #11809: Avoid leaking memory\n\t\t\t\tfragment = first = null;\n\t\t\t}\n\t\t}\n\n\t\treturn this;\n\t}\n});\n\n// Support: IE<8\n// Manipulating tables requires a tbody\nfunction manipulationTarget( elem, content ) {\n\treturn jQuery.nodeName( elem, \"table\" ) &&\n\t\tjQuery.nodeName( content.nodeType === 1 ? content : content.firstChild, \"tr\" ) ?\n\n\t\telem.getElementsByTagName(\"tbody\")[0] ||\n\t\t\telem.appendChild( elem.ownerDocument.createElement(\"tbody\") ) :\n\t\telem;\n}\n\n// Replace/restore the type attribute of script elements for safe DOM manipulation\nfunction disableScript( elem ) {\n\telem.type = (jQuery.find.attr( elem, \"type\" ) !== null) + \"/\" + elem.type;\n\treturn elem;\n}\nfunction restoreScript( elem ) {\n\tvar match = rscriptTypeMasked.exec( elem.type );\n\tif ( match ) {\n\t\telem.type = match[1];\n\t} else {\n\t\telem.removeAttribute(\"type\");\n\t}\n\treturn elem;\n}\n\n// Mark scripts as having already been evaluated\nfunction setGlobalEval( elems, refElements ) {\n\tvar elem,\n\t\ti = 0;\n\tfor ( ; (elem = elems[i]) != null; i++ ) {\n\t\tjQuery._data( elem, \"globalEval\", !refElements || jQuery._data( refElements[i], \"globalEval\" ) );\n\t}\n}\n\nfunction cloneCopyEvent( src, dest ) {\n\n\tif ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) {\n\t\treturn;\n\t}\n\n\tvar type, i, l,\n\t\toldData = jQuery._data( src ),\n\t\tcurData = jQuery._data( dest, oldData ),\n\t\tevents = oldData.events;\n\n\tif ( events ) {\n\t\tdelete curData.handle;\n\t\tcurData.events = {};\n\n\t\tfor ( type in events ) {\n\t\t\tfor ( i = 0, l = events[ type ].length; i < l; i++ ) {\n\t\t\t\tjQuery.event.add( dest, type, events[ type ][ i ] );\n\t\t\t}\n\t\t}\n\t}\n\n\t// make the cloned public data object a copy from the original\n\tif ( curData.data ) {\n\t\tcurData.data = jQuery.extend( {}, curData.data );\n\t}\n}\n\nfunction fixCloneNodeIssues( src, dest ) {\n\tvar nodeName, e, data;\n\n\t// We do not need to do anything for non-Elements\n\tif ( dest.nodeType !== 1 ) {\n\t\treturn;\n\t}\n\n\tnodeName = dest.nodeName.toLowerCase();\n\n\t// IE6-8 copies events bound via attachEvent when using cloneNode.\n\tif ( !jQuery.support.noCloneEvent && dest[ jQuery.expando ] ) {\n\t\tdata = jQuery._data( dest );\n\n\t\tfor ( e in data.events ) {\n\t\t\tjQuery.removeEvent( dest, e, data.handle );\n\t\t}\n\n\t\t// Event data gets referenced instead of copied if the expando gets copied too\n\t\tdest.removeAttribute( jQuery.expando );\n\t}\n\n\t// IE blanks contents when cloning scripts, and tries to evaluate newly-set text\n\tif ( nodeName === \"script\" && dest.text !== src.text ) {\n\t\tdisableScript( dest ).text = src.text;\n\t\trestoreScript( dest );\n\n\t// IE6-10 improperly clones children of object elements using classid.\n\t// IE10 throws NoModificationAllowedError if parent is null, #12132.\n\t} else if ( nodeName === \"object\" ) {\n\t\tif ( dest.parentNode ) {\n\t\t\tdest.outerHTML = src.outerHTML;\n\t\t}\n\n\t\t// This path appears unavoidable for IE9. When cloning an object\n\t\t// element in IE9, the outerHTML strategy above is not sufficient.\n\t\t// If the src has innerHTML and the destination does not,\n\t\t// copy the src.innerHTML into the dest.innerHTML. #10324\n\t\tif ( jQuery.support.html5Clone && ( src.innerHTML && !jQuery.trim(dest.innerHTML) ) ) {\n\t\t\tdest.innerHTML = src.innerHTML;\n\t\t}\n\n\t} else if ( nodeName === \"input\" && manipulation_rcheckableType.test( src.type ) ) {\n\t\t// IE6-8 fails to persist the checked state of a cloned checkbox\n\t\t// or radio button. Worse, IE6-7 fail to give the cloned element\n\t\t// a checked appearance if the defaultChecked value isn't also set\n\n\t\tdest.defaultChecked = dest.checked = src.checked;\n\n\t\t// IE6-7 get confused and end up setting the value of a cloned\n\t\t// checkbox/radio button to an empty string instead of \"on\"\n\t\tif ( dest.value !== src.value ) {\n\t\t\tdest.value = src.value;\n\t\t}\n\n\t// IE6-8 fails to return the selected option to the default selected\n\t// state when cloning options\n\t} else if ( nodeName === \"option\" ) {\n\t\tdest.defaultSelected = dest.selected = src.defaultSelected;\n\n\t// IE6-8 fails to set the defaultValue to the correct value when\n\t// cloning other types of input fields\n\t} else if ( nodeName === \"input\" || nodeName === \"textarea\" ) {\n\t\tdest.defaultValue = src.defaultValue;\n\t}\n}\n\njQuery.each({\n\tappendTo: \"append\",\n\tprependTo: \"prepend\",\n\tinsertBefore: \"before\",\n\tinsertAfter: \"after\",\n\treplaceAll: \"replaceWith\"\n}, function( name, original ) {\n\tjQuery.fn[ name ] = function( selector ) {\n\t\tvar elems,\n\t\t\ti = 0,\n\t\t\tret = [],\n\t\t\tinsert = jQuery( selector ),\n\t\t\tlast = insert.length - 1;\n\n\t\tfor ( ; i <= last; i++ ) {\n\t\t\telems = i === last ? this : this.clone(true);\n\t\t\tjQuery( insert[i] )[ original ]( elems );\n\n\t\t\t// Modern browsers can apply jQuery collections as arrays, but oldIE needs a .get()\n\t\t\tcore_push.apply( ret, elems.get() );\n\t\t}\n\n\t\treturn this.pushStack( ret );\n\t};\n});\n\nfunction getAll( context, tag ) {\n\tvar elems, elem,\n\t\ti = 0,\n\t\tfound = typeof context.getElementsByTagName !== core_strundefined ? context.getElementsByTagName( tag || \"*\" ) :\n\t\t\ttypeof context.querySelectorAll !== core_strundefined ? context.querySelectorAll( tag || \"*\" ) :\n\t\t\tundefined;\n\n\tif ( !found ) {\n\t\tfor ( found = [], elems = context.childNodes || context; (elem = elems[i]) != null; i++ ) {\n\t\t\tif ( !tag || jQuery.nodeName( elem, tag ) ) {\n\t\t\t\tfound.push( elem );\n\t\t\t} else {\n\t\t\t\tjQuery.merge( found, getAll( elem, tag ) );\n\t\t\t}\n\t\t}\n\t}\n\n\treturn tag === undefined || tag && jQuery.nodeName( context, tag ) ?\n\t\tjQuery.merge( [ context ], found ) :\n\t\tfound;\n}\n\n// Used in buildFragment, fixes the defaultChecked property\nfunction fixDefaultChecked( elem ) {\n\tif ( manipulation_rcheckableType.test( elem.type ) ) {\n\t\telem.defaultChecked = elem.checked;\n\t}\n}\n\njQuery.extend({\n\tclone: function( elem, dataAndEvents, deepDataAndEvents ) {\n\t\tvar destElements, node, clone, i, srcElements,\n\t\t\tinPage = jQuery.contains( elem.ownerDocument, elem );\n\n\t\tif ( jQuery.support.html5Clone || jQuery.isXMLDoc(elem) || !rnoshimcache.test( \"<\" + elem.nodeName + \">\" ) ) {\n\t\t\tclone = elem.cloneNode( true );\n\n\t\t// IE<=8 does not properly clone detached, unknown element nodes\n\t\t} else {\n\t\t\tfragmentDiv.innerHTML = elem.outerHTML;\n\t\t\tfragmentDiv.removeChild( clone = fragmentDiv.firstChild );\n\t\t}\n\n\t\tif ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) &&\n\t\t\t\t(elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) {\n\n\t\t\t// We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2\n\t\t\tdestElements = getAll( clone );\n\t\t\tsrcElements = getAll( elem );\n\n\t\t\t// Fix all IE cloning issues\n\t\t\tfor ( i = 0; (node = srcElements[i]) != null; ++i ) {\n\t\t\t\t// Ensure that the destination node is not null; Fixes #9587\n\t\t\t\tif ( destElements[i] ) {\n\t\t\t\t\tfixCloneNodeIssues( node, destElements[i] );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Copy the events from the original to the clone\n\t\tif ( dataAndEvents ) {\n\t\t\tif ( deepDataAndEvents ) {\n\t\t\t\tsrcElements = srcElements || getAll( elem );\n\t\t\t\tdestElements = destElements || getAll( clone );\n\n\t\t\t\tfor ( i = 0; (node = srcElements[i]) != null; i++ ) {\n\t\t\t\t\tcloneCopyEvent( node, destElements[i] );\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tcloneCopyEvent( elem, clone );\n\t\t\t}\n\t\t}\n\n\t\t// Preserve script evaluation history\n\t\tdestElements = getAll( clone, \"script\" );\n\t\tif ( destElements.length > 0 ) {\n\t\t\tsetGlobalEval( destElements, !inPage && getAll( elem, \"script\" ) );\n\t\t}\n\n\t\tdestElements = srcElements = node = null;\n\n\t\t// Return the cloned set\n\t\treturn clone;\n\t},\n\n\tbuildFragment: function( elems, context, scripts, selection ) {\n\t\tvar j, elem, contains,\n\t\t\ttmp, tag, tbody, wrap,\n\t\t\tl = elems.length,\n\n\t\t\t// Ensure a safe fragment\n\t\t\tsafe = createSafeFragment( context ),\n\n\t\t\tnodes = [],\n\t\t\ti = 0;\n\n\t\tfor ( ; i < l; i++ ) {\n\t\t\telem = elems[ i ];\n\n\t\t\tif ( elem || elem === 0 ) {\n\n\t\t\t\t// Add nodes directly\n\t\t\t\tif ( jQuery.type( elem ) === \"object\" ) {\n\t\t\t\t\tjQuery.merge( nodes, elem.nodeType ? [ elem ] : elem );\n\n\t\t\t\t// Convert non-html into a text node\n\t\t\t\t} else if ( !rhtml.test( elem ) ) {\n\t\t\t\t\tnodes.push( context.createTextNode( elem ) );\n\n\t\t\t\t// Convert html into DOM nodes\n\t\t\t\t} else {\n\t\t\t\t\ttmp = tmp || safe.appendChild( context.createElement(\"div\") );\n\n\t\t\t\t\t// Deserialize a standard representation\n\t\t\t\t\ttag = ( rtagName.exec( elem ) || [\"\", \"\"] )[1].toLowerCase();\n\t\t\t\t\twrap = wrapMap[ tag ] || wrapMap._default;\n\n\t\t\t\t\ttmp.innerHTML = wrap[1] + elem.replace( rxhtmlTag, \"<$1></$2>\" ) + wrap[2];\n\n\t\t\t\t\t// Descend through wrappers to the right content\n\t\t\t\t\tj = wrap[0];\n\t\t\t\t\twhile ( j-- ) {\n\t\t\t\t\t\ttmp = tmp.lastChild;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Manually add leading whitespace removed by IE\n\t\t\t\t\tif ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) {\n\t\t\t\t\t\tnodes.push( context.createTextNode( rleadingWhitespace.exec( elem )[0] ) );\n\t\t\t\t\t}\n\n\t\t\t\t\t// Remove IE's autoinserted <tbody> from table fragments\n\t\t\t\t\tif ( !jQuery.support.tbody ) {\n\n\t\t\t\t\t\t// String was a <table>, *may* have spurious <tbody>\n\t\t\t\t\t\telem = tag === \"table\" && !rtbody.test( elem ) ?\n\t\t\t\t\t\t\ttmp.firstChild :\n\n\t\t\t\t\t\t\t// String was a bare <thead> or <tfoot>\n\t\t\t\t\t\t\twrap[1] === \"<table>\" && !rtbody.test( elem ) ?\n\t\t\t\t\t\t\t\ttmp :\n\t\t\t\t\t\t\t\t0;\n\n\t\t\t\t\t\tj = elem && elem.childNodes.length;\n\t\t\t\t\t\twhile ( j-- ) {\n\t\t\t\t\t\t\tif ( jQuery.nodeName( (tbody = elem.childNodes[j]), \"tbody\" ) && !tbody.childNodes.length ) {\n\t\t\t\t\t\t\t\telem.removeChild( tbody );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tjQuery.merge( nodes, tmp.childNodes );\n\n\t\t\t\t\t// Fix #12392 for WebKit and IE > 9\n\t\t\t\t\ttmp.textContent = \"\";\n\n\t\t\t\t\t// Fix #12392 for oldIE\n\t\t\t\t\twhile ( tmp.firstChild ) {\n\t\t\t\t\t\ttmp.removeChild( tmp.firstChild );\n\t\t\t\t\t}\n\n\t\t\t\t\t// Remember the top-level container for proper cleanup\n\t\t\t\t\ttmp = safe.lastChild;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Fix #11356: Clear elements from fragment\n\t\tif ( tmp ) {\n\t\t\tsafe.removeChild( tmp );\n\t\t}\n\n\t\t// Reset defaultChecked for any radios and checkboxes\n\t\t// about to be appended to the DOM in IE 6/7 (#8060)\n\t\tif ( !jQuery.support.appendChecked ) {\n\t\t\tjQuery.grep( getAll( nodes, \"input\" ), fixDefaultChecked );\n\t\t}\n\n\t\ti = 0;\n\t\twhile ( (elem = nodes[ i++ ]) ) {\n\n\t\t\t// #4087 - If origin and destination elements are the same, and this is\n\t\t\t// that element, do not do anything\n\t\t\tif ( selection && jQuery.inArray( elem, selection ) !== -1 ) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tcontains = jQuery.contains( elem.ownerDocument, elem );\n\n\t\t\t// Append to fragment\n\t\t\ttmp = getAll( safe.appendChild( elem ), \"script\" );\n\n\t\t\t// Preserve script evaluation history\n\t\t\tif ( contains ) {\n\t\t\t\tsetGlobalEval( tmp );\n\t\t\t}\n\n\t\t\t// Capture executables\n\t\t\tif ( scripts ) {\n\t\t\t\tj = 0;\n\t\t\t\twhile ( (elem = tmp[ j++ ]) ) {\n\t\t\t\t\tif ( rscriptType.test( elem.type || \"\" ) ) {\n\t\t\t\t\t\tscripts.push( elem );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\ttmp = null;\n\n\t\treturn safe;\n\t},\n\n\tcleanData: function( elems, /* internal */ acceptData ) {\n\t\tvar elem, type, id, data,\n\t\t\ti = 0,\n\t\t\tinternalKey = jQuery.expando,\n\t\t\tcache = jQuery.cache,\n\t\t\tdeleteExpando = jQuery.support.deleteExpando,\n\t\t\tspecial = jQuery.event.special;\n\n\t\tfor ( ; (elem = elems[i]) != null; i++ ) {\n\n\t\t\tif ( acceptData || jQuery.acceptData( elem ) ) {\n\n\t\t\t\tid = elem[ internalKey ];\n\t\t\t\tdata = id && cache[ id ];\n\n\t\t\t\tif ( data ) {\n\t\t\t\t\tif ( data.events ) {\n\t\t\t\t\t\tfor ( type in data.events ) {\n\t\t\t\t\t\t\tif ( special[ type ] ) {\n\t\t\t\t\t\t\t\tjQuery.event.remove( elem, type );\n\n\t\t\t\t\t\t\t// This is a shortcut to avoid jQuery.event.remove's overhead\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tjQuery.removeEvent( elem, type, data.handle );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Remove cache only if it was not already removed by jQuery.event.remove\n\t\t\t\t\tif ( cache[ id ] ) {\n\n\t\t\t\t\t\tdelete cache[ id ];\n\n\t\t\t\t\t\t// IE does not allow us to delete expando properties from nodes,\n\t\t\t\t\t\t// nor does it have a removeAttribute function on Document nodes;\n\t\t\t\t\t\t// we must handle all of these cases\n\t\t\t\t\t\tif ( deleteExpando ) {\n\t\t\t\t\t\t\tdelete elem[ internalKey ];\n\n\t\t\t\t\t\t} else if ( typeof elem.removeAttribute !== core_strundefined ) {\n\t\t\t\t\t\t\telem.removeAttribute( internalKey );\n\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\telem[ internalKey ] = null;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tcore_deletedIds.push( id );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\n\t_evalUrl: function( url ) {\n\t\treturn jQuery.ajax({\n\t\t\turl: url,\n\t\t\ttype: \"GET\",\n\t\t\tdataType: \"script\",\n\t\t\tasync: false,\n\t\t\tglobal: false,\n\t\t\t\"throws\": true\n\t\t});\n\t}\n});\njQuery.fn.extend({\n\twrapAll: function( html ) {\n\t\tif ( jQuery.isFunction( html ) ) {\n\t\t\treturn this.each(function(i) {\n\t\t\t\tjQuery(this).wrapAll( html.call(this, i) );\n\t\t\t});\n\t\t}\n\n\t\tif ( this[0] ) {\n\t\t\t// The elements to wrap the target around\n\t\t\tvar wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true);\n\n\t\t\tif ( this[0].parentNode ) {\n\t\t\t\twrap.insertBefore( this[0] );\n\t\t\t}\n\n\t\t\twrap.map(function() {\n\t\t\t\tvar elem = this;\n\n\t\t\t\twhile ( elem.firstChild && elem.firstChild.nodeType === 1 ) {\n\t\t\t\t\telem = elem.firstChild;\n\t\t\t\t}\n\n\t\t\t\treturn elem;\n\t\t\t}).append( this );\n\t\t}\n\n\t\treturn this;\n\t},\n\n\twrapInner: function( html ) {\n\t\tif ( jQuery.isFunction( html ) ) {\n\t\t\treturn this.each(function(i) {\n\t\t\t\tjQuery(this).wrapInner( html.call(this, i) );\n\t\t\t});\n\t\t}\n\n\t\treturn this.each(function() {\n\t\t\tvar self = jQuery( this ),\n\t\t\t\tcontents = self.contents();\n\n\t\t\tif ( contents.length ) {\n\t\t\t\tcontents.wrapAll( html );\n\n\t\t\t} else {\n\t\t\t\tself.append( html );\n\t\t\t}\n\t\t});\n\t},\n\n\twrap: function( html ) {\n\t\tvar isFunction = jQuery.isFunction( html );\n\n\t\treturn this.each(function(i) {\n\t\t\tjQuery( this ).wrapAll( isFunction ? html.call(this, i) : html );\n\t\t});\n\t},\n\n\tunwrap: function() {\n\t\treturn this.parent().each(function() {\n\t\t\tif ( !jQuery.nodeName( this, \"body\" ) ) {\n\t\t\t\tjQuery( this ).replaceWith( this.childNodes );\n\t\t\t}\n\t\t}).end();\n\t}\n});\nvar iframe, getStyles, curCSS,\n\tralpha = /alpha\\([^)]*\\)/i,\n\tropacity = /opacity\\s*=\\s*([^)]*)/,\n\trposition = /^(top|right|bottom|left)$/,\n\t// swappable if display is none or starts with table except \"table\", \"table-cell\", or \"table-caption\"\n\t// see here for display values: https://developer.mozilla.org/en-US/docs/CSS/display\n\trdisplayswap = /^(none|table(?!-c[ea]).+)/,\n\trmargin = /^margin/,\n\trnumsplit = new RegExp( \"^(\" + core_pnum + \")(.*)$\", \"i\" ),\n\trnumnonpx = new RegExp( \"^(\" + core_pnum + \")(?!px)[a-z%]+$\", \"i\" ),\n\trrelNum = new RegExp( \"^([+-])=(\" + core_pnum + \")\", \"i\" ),\n\telemdisplay = { BODY: \"block\" },\n\n\tcssShow = { position: \"absolute\", visibility: \"hidden\", display: \"block\" },\n\tcssNormalTransform = {\n\t\tletterSpacing: 0,\n\t\tfontWeight: 400\n\t},\n\n\tcssExpand = [ \"Top\", \"Right\", \"Bottom\", \"Left\" ],\n\tcssPrefixes = [ \"Webkit\", \"O\", \"Moz\", \"ms\" ];\n\n// return a css property mapped to a potentially vendor prefixed property\nfunction vendorPropName( style, name ) {\n\n\t// shortcut for names that are not vendor prefixed\n\tif ( name in style ) {\n\t\treturn name;\n\t}\n\n\t// check for vendor prefixed names\n\tvar capName = name.charAt(0).toUpperCase() + name.slice(1),\n\t\torigName = name,\n\t\ti = cssPrefixes.length;\n\n\twhile ( i-- ) {\n\t\tname = cssPrefixes[ i ] + capName;\n\t\tif ( name in style ) {\n\t\t\treturn name;\n\t\t}\n\t}\n\n\treturn origName;\n}\n\nfunction isHidden( elem, el ) {\n\t// isHidden might be called from jQuery#filter function;\n\t// in that case, element will be second argument\n\telem = el || elem;\n\treturn jQuery.css( elem, \"display\" ) === \"none\" || !jQuery.contains( elem.ownerDocument, elem );\n}\n\nfunction showHide( elements, show ) {\n\tvar display, elem, hidden,\n\t\tvalues = [],\n\t\tindex = 0,\n\t\tlength = elements.length;\n\n\tfor ( ; index < length; index++ ) {\n\t\telem = elements[ index ];\n\t\tif ( !elem.style ) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tvalues[ index ] = jQuery._data( elem, \"olddisplay\" );\n\t\tdisplay = elem.style.display;\n\t\tif ( show ) {\n\t\t\t// Reset the inline display of this element to learn if it is\n\t\t\t// being hidden by cascaded rules or not\n\t\t\tif ( !values[ index ] && display === \"none\" ) {\n\t\t\t\telem.style.display = \"\";\n\t\t\t}\n\n\t\t\t// Set elements which have been overridden with display: none\n\t\t\t// in a stylesheet to whatever the default browser style is\n\t\t\t// for such an element\n\t\t\tif ( elem.style.display === \"\" && isHidden( elem ) ) {\n\t\t\t\tvalues[ index ] = jQuery._data( elem, \"olddisplay\", css_defaultDisplay(elem.nodeName) );\n\t\t\t}\n\t\t} else {\n\n\t\t\tif ( !values[ index ] ) {\n\t\t\t\thidden = isHidden( elem );\n\n\t\t\t\tif ( display && display !== \"none\" || !hidden ) {\n\t\t\t\t\tjQuery._data( elem, \"olddisplay\", hidden ? display : jQuery.css( elem, \"display\" ) );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Set the display of most of the elements in a second loop\n\t// to avoid the constant reflow\n\tfor ( index = 0; index < length; index++ ) {\n\t\telem = elements[ index ];\n\t\tif ( !elem.style ) {\n\t\t\tcontinue;\n\t\t}\n\t\tif ( !show || elem.style.display === \"none\" || elem.style.display === \"\" ) {\n\t\t\telem.style.display = show ? values[ index ] || \"\" : \"none\";\n\t\t}\n\t}\n\n\treturn elements;\n}\n\njQuery.fn.extend({\n\tcss: function( name, value ) {\n\t\treturn jQuery.access( this, function( elem, name, value ) {\n\t\t\tvar len, styles,\n\t\t\t\tmap = {},\n\t\t\t\ti = 0;\n\n\t\t\tif ( jQuery.isArray( name ) ) {\n\t\t\t\tstyles = getStyles( elem );\n\t\t\t\tlen = name.length;\n\n\t\t\t\tfor ( ; i < len; i++ ) {\n\t\t\t\t\tmap[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles );\n\t\t\t\t}\n\n\t\t\t\treturn map;\n\t\t\t}\n\n\t\t\treturn value !== undefined ?\n\t\t\t\tjQuery.style( elem, name, value ) :\n\t\t\t\tjQuery.css( elem, name );\n\t\t}, name, value, arguments.length > 1 );\n\t},\n\tshow: function() {\n\t\treturn showHide( this, true );\n\t},\n\thide: function() {\n\t\treturn showHide( this );\n\t},\n\ttoggle: function( state ) {\n\t\tvar bool = typeof state === \"boolean\";\n\n\t\treturn this.each(function() {\n\t\t\tif ( bool ? state : isHidden( this ) ) {\n\t\t\t\tjQuery( this ).show();\n\t\t\t} else {\n\t\t\t\tjQuery( this ).hide();\n\t\t\t}\n\t\t});\n\t}\n});\n\njQuery.extend({\n\t// Add in style property hooks for overriding the default\n\t// behavior of getting and setting a style property\n\tcssHooks: {\n\t\topacity: {\n\t\t\tget: function( elem, computed ) {\n\t\t\t\tif ( computed ) {\n\t\t\t\t\t// We should always get a number back from opacity\n\t\t\t\t\tvar ret = curCSS( elem, \"opacity\" );\n\t\t\t\t\treturn ret === \"\" ? \"1\" : ret;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\n\t// Don't automatically add \"px\" to these possibly-unitless properties\n\tcssNumber: {\n\t\t\"columnCount\": true,\n\t\t\"fillOpacity\": true,\n\t\t\"fontWeight\": true,\n\t\t\"lineHeight\": true,\n\t\t\"opacity\": true,\n\t\t\"orphans\": true,\n\t\t\"widows\": true,\n\t\t\"zIndex\": true,\n\t\t\"zoom\": true\n\t},\n\n\t// Add in properties whose names you wish to fix before\n\t// setting or getting the value\n\tcssProps: {\n\t\t// normalize float css property\n\t\t\"float\": jQuery.support.cssFloat ? \"cssFloat\" : \"styleFloat\"\n\t},\n\n\t// Get and set the style property on a DOM Node\n\tstyle: function( elem, name, value, extra ) {\n\t\t// Don't set styles on text and comment nodes\n\t\tif ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Make sure that we're working with the right name\n\t\tvar ret, type, hooks,\n\t\t\torigName = jQuery.camelCase( name ),\n\t\t\tstyle = elem.style;\n\n\t\tname = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( style, origName ) );\n\n\t\t// gets hook for the prefixed version\n\t\t// followed by the unprefixed version\n\t\thooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];\n\n\t\t// Check if we're setting a value\n\t\tif ( value !== undefined ) {\n\t\t\ttype = typeof value;\n\n\t\t\t// convert relative number strings (+= or -=) to relative numbers. #7345\n\t\t\tif ( type === \"string\" && (ret = rrelNum.exec( value )) ) {\n\t\t\t\tvalue = ( ret[1] + 1 ) * ret[2] + parseFloat( jQuery.css( elem, name ) );\n\t\t\t\t// Fixes bug #9237\n\t\t\t\ttype = \"number\";\n\t\t\t}\n\n\t\t\t// Make sure that NaN and null values aren't set. See: #7116\n\t\t\tif ( value == null || type === \"number\" && isNaN( value ) ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// If a number was passed in, add 'px' to the (except for certain CSS properties)\n\t\t\tif ( type === \"number\" && !jQuery.cssNumber[ origName ] ) {\n\t\t\t\tvalue += \"px\";\n\t\t\t}\n\n\t\t\t// Fixes #8908, it can be done more correctly by specifing setters in cssHooks,\n\t\t\t// but it would mean to define eight (for every problematic property) identical functions\n\t\t\tif ( !jQuery.support.clearCloneStyle && value === \"\" && name.indexOf(\"background\") === 0 ) {\n\t\t\t\tstyle[ name ] = \"inherit\";\n\t\t\t}\n\n\t\t\t// If a hook was provided, use that value, otherwise just set the specified value\n\t\t\tif ( !hooks || !(\"set\" in hooks) || (value = hooks.set( elem, value, extra )) !== undefined ) {\n\n\t\t\t\t// Wrapped to prevent IE from throwing errors when 'invalid' values are provided\n\t\t\t\t// Fixes bug #5509\n\t\t\t\ttry {\n\t\t\t\t\tstyle[ name ] = value;\n\t\t\t\t} catch(e) {}\n\t\t\t}\n\n\t\t} else {\n\t\t\t// If a hook was provided get the non-computed value from there\n\t\t\tif ( hooks && \"get\" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) {\n\t\t\t\treturn ret;\n\t\t\t}\n\n\t\t\t// Otherwise just get the value from the style object\n\t\t\treturn style[ name ];\n\t\t}\n\t},\n\n\tcss: function( elem, name, extra, styles ) {\n\t\tvar num, val, hooks,\n\t\t\torigName = jQuery.camelCase( name );\n\n\t\t// Make sure that we're working with the right name\n\t\tname = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( elem.style, origName ) );\n\n\t\t// gets hook for the prefixed version\n\t\t// followed by the unprefixed version\n\t\thooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];\n\n\t\t// If a hook was provided get the computed value from there\n\t\tif ( hooks && \"get\" in hooks ) {\n\t\t\tval = hooks.get( elem, true, extra );\n\t\t}\n\n\t\t// Otherwise, if a way to get the computed value exists, use that\n\t\tif ( val === undefined ) {\n\t\t\tval = curCSS( elem, name, styles );\n\t\t}\n\n\t\t//convert \"normal\" to computed value\n\t\tif ( val === \"normal\" && name in cssNormalTransform ) {\n\t\t\tval = cssNormalTransform[ name ];\n\t\t}\n\n\t\t// Return, converting to number if forced or a qualifier was provided and val looks numeric\n\t\tif ( extra === \"\" || extra ) {\n\t\t\tnum = parseFloat( val );\n\t\t\treturn extra === true || jQuery.isNumeric( num ) ? num || 0 : val;\n\t\t}\n\t\treturn val;\n\t}\n});\n\n// NOTE: we've included the \"window\" in window.getComputedStyle\n// because jsdom on node.js will break without it.\nif ( window.getComputedStyle ) {\n\tgetStyles = function( elem ) {\n\t\treturn window.getComputedStyle( elem, null );\n\t};\n\n\tcurCSS = function( elem, name, _computed ) {\n\t\tvar width, minWidth, maxWidth,\n\t\t\tcomputed = _computed || getStyles( elem ),\n\n\t\t\t// getPropertyValue is only needed for .css('filter') in IE9, see #12537\n\t\t\tret = computed ? computed.getPropertyValue( name ) || computed[ name ] : undefined,\n\t\t\tstyle = elem.style;\n\n\t\tif ( computed ) {\n\n\t\t\tif ( ret === \"\" && !jQuery.contains( elem.ownerDocument, elem ) ) {\n\t\t\t\tret = jQuery.style( elem, name );\n\t\t\t}\n\n\t\t\t// A tribute to the \"awesome hack by Dean Edwards\"\n\t\t\t// Chrome < 17 and Safari 5.0 uses \"computed value\" instead of \"used value\" for margin-right\n\t\t\t// Safari 5.1.7 (at least) returns percentage for a larger set of values, but width seems to be reliably pixels\n\t\t\t// this is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values\n\t\t\tif ( rnumnonpx.test( ret ) && rmargin.test( name ) ) {\n\n\t\t\t\t// Remember the original values\n\t\t\t\twidth = style.width;\n\t\t\t\tminWidth = style.minWidth;\n\t\t\t\tmaxWidth = style.maxWidth;\n\n\t\t\t\t// Put in the new values to get a computed value out\n\t\t\t\tstyle.minWidth = style.maxWidth = style.width = ret;\n\t\t\t\tret = computed.width;\n\n\t\t\t\t// Revert the changed values\n\t\t\t\tstyle.width = width;\n\t\t\t\tstyle.minWidth = minWidth;\n\t\t\t\tstyle.maxWidth = maxWidth;\n\t\t\t}\n\t\t}\n\n\t\treturn ret;\n\t};\n} else if ( document.documentElement.currentStyle ) {\n\tgetStyles = function( elem ) {\n\t\treturn elem.currentStyle;\n\t};\n\n\tcurCSS = function( elem, name, _computed ) {\n\t\tvar left, rs, rsLeft,\n\t\t\tcomputed = _computed || getStyles( elem ),\n\t\t\tret = computed ? computed[ name ] : undefined,\n\t\t\tstyle = elem.style;\n\n\t\t// Avoid setting ret to empty string here\n\t\t// so we don't default to auto\n\t\tif ( ret == null && style && style[ name ] ) {\n\t\t\tret = style[ name ];\n\t\t}\n\n\t\t// From the awesome hack by Dean Edwards\n\t\t// http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291\n\n\t\t// If we're not dealing with a regular pixel number\n\t\t// but a number that has a weird ending, we need to convert it to pixels\n\t\t// but not position css attributes, as those are proportional to the parent element instead\n\t\t// and we can't measure the parent instead because it might trigger a \"stacking dolls\" problem\n\t\tif ( rnumnonpx.test( ret ) && !rposition.test( name ) ) {\n\n\t\t\t// Remember the original values\n\t\t\tleft = style.left;\n\t\t\trs = elem.runtimeStyle;\n\t\t\trsLeft = rs && rs.left;\n\n\t\t\t// Put in the new values to get a computed value out\n\t\t\tif ( rsLeft ) {\n\t\t\t\trs.left = elem.currentStyle.left;\n\t\t\t}\n\t\t\tstyle.left = name === \"fontSize\" ? \"1em\" : ret;\n\t\t\tret = style.pixelLeft + \"px\";\n\n\t\t\t// Revert the changed values\n\t\t\tstyle.left = left;\n\t\t\tif ( rsLeft ) {\n\t\t\t\trs.left = rsLeft;\n\t\t\t}\n\t\t}\n\n\t\treturn ret === \"\" ? \"auto\" : ret;\n\t};\n}\n\nfunction setPositiveNumber( elem, value, subtract ) {\n\tvar matches = rnumsplit.exec( value );\n\treturn matches ?\n\t\t// Guard against undefined \"subtract\", e.g., when used as in cssHooks\n\t\tMath.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || \"px\" ) :\n\t\tvalue;\n}\n\nfunction augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) {\n\tvar i = extra === ( isBorderBox ? \"border\" : \"content\" ) ?\n\t\t// If we already have the right measurement, avoid augmentation\n\t\t4 :\n\t\t// Otherwise initialize for horizontal or vertical properties\n\t\tname === \"width\" ? 1 : 0,\n\n\t\tval = 0;\n\n\tfor ( ; i < 4; i += 2 ) {\n\t\t// both box models exclude margin, so add it if we want it\n\t\tif ( extra === \"margin\" ) {\n\t\t\tval += jQuery.css( elem, extra + cssExpand[ i ], true, styles );\n\t\t}\n\n\t\tif ( isBorderBox ) {\n\t\t\t// border-box includes padding, so remove it if we want content\n\t\t\tif ( extra === \"content\" ) {\n\t\t\t\tval -= jQuery.css( elem, \"padding\" + cssExpand[ i ], true, styles );\n\t\t\t}\n\n\t\t\t// at this point, extra isn't border nor margin, so remove border\n\t\t\tif ( extra !== \"margin\" ) {\n\t\t\t\tval -= jQuery.css( elem, \"border\" + cssExpand[ i ] + \"Width\", true, styles );\n\t\t\t}\n\t\t} else {\n\t\t\t// at this point, extra isn't content, so add padding\n\t\t\tval += jQuery.css( elem, \"padding\" + cssExpand[ i ], true, styles );\n\n\t\t\t// at this point, extra isn't content nor padding, so add border\n\t\t\tif ( extra !== \"padding\" ) {\n\t\t\t\tval += jQuery.css( elem, \"border\" + cssExpand[ i ] + \"Width\", true, styles );\n\t\t\t}\n\t\t}\n\t}\n\n\treturn val;\n}\n\nfunction getWidthOrHeight( elem, name, extra ) {\n\n\t// Start with offset property, which is equivalent to the border-box value\n\tvar valueIsBorderBox = true,\n\t\tval = name === \"width\" ? elem.offsetWidth : elem.offsetHeight,\n\t\tstyles = getStyles( elem ),\n\t\tisBorderBox = jQuery.support.boxSizing && jQuery.css( elem, \"boxSizing\", false, styles ) === \"border-box\";\n\n\t// some non-html elements return undefined for offsetWidth, so check for null/undefined\n\t// svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285\n\t// MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668\n\tif ( val <= 0 || val == null ) {\n\t\t// Fall back to computed then uncomputed css if necessary\n\t\tval = curCSS( elem, name, styles );\n\t\tif ( val < 0 || val == null ) {\n\t\t\tval = elem.style[ name ];\n\t\t}\n\n\t\t// Computed unit is not pixels. Stop here and return.\n\t\tif ( rnumnonpx.test(val) ) {\n\t\t\treturn val;\n\t\t}\n\n\t\t// we need the check for style in case a browser which returns unreliable values\n\t\t// for getComputedStyle silently falls back to the reliable elem.style\n\t\tvalueIsBorderBox = isBorderBox && ( jQuery.support.boxSizingReliable || val === elem.style[ name ] );\n\n\t\t// Normalize \"\", auto, and prepare for extra\n\t\tval = parseFloat( val ) || 0;\n\t}\n\n\t// use the active box-sizing model to add/subtract irrelevant styles\n\treturn ( val +\n\t\taugmentWidthOrHeight(\n\t\t\telem,\n\t\t\tname,\n\t\t\textra || ( isBorderBox ? \"border\" : \"content\" ),\n\t\t\tvalueIsBorderBox,\n\t\t\tstyles\n\t\t)\n\t) + \"px\";\n}\n\n// Try to determine the default display value of an element\nfunction css_defaultDisplay( nodeName ) {\n\tvar doc = document,\n\t\tdisplay = elemdisplay[ nodeName ];\n\n\tif ( !display ) {\n\t\tdisplay = actualDisplay( nodeName, doc );\n\n\t\t// If the simple way fails, read from inside an iframe\n\t\tif ( display === \"none\" || !display ) {\n\t\t\t// Use the already-created iframe if possible\n\t\t\tiframe = ( iframe ||\n\t\t\t\tjQuery(\"<iframe frameborder='0' width='0' height='0'/>\")\n\t\t\t\t.css( \"cssText\", \"display:block !important\" )\n\t\t\t).appendTo( doc.documentElement );\n\n\t\t\t// Always write a new HTML skeleton so Webkit and Firefox don't choke on reuse\n\t\t\tdoc = ( iframe[0].contentWindow || iframe[0].contentDocument ).document;\n\t\t\tdoc.write(\"<!doctype html><html><body>\");\n\t\t\tdoc.close();\n\n\t\t\tdisplay = actualDisplay( nodeName, doc );\n\t\t\tiframe.detach();\n\t\t}\n\n\t\t// Store the correct default display\n\t\telemdisplay[ nodeName ] = display;\n\t}\n\n\treturn display;\n}\n\n// Called ONLY from within css_defaultDisplay\nfunction actualDisplay( name, doc ) {\n\tvar elem = jQuery( doc.createElement( name ) ).appendTo( doc.body ),\n\t\tdisplay = jQuery.css( elem[0], \"display\" );\n\telem.remove();\n\treturn display;\n}\n\njQuery.each([ \"height\", \"width\" ], function( i, name ) {\n\tjQuery.cssHooks[ name ] = {\n\t\tget: function( elem, computed, extra ) {\n\t\t\tif ( computed ) {\n\t\t\t\t// certain elements can have dimension info if we invisibly show them\n\t\t\t\t// however, it must have a current display style that would benefit from this\n\t\t\t\treturn elem.offsetWidth === 0 && rdisplayswap.test( jQuery.css( elem, \"display\" ) ) ?\n\t\t\t\t\tjQuery.swap( elem, cssShow, function() {\n\t\t\t\t\t\treturn getWidthOrHeight( elem, name, extra );\n\t\t\t\t\t}) :\n\t\t\t\t\tgetWidthOrHeight( elem, name, extra );\n\t\t\t}\n\t\t},\n\n\t\tset: function( elem, value, extra ) {\n\t\t\tvar styles = extra && getStyles( elem );\n\t\t\treturn setPositiveNumber( elem, value, extra ?\n\t\t\t\taugmentWidthOrHeight(\n\t\t\t\t\telem,\n\t\t\t\t\tname,\n\t\t\t\t\textra,\n\t\t\t\t\tjQuery.support.boxSizing && jQuery.css( elem, \"boxSizing\", false, styles ) === \"border-box\",\n\t\t\t\t\tstyles\n\t\t\t\t) : 0\n\t\t\t);\n\t\t}\n\t};\n});\n\nif ( !jQuery.support.opacity ) {\n\tjQuery.cssHooks.opacity = {\n\t\tget: function( elem, computed ) {\n\t\t\t// IE uses filters for opacity\n\t\t\treturn ropacity.test( (computed && elem.currentStyle ? elem.currentStyle.filter : elem.style.filter) || \"\" ) ?\n\t\t\t\t( 0.01 * parseFloat( RegExp.$1 ) ) + \"\" :\n\t\t\t\tcomputed ? \"1\" : \"\";\n\t\t},\n\n\t\tset: function( elem, value ) {\n\t\t\tvar style = elem.style,\n\t\t\t\tcurrentStyle = elem.currentStyle,\n\t\t\t\topacity = jQuery.isNumeric( value ) ? \"alpha(opacity=\" + value * 100 + \")\" : \"\",\n\t\t\t\tfilter = currentStyle && currentStyle.filter || style.filter || \"\";\n\n\t\t\t// IE has trouble with opacity if it does not have layout\n\t\t\t// Force it by setting the zoom level\n\t\t\tstyle.zoom = 1;\n\n\t\t\t// if setting opacity to 1, and no other filters exist - attempt to remove filter attribute #6652\n\t\t\t// if value === \"\", then remove inline opacity #12685\n\t\t\tif ( ( value >= 1 || value === \"\" ) &&\n\t\t\t\t\tjQuery.trim( filter.replace( ralpha, \"\" ) ) === \"\" &&\n\t\t\t\t\tstyle.removeAttribute ) {\n\n\t\t\t\t// Setting style.filter to null, \"\" & \" \" still leave \"filter:\" in the cssText\n\t\t\t\t// if \"filter:\" is present at all, clearType is disabled, we want to avoid this\n\t\t\t\t// style.removeAttribute is IE Only, but so apparently is this code path...\n\t\t\t\tstyle.removeAttribute( \"filter\" );\n\n\t\t\t\t// if there is no filter style applied in a css rule or unset inline opacity, we are done\n\t\t\t\tif ( value === \"\" || currentStyle && !currentStyle.filter ) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// otherwise, set new filter values\n\t\t\tstyle.filter = ralpha.test( filter ) ?\n\t\t\t\tfilter.replace( ralpha, opacity ) :\n\t\t\t\tfilter + \" \" + opacity;\n\t\t}\n\t};\n}\n\n// These hooks cannot be added until DOM ready because the support test\n// for it is not run until after DOM ready\njQuery(function() {\n\tif ( !jQuery.support.reliableMarginRight ) {\n\t\tjQuery.cssHooks.marginRight = {\n\t\t\tget: function( elem, computed ) {\n\t\t\t\tif ( computed ) {\n\t\t\t\t\t// WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right\n\t\t\t\t\t// Work around by temporarily setting element display to inline-block\n\t\t\t\t\treturn jQuery.swap( elem, { \"display\": \"inline-block\" },\n\t\t\t\t\t\tcurCSS, [ elem, \"marginRight\" ] );\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t}\n\n\t// Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084\n\t// getComputedStyle returns percent when specified for top/left/bottom/right\n\t// rather than make the css module depend on the offset module, we just check for it here\n\tif ( !jQuery.support.pixelPosition && jQuery.fn.position ) {\n\t\tjQuery.each( [ \"top\", \"left\" ], function( i, prop ) {\n\t\t\tjQuery.cssHooks[ prop ] = {\n\t\t\t\tget: function( elem, computed ) {\n\t\t\t\t\tif ( computed ) {\n\t\t\t\t\t\tcomputed = curCSS( elem, prop );\n\t\t\t\t\t\t// if curCSS returns percentage, fallback to offset\n\t\t\t\t\t\treturn rnumnonpx.test( computed ) ?\n\t\t\t\t\t\t\tjQuery( elem ).position()[ prop ] + \"px\" :\n\t\t\t\t\t\t\tcomputed;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t};\n\t\t});\n\t}\n\n});\n\nif ( jQuery.expr && jQuery.expr.filters ) {\n\tjQuery.expr.filters.hidden = function( elem ) {\n\t\t// Support: Opera <= 12.12\n\t\t// Opera reports offsetWidths and offsetHeights less than zero on some elements\n\t\treturn elem.offsetWidth <= 0 && elem.offsetHeight <= 0 ||\n\t\t\t(!jQuery.support.reliableHiddenOffsets && ((elem.style && elem.style.display) || jQuery.css( elem, \"display\" )) === \"none\");\n\t};\n\n\tjQuery.expr.filters.visible = function( elem ) {\n\t\treturn !jQuery.expr.filters.hidden( elem );\n\t};\n}\n\n// These hooks are used by animate to expand properties\njQuery.each({\n\tmargin: \"\",\n\tpadding: \"\",\n\tborder: \"Width\"\n}, function( prefix, suffix ) {\n\tjQuery.cssHooks[ prefix + suffix ] = {\n\t\texpand: function( value ) {\n\t\t\tvar i = 0,\n\t\t\t\texpanded = {},\n\n\t\t\t\t// assumes a single number if not a string\n\t\t\t\tparts = typeof value === \"string\" ? value.split(\" \") : [ value ];\n\n\t\t\tfor ( ; i < 4; i++ ) {\n\t\t\t\texpanded[ prefix + cssExpand[ i ] + suffix ] =\n\t\t\t\t\tparts[ i ] || parts[ i - 2 ] || parts[ 0 ];\n\t\t\t}\n\n\t\t\treturn expanded;\n\t\t}\n\t};\n\n\tif ( !rmargin.test( prefix ) ) {\n\t\tjQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber;\n\t}\n});\nvar r20 = /%20/g,\n\trbracket = /\\[\\]$/,\n\trCRLF = /\\r?\\n/g,\n\trsubmitterTypes = /^(?:submit|button|image|reset|file)$/i,\n\trsubmittable = /^(?:input|select|textarea|keygen)/i;\n\njQuery.fn.extend({\n\tserialize: function() {\n\t\treturn jQuery.param( this.serializeArray() );\n\t},\n\tserializeArray: function() {\n\t\treturn this.map(function(){\n\t\t\t// Can add propHook for \"elements\" to filter or add form elements\n\t\t\tvar elements = jQuery.prop( this, \"elements\" );\n\t\t\treturn elements ? jQuery.makeArray( elements ) : this;\n\t\t})\n\t\t.filter(function(){\n\t\t\tvar type = this.type;\n\t\t\t// Use .is(\":disabled\") so that fieldset[disabled] works\n\t\t\treturn this.name && !jQuery( this ).is( \":disabled\" ) &&\n\t\t\t\trsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) &&\n\t\t\t\t( this.checked || !manipulation_rcheckableType.test( type ) );\n\t\t})\n\t\t.map(function( i, elem ){\n\t\t\tvar val = jQuery( this ).val();\n\n\t\t\treturn val == null ?\n\t\t\t\tnull :\n\t\t\t\tjQuery.isArray( val ) ?\n\t\t\t\t\tjQuery.map( val, function( val ){\n\t\t\t\t\t\treturn { name: elem.name, value: val.replace( rCRLF, \"\\r\\n\" ) };\n\t\t\t\t\t}) :\n\t\t\t\t\t{ name: elem.name, value: val.replace( rCRLF, \"\\r\\n\" ) };\n\t\t}).get();\n\t}\n});\n\n//Serialize an array of form elements or a set of\n//key/values into a query string\njQuery.param = function( a, traditional ) {\n\tvar prefix,\n\t\ts = [],\n\t\tadd = function( key, value ) {\n\t\t\t// If value is a function, invoke it and return its value\n\t\t\tvalue = jQuery.isFunction( value ) ? value() : ( value == null ? \"\" : value );\n\t\t\ts[ s.length ] = encodeURIComponent( key ) + \"=\" + encodeURIComponent( value );\n\t\t};\n\n\t// Set traditional to true for jQuery <= 1.3.2 behavior.\n\tif ( traditional === undefined ) {\n\t\ttraditional = jQuery.ajaxSettings && jQuery.ajaxSettings.traditional;\n\t}\n\n\t// If an array was passed in, assume that it is an array of form elements.\n\tif ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {\n\t\t// Serialize the form elements\n\t\tjQuery.each( a, function() {\n\t\t\tadd( this.name, this.value );\n\t\t});\n\n\t} else {\n\t\t// If traditional, encode the \"old\" way (the way 1.3.2 or older\n\t\t// did it), otherwise encode params recursively.\n\t\tfor ( prefix in a ) {\n\t\t\tbuildParams( prefix, a[ prefix ], traditional, add );\n\t\t}\n\t}\n\n\t// Return the resulting serialization\n\treturn s.join( \"&\" ).replace( r20, \"+\" );\n};\n\nfunction buildParams( prefix, obj, traditional, add ) {\n\tvar name;\n\n\tif ( jQuery.isArray( obj ) ) {\n\t\t// Serialize array item.\n\t\tjQuery.each( obj, function( i, v ) {\n\t\t\tif ( traditional || rbracket.test( prefix ) ) {\n\t\t\t\t// Treat each array item as a scalar.\n\t\t\t\tadd( prefix, v );\n\n\t\t\t} else {\n\t\t\t\t// Item is non-scalar (array or object), encode its numeric index.\n\t\t\t\tbuildParams( prefix + \"[\" + ( typeof v === \"object\" ? i : \"\" ) + \"]\", v, traditional, add );\n\t\t\t}\n\t\t});\n\n\t} else if ( !traditional && jQuery.type( obj ) === \"object\" ) {\n\t\t// Serialize object item.\n\t\tfor ( name in obj ) {\n\t\t\tbuildParams( prefix + \"[\" + name + \"]\", obj[ name ], traditional, add );\n\t\t}\n\n\t} else {\n\t\t// Serialize scalar item.\n\t\tadd( prefix, obj );\n\t}\n}\njQuery.each( (\"blur focus focusin focusout load resize scroll unload click dblclick \" +\n\t\"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave \" +\n\t\"change select submit keydown keypress keyup error contextmenu\").split(\" \"), function( i, name ) {\n\n\t// Handle event binding\n\tjQuery.fn[ name ] = function( data, fn ) {\n\t\treturn arguments.length > 0 ?\n\t\t\tthis.on( name, null, data, fn ) :\n\t\t\tthis.trigger( name );\n\t};\n});\n\njQuery.fn.extend({\n\thover: function( fnOver, fnOut ) {\n\t\treturn this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );\n\t},\n\n\tbind: function( types, data, fn ) {\n\t\treturn this.on( types, null, data, fn );\n\t},\n\tunbind: function( types, fn ) {\n\t\treturn this.off( types, null, fn );\n\t},\n\n\tdelegate: function( selector, types, data, fn ) {\n\t\treturn this.on( types, selector, data, fn );\n\t},\n\tundelegate: function( selector, types, fn ) {\n\t\t// ( namespace ) or ( selector, types [, fn] )\n\t\treturn arguments.length === 1 ? this.off( selector, \"**\" ) : this.off( types, selector || \"**\", fn );\n\t}\n});\nvar\n\t// Document location\n\tajaxLocParts,\n\tajaxLocation,\n\tajax_nonce = jQuery.now(),\n\n\tajax_rquery = /\\?/,\n\trhash = /#.*$/,\n\trts = /([?&])_=[^&]*/,\n\trheaders = /^(.*?):[ \\t]*([^\\r\\n]*)\\r?$/mg, // IE leaves an \\r character at EOL\n\t// #7653, #8125, #8152: local protocol detection\n\trlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/,\n\trnoContent = /^(?:GET|HEAD)$/,\n\trprotocol = /^\\/\\//,\n\trurl = /^([\\w.+-]+:)(?:\\/\\/([^\\/?#:]*)(?::(\\d+)|)|)/,\n\n\t// Keep a copy of the old load method\n\t_load = jQuery.fn.load,\n\n\t/* Prefilters\n\t * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)\n\t * 2) These are called:\n\t *    - BEFORE asking for a transport\n\t *    - AFTER param serialization (s.data is a string if s.processData is true)\n\t * 3) key is the dataType\n\t * 4) the catchall symbol \"*\" can be used\n\t * 5) execution will start with transport dataType and THEN continue down to \"*\" if needed\n\t */\n\tprefilters = {},\n\n\t/* Transports bindings\n\t * 1) key is the dataType\n\t * 2) the catchall symbol \"*\" can be used\n\t * 3) selection will start with transport dataType and THEN go to \"*\" if needed\n\t */\n\ttransports = {},\n\n\t// Avoid comment-prolog char sequence (#10098); must appease lint and evade compression\n\tallTypes = \"*/\".concat(\"*\");\n\n// #8138, IE may throw an exception when accessing\n// a field from window.location if document.domain has been set\ntry {\n\tajaxLocation = location.href;\n} catch( e ) {\n\t// Use the href attribute of an A element\n\t// since IE will modify it given document.location\n\tajaxLocation = document.createElement( \"a\" );\n\tajaxLocation.href = \"\";\n\tajaxLocation = ajaxLocation.href;\n}\n\n// Segment location into parts\najaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || [];\n\n// Base \"constructor\" for jQuery.ajaxPrefilter and jQuery.ajaxTransport\nfunction addToPrefiltersOrTransports( structure ) {\n\n\t// dataTypeExpression is optional and defaults to \"*\"\n\treturn function( dataTypeExpression, func ) {\n\n\t\tif ( typeof dataTypeExpression !== \"string\" ) {\n\t\t\tfunc = dataTypeExpression;\n\t\t\tdataTypeExpression = \"*\";\n\t\t}\n\n\t\tvar dataType,\n\t\t\ti = 0,\n\t\t\tdataTypes = dataTypeExpression.toLowerCase().match( core_rnotwhite ) || [];\n\n\t\tif ( jQuery.isFunction( func ) ) {\n\t\t\t// For each dataType in the dataTypeExpression\n\t\t\twhile ( (dataType = dataTypes[i++]) ) {\n\t\t\t\t// Prepend if requested\n\t\t\t\tif ( dataType[0] === \"+\" ) {\n\t\t\t\t\tdataType = dataType.slice( 1 ) || \"*\";\n\t\t\t\t\t(structure[ dataType ] = structure[ dataType ] || []).unshift( func );\n\n\t\t\t\t// Otherwise append\n\t\t\t\t} else {\n\t\t\t\t\t(structure[ dataType ] = structure[ dataType ] || []).push( func );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t};\n}\n\n// Base inspection function for prefilters and transports\nfunction inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) {\n\n\tvar inspected = {},\n\t\tseekingTransport = ( structure === transports );\n\n\tfunction inspect( dataType ) {\n\t\tvar selected;\n\t\tinspected[ dataType ] = true;\n\t\tjQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) {\n\t\t\tvar dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR );\n\t\t\tif( typeof dataTypeOrTransport === \"string\" && !seekingTransport && !inspected[ dataTypeOrTransport ] ) {\n\t\t\t\toptions.dataTypes.unshift( dataTypeOrTransport );\n\t\t\t\tinspect( dataTypeOrTransport );\n\t\t\t\treturn false;\n\t\t\t} else if ( seekingTransport ) {\n\t\t\t\treturn !( selected = dataTypeOrTransport );\n\t\t\t}\n\t\t});\n\t\treturn selected;\n\t}\n\n\treturn inspect( options.dataTypes[ 0 ] ) || !inspected[ \"*\" ] && inspect( \"*\" );\n}\n\n// A special extend for ajax options\n// that takes \"flat\" options (not to be deep extended)\n// Fixes #9887\nfunction ajaxExtend( target, src ) {\n\tvar deep, key,\n\t\tflatOptions = jQuery.ajaxSettings.flatOptions || {};\n\n\tfor ( key in src ) {\n\t\tif ( src[ key ] !== undefined ) {\n\t\t\t( flatOptions[ key ] ? target : ( deep || (deep = {}) ) )[ key ] = src[ key ];\n\t\t}\n\t}\n\tif ( deep ) {\n\t\tjQuery.extend( true, target, deep );\n\t}\n\n\treturn target;\n}\n\njQuery.fn.load = function( url, params, callback ) {\n\tif ( typeof url !== \"string\" && _load ) {\n\t\treturn _load.apply( this, arguments );\n\t}\n\n\tvar selector, response, type,\n\t\tself = this,\n\t\toff = url.indexOf(\" \");\n\n\tif ( off >= 0 ) {\n\t\tselector = url.slice( off, url.length );\n\t\turl = url.slice( 0, off );\n\t}\n\n\t// If it's a function\n\tif ( jQuery.isFunction( params ) ) {\n\n\t\t// We assume that it's the callback\n\t\tcallback = params;\n\t\tparams = undefined;\n\n\t// Otherwise, build a param string\n\t} else if ( params && typeof params === \"object\" ) {\n\t\ttype = \"POST\";\n\t}\n\n\t// If we have elements to modify, make the request\n\tif ( self.length > 0 ) {\n\t\tjQuery.ajax({\n\t\t\turl: url,\n\n\t\t\t// if \"type\" variable is undefined, then \"GET\" method will be used\n\t\t\ttype: type,\n\t\t\tdataType: \"html\",\n\t\t\tdata: params\n\t\t}).done(function( responseText ) {\n\n\t\t\t// Save response for use in complete callback\n\t\t\tresponse = arguments;\n\n\t\t\tself.html( selector ?\n\n\t\t\t\t// If a selector was specified, locate the right elements in a dummy div\n\t\t\t\t// Exclude scripts to avoid IE 'Permission Denied' errors\n\t\t\t\tjQuery(\"<div>\").append( jQuery.parseHTML( responseText ) ).find( selector ) :\n\n\t\t\t\t// Otherwise use the full result\n\t\t\t\tresponseText );\n\n\t\t}).complete( callback && function( jqXHR, status ) {\n\t\t\tself.each( callback, response || [ jqXHR.responseText, status, jqXHR ] );\n\t\t});\n\t}\n\n\treturn this;\n};\n\n// Attach a bunch of functions for handling common AJAX events\njQuery.each( [ \"ajaxStart\", \"ajaxStop\", \"ajaxComplete\", \"ajaxError\", \"ajaxSuccess\", \"ajaxSend\" ], function( i, type ){\n\tjQuery.fn[ type ] = function( fn ){\n\t\treturn this.on( type, fn );\n\t};\n});\n\njQuery.extend({\n\n\t// Counter for holding the number of active queries\n\tactive: 0,\n\n\t// Last-Modified header cache for next request\n\tlastModified: {},\n\tetag: {},\n\n\tajaxSettings: {\n\t\turl: ajaxLocation,\n\t\ttype: \"GET\",\n\t\tisLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ),\n\t\tglobal: true,\n\t\tprocessData: true,\n\t\tasync: true,\n\t\tcontentType: \"application/x-www-form-urlencoded; charset=UTF-8\",\n\t\t/*\n\t\ttimeout: 0,\n\t\tdata: null,\n\t\tdataType: null,\n\t\tusername: null,\n\t\tpassword: null,\n\t\tcache: null,\n\t\tthrows: false,\n\t\ttraditional: false,\n\t\theaders: {},\n\t\t*/\n\n\t\taccepts: {\n\t\t\t\"*\": allTypes,\n\t\t\ttext: \"text/plain\",\n\t\t\thtml: \"text/html\",\n\t\t\txml: \"application/xml, text/xml\",\n\t\t\tjson: \"application/json, text/javascript\"\n\t\t},\n\n\t\tcontents: {\n\t\t\txml: /xml/,\n\t\t\thtml: /html/,\n\t\t\tjson: /json/\n\t\t},\n\n\t\tresponseFields: {\n\t\t\txml: \"responseXML\",\n\t\t\ttext: \"responseText\",\n\t\t\tjson: \"responseJSON\"\n\t\t},\n\n\t\t// Data converters\n\t\t// Keys separate source (or catchall \"*\") and destination types with a single space\n\t\tconverters: {\n\n\t\t\t// Convert anything to text\n\t\t\t\"* text\": String,\n\n\t\t\t// Text to html (true = no transformation)\n\t\t\t\"text html\": true,\n\n\t\t\t// Evaluate text as a json expression\n\t\t\t\"text json\": jQuery.parseJSON,\n\n\t\t\t// Parse text as xml\n\t\t\t\"text xml\": jQuery.parseXML\n\t\t},\n\n\t\t// For options that shouldn't be deep extended:\n\t\t// you can add your own custom options here if\n\t\t// and when you create one that shouldn't be\n\t\t// deep extended (see ajaxExtend)\n\t\tflatOptions: {\n\t\t\turl: true,\n\t\t\tcontext: true\n\t\t}\n\t},\n\n\t// Creates a full fledged settings object into target\n\t// with both ajaxSettings and settings fields.\n\t// If target is omitted, writes into ajaxSettings.\n\tajaxSetup: function( target, settings ) {\n\t\treturn settings ?\n\n\t\t\t// Building a settings object\n\t\t\tajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) :\n\n\t\t\t// Extending ajaxSettings\n\t\t\tajaxExtend( jQuery.ajaxSettings, target );\n\t},\n\n\tajaxPrefilter: addToPrefiltersOrTransports( prefilters ),\n\tajaxTransport: addToPrefiltersOrTransports( transports ),\n\n\t// Main method\n\tajax: function( url, options ) {\n\n\t\t// If url is an object, simulate pre-1.5 signature\n\t\tif ( typeof url === \"object\" ) {\n\t\t\toptions = url;\n\t\t\turl = undefined;\n\t\t}\n\n\t\t// Force options to be an object\n\t\toptions = options || {};\n\n\t\tvar // Cross-domain detection vars\n\t\t\tparts,\n\t\t\t// Loop variable\n\t\t\ti,\n\t\t\t// URL without anti-cache param\n\t\t\tcacheURL,\n\t\t\t// Response headers as string\n\t\t\tresponseHeadersString,\n\t\t\t// timeout handle\n\t\t\ttimeoutTimer,\n\n\t\t\t// To know if global events are to be dispatched\n\t\t\tfireGlobals,\n\n\t\t\ttransport,\n\t\t\t// Response headers\n\t\t\tresponseHeaders,\n\t\t\t// Create the final options object\n\t\t\ts = jQuery.ajaxSetup( {}, options ),\n\t\t\t// Callbacks context\n\t\t\tcallbackContext = s.context || s,\n\t\t\t// Context for global events is callbackContext if it is a DOM node or jQuery collection\n\t\t\tglobalEventContext = s.context && ( callbackContext.nodeType || callbackContext.jquery ) ?\n\t\t\t\tjQuery( callbackContext ) :\n\t\t\t\tjQuery.event,\n\t\t\t// Deferreds\n\t\t\tdeferred = jQuery.Deferred(),\n\t\t\tcompleteDeferred = jQuery.Callbacks(\"once memory\"),\n\t\t\t// Status-dependent callbacks\n\t\t\tstatusCode = s.statusCode || {},\n\t\t\t// Headers (they are sent all at once)\n\t\t\trequestHeaders = {},\n\t\t\trequestHeadersNames = {},\n\t\t\t// The jqXHR state\n\t\t\tstate = 0,\n\t\t\t// Default abort message\n\t\t\tstrAbort = \"canceled\",\n\t\t\t// Fake xhr\n\t\t\tjqXHR = {\n\t\t\t\treadyState: 0,\n\n\t\t\t\t// Builds headers hashtable if needed\n\t\t\t\tgetResponseHeader: function( key ) {\n\t\t\t\t\tvar match;\n\t\t\t\t\tif ( state === 2 ) {\n\t\t\t\t\t\tif ( !responseHeaders ) {\n\t\t\t\t\t\t\tresponseHeaders = {};\n\t\t\t\t\t\t\twhile ( (match = rheaders.exec( responseHeadersString )) ) {\n\t\t\t\t\t\t\t\tresponseHeaders[ match[1].toLowerCase() ] = match[ 2 ];\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tmatch = responseHeaders[ key.toLowerCase() ];\n\t\t\t\t\t}\n\t\t\t\t\treturn match == null ? null : match;\n\t\t\t\t},\n\n\t\t\t\t// Raw string\n\t\t\t\tgetAllResponseHeaders: function() {\n\t\t\t\t\treturn state === 2 ? responseHeadersString : null;\n\t\t\t\t},\n\n\t\t\t\t// Caches the header\n\t\t\t\tsetRequestHeader: function( name, value ) {\n\t\t\t\t\tvar lname = name.toLowerCase();\n\t\t\t\t\tif ( !state ) {\n\t\t\t\t\t\tname = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name;\n\t\t\t\t\t\trequestHeaders[ name ] = value;\n\t\t\t\t\t}\n\t\t\t\t\treturn this;\n\t\t\t\t},\n\n\t\t\t\t// Overrides response content-type header\n\t\t\t\toverrideMimeType: function( type ) {\n\t\t\t\t\tif ( !state ) {\n\t\t\t\t\t\ts.mimeType = type;\n\t\t\t\t\t}\n\t\t\t\t\treturn this;\n\t\t\t\t},\n\n\t\t\t\t// Status-dependent callbacks\n\t\t\t\tstatusCode: function( map ) {\n\t\t\t\t\tvar code;\n\t\t\t\t\tif ( map ) {\n\t\t\t\t\t\tif ( state < 2 ) {\n\t\t\t\t\t\t\tfor ( code in map ) {\n\t\t\t\t\t\t\t\t// Lazy-add the new callback in a way that preserves old ones\n\t\t\t\t\t\t\t\tstatusCode[ code ] = [ statusCode[ code ], map[ code ] ];\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// Execute the appropriate callbacks\n\t\t\t\t\t\t\tjqXHR.always( map[ jqXHR.status ] );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn this;\n\t\t\t\t},\n\n\t\t\t\t// Cancel the request\n\t\t\t\tabort: function( statusText ) {\n\t\t\t\t\tvar finalText = statusText || strAbort;\n\t\t\t\t\tif ( transport ) {\n\t\t\t\t\t\ttransport.abort( finalText );\n\t\t\t\t\t}\n\t\t\t\t\tdone( 0, finalText );\n\t\t\t\t\treturn this;\n\t\t\t\t}\n\t\t\t};\n\n\t\t// Attach deferreds\n\t\tdeferred.promise( jqXHR ).complete = completeDeferred.add;\n\t\tjqXHR.success = jqXHR.done;\n\t\tjqXHR.error = jqXHR.fail;\n\n\t\t// Remove hash character (#7531: and string promotion)\n\t\t// Add protocol if not provided (#5866: IE7 issue with protocol-less urls)\n\t\t// Handle falsy url in the settings object (#10093: consistency with old signature)\n\t\t// We also use the url parameter if available\n\t\ts.url = ( ( url || s.url || ajaxLocation ) + \"\" ).replace( rhash, \"\" ).replace( rprotocol, ajaxLocParts[ 1 ] + \"//\" );\n\n\t\t// Alias method option to type as per ticket #12004\n\t\ts.type = options.method || options.type || s.method || s.type;\n\n\t\t// Extract dataTypes list\n\t\ts.dataTypes = jQuery.trim( s.dataType || \"*\" ).toLowerCase().match( core_rnotwhite ) || [\"\"];\n\n\t\t// A cross-domain request is in order when we have a protocol:host:port mismatch\n\t\tif ( s.crossDomain == null ) {\n\t\t\tparts = rurl.exec( s.url.toLowerCase() );\n\t\t\ts.crossDomain = !!( parts &&\n\t\t\t\t( parts[ 1 ] !== ajaxLocParts[ 1 ] || parts[ 2 ] !== ajaxLocParts[ 2 ] ||\n\t\t\t\t\t( parts[ 3 ] || ( parts[ 1 ] === \"http:\" ? \"80\" : \"443\" ) ) !==\n\t\t\t\t\t\t( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === \"http:\" ? \"80\" : \"443\" ) ) )\n\t\t\t);\n\t\t}\n\n\t\t// Convert data if not already a string\n\t\tif ( s.data && s.processData && typeof s.data !== \"string\" ) {\n\t\t\ts.data = jQuery.param( s.data, s.traditional );\n\t\t}\n\n\t\t// Apply prefilters\n\t\tinspectPrefiltersOrTransports( prefilters, s, options, jqXHR );\n\n\t\t// If request was aborted inside a prefilter, stop there\n\t\tif ( state === 2 ) {\n\t\t\treturn jqXHR;\n\t\t}\n\n\t\t// We can fire global events as of now if asked to\n\t\tfireGlobals = s.global;\n\n\t\t// Watch for a new set of requests\n\t\tif ( fireGlobals && jQuery.active++ === 0 ) {\n\t\t\tjQuery.event.trigger(\"ajaxStart\");\n\t\t}\n\n\t\t// Uppercase the type\n\t\ts.type = s.type.toUpperCase();\n\n\t\t// Determine if request has content\n\t\ts.hasContent = !rnoContent.test( s.type );\n\n\t\t// Save the URL in case we're toying with the If-Modified-Since\n\t\t// and/or If-None-Match header later on\n\t\tcacheURL = s.url;\n\n\t\t// More options handling for requests with no content\n\t\tif ( !s.hasContent ) {\n\n\t\t\t// If data is available, append data to url\n\t\t\tif ( s.data ) {\n\t\t\t\tcacheURL = ( s.url += ( ajax_rquery.test( cacheURL ) ? \"&\" : \"?\" ) + s.data );\n\t\t\t\t// #9682: remove data so that it's not used in an eventual retry\n\t\t\t\tdelete s.data;\n\t\t\t}\n\n\t\t\t// Add anti-cache in url if needed\n\t\t\tif ( s.cache === false ) {\n\t\t\t\ts.url = rts.test( cacheURL ) ?\n\n\t\t\t\t\t// If there is already a '_' parameter, set its value\n\t\t\t\t\tcacheURL.replace( rts, \"$1_=\" + ajax_nonce++ ) :\n\n\t\t\t\t\t// Otherwise add one to the end\n\t\t\t\t\tcacheURL + ( ajax_rquery.test( cacheURL ) ? \"&\" : \"?\" ) + \"_=\" + ajax_nonce++;\n\t\t\t}\n\t\t}\n\n\t\t// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.\n\t\tif ( s.ifModified ) {\n\t\t\tif ( jQuery.lastModified[ cacheURL ] ) {\n\t\t\t\tjqXHR.setRequestHeader( \"If-Modified-Since\", jQuery.lastModified[ cacheURL ] );\n\t\t\t}\n\t\t\tif ( jQuery.etag[ cacheURL ] ) {\n\t\t\t\tjqXHR.setRequestHeader( \"If-None-Match\", jQuery.etag[ cacheURL ] );\n\t\t\t}\n\t\t}\n\n\t\t// Set the correct header, if data is being sent\n\t\tif ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {\n\t\t\tjqXHR.setRequestHeader( \"Content-Type\", s.contentType );\n\t\t}\n\n\t\t// Set the Accepts header for the server, depending on the dataType\n\t\tjqXHR.setRequestHeader(\n\t\t\t\"Accept\",\n\t\t\ts.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ?\n\t\t\t\ts.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== \"*\" ? \", \" + allTypes + \"; q=0.01\" : \"\" ) :\n\t\t\t\ts.accepts[ \"*\" ]\n\t\t);\n\n\t\t// Check for headers option\n\t\tfor ( i in s.headers ) {\n\t\t\tjqXHR.setRequestHeader( i, s.headers[ i ] );\n\t\t}\n\n\t\t// Allow custom headers/mimetypes and early abort\n\t\tif ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) {\n\t\t\t// Abort if not done already and return\n\t\t\treturn jqXHR.abort();\n\t\t}\n\n\t\t// aborting is no longer a cancellation\n\t\tstrAbort = \"abort\";\n\n\t\t// Install callbacks on deferreds\n\t\tfor ( i in { success: 1, error: 1, complete: 1 } ) {\n\t\t\tjqXHR[ i ]( s[ i ] );\n\t\t}\n\n\t\t// Get transport\n\t\ttransport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );\n\n\t\t// If no transport, we auto-abort\n\t\tif ( !transport ) {\n\t\t\tdone( -1, \"No Transport\" );\n\t\t} else {\n\t\t\tjqXHR.readyState = 1;\n\n\t\t\t// Send global event\n\t\t\tif ( fireGlobals ) {\n\t\t\t\tglobalEventContext.trigger( \"ajaxSend\", [ jqXHR, s ] );\n\t\t\t}\n\t\t\t// Timeout\n\t\t\tif ( s.async && s.timeout > 0 ) {\n\t\t\t\ttimeoutTimer = setTimeout(function() {\n\t\t\t\t\tjqXHR.abort(\"timeout\");\n\t\t\t\t}, s.timeout );\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tstate = 1;\n\t\t\t\ttransport.send( requestHeaders, done );\n\t\t\t} catch ( e ) {\n\t\t\t\t// Propagate exception as error if not done\n\t\t\t\tif ( state < 2 ) {\n\t\t\t\t\tdone( -1, e );\n\t\t\t\t// Simply rethrow otherwise\n\t\t\t\t} else {\n\t\t\t\t\tthrow e;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Callback for when everything is done\n\t\tfunction done( status, nativeStatusText, responses, headers ) {\n\t\t\tvar isSuccess, success, error, response, modified,\n\t\t\t\tstatusText = nativeStatusText;\n\n\t\t\t// Called once\n\t\t\tif ( state === 2 ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// State is \"done\" now\n\t\t\tstate = 2;\n\n\t\t\t// Clear timeout if it exists\n\t\t\tif ( timeoutTimer ) {\n\t\t\t\tclearTimeout( timeoutTimer );\n\t\t\t}\n\n\t\t\t// Dereference transport for early garbage collection\n\t\t\t// (no matter how long the jqXHR object will be used)\n\t\t\ttransport = undefined;\n\n\t\t\t// Cache response headers\n\t\t\tresponseHeadersString = headers || \"\";\n\n\t\t\t// Set readyState\n\t\t\tjqXHR.readyState = status > 0 ? 4 : 0;\n\n\t\t\t// Determine if successful\n\t\t\tisSuccess = status >= 200 && status < 300 || status === 304;\n\n\t\t\t// Get response data\n\t\t\tif ( responses ) {\n\t\t\t\tresponse = ajaxHandleResponses( s, jqXHR, responses );\n\t\t\t}\n\n\t\t\t// Convert no matter what (that way responseXXX fields are always set)\n\t\t\tresponse = ajaxConvert( s, response, jqXHR, isSuccess );\n\n\t\t\t// If successful, handle type chaining\n\t\t\tif ( isSuccess ) {\n\n\t\t\t\t// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.\n\t\t\t\tif ( s.ifModified ) {\n\t\t\t\t\tmodified = jqXHR.getResponseHeader(\"Last-Modified\");\n\t\t\t\t\tif ( modified ) {\n\t\t\t\t\t\tjQuery.lastModified[ cacheURL ] = modified;\n\t\t\t\t\t}\n\t\t\t\t\tmodified = jqXHR.getResponseHeader(\"etag\");\n\t\t\t\t\tif ( modified ) {\n\t\t\t\t\t\tjQuery.etag[ cacheURL ] = modified;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// if no content\n\t\t\t\tif ( status === 204 || s.type === \"HEAD\" ) {\n\t\t\t\t\tstatusText = \"nocontent\";\n\n\t\t\t\t// if not modified\n\t\t\t\t} else if ( status === 304 ) {\n\t\t\t\t\tstatusText = \"notmodified\";\n\n\t\t\t\t// If we have data, let's convert it\n\t\t\t\t} else {\n\t\t\t\t\tstatusText = response.state;\n\t\t\t\t\tsuccess = response.data;\n\t\t\t\t\terror = response.error;\n\t\t\t\t\tisSuccess = !error;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// We extract error from statusText\n\t\t\t\t// then normalize statusText and status for non-aborts\n\t\t\t\terror = statusText;\n\t\t\t\tif ( status || !statusText ) {\n\t\t\t\t\tstatusText = \"error\";\n\t\t\t\t\tif ( status < 0 ) {\n\t\t\t\t\t\tstatus = 0;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Set data for the fake xhr object\n\t\t\tjqXHR.status = status;\n\t\t\tjqXHR.statusText = ( nativeStatusText || statusText ) + \"\";\n\n\t\t\t// Success/Error\n\t\t\tif ( isSuccess ) {\n\t\t\t\tdeferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );\n\t\t\t} else {\n\t\t\t\tdeferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );\n\t\t\t}\n\n\t\t\t// Status-dependent callbacks\n\t\t\tjqXHR.statusCode( statusCode );\n\t\t\tstatusCode = undefined;\n\n\t\t\tif ( fireGlobals ) {\n\t\t\t\tglobalEventContext.trigger( isSuccess ? \"ajaxSuccess\" : \"ajaxError\",\n\t\t\t\t\t[ jqXHR, s, isSuccess ? success : error ] );\n\t\t\t}\n\n\t\t\t// Complete\n\t\t\tcompleteDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );\n\n\t\t\tif ( fireGlobals ) {\n\t\t\t\tglobalEventContext.trigger( \"ajaxComplete\", [ jqXHR, s ] );\n\t\t\t\t// Handle the global AJAX counter\n\t\t\t\tif ( !( --jQuery.active ) ) {\n\t\t\t\t\tjQuery.event.trigger(\"ajaxStop\");\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn jqXHR;\n\t},\n\n\tgetJSON: function( url, data, callback ) {\n\t\treturn jQuery.get( url, data, callback, \"json\" );\n\t},\n\n\tgetScript: function( url, callback ) {\n\t\treturn jQuery.get( url, undefined, callback, \"script\" );\n\t}\n});\n\njQuery.each( [ \"get\", \"post\" ], function( i, method ) {\n\tjQuery[ method ] = function( url, data, callback, type ) {\n\t\t// shift arguments if data argument was omitted\n\t\tif ( jQuery.isFunction( data ) ) {\n\t\t\ttype = type || callback;\n\t\t\tcallback = data;\n\t\t\tdata = undefined;\n\t\t}\n\n\t\treturn jQuery.ajax({\n\t\t\turl: url,\n\t\t\ttype: method,\n\t\t\tdataType: type,\n\t\t\tdata: data,\n\t\t\tsuccess: callback\n\t\t});\n\t};\n});\n\n/* Handles responses to an ajax request:\n * - finds the right dataType (mediates between content-type and expected dataType)\n * - returns the corresponding response\n */\nfunction ajaxHandleResponses( s, jqXHR, responses ) {\n\tvar firstDataType, ct, finalDataType, type,\n\t\tcontents = s.contents,\n\t\tdataTypes = s.dataTypes;\n\n\t// Remove auto dataType and get content-type in the process\n\twhile( dataTypes[ 0 ] === \"*\" ) {\n\t\tdataTypes.shift();\n\t\tif ( ct === undefined ) {\n\t\t\tct = s.mimeType || jqXHR.getResponseHeader(\"Content-Type\");\n\t\t}\n\t}\n\n\t// Check if we're dealing with a known content-type\n\tif ( ct ) {\n\t\tfor ( type in contents ) {\n\t\t\tif ( contents[ type ] && contents[ type ].test( ct ) ) {\n\t\t\t\tdataTypes.unshift( type );\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Check to see if we have a response for the expected dataType\n\tif ( dataTypes[ 0 ] in responses ) {\n\t\tfinalDataType = dataTypes[ 0 ];\n\t} else {\n\t\t// Try convertible dataTypes\n\t\tfor ( type in responses ) {\n\t\t\tif ( !dataTypes[ 0 ] || s.converters[ type + \" \" + dataTypes[0] ] ) {\n\t\t\t\tfinalDataType = type;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif ( !firstDataType ) {\n\t\t\t\tfirstDataType = type;\n\t\t\t}\n\t\t}\n\t\t// Or just use first one\n\t\tfinalDataType = finalDataType || firstDataType;\n\t}\n\n\t// If we found a dataType\n\t// We add the dataType to the list if needed\n\t// and return the corresponding response\n\tif ( finalDataType ) {\n\t\tif ( finalDataType !== dataTypes[ 0 ] ) {\n\t\t\tdataTypes.unshift( finalDataType );\n\t\t}\n\t\treturn responses[ finalDataType ];\n\t}\n}\n\n/* Chain conversions given the request and the original response\n * Also sets the responseXXX fields on the jqXHR instance\n */\nfunction ajaxConvert( s, response, jqXHR, isSuccess ) {\n\tvar conv2, current, conv, tmp, prev,\n\t\tconverters = {},\n\t\t// Work with a copy of dataTypes in case we need to modify it for conversion\n\t\tdataTypes = s.dataTypes.slice();\n\n\t// Create converters map with lowercased keys\n\tif ( dataTypes[ 1 ] ) {\n\t\tfor ( conv in s.converters ) {\n\t\t\tconverters[ conv.toLowerCase() ] = s.converters[ conv ];\n\t\t}\n\t}\n\n\tcurrent = dataTypes.shift();\n\n\t// Convert to each sequential dataType\n\twhile ( current ) {\n\n\t\tif ( s.responseFields[ current ] ) {\n\t\t\tjqXHR[ s.responseFields[ current ] ] = response;\n\t\t}\n\n\t\t// Apply the dataFilter if provided\n\t\tif ( !prev && isSuccess && s.dataFilter ) {\n\t\t\tresponse = s.dataFilter( response, s.dataType );\n\t\t}\n\n\t\tprev = current;\n\t\tcurrent = dataTypes.shift();\n\n\t\tif ( current ) {\n\n\t\t\t// There's only work to do if current dataType is non-auto\n\t\t\tif ( current === \"*\" ) {\n\n\t\t\t\tcurrent = prev;\n\n\t\t\t// Convert response if prev dataType is non-auto and differs from current\n\t\t\t} else if ( prev !== \"*\" && prev !== current ) {\n\n\t\t\t\t// Seek a direct converter\n\t\t\t\tconv = converters[ prev + \" \" + current ] || converters[ \"* \" + current ];\n\n\t\t\t\t// If none found, seek a pair\n\t\t\t\tif ( !conv ) {\n\t\t\t\t\tfor ( conv2 in converters ) {\n\n\t\t\t\t\t\t// If conv2 outputs current\n\t\t\t\t\t\ttmp = conv2.split( \" \" );\n\t\t\t\t\t\tif ( tmp[ 1 ] === current ) {\n\n\t\t\t\t\t\t\t// If prev can be converted to accepted input\n\t\t\t\t\t\t\tconv = converters[ prev + \" \" + tmp[ 0 ] ] ||\n\t\t\t\t\t\t\t\tconverters[ \"* \" + tmp[ 0 ] ];\n\t\t\t\t\t\t\tif ( conv ) {\n\t\t\t\t\t\t\t\t// Condense equivalence converters\n\t\t\t\t\t\t\t\tif ( conv === true ) {\n\t\t\t\t\t\t\t\t\tconv = converters[ conv2 ];\n\n\t\t\t\t\t\t\t\t// Otherwise, insert the intermediate dataType\n\t\t\t\t\t\t\t\t} else if ( converters[ conv2 ] !== true ) {\n\t\t\t\t\t\t\t\t\tcurrent = tmp[ 0 ];\n\t\t\t\t\t\t\t\t\tdataTypes.unshift( tmp[ 1 ] );\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Apply converter (if not an equivalence)\n\t\t\t\tif ( conv !== true ) {\n\n\t\t\t\t\t// Unless errors are allowed to bubble, catch and return them\n\t\t\t\t\tif ( conv && s[ \"throws\" ] ) {\n\t\t\t\t\t\tresponse = conv( response );\n\t\t\t\t\t} else {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tresponse = conv( response );\n\t\t\t\t\t\t} catch ( e ) {\n\t\t\t\t\t\t\treturn { state: \"parsererror\", error: conv ? e : \"No conversion from \" + prev + \" to \" + current };\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn { state: \"success\", data: response };\n}\n// Install script dataType\njQuery.ajaxSetup({\n\taccepts: {\n\t\tscript: \"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript\"\n\t},\n\tcontents: {\n\t\tscript: /(?:java|ecma)script/\n\t},\n\tconverters: {\n\t\t\"text script\": function( text ) {\n\t\t\tjQuery.globalEval( text );\n\t\t\treturn text;\n\t\t}\n\t}\n});\n\n// Handle cache's special case and global\njQuery.ajaxPrefilter( \"script\", function( s ) {\n\tif ( s.cache === undefined ) {\n\t\ts.cache = false;\n\t}\n\tif ( s.crossDomain ) {\n\t\ts.type = \"GET\";\n\t\ts.global = false;\n\t}\n});\n\n// Bind script tag hack transport\njQuery.ajaxTransport( \"script\", function(s) {\n\n\t// This transport only deals with cross domain requests\n\tif ( s.crossDomain ) {\n\n\t\tvar script,\n\t\t\thead = document.head || jQuery(\"head\")[0] || document.documentElement;\n\n\t\treturn {\n\n\t\t\tsend: function( _, callback ) {\n\n\t\t\t\tscript = document.createElement(\"script\");\n\n\t\t\t\tscript.async = true;\n\n\t\t\t\tif ( s.scriptCharset ) {\n\t\t\t\t\tscript.charset = s.scriptCharset;\n\t\t\t\t}\n\n\t\t\t\tscript.src = s.url;\n\n\t\t\t\t// Attach handlers for all browsers\n\t\t\t\tscript.onload = script.onreadystatechange = function( _, isAbort ) {\n\n\t\t\t\t\tif ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) {\n\n\t\t\t\t\t\t// Handle memory leak in IE\n\t\t\t\t\t\tscript.onload = script.onreadystatechange = null;\n\n\t\t\t\t\t\t// Remove the script\n\t\t\t\t\t\tif ( script.parentNode ) {\n\t\t\t\t\t\t\tscript.parentNode.removeChild( script );\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Dereference the script\n\t\t\t\t\t\tscript = null;\n\n\t\t\t\t\t\t// Callback if not abort\n\t\t\t\t\t\tif ( !isAbort ) {\n\t\t\t\t\t\t\tcallback( 200, \"success\" );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\t// Circumvent IE6 bugs with base elements (#2709 and #4378) by prepending\n\t\t\t\t// Use native DOM manipulation to avoid our domManip AJAX trickery\n\t\t\t\thead.insertBefore( script, head.firstChild );\n\t\t\t},\n\n\t\t\tabort: function() {\n\t\t\t\tif ( script ) {\n\t\t\t\t\tscript.onload( undefined, true );\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t}\n});\nvar oldCallbacks = [],\n\trjsonp = /(=)\\?(?=&|$)|\\?\\?/;\n\n// Default jsonp settings\njQuery.ajaxSetup({\n\tjsonp: \"callback\",\n\tjsonpCallback: function() {\n\t\tvar callback = oldCallbacks.pop() || ( jQuery.expando + \"_\" + ( ajax_nonce++ ) );\n\t\tthis[ callback ] = true;\n\t\treturn callback;\n\t}\n});\n\n// Detect, normalize options and install callbacks for jsonp requests\njQuery.ajaxPrefilter( \"json jsonp\", function( s, originalSettings, jqXHR ) {\n\n\tvar callbackName, overwritten, responseContainer,\n\t\tjsonProp = s.jsonp !== false && ( rjsonp.test( s.url ) ?\n\t\t\t\"url\" :\n\t\t\ttypeof s.data === \"string\" && !( s.contentType || \"\" ).indexOf(\"application/x-www-form-urlencoded\") && rjsonp.test( s.data ) && \"data\"\n\t\t);\n\n\t// Handle iff the expected data type is \"jsonp\" or we have a parameter to set\n\tif ( jsonProp || s.dataTypes[ 0 ] === \"jsonp\" ) {\n\n\t\t// Get callback name, remembering preexisting value associated with it\n\t\tcallbackName = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ?\n\t\t\ts.jsonpCallback() :\n\t\t\ts.jsonpCallback;\n\n\t\t// Insert callback into url or form data\n\t\tif ( jsonProp ) {\n\t\t\ts[ jsonProp ] = s[ jsonProp ].replace( rjsonp, \"$1\" + callbackName );\n\t\t} else if ( s.jsonp !== false ) {\n\t\t\ts.url += ( ajax_rquery.test( s.url ) ? \"&\" : \"?\" ) + s.jsonp + \"=\" + callbackName;\n\t\t}\n\n\t\t// Use data converter to retrieve json after script execution\n\t\ts.converters[\"script json\"] = function() {\n\t\t\tif ( !responseContainer ) {\n\t\t\t\tjQuery.error( callbackName + \" was not called\" );\n\t\t\t}\n\t\t\treturn responseContainer[ 0 ];\n\t\t};\n\n\t\t// force json dataType\n\t\ts.dataTypes[ 0 ] = \"json\";\n\n\t\t// Install callback\n\t\toverwritten = window[ callbackName ];\n\t\twindow[ callbackName ] = function() {\n\t\t\tresponseContainer = arguments;\n\t\t};\n\n\t\t// Clean-up function (fires after converters)\n\t\tjqXHR.always(function() {\n\t\t\t// Restore preexisting value\n\t\t\twindow[ callbackName ] = overwritten;\n\n\t\t\t// Save back as free\n\t\t\tif ( s[ callbackName ] ) {\n\t\t\t\t// make sure that re-using the options doesn't screw things around\n\t\t\t\ts.jsonpCallback = originalSettings.jsonpCallback;\n\n\t\t\t\t// save the callback name for future use\n\t\t\t\toldCallbacks.push( callbackName );\n\t\t\t}\n\n\t\t\t// Call if it was a function and we have a response\n\t\t\tif ( responseContainer && jQuery.isFunction( overwritten ) ) {\n\t\t\t\toverwritten( responseContainer[ 0 ] );\n\t\t\t}\n\n\t\t\tresponseContainer = overwritten = undefined;\n\t\t});\n\n\t\t// Delegate to script\n\t\treturn \"script\";\n\t}\n});\nvar xhrCallbacks, xhrSupported,\n\txhrId = 0,\n\t// #5280: Internet Explorer will keep connections alive if we don't abort on unload\n\txhrOnUnloadAbort = window.ActiveXObject && function() {\n\t\t// Abort all pending requests\n\t\tvar key;\n\t\tfor ( key in xhrCallbacks ) {\n\t\t\txhrCallbacks[ key ]( undefined, true );\n\t\t}\n\t};\n\n// Functions to create xhrs\nfunction createStandardXHR() {\n\ttry {\n\t\treturn new window.XMLHttpRequest();\n\t} catch( e ) {}\n}\n\nfunction createActiveXHR() {\n\ttry {\n\t\treturn new window.ActiveXObject(\"Microsoft.XMLHTTP\");\n\t} catch( e ) {}\n}\n\n// Create the request object\n// (This is still attached to ajaxSettings for backward compatibility)\njQuery.ajaxSettings.xhr = window.ActiveXObject ?\n\t/* Microsoft failed to properly\n\t * implement the XMLHttpRequest in IE7 (can't request local files),\n\t * so we use the ActiveXObject when it is available\n\t * Additionally XMLHttpRequest can be disabled in IE7/IE8 so\n\t * we need a fallback.\n\t */\n\tfunction() {\n\t\treturn !this.isLocal && createStandardXHR() || createActiveXHR();\n\t} :\n\t// For all other browsers, use the standard XMLHttpRequest object\n\tcreateStandardXHR;\n\n// Determine support properties\nxhrSupported = jQuery.ajaxSettings.xhr();\njQuery.support.cors = !!xhrSupported && ( \"withCredentials\" in xhrSupported );\nxhrSupported = jQuery.support.ajax = !!xhrSupported;\n\n// Create transport if the browser can provide an xhr\nif ( xhrSupported ) {\n\n\tjQuery.ajaxTransport(function( s ) {\n\t\t// Cross domain only allowed if supported through XMLHttpRequest\n\t\tif ( !s.crossDomain || jQuery.support.cors ) {\n\n\t\t\tvar callback;\n\n\t\t\treturn {\n\t\t\t\tsend: function( headers, complete ) {\n\n\t\t\t\t\t// Get a new xhr\n\t\t\t\t\tvar handle, i,\n\t\t\t\t\t\txhr = s.xhr();\n\n\t\t\t\t\t// Open the socket\n\t\t\t\t\t// Passing null username, generates a login popup on Opera (#2865)\n\t\t\t\t\tif ( s.username ) {\n\t\t\t\t\t\txhr.open( s.type, s.url, s.async, s.username, s.password );\n\t\t\t\t\t} else {\n\t\t\t\t\t\txhr.open( s.type, s.url, s.async );\n\t\t\t\t\t}\n\n\t\t\t\t\t// Apply custom fields if provided\n\t\t\t\t\tif ( s.xhrFields ) {\n\t\t\t\t\t\tfor ( i in s.xhrFields ) {\n\t\t\t\t\t\t\txhr[ i ] = s.xhrFields[ i ];\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Override mime type if needed\n\t\t\t\t\tif ( s.mimeType && xhr.overrideMimeType ) {\n\t\t\t\t\t\txhr.overrideMimeType( s.mimeType );\n\t\t\t\t\t}\n\n\t\t\t\t\t// X-Requested-With header\n\t\t\t\t\t// For cross-domain requests, seeing as conditions for a preflight are\n\t\t\t\t\t// akin to a jigsaw puzzle, we simply never set it to be sure.\n\t\t\t\t\t// (it can always be set on a per-request basis or even using ajaxSetup)\n\t\t\t\t\t// For same-domain requests, won't change header if already provided.\n\t\t\t\t\tif ( !s.crossDomain && !headers[\"X-Requested-With\"] ) {\n\t\t\t\t\t\theaders[\"X-Requested-With\"] = \"XMLHttpRequest\";\n\t\t\t\t\t}\n\n\t\t\t\t\t// Need an extra try/catch for cross domain requests in Firefox 3\n\t\t\t\t\ttry {\n\t\t\t\t\t\tfor ( i in headers ) {\n\t\t\t\t\t\t\txhr.setRequestHeader( i, headers[ i ] );\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch( err ) {}\n\n\t\t\t\t\t// Do send the request\n\t\t\t\t\t// This may raise an exception which is actually\n\t\t\t\t\t// handled in jQuery.ajax (so no try/catch here)\n\t\t\t\t\txhr.send( ( s.hasContent && s.data ) || null );\n\n\t\t\t\t\t// Listener\n\t\t\t\t\tcallback = function( _, isAbort ) {\n\t\t\t\t\t\tvar status, responseHeaders, statusText, responses;\n\n\t\t\t\t\t\t// Firefox throws exceptions when accessing properties\n\t\t\t\t\t\t// of an xhr when a network error occurred\n\t\t\t\t\t\t// http://helpful.knobs-dials.com/index.php/Component_returned_failure_code:_0x80040111_(NS_ERROR_NOT_AVAILABLE)\n\t\t\t\t\t\ttry {\n\n\t\t\t\t\t\t\t// Was never called and is aborted or complete\n\t\t\t\t\t\t\tif ( callback && ( isAbort || xhr.readyState === 4 ) ) {\n\n\t\t\t\t\t\t\t\t// Only called once\n\t\t\t\t\t\t\t\tcallback = undefined;\n\n\t\t\t\t\t\t\t\t// Do not keep as active anymore\n\t\t\t\t\t\t\t\tif ( handle ) {\n\t\t\t\t\t\t\t\t\txhr.onreadystatechange = jQuery.noop;\n\t\t\t\t\t\t\t\t\tif ( xhrOnUnloadAbort ) {\n\t\t\t\t\t\t\t\t\t\tdelete xhrCallbacks[ handle ];\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t// If it's an abort\n\t\t\t\t\t\t\t\tif ( isAbort ) {\n\t\t\t\t\t\t\t\t\t// Abort it manually if needed\n\t\t\t\t\t\t\t\t\tif ( xhr.readyState !== 4 ) {\n\t\t\t\t\t\t\t\t\t\txhr.abort();\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tresponses = {};\n\t\t\t\t\t\t\t\t\tstatus = xhr.status;\n\t\t\t\t\t\t\t\t\tresponseHeaders = xhr.getAllResponseHeaders();\n\n\t\t\t\t\t\t\t\t\t// When requesting binary data, IE6-9 will throw an exception\n\t\t\t\t\t\t\t\t\t// on any attempt to access responseText (#11426)\n\t\t\t\t\t\t\t\t\tif ( typeof xhr.responseText === \"string\" ) {\n\t\t\t\t\t\t\t\t\t\tresponses.text = xhr.responseText;\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t// Firefox throws an exception when accessing\n\t\t\t\t\t\t\t\t\t// statusText for faulty cross-domain requests\n\t\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t\tstatusText = xhr.statusText;\n\t\t\t\t\t\t\t\t\t} catch( e ) {\n\t\t\t\t\t\t\t\t\t\t// We normalize with Webkit giving an empty statusText\n\t\t\t\t\t\t\t\t\t\tstatusText = \"\";\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t// Filter status for non standard behaviors\n\n\t\t\t\t\t\t\t\t\t// If the request is local and we have data: assume a success\n\t\t\t\t\t\t\t\t\t// (success with no data won't get notified, that's the best we\n\t\t\t\t\t\t\t\t\t// can do given current implementations)\n\t\t\t\t\t\t\t\t\tif ( !status && s.isLocal && !s.crossDomain ) {\n\t\t\t\t\t\t\t\t\t\tstatus = responses.text ? 200 : 404;\n\t\t\t\t\t\t\t\t\t// IE - #1450: sometimes returns 1223 when it should be 204\n\t\t\t\t\t\t\t\t\t} else if ( status === 1223 ) {\n\t\t\t\t\t\t\t\t\t\tstatus = 204;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} catch( firefoxAccessException ) {\n\t\t\t\t\t\t\tif ( !isAbort ) {\n\t\t\t\t\t\t\t\tcomplete( -1, firefoxAccessException );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Call complete if needed\n\t\t\t\t\t\tif ( responses ) {\n\t\t\t\t\t\t\tcomplete( status, statusText, responses, responseHeaders );\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\n\t\t\t\t\tif ( !s.async ) {\n\t\t\t\t\t\t// if we're in sync mode we fire the callback\n\t\t\t\t\t\tcallback();\n\t\t\t\t\t} else if ( xhr.readyState === 4 ) {\n\t\t\t\t\t\t// (IE6 & IE7) if it's in cache and has been\n\t\t\t\t\t\t// retrieved directly we need to fire the callback\n\t\t\t\t\t\tsetTimeout( callback );\n\t\t\t\t\t} else {\n\t\t\t\t\t\thandle = ++xhrId;\n\t\t\t\t\t\tif ( xhrOnUnloadAbort ) {\n\t\t\t\t\t\t\t// Create the active xhrs callbacks list if needed\n\t\t\t\t\t\t\t// and attach the unload handler\n\t\t\t\t\t\t\tif ( !xhrCallbacks ) {\n\t\t\t\t\t\t\t\txhrCallbacks = {};\n\t\t\t\t\t\t\t\tjQuery( window ).unload( xhrOnUnloadAbort );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t// Add to list of active xhrs callbacks\n\t\t\t\t\t\t\txhrCallbacks[ handle ] = callback;\n\t\t\t\t\t\t}\n\t\t\t\t\t\txhr.onreadystatechange = callback;\n\t\t\t\t\t}\n\t\t\t\t},\n\n\t\t\t\tabort: function() {\n\t\t\t\t\tif ( callback ) {\n\t\t\t\t\t\tcallback( undefined, true );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\t});\n}\nvar fxNow, timerId,\n\trfxtypes = /^(?:toggle|show|hide)$/,\n\trfxnum = new RegExp( \"^(?:([+-])=|)(\" + core_pnum + \")([a-z%]*)$\", \"i\" ),\n\trrun = /queueHooks$/,\n\tanimationPrefilters = [ defaultPrefilter ],\n\ttweeners = {\n\t\t\"*\": [function( prop, value ) {\n\t\t\tvar tween = this.createTween( prop, value ),\n\t\t\t\ttarget = tween.cur(),\n\t\t\t\tparts = rfxnum.exec( value ),\n\t\t\t\tunit = parts && parts[ 3 ] || ( jQuery.cssNumber[ prop ] ? \"\" : \"px\" ),\n\n\t\t\t\t// Starting value computation is required for potential unit mismatches\n\t\t\t\tstart = ( jQuery.cssNumber[ prop ] || unit !== \"px\" && +target ) &&\n\t\t\t\t\trfxnum.exec( jQuery.css( tween.elem, prop ) ),\n\t\t\t\tscale = 1,\n\t\t\t\tmaxIterations = 20;\n\n\t\t\tif ( start && start[ 3 ] !== unit ) {\n\t\t\t\t// Trust units reported by jQuery.css\n\t\t\t\tunit = unit || start[ 3 ];\n\n\t\t\t\t// Make sure we update the tween properties later on\n\t\t\t\tparts = parts || [];\n\n\t\t\t\t// Iteratively approximate from a nonzero starting point\n\t\t\t\tstart = +target || 1;\n\n\t\t\t\tdo {\n\t\t\t\t\t// If previous iteration zeroed out, double until we get *something*\n\t\t\t\t\t// Use a string for doubling factor so we don't accidentally see scale as unchanged below\n\t\t\t\t\tscale = scale || \".5\";\n\n\t\t\t\t\t// Adjust and apply\n\t\t\t\t\tstart = start / scale;\n\t\t\t\t\tjQuery.style( tween.elem, prop, start + unit );\n\n\t\t\t\t// Update scale, tolerating zero or NaN from tween.cur()\n\t\t\t\t// And breaking the loop if scale is unchanged or perfect, or if we've just had enough\n\t\t\t\t} while ( scale !== (scale = tween.cur() / target) && scale !== 1 && --maxIterations );\n\t\t\t}\n\n\t\t\t// Update tween properties\n\t\t\tif ( parts ) {\n\t\t\t\ttween.unit = unit;\n\t\t\t\ttween.start = +start || +target || 0;\n\t\t\t\t// If a +=/-= token was provided, we're doing a relative animation\n\t\t\t\ttween.end = parts[ 1 ] ?\n\t\t\t\t\tstart + ( parts[ 1 ] + 1 ) * parts[ 2 ] :\n\t\t\t\t\t+parts[ 2 ];\n\t\t\t}\n\n\t\t\treturn tween;\n\t\t}]\n\t};\n\n// Animations created synchronously will run synchronously\nfunction createFxNow() {\n\tsetTimeout(function() {\n\t\tfxNow = undefined;\n\t});\n\treturn ( fxNow = jQuery.now() );\n}\n\nfunction createTween( value, prop, animation ) {\n\tvar tween,\n\t\tcollection = ( tweeners[ prop ] || [] ).concat( tweeners[ \"*\" ] ),\n\t\tindex = 0,\n\t\tlength = collection.length;\n\tfor ( ; index < length; index++ ) {\n\t\tif ( (tween = collection[ index ].call( animation, prop, value )) ) {\n\n\t\t\t// we're done with this property\n\t\t\treturn tween;\n\t\t}\n\t}\n}\n\nfunction Animation( elem, properties, options ) {\n\tvar result,\n\t\tstopped,\n\t\tindex = 0,\n\t\tlength = animationPrefilters.length,\n\t\tdeferred = jQuery.Deferred().always( function() {\n\t\t\t// don't match elem in the :animated selector\n\t\t\tdelete tick.elem;\n\t\t}),\n\t\ttick = function() {\n\t\t\tif ( stopped ) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tvar currentTime = fxNow || createFxNow(),\n\t\t\t\tremaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),\n\t\t\t\t// archaic crash bug won't allow us to use 1 - ( 0.5 || 0 ) (#12497)\n\t\t\t\ttemp = remaining / animation.duration || 0,\n\t\t\t\tpercent = 1 - temp,\n\t\t\t\tindex = 0,\n\t\t\t\tlength = animation.tweens.length;\n\n\t\t\tfor ( ; index < length ; index++ ) {\n\t\t\t\tanimation.tweens[ index ].run( percent );\n\t\t\t}\n\n\t\t\tdeferred.notifyWith( elem, [ animation, percent, remaining ]);\n\n\t\t\tif ( percent < 1 && length ) {\n\t\t\t\treturn remaining;\n\t\t\t} else {\n\t\t\t\tdeferred.resolveWith( elem, [ animation ] );\n\t\t\t\treturn false;\n\t\t\t}\n\t\t},\n\t\tanimation = deferred.promise({\n\t\t\telem: elem,\n\t\t\tprops: jQuery.extend( {}, properties ),\n\t\t\topts: jQuery.extend( true, { specialEasing: {} }, options ),\n\t\t\toriginalProperties: properties,\n\t\t\toriginalOptions: options,\n\t\t\tstartTime: fxNow || createFxNow(),\n\t\t\tduration: options.duration,\n\t\t\ttweens: [],\n\t\t\tcreateTween: function( prop, end ) {\n\t\t\t\tvar tween = jQuery.Tween( elem, animation.opts, prop, end,\n\t\t\t\t\t\tanimation.opts.specialEasing[ prop ] || animation.opts.easing );\n\t\t\t\tanimation.tweens.push( tween );\n\t\t\t\treturn tween;\n\t\t\t},\n\t\t\tstop: function( gotoEnd ) {\n\t\t\t\tvar index = 0,\n\t\t\t\t\t// if we are going to the end, we want to run all the tweens\n\t\t\t\t\t// otherwise we skip this part\n\t\t\t\t\tlength = gotoEnd ? animation.tweens.length : 0;\n\t\t\t\tif ( stopped ) {\n\t\t\t\t\treturn this;\n\t\t\t\t}\n\t\t\t\tstopped = true;\n\t\t\t\tfor ( ; index < length ; index++ ) {\n\t\t\t\t\tanimation.tweens[ index ].run( 1 );\n\t\t\t\t}\n\n\t\t\t\t// resolve when we played the last frame\n\t\t\t\t// otherwise, reject\n\t\t\t\tif ( gotoEnd ) {\n\t\t\t\t\tdeferred.resolveWith( elem, [ animation, gotoEnd ] );\n\t\t\t\t} else {\n\t\t\t\t\tdeferred.rejectWith( elem, [ animation, gotoEnd ] );\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t\t}\n\t\t}),\n\t\tprops = animation.props;\n\n\tpropFilter( props, animation.opts.specialEasing );\n\n\tfor ( ; index < length ; index++ ) {\n\t\tresult = animationPrefilters[ index ].call( animation, elem, props, animation.opts );\n\t\tif ( result ) {\n\t\t\treturn result;\n\t\t}\n\t}\n\n\tjQuery.map( props, createTween, animation );\n\n\tif ( jQuery.isFunction( animation.opts.start ) ) {\n\t\tanimation.opts.start.call( elem, animation );\n\t}\n\n\tjQuery.fx.timer(\n\t\tjQuery.extend( tick, {\n\t\t\telem: elem,\n\t\t\tanim: animation,\n\t\t\tqueue: animation.opts.queue\n\t\t})\n\t);\n\n\t// attach callbacks from options\n\treturn animation.progress( animation.opts.progress )\n\t\t.done( animation.opts.done, animation.opts.complete )\n\t\t.fail( animation.opts.fail )\n\t\t.always( animation.opts.always );\n}\n\nfunction propFilter( props, specialEasing ) {\n\tvar index, name, easing, value, hooks;\n\n\t// camelCase, specialEasing and expand cssHook pass\n\tfor ( index in props ) {\n\t\tname = jQuery.camelCase( index );\n\t\teasing = specialEasing[ name ];\n\t\tvalue = props[ index ];\n\t\tif ( jQuery.isArray( value ) ) {\n\t\t\teasing = value[ 1 ];\n\t\t\tvalue = props[ index ] = value[ 0 ];\n\t\t}\n\n\t\tif ( index !== name ) {\n\t\t\tprops[ name ] = value;\n\t\t\tdelete props[ index ];\n\t\t}\n\n\t\thooks = jQuery.cssHooks[ name ];\n\t\tif ( hooks && \"expand\" in hooks ) {\n\t\t\tvalue = hooks.expand( value );\n\t\t\tdelete props[ name ];\n\n\t\t\t// not quite $.extend, this wont overwrite keys already present.\n\t\t\t// also - reusing 'index' from above because we have the correct \"name\"\n\t\t\tfor ( index in value ) {\n\t\t\t\tif ( !( index in props ) ) {\n\t\t\t\t\tprops[ index ] = value[ index ];\n\t\t\t\t\tspecialEasing[ index ] = easing;\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tspecialEasing[ name ] = easing;\n\t\t}\n\t}\n}\n\njQuery.Animation = jQuery.extend( Animation, {\n\n\ttweener: function( props, callback ) {\n\t\tif ( jQuery.isFunction( props ) ) {\n\t\t\tcallback = props;\n\t\t\tprops = [ \"*\" ];\n\t\t} else {\n\t\t\tprops = props.split(\" \");\n\t\t}\n\n\t\tvar prop,\n\t\t\tindex = 0,\n\t\t\tlength = props.length;\n\n\t\tfor ( ; index < length ; index++ ) {\n\t\t\tprop = props[ index ];\n\t\t\ttweeners[ prop ] = tweeners[ prop ] || [];\n\t\t\ttweeners[ prop ].unshift( callback );\n\t\t}\n\t},\n\n\tprefilter: function( callback, prepend ) {\n\t\tif ( prepend ) {\n\t\t\tanimationPrefilters.unshift( callback );\n\t\t} else {\n\t\t\tanimationPrefilters.push( callback );\n\t\t}\n\t}\n});\n\nfunction defaultPrefilter( elem, props, opts ) {\n\t/* jshint validthis: true */\n\tvar prop, value, toggle, tween, hooks, oldfire,\n\t\tanim = this,\n\t\torig = {},\n\t\tstyle = elem.style,\n\t\thidden = elem.nodeType && isHidden( elem ),\n\t\tdataShow = jQuery._data( elem, \"fxshow\" );\n\n\t// handle queue: false promises\n\tif ( !opts.queue ) {\n\t\thooks = jQuery._queueHooks( elem, \"fx\" );\n\t\tif ( hooks.unqueued == null ) {\n\t\t\thooks.unqueued = 0;\n\t\t\toldfire = hooks.empty.fire;\n\t\t\thooks.empty.fire = function() {\n\t\t\t\tif ( !hooks.unqueued ) {\n\t\t\t\t\toldfire();\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\t\thooks.unqueued++;\n\n\t\tanim.always(function() {\n\t\t\t// doing this makes sure that the complete handler will be called\n\t\t\t// before this completes\n\t\t\tanim.always(function() {\n\t\t\t\thooks.unqueued--;\n\t\t\t\tif ( !jQuery.queue( elem, \"fx\" ).length ) {\n\t\t\t\t\thooks.empty.fire();\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\t}\n\n\t// height/width overflow pass\n\tif ( elem.nodeType === 1 && ( \"height\" in props || \"width\" in props ) ) {\n\t\t// Make sure that nothing sneaks out\n\t\t// Record all 3 overflow attributes because IE does not\n\t\t// change the overflow attribute when overflowX and\n\t\t// overflowY are set to the same value\n\t\topts.overflow = [ style.overflow, style.overflowX, style.overflowY ];\n\n\t\t// Set display property to inline-block for height/width\n\t\t// animations on inline elements that are having width/height animated\n\t\tif ( jQuery.css( elem, \"display\" ) === \"inline\" &&\n\t\t\t\tjQuery.css( elem, \"float\" ) === \"none\" ) {\n\n\t\t\t// inline-level elements accept inline-block;\n\t\t\t// block-level elements need to be inline with layout\n\t\t\tif ( !jQuery.support.inlineBlockNeedsLayout || css_defaultDisplay( elem.nodeName ) === \"inline\" ) {\n\t\t\t\tstyle.display = \"inline-block\";\n\n\t\t\t} else {\n\t\t\t\tstyle.zoom = 1;\n\t\t\t}\n\t\t}\n\t}\n\n\tif ( opts.overflow ) {\n\t\tstyle.overflow = \"hidden\";\n\t\tif ( !jQuery.support.shrinkWrapBlocks ) {\n\t\t\tanim.always(function() {\n\t\t\t\tstyle.overflow = opts.overflow[ 0 ];\n\t\t\t\tstyle.overflowX = opts.overflow[ 1 ];\n\t\t\t\tstyle.overflowY = opts.overflow[ 2 ];\n\t\t\t});\n\t\t}\n\t}\n\n\n\t// show/hide pass\n\tfor ( prop in props ) {\n\t\tvalue = props[ prop ];\n\t\tif ( rfxtypes.exec( value ) ) {\n\t\t\tdelete props[ prop ];\n\t\t\ttoggle = toggle || value === \"toggle\";\n\t\t\tif ( value === ( hidden ? \"hide\" : \"show\" ) ) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\torig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop );\n\t\t}\n\t}\n\n\tif ( !jQuery.isEmptyObject( orig ) ) {\n\t\tif ( dataShow ) {\n\t\t\tif ( \"hidden\" in dataShow ) {\n\t\t\t\thidden = dataShow.hidden;\n\t\t\t}\n\t\t} else {\n\t\t\tdataShow = jQuery._data( elem, \"fxshow\", {} );\n\t\t}\n\n\t\t// store state if its toggle - enables .stop().toggle() to \"reverse\"\n\t\tif ( toggle ) {\n\t\t\tdataShow.hidden = !hidden;\n\t\t}\n\t\tif ( hidden ) {\n\t\t\tjQuery( elem ).show();\n\t\t} else {\n\t\t\tanim.done(function() {\n\t\t\t\tjQuery( elem ).hide();\n\t\t\t});\n\t\t}\n\t\tanim.done(function() {\n\t\t\tvar prop;\n\t\t\tjQuery._removeData( elem, \"fxshow\" );\n\t\t\tfor ( prop in orig ) {\n\t\t\t\tjQuery.style( elem, prop, orig[ prop ] );\n\t\t\t}\n\t\t});\n\t\tfor ( prop in orig ) {\n\t\t\ttween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim );\n\n\t\t\tif ( !( prop in dataShow ) ) {\n\t\t\t\tdataShow[ prop ] = tween.start;\n\t\t\t\tif ( hidden ) {\n\t\t\t\t\ttween.end = tween.start;\n\t\t\t\t\ttween.start = prop === \"width\" || prop === \"height\" ? 1 : 0;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunction Tween( elem, options, prop, end, easing ) {\n\treturn new Tween.prototype.init( elem, options, prop, end, easing );\n}\njQuery.Tween = Tween;\n\nTween.prototype = {\n\tconstructor: Tween,\n\tinit: function( elem, options, prop, end, easing, unit ) {\n\t\tthis.elem = elem;\n\t\tthis.prop = prop;\n\t\tthis.easing = easing || \"swing\";\n\t\tthis.options = options;\n\t\tthis.start = this.now = this.cur();\n\t\tthis.end = end;\n\t\tthis.unit = unit || ( jQuery.cssNumber[ prop ] ? \"\" : \"px\" );\n\t},\n\tcur: function() {\n\t\tvar hooks = Tween.propHooks[ this.prop ];\n\n\t\treturn hooks && hooks.get ?\n\t\t\thooks.get( this ) :\n\t\t\tTween.propHooks._default.get( this );\n\t},\n\trun: function( percent ) {\n\t\tvar eased,\n\t\t\thooks = Tween.propHooks[ this.prop ];\n\n\t\tif ( this.options.duration ) {\n\t\t\tthis.pos = eased = jQuery.easing[ this.easing ](\n\t\t\t\tpercent, this.options.duration * percent, 0, 1, this.options.duration\n\t\t\t);\n\t\t} else {\n\t\t\tthis.pos = eased = percent;\n\t\t}\n\t\tthis.now = ( this.end - this.start ) * eased + this.start;\n\n\t\tif ( this.options.step ) {\n\t\t\tthis.options.step.call( this.elem, this.now, this );\n\t\t}\n\n\t\tif ( hooks && hooks.set ) {\n\t\t\thooks.set( this );\n\t\t} else {\n\t\t\tTween.propHooks._default.set( this );\n\t\t}\n\t\treturn this;\n\t}\n};\n\nTween.prototype.init.prototype = Tween.prototype;\n\nTween.propHooks = {\n\t_default: {\n\t\tget: function( tween ) {\n\t\t\tvar result;\n\n\t\t\tif ( tween.elem[ tween.prop ] != null &&\n\t\t\t\t(!tween.elem.style || tween.elem.style[ tween.prop ] == null) ) {\n\t\t\t\treturn tween.elem[ tween.prop ];\n\t\t\t}\n\n\t\t\t// passing an empty string as a 3rd parameter to .css will automatically\n\t\t\t// attempt a parseFloat and fallback to a string if the parse fails\n\t\t\t// so, simple values such as \"10px\" are parsed to Float.\n\t\t\t// complex values such as \"rotate(1rad)\" are returned as is.\n\t\t\tresult = jQuery.css( tween.elem, tween.prop, \"\" );\n\t\t\t// Empty strings, null, undefined and \"auto\" are converted to 0.\n\t\t\treturn !result || result === \"auto\" ? 0 : result;\n\t\t},\n\t\tset: function( tween ) {\n\t\t\t// use step hook for back compat - use cssHook if its there - use .style if its\n\t\t\t// available and use plain properties where available\n\t\t\tif ( jQuery.fx.step[ tween.prop ] ) {\n\t\t\t\tjQuery.fx.step[ tween.prop ]( tween );\n\t\t\t} else if ( tween.elem.style && ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null || jQuery.cssHooks[ tween.prop ] ) ) {\n\t\t\t\tjQuery.style( tween.elem, tween.prop, tween.now + tween.unit );\n\t\t\t} else {\n\t\t\t\ttween.elem[ tween.prop ] = tween.now;\n\t\t\t}\n\t\t}\n\t}\n};\n\n// Support: IE <=9\n// Panic based approach to setting things on disconnected nodes\n\nTween.propHooks.scrollTop = Tween.propHooks.scrollLeft = {\n\tset: function( tween ) {\n\t\tif ( tween.elem.nodeType && tween.elem.parentNode ) {\n\t\t\ttween.elem[ tween.prop ] = tween.now;\n\t\t}\n\t}\n};\n\njQuery.each([ \"toggle\", \"show\", \"hide\" ], function( i, name ) {\n\tvar cssFn = jQuery.fn[ name ];\n\tjQuery.fn[ name ] = function( speed, easing, callback ) {\n\t\treturn speed == null || typeof speed === \"boolean\" ?\n\t\t\tcssFn.apply( this, arguments ) :\n\t\t\tthis.animate( genFx( name, true ), speed, easing, callback );\n\t};\n});\n\njQuery.fn.extend({\n\tfadeTo: function( speed, to, easing, callback ) {\n\n\t\t// show any hidden elements after setting opacity to 0\n\t\treturn this.filter( isHidden ).css( \"opacity\", 0 ).show()\n\n\t\t\t// animate to the value specified\n\t\t\t.end().animate({ opacity: to }, speed, easing, callback );\n\t},\n\tanimate: function( prop, speed, easing, callback ) {\n\t\tvar empty = jQuery.isEmptyObject( prop ),\n\t\t\toptall = jQuery.speed( speed, easing, callback ),\n\t\t\tdoAnimation = function() {\n\t\t\t\t// Operate on a copy of prop so per-property easing won't be lost\n\t\t\t\tvar anim = Animation( this, jQuery.extend( {}, prop ), optall );\n\t\t\t\tdoAnimation.finish = function() {\n\t\t\t\t\tanim.stop( true );\n\t\t\t\t};\n\t\t\t\t// Empty animations, or finishing resolves immediately\n\t\t\t\tif ( empty || jQuery._data( this, \"finish\" ) ) {\n\t\t\t\t\tanim.stop( true );\n\t\t\t\t}\n\t\t\t};\n\t\t\tdoAnimation.finish = doAnimation;\n\n\t\treturn empty || optall.queue === false ?\n\t\t\tthis.each( doAnimation ) :\n\t\t\tthis.queue( optall.queue, doAnimation );\n\t},\n\tstop: function( type, clearQueue, gotoEnd ) {\n\t\tvar stopQueue = function( hooks ) {\n\t\t\tvar stop = hooks.stop;\n\t\t\tdelete hooks.stop;\n\t\t\tstop( gotoEnd );\n\t\t};\n\n\t\tif ( typeof type !== \"string\" ) {\n\t\t\tgotoEnd = clearQueue;\n\t\t\tclearQueue = type;\n\t\t\ttype = undefined;\n\t\t}\n\t\tif ( clearQueue && type !== false ) {\n\t\t\tthis.queue( type || \"fx\", [] );\n\t\t}\n\n\t\treturn this.each(function() {\n\t\t\tvar dequeue = true,\n\t\t\t\tindex = type != null && type + \"queueHooks\",\n\t\t\t\ttimers = jQuery.timers,\n\t\t\t\tdata = jQuery._data( this );\n\n\t\t\tif ( index ) {\n\t\t\t\tif ( data[ index ] && data[ index ].stop ) {\n\t\t\t\t\tstopQueue( data[ index ] );\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tfor ( index in data ) {\n\t\t\t\t\tif ( data[ index ] && data[ index ].stop && rrun.test( index ) ) {\n\t\t\t\t\t\tstopQueue( data[ index ] );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor ( index = timers.length; index--; ) {\n\t\t\t\tif ( timers[ index ].elem === this && (type == null || timers[ index ].queue === type) ) {\n\t\t\t\t\ttimers[ index ].anim.stop( gotoEnd );\n\t\t\t\t\tdequeue = false;\n\t\t\t\t\ttimers.splice( index, 1 );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// start the next in the queue if the last step wasn't forced\n\t\t\t// timers currently will call their complete callbacks, which will dequeue\n\t\t\t// but only if they were gotoEnd\n\t\t\tif ( dequeue || !gotoEnd ) {\n\t\t\t\tjQuery.dequeue( this, type );\n\t\t\t}\n\t\t});\n\t},\n\tfinish: function( type ) {\n\t\tif ( type !== false ) {\n\t\t\ttype = type || \"fx\";\n\t\t}\n\t\treturn this.each(function() {\n\t\t\tvar index,\n\t\t\t\tdata = jQuery._data( this ),\n\t\t\t\tqueue = data[ type + \"queue\" ],\n\t\t\t\thooks = data[ type + \"queueHooks\" ],\n\t\t\t\ttimers = jQuery.timers,\n\t\t\t\tlength = queue ? queue.length : 0;\n\n\t\t\t// enable finishing flag on private data\n\t\t\tdata.finish = true;\n\n\t\t\t// empty the queue first\n\t\t\tjQuery.queue( this, type, [] );\n\n\t\t\tif ( hooks && hooks.cur && hooks.cur.finish ) {\n\t\t\t\thooks.cur.finish.call( this );\n\t\t\t}\n\n\t\t\t// look for any active animations, and finish them\n\t\t\tfor ( index = timers.length; index--; ) {\n\t\t\t\tif ( timers[ index ].elem === this && timers[ index ].queue === type ) {\n\t\t\t\t\ttimers[ index ].anim.stop( true );\n\t\t\t\t\ttimers.splice( index, 1 );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// look for any animations in the old queue and finish them\n\t\t\tfor ( index = 0; index < length; index++ ) {\n\t\t\t\tif ( queue[ index ] && queue[ index ].finish ) {\n\t\t\t\t\tqueue[ index ].finish.call( this );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// turn off finishing flag\n\t\t\tdelete data.finish;\n\t\t});\n\t}\n});\n\n// Generate parameters to create a standard animation\nfunction genFx( type, includeWidth ) {\n\tvar which,\n\t\tattrs = { height: type },\n\t\ti = 0;\n\n\t// if we include width, step value is 1 to do all cssExpand values,\n\t// if we don't include width, step value is 2 to skip over Left and Right\n\tincludeWidth = includeWidth? 1 : 0;\n\tfor( ; i < 4 ; i += 2 - includeWidth ) {\n\t\twhich = cssExpand[ i ];\n\t\tattrs[ \"margin\" + which ] = attrs[ \"padding\" + which ] = type;\n\t}\n\n\tif ( includeWidth ) {\n\t\tattrs.opacity = attrs.width = type;\n\t}\n\n\treturn attrs;\n}\n\n// Generate shortcuts for custom animations\njQuery.each({\n\tslideDown: genFx(\"show\"),\n\tslideUp: genFx(\"hide\"),\n\tslideToggle: genFx(\"toggle\"),\n\tfadeIn: { opacity: \"show\" },\n\tfadeOut: { opacity: \"hide\" },\n\tfadeToggle: { opacity: \"toggle\" }\n}, function( name, props ) {\n\tjQuery.fn[ name ] = function( speed, easing, callback ) {\n\t\treturn this.animate( props, speed, easing, callback );\n\t};\n});\n\njQuery.speed = function( speed, easing, fn ) {\n\tvar opt = speed && typeof speed === \"object\" ? jQuery.extend( {}, speed ) : {\n\t\tcomplete: fn || !fn && easing ||\n\t\t\tjQuery.isFunction( speed ) && speed,\n\t\tduration: speed,\n\t\teasing: fn && easing || easing && !jQuery.isFunction( easing ) && easing\n\t};\n\n\topt.duration = jQuery.fx.off ? 0 : typeof opt.duration === \"number\" ? opt.duration :\n\t\topt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default;\n\n\t// normalize opt.queue - true/undefined/null -> \"fx\"\n\tif ( opt.queue == null || opt.queue === true ) {\n\t\topt.queue = \"fx\";\n\t}\n\n\t// Queueing\n\topt.old = opt.complete;\n\n\topt.complete = function() {\n\t\tif ( jQuery.isFunction( opt.old ) ) {\n\t\t\topt.old.call( this );\n\t\t}\n\n\t\tif ( opt.queue ) {\n\t\t\tjQuery.dequeue( this, opt.queue );\n\t\t}\n\t};\n\n\treturn opt;\n};\n\njQuery.easing = {\n\tlinear: function( p ) {\n\t\treturn p;\n\t},\n\tswing: function( p ) {\n\t\treturn 0.5 - Math.cos( p*Math.PI ) / 2;\n\t}\n};\n\njQuery.timers = [];\njQuery.fx = Tween.prototype.init;\njQuery.fx.tick = function() {\n\tvar timer,\n\t\ttimers = jQuery.timers,\n\t\ti = 0;\n\n\tfxNow = jQuery.now();\n\n\tfor ( ; i < timers.length; i++ ) {\n\t\ttimer = timers[ i ];\n\t\t// Checks the timer has not already been removed\n\t\tif ( !timer() && timers[ i ] === timer ) {\n\t\t\ttimers.splice( i--, 1 );\n\t\t}\n\t}\n\n\tif ( !timers.length ) {\n\t\tjQuery.fx.stop();\n\t}\n\tfxNow = undefined;\n};\n\njQuery.fx.timer = function( timer ) {\n\tif ( timer() && jQuery.timers.push( timer ) ) {\n\t\tjQuery.fx.start();\n\t}\n};\n\njQuery.fx.interval = 13;\n\njQuery.fx.start = function() {\n\tif ( !timerId ) {\n\t\ttimerId = setInterval( jQuery.fx.tick, jQuery.fx.interval );\n\t}\n};\n\njQuery.fx.stop = function() {\n\tclearInterval( timerId );\n\ttimerId = null;\n};\n\njQuery.fx.speeds = {\n\tslow: 600,\n\tfast: 200,\n\t// Default speed\n\t_default: 400\n};\n\n// Back Compat <1.8 extension point\njQuery.fx.step = {};\n\nif ( jQuery.expr && jQuery.expr.filters ) {\n\tjQuery.expr.filters.animated = function( elem ) {\n\t\treturn jQuery.grep(jQuery.timers, function( fn ) {\n\t\t\treturn elem === fn.elem;\n\t\t}).length;\n\t};\n}\njQuery.fn.offset = function( options ) {\n\tif ( arguments.length ) {\n\t\treturn options === undefined ?\n\t\t\tthis :\n\t\t\tthis.each(function( i ) {\n\t\t\t\tjQuery.offset.setOffset( this, options, i );\n\t\t\t});\n\t}\n\n\tvar docElem, win,\n\t\tbox = { top: 0, left: 0 },\n\t\telem = this[ 0 ],\n\t\tdoc = elem && elem.ownerDocument;\n\n\tif ( !doc ) {\n\t\treturn;\n\t}\n\n\tdocElem = doc.documentElement;\n\n\t// Make sure it's not a disconnected DOM node\n\tif ( !jQuery.contains( docElem, elem ) ) {\n\t\treturn box;\n\t}\n\n\t// If we don't have gBCR, just use 0,0 rather than error\n\t// BlackBerry 5, iOS 3 (original iPhone)\n\tif ( typeof elem.getBoundingClientRect !== core_strundefined ) {\n\t\tbox = elem.getBoundingClientRect();\n\t}\n\twin = getWindow( doc );\n\treturn {\n\t\ttop: box.top  + ( win.pageYOffset || docElem.scrollTop )  - ( docElem.clientTop  || 0 ),\n\t\tleft: box.left + ( win.pageXOffset || docElem.scrollLeft ) - ( docElem.clientLeft || 0 )\n\t};\n};\n\njQuery.offset = {\n\n\tsetOffset: function( elem, options, i ) {\n\t\tvar position = jQuery.css( elem, \"position\" );\n\n\t\t// set position first, in-case top/left are set even on static elem\n\t\tif ( position === \"static\" ) {\n\t\t\telem.style.position = \"relative\";\n\t\t}\n\n\t\tvar curElem = jQuery( elem ),\n\t\t\tcurOffset = curElem.offset(),\n\t\t\tcurCSSTop = jQuery.css( elem, \"top\" ),\n\t\t\tcurCSSLeft = jQuery.css( elem, \"left\" ),\n\t\t\tcalculatePosition = ( position === \"absolute\" || position === \"fixed\" ) && jQuery.inArray(\"auto\", [curCSSTop, curCSSLeft]) > -1,\n\t\t\tprops = {}, curPosition = {}, curTop, curLeft;\n\n\t\t// need to be able to calculate position if either top or left is auto and position is either absolute or fixed\n\t\tif ( calculatePosition ) {\n\t\t\tcurPosition = curElem.position();\n\t\t\tcurTop = curPosition.top;\n\t\t\tcurLeft = curPosition.left;\n\t\t} else {\n\t\t\tcurTop = parseFloat( curCSSTop ) || 0;\n\t\t\tcurLeft = parseFloat( curCSSLeft ) || 0;\n\t\t}\n\n\t\tif ( jQuery.isFunction( options ) ) {\n\t\t\toptions = options.call( elem, i, curOffset );\n\t\t}\n\n\t\tif ( options.top != null ) {\n\t\t\tprops.top = ( options.top - curOffset.top ) + curTop;\n\t\t}\n\t\tif ( options.left != null ) {\n\t\t\tprops.left = ( options.left - curOffset.left ) + curLeft;\n\t\t}\n\n\t\tif ( \"using\" in options ) {\n\t\t\toptions.using.call( elem, props );\n\t\t} else {\n\t\t\tcurElem.css( props );\n\t\t}\n\t}\n};\n\n\njQuery.fn.extend({\n\n\tposition: function() {\n\t\tif ( !this[ 0 ] ) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar offsetParent, offset,\n\t\t\tparentOffset = { top: 0, left: 0 },\n\t\t\telem = this[ 0 ];\n\n\t\t// fixed elements are offset from window (parentOffset = {top:0, left: 0}, because it is it's only offset parent\n\t\tif ( jQuery.css( elem, \"position\" ) === \"fixed\" ) {\n\t\t\t// we assume that getBoundingClientRect is available when computed position is fixed\n\t\t\toffset = elem.getBoundingClientRect();\n\t\t} else {\n\t\t\t// Get *real* offsetParent\n\t\t\toffsetParent = this.offsetParent();\n\n\t\t\t// Get correct offsets\n\t\t\toffset = this.offset();\n\t\t\tif ( !jQuery.nodeName( offsetParent[ 0 ], \"html\" ) ) {\n\t\t\t\tparentOffset = offsetParent.offset();\n\t\t\t}\n\n\t\t\t// Add offsetParent borders\n\t\t\tparentOffset.top  += jQuery.css( offsetParent[ 0 ], \"borderTopWidth\", true );\n\t\t\tparentOffset.left += jQuery.css( offsetParent[ 0 ], \"borderLeftWidth\", true );\n\t\t}\n\n\t\t// Subtract parent offsets and element margins\n\t\t// note: when an element has margin: auto the offsetLeft and marginLeft\n\t\t// are the same in Safari causing offset.left to incorrectly be 0\n\t\treturn {\n\t\t\ttop:  offset.top  - parentOffset.top - jQuery.css( elem, \"marginTop\", true ),\n\t\t\tleft: offset.left - parentOffset.left - jQuery.css( elem, \"marginLeft\", true)\n\t\t};\n\t},\n\n\toffsetParent: function() {\n\t\treturn this.map(function() {\n\t\t\tvar offsetParent = this.offsetParent || docElem;\n\t\t\twhile ( offsetParent && ( !jQuery.nodeName( offsetParent, \"html\" ) && jQuery.css( offsetParent, \"position\") === \"static\" ) ) {\n\t\t\t\toffsetParent = offsetParent.offsetParent;\n\t\t\t}\n\t\t\treturn offsetParent || docElem;\n\t\t});\n\t}\n});\n\n\n// Create scrollLeft and scrollTop methods\njQuery.each( {scrollLeft: \"pageXOffset\", scrollTop: \"pageYOffset\"}, function( method, prop ) {\n\tvar top = /Y/.test( prop );\n\n\tjQuery.fn[ method ] = function( val ) {\n\t\treturn jQuery.access( this, function( elem, method, val ) {\n\t\t\tvar win = getWindow( elem );\n\n\t\t\tif ( val === undefined ) {\n\t\t\t\treturn win ? (prop in win) ? win[ prop ] :\n\t\t\t\t\twin.document.documentElement[ method ] :\n\t\t\t\t\telem[ method ];\n\t\t\t}\n\n\t\t\tif ( win ) {\n\t\t\t\twin.scrollTo(\n\t\t\t\t\t!top ? val : jQuery( win ).scrollLeft(),\n\t\t\t\t\ttop ? val : jQuery( win ).scrollTop()\n\t\t\t\t);\n\n\t\t\t} else {\n\t\t\t\telem[ method ] = val;\n\t\t\t}\n\t\t}, method, val, arguments.length, null );\n\t};\n});\n\nfunction getWindow( elem ) {\n\treturn jQuery.isWindow( elem ) ?\n\t\telem :\n\t\telem.nodeType === 9 ?\n\t\t\telem.defaultView || elem.parentWindow :\n\t\t\tfalse;\n}\n// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods\njQuery.each( { Height: \"height\", Width: \"width\" }, function( name, type ) {\n\tjQuery.each( { padding: \"inner\" + name, content: type, \"\": \"outer\" + name }, function( defaultExtra, funcName ) {\n\t\t// margin is only for outerHeight, outerWidth\n\t\tjQuery.fn[ funcName ] = function( margin, value ) {\n\t\t\tvar chainable = arguments.length && ( defaultExtra || typeof margin !== \"boolean\" ),\n\t\t\t\textra = defaultExtra || ( margin === true || value === true ? \"margin\" : \"border\" );\n\n\t\t\treturn jQuery.access( this, function( elem, type, value ) {\n\t\t\t\tvar doc;\n\n\t\t\t\tif ( jQuery.isWindow( elem ) ) {\n\t\t\t\t\t// As of 5/8/2012 this will yield incorrect results for Mobile Safari, but there\n\t\t\t\t\t// isn't a whole lot we can do. See pull request at this URL for discussion:\n\t\t\t\t\t// https://github.com/jquery/jquery/pull/764\n\t\t\t\t\treturn elem.document.documentElement[ \"client\" + name ];\n\t\t\t\t}\n\n\t\t\t\t// Get document width or height\n\t\t\t\tif ( elem.nodeType === 9 ) {\n\t\t\t\t\tdoc = elem.documentElement;\n\n\t\t\t\t\t// Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height], whichever is greatest\n\t\t\t\t\t// unfortunately, this causes bug #3838 in IE6/8 only, but there is currently no good, small way to fix it.\n\t\t\t\t\treturn Math.max(\n\t\t\t\t\t\telem.body[ \"scroll\" + name ], doc[ \"scroll\" + name ],\n\t\t\t\t\t\telem.body[ \"offset\" + name ], doc[ \"offset\" + name ],\n\t\t\t\t\t\tdoc[ \"client\" + name ]\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\treturn value === undefined ?\n\t\t\t\t\t// Get width or height on the element, requesting but not forcing parseFloat\n\t\t\t\t\tjQuery.css( elem, type, extra ) :\n\n\t\t\t\t\t// Set width or height on the element\n\t\t\t\t\tjQuery.style( elem, type, value, extra );\n\t\t\t}, type, chainable ? margin : undefined, chainable, null );\n\t\t};\n\t});\n});\n// Limit scope pollution from any deprecated API\n// (function() {\n\n// The number of elements contained in the matched element set\njQuery.fn.size = function() {\n\treturn this.length;\n};\n\njQuery.fn.andSelf = jQuery.fn.addBack;\n\n// })();\nif ( typeof module === \"object\" && typeof module.exports === \"object\" ) {\n\t// Expose jQuery as module.exports in loaders that implement the Node\n\t// module pattern (including browserify). Do not create the global, since\n\t// the user will be storing it themselves locally, and globals are frowned\n\t// upon in the Node module world.\n\tmodule.exports = jQuery;\n} else {\n\t// Otherwise expose jQuery to the global object as usual\n\twindow.jQuery = window.$ = jQuery;\n\n\t// Register as a named AMD module, since jQuery can be concatenated with other\n\t// files that may use define, but not via a proper concatenation script that\n\t// understands anonymous AMD modules. A named AMD is safest and most robust\n\t// way to register. Lowercase jquery is used because AMD module names are\n\t// derived from file names, and jQuery is normally delivered in a lowercase\n\t// file name. Do this after creating the global so that if an AMD module wants\n\t// to call noConflict to hide this version of jQuery, it will work.\n\tif ( typeof define === \"function\" && define.amd ) {\n\t\tdefine( \"jquery\", [], function () { return jQuery; } );\n\t}\n}\n\n})( window );"
  },
  {
    "path": "test/static/third-party/jquery.simulate/jquery.simulate.js",
    "content": " /*!\n * jQuery Simulate v@VERSION - simulate browser mouse and keyboard events\n * https://github.com/jquery/jquery-simulate\n *\n * Copyright 2012 jQuery Foundation and other contributors\n * Released under the MIT license.\n * http://jquery.org/license\n *\n * Date: @DATE\n */\n\n;(function( $, undefined ) {\n\nvar rkeyEvent = /^key/,\n\trmouseEvent = /^(?:mouse|contextmenu)|click/;\n\n$.fn.simulate = function( type, options ) {\n\treturn this.each(function() {\n\t\tnew $.simulate( this, type, options );\n\t});\n};\n\n$.simulate = function( elem, type, options ) {\n\tvar method = $.camelCase( \"simulate-\" + type );\n\n\tthis.target = elem;\n\tthis.options = options;\n\n\tif ( this[ method ] ) {\n\t\tthis[ method ]();\n\t} else {\n\t\tthis.simulateEvent( elem, type, options );\n\t}\n};\n\n$.extend( $.simulate, {\n\n\tkeyCode: {\n\t\tBACKSPACE: 8,\n\t\tCOMMA: 188,\n\t\tDELETE: 46,\n\t\tDOWN: 40,\n\t\tEND: 35,\n\t\tENTER: 13,\n\t\tESCAPE: 27,\n\t\tHOME: 36,\n\t\tLEFT: 37,\n\t\tNUMPAD_ADD: 107,\n\t\tNUMPAD_DECIMAL: 110,\n\t\tNUMPAD_DIVIDE: 111,\n\t\tNUMPAD_ENTER: 108,\n\t\tNUMPAD_MULTIPLY: 106,\n\t\tNUMPAD_SUBTRACT: 109,\n\t\tPAGE_DOWN: 34,\n\t\tPAGE_UP: 33,\n\t\tPERIOD: 190,\n\t\tRIGHT: 39,\n\t\tSPACE: 32,\n\t\tTAB: 9,\n\t\tUP: 38\n\t},\n\n\tbuttonCode: {\n\t\tLEFT: 0,\n\t\tMIDDLE: 1,\n\t\tRIGHT: 2\n\t}\n});\n\n$.extend( $.simulate.prototype, {\n\n\tsimulateEvent: function( elem, type, options ) {\n\t\tvar event = this.createEvent( type, options );\n\t\tthis.dispatchEvent( elem, type, event, options );\n\t},\n\n\tcreateEvent: function( type, options ) {\n\t\tif ( rkeyEvent.test( type ) ) {\n\t\t\treturn this.keyEvent( type, options );\n\t\t}\n\n\t\tif ( rmouseEvent.test( type ) ) {\n\t\t\treturn this.mouseEvent( type, options );\n\t\t}\n\t},\n\n\tmouseEvent: function( type, options ) {\n\t\tvar event, eventDoc, doc, body;\n\t\toptions = $.extend({\n\t\t\tbubbles: true,\n\t\t\tcancelable: (type !== \"mousemove\"),\n\t\t\tview: window,\n\t\t\tdetail: 0,\n\t\t\tscreenX: 0,\n\t\t\tscreenY: 0,\n\t\t\tclientX: 1,\n\t\t\tclientY: 1,\n\t\t\tctrlKey: false,\n\t\t\taltKey: false,\n\t\t\tshiftKey: false,\n\t\t\tmetaKey: false,\n\t\t\tbutton: 0,\n\t\t\trelatedTarget: undefined\n\t\t}, options );\n\n\t\tif ( document.createEvent ) {\n\t\t\tevent = document.createEvent( \"MouseEvents\" );\n\t\t\tevent.initMouseEvent( type, options.bubbles, options.cancelable,\n\t\t\t\toptions.view, options.detail,\n\t\t\t\toptions.screenX, options.screenY, options.clientX, options.clientY,\n\t\t\t\toptions.ctrlKey, options.altKey, options.shiftKey, options.metaKey,\n\t\t\t\toptions.button, options.relatedTarget || document.body.parentNode );\n\n\t\t\t// IE 9+ creates events with pageX and pageY set to 0.\n\t\t\t// Trying to modify the properties throws an error,\n\t\t\t// so we define getters to return the correct values.\n\t\t\tif ( event.pageX === 0 && event.pageY === 0 && Object.defineProperty ) {\n\t\t\t\teventDoc = event.relatedTarget.ownerDocument || document;\n\t\t\t\tdoc = eventDoc.documentElement;\n\t\t\t\tbody = eventDoc.body;\n\n\t\t\t\tObject.defineProperty( event, \"pageX\", {\n\t\t\t\t\tget: function() {\n\t\t\t\t\t\treturn options.clientX +\n\t\t\t\t\t\t\t( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) -\n\t\t\t\t\t\t\t( doc && doc.clientLeft || body && body.clientLeft || 0 );\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tObject.defineProperty( event, \"pageY\", {\n\t\t\t\t\tget: function() {\n\t\t\t\t\t\treturn options.clientY +\n\t\t\t\t\t\t\t( doc && doc.scrollTop || body && body.scrollTop || 0 ) -\n\t\t\t\t\t\t\t( doc && doc.clientTop || body && body.clientTop || 0 );\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t} else if ( document.createEventObject ) {\n\t\t\tevent = document.createEventObject();\n\t\t\t$.extend( event, options );\n\t\t\t// standards event.button uses constants defined here: http://msdn.microsoft.com/en-us/library/ie/ff974877(v=vs.85).aspx\n\t\t\t// old IE event.button uses constants defined here: http://msdn.microsoft.com/en-us/library/ie/ms533544(v=vs.85).aspx\n\t\t\t// so we actually need to map the standard back to oldIE\n\t\t\tevent.button = {\n\t\t\t\t0: 1,\n\t\t\t\t1: 4,\n\t\t\t\t2: 2\n\t\t\t}[ event.button ] || event.button;\n\t\t}\n\n\t\treturn event;\n\t},\n\n\tkeyEvent: function( type, options ) {\n\t\tvar event;\n\t\toptions = $.extend({\n\t\t\tbubbles: true,\n\t\t\tcancelable: true,\n\t\t\tview: window,\n\t\t\tctrlKey: false,\n\t\t\taltKey: false,\n\t\t\tshiftKey: false,\n\t\t\tmetaKey: false,\n\t\t\tkeyCode: 0,\n\t\t\tcharCode: undefined\n\t\t}, options );\n\n\t\tif ( document.createEvent ) {\n\t\t\ttry {\n\t\t\t\tevent = document.createEvent( \"KeyEvents\" );\n\t\t\t\tevent.initKeyEvent( type, options.bubbles, options.cancelable, options.view,\n\t\t\t\t\toptions.ctrlKey, options.altKey, options.shiftKey, options.metaKey,\n\t\t\t\t\toptions.keyCode, options.charCode );\n\t\t\t// initKeyEvent throws an exception in WebKit\n\t\t\t// see: http://stackoverflow.com/questions/6406784/initkeyevent-keypress-only-works-in-firefox-need-a-cross-browser-solution\n\t\t\t// and also https://bugs.webkit.org/show_bug.cgi?id=13368\n\t\t\t// fall back to a generic event until we decide to implement initKeyboardEvent\n\t\t\t} catch( err ) {\n\t\t\t\tevent = document.createEvent( \"Events\" );\n\t\t\t\tevent.initEvent( type, options.bubbles, options.cancelable );\n\t\t\t\t$.extend( event, {\n\t\t\t\t\tview: options.view,\n\t\t\t\t\tctrlKey: options.ctrlKey,\n\t\t\t\t\taltKey: options.altKey,\n\t\t\t\t\tshiftKey: options.shiftKey,\n\t\t\t\t\tmetaKey: options.metaKey,\n\t\t\t\t\tkeyCode: options.keyCode,\n\t\t\t\t\tcharCode: options.charCode\n\t\t\t\t});\n\t\t\t}\n\t\t} else if ( document.createEventObject ) {\n\t\t\tevent = document.createEventObject();\n\t\t\t$.extend( event, options );\n\t\t}\n\n\t\tif ( !!/msie [\\w.]+/.exec( navigator.userAgent.toLowerCase() ) || (({}).toString.call( window.opera ) === \"[object Opera]\") ) {\n\t\t\tevent.keyCode = (options.charCode > 0) ? options.charCode : options.keyCode;\n\t\t\tevent.charCode = undefined;\n\t\t}\n\n\t\treturn event;\n\t},\n\n\tdispatchEvent: function( elem, type, event ) {\n\t\tif ( elem[ type ] ) {\n\t\t\telem[ type ]();\n\t\t} else if ( elem.dispatchEvent ) {\n\t\t\telem.dispatchEvent( event );\n\t\t} else if ( elem.fireEvent ) {\n\t\t\telem.fireEvent( \"on\" + type, event );\n\t\t}\n\t},\n\n\tsimulateFocus: function() {\n\t\tvar focusinEvent,\n\t\t\ttriggered = false,\n\t\t\telement = $( this.target );\n\n\t\tfunction trigger() {\n\t\t\ttriggered = true;\n\t\t}\n\n\t\telement.bind( \"focus\", trigger );\n\t\telement[ 0 ].focus();\n\n\t\tif ( !triggered ) {\n\t\t\tfocusinEvent = $.Event( \"focusin\" );\n\t\t\tfocusinEvent.preventDefault();\n\t\t\telement.trigger( focusinEvent );\n\t\t\telement.triggerHandler( \"focus\" );\n\t\t}\n\t\telement.unbind( \"focus\", trigger );\n\t},\n\n\tsimulateBlur: function() {\n\t\tvar focusoutEvent,\n\t\t\ttriggered = false,\n\t\t\telement = $( this.target );\n\n\t\tfunction trigger() {\n\t\t\ttriggered = true;\n\t\t}\n\n\t\telement.bind( \"blur\", trigger );\n\t\telement[ 0 ].blur();\n\n\t\t// blur events are async in IE\n\t\tsetTimeout(function() {\n\t\t\t// IE won't let the blur occur if the window is inactive\n\t\t\tif ( element[ 0 ].ownerDocument.activeElement === element[ 0 ] ) {\n\t\t\t\telement[ 0 ].ownerDocument.body.focus();\n\t\t\t}\n\n\t\t\t// Firefox won't trigger events if the window is inactive\n\t\t\t// IE doesn't trigger events if we had to manually focus the body\n\t\t\tif ( !triggered ) {\n\t\t\t\tfocusoutEvent = $.Event( \"focusout\" );\n\t\t\t\tfocusoutEvent.preventDefault();\n\t\t\t\telement.trigger( focusoutEvent );\n\t\t\t\telement.triggerHandler( \"blur\" );\n\t\t\t}\n\t\t\telement.unbind( \"blur\", trigger );\n\t\t}, 1 );\n\t}\n});\n\n\n\n/** complex events **/\n\nfunction findCenter( elem ) {\n\tvar offset,\n\t\tdocument = $( elem.ownerDocument );\n\telem = $( elem );\n\toffset = elem.offset();\n\n\treturn {\n\t\tx: offset.left + elem.outerWidth() / 2 - document.scrollLeft(),\n\t\ty: offset.top + elem.outerHeight() / 2 - document.scrollTop()\n\t};\n}\n\n$.extend( $.simulate.prototype, {\n\tsimulateDrag: function() {\n\t\tvar i = 0,\n\t\t\ttarget = this.target,\n\t\t\toptions = this.options,\n\t\t\tcenter = findCenter( target ),\n\t\t\tx = Math.floor( center.x ),\n\t\t\ty = Math.floor( center.y ),\n\t\t\tdx = options.dx || 0,\n\t\t\tdy = options.dy || 0,\n\t\t\tmoves = options.moves || 3,\n\t\t\tcoord = { clientX: x, clientY: y };\n\n\t\tthis.simulateEvent( target, \"mousedown\", coord );\n\n\t\tfor ( ; i < moves ; i++ ) {\n\t\t\tx += dx / moves;\n\t\t\ty += dy / moves;\n\n\t\t\tcoord = {\n\t\t\t\tclientX: Math.round( x ),\n\t\t\t\tclientY: Math.round( y )\n\t\t\t};\n\n\t\t\tthis.simulateEvent( document, \"mousemove\", coord );\n\t\t}\n\n\t\tthis.simulateEvent( target, \"mouseup\", coord );\n\t\tthis.simulateEvent( target, \"click\", coord );\n\t}\n});\n\n})( jQuery );\n"
  },
  {
    "path": "test/static/third-party/json2/README",
    "content": "JSON in JavaScript\n\n\nDouglas Crockford\ndouglas@crockford.com\n\n2010-11-18\n\n\nJSON is a light-weight, language independent, data interchange format.\nSee http://www.JSON.org/\n\nThe files in this collection implement JSON encoders/decoders in JavaScript.\n\nJSON became a built-in feature of JavaScript when the ECMAScript Programming\nLanguage Standard - Fifth Edition was adopted by the ECMA General Assembly\nin December 2009. Most of the files in this collection are for applications\nthat are expected to run in obsolete web browsers. For most purposes, json2.js\nis the best choice.\n\n\njson2.js: This file creates a JSON property in the global object, if there\nisn't already one, setting its value to an object containing a stringify\nmethod and a parse method. The parse method uses the eval method to do the\nparsing, guarding it with several regular expressions to defend against\naccidental code execution hazards. On current browsers, this file does nothing,\nprefering the built-in JSON object.\n\njson.js: This file does everything that json2.js does. It also adds a\ntoJSONString method and a parseJSON method to Object.prototype. Use of this\nfile is not recommended.\n\njson_parse.js: This file contains an alternative JSON parse function that\nuses recursive descent instead of eval.\n\njson_parse_state.js: This files contains an alternative JSON parse function that\nuses a state machine instead of eval.\n\ncycle.js: This file contains two functions, JSON.decycle and JSON.retrocycle,\nwhich make it possible to encode cyclical structures and dags in JSON, and to\nthen recover them. JSONPath is used to represent the links.\nhttp://GOESSNER.net/articles/JsonPath/\n"
  },
  {
    "path": "test/static/third-party/json2/cycle.js",
    "content": "/*\n    cycle.js\n    2013-02-19\n\n    Public Domain.\n\n    NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.\n\n    This code should be minified before deployment.\n    See http://javascript.crockford.com/jsmin.html\n\n    USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO\n    NOT CONTROL.\n*/\n\n/*jslint evil: true, regexp: true */\n\n/*members $ref, apply, call, decycle, hasOwnProperty, length, prototype, push,\n    retrocycle, stringify, test, toString\n*/\n\nif (typeof JSON.decycle !== 'function') {\n    JSON.decycle = function decycle(object) {\n        'use strict';\n\n// Make a deep copy of an object or array, assuring that there is at most\n// one instance of each object or array in the resulting structure. The\n// duplicate references (which might be forming cycles) are replaced with\n// an object of the form\n//      {$ref: PATH}\n// where the PATH is a JSONPath string that locates the first occurrence.\n// So,\n//      var a = [];\n//      a[0] = a;\n//      return JSON.stringify(JSON.decycle(a));\n// produces the string '[{\"$ref\":\"$\"}]'.\n\n// JSONPath is used to locate the unique object. $ indicates the top level of\n// the object or array. [NUMBER] or [STRING] indicates a child member or\n// property.\n\n        var objects = [],   // Keep a reference to each unique object or array\n            paths = [];     // Keep the path to each unique object or array\n\n        return (function derez(value, path) {\n\n// The derez recurses through the object, producing the deep copy.\n\n            var i,          // The loop counter\n                name,       // Property name\n                nu;         // The new object or array\n\n// typeof null === 'object', so go on if this value is really an object but not\n// one of the weird builtin objects.\n\n            if (typeof value === 'object' && value !== null &&\n                    !(value instanceof Boolean) &&\n                    !(value instanceof Date)    &&\n                    !(value instanceof Number)  &&\n                    !(value instanceof RegExp)  &&\n                    !(value instanceof String)) {\n\n// If the value is an object or array, look to see if we have already\n// encountered it. If so, return a $ref/path object. This is a hard way,\n// linear search that will get slower as the number of unique objects grows.\n\n                for (i = 0; i < objects.length; i += 1) {\n                    if (objects[i] === value) {\n                        return {$ref: paths[i]};\n                    }\n                }\n\n// Otherwise, accumulate the unique value and its path.\n\n                objects.push(value);\n                paths.push(path);\n\n// If it is an array, replicate the array.\n\n                if (Object.prototype.toString.apply(value) === '[object Array]') {\n                    nu = [];\n                    for (i = 0; i < value.length; i += 1) {\n                        nu[i] = derez(value[i], path + '[' + i + ']');\n                    }\n                } else {\n\n// If it is an object, replicate the object.\n\n                    nu = {};\n                    for (name in value) {\n                        if (Object.prototype.hasOwnProperty.call(value, name)) {\n                            nu[name] = derez(value[name],\n                                path + '[' + JSON.stringify(name) + ']');\n                        }\n                    }\n                }\n                return nu;\n            }\n            return value;\n        }(object, '$'));\n    };\n}\n\n\nif (typeof JSON.retrocycle !== 'function') {\n    JSON.retrocycle = function retrocycle($) {\n        'use strict';\n\n// Restore an object that was reduced by decycle. Members whose values are\n// objects of the form\n//      {$ref: PATH}\n// are replaced with references to the value found by the PATH. This will\n// restore cycles. The object will be mutated.\n\n// The eval function is used to locate the values described by a PATH. The\n// root object is kept in a $ variable. A regular expression is used to\n// assure that the PATH is extremely well formed. The regexp contains nested\n// * quantifiers. That has been known to have extremely bad performance\n// problems on some browsers for very long strings. A PATH is expected to be\n// reasonably short. A PATH is allowed to belong to a very restricted subset of\n// Goessner's JSONPath.\n\n// So,\n//      var s = '[{\"$ref\":\"$\"}]';\n//      return JSON.retrocycle(JSON.parse(s));\n// produces an array containing a single element which is the array itself.\n\n        var px =\n            /^\\$(?:\\[(?:\\d+|\\\"(?:[^\\\\\\\"\\u0000-\\u001f]|\\\\([\\\\\\\"\\/bfnrt]|u[0-9a-zA-Z]{4}))*\\\")\\])*$/;\n\n        (function rez(value) {\n\n// The rez function walks recursively through the object looking for $ref\n// properties. When it finds one that has a value that is a path, then it\n// replaces the $ref object with a reference to the value that is found by\n// the path.\n\n            var i, item, name, path;\n\n            if (value && typeof value === 'object') {\n                if (Object.prototype.toString.apply(value) === '[object Array]') {\n                    for (i = 0; i < value.length; i += 1) {\n                        item = value[i];\n                        if (item && typeof item === 'object') {\n                            path = item.$ref;\n                            if (typeof path === 'string' && px.test(path)) {\n                                value[i] = eval(path);\n                            } else {\n                                rez(item);\n                            }\n                        }\n                    }\n                } else {\n                    for (name in value) {\n                        if (typeof value[name] === 'object') {\n                            item = value[name];\n                            if (item) {\n                                path = item.$ref;\n                                if (typeof path === 'string' && px.test(path)) {\n                                    value[name] = eval(path);\n                                } else {\n                                    rez(item);\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }($));\n        return $;\n    };\n}\n"
  },
  {
    "path": "test/static/third-party/json2/json.js",
    "content": "/*\n    json.js\n    2014-02-04\n\n    Public Domain\n\n    No warranty expressed or implied. Use at your own risk.\n\n    This file has been superceded by http://www.JSON.org/json2.js\n\n    See http://www.JSON.org/js.html\n\n    This code should be minified before deployment.\n    See http://javascript.crockford.com/jsmin.html\n\n    USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO\n    NOT CONTROL.\n\n    This file adds these methods to JavaScript:\n\n        object.toJSONString(whitelist)\n            This method produce a JSON text from a JavaScript value.\n            It must not contain any cyclical references. Illegal values\n            will be excluded.\n\n            The default conversion for dates is to an ISO string. You can\n            add a toJSONString method to any date object to get a different\n            representation.\n\n            The object and array methods can take an optional whitelist\n            argument. A whitelist is an array of strings. If it is provided,\n            keys in objects not found in the whitelist are excluded.\n\n        string.parseJSON(filter)\n            This method parses a JSON text to produce an object or\n            array. It can throw a SyntaxError exception.\n\n            The optional filter parameter is a function which can filter and\n            transform the results. It receives each of the keys and values, and\n            its return value is used instead of the original value. If it\n            returns what it received, then structure is not modified. If it\n            returns undefined then the member is deleted.\n\n            Example:\n\n            // Parse the text. If a key contains the string 'date' then\n            // convert the value to a date.\n\n            myData = text.parseJSON(function (key, value) {\n                return key.indexOf('date') >= 0 ? new Date(value) : value;\n            });\n\n    This file will break programs with improper for..in loops. See\n    http://yuiblog.com/blog/2006/09/26/for-in-intrigue/\n\n    This file creates a global JSON object containing two methods: stringify\n    and parse.\n\n        JSON.stringify(value, replacer, space)\n            value       any JavaScript value, usually an object or array.\n\n            replacer    an optional parameter that determines how object\n                        values are stringified for objects. It can be a\n                        function or an array of strings.\n\n            space       an optional parameter that specifies the indentation\n                        of nested structures. If it is omitted, the text will\n                        be packed without extra whitespace. If it is a number,\n                        it will specify the number of spaces to indent at each\n                        level. If it is a string (such as '\\t' or '&nbsp;'),\n                        it contains the characters used to indent at each level.\n\n            This method produces a JSON text from a JavaScript value.\n\n            When an object value is found, if the object contains a toJSON\n            method, its toJSON method will be called and the result will be\n            stringified. A toJSON method does not serialize: it returns the\n            value represented by the name/value pair that should be serialized,\n            or undefined if nothing should be serialized. The toJSON method\n            will be passed the key associated with the value, and this will be\n            bound to the object holding the key.\n\n            For example, this would serialize Dates as ISO strings.\n\n                Date.prototype.toJSON = function (key) {\n                    function f(n) {\n                        // Format integers to have at least two digits.\n                        return n < 10 ? '0' + n : n;\n                    }\n\n                    return this.getUTCFullYear()   + '-' +\n                         f(this.getUTCMonth() + 1) + '-' +\n                         f(this.getUTCDate())      + 'T' +\n                         f(this.getUTCHours())     + ':' +\n                         f(this.getUTCMinutes())   + ':' +\n                         f(this.getUTCSeconds())   + 'Z';\n                };\n\n            You can provide an optional replacer method. It will be passed the\n            key and value of each member, with this bound to the containing\n            object. The value that is returned from your method will be\n            serialized. If your method returns undefined, then the member will\n            be excluded from the serialization.\n\n            If the replacer parameter is an array of strings, then it will be\n            used to select the members to be serialized. It filters the results\n            such that only members with keys listed in the replacer array are\n            stringified.\n\n            Values that do not have JSON representations, such as undefined or\n            functions, will not be serialized. Such values in objects will be\n            dropped; in arrays they will be replaced with null. You can use\n            a replacer function to replace those with JSON values.\n            JSON.stringify(undefined) returns undefined.\n\n            The optional space parameter produces a stringification of the\n            value that is filled with line breaks and indentation to make it\n            easier to read.\n\n            If the space parameter is a non-empty string, then that string will\n            be used for indentation. If the space parameter is a number, then\n            the indentation will be that many spaces.\n\n            Example:\n\n            text = JSON.stringify(['e', {pluribus: 'unum'}]);\n            // text is '[\"e\",{\"pluribus\":\"unum\"}]'\n\n\n            text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\\t');\n            // text is '[\\n\\t\"e\",\\n\\t{\\n\\t\\t\"pluribus\": \"unum\"\\n\\t}\\n]'\n\n            text = JSON.stringify([new Date()], function (key, value) {\n                return this[key] instanceof Date ?\n                    'Date(' + this[key] + ')' : value;\n            });\n            // text is '[\"Date(---current time---)\"]'\n\n\n        JSON.parse(text, reviver)\n            This method parses a JSON text to produce an object or array.\n            It can throw a SyntaxError exception.\n\n            The optional reviver parameter is a function that can filter and\n            transform the results. It receives each of the keys and values,\n            and its return value is used instead of the original value.\n            If it returns what it received, then the structure is not modified.\n            If it returns undefined then the member is deleted.\n\n            Example:\n\n            // Parse the text. Values that look like ISO date strings will\n            // be converted to Date objects.\n\n            myData = JSON.parse(text, function (key, value) {\n                var a;\n                if (typeof value === 'string') {\n                    a =\n/^(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2}(?:\\.\\d*)?)Z$/.exec(value);\n                    if (a) {\n                        return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],\n                            +a[5], +a[6]));\n                    }\n                }\n                return value;\n            });\n\n            myData = JSON.parse('[\"Date(09/09/2001)\"]', function (key, value) {\n                var d;\n                if (typeof value === 'string' &&\n                        value.slice(0, 5) === 'Date(' &&\n                        value.slice(-1) === ')') {\n                    d = new Date(value.slice(5, -1));\n                    if (d) {\n                        return d;\n                    }\n                }\n                return value;\n            });\n\n\n    This is a reference implementation. You are free to copy, modify, or\n    redistribute.\n*/\n\n/*jslint evil: true, regexp: true, unparam: true */\n\n/*members \"\", \"\\b\", \"\\t\", \"\\n\", \"\\f\", \"\\r\", \"\\\"\", JSON, \"\\\\\", apply,\n    call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,\n    getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,\n    lastIndex, length, parse, parseJSON, prototype, push, replace, slice,\n    stringify, test, toJSON, toJSONString, toString, valueOf\n*/\n\n\n// Create a JSON object only if one does not already exist. We create the\n// methods in a closure to avoid creating global variables.\n\nif (typeof JSON !== 'object') {\n    JSON = {};\n}\n\n(function () {\n    'use strict';\n\n    function f(n) {\n        // Format integers to have at least two digits.\n        return n < 10 ? '0' + n : n;\n    }\n\n    if (typeof Date.prototype.toJSON !== 'function') {\n\n        Date.prototype.toJSON = function (key) {\n\n            return isFinite(this.valueOf())\n                ?     this.getUTCFullYear()   + '-' +\n                    f(this.getUTCMonth() + 1) + '-' +\n                    f(this.getUTCDate())      + 'T' +\n                    f(this.getUTCHours())     + ':' +\n                    f(this.getUTCMinutes())   + ':' +\n                    f(this.getUTCSeconds())   + 'Z'\n                : null;\n        };\n\n        String.prototype.toJSON      =\n            Number.prototype.toJSON  =\n            Boolean.prototype.toJSON = function (key) {\n                return this.valueOf();\n            };\n    }\n\n    var cx,\n        escapable,\n        gap,\n        indent,\n        meta,\n        rep;\n\n\n    function quote(string) {\n\n// If the string contains no control characters, no quote characters, and no\n// backslash characters, then we can safely slap some quotes around it.\n// Otherwise we must also replace the offending characters with safe escape\n// sequences.\n\n        escapable.lastIndex = 0;\n        return escapable.test(string) ? '\"' + string.replace(escapable, function (a) {\n            var c = meta[a];\n            return typeof c === 'string'\n                ? c\n                : '\\\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);\n        }) + '\"' : '\"' + string + '\"';\n    }\n\n\n    function str(key, holder) {\n\n// Produce a string from holder[key].\n\n        var i,          // The loop counter.\n            k,          // The member key.\n            v,          // The member value.\n            length,\n            mind = gap,\n            partial,\n            value = holder[key];\n\n// If the value has a toJSON method, call it to obtain a replacement value.\n\n        if (value && typeof value === 'object' &&\n                typeof value.toJSON === 'function') {\n            value = value.toJSON(key);\n        }\n\n// If we were called with a replacer function, then call the replacer to\n// obtain a replacement value.\n\n        if (typeof rep === 'function') {\n            value = rep.call(holder, key, value);\n        }\n\n// What happens next depends on the value's type.\n\n        switch (typeof value) {\n        case 'string':\n            return quote(value);\n\n        case 'number':\n\n// JSON numbers must be finite. Encode non-finite numbers as null.\n\n            return isFinite(value) ? String(value) : 'null';\n\n        case 'boolean':\n        case 'null':\n\n// If the value is a boolean or null, convert it to a string. Note:\n// typeof null does not produce 'null'. The case is included here in\n// the remote chance that this gets fixed someday.\n\n            return String(value);\n\n// If the type is 'object', we might be dealing with an object or an array or\n// null.\n\n        case 'object':\n\n// Due to a specification blunder in ECMAScript, typeof null is 'object',\n// so watch out for that case.\n\n            if (!value) {\n                return 'null';\n            }\n\n// Make an array to hold the partial results of stringifying this object value.\n\n            gap += indent;\n            partial = [];\n\n// Is the value an array?\n\n            if (Object.prototype.toString.apply(value) === '[object Array]') {\n\n// The value is an array. Stringify every element. Use null as a placeholder\n// for non-JSON values.\n\n                length = value.length;\n                for (i = 0; i < length; i += 1) {\n                    partial[i] = str(i, value) || 'null';\n                }\n\n// Join all of the elements together, separated with commas, and wrap them in\n// brackets.\n\n                v = partial.length === 0\n                    ? '[]'\n                    : gap\n                    ? '[\\n' + gap + partial.join(',\\n' + gap) + '\\n' + mind + ']'\n                    : '[' + partial.join(',') + ']';\n                gap = mind;\n                return v;\n            }\n\n// If the replacer is an array, use it to select the members to be stringified.\n\n            if (rep && typeof rep === 'object') {\n                length = rep.length;\n                for (i = 0; i < length; i += 1) {\n                    k = rep[i];\n                    if (typeof k === 'string') {\n                        v = str(k, value);\n                        if (v) {\n                            partial.push(quote(k) + (gap ? ': ' : ':') + v);\n                        }\n                    }\n                }\n            } else {\n\n// Otherwise, iterate through all of the keys in the object.\n\n                for (k in value) {\n                    if (Object.prototype.hasOwnProperty.call(value, k)) {\n                        v = str(k, value);\n                        if (v) {\n                            partial.push(quote(k) + (gap ? ': ' : ':') + v);\n                        }\n                    }\n                }\n            }\n\n// Join all of the member texts together, separated with commas,\n// and wrap them in braces.\n\n            v = partial.length === 0 ? '{}'\n                : gap\n                ? '{\\n' + gap + partial.join(',\\n' + gap) + '\\n' + mind + '}'\n                : '{' + partial.join(',') + '}';\n            gap = mind;\n            return v;\n        }\n    }\n\n// If the JSON object does not yet have a stringify method, give it one.\n\n    if (typeof JSON.stringify !== 'function') {\n        escapable = /[\\\\\\\"\\x00-\\x1f\\x7f-\\x9f\\u00ad\\u0600-\\u0604\\u070f\\u17b4\\u17b5\\u200c-\\u200f\\u2028-\\u202f\\u2060-\\u206f\\ufeff\\ufff0-\\uffff]/g;\n        meta = {    // table of character substitutions\n            '\\b': '\\\\b',\n            '\\t': '\\\\t',\n            '\\n': '\\\\n',\n            '\\f': '\\\\f',\n            '\\r': '\\\\r',\n            '\"' : '\\\\\"',\n            '\\\\': '\\\\\\\\'\n        };\n        JSON.stringify = function (value, replacer, space) {\n\n// The stringify method takes a value and an optional replacer, and an optional\n// space parameter, and returns a JSON text. The replacer can be a function\n// that can replace values, or an array of strings that will select the keys.\n// A default replacer method can be provided. Use of the space parameter can\n// produce text that is more easily readable.\n\n            var i;\n            gap = '';\n            indent = '';\n\n// If the space parameter is a number, make an indent string containing that\n// many spaces.\n\n            if (typeof space === 'number') {\n                for (i = 0; i < space; i += 1) {\n                    indent += ' ';\n                }\n\n// If the space parameter is a string, it will be used as the indent string.\n\n            } else if (typeof space === 'string') {\n                indent = space;\n            }\n\n// If there is a replacer, it must be a function or an array.\n// Otherwise, throw an error.\n\n            rep = replacer;\n            if (replacer && typeof replacer !== 'function' &&\n                    (typeof replacer !== 'object' ||\n                    typeof replacer.length !== 'number')) {\n                throw new Error('JSON.stringify');\n            }\n\n// Make a fake root object containing our value under the key of ''.\n// Return the result of stringifying the value.\n\n            return str('', {'': value});\n        };\n    }\n\n\n// If the JSON object does not yet have a parse method, give it one.\n\n    if (typeof JSON.parse !== 'function') {\n        cx = /[\\u0000\\u00ad\\u0600-\\u0604\\u070f\\u17b4\\u17b5\\u200c-\\u200f\\u2028-\\u202f\\u2060-\\u206f\\ufeff\\ufff0-\\uffff]/g;\n        JSON.parse = function (text, reviver) {\n\n// The parse method takes a text and an optional reviver function, and returns\n// a JavaScript value if the text is a valid JSON text.\n\n            var j;\n\n            function walk(holder, key) {\n\n// The walk method is used to recursively walk the resulting structure so\n// that modifications can be made.\n\n                var k, v, value = holder[key];\n                if (value && typeof value === 'object') {\n                    for (k in value) {\n                        if (Object.prototype.hasOwnProperty.call(value, k)) {\n                            v = walk(value, k);\n                            if (v !== undefined) {\n                                value[k] = v;\n                            } else {\n                                delete value[k];\n                            }\n                        }\n                    }\n                }\n                return reviver.call(holder, key, value);\n            }\n\n\n// Parsing happens in four stages. In the first stage, we replace certain\n// Unicode characters with escape sequences. JavaScript handles many characters\n// incorrectly, either silently deleting them, or treating them as line endings.\n\n            text = String(text);\n            cx.lastIndex = 0;\n            if (cx.test(text)) {\n                text = text.replace(cx, function (a) {\n                    return '\\\\u' +\n                        ('0000' + a.charCodeAt(0).toString(16)).slice(-4);\n                });\n            }\n\n// In the second stage, we run the text against regular expressions that look\n// for non-JSON patterns. We are especially concerned with '()' and 'new'\n// because they can cause invocation, and '=' because it can cause mutation.\n// But just to be safe, we want to reject all unexpected forms.\n\n// We split the second stage into 4 regexp operations in order to work around\n// crippling inefficiencies in IE's and Safari's regexp engines. First we\n// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we\n// replace all simple value tokens with ']' characters. Third, we delete all\n// open brackets that follow a colon or comma or that begin the text. Finally,\n// we look to see that the remaining characters are only whitespace or ']' or\n// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.\n\n            if (/^[\\],:{}\\s]*$/\n                    .test(text.replace(/\\\\(?:[\"\\\\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')\n                        .replace(/\"[^\"\\\\\\n\\r]*\"|true|false|null|-?\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d+)?/g, ']')\n                        .replace(/(?:^|:|,)(?:\\s*\\[)+/g, ''))) {\n\n// In the third stage we use the eval function to compile the text into a\n// JavaScript structure. The '{' operator is subject to a syntactic ambiguity\n// in JavaScript: it can begin a block or an object literal. We wrap the text\n// in parens to eliminate the ambiguity.\n\n                j = eval('(' + text + ')');\n\n// In the optional fourth stage, we recursively walk the new structure, passing\n// each name/value pair to a reviver function for possible transformation.\n\n                return typeof reviver === 'function'\n                    ? walk({'': j}, '')\n                    : j;\n            }\n\n// If the text is not JSON parseable, then a SyntaxError is thrown.\n\n            throw new SyntaxError('JSON.parse');\n        };\n    }\n\n// Augment the basic prototypes if they have not already been augmented.\n// These forms are obsolete. It is recommended that JSON.stringify and\n// JSON.parse be used instead.\n\n    if (!Object.prototype.toJSONString) {\n        Object.prototype.toJSONString = function (filter) {\n            return JSON.stringify(this, filter);\n        };\n        Object.prototype.parseJSON = function (filter) {\n            return JSON.parse(this, filter);\n        };\n    }\n}());\n"
  },
  {
    "path": "test/static/third-party/json2/json2.js",
    "content": "/*\n    json2.js\n    2014-02-04\n\n    Public Domain.\n\n    NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.\n\n    See http://www.JSON.org/js.html\n\n\n    This code should be minified before deployment.\n    See http://javascript.crockford.com/jsmin.html\n\n    USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO\n    NOT CONTROL.\n\n\n    This file creates a global JSON object containing two methods: stringify\n    and parse.\n\n        JSON.stringify(value, replacer, space)\n            value       any JavaScript value, usually an object or array.\n\n            replacer    an optional parameter that determines how object\n                        values are stringified for objects. It can be a\n                        function or an array of strings.\n\n            space       an optional parameter that specifies the indentation\n                        of nested structures. If it is omitted, the text will\n                        be packed without extra whitespace. If it is a number,\n                        it will specify the number of spaces to indent at each\n                        level. If it is a string (such as '\\t' or '&nbsp;'),\n                        it contains the characters used to indent at each level.\n\n            This method produces a JSON text from a JavaScript value.\n\n            When an object value is found, if the object contains a toJSON\n            method, its toJSON method will be called and the result will be\n            stringified. A toJSON method does not serialize: it returns the\n            value represented by the name/value pair that should be serialized,\n            or undefined if nothing should be serialized. The toJSON method\n            will be passed the key associated with the value, and this will be\n            bound to the value\n\n            For example, this would serialize Dates as ISO strings.\n\n                Date.prototype.toJSON = function (key) {\n                    function f(n) {\n                        // Format integers to have at least two digits.\n                        return n < 10 ? '0' + n : n;\n                    }\n\n                    return this.getUTCFullYear()   + '-' +\n                         f(this.getUTCMonth() + 1) + '-' +\n                         f(this.getUTCDate())      + 'T' +\n                         f(this.getUTCHours())     + ':' +\n                         f(this.getUTCMinutes())   + ':' +\n                         f(this.getUTCSeconds())   + 'Z';\n                };\n\n            You can provide an optional replacer method. It will be passed the\n            key and value of each member, with this bound to the containing\n            object. The value that is returned from your method will be\n            serialized. If your method returns undefined, then the member will\n            be excluded from the serialization.\n\n            If the replacer parameter is an array of strings, then it will be\n            used to select the members to be serialized. It filters the results\n            such that only members with keys listed in the replacer array are\n            stringified.\n\n            Values that do not have JSON representations, such as undefined or\n            functions, will not be serialized. Such values in objects will be\n            dropped; in arrays they will be replaced with null. You can use\n            a replacer function to replace those with JSON values.\n            JSON.stringify(undefined) returns undefined.\n\n            The optional space parameter produces a stringification of the\n            value that is filled with line breaks and indentation to make it\n            easier to read.\n\n            If the space parameter is a non-empty string, then that string will\n            be used for indentation. If the space parameter is a number, then\n            the indentation will be that many spaces.\n\n            Example:\n\n            text = JSON.stringify(['e', {pluribus: 'unum'}]);\n            // text is '[\"e\",{\"pluribus\":\"unum\"}]'\n\n\n            text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\\t');\n            // text is '[\\n\\t\"e\",\\n\\t{\\n\\t\\t\"pluribus\": \"unum\"\\n\\t}\\n]'\n\n            text = JSON.stringify([new Date()], function (key, value) {\n                return this[key] instanceof Date ?\n                    'Date(' + this[key] + ')' : value;\n            });\n            // text is '[\"Date(---current time---)\"]'\n\n\n        JSON.parse(text, reviver)\n            This method parses a JSON text to produce an object or array.\n            It can throw a SyntaxError exception.\n\n            The optional reviver parameter is a function that can filter and\n            transform the results. It receives each of the keys and values,\n            and its return value is used instead of the original value.\n            If it returns what it received, then the structure is not modified.\n            If it returns undefined then the member is deleted.\n\n            Example:\n\n            // Parse the text. Values that look like ISO date strings will\n            // be converted to Date objects.\n\n            myData = JSON.parse(text, function (key, value) {\n                var a;\n                if (typeof value === 'string') {\n                    a =\n/^(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2}(?:\\.\\d*)?)Z$/.exec(value);\n                    if (a) {\n                        return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],\n                            +a[5], +a[6]));\n                    }\n                }\n                return value;\n            });\n\n            myData = JSON.parse('[\"Date(09/09/2001)\"]', function (key, value) {\n                var d;\n                if (typeof value === 'string' &&\n                        value.slice(0, 5) === 'Date(' &&\n                        value.slice(-1) === ')') {\n                    d = new Date(value.slice(5, -1));\n                    if (d) {\n                        return d;\n                    }\n                }\n                return value;\n            });\n\n\n    This is a reference implementation. You are free to copy, modify, or\n    redistribute.\n*/\n\n/*jslint evil: true, regexp: true */\n\n/*members \"\", \"\\b\", \"\\t\", \"\\n\", \"\\f\", \"\\r\", \"\\\"\", JSON, \"\\\\\", apply,\n    call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,\n    getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,\n    lastIndex, length, parse, prototype, push, replace, slice, stringify,\n    test, toJSON, toString, valueOf\n*/\n\n\n// Create a JSON object only if one does not already exist. We create the\n// methods in a closure to avoid creating global variables.\n\nif (typeof JSON !== 'object') {\n    JSON = {};\n}\n\n(function () {\n    'use strict';\n\n    function f(n) {\n        // Format integers to have at least two digits.\n        return n < 10 ? '0' + n : n;\n    }\n\n    if (typeof Date.prototype.toJSON !== 'function') {\n\n        Date.prototype.toJSON = function () {\n\n            return isFinite(this.valueOf())\n                ? this.getUTCFullYear()     + '-' +\n                    f(this.getUTCMonth() + 1) + '-' +\n                    f(this.getUTCDate())      + 'T' +\n                    f(this.getUTCHours())     + ':' +\n                    f(this.getUTCMinutes())   + ':' +\n                    f(this.getUTCSeconds())   + 'Z'\n                : null;\n        };\n\n        String.prototype.toJSON      =\n            Number.prototype.toJSON  =\n            Boolean.prototype.toJSON = function () {\n                return this.valueOf();\n            };\n    }\n\n    var cx,\n        escapable,\n        gap,\n        indent,\n        meta,\n        rep;\n\n\n    function quote(string) {\n\n// If the string contains no control characters, no quote characters, and no\n// backslash characters, then we can safely slap some quotes around it.\n// Otherwise we must also replace the offending characters with safe escape\n// sequences.\n\n        escapable.lastIndex = 0;\n        return escapable.test(string) ? '\"' + string.replace(escapable, function (a) {\n            var c = meta[a];\n            return typeof c === 'string'\n                ? c\n                : '\\\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);\n        }) + '\"' : '\"' + string + '\"';\n    }\n\n\n    function str(key, holder) {\n\n// Produce a string from holder[key].\n\n        var i,          // The loop counter.\n            k,          // The member key.\n            v,          // The member value.\n            length,\n            mind = gap,\n            partial,\n            value = holder[key];\n\n// If the value has a toJSON method, call it to obtain a replacement value.\n\n        if (value && typeof value === 'object' &&\n                typeof value.toJSON === 'function') {\n            value = value.toJSON(key);\n        }\n\n// If we were called with a replacer function, then call the replacer to\n// obtain a replacement value.\n\n        if (typeof rep === 'function') {\n            value = rep.call(holder, key, value);\n        }\n\n// What happens next depends on the value's type.\n\n        switch (typeof value) {\n        case 'string':\n            return quote(value);\n\n        case 'number':\n\n// JSON numbers must be finite. Encode non-finite numbers as null.\n\n            return isFinite(value) ? String(value) : 'null';\n\n        case 'boolean':\n        case 'null':\n\n// If the value is a boolean or null, convert it to a string. Note:\n// typeof null does not produce 'null'. The case is included here in\n// the remote chance that this gets fixed someday.\n\n            return String(value);\n\n// If the type is 'object', we might be dealing with an object or an array or\n// null.\n\n        case 'object':\n\n// Due to a specification blunder in ECMAScript, typeof null is 'object',\n// so watch out for that case.\n\n            if (!value) {\n                return 'null';\n            }\n\n// Make an array to hold the partial results of stringifying this object value.\n\n            gap += indent;\n            partial = [];\n\n// Is the value an array?\n\n            if (Object.prototype.toString.apply(value) === '[object Array]') {\n\n// The value is an array. Stringify every element. Use null as a placeholder\n// for non-JSON values.\n\n                length = value.length;\n                for (i = 0; i < length; i += 1) {\n                    partial[i] = str(i, value) || 'null';\n                }\n\n// Join all of the elements together, separated with commas, and wrap them in\n// brackets.\n\n                v = partial.length === 0\n                    ? '[]'\n                    : gap\n                    ? '[\\n' + gap + partial.join(',\\n' + gap) + '\\n' + mind + ']'\n                    : '[' + partial.join(',') + ']';\n                gap = mind;\n                return v;\n            }\n\n// If the replacer is an array, use it to select the members to be stringified.\n\n            if (rep && typeof rep === 'object') {\n                length = rep.length;\n                for (i = 0; i < length; i += 1) {\n                    if (typeof rep[i] === 'string') {\n                        k = rep[i];\n                        v = str(k, value);\n                        if (v) {\n                            partial.push(quote(k) + (gap ? ': ' : ':') + v);\n                        }\n                    }\n                }\n            } else {\n\n// Otherwise, iterate through all of the keys in the object.\n\n                for (k in value) {\n                    if (Object.prototype.hasOwnProperty.call(value, k)) {\n                        v = str(k, value);\n                        if (v) {\n                            partial.push(quote(k) + (gap ? ': ' : ':') + v);\n                        }\n                    }\n                }\n            }\n\n// Join all of the member texts together, separated with commas,\n// and wrap them in braces.\n\n            v = partial.length === 0\n                ? '{}'\n                : gap\n                ? '{\\n' + gap + partial.join(',\\n' + gap) + '\\n' + mind + '}'\n                : '{' + partial.join(',') + '}';\n            gap = mind;\n            return v;\n        }\n    }\n\n// If the JSON object does not yet have a stringify method, give it one.\n\n    if (typeof JSON.stringify !== 'function') {\n        escapable = /[\\\\\\\"\\x00-\\x1f\\x7f-\\x9f\\u00ad\\u0600-\\u0604\\u070f\\u17b4\\u17b5\\u200c-\\u200f\\u2028-\\u202f\\u2060-\\u206f\\ufeff\\ufff0-\\uffff]/g;\n        meta = {    // table of character substitutions\n            '\\b': '\\\\b',\n            '\\t': '\\\\t',\n            '\\n': '\\\\n',\n            '\\f': '\\\\f',\n            '\\r': '\\\\r',\n            '\"' : '\\\\\"',\n            '\\\\': '\\\\\\\\'\n        };\n        JSON.stringify = function (value, replacer, space) {\n\n// The stringify method takes a value and an optional replacer, and an optional\n// space parameter, and returns a JSON text. The replacer can be a function\n// that can replace values, or an array of strings that will select the keys.\n// A default replacer method can be provided. Use of the space parameter can\n// produce text that is more easily readable.\n\n            var i;\n            gap = '';\n            indent = '';\n\n// If the space parameter is a number, make an indent string containing that\n// many spaces.\n\n            if (typeof space === 'number') {\n                for (i = 0; i < space; i += 1) {\n                    indent += ' ';\n                }\n\n// If the space parameter is a string, it will be used as the indent string.\n\n            } else if (typeof space === 'string') {\n                indent = space;\n            }\n\n// If there is a replacer, it must be a function or an array.\n// Otherwise, throw an error.\n\n            rep = replacer;\n            if (replacer && typeof replacer !== 'function' &&\n                    (typeof replacer !== 'object' ||\n                    typeof replacer.length !== 'number')) {\n                throw new Error('JSON.stringify');\n            }\n\n// Make a fake root object containing our value under the key of ''.\n// Return the result of stringifying the value.\n\n            return str('', {'': value});\n        };\n    }\n\n\n// If the JSON object does not yet have a parse method, give it one.\n\n    if (typeof JSON.parse !== 'function') {\n        cx = /[\\u0000\\u00ad\\u0600-\\u0604\\u070f\\u17b4\\u17b5\\u200c-\\u200f\\u2028-\\u202f\\u2060-\\u206f\\ufeff\\ufff0-\\uffff]/g;\n        JSON.parse = function (text, reviver) {\n\n// The parse method takes a text and an optional reviver function, and returns\n// a JavaScript value if the text is a valid JSON text.\n\n            var j;\n\n            function walk(holder, key) {\n\n// The walk method is used to recursively walk the resulting structure so\n// that modifications can be made.\n\n                var k, v, value = holder[key];\n                if (value && typeof value === 'object') {\n                    for (k in value) {\n                        if (Object.prototype.hasOwnProperty.call(value, k)) {\n                            v = walk(value, k);\n                            if (v !== undefined) {\n                                value[k] = v;\n                            } else {\n                                delete value[k];\n                            }\n                        }\n                    }\n                }\n                return reviver.call(holder, key, value);\n            }\n\n\n// Parsing happens in four stages. In the first stage, we replace certain\n// Unicode characters with escape sequences. JavaScript handles many characters\n// incorrectly, either silently deleting them, or treating them as line endings.\n\n            text = String(text);\n            cx.lastIndex = 0;\n            if (cx.test(text)) {\n                text = text.replace(cx, function (a) {\n                    return '\\\\u' +\n                        ('0000' + a.charCodeAt(0).toString(16)).slice(-4);\n                });\n            }\n\n// In the second stage, we run the text against regular expressions that look\n// for non-JSON patterns. We are especially concerned with '()' and 'new'\n// because they can cause invocation, and '=' because it can cause mutation.\n// But just to be safe, we want to reject all unexpected forms.\n\n// We split the second stage into 4 regexp operations in order to work around\n// crippling inefficiencies in IE's and Safari's regexp engines. First we\n// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we\n// replace all simple value tokens with ']' characters. Third, we delete all\n// open brackets that follow a colon or comma or that begin the text. Finally,\n// we look to see that the remaining characters are only whitespace or ']' or\n// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.\n\n            if (/^[\\],:{}\\s]*$/\n                    .test(text.replace(/\\\\(?:[\"\\\\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')\n                        .replace(/\"[^\"\\\\\\n\\r]*\"|true|false|null|-?\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d+)?/g, ']')\n                        .replace(/(?:^|:|,)(?:\\s*\\[)+/g, ''))) {\n\n// In the third stage we use the eval function to compile the text into a\n// JavaScript structure. The '{' operator is subject to a syntactic ambiguity\n// in JavaScript: it can begin a block or an object literal. We wrap the text\n// in parens to eliminate the ambiguity.\n\n                j = eval('(' + text + ')');\n\n// In the optional fourth stage, we recursively walk the new structure, passing\n// each name/value pair to a reviver function for possible transformation.\n\n                return typeof reviver === 'function'\n                    ? walk({'': j}, '')\n                    : j;\n            }\n\n// If the text is not JSON parseable, then a SyntaxError is thrown.\n\n            throw new SyntaxError('JSON.parse');\n        };\n    }\n}());\n"
  },
  {
    "path": "test/static/third-party/json2/json_parse.js",
    "content": "/*\n    json_parse.js\n    2012-06-20\n\n    Public Domain.\n\n    NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.\n\n    This file creates a json_parse function.\n\n        json_parse(text, reviver)\n            This method parses a JSON text to produce an object or array.\n            It can throw a SyntaxError exception.\n\n            The optional reviver parameter is a function that can filter and\n            transform the results. It receives each of the keys and values,\n            and its return value is used instead of the original value.\n            If it returns what it received, then the structure is not modified.\n            If it returns undefined then the member is deleted.\n\n            Example:\n\n            // Parse the text. Values that look like ISO date strings will\n            // be converted to Date objects.\n\n            myData = json_parse(text, function (key, value) {\n                var a;\n                if (typeof value === 'string') {\n                    a =\n/^(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2}(?:\\.\\d*)?)Z$/.exec(value);\n                    if (a) {\n                        return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],\n                            +a[5], +a[6]));\n                    }\n                }\n                return value;\n            });\n\n    This is a reference implementation. You are free to copy, modify, or\n    redistribute.\n\n    This code should be minified before deployment.\n    See http://javascript.crockford.com/jsmin.html\n\n    USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO\n    NOT CONTROL.\n*/\n\n/*members \"\", \"\\\"\", \"\\/\", \"\\\\\", at, b, call, charAt, f, fromCharCode,\n    hasOwnProperty, message, n, name, prototype, push, r, t, text\n*/\n\nvar json_parse = (function () {\n    \"use strict\";\n\n// This is a function that can parse a JSON text, producing a JavaScript\n// data structure. It is a simple, recursive descent parser. It does not use\n// eval or regular expressions, so it can be used as a model for implementing\n// a JSON parser in other languages.\n\n// We are defining the function inside of another function to avoid creating\n// global variables.\n\n    var at,     // The index of the current character\n        ch,     // The current character\n        escapee = {\n            '\"':  '\"',\n            '\\\\': '\\\\',\n            '/':  '/',\n            b:    '\\b',\n            f:    '\\f',\n            n:    '\\n',\n            r:    '\\r',\n            t:    '\\t'\n        },\n        text,\n\n        error = function (m) {\n\n// Call error when something is wrong.\n\n            throw {\n                name:    'SyntaxError',\n                message: m,\n                at:      at,\n                text:    text\n            };\n        },\n\n        next = function (c) {\n\n// If a c parameter is provided, verify that it matches the current character.\n\n            if (c && c !== ch) {\n                error(\"Expected '\" + c + \"' instead of '\" + ch + \"'\");\n            }\n\n// Get the next character. When there are no more characters,\n// return the empty string.\n\n            ch = text.charAt(at);\n            at += 1;\n            return ch;\n        },\n\n        number = function () {\n\n// Parse a number value.\n\n            var number,\n                string = '';\n\n            if (ch === '-') {\n                string = '-';\n                next('-');\n            }\n            while (ch >= '0' && ch <= '9') {\n                string += ch;\n                next();\n            }\n            if (ch === '.') {\n                string += '.';\n                while (next() && ch >= '0' && ch <= '9') {\n                    string += ch;\n                }\n            }\n            if (ch === 'e' || ch === 'E') {\n                string += ch;\n                next();\n                if (ch === '-' || ch === '+') {\n                    string += ch;\n                    next();\n                }\n                while (ch >= '0' && ch <= '9') {\n                    string += ch;\n                    next();\n                }\n            }\n            number = +string;\n            if (!isFinite(number)) {\n                error(\"Bad number\");\n            } else {\n                return number;\n            }\n        },\n\n        string = function () {\n\n// Parse a string value.\n\n            var hex,\n                i,\n                string = '',\n                uffff;\n\n// When parsing for string values, we must look for \" and \\ characters.\n\n            if (ch === '\"') {\n                while (next()) {\n                    if (ch === '\"') {\n                        next();\n                        return string;\n                    }\n                    if (ch === '\\\\') {\n                        next();\n                        if (ch === 'u') {\n                            uffff = 0;\n                            for (i = 0; i < 4; i += 1) {\n                                hex = parseInt(next(), 16);\n                                if (!isFinite(hex)) {\n                                    break;\n                                }\n                                uffff = uffff * 16 + hex;\n                            }\n                            string += String.fromCharCode(uffff);\n                        } else if (typeof escapee[ch] === 'string') {\n                            string += escapee[ch];\n                        } else {\n                            break;\n                        }\n                    } else {\n                        string += ch;\n                    }\n                }\n            }\n            error(\"Bad string\");\n        },\n\n        white = function () {\n\n// Skip whitespace.\n\n            while (ch && ch <= ' ') {\n                next();\n            }\n        },\n\n        word = function () {\n\n// true, false, or null.\n\n            switch (ch) {\n            case 't':\n                next('t');\n                next('r');\n                next('u');\n                next('e');\n                return true;\n            case 'f':\n                next('f');\n                next('a');\n                next('l');\n                next('s');\n                next('e');\n                return false;\n            case 'n':\n                next('n');\n                next('u');\n                next('l');\n                next('l');\n                return null;\n            }\n            error(\"Unexpected '\" + ch + \"'\");\n        },\n\n        value,  // Place holder for the value function.\n\n        array = function () {\n\n// Parse an array value.\n\n            var array = [];\n\n            if (ch === '[') {\n                next('[');\n                white();\n                if (ch === ']') {\n                    next(']');\n                    return array;   // empty array\n                }\n                while (ch) {\n                    array.push(value());\n                    white();\n                    if (ch === ']') {\n                        next(']');\n                        return array;\n                    }\n                    next(',');\n                    white();\n                }\n            }\n            error(\"Bad array\");\n        },\n\n        object = function () {\n\n// Parse an object value.\n\n            var key,\n                object = {};\n\n            if (ch === '{') {\n                next('{');\n                white();\n                if (ch === '}') {\n                    next('}');\n                    return object;   // empty object\n                }\n                while (ch) {\n                    key = string();\n                    white();\n                    next(':');\n                    if (Object.hasOwnProperty.call(object, key)) {\n                        error('Duplicate key \"' + key + '\"');\n                    }\n                    object[key] = value();\n                    white();\n                    if (ch === '}') {\n                        next('}');\n                        return object;\n                    }\n                    next(',');\n                    white();\n                }\n            }\n            error(\"Bad object\");\n        };\n\n    value = function () {\n\n// Parse a JSON value. It could be an object, an array, a string, a number,\n// or a word.\n\n        white();\n        switch (ch) {\n        case '{':\n            return object();\n        case '[':\n            return array();\n        case '\"':\n            return string();\n        case '-':\n            return number();\n        default:\n            return ch >= '0' && ch <= '9' ? number() : word();\n        }\n    };\n\n// Return the json_parse function. It will have access to all of the above\n// functions and variables.\n\n    return function (source, reviver) {\n        var result;\n\n        text = source;\n        at = 0;\n        ch = ' ';\n        result = value();\n        white();\n        if (ch) {\n            error(\"Syntax error\");\n        }\n\n// If there is a reviver function, we recursively walk the new structure,\n// passing each name/value pair to the reviver function for possible\n// transformation, starting with a temporary root object that holds the result\n// in an empty key. If there is not a reviver function, we simply return the\n// result.\n\n        return typeof reviver === 'function'\n            ? (function walk(holder, key) {\n                var k, v, value = holder[key];\n                if (value && typeof value === 'object') {\n                    for (k in value) {\n                        if (Object.prototype.hasOwnProperty.call(value, k)) {\n                            v = walk(value, k);\n                            if (v !== undefined) {\n                                value[k] = v;\n                            } else {\n                                delete value[k];\n                            }\n                        }\n                    }\n                }\n                return reviver.call(holder, key, value);\n            }({'': result}, ''))\n            : result;\n    };\n}());\n"
  },
  {
    "path": "test/static/third-party/json2/json_parse_state.js",
    "content": "/*\n    json_parse_state.js\n    2013-05-26\n\n    Public Domain.\n\n    NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.\n\n    This file creates a json_parse function.\n\n        json_parse(text, reviver)\n            This method parses a JSON text to produce an object or array.\n            It can throw a SyntaxError exception.\n\n            The optional reviver parameter is a function that can filter and\n            transform the results. It receives each of the keys and values,\n            and its return value is used instead of the original value.\n            If it returns what it received, then the structure is not modified.\n            If it returns undefined then the member is deleted.\n\n            Example:\n\n            // Parse the text. Values that look like ISO date strings will\n            // be converted to Date objects.\n\n            myData = json_parse(text, function (key, value) {\n                var a;\n                if (typeof value === 'string') {\n                    a =\n/^(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2}(?:\\.\\d*)?)Z$/.exec(value);\n                    if (a) {\n                        return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],\n                            +a[5], +a[6]));\n                    }\n                }\n                return value;\n            });\n\n    This is a reference implementation. You are free to copy, modify, or\n    redistribute.\n\n    This code should be minified before deployment.\n    See http://javascript.crockford.com/jsmin.html\n\n    USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO\n    NOT CONTROL.\n*/\n\n/*jslint regexp: true, unparam: true */\n\n/*members \"\", \"\\\"\", \",\", \"\\/\", \":\", \"[\", \"\\\\\", \"]\", acomma, avalue, b,\n    call, colon, container, exec, f, false, firstavalue, firstokey,\n    fromCharCode, go, hasOwnProperty, key, length, n, null, ocomma, okey,\n    ovalue, pop, prototype, push, r, replace, slice, state, t, test, true,\n    value, \"{\", \"}\"\n*/\n\nvar json_parse = (function () {\n    \"use strict\";\n\n// This function creates a JSON parse function that uses a state machine rather\n// than the dangerous eval function to parse a JSON text.\n\n    var state,      // The state of the parser, one of\n                    // 'go'         The starting state\n                    // 'ok'         The final, accepting state\n                    // 'firstokey'  Ready for the first key of the object or\n                    //              the closing of an empty object\n                    // 'okey'       Ready for the next key of the object\n                    // 'colon'      Ready for the colon\n                    // 'ovalue'     Ready for the value half of a key/value pair\n                    // 'ocomma'     Ready for a comma or closing }\n                    // 'firstavalue' Ready for the first value of an array or\n                    //              an empty array\n                    // 'avalue'     Ready for the next value of an array\n                    // 'acomma'     Ready for a comma or closing ]\n        stack,      // The stack, for controlling nesting.\n        container,  // The current container object or array\n        key,        // The current key\n        value,      // The current value\n        escapes = { // Escapement translation table\n            '\\\\': '\\\\',\n            '\"': '\"',\n            '/': '/',\n            't': '\\t',\n            'n': '\\n',\n            'r': '\\r',\n            'f': '\\f',\n            'b': '\\b'\n        },\n        string = {   // The actions for string tokens\n            go: function () {\n                state = 'ok';\n            },\n            firstokey: function () {\n                key = value;\n                state = 'colon';\n            },\n            okey: function () {\n                key = value;\n                state = 'colon';\n            },\n            ovalue: function () {\n                state = 'ocomma';\n            },\n            firstavalue: function () {\n                state = 'acomma';\n            },\n            avalue: function () {\n                state = 'acomma';\n            }\n        },\n        number = {   // The actions for number tokens\n            go: function () {\n                state = 'ok';\n            },\n            ovalue: function () {\n                state = 'ocomma';\n            },\n            firstavalue: function () {\n                state = 'acomma';\n            },\n            avalue: function () {\n                state = 'acomma';\n            }\n        },\n        action = {\n\n// The action table describes the behavior of the machine. It contains an\n// object for each token. Each object contains a method that is called when\n// a token is matched in a state. An object will lack a method for illegal\n// states.\n\n            '{': {\n                go: function () {\n                    stack.push({state: 'ok'});\n                    container = {};\n                    state = 'firstokey';\n                },\n                ovalue: function () {\n                    stack.push({container: container, state: 'ocomma', key: key});\n                    container = {};\n                    state = 'firstokey';\n                },\n                firstavalue: function () {\n                    stack.push({container: container, state: 'acomma'});\n                    container = {};\n                    state = 'firstokey';\n                },\n                avalue: function () {\n                    stack.push({container: container, state: 'acomma'});\n                    container = {};\n                    state = 'firstokey';\n                }\n            },\n            '}': {\n                firstokey: function () {\n                    var pop = stack.pop();\n                    value = container;\n                    container = pop.container;\n                    key = pop.key;\n                    state = pop.state;\n                },\n                ocomma: function () {\n                    var pop = stack.pop();\n                    container[key] = value;\n                    value = container;\n                    container = pop.container;\n                    key = pop.key;\n                    state = pop.state;\n                }\n            },\n            '[': {\n                go: function () {\n                    stack.push({state: 'ok'});\n                    container = [];\n                    state = 'firstavalue';\n                },\n                ovalue: function () {\n                    stack.push({container: container, state: 'ocomma', key: key});\n                    container = [];\n                    state = 'firstavalue';\n                },\n                firstavalue: function () {\n                    stack.push({container: container, state: 'acomma'});\n                    container = [];\n                    state = 'firstavalue';\n                },\n                avalue: function () {\n                    stack.push({container: container, state: 'acomma'});\n                    container = [];\n                    state = 'firstavalue';\n                }\n            },\n            ']': {\n                firstavalue: function () {\n                    var pop = stack.pop();\n                    value = container;\n                    container = pop.container;\n                    key = pop.key;\n                    state = pop.state;\n                },\n                acomma: function () {\n                    var pop = stack.pop();\n                    container.push(value);\n                    value = container;\n                    container = pop.container;\n                    key = pop.key;\n                    state = pop.state;\n                }\n            },\n            ':': {\n                colon: function () {\n                    if (Object.hasOwnProperty.call(container, key)) {\n                        throw new SyntaxError('Duplicate key \"' + key + '\"');\n                    }\n                    state = 'ovalue';\n                }\n            },\n            ',': {\n                ocomma: function () {\n                    container[key] = value;\n                    state = 'okey';\n                },\n                acomma: function () {\n                    container.push(value);\n                    state = 'avalue';\n                }\n            },\n            'true': {\n                go: function () {\n                    value = true;\n                    state = 'ok';\n                },\n                ovalue: function () {\n                    value = true;\n                    state = 'ocomma';\n                },\n                firstavalue: function () {\n                    value = true;\n                    state = 'acomma';\n                },\n                avalue: function () {\n                    value = true;\n                    state = 'acomma';\n                }\n            },\n            'false': {\n                go: function () {\n                    value = false;\n                    state = 'ok';\n                },\n                ovalue: function () {\n                    value = false;\n                    state = 'ocomma';\n                },\n                firstavalue: function () {\n                    value = false;\n                    state = 'acomma';\n                },\n                avalue: function () {\n                    value = false;\n                    state = 'acomma';\n                }\n            },\n            'null': {\n                go: function () {\n                    value = null;\n                    state = 'ok';\n                },\n                ovalue: function () {\n                    value = null;\n                    state = 'ocomma';\n                },\n                firstavalue: function () {\n                    value = null;\n                    state = 'acomma';\n                },\n                avalue: function () {\n                    value = null;\n                    state = 'acomma';\n                }\n            }\n        };\n\n    function debackslashify(text) {\n\n// Remove and replace any backslash escapement.\n\n        return text.replace(/\\\\(?:u(.{4})|([^u]))/g, function (a, b, c) {\n            return b ? String.fromCharCode(parseInt(b, 16)) : escapes[c];\n        });\n    }\n\n    return function (source, reviver) {\n\n// A regular expression is used to extract tokens from the JSON text.\n// The extraction process is cautious.\n\n        var r,          // The result of the exec method.\n            tx = /^[\\x20\\t\\n\\r]*(?:([,:\\[\\]{}]|true|false|null)|(-?\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d+)?)|\"((?:[^\\r\\n\\t\\\\\\\"]|\\\\(?:[\"\\\\\\/trnfb]|u[0-9a-fA-F]{4}))*)\")/;\n\n// Set the starting state.\n\n        state = 'go';\n\n// The stack records the container, key, and state for each object or array\n// that contains another object or array while processing nested structures.\n\n        stack = [];\n\n// If any error occurs, we will catch it and ultimately throw a syntax error.\n\n        try {\n\n// For each token...\n\n            for (;;) {\n                r = tx.exec(source);\n                if (!r) {\n                    break;\n                }\n\n// r is the result array from matching the tokenizing regular expression.\n//  r[0] contains everything that matched, including any initial whitespace.\n//  r[1] contains any punctuation that was matched, or true, false, or null.\n//  r[2] contains a matched number, still in string form.\n//  r[3] contains a matched string, without quotes but with escapement.\n\n                if (r[1]) {\n\n// Token: Execute the action for this state and token.\n\n                    action[r[1]][state]();\n\n                } else if (r[2]) {\n\n// Number token: Convert the number string into a number value and execute\n// the action for this state and number.\n\n                    value = +r[2];\n                    number[state]();\n                } else {\n\n// String token: Replace the escapement sequences and execute the action for\n// this state and string.\n\n                    value = debackslashify(r[3]);\n                    string[state]();\n                }\n\n// Remove the token from the string. The loop will continue as long as there\n// are tokens. This is a slow process, but it allows the use of ^ matching,\n// which assures that no illegal tokens slip through.\n\n                source = source.slice(r[0].length);\n            }\n\n// If we find a state/token combination that is illegal, then the action will\n// cause an error. We handle the error by simply changing the state.\n\n        } catch (e) {\n            state = e;\n        }\n\n// The parsing is finished. If we are not in the final 'ok' state, or if the\n// remaining source contains anything except whitespace, then we did not have\n//a well-formed JSON text.\n\n        if (state !== 'ok' || /[^\\x20\\t\\n\\r]/.test(source)) {\n            throw state instanceof SyntaxError ? state : new SyntaxError('JSON');\n        }\n\n// If there is a reviver function, we recursively walk the new structure,\n// passing each name/value pair to the reviver function for possible\n// transformation, starting with a temporary root object that holds the current\n// value in an empty key. If there is not a reviver function, we simply return\n// that value.\n\n        return typeof reviver === 'function' ? (function walk(holder, key) {\n            var k, v, value = holder[key];\n            if (value && typeof value === 'object') {\n                for (k in value) {\n                    if (Object.prototype.hasOwnProperty.call(value, k)) {\n                        v = walk(value, k);\n                        if (v !== undefined) {\n                            value[k] = v;\n                        } else {\n                            delete value[k];\n                        }\n                    }\n                }\n            }\n            return reviver.call(holder, key, value);\n        }({'': value}, '')) : value;\n    };\n}());\n"
  },
  {
    "path": "test/static/third-party/mocha/css/mocha.css",
    "content": "@charset \"utf-8\";\n\nbody {\n  margin:0;\n}\n\n#mocha {\n  font: 20px/1.5 \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n  margin: 60px 50px;\n}\n\n#mocha ul, #mocha li {\n  margin: 0;\n  padding: 0;\n}\n\n#mocha ul {\n  list-style: none;\n}\n\n#mocha h1, #mocha h2 {\n  margin: 0;\n}\n\n#mocha h1 {\n  margin-top: 15px;\n  font-size: 1em;\n  font-weight: 200;\n}\n\n#mocha h1 a {\n  text-decoration: none;\n  color: inherit;\n}\n\n#mocha h1 a:hover {\n  text-decoration: underline;\n}\n\n#mocha .suite .suite h1 {\n  margin-top: 0;\n  font-size: .8em;\n}\n\n#mocha .hidden {\n  display: none;\n}\n\n#mocha h2 {\n  font-size: 12px;\n  font-weight: normal;\n  cursor: pointer;\n}\n\n#mocha .suite {\n  margin-left: 15px;\n}\n\n#mocha .test {\n  margin-left: 15px;\n  overflow: hidden;\n}\n\n#mocha .test.pending:hover h2::after {\n  content: '(pending)';\n  font-family: arial, sans-serif;\n}\n\n#mocha .test.pass.medium .duration {\n  background: #C09853;\n}\n\n#mocha .test.pass.slow .duration {\n  background: #B94A48;\n}\n\n#mocha .test.pass::before {\n  content: '✓';\n  font-size: 12px;\n  display: block;\n  float: left;\n  margin-right: 5px;\n  color: #00d6b2;\n}\n\n#mocha .test.pass .duration {\n  font-size: 9px;\n  margin-left: 5px;\n  padding: 2px 5px;\n  color: white;\n  -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.2);\n  -moz-box-shadow: inset 0 1px 1px rgba(0,0,0,.2);\n  box-shadow: inset 0 1px 1px rgba(0,0,0,.2);\n  -webkit-border-radius: 5px;\n  -moz-border-radius: 5px;\n  -ms-border-radius: 5px;\n  -o-border-radius: 5px;\n  border-radius: 5px;\n}\n\n#mocha .test.pass.fast .duration {\n  display: none;\n}\n\n#mocha .test.pending {\n  color: #0b97c4;\n}\n\n#mocha .test.pending::before {\n  content: '◦';\n  color: #0b97c4;\n}\n\n#mocha .test.fail {\n  color: #c00;\n}\n\n#mocha .test.fail pre {\n  color: black;\n}\n\n#mocha .test.fail::before {\n  content: '✖';\n  font-size: 12px;\n  display: block;\n  float: left;\n  margin-right: 5px;\n  color: #c00;\n}\n\n#mocha .test pre.error {\n  color: #c00;\n  max-height: 300px;\n  overflow: auto;\n}\n\n#mocha .test pre {\n  display: block;\n  float: left;\n  clear: left;\n  font: 12px/1.5 monaco, monospace;\n  margin: 5px;\n  padding: 15px;\n  border: 1px solid #eee;\n  border-bottom-color: #ddd;\n  -webkit-border-radius: 3px;\n  -webkit-box-shadow: 0 1px 3px #eee;\n  -moz-border-radius: 3px;\n  -moz-box-shadow: 0 1px 3px #eee;\n}\n\n#mocha .test h2 {\n  position: relative;\n}\n\n#mocha .test a.replay {\n  position: absolute;\n  top: 3px;\n  right: 0;\n  text-decoration: none;\n  vertical-align: middle;\n  display: block;\n  width: 15px;\n  height: 15px;\n  line-height: 15px;\n  text-align: center;\n  background: #eee;\n  font-size: 15px;\n  -moz-border-radius: 15px;\n  border-radius: 15px;\n  -webkit-transition: opacity 200ms;\n  -moz-transition: opacity 200ms;\n  transition: opacity 200ms;\n  opacity: 0.3;\n  color: #888;\n}\n\n#mocha .test:hover a.replay {\n  opacity: 1;\n}\n\n#mocha-report.pass .test.fail {\n  display: none;\n}\n\n#mocha-report.fail .test.pass {\n  display: none;\n}\n\n#mocha-error {\n  color: #c00;\n  font-size: 1.5em;\n  font-weight: 100;\n  letter-spacing: 1px;\n}\n\n#mocha-stats {\n  position: fixed;\n  top: 15px;\n  right: 10px;\n  font-size: 12px;\n  margin: 0;\n  color: #888;\n}\n\n#mocha-stats .progress {\n  float: right;\n  padding-top: 0;\n}\n\n#mocha-stats em {\n  color: black;\n}\n\n#mocha-stats a {\n  text-decoration: none;\n  color: inherit;\n}\n\n#mocha-stats a:hover {\n  border-bottom: 1px solid #eee;\n}\n\n#mocha-stats li {\n  display: inline-block;\n  margin: 0 5px;\n  list-style: none;\n  padding-top: 11px;\n}\n\n#mocha-stats canvas {\n  width: 40px;\n  height: 40px;\n}\n\n#mocha code .comment { color: #ddd }\n#mocha code .init { color: #2F6FAD }\n#mocha code .string { color: #5890AD }\n#mocha code .keyword { color: #8A6343 }\n#mocha code .number { color: #2F6FAD }\n\n@media screen and (max-device-width: 480px) {\n  #mocha  {\n    margin: 60px 0px;\n  }\n\n  #mocha #stats {\n    position: absolute;\n  }\n}\n"
  },
  {
    "path": "test/static/third-party/mocha/js/mocha.js",
    "content": ";(function(){\n\n// CommonJS require()\n\nfunction require(p){\n    var path = require.resolve(p)\n      , mod = require.modules[path];\n    if (!mod) throw new Error('failed to require \"' + p + '\"');\n    if (!mod.exports) {\n      mod.exports = {};\n      mod.call(mod.exports, mod, mod.exports, require.relative(path));\n    }\n    return mod.exports;\n  }\n\nrequire.modules = {};\n\nrequire.resolve = function (path){\n    var orig = path\n      , reg = path + '.js'\n      , index = path + '/index.js';\n    return require.modules[reg] && reg\n      || require.modules[index] && index\n      || orig;\n  };\n\nrequire.register = function (path, fn){\n    require.modules[path] = fn;\n  };\n\nrequire.relative = function (parent) {\n    return function(p){\n      if ('.' != p.charAt(0)) return require(p);\n\n      var path = parent.split('/')\n        , segs = p.split('/');\n      path.pop();\n\n      for (var i = 0; i < segs.length; i++) {\n        var seg = segs[i];\n        if ('..' == seg) path.pop();\n        else if ('.' != seg) path.push(seg);\n      }\n\n      return require(path.join('/'));\n    };\n  };\n\n\nrequire.register(\"browser/debug.js\", function(module, exports, require){\n\nmodule.exports = function(type){\n  return function(){\n  }\n};\n\n}); // module: browser/debug.js\n\nrequire.register(\"browser/diff.js\", function(module, exports, require){\n/* See license.txt for terms of usage */\n\n/*\n * Text diff implementation.\n * \n * This library supports the following APIS:\n * JsDiff.diffChars: Character by character diff\n * JsDiff.diffWords: Word (as defined by \\b regex) diff which ignores whitespace\n * JsDiff.diffLines: Line based diff\n * \n * JsDiff.diffCss: Diff targeted at CSS content\n * \n * These methods are based on the implementation proposed in\n * \"An O(ND) Difference Algorithm and its Variations\" (Myers, 1986).\n * http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.4.6927\n */\nvar JsDiff = (function() {\n  function clonePath(path) {\n    return { newPos: path.newPos, components: path.components.slice(0) };\n  }\n  function removeEmpty(array) {\n    var ret = [];\n    for (var i = 0; i < array.length; i++) {\n      if (array[i]) {\n        ret.push(array[i]);\n      }\n    }\n    return ret;\n  }\n  function escapeHTML(s) {\n    var n = s;\n    n = n.replace(/&/g, \"&amp;\");\n    n = n.replace(/</g, \"&lt;\");\n    n = n.replace(/>/g, \"&gt;\");\n    n = n.replace(/\"/g, \"&quot;\");\n\n    return n;\n  }\n\n\n  var fbDiff = function(ignoreWhitespace) {\n    this.ignoreWhitespace = ignoreWhitespace;\n  };\n  fbDiff.prototype = {\n      diff: function(oldString, newString) {\n        // Handle the identity case (this is due to unrolling editLength == 0\n        if (newString == oldString) {\n          return [{ value: newString }];\n        }\n        if (!newString) {\n          return [{ value: oldString, removed: true }];\n        }\n        if (!oldString) {\n          return [{ value: newString, added: true }];\n        }\n\n        newString = this.tokenize(newString);\n        oldString = this.tokenize(oldString);\n\n        var newLen = newString.length, oldLen = oldString.length;\n        var maxEditLength = newLen + oldLen;\n        var bestPath = [{ newPos: -1, components: [] }];\n\n        // Seed editLength = 0\n        var oldPos = this.extractCommon(bestPath[0], newString, oldString, 0);\n        if (bestPath[0].newPos+1 >= newLen && oldPos+1 >= oldLen) {\n          return bestPath[0].components;\n        }\n\n        for (var editLength = 1; editLength <= maxEditLength; editLength++) {\n          for (var diagonalPath = -1*editLength; diagonalPath <= editLength; diagonalPath+=2) {\n            var basePath;\n            var addPath = bestPath[diagonalPath-1],\n                removePath = bestPath[diagonalPath+1];\n            oldPos = (removePath ? removePath.newPos : 0) - diagonalPath;\n            if (addPath) {\n              // No one else is going to attempt to use this value, clear it\n              bestPath[diagonalPath-1] = undefined;\n            }\n\n            var canAdd = addPath && addPath.newPos+1 < newLen;\n            var canRemove = removePath && 0 <= oldPos && oldPos < oldLen;\n            if (!canAdd && !canRemove) {\n              bestPath[diagonalPath] = undefined;\n              continue;\n            }\n\n            // Select the diagonal that we want to branch from. We select the prior\n            // path whose position in the new string is the farthest from the origin\n            // and does not pass the bounds of the diff graph\n            if (!canAdd || (canRemove && addPath.newPos < removePath.newPos)) {\n              basePath = clonePath(removePath);\n              this.pushComponent(basePath.components, oldString[oldPos], undefined, true);\n            } else {\n              basePath = clonePath(addPath);\n              basePath.newPos++;\n              this.pushComponent(basePath.components, newString[basePath.newPos], true, undefined);\n            }\n\n            var oldPos = this.extractCommon(basePath, newString, oldString, diagonalPath);\n\n            if (basePath.newPos+1 >= newLen && oldPos+1 >= oldLen) {\n              return basePath.components;\n            } else {\n              bestPath[diagonalPath] = basePath;\n            }\n          }\n        }\n      },\n\n      pushComponent: function(components, value, added, removed) {\n        var last = components[components.length-1];\n        if (last && last.added === added && last.removed === removed) {\n          // We need to clone here as the component clone operation is just\n          // as shallow array clone\n          components[components.length-1] =\n            {value: this.join(last.value, value), added: added, removed: removed };\n        } else {\n          components.push({value: value, added: added, removed: removed });\n        }\n      },\n      extractCommon: function(basePath, newString, oldString, diagonalPath) {\n        var newLen = newString.length,\n            oldLen = oldString.length,\n            newPos = basePath.newPos,\n            oldPos = newPos - diagonalPath;\n        while (newPos+1 < newLen && oldPos+1 < oldLen && this.equals(newString[newPos+1], oldString[oldPos+1])) {\n          newPos++;\n          oldPos++;\n          \n          this.pushComponent(basePath.components, newString[newPos], undefined, undefined);\n        }\n        basePath.newPos = newPos;\n        return oldPos;\n      },\n\n      equals: function(left, right) {\n        var reWhitespace = /\\S/;\n        if (this.ignoreWhitespace && !reWhitespace.test(left) && !reWhitespace.test(right)) {\n          return true;\n        } else {\n          return left == right;\n        }\n      },\n      join: function(left, right) {\n        return left + right;\n      },\n      tokenize: function(value) {\n        return value;\n      }\n  };\n  \n  var CharDiff = new fbDiff();\n  \n  var WordDiff = new fbDiff(true);\n  WordDiff.tokenize = function(value) {\n    return removeEmpty(value.split(/(\\s+|\\b)/));\n  };\n  \n  var CssDiff = new fbDiff(true);\n  CssDiff.tokenize = function(value) {\n    return removeEmpty(value.split(/([{}:;,]|\\s+)/));\n  };\n  \n  var LineDiff = new fbDiff();\n  LineDiff.tokenize = function(value) {\n    return value.split(/^/m);\n  };\n  \n  return {\n    diffChars: function(oldStr, newStr) { return CharDiff.diff(oldStr, newStr); },\n    diffWords: function(oldStr, newStr) { return WordDiff.diff(oldStr, newStr); },\n    diffLines: function(oldStr, newStr) { return LineDiff.diff(oldStr, newStr); },\n\n    diffCss: function(oldStr, newStr) { return CssDiff.diff(oldStr, newStr); },\n\n    createPatch: function(fileName, oldStr, newStr, oldHeader, newHeader) {\n      var ret = [];\n\n      ret.push(\"Index: \" + fileName);\n      ret.push(\"===================================================================\");\n      ret.push(\"--- \" + fileName + (typeof oldHeader === \"undefined\" ? \"\" : \"\\t\" + oldHeader));\n      ret.push(\"+++ \" + fileName + (typeof newHeader === \"undefined\" ? \"\" : \"\\t\" + newHeader));\n\n      var diff = LineDiff.diff(oldStr, newStr);\n      if (!diff[diff.length-1].value) {\n        diff.pop();   // Remove trailing newline add\n      }\n      diff.push({value: \"\", lines: []});   // Append an empty value to make cleanup easier\n\n      function contextLines(lines) {\n        return lines.map(function(entry) { return ' ' + entry; });\n      }\n      function eofNL(curRange, i, current) {\n        var last = diff[diff.length-2],\n            isLast = i === diff.length-2,\n            isLastOfType = i === diff.length-3 && (current.added === !last.added || current.removed === !last.removed);\n\n        // Figure out if this is the last line for the given file and missing NL\n        if (!/\\n$/.test(current.value) && (isLast || isLastOfType)) {\n          curRange.push('\\\\ No newline at end of file');\n        }\n      }\n\n      var oldRangeStart = 0, newRangeStart = 0, curRange = [],\n          oldLine = 1, newLine = 1;\n      for (var i = 0; i < diff.length; i++) {\n        var current = diff[i],\n            lines = current.lines || current.value.replace(/\\n$/, \"\").split(\"\\n\");\n        current.lines = lines;\n\n        if (current.added || current.removed) {\n          if (!oldRangeStart) {\n            var prev = diff[i-1];\n            oldRangeStart = oldLine;\n            newRangeStart = newLine;\n            \n            if (prev) {\n              curRange = contextLines(prev.lines.slice(-4));\n              oldRangeStart -= curRange.length;\n              newRangeStart -= curRange.length;\n            }\n          }\n          curRange.push.apply(curRange, lines.map(function(entry) { return (current.added?\"+\":\"-\") + entry; }));\n          eofNL(curRange, i, current);\n\n          if (current.added) {\n            newLine += lines.length;\n          } else {\n            oldLine += lines.length;\n          }\n        } else {\n          if (oldRangeStart) {\n            // Close out any changes that have been output (or join overlapping)\n            if (lines.length <= 8 && i < diff.length-2) {\n              // Overlapping\n              curRange.push.apply(curRange, contextLines(lines));\n            } else {\n              // end the range and output\n              var contextSize = Math.min(lines.length, 4);\n              ret.push(\n                  \"@@ -\" + oldRangeStart + \",\" + (oldLine-oldRangeStart+contextSize)\n                  + \" +\" + newRangeStart + \",\" + (newLine-newRangeStart+contextSize)\n                  + \" @@\");\n              ret.push.apply(ret, curRange);\n              ret.push.apply(ret, contextLines(lines.slice(0, contextSize)));\n              if (lines.length <= 4) {\n                eofNL(ret, i, current);\n              }\n\n              oldRangeStart = 0;  newRangeStart = 0; curRange = [];\n            }\n          }\n          oldLine += lines.length;\n          newLine += lines.length;\n        }\n      }\n\n      return ret.join('\\n') + '\\n';\n    },\n\n    convertChangesToXML: function(changes){\n      var ret = [];\n      for ( var i = 0; i < changes.length; i++) {\n        var change = changes[i];\n        if (change.added) {\n          ret.push(\"<ins>\");\n        } else if (change.removed) {\n          ret.push(\"<del>\");\n        }\n\n        ret.push(escapeHTML(change.value));\n\n        if (change.added) {\n          ret.push(\"</ins>\");\n        } else if (change.removed) {\n          ret.push(\"</del>\");\n        }\n      }\n      return ret.join(\"\");\n    }\n  };\n})();\n\nif (typeof module !== \"undefined\") {\n    module.exports = JsDiff;\n}\n\n}); // module: browser/diff.js\n\nrequire.register(\"browser/events.js\", function(module, exports, require){\n\n/**\n * Module exports.\n */\n\nexports.EventEmitter = EventEmitter;\n\n/**\n * Check if `obj` is an array.\n */\n\nfunction isArray(obj) {\n  return '[object Array]' == {}.toString.call(obj);\n}\n\n/**\n * Event emitter constructor.\n *\n * @api public\n */\n\nfunction EventEmitter(){};\n\n/**\n * Adds a listener.\n *\n * @api public\n */\n\nEventEmitter.prototype.on = function (name, fn) {\n  if (!this.$events) {\n    this.$events = {};\n  }\n\n  if (!this.$events[name]) {\n    this.$events[name] = fn;\n  } else if (isArray(this.$events[name])) {\n    this.$events[name].push(fn);\n  } else {\n    this.$events[name] = [this.$events[name], fn];\n  }\n\n  return this;\n};\n\nEventEmitter.prototype.addListener = EventEmitter.prototype.on;\n\n/**\n * Adds a volatile listener.\n *\n * @api public\n */\n\nEventEmitter.prototype.once = function (name, fn) {\n  var self = this;\n\n  function on () {\n    self.removeListener(name, on);\n    fn.apply(this, arguments);\n  };\n\n  on.listener = fn;\n  this.on(name, on);\n\n  return this;\n};\n\n/**\n * Removes a listener.\n *\n * @api public\n */\n\nEventEmitter.prototype.removeListener = function (name, fn) {\n  if (this.$events && this.$events[name]) {\n    var list = this.$events[name];\n\n    if (isArray(list)) {\n      var pos = -1;\n\n      for (var i = 0, l = list.length; i < l; i++) {\n        if (list[i] === fn || (list[i].listener && list[i].listener === fn)) {\n          pos = i;\n          break;\n        }\n      }\n\n      if (pos < 0) {\n        return this;\n      }\n\n      list.splice(pos, 1);\n\n      if (!list.length) {\n        delete this.$events[name];\n      }\n    } else if (list === fn || (list.listener && list.listener === fn)) {\n      delete this.$events[name];\n    }\n  }\n\n  return this;\n};\n\n/**\n * Removes all listeners for an event.\n *\n * @api public\n */\n\nEventEmitter.prototype.removeAllListeners = function (name) {\n  if (name === undefined) {\n    this.$events = {};\n    return this;\n  }\n\n  if (this.$events && this.$events[name]) {\n    this.$events[name] = null;\n  }\n\n  return this;\n};\n\n/**\n * Gets all listeners for a certain event.\n *\n * @api public\n */\n\nEventEmitter.prototype.listeners = function (name) {\n  if (!this.$events) {\n    this.$events = {};\n  }\n\n  if (!this.$events[name]) {\n    this.$events[name] = [];\n  }\n\n  if (!isArray(this.$events[name])) {\n    this.$events[name] = [this.$events[name]];\n  }\n\n  return this.$events[name];\n};\n\n/**\n * Emits an event.\n *\n * @api public\n */\n\nEventEmitter.prototype.emit = function (name) {\n  if (!this.$events) {\n    return false;\n  }\n\n  var handler = this.$events[name];\n\n  if (!handler) {\n    return false;\n  }\n\n  var args = [].slice.call(arguments, 1);\n\n  if ('function' == typeof handler) {\n    handler.apply(this, args);\n  } else if (isArray(handler)) {\n    var listeners = handler.slice();\n\n    for (var i = 0, l = listeners.length; i < l; i++) {\n      listeners[i].apply(this, args);\n    }\n  } else {\n    return false;\n  }\n\n  return true;\n};\n}); // module: browser/events.js\n\nrequire.register(\"browser/fs.js\", function(module, exports, require){\n\n}); // module: browser/fs.js\n\nrequire.register(\"browser/path.js\", function(module, exports, require){\n\n}); // module: browser/path.js\n\nrequire.register(\"browser/progress.js\", function(module, exports, require){\n\n/**\n * Expose `Progress`.\n */\n\nmodule.exports = Progress;\n\n/**\n * Initialize a new `Progress` indicator.\n */\n\nfunction Progress() {\n  this.percent = 0;\n  this.size(0);\n  this.fontSize(11);\n  this.font('helvetica, arial, sans-serif');\n}\n\n/**\n * Set progress size to `n`.\n *\n * @param {Number} n\n * @return {Progress} for chaining\n * @api public\n */\n\nProgress.prototype.size = function(n){\n  this._size = n;\n  return this;\n};\n\n/**\n * Set text to `str`.\n *\n * @param {String} str\n * @return {Progress} for chaining\n * @api public\n */\n\nProgress.prototype.text = function(str){\n  this._text = str;\n  return this;\n};\n\n/**\n * Set font size to `n`.\n *\n * @param {Number} n\n * @return {Progress} for chaining\n * @api public\n */\n\nProgress.prototype.fontSize = function(n){\n  this._fontSize = n;\n  return this;\n};\n\n/**\n * Set font `family`.\n *\n * @param {String} family\n * @return {Progress} for chaining\n */\n\nProgress.prototype.font = function(family){\n  this._font = family;\n  return this;\n};\n\n/**\n * Update percentage to `n`.\n *\n * @param {Number} n\n * @return {Progress} for chaining\n */\n\nProgress.prototype.update = function(n){\n  this.percent = n;\n  return this;\n};\n\n/**\n * Draw on `ctx`.\n *\n * @param {CanvasRenderingContext2d} ctx\n * @return {Progress} for chaining\n */\n\nProgress.prototype.draw = function(ctx){\n  var percent = Math.min(this.percent, 100)\n    , size = this._size\n    , half = size / 2\n    , x = half\n    , y = half\n    , rad = half - 1\n    , fontSize = this._fontSize;\n\n  ctx.font = fontSize + 'px ' + this._font;\n\n  var angle = Math.PI * 2 * (percent / 100);\n  ctx.clearRect(0, 0, size, size);\n\n  // outer circle\n  ctx.strokeStyle = '#9f9f9f';\n  ctx.beginPath();\n  ctx.arc(x, y, rad, 0, angle, false);\n  ctx.stroke();\n\n  // inner circle\n  ctx.strokeStyle = '#eee';\n  ctx.beginPath();\n  ctx.arc(x, y, rad - 1, 0, angle, true);\n  ctx.stroke();\n\n  // text\n  var text = this._text || (percent | 0) + '%'\n    , w = ctx.measureText(text).width;\n\n  ctx.fillText(\n      text\n    , x - w / 2 + 1\n    , y + fontSize / 2 - 1);\n\n  return this;\n};\n\n}); // module: browser/progress.js\n\nrequire.register(\"browser/tty.js\", function(module, exports, require){\n\nexports.isatty = function(){\n  return true;\n};\n\nexports.getWindowSize = function(){\n  return [window.innerHeight, window.innerWidth];\n};\n}); // module: browser/tty.js\n\nrequire.register(\"context.js\", function(module, exports, require){\n\n/**\n * Expose `Context`.\n */\n\nmodule.exports = Context;\n\n/**\n * Initialize a new `Context`.\n *\n * @api private\n */\n\nfunction Context(){}\n\n/**\n * Set or get the context `Runnable` to `runnable`.\n *\n * @param {Runnable} runnable\n * @return {Context}\n * @api private\n */\n\nContext.prototype.runnable = function(runnable){\n  if (0 == arguments.length) return this._runnable;\n  this.test = this._runnable = runnable;\n  return this;\n};\n\n/**\n * Set test timeout `ms`.\n *\n * @param {Number} ms\n * @return {Context} self\n * @api private\n */\n\nContext.prototype.timeout = function(ms){\n  this.runnable().timeout(ms);\n  return this;\n};\n\n/**\n * Set test slowness threshold `ms`.\n *\n * @param {Number} ms\n * @return {Context} self\n * @api private\n */\n\nContext.prototype.slow = function(ms){\n  this.runnable().slow(ms);\n  return this;\n};\n\n/**\n * Inspect the context void of `._runnable`.\n *\n * @return {String}\n * @api private\n */\n\nContext.prototype.inspect = function(){\n  return JSON.stringify(this, function(key, val){\n    if ('_runnable' == key) return;\n    if ('test' == key) return;\n    return val;\n  }, 2);\n};\n\n}); // module: context.js\n\nrequire.register(\"hook.js\", function(module, exports, require){\n\n/**\n * Module dependencies.\n */\n\nvar Runnable = require('./runnable');\n\n/**\n * Expose `Hook`.\n */\n\nmodule.exports = Hook;\n\n/**\n * Initialize a new `Hook` with the given `title` and callback `fn`.\n *\n * @param {String} title\n * @param {Function} fn\n * @api private\n */\n\nfunction Hook(title, fn) {\n  Runnable.call(this, title, fn);\n  this.type = 'hook';\n}\n\n/**\n * Inherit from `Runnable.prototype`.\n */\n\nfunction F(){};\nF.prototype = Runnable.prototype;\nHook.prototype = new F;\nHook.prototype.constructor = Hook;\n\n\n/**\n * Get or set the test `err`.\n *\n * @param {Error} err\n * @return {Error}\n * @api public\n */\n\nHook.prototype.error = function(err){\n  if (0 == arguments.length) {\n    var err = this._error;\n    this._error = null;\n    return err;\n  }\n\n  this._error = err;\n};\n\n}); // module: hook.js\n\nrequire.register(\"interfaces/bdd.js\", function(module, exports, require){\n\n/**\n * Module dependencies.\n */\n\nvar Suite = require('../suite')\n  , Test = require('../test');\n\n/**\n * BDD-style interface:\n *\n *      describe('Array', function(){\n *        describe('#indexOf()', function(){\n *          it('should return -1 when not present', function(){\n *\n *          });\n *\n *          it('should return the index when present', function(){\n *\n *          });\n *        });\n *      });\n *\n */\n\nmodule.exports = function(suite){\n  var suites = [suite];\n\n  suite.on('pre-require', function(context, file, mocha){\n\n    /**\n     * Execute before running tests.\n     */\n\n    context.before = function(fn){\n      suites[0].beforeAll(fn);\n    };\n\n    /**\n     * Execute after running tests.\n     */\n\n    context.after = function(fn){\n      suites[0].afterAll(fn);\n    };\n\n    /**\n     * Execute before each test case.\n     */\n\n    context.beforeEach = function(fn){\n      suites[0].beforeEach(fn);\n    };\n\n    /**\n     * Execute after each test case.\n     */\n\n    context.afterEach = function(fn){\n      suites[0].afterEach(fn);\n    };\n\n    /**\n     * Describe a \"suite\" with the given `title`\n     * and callback `fn` containing nested suites\n     * and/or tests.\n     */\n\n    context.describe = context.context = function(title, fn){\n      var suite = Suite.create(suites[0], title);\n      suites.unshift(suite);\n      fn.call(suite);\n      suites.shift();\n      return suite;\n    };\n\n    /**\n     * Pending describe.\n     */\n\n    context.xdescribe =\n    context.xcontext =\n    context.describe.skip = function(title, fn){\n      var suite = Suite.create(suites[0], title);\n      suite.pending = true;\n      suites.unshift(suite);\n      fn.call(suite);\n      suites.shift();\n    };\n\n    /**\n     * Exclusive suite.\n     */\n\n    context.describe.only = function(title, fn){\n      var suite = context.describe(title, fn);\n      mocha.grep(suite.fullTitle());\n    };\n\n    /**\n     * Describe a specification or test-case\n     * with the given `title` and callback `fn`\n     * acting as a thunk.\n     */\n\n    context.it = context.specify = function(title, fn){\n      var suite = suites[0];\n      if (suite.pending) var fn = null;\n      var test = new Test(title, fn);\n      suite.addTest(test);\n      return test;\n    };\n\n    /**\n     * Exclusive test-case.\n     */\n\n    context.it.only = function(title, fn){\n      var test = context.it(title, fn);\n      mocha.grep(test.fullTitle());\n    };\n\n    /**\n     * Pending test case.\n     */\n\n    context.xit =\n    context.xspecify =\n    context.it.skip = function(title){\n      context.it(title);\n    };\n  });\n};\n\n}); // module: interfaces/bdd.js\n\nrequire.register(\"interfaces/exports.js\", function(module, exports, require){\n\n/**\n * Module dependencies.\n */\n\nvar Suite = require('../suite')\n  , Test = require('../test');\n\n/**\n * TDD-style interface:\n *\n *     exports.Array = {\n *       '#indexOf()': {\n *         'should return -1 when the value is not present': function(){\n *\n *         },\n *\n *         'should return the correct index when the value is present': function(){\n *\n *         }\n *       }\n *     };\n *\n */\n\nmodule.exports = function(suite){\n  var suites = [suite];\n\n  suite.on('require', visit);\n\n  function visit(obj) {\n    var suite;\n    for (var key in obj) {\n      if ('function' == typeof obj[key]) {\n        var fn = obj[key];\n        switch (key) {\n          case 'before':\n            suites[0].beforeAll(fn);\n            break;\n          case 'after':\n            suites[0].afterAll(fn);\n            break;\n          case 'beforeEach':\n            suites[0].beforeEach(fn);\n            break;\n          case 'afterEach':\n            suites[0].afterEach(fn);\n            break;\n          default:\n            suites[0].addTest(new Test(key, fn));\n        }\n      } else {\n        var suite = Suite.create(suites[0], key);\n        suites.unshift(suite);\n        visit(obj[key]);\n        suites.shift();\n      }\n    }\n  }\n};\n\n}); // module: interfaces/exports.js\n\nrequire.register(\"interfaces/index.js\", function(module, exports, require){\n\nexports.bdd = require('./bdd');\nexports.tdd = require('./tdd');\nexports.qunit = require('./qunit');\nexports.exports = require('./exports');\n\n}); // module: interfaces/index.js\n\nrequire.register(\"interfaces/qunit.js\", function(module, exports, require){\n\n/**\n * Module dependencies.\n */\n\nvar Suite = require('../suite')\n  , Test = require('../test');\n\n/**\n * QUnit-style interface:\n *\n *     suite('Array');\n *\n *     test('#length', function(){\n *       var arr = [1,2,3];\n *       ok(arr.length == 3);\n *     });\n *\n *     test('#indexOf()', function(){\n *       var arr = [1,2,3];\n *       ok(arr.indexOf(1) == 0);\n *       ok(arr.indexOf(2) == 1);\n *       ok(arr.indexOf(3) == 2);\n *     });\n *\n *     suite('String');\n *\n *     test('#length', function(){\n *       ok('foo'.length == 3);\n *     });\n *\n */\n\nmodule.exports = function(suite){\n  var suites = [suite];\n\n  suite.on('pre-require', function(context, file, mocha){\n\n    /**\n     * Execute before running tests.\n     */\n\n    context.before = function(fn){\n      suites[0].beforeAll(fn);\n    };\n\n    /**\n     * Execute after running tests.\n     */\n\n    context.after = function(fn){\n      suites[0].afterAll(fn);\n    };\n\n    /**\n     * Execute before each test case.\n     */\n\n    context.beforeEach = function(fn){\n      suites[0].beforeEach(fn);\n    };\n\n    /**\n     * Execute after each test case.\n     */\n\n    context.afterEach = function(fn){\n      suites[0].afterEach(fn);\n    };\n\n    /**\n     * Describe a \"suite\" with the given `title`.\n     */\n\n    context.suite = function(title){\n      if (suites.length > 1) suites.shift();\n      var suite = Suite.create(suites[0], title);\n      suites.unshift(suite);\n      return suite;\n    };\n\n    /**\n     * Exclusive test-case.\n     */\n\n    context.suite.only = function(title, fn){\n      var suite = context.suite(title, fn);\n      mocha.grep(suite.fullTitle());\n    };\n\n    /**\n     * Describe a specification or test-case\n     * with the given `title` and callback `fn`\n     * acting as a thunk.\n     */\n\n    context.test = function(title, fn){\n      var test = new Test(title, fn);\n      suites[0].addTest(test);\n      return test;\n    };\n\n    /**\n     * Exclusive test-case.\n     */\n\n    context.test.only = function(title, fn){\n      var test = context.test(title, fn);\n      mocha.grep(test.fullTitle());\n    };\n\n    /**\n     * Pending test case.\n     */\n\n    context.test.skip = function(title){\n      context.test(title);\n    };\n  });\n};\n\n}); // module: interfaces/qunit.js\n\nrequire.register(\"interfaces/tdd.js\", function(module, exports, require){\n\n/**\n * Module dependencies.\n */\n\nvar Suite = require('../suite')\n  , Test = require('../test');\n\n/**\n * TDD-style interface:\n *\n *      suite('Array', function(){\n *        suite('#indexOf()', function(){\n *          suiteSetup(function(){\n *\n *          });\n *\n *          test('should return -1 when not present', function(){\n *\n *          });\n *\n *          test('should return the index when present', function(){\n *\n *          });\n *\n *          suiteTeardown(function(){\n *\n *          });\n *        });\n *      });\n *\n */\n\nmodule.exports = function(suite){\n  var suites = [suite];\n\n  suite.on('pre-require', function(context, file, mocha){\n\n    /**\n     * Execute before each test case.\n     */\n\n    context.setup = function(fn){\n      suites[0].beforeEach(fn);\n    };\n\n    /**\n     * Execute after each test case.\n     */\n\n    context.teardown = function(fn){\n      suites[0].afterEach(fn);\n    };\n\n    /**\n     * Execute before the suite.\n     */\n\n    context.suiteSetup = function(fn){\n      suites[0].beforeAll(fn);\n    };\n\n    /**\n     * Execute after the suite.\n     */\n\n    context.suiteTeardown = function(fn){\n      suites[0].afterAll(fn);\n    };\n\n    /**\n     * Describe a \"suite\" with the given `title`\n     * and callback `fn` containing nested suites\n     * and/or tests.\n     */\n\n    context.suite = function(title, fn){\n      var suite = Suite.create(suites[0], title);\n      suites.unshift(suite);\n      fn.call(suite);\n      suites.shift();\n      return suite;\n    };\n\n    /**\n     * Exclusive test-case.\n     */\n\n    context.suite.only = function(title, fn){\n      var suite = context.suite(title, fn);\n      mocha.grep(suite.fullTitle());\n    };\n\n    /**\n     * Describe a specification or test-case\n     * with the given `title` and callback `fn`\n     * acting as a thunk.\n     */\n\n    context.test = function(title, fn){\n      var test = new Test(title, fn);\n      suites[0].addTest(test);\n      return test;\n    };\n\n    /**\n     * Exclusive test-case.\n     */\n\n    context.test.only = function(title, fn){\n      var test = context.test(title, fn);\n      mocha.grep(test.fullTitle());\n    };\n\n    /**\n     * Pending test case.\n     */\n\n    context.test.skip = function(title){\n      context.test(title);\n    };\n  });\n};\n\n}); // module: interfaces/tdd.js\n\nrequire.register(\"mocha.js\", function(module, exports, require){\n/*!\n * mocha\n * Copyright(c) 2011 TJ Holowaychuk <tj@vision-media.ca>\n * MIT Licensed\n */\n\n/**\n * Module dependencies.\n */\n\nvar path = require('browser/path')\n  , utils = require('./utils');\n\n/**\n * Expose `Mocha`.\n */\n\nexports = module.exports = Mocha;\n\n/**\n * Expose internals.\n */\n\nexports.utils = utils;\nexports.interfaces = require('./interfaces');\nexports.reporters = require('./reporters');\nexports.Runnable = require('./runnable');\nexports.Context = require('./context');\nexports.Runner = require('./runner');\nexports.Suite = require('./suite');\nexports.Hook = require('./hook');\nexports.Test = require('./test');\n\n/**\n * Return image `name` path.\n *\n * @param {String} name\n * @return {String}\n * @api private\n */\n\nfunction image(name) {\n  return __dirname + '/../images/' + name + '.png';\n}\n\n/**\n * Setup mocha with `options`.\n *\n * Options:\n *\n *   - `ui` name \"bdd\", \"tdd\", \"exports\" etc\n *   - `reporter` reporter instance, defaults to `mocha.reporters.Dot`\n *   - `globals` array of accepted globals\n *   - `timeout` timeout in milliseconds\n *   - `bail` bail on the first test failure\n *   - `slow` milliseconds to wait before considering a test slow\n *   - `ignoreLeaks` ignore global leaks\n *   - `grep` string or regexp to filter tests with\n *\n * @param {Object} options\n * @api public\n */\n\nfunction Mocha(options) {\n  options = options || {};\n  this.files = [];\n  this.options = options;\n  this.grep(options.grep);\n  this.suite = new exports.Suite('', new exports.Context);\n  this.ui(options.ui);\n  this.bail(options.bail);\n  this.reporter(options.reporter);\n  if (options.timeout) this.timeout(options.timeout);\n  if (options.slow) this.slow(options.slow);\n}\n\n/**\n * Enable or disable bailing on the first failure.\n *\n * @param {Boolean} [bail]\n * @api public\n */\n\nMocha.prototype.bail = function(bail){\n  if (0 == arguments.length) bail = true;\n  this.suite.bail(bail);\n  return this;\n};\n\n/**\n * Add test `file`.\n *\n * @param {String} file\n * @api public\n */\n\nMocha.prototype.addFile = function(file){\n  this.files.push(file);\n  return this;\n};\n\n/**\n * Set reporter to `reporter`, defaults to \"dot\".\n *\n * @param {String|Function} reporter name or constructor\n * @api public\n */\n\nMocha.prototype.reporter = function(reporter){\n  if ('function' == typeof reporter) {\n    this._reporter = reporter;\n  } else {\n    reporter = reporter || 'dot';\n    try {\n      this._reporter = require('./reporters/' + reporter);\n    } catch (err) {\n      this._reporter = require(reporter);\n    }\n    if (!this._reporter) throw new Error('invalid reporter \"' + reporter + '\"');\n  }\n  return this;\n};\n\n/**\n * Set test UI `name`, defaults to \"bdd\".\n *\n * @param {String} bdd\n * @api public\n */\n\nMocha.prototype.ui = function(name){\n  name = name || 'bdd';\n  this._ui = exports.interfaces[name];\n  if (!this._ui) throw new Error('invalid interface \"' + name + '\"');\n  this._ui = this._ui(this.suite);\n  return this;\n};\n\n/**\n * Load registered files.\n *\n * @api private\n */\n\nMocha.prototype.loadFiles = function(fn){\n  var self = this;\n  var suite = this.suite;\n  var pending = this.files.length;\n  this.files.forEach(function(file){\n    file = path.resolve(file);\n    suite.emit('pre-require', global, file, self);\n    suite.emit('require', require(file), file, self);\n    suite.emit('post-require', global, file, self);\n    --pending || (fn && fn());\n  });\n};\n\n/**\n * Enable growl support.\n *\n * @api private\n */\n\nMocha.prototype._growl = function(runner, reporter) {\n  var notify = require('growl');\n\n  runner.on('end', function(){\n    var stats = reporter.stats;\n    if (stats.failures) {\n      var msg = stats.failures + ' of ' + runner.total + ' tests failed';\n      notify(msg, { name: 'mocha', title: 'Failed', image: image('error') });\n    } else {\n      notify(stats.passes + ' tests passed in ' + stats.duration + 'ms', {\n          name: 'mocha'\n        , title: 'Passed'\n        , image: image('ok')\n      });\n    }\n  });\n};\n\n/**\n * Add regexp to grep, if `re` is a string it is escaped.\n *\n * @param {RegExp|String} re\n * @return {Mocha}\n * @api public\n */\n\nMocha.prototype.grep = function(re){\n  this.options.grep = 'string' == typeof re\n    ? new RegExp(utils.escapeRegexp(re))\n    : re;\n  return this;\n};\n\n/**\n * Invert `.grep()` matches.\n *\n * @return {Mocha}\n * @api public\n */\n\nMocha.prototype.invert = function(){\n  this.options.invert = true;\n  return this;\n};\n\n/**\n * Ignore global leaks.\n *\n * @return {Mocha}\n * @api public\n */\n\nMocha.prototype.ignoreLeaks = function(){\n  this.options.ignoreLeaks = true;\n  return this;\n};\n\n/**\n * Enable global leak checking.\n *\n * @return {Mocha}\n * @api public\n */\n\nMocha.prototype.checkLeaks = function(){\n  this.options.ignoreLeaks = false;\n  return this;\n};\n\n/**\n * Enable growl support.\n *\n * @return {Mocha}\n * @api public\n */\n\nMocha.prototype.growl = function(){\n  this.options.growl = true;\n  return this;\n};\n\n/**\n * Ignore `globals` array or string.\n *\n * @param {Array|String} globals\n * @return {Mocha}\n * @api public\n */\n\nMocha.prototype.globals = function(globals){\n  this.options.globals = (this.options.globals || []).concat(globals);\n  return this;\n};\n\n/**\n * Set the timeout in milliseconds.\n *\n * @param {Number} timeout\n * @return {Mocha}\n * @api public\n */\n\nMocha.prototype.timeout = function(timeout){\n  this.suite.timeout(timeout);\n  return this;\n};\n\n/**\n * Set slowness threshold in milliseconds.\n *\n * @param {Number} slow\n * @return {Mocha}\n * @api public\n */\n\nMocha.prototype.slow = function(slow){\n  this.suite.slow(slow);\n  return this;\n};\n\n/**\n * Makes all tests async (accepting a callback)\n *\n * @return {Mocha}\n * @api public\n */\n\nMocha.prototype.asyncOnly = function(){\n  this.options.asyncOnly = true;\n  return this;\n};\n\n/**\n * Run tests and invoke `fn()` when complete.\n *\n * @param {Function} fn\n * @return {Runner}\n * @api public\n */\n\nMocha.prototype.run = function(fn){\n  if (this.files.length) this.loadFiles();\n  var suite = this.suite;\n  var options = this.options;\n  var runner = new exports.Runner(suite);\n  var reporter = new this._reporter(runner);\n  runner.ignoreLeaks = false !== options.ignoreLeaks;\n  runner.asyncOnly = options.asyncOnly;\n  if (options.grep) runner.grep(options.grep, options.invert);\n  if (options.globals) runner.globals(options.globals);\n  if (options.growl) this._growl(runner, reporter);\n  return runner.run(fn);\n};\n\n}); // module: mocha.js\n\nrequire.register(\"ms.js\", function(module, exports, require){\n\n/**\n * Helpers.\n */\n\nvar s = 1000;\nvar m = s * 60;\nvar h = m * 60;\nvar d = h * 24;\n\n/**\n * Parse or format the given `val`.\n *\n * @param {String|Number} val\n * @return {String|Number}\n * @api public\n */\n\nmodule.exports = function(val){\n  if ('string' == typeof val) return parse(val);\n  return format(val);\n}\n\n/**\n * Parse the given `str` and return milliseconds.\n *\n * @param {String} str\n * @return {Number}\n * @api private\n */\n\nfunction parse(str) {\n  var m = /^((?:\\d+)?\\.?\\d+) *(ms|seconds?|s|minutes?|m|hours?|h|days?|d|years?|y)?$/i.exec(str);\n  if (!m) return;\n  var n = parseFloat(m[1]);\n  var type = (m[2] || 'ms').toLowerCase();\n  switch (type) {\n    case 'years':\n    case 'year':\n    case 'y':\n      return n * 31557600000;\n    case 'days':\n    case 'day':\n    case 'd':\n      return n * 86400000;\n    case 'hours':\n    case 'hour':\n    case 'h':\n      return n * 3600000;\n    case 'minutes':\n    case 'minute':\n    case 'm':\n      return n * 60000;\n    case 'seconds':\n    case 'second':\n    case 's':\n      return n * 1000;\n    case 'ms':\n      return n;\n  }\n}\n\n/**\n * Format the given `ms`.\n *\n * @param {Number} ms\n * @return {String}\n * @api public\n */\n\nfunction format(ms) {\n  if (ms == d) return Math.round(ms / d) + ' day';\n  if (ms > d) return Math.round(ms / d) + ' days';\n  if (ms == h) return Math.round(ms / h) + ' hour';\n  if (ms > h) return Math.round(ms / h) + ' hours';\n  if (ms == m) return Math.round(ms / m) + ' minute';\n  if (ms > m) return Math.round(ms / m) + ' minutes';\n  if (ms == s) return Math.round(ms / s) + ' second';\n  if (ms > s) return Math.round(ms / s) + ' seconds';\n  return ms + ' ms';\n}\n}); // module: ms.js\n\nrequire.register(\"reporters/base.js\", function(module, exports, require){\n\n/**\n * Module dependencies.\n */\n\nvar tty = require('browser/tty')\n  , diff = require('browser/diff')\n  , ms = require('../ms');\n\n/**\n * Save timer references to avoid Sinon interfering (see GH-237).\n */\n\nvar Date = global.Date\n  , setTimeout = global.setTimeout\n  , setInterval = global.setInterval\n  , clearTimeout = global.clearTimeout\n  , clearInterval = global.clearInterval;\n\n/**\n * Check if both stdio streams are associated with a tty.\n */\n\nvar isatty = tty.isatty(1) && tty.isatty(2);\n\n/**\n * Expose `Base`.\n */\n\nexports = module.exports = Base;\n\n/**\n * Enable coloring by default.\n */\n\nexports.useColors = isatty;\n\n/**\n * Default color map.\n */\n\nexports.colors = {\n    'pass': 90\n  , 'fail': 31\n  , 'bright pass': 92\n  , 'bright fail': 91\n  , 'bright yellow': 93\n  , 'pending': 36\n  , 'suite': 0\n  , 'error title': 0\n  , 'error message': 31\n  , 'error stack': 90\n  , 'checkmark': 32\n  , 'fast': 90\n  , 'medium': 33\n  , 'slow': 31\n  , 'green': 32\n  , 'light': 90\n  , 'diff gutter': 90\n  , 'diff added': 42\n  , 'diff removed': 41\n};\n\n/**\n * Default symbol map.\n */\n\nexports.symbols = {\n  ok: '✓',\n  err: '✖',\n  dot: '․'\n};\n\n// With node.js on Windows: use symbols available in terminal default fonts\nif ('win32' == process.platform) {\n  exports.symbols.ok = '\\u221A';\n  exports.symbols.err = '\\u00D7';\n  exports.symbols.dot = '.';\n}\n\n/**\n * Color `str` with the given `type`,\n * allowing colors to be disabled,\n * as well as user-defined color\n * schemes.\n *\n * @param {String} type\n * @param {String} str\n * @return {String}\n * @api private\n */\n\nvar color = exports.color = function(type, str) {\n  if (!exports.useColors) return str;\n  return '\\u001b[' + exports.colors[type] + 'm' + str + '\\u001b[0m';\n};\n\n/**\n * Expose term window size, with some\n * defaults for when stderr is not a tty.\n */\n\nexports.window = {\n  width: isatty\n    ? process.stdout.getWindowSize\n      ? process.stdout.getWindowSize(1)[0]\n      : tty.getWindowSize()[1]\n    : 75\n};\n\n/**\n * Expose some basic cursor interactions\n * that are common among reporters.\n */\n\nexports.cursor = {\n  hide: function(){\n    process.stdout.write('\\u001b[?25l');\n  },\n\n  show: function(){\n    process.stdout.write('\\u001b[?25h');\n  },\n\n  deleteLine: function(){\n    process.stdout.write('\\u001b[2K');\n  },\n\n  beginningOfLine: function(){\n    process.stdout.write('\\u001b[0G');\n  },\n\n  CR: function(){\n    exports.cursor.deleteLine();\n    exports.cursor.beginningOfLine();\n  }\n};\n\n/**\n * Outut the given `failures` as a list.\n *\n * @param {Array} failures\n * @api public\n */\n\nexports.list = function(failures){\n  console.error();\n  failures.forEach(function(test, i){\n    // format\n    var fmt = color('error title', '  %s) %s:\\n')\n      + color('error message', '     %s')\n      + color('error stack', '\\n%s\\n');\n\n    // msg\n    var err = test.err\n      , message = err.message || ''\n      , stack = err.stack || message\n      , index = stack.indexOf(message) + message.length\n      , msg = stack.slice(0, index)\n      , actual = err.actual\n      , expected = err.expected\n      , escape = true;\n\n    // explicitly show diff\n    if (err.showDiff) {\n      escape = false;\n      err.actual = actual = JSON.stringify(actual, null, 2);\n      err.expected = expected = JSON.stringify(expected, null, 2);\n    }\n\n    // actual / expected diff\n    if ('string' == typeof actual && 'string' == typeof expected) {\n      msg = errorDiff(err, 'Words', escape);\n\n      // linenos\n      var lines = msg.split('\\n');\n      if (lines.length > 4) {\n        var width = String(lines.length).length;\n        msg = lines.map(function(str, i){\n          return pad(++i, width) + ' |' + ' ' + str;\n        }).join('\\n');\n      }\n\n      // legend\n      msg = '\\n'\n        + color('diff removed', 'actual')\n        + ' '\n        + color('diff added', 'expected')\n        + '\\n\\n'\n        + msg\n        + '\\n';\n\n      // indent\n      msg = msg.replace(/^/gm, '      ');\n\n      fmt = color('error title', '  %s) %s:\\n%s')\n        + color('error stack', '\\n%s\\n');\n    }\n\n    // indent stack trace without msg\n    stack = stack.slice(index ? index + 1 : index)\n      .replace(/^/gm, '  ');\n\n    console.error(fmt, (i + 1), test.fullTitle(), msg, stack);\n  });\n};\n\n/**\n * Initialize a new `Base` reporter.\n *\n * All other reporters generally\n * inherit from this reporter, providing\n * stats such as test duration, number\n * of tests passed / failed etc.\n *\n * @param {Runner} runner\n * @api public\n */\n\nfunction Base(runner) {\n  var self = this\n    , stats = this.stats = { suites: 0, tests: 0, passes: 0, pending: 0, failures: 0 }\n    , failures = this.failures = [];\n\n  if (!runner) return;\n  this.runner = runner;\n\n  runner.stats = stats;\n\n  runner.on('start', function(){\n    stats.start = new Date;\n  });\n\n  runner.on('suite', function(suite){\n    stats.suites = stats.suites || 0;\n    suite.root || stats.suites++;\n  });\n\n  runner.on('test end', function(test){\n    stats.tests = stats.tests || 0;\n    stats.tests++;\n  });\n\n  runner.on('pass', function(test){\n    stats.passes = stats.passes || 0;\n\n    var medium = test.slow() / 2;\n    test.speed = test.duration > test.slow()\n      ? 'slow'\n      : test.duration > medium\n        ? 'medium'\n        : 'fast';\n\n    stats.passes++;\n  });\n\n  runner.on('fail', function(test, err){\n    stats.failures = stats.failures || 0;\n    stats.failures++;\n    test.err = err;\n    failures.push(test);\n  });\n\n  runner.on('end', function(){\n    stats.end = new Date;\n    stats.duration = new Date - stats.start;\n  });\n\n  runner.on('pending', function(){\n    stats.pending++;\n  });\n}\n\n/**\n * Output common epilogue used by many of\n * the bundled reporters.\n *\n * @api public\n */\n\nBase.prototype.epilogue = function(){\n  var stats = this.stats\n    , fmt\n    , tests;\n\n  console.log();\n\n  function pluralize(n) {\n    return 1 == n ? 'test' : 'tests';\n  }\n\n  // failure\n  if (stats.failures) {\n    fmt = color('bright fail', '  ' + exports.symbols.err)\n      + color('fail', ' %d of %d %s failed')\n      + color('light', ':')\n\n    console.error(fmt,\n      stats.failures,\n      this.runner.total,\n      pluralize(this.runner.total));\n\n    Base.list(this.failures);\n    console.error();\n    return;\n  }\n\n  // pass\n  fmt = color('bright pass', ' ')\n    + color('green', ' %d %s complete')\n    + color('light', ' (%s)');\n\n  console.log(fmt,\n    stats.tests || 0,\n    pluralize(stats.tests),\n    ms(stats.duration));\n\n  // pending\n  if (stats.pending) {\n    fmt = color('pending', ' ')\n      + color('pending', ' %d %s pending');\n\n    console.log(fmt, stats.pending, pluralize(stats.pending));\n  }\n\n  console.log();\n};\n\n/**\n * Pad the given `str` to `len`.\n *\n * @param {String} str\n * @param {String} len\n * @return {String}\n * @api private\n */\n\nfunction pad(str, len) {\n  str = String(str);\n  return Array(len - str.length + 1).join(' ') + str;\n}\n\n/**\n * Return a character diff for `err`.\n *\n * @param {Error} err\n * @return {String}\n * @api private\n */\n\nfunction errorDiff(err, type, escape) {\n  return diff['diff' + type](err.actual, err.expected).map(function(str){\n    if (escape) {\n      str.value = str.value\n        .replace(/\\t/g, '<tab>')\n        .replace(/\\r/g, '<CR>')\n        .replace(/\\n/g, '<LF>\\n');\n    }\n    if (str.added) return colorLines('diff added', str.value);\n    if (str.removed) return colorLines('diff removed', str.value);\n    return str.value;\n  }).join('');\n}\n\n/**\n * Color lines for `str`, using the color `name`.\n *\n * @param {String} name\n * @param {String} str\n * @return {String}\n * @api private\n */\n\nfunction colorLines(name, str) {\n  return str.split('\\n').map(function(str){\n    return color(name, str);\n  }).join('\\n');\n}\n\n}); // module: reporters/base.js\n\nrequire.register(\"reporters/doc.js\", function(module, exports, require){\n\n/**\n * Module dependencies.\n */\n\nvar Base = require('./base')\n  , utils = require('../utils');\n\n/**\n * Expose `Doc`.\n */\n\nexports = module.exports = Doc;\n\n/**\n * Initialize a new `Doc` reporter.\n *\n * @param {Runner} runner\n * @api public\n */\n\nfunction Doc(runner) {\n  Base.call(this, runner);\n\n  var self = this\n    , stats = this.stats\n    , total = runner.total\n    , indents = 2;\n\n  function indent() {\n    return Array(indents).join('  ');\n  }\n\n  runner.on('suite', function(suite){\n    if (suite.root) return;\n    ++indents;\n    console.log('%s<section class=\"suite\">', indent());\n    ++indents;\n    console.log('%s<h1>%s</h1>', indent(), utils.escape(suite.title));\n    console.log('%s<dl>', indent());\n  });\n\n  runner.on('suite end', function(suite){\n    if (suite.root) return;\n    console.log('%s</dl>', indent());\n    --indents;\n    console.log('%s</section>', indent());\n    --indents;\n  });\n\n  runner.on('pass', function(test){\n    console.log('%s  <dt>%s</dt>', indent(), utils.escape(test.title));\n    var code = utils.escape(utils.clean(test.fn.toString()));\n    console.log('%s  <dd><pre><code>%s</code></pre></dd>', indent(), code);\n  });\n}\n\n}); // module: reporters/doc.js\n\nrequire.register(\"reporters/dot.js\", function(module, exports, require){\n\n/**\n * Module dependencies.\n */\n\nvar Base = require('./base')\n  , color = Base.color;\n\n/**\n * Expose `Dot`.\n */\n\nexports = module.exports = Dot;\n\n/**\n * Initialize a new `Dot` matrix test reporter.\n *\n * @param {Runner} runner\n * @api public\n */\n\nfunction Dot(runner) {\n  Base.call(this, runner);\n\n  var self = this\n    , stats = this.stats\n    , width = Base.window.width * .75 | 0\n    , n = 0;\n\n  runner.on('start', function(){\n    process.stdout.write('\\n  ');\n  });\n\n  runner.on('pending', function(test){\n    process.stdout.write(color('pending', Base.symbols.dot));\n  });\n\n  runner.on('pass', function(test){\n    if (++n % width == 0) process.stdout.write('\\n  ');\n    if ('slow' == test.speed) {\n      process.stdout.write(color('bright yellow', Base.symbols.dot));\n    } else {\n      process.stdout.write(color(test.speed, Base.symbols.dot));\n    }\n  });\n\n  runner.on('fail', function(test, err){\n    if (++n % width == 0) process.stdout.write('\\n  ');\n    process.stdout.write(color('fail', Base.symbols.dot));\n  });\n\n  runner.on('end', function(){\n    console.log();\n    self.epilogue();\n  });\n}\n\n/**\n * Inherit from `Base.prototype`.\n */\n\nfunction F(){};\nF.prototype = Base.prototype;\nDot.prototype = new F;\nDot.prototype.constructor = Dot;\n\n}); // module: reporters/dot.js\n\nrequire.register(\"reporters/html-cov.js\", function(module, exports, require){\n\n/**\n * Module dependencies.\n */\n\nvar JSONCov = require('./json-cov')\n  , fs = require('browser/fs');\n\n/**\n * Expose `HTMLCov`.\n */\n\nexports = module.exports = HTMLCov;\n\n/**\n * Initialize a new `JsCoverage` reporter.\n *\n * @param {Runner} runner\n * @api public\n */\n\nfunction HTMLCov(runner) {\n  var jade = require('jade')\n    , file = __dirname + '/templates/coverage.jade'\n    , str = fs.readFileSync(file, 'utf8')\n    , fn = jade.compile(str, { filename: file })\n    , self = this;\n\n  JSONCov.call(this, runner, false);\n\n  runner.on('end', function(){\n    process.stdout.write(fn({\n        cov: self.cov\n      , coverageClass: coverageClass\n    }));\n  });\n}\n\n/**\n * Return coverage class for `n`.\n *\n * @return {String}\n * @api private\n */\n\nfunction coverageClass(n) {\n  if (n >= 75) return 'high';\n  if (n >= 50) return 'medium';\n  if (n >= 25) return 'low';\n  return 'terrible';\n}\n}); // module: reporters/html-cov.js\n\nrequire.register(\"reporters/html.js\", function(module, exports, require){\n\n/**\n * Module dependencies.\n */\n\nvar Base = require('./base')\n  , utils = require('../utils')\n  , Progress = require('../browser/progress')\n  , escape = utils.escape;\n\n/**\n * Save timer references to avoid Sinon interfering (see GH-237).\n */\n\nvar Date = global.Date\n  , setTimeout = global.setTimeout\n  , setInterval = global.setInterval\n  , clearTimeout = global.clearTimeout\n  , clearInterval = global.clearInterval;\n\n/**\n * Expose `Doc`.\n */\n\nexports = module.exports = HTML;\n\n/**\n * Stats template.\n */\n\nvar statsTemplate = '<ul id=\"mocha-stats\">'\n  + '<li class=\"progress\"><canvas width=\"40\" height=\"40\"></canvas></li>'\n  + '<li class=\"passes\"><a href=\"#\">passes:</a> <em>0</em></li>'\n  + '<li class=\"failures\"><a href=\"#\">failures:</a> <em>0</em></li>'\n  + '<li class=\"duration\">duration: <em>0</em>s</li>'\n  + '</ul>';\n\n/**\n * Initialize a new `Doc` reporter.\n *\n * @param {Runner} runner\n * @api public\n */\n\nfunction HTML(runner, root) {\n  Base.call(this, runner);\n\n  var self = this\n    , stats = this.stats\n    , total = runner.total\n    , stat = fragment(statsTemplate)\n    , items = stat.getElementsByTagName('li')\n    , passes = items[1].getElementsByTagName('em')[0]\n    , passesLink = items[1].getElementsByTagName('a')[0]\n    , failures = items[2].getElementsByTagName('em')[0]\n    , failuresLink = items[2].getElementsByTagName('a')[0]\n    , duration = items[3].getElementsByTagName('em')[0]\n    , canvas = stat.getElementsByTagName('canvas')[0]\n    , report = fragment('<ul id=\"mocha-report\"></ul>')\n    , stack = [report]\n    , progress\n    , ctx\n\n  root = root || document.getElementById('mocha');\n\n  if (canvas.getContext) {\n    var ratio = window.devicePixelRatio || 1;\n    canvas.style.width = canvas.width;\n    canvas.style.height = canvas.height;\n    canvas.width *= ratio;\n    canvas.height *= ratio;\n    ctx = canvas.getContext('2d');\n    ctx.scale(ratio, ratio);\n    progress = new Progress;\n  }\n\n  if (!root) return error('#mocha div missing, add it to your document');\n\n  // pass toggle\n  on(passesLink, 'click', function(){\n    unhide();\n    var name = /pass/.test(report.className) ? '' : ' pass';\n    report.className = report.className.replace(/fail|pass/g, '') + name;\n    if (report.className.trim()) hideSuitesWithout('test pass');\n  });\n\n  // failure toggle\n  on(failuresLink, 'click', function(){\n    unhide();\n    var name = /fail/.test(report.className) ? '' : ' fail';\n    report.className = report.className.replace(/fail|pass/g, '') + name;\n    if (report.className.trim()) hideSuitesWithout('test fail');\n  });\n\n  root.appendChild(stat);\n  root.appendChild(report);\n\n  if (progress) progress.size(40);\n\n  runner.on('suite', function(suite){\n    if (suite.root) return;\n\n    // suite\n    var url = '?grep=' + encodeURIComponent(suite.fullTitle());\n    var el = fragment('<li class=\"suite\"><h1><a href=\"%s\">%s</a></h1></li>', url, escape(suite.title));\n\n    // container\n    stack[0].appendChild(el);\n    stack.unshift(document.createElement('ul'));\n    el.appendChild(stack[0]);\n  });\n\n  runner.on('suite end', function(suite){\n    if (suite.root) return;\n    stack.shift();\n  });\n\n  runner.on('fail', function(test, err){\n    if ('hook' == test.type) runner.emit('test end', test);\n  });\n\n  runner.on('test end', function(test){\n    // TODO: add to stats\n    var percent = stats.tests / this.total * 100 | 0;\n    if (progress) progress.update(percent).draw(ctx);\n\n    // update stats\n    var ms = new Date - stats.start;\n    text(passes, stats.passes);\n    text(failures, stats.failures);\n    text(duration, (ms / 1000).toFixed(2));\n\n    // test\n    if ('passed' == test.state) {\n      var el = fragment('<li class=\"test pass %e\"><h2>%e<span class=\"duration\">%ems</span> <a href=\"?grep=%e\" class=\"replay\">‣</a></h2></li>', test.speed, test.title, test.duration, encodeURIComponent(test.fullTitle()));\n    } else if (test.pending) {\n      var el = fragment('<li class=\"test pass pending\"><h2>%e</h2></li>', test.title);\n    } else {\n      var el = fragment('<li class=\"test fail\"><h2>%e <a href=\"?grep=%e\" class=\"replay\">‣</a></h2></li>', test.title, encodeURIComponent(test.fullTitle()));\n      var str = test.err.stack || test.err.toString();\n\n      // FF / Opera do not add the message\n      if (!~str.indexOf(test.err.message)) {\n        str = test.err.message + '\\n' + str;\n      }\n\n      // <=IE7 stringifies to [Object Error]. Since it can be overloaded, we\n      // check for the result of the stringifying.\n      if ('[object Error]' == str) str = test.err.message;\n\n      // Safari doesn't give you a stack. Let's at least provide a source line.\n      if (!test.err.stack && test.err.sourceURL && test.err.line !== undefined) {\n        str += \"\\n(\" + test.err.sourceURL + \":\" + test.err.line + \")\";\n      }\n\n      el.appendChild(fragment('<pre class=\"error\">%e</pre>', str));\n    }\n\n    // toggle code\n    // TODO: defer\n    if (!test.pending) {\n      var h2 = el.getElementsByTagName('h2')[0];\n\n      on(h2, 'click', function(){\n        pre.style.display = 'none' == pre.style.display\n          ? 'block'\n          : 'none';\n      });\n\n      var pre = fragment('<pre><code>%e</code></pre>', utils.clean(test.fn.toString()));\n      el.appendChild(pre);\n      pre.style.display = 'none';\n    }\n\n    // Don't call .appendChild if #mocha-report was already .shift()'ed off the stack.\n    if (stack[0]) stack[0].appendChild(el);\n  });\n}\n\n/**\n * Display error `msg`.\n */\n\nfunction error(msg) {\n  document.body.appendChild(fragment('<div id=\"mocha-error\">%s</div>', msg));\n}\n\n/**\n * Return a DOM fragment from `html`.\n */\n\nfunction fragment(html) {\n  var args = arguments\n    , div = document.createElement('div')\n    , i = 1;\n\n  div.innerHTML = html.replace(/%([se])/g, function(_, type){\n    switch (type) {\n      case 's': return String(args[i++]);\n      case 'e': return escape(args[i++]);\n    }\n  });\n\n  return div.firstChild;\n}\n\n/**\n * Check for suites that do not have elements\n * with `classname`, and hide them.\n */\n\nfunction hideSuitesWithout(classname) {\n  var suites = document.getElementsByClassName('suite');\n  for (var i = 0; i < suites.length; i++) {\n    var els = suites[i].getElementsByClassName(classname);\n    if (0 == els.length) suites[i].className += ' hidden';\n  }\n}\n\n/**\n * Unhide .hidden suites.\n */\n\nfunction unhide() {\n  var els = document.getElementsByClassName('suite hidden');\n  for (var i = 0; i < els.length; ++i) {\n    els[i].className = els[i].className.replace('suite hidden', 'suite');\n  }\n}\n\n/**\n * Set `el` text to `str`.\n */\n\nfunction text(el, str) {\n  if (el.textContent) {\n    el.textContent = str;\n  } else {\n    el.innerText = str;\n  }\n}\n\n/**\n * Listen on `event` with callback `fn`.\n */\n\nfunction on(el, event, fn) {\n  if (el.addEventListener) {\n    el.addEventListener(event, fn, false);\n  } else {\n    el.attachEvent('on' + event, fn);\n  }\n}\n\n}); // module: reporters/html.js\n\nrequire.register(\"reporters/index.js\", function(module, exports, require){\n\nexports.Base = require('./base');\nexports.Dot = require('./dot');\nexports.Doc = require('./doc');\nexports.TAP = require('./tap');\nexports.JSON = require('./json');\nexports.HTML = require('./html');\nexports.List = require('./list');\nexports.Min = require('./min');\nexports.Spec = require('./spec');\nexports.Nyan = require('./nyan');\nexports.XUnit = require('./xunit');\nexports.Markdown = require('./markdown');\nexports.Progress = require('./progress');\nexports.Landing = require('./landing');\nexports.JSONCov = require('./json-cov');\nexports.HTMLCov = require('./html-cov');\nexports.JSONStream = require('./json-stream');\nexports.Teamcity = require('./teamcity');\n\n}); // module: reporters/index.js\n\nrequire.register(\"reporters/json-cov.js\", function(module, exports, require){\n\n/**\n * Module dependencies.\n */\n\nvar Base = require('./base');\n\n/**\n * Expose `JSONCov`.\n */\n\nexports = module.exports = JSONCov;\n\n/**\n * Initialize a new `JsCoverage` reporter.\n *\n * @param {Runner} runner\n * @param {Boolean} output\n * @api public\n */\n\nfunction JSONCov(runner, output) {\n  var self = this\n    , output = 1 == arguments.length ? true : output;\n\n  Base.call(this, runner);\n\n  var tests = []\n    , failures = []\n    , passes = [];\n\n  runner.on('test end', function(test){\n    tests.push(test);\n  });\n\n  runner.on('pass', function(test){\n    passes.push(test);\n  });\n\n  runner.on('fail', function(test){\n    failures.push(test);\n  });\n\n  runner.on('end', function(){\n    var cov = global._$jscoverage || {};\n    var result = self.cov = map(cov);\n    result.stats = self.stats;\n    result.tests = tests.map(clean);\n    result.failures = failures.map(clean);\n    result.passes = passes.map(clean);\n    if (!output) return;\n    process.stdout.write(JSON.stringify(result, null, 2 ));\n  });\n}\n\n/**\n * Map jscoverage data to a JSON structure\n * suitable for reporting.\n *\n * @param {Object} cov\n * @return {Object}\n * @api private\n */\n\nfunction map(cov) {\n  var ret = {\n      instrumentation: 'node-jscoverage'\n    , sloc: 0\n    , hits: 0\n    , misses: 0\n    , coverage: 0\n    , files: []\n  };\n\n  for (var filename in cov) {\n    var data = coverage(filename, cov[filename]);\n    ret.files.push(data);\n    ret.hits += data.hits;\n    ret.misses += data.misses;\n    ret.sloc += data.sloc;\n  }\n\n  ret.files.sort(function(a, b) {\n    return a.filename.localeCompare(b.filename);\n  });\n\n  if (ret.sloc > 0) {\n    ret.coverage = (ret.hits / ret.sloc) * 100;\n  }\n\n  return ret;\n};\n\n/**\n * Map jscoverage data for a single source file\n * to a JSON structure suitable for reporting.\n *\n * @param {String} filename name of the source file\n * @param {Object} data jscoverage coverage data\n * @return {Object}\n * @api private\n */\n\nfunction coverage(filename, data) {\n  var ret = {\n    filename: filename,\n    coverage: 0,\n    hits: 0,\n    misses: 0,\n    sloc: 0,\n    source: {}\n  };\n\n  data.source.forEach(function(line, num){\n    num++;\n\n    if (data[num] === 0) {\n      ret.misses++;\n      ret.sloc++;\n    } else if (data[num] !== undefined) {\n      ret.hits++;\n      ret.sloc++;\n    }\n\n    ret.source[num] = {\n        source: line\n      , coverage: data[num] === undefined\n        ? ''\n        : data[num]\n    };\n  });\n\n  ret.coverage = ret.hits / ret.sloc * 100;\n\n  return ret;\n}\n\n/**\n * Return a plain-object representation of `test`\n * free of cyclic properties etc.\n *\n * @param {Object} test\n * @return {Object}\n * @api private\n */\n\nfunction clean(test) {\n  return {\n      title: test.title\n    , fullTitle: test.fullTitle()\n    , duration: test.duration\n  }\n}\n\n}); // module: reporters/json-cov.js\n\nrequire.register(\"reporters/json-stream.js\", function(module, exports, require){\n\n/**\n * Module dependencies.\n */\n\nvar Base = require('./base')\n  , color = Base.color;\n\n/**\n * Expose `List`.\n */\n\nexports = module.exports = List;\n\n/**\n * Initialize a new `List` test reporter.\n *\n * @param {Runner} runner\n * @api public\n */\n\nfunction List(runner) {\n  Base.call(this, runner);\n\n  var self = this\n    , stats = this.stats\n    , total = runner.total;\n\n  runner.on('start', function(){\n    console.log(JSON.stringify(['start', { total: total }]));\n  });\n\n  runner.on('pass', function(test){\n    console.log(JSON.stringify(['pass', clean(test)]));\n  });\n\n  runner.on('fail', function(test, err){\n    console.log(JSON.stringify(['fail', clean(test)]));\n  });\n\n  runner.on('end', function(){\n    process.stdout.write(JSON.stringify(['end', self.stats]));\n  });\n}\n\n/**\n * Return a plain-object representation of `test`\n * free of cyclic properties etc.\n *\n * @param {Object} test\n * @return {Object}\n * @api private\n */\n\nfunction clean(test) {\n  return {\n      title: test.title\n    , fullTitle: test.fullTitle()\n    , duration: test.duration\n  }\n}\n}); // module: reporters/json-stream.js\n\nrequire.register(\"reporters/json.js\", function(module, exports, require){\n\n/**\n * Module dependencies.\n */\n\nvar Base = require('./base')\n  , cursor = Base.cursor\n  , color = Base.color;\n\n/**\n * Expose `JSON`.\n */\n\nexports = module.exports = JSONReporter;\n\n/**\n * Initialize a new `JSON` reporter.\n *\n * @param {Runner} runner\n * @api public\n */\n\nfunction JSONReporter(runner) {\n  var self = this;\n  Base.call(this, runner);\n\n  var tests = []\n    , failures = []\n    , passes = [];\n\n  runner.on('test end', function(test){\n    tests.push(test);\n  });\n\n  runner.on('pass', function(test){\n    passes.push(test);\n  });\n\n  runner.on('fail', function(test){\n    failures.push(test);\n  });\n\n  runner.on('end', function(){\n    var obj = {\n        stats: self.stats\n      , tests: tests.map(clean)\n      , failures: failures.map(clean)\n      , passes: passes.map(clean)\n    };\n\n    process.stdout.write(JSON.stringify(obj, null, 2));\n  });\n}\n\n/**\n * Return a plain-object representation of `test`\n * free of cyclic properties etc.\n *\n * @param {Object} test\n * @return {Object}\n * @api private\n */\n\nfunction clean(test) {\n  return {\n      title: test.title\n    , fullTitle: test.fullTitle()\n    , duration: test.duration\n  }\n}\n}); // module: reporters/json.js\n\nrequire.register(\"reporters/landing.js\", function(module, exports, require){\n\n/**\n * Module dependencies.\n */\n\nvar Base = require('./base')\n  , cursor = Base.cursor\n  , color = Base.color;\n\n/**\n * Expose `Landing`.\n */\n\nexports = module.exports = Landing;\n\n/**\n * Airplane color.\n */\n\nBase.colors.plane = 0;\n\n/**\n * Airplane crash color.\n */\n\nBase.colors['plane crash'] = 31;\n\n/**\n * Runway color.\n */\n\nBase.colors.runway = 90;\n\n/**\n * Initialize a new `Landing` reporter.\n *\n * @param {Runner} runner\n * @api public\n */\n\nfunction Landing(runner) {\n  Base.call(this, runner);\n\n  var self = this\n    , stats = this.stats\n    , width = Base.window.width * .75 | 0\n    , total = runner.total\n    , stream = process.stdout\n    , plane = color('plane', '✈')\n    , crashed = -1\n    , n = 0;\n\n  function runway() {\n    var buf = Array(width).join('-');\n    return '  ' + color('runway', buf);\n  }\n\n  runner.on('start', function(){\n    stream.write('\\n  ');\n    cursor.hide();\n  });\n\n  runner.on('test end', function(test){\n    // check if the plane crashed\n    var col = -1 == crashed\n      ? width * ++n / total | 0\n      : crashed;\n\n    // show the crash\n    if ('failed' == test.state) {\n      plane = color('plane crash', '✈');\n      crashed = col;\n    }\n\n    // render landing strip\n    stream.write('\\u001b[4F\\n\\n');\n    stream.write(runway());\n    stream.write('\\n  ');\n    stream.write(color('runway', Array(col).join('⋅')));\n    stream.write(plane)\n    stream.write(color('runway', Array(width - col).join('⋅') + '\\n'));\n    stream.write(runway());\n    stream.write('\\u001b[0m');\n  });\n\n  runner.on('end', function(){\n    cursor.show();\n    console.log();\n    self.epilogue();\n  });\n}\n\n/**\n * Inherit from `Base.prototype`.\n */\n\nfunction F(){};\nF.prototype = Base.prototype;\nLanding.prototype = new F;\nLanding.prototype.constructor = Landing;\n\n}); // module: reporters/landing.js\n\nrequire.register(\"reporters/list.js\", function(module, exports, require){\n\n/**\n * Module dependencies.\n */\n\nvar Base = require('./base')\n  , cursor = Base.cursor\n  , color = Base.color;\n\n/**\n * Expose `List`.\n */\n\nexports = module.exports = List;\n\n/**\n * Initialize a new `List` test reporter.\n *\n * @param {Runner} runner\n * @api public\n */\n\nfunction List(runner) {\n  Base.call(this, runner);\n\n  var self = this\n    , stats = this.stats\n    , n = 0;\n\n  runner.on('start', function(){\n    console.log();\n  });\n\n  runner.on('test', function(test){\n    process.stdout.write(color('pass', '    ' + test.fullTitle() + ': '));\n  });\n\n  runner.on('pending', function(test){\n    var fmt = color('checkmark', '  -')\n      + color('pending', ' %s');\n    console.log(fmt, test.fullTitle());\n  });\n\n  runner.on('pass', function(test){\n    var fmt = color('checkmark', '  '+Base.symbols.dot)\n      + color('pass', ' %s: ')\n      + color(test.speed, '%dms');\n    cursor.CR();\n    console.log(fmt, test.fullTitle(), test.duration);\n  });\n\n  runner.on('fail', function(test, err){\n    cursor.CR();\n    console.log(color('fail', '  %d) %s'), ++n, test.fullTitle());\n  });\n\n  runner.on('end', self.epilogue.bind(self));\n}\n\n/**\n * Inherit from `Base.prototype`.\n */\n\nfunction F(){};\nF.prototype = Base.prototype;\nList.prototype = new F;\nList.prototype.constructor = List;\n\n\n}); // module: reporters/list.js\n\nrequire.register(\"reporters/markdown.js\", function(module, exports, require){\n/**\n * Module dependencies.\n */\n\nvar Base = require('./base')\n  , utils = require('../utils');\n\n/**\n * Expose `Markdown`.\n */\n\nexports = module.exports = Markdown;\n\n/**\n * Initialize a new `Markdown` reporter.\n *\n * @param {Runner} runner\n * @api public\n */\n\nfunction Markdown(runner) {\n  Base.call(this, runner);\n\n  var self = this\n    , stats = this.stats\n    , level = 0\n    , buf = '';\n\n  function title(str) {\n    return Array(level).join('#') + ' ' + str;\n  }\n\n  function indent() {\n    return Array(level).join('  ');\n  }\n\n  function mapTOC(suite, obj) {\n    var ret = obj;\n    obj = obj[suite.title] = obj[suite.title] || { suite: suite };\n    suite.suites.forEach(function(suite){\n      mapTOC(suite, obj);\n    });\n    return ret;\n  }\n\n  function stringifyTOC(obj, level) {\n    ++level;\n    var buf = '';\n    var link;\n    for (var key in obj) {\n      if ('suite' == key) continue;\n      if (key) link = ' - [' + key + '](#' + utils.slug(obj[key].suite.fullTitle()) + ')\\n';\n      if (key) buf += Array(level).join('  ') + link;\n      buf += stringifyTOC(obj[key], level);\n    }\n    --level;\n    return buf;\n  }\n\n  function generateTOC(suite) {\n    var obj = mapTOC(suite, {});\n    return stringifyTOC(obj, 0);\n  }\n\n  generateTOC(runner.suite);\n\n  runner.on('suite', function(suite){\n    ++level;\n    var slug = utils.slug(suite.fullTitle());\n    buf += '<a name=\"' + slug + '\"></a>' + '\\n';\n    buf += title(suite.title) + '\\n';\n  });\n\n  runner.on('suite end', function(suite){\n    --level;\n  });\n\n  runner.on('pass', function(test){\n    var code = utils.clean(test.fn.toString());\n    buf += test.title + '.\\n';\n    buf += '\\n```js\\n';\n    buf += code + '\\n';\n    buf += '```\\n\\n';\n  });\n\n  runner.on('end', function(){\n    process.stdout.write('# TOC\\n');\n    process.stdout.write(generateTOC(runner.suite));\n    process.stdout.write(buf);\n  });\n}\n}); // module: reporters/markdown.js\n\nrequire.register(\"reporters/min.js\", function(module, exports, require){\n\n/**\n * Module dependencies.\n */\n\nvar Base = require('./base');\n\n/**\n * Expose `Min`.\n */\n\nexports = module.exports = Min;\n\n/**\n * Initialize a new `Min` minimal test reporter (best used with --watch).\n *\n * @param {Runner} runner\n * @api public\n */\n\nfunction Min(runner) {\n  Base.call(this, runner);\n\n  runner.on('start', function(){\n    // clear screen\n    process.stdout.write('\\u001b[2J');\n    // set cursor position\n    process.stdout.write('\\u001b[1;3H');\n  });\n\n  runner.on('end', this.epilogue.bind(this));\n}\n\n/**\n * Inherit from `Base.prototype`.\n */\n\nfunction F(){};\nF.prototype = Base.prototype;\nMin.prototype = new F;\nMin.prototype.constructor = Min;\n\n\n}); // module: reporters/min.js\n\nrequire.register(\"reporters/nyan.js\", function(module, exports, require){\n/**\n * Module dependencies.\n */\n\nvar Base = require('./base')\n  , color = Base.color;\n\n/**\n * Expose `Dot`.\n */\n\nexports = module.exports = NyanCat;\n\n/**\n * Initialize a new `Dot` matrix test reporter.\n *\n * @param {Runner} runner\n * @api public\n */\n\nfunction NyanCat(runner) {\n  Base.call(this, runner);\n\n  var self = this\n    , stats = this.stats\n    , width = Base.window.width * .75 | 0\n    , rainbowColors = this.rainbowColors = self.generateColors()\n    , colorIndex = this.colorIndex = 0\n    , numerOfLines = this.numberOfLines = 4\n    , trajectories = this.trajectories = [[], [], [], []]\n    , nyanCatWidth = this.nyanCatWidth = 11\n    , trajectoryWidthMax = this.trajectoryWidthMax = (width - nyanCatWidth)\n    , scoreboardWidth = this.scoreboardWidth = 5\n    , tick = this.tick = 0\n    , n = 0;\n\n  runner.on('start', function(){\n    Base.cursor.hide();\n    self.draw('start');\n  });\n\n  runner.on('pending', function(test){\n    self.draw('pending');\n  });\n\n  runner.on('pass', function(test){\n    self.draw('pass');\n  });\n\n  runner.on('fail', function(test, err){\n    self.draw('fail');\n  });\n\n  runner.on('end', function(){\n    Base.cursor.show();\n    for (var i = 0; i < self.numberOfLines; i++) write('\\n');\n    self.epilogue();\n  });\n}\n\n/**\n * Draw the nyan cat with runner `status`.\n *\n * @param {String} status\n * @api private\n */\n\nNyanCat.prototype.draw = function(status){\n  this.appendRainbow();\n  this.drawScoreboard();\n  this.drawRainbow();\n  this.drawNyanCat(status);\n  this.tick = !this.tick;\n};\n\n/**\n * Draw the \"scoreboard\" showing the number\n * of passes, failures and pending tests.\n *\n * @api private\n */\n\nNyanCat.prototype.drawScoreboard = function(){\n  var stats = this.stats;\n  var colors = Base.colors;\n\n  function draw(color, n) {\n    write(' ');\n    write('\\u001b[' + color + 'm' + n + '\\u001b[0m');\n    write('\\n');\n  }\n\n  draw(colors.green, stats.passes);\n  draw(colors.fail, stats.failures);\n  draw(colors.pending, stats.pending);\n  write('\\n');\n\n  this.cursorUp(this.numberOfLines);\n};\n\n/**\n * Append the rainbow.\n *\n * @api private\n */\n\nNyanCat.prototype.appendRainbow = function(){\n  var segment = this.tick ? '_' : '-';\n  var rainbowified = this.rainbowify(segment);\n\n  for (var index = 0; index < this.numberOfLines; index++) {\n    var trajectory = this.trajectories[index];\n    if (trajectory.length >= this.trajectoryWidthMax) trajectory.shift();\n    trajectory.push(rainbowified);\n  }\n};\n\n/**\n * Draw the rainbow.\n *\n * @api private\n */\n\nNyanCat.prototype.drawRainbow = function(){\n  var self = this;\n\n  this.trajectories.forEach(function(line, index) {\n    write('\\u001b[' + self.scoreboardWidth + 'C');\n    write(line.join(''));\n    write('\\n');\n  });\n\n  this.cursorUp(this.numberOfLines);\n};\n\n/**\n * Draw the nyan cat with `status`.\n *\n * @param {String} status\n * @api private\n */\n\nNyanCat.prototype.drawNyanCat = function(status) {\n  var self = this;\n  var startWidth = this.scoreboardWidth + this.trajectories[0].length;\n  var color = '\\u001b[' + startWidth + 'C';\n  var padding = '';\n  \n  write(color);\n  write('_,------,');\n  write('\\n');\n  \n  write(color);\n  padding = self.tick ? '  ' : '   ';\n  write('_|' + padding + '/\\\\_/\\\\ ');\n  write('\\n');\n  \n  write(color);\n  padding = self.tick ? '_' : '__';\n  var tail = self.tick ? '~' : '^';\n  var face;\n  switch (status) {\n    case 'pass':\n      face = '( ^ .^)';\n      break;\n    case 'fail':\n      face = '( o .o)';\n      break;\n    default:\n      face = '( - .-)';\n  }\n  write(tail + '|' + padding + face + ' ');\n  write('\\n');\n  \n  write(color);\n  padding = self.tick ? ' ' : '  ';\n  write(padding + '\"\"  \"\" ');\n  write('\\n');\n\n  this.cursorUp(this.numberOfLines);\n};\n\n/**\n * Move cursor up `n`.\n *\n * @param {Number} n\n * @api private\n */\n\nNyanCat.prototype.cursorUp = function(n) {\n  write('\\u001b[' + n + 'A');\n};\n\n/**\n * Move cursor down `n`.\n *\n * @param {Number} n\n * @api private\n */\n\nNyanCat.prototype.cursorDown = function(n) {\n  write('\\u001b[' + n + 'B');\n};\n\n/**\n * Generate rainbow colors.\n *\n * @return {Array}\n * @api private\n */\n\nNyanCat.prototype.generateColors = function(){\n  var colors = [];\n\n  for (var i = 0; i < (6 * 7); i++) {\n    var pi3 = Math.floor(Math.PI / 3);\n    var n = (i * (1.0 / 6));\n    var r = Math.floor(3 * Math.sin(n) + 3);\n    var g = Math.floor(3 * Math.sin(n + 2 * pi3) + 3);\n    var b = Math.floor(3 * Math.sin(n + 4 * pi3) + 3);\n    colors.push(36 * r + 6 * g + b + 16);\n  }\n\n  return colors;\n};\n\n/**\n * Apply rainbow to the given `str`.\n *\n * @param {String} str\n * @return {String}\n * @api private\n */\n\nNyanCat.prototype.rainbowify = function(str){\n  var color = this.rainbowColors[this.colorIndex % this.rainbowColors.length];\n  this.colorIndex += 1;\n  return '\\u001b[38;5;' + color + 'm' + str + '\\u001b[0m';\n};\n\n/**\n * Stdout helper.\n */\n\nfunction write(string) {\n  process.stdout.write(string);\n}\n\n/**\n * Inherit from `Base.prototype`.\n */\n\nfunction F(){};\nF.prototype = Base.prototype;\nNyanCat.prototype = new F;\nNyanCat.prototype.constructor = NyanCat;\n\n\n}); // module: reporters/nyan.js\n\nrequire.register(\"reporters/progress.js\", function(module, exports, require){\n\n/**\n * Module dependencies.\n */\n\nvar Base = require('./base')\n  , cursor = Base.cursor\n  , color = Base.color;\n\n/**\n * Expose `Progress`.\n */\n\nexports = module.exports = Progress;\n\n/**\n * General progress bar color.\n */\n\nBase.colors.progress = 90;\n\n/**\n * Initialize a new `Progress` bar test reporter.\n *\n * @param {Runner} runner\n * @param {Object} options\n * @api public\n */\n\nfunction Progress(runner, options) {\n  Base.call(this, runner);\n\n  var self = this\n    , options = options || {}\n    , stats = this.stats\n    , width = Base.window.width * .50 | 0\n    , total = runner.total\n    , complete = 0\n    , max = Math.max;\n\n  // default chars\n  options.open = options.open || '[';\n  options.complete = options.complete || '▬';\n  options.incomplete = options.incomplete || Base.symbols.dot;\n  options.close = options.close || ']';\n  options.verbose = false;\n\n  // tests started\n  runner.on('start', function(){\n    console.log();\n    cursor.hide();\n  });\n\n  // tests complete\n  runner.on('test end', function(){\n    complete++;\n    var incomplete = total - complete\n      , percent = complete / total\n      , n = width * percent | 0\n      , i = width - n;\n\n    cursor.CR();\n    process.stdout.write('\\u001b[J');\n    process.stdout.write(color('progress', '  ' + options.open));\n    process.stdout.write(Array(n).join(options.complete));\n    process.stdout.write(Array(i).join(options.incomplete));\n    process.stdout.write(color('progress', options.close));\n    if (options.verbose) {\n      process.stdout.write(color('progress', ' ' + complete + ' of ' + total));\n    }\n  });\n\n  // tests are complete, output some stats\n  // and the failures if any\n  runner.on('end', function(){\n    cursor.show();\n    console.log();\n    self.epilogue();\n  });\n}\n\n/**\n * Inherit from `Base.prototype`.\n */\n\nfunction F(){};\nF.prototype = Base.prototype;\nProgress.prototype = new F;\nProgress.prototype.constructor = Progress;\n\n\n}); // module: reporters/progress.js\n\nrequire.register(\"reporters/spec.js\", function(module, exports, require){\n\n/**\n * Module dependencies.\n */\n\nvar Base = require('./base')\n  , cursor = Base.cursor\n  , color = Base.color;\n\n/**\n * Expose `Spec`.\n */\n\nexports = module.exports = Spec;\n\n/**\n * Initialize a new `Spec` test reporter.\n *\n * @param {Runner} runner\n * @api public\n */\n\nfunction Spec(runner) {\n  Base.call(this, runner);\n\n  var self = this\n    , stats = this.stats\n    , indents = 0\n    , n = 0;\n\n  function indent() {\n    return Array(indents).join('  ')\n  }\n\n  runner.on('start', function(){\n    console.log();\n  });\n\n  runner.on('suite', function(suite){\n    ++indents;\n    console.log(color('suite', '%s%s'), indent(), suite.title);\n  });\n\n  runner.on('suite end', function(suite){\n    --indents;\n    if (1 == indents) console.log();\n  });\n\n  runner.on('test', function(test){\n    process.stdout.write(indent() + color('pass', '  ◦ ' + test.title + ': '));\n  });\n\n  runner.on('pending', function(test){\n    var fmt = indent() + color('pending', '  - %s');\n    console.log(fmt, test.title);\n  });\n\n  runner.on('pass', function(test){\n    if ('fast' == test.speed) {\n      var fmt = indent()\n        + color('checkmark', '  ' + Base.symbols.ok)\n        + color('pass', ' %s ');\n      cursor.CR();\n      console.log(fmt, test.title);\n    } else {\n      var fmt = indent()\n        + color('checkmark', '  ' + Base.symbols.ok)\n        + color('pass', ' %s ')\n        + color(test.speed, '(%dms)');\n      cursor.CR();\n      console.log(fmt, test.title, test.duration);\n    }\n  });\n\n  runner.on('fail', function(test, err){\n    cursor.CR();\n    console.log(indent() + color('fail', '  %d) %s'), ++n, test.title);\n  });\n\n  runner.on('end', self.epilogue.bind(self));\n}\n\n/**\n * Inherit from `Base.prototype`.\n */\n\nfunction F(){};\nF.prototype = Base.prototype;\nSpec.prototype = new F;\nSpec.prototype.constructor = Spec;\n\n\n}); // module: reporters/spec.js\n\nrequire.register(\"reporters/tap.js\", function(module, exports, require){\n\n/**\n * Module dependencies.\n */\n\nvar Base = require('./base')\n  , cursor = Base.cursor\n  , color = Base.color;\n\n/**\n * Expose `TAP`.\n */\n\nexports = module.exports = TAP;\n\n/**\n * Initialize a new `TAP` reporter.\n *\n * @param {Runner} runner\n * @api public\n */\n\nfunction TAP(runner) {\n  Base.call(this, runner);\n\n  var self = this\n    , stats = this.stats\n    , n = 1\n    , passes = 0\n    , failures = 0;\n\n  runner.on('start', function(){\n    var total = runner.grepTotal(runner.suite);\n    console.log('%d..%d', 1, total);\n  });\n\n  runner.on('test end', function(){\n    ++n;\n  });\n\n  runner.on('pending', function(test){\n    console.log('ok %d %s # SKIP -', n, title(test));\n  });\n\n  runner.on('pass', function(test){\n    passes++;\n    console.log('ok %d %s', n, title(test));\n  });\n\n  runner.on('fail', function(test, err){\n    failures++;\n    console.log('not ok %d %s', n, title(test));\n    if (err.stack) console.log(err.stack.replace(/^/gm, '  '));\n  });\n\n  runner.on('end', function(){\n    console.log('# tests ' + (passes + failures));\n    console.log('# pass ' + passes);\n    console.log('# fail ' + failures);\n  });\n}\n\n/**\n * Return a TAP-safe title of `test`\n *\n * @param {Object} test\n * @return {String}\n * @api private\n */\n\nfunction title(test) {\n  return test.fullTitle().replace(/#/g, '');\n}\n\n}); // module: reporters/tap.js\n\nrequire.register(\"reporters/teamcity.js\", function(module, exports, require){\n\n/**\n * Module dependencies.\n */\n\nvar Base = require('./base');\n\n/**\n * Expose `Teamcity`.\n */\n\nexports = module.exports = Teamcity;\n\n/**\n * Initialize a new `Teamcity` reporter.\n *\n * @param {Runner} runner\n * @api public\n */\n\nfunction Teamcity(runner) {\n  Base.call(this, runner);\n  var stats = this.stats;\n\n  runner.on('start', function() {\n    console.log(\"##teamcity[testSuiteStarted name='mocha.suite']\");\n  });\n\n  runner.on('test', function(test) {\n    console.log(\"##teamcity[testStarted name='\" + escape(test.fullTitle()) + \"']\");\n  });\n\n  runner.on('fail', function(test, err) {\n    console.log(\"##teamcity[testFailed name='\" + escape(test.fullTitle()) + \"' message='\" + escape(err.message) + \"']\");\n  });\n\n  runner.on('pending', function(test) {\n    console.log(\"##teamcity[testIgnored name='\" + escape(test.fullTitle()) + \"' message='pending']\");\n  });\n\n  runner.on('test end', function(test) {\n    console.log(\"##teamcity[testFinished name='\" + escape(test.fullTitle()) + \"' duration='\" + test.duration + \"']\");\n  });\n\n  runner.on('end', function() {\n    console.log(\"##teamcity[testSuiteFinished name='mocha.suite' duration='\" + stats.duration + \"']\");\n  });\n}\n\n/**\n * Escape the given `str`.\n */\n\nfunction escape(str) {\n  return str\n    .replace(/\\|/g, \"||\")\n    .replace(/\\n/g, \"|n\")\n    .replace(/\\r/g, \"|r\")\n    .replace(/\\[/g, \"|[\")\n    .replace(/\\]/g, \"|]\")\n    .replace(/\\u0085/g, \"|x\")\n    .replace(/\\u2028/g, \"|l\")\n    .replace(/\\u2029/g, \"|p\")\n    .replace(/'/g, \"|'\");\n}\n\n}); // module: reporters/teamcity.js\n\nrequire.register(\"reporters/xunit.js\", function(module, exports, require){\n\n/**\n * Module dependencies.\n */\n\nvar Base = require('./base')\n  , utils = require('../utils')\n  , escape = utils.escape;\n\n/**\n * Save timer references to avoid Sinon interfering (see GH-237).\n */\n\nvar Date = global.Date\n  , setTimeout = global.setTimeout\n  , setInterval = global.setInterval\n  , clearTimeout = global.clearTimeout\n  , clearInterval = global.clearInterval;\n\n/**\n * Expose `XUnit`.\n */\n\nexports = module.exports = XUnit;\n\n/**\n * Initialize a new `XUnit` reporter.\n *\n * @param {Runner} runner\n * @api public\n */\n\nfunction XUnit(runner) {\n  Base.call(this, runner);\n  var stats = this.stats\n    , tests = []\n    , self = this;\n\n  runner.on('pass', function(test){\n    tests.push(test);\n  });\n\n  runner.on('fail', function(test){\n    tests.push(test);\n  });\n\n  runner.on('end', function(){\n    console.log(tag('testsuite', {\n        name: 'Mocha Tests'\n      , tests: stats.tests\n      , failures: stats.failures\n      , errors: stats.failures\n      , skip: stats.tests - stats.failures - stats.passes\n      , timestamp: (new Date).toUTCString()\n      , time: stats.duration / 1000\n    }, false));\n\n    tests.forEach(test);\n    console.log('</testsuite>');\n  });\n}\n\n/**\n * Inherit from `Base.prototype`.\n */\n\nfunction F(){};\nF.prototype = Base.prototype;\nXUnit.prototype = new F;\nXUnit.prototype.constructor = XUnit;\n\n\n/**\n * Output tag for the given `test.`\n */\n\nfunction test(test) {\n  var attrs = {\n      classname: test.parent.fullTitle()\n    , name: test.title\n    , time: test.duration / 1000\n  };\n\n  if ('failed' == test.state) {\n    var err = test.err;\n    attrs.message = escape(err.message);\n    console.log(tag('testcase', attrs, false, tag('failure', attrs, false, cdata(err.stack))));\n  } else if (test.pending) {\n    console.log(tag('testcase', attrs, false, tag('skipped', {}, true)));\n  } else {\n    console.log(tag('testcase', attrs, true) );\n  }\n}\n\n/**\n * HTML tag helper.\n */\n\nfunction tag(name, attrs, close, content) {\n  var end = close ? '/>' : '>'\n    , pairs = []\n    , tag;\n\n  for (var key in attrs) {\n    pairs.push(key + '=\"' + escape(attrs[key]) + '\"');\n  }\n\n  tag = '<' + name + (pairs.length ? ' ' + pairs.join(' ') : '') + end;\n  if (content) tag += content + '</' + name + end;\n  return tag;\n}\n\n/**\n * Return cdata escaped CDATA `str`.\n */\n\nfunction cdata(str) {\n  return '<![CDATA[' + escape(str) + ']]>';\n}\n\n}); // module: reporters/xunit.js\n\nrequire.register(\"runnable.js\", function(module, exports, require){\n\n/**\n * Module dependencies.\n */\n\nvar EventEmitter = require('browser/events').EventEmitter\n  , debug = require('browser/debug')('mocha:runnable')\n  , milliseconds = require('./ms');\n\n/**\n * Save timer references to avoid Sinon interfering (see GH-237).\n */\n\nvar Date = global.Date\n  , setTimeout = global.setTimeout\n  , setInterval = global.setInterval\n  , clearTimeout = global.clearTimeout\n  , clearInterval = global.clearInterval;\n\n/**\n * Object#toString().\n */\n\nvar toString = Object.prototype.toString;\n\n/**\n * Expose `Runnable`.\n */\n\nmodule.exports = Runnable;\n\n/**\n * Initialize a new `Runnable` with the given `title` and callback `fn`.\n *\n * @param {String} title\n * @param {Function} fn\n * @api private\n */\n\nfunction Runnable(title, fn) {\n  this.title = title;\n  this.fn = fn;\n  this.async = fn && fn.length;\n  this.sync = ! this.async;\n  this._timeout = 2000;\n  this._slow = 75;\n  this.timedOut = false;\n}\n\n/**\n * Inherit from `EventEmitter.prototype`.\n */\n\nfunction F(){};\nF.prototype = EventEmitter.prototype;\nRunnable.prototype = new F;\nRunnable.prototype.constructor = Runnable;\n\n\n/**\n * Set & get timeout `ms`.\n *\n * @param {Number|String} ms\n * @return {Runnable|Number} ms or self\n * @api private\n */\n\nRunnable.prototype.timeout = function(ms){\n  if (0 == arguments.length) return this._timeout;\n  if ('string' == typeof ms) ms = milliseconds(ms);\n  debug('timeout %d', ms);\n  this._timeout = ms;\n  if (this.timer) this.resetTimeout();\n  return this;\n};\n\n/**\n * Set & get slow `ms`.\n *\n * @param {Number|String} ms\n * @return {Runnable|Number} ms or self\n * @api private\n */\n\nRunnable.prototype.slow = function(ms){\n  if (0 === arguments.length) return this._slow;\n  if ('string' == typeof ms) ms = milliseconds(ms);\n  debug('timeout %d', ms);\n  this._slow = ms;\n  return this;\n};\n\n/**\n * Return the full title generated by recursively\n * concatenating the parent's full title.\n *\n * @return {String}\n * @api public\n */\n\nRunnable.prototype.fullTitle = function(){\n  return this.parent.fullTitle() + ' ' + this.title;\n};\n\n/**\n * Clear the timeout.\n *\n * @api private\n */\n\nRunnable.prototype.clearTimeout = function(){\n  clearTimeout(this.timer);\n};\n\n/**\n * Inspect the runnable void of private properties.\n *\n * @return {String}\n * @api private\n */\n\nRunnable.prototype.inspect = function(){\n  return JSON.stringify(this, function(key, val){\n    if ('_' == key[0]) return;\n    if ('parent' == key) return '#<Suite>';\n    if ('ctx' == key) return '#<Context>';\n    return val;\n  }, 2);\n};\n\n/**\n * Reset the timeout.\n *\n * @api private\n */\n\nRunnable.prototype.resetTimeout = function(){\n  var self = this\n    , ms = this.timeout();\n\n  this.clearTimeout();\n  if (ms) {\n    this.timer = setTimeout(function(){\n      self.callback(new Error('timeout of ' + ms + 'ms exceeded'));\n      self.timedOut = true;\n    }, ms);\n  }\n};\n\n/**\n * Run the test and invoke `fn(err)`.\n *\n * @param {Function} fn\n * @api private\n */\n\nRunnable.prototype.run = function(fn){\n  var self = this\n    , ms = this.timeout()\n    , start = new Date\n    , ctx = this.ctx\n    , finished\n    , emitted;\n\n  if (ctx) ctx.runnable(this);\n\n  // timeout\n  if (this.async) {\n    if (ms) {\n      this.timer = setTimeout(function(){\n        done(new Error('timeout of ' + ms + 'ms exceeded'));\n        self.timedOut = true;\n      }, ms);\n    }\n  }\n\n  // called multiple times\n  function multiple(err) {\n    if (emitted) return;\n    emitted = true;\n    self.emit('error', err || new Error('done() called multiple times'));\n  }\n\n  // finished\n  function done(err) {\n    if (self.timedOut) return;\n    if (finished) return multiple(err);\n    self.clearTimeout();\n    self.duration = new Date - start;\n    finished = true;\n    fn(err);\n  }\n\n  // for .resetTimeout()\n  this.callback = done;\n\n  // async\n  if (this.async) {\n    try {\n      this.fn.call(ctx, function(err){\n        if (err instanceof Error || toString.call(err) === \"[object Error]\") return done(err);\n        if (null != err) return done(new Error('done() invoked with non-Error: ' + err));\n        done();\n      });\n    } catch (err) {\n      done(err);\n    }\n    return;\n  }\n\n  if (this.asyncOnly) {\n    return done(new Error('--async-only option in use without declaring `done()`'));\n  }\n\n  // sync\n  try {\n    if (!this.pending) this.fn.call(ctx);\n    this.duration = new Date - start;\n    fn();\n  } catch (err) {\n    fn(err);\n  }\n};\n\n}); // module: runnable.js\n\nrequire.register(\"runner.js\", function(module, exports, require){\n\n/**\n * Module dependencies.\n */\n\nvar EventEmitter = require('browser/events').EventEmitter\n  , debug = require('browser/debug')('mocha:runner')\n  , Test = require('./test')\n  , utils = require('./utils')\n  , filter = utils.filter\n  , keys = utils.keys;\n\n/**\n * Non-enumerable globals.\n */\n\nvar globals = [\n  'setTimeout',\n  'clearTimeout',\n  'setInterval',\n  'clearInterval',\n  'XMLHttpRequest',\n  'Date'\n];\n\n/**\n * Expose `Runner`.\n */\n\nmodule.exports = Runner;\n\n/**\n * Initialize a `Runner` for the given `suite`.\n *\n * Events:\n *\n *   - `start`  execution started\n *   - `end`  execution complete\n *   - `suite`  (suite) test suite execution started\n *   - `suite end`  (suite) all tests (and sub-suites) have finished\n *   - `test`  (test) test execution started\n *   - `test end`  (test) test completed\n *   - `hook`  (hook) hook execution started\n *   - `hook end`  (hook) hook complete\n *   - `pass`  (test) test passed\n *   - `fail`  (test, err) test failed\n *\n * @api public\n */\n\nfunction Runner(suite) {\n  var self = this;\n  this._globals = [];\n  this.suite = suite;\n  this.total = suite.total();\n  this.failures = 0;\n  this.on('test end', function(test){ self.checkGlobals(test); });\n  this.on('hook end', function(hook){ self.checkGlobals(hook); });\n  this.grep(/.*/);\n  this.globals(this.globalProps().concat(['errno']));\n}\n\n/**\n * Wrapper for setImmediate, process.nextTick, or browser polyfill.\n *\n * @param {Function} fn\n * @api private\n */\n\nRunner.immediately = global.setImmediate || process.nextTick;\n\n/**\n * Inherit from `EventEmitter.prototype`.\n */\n\nfunction F(){};\nF.prototype = EventEmitter.prototype;\nRunner.prototype = new F;\nRunner.prototype.constructor = Runner;\n\n\n/**\n * Run tests with full titles matching `re`. Updates runner.total\n * with number of tests matched.\n *\n * @param {RegExp} re\n * @param {Boolean} invert\n * @return {Runner} for chaining\n * @api public\n */\n\nRunner.prototype.grep = function(re, invert){\n  debug('grep %s', re);\n  this._grep = re;\n  this._invert = invert;\n  this.total = this.grepTotal(this.suite);\n  return this;\n};\n\n/**\n * Returns the number of tests matching the grep search for the\n * given suite.\n *\n * @param {Suite} suite\n * @return {Number}\n * @api public\n */\n\nRunner.prototype.grepTotal = function(suite) {\n  var self = this;\n  var total = 0;\n\n  suite.eachTest(function(test){\n    var match = self._grep.test(test.fullTitle());\n    if (self._invert) match = !match;\n    if (match) total++;\n  });\n\n  return total;\n};\n\n/**\n * Return a list of global properties.\n *\n * @return {Array}\n * @api private\n */\n\nRunner.prototype.globalProps = function() {\n  var props = utils.keys(global);\n\n  // non-enumerables\n  for (var i = 0; i < globals.length; ++i) {\n    if (~utils.indexOf(props, globals[i])) continue;\n    props.push(globals[i]);\n  }\n\n  return props;\n};\n\n/**\n * Allow the given `arr` of globals.\n *\n * @param {Array} arr\n * @return {Runner} for chaining\n * @api public\n */\n\nRunner.prototype.globals = function(arr){\n  if (0 == arguments.length) return this._globals;\n  debug('globals %j', arr);\n  utils.forEach(arr, function(arr){\n    this._globals.push(arr);\n  }, this);\n  return this;\n};\n\n/**\n * Check for global variable leaks.\n *\n * @api private\n */\n\nRunner.prototype.checkGlobals = function(test){\n  if (this.ignoreLeaks) return;\n  var ok = this._globals;\n  var globals = this.globalProps();\n  var isNode = process.kill;\n  var leaks;\n\n  // check length - 2 ('errno' and 'location' globals)\n  if (isNode && 1 == ok.length - globals.length) return\n  else if (2 == ok.length - globals.length) return;\n\n  leaks = filterLeaks(ok, globals);\n  this._globals = this._globals.concat(leaks);\n\n  if (leaks.length > 1) {\n    this.fail(test, new Error('global leaks detected: ' + leaks.join(', ') + ''));\n  } else if (leaks.length) {\n    this.fail(test, new Error('global leak detected: ' + leaks[0]));\n  }\n};\n\n/**\n * Fail the given `test`.\n *\n * @param {Test} test\n * @param {Error} err\n * @api private\n */\n\nRunner.prototype.fail = function(test, err){\n  ++this.failures;\n  test.state = 'failed';\n\n  if ('string' == typeof err) {\n    err = new Error('the string \"' + err + '\" was thrown, throw an Error :)');\n  }\n\n  this.emit('fail', test, err);\n};\n\n/**\n * Fail the given `hook` with `err`.\n *\n * Hook failures (currently) hard-end due\n * to that fact that a failing hook will\n * surely cause subsequent tests to fail,\n * causing jumbled reporting.\n *\n * @param {Hook} hook\n * @param {Error} err\n * @api private\n */\n\nRunner.prototype.failHook = function(hook, err){\n  this.fail(hook, err);\n  this.emit('end');\n};\n\n/**\n * Run hook `name` callbacks and then invoke `fn()`.\n *\n * @param {String} name\n * @param {Function} function\n * @api private\n */\n\nRunner.prototype.hook = function(name, fn){\n  var suite = this.suite\n    , hooks = suite['_' + name]\n    , self = this\n    , timer;\n\n  function next(i) {\n    var hook = hooks[i];\n    if (!hook) return fn();\n    self.currentRunnable = hook;\n\n    self.emit('hook', hook);\n\n    hook.on('error', function(err){\n      self.failHook(hook, err);\n    });\n\n    hook.run(function(err){\n      hook.removeAllListeners('error');\n      var testError = hook.error();\n      if (testError) self.fail(self.test, testError);\n      if (err) return self.failHook(hook, err);\n      self.emit('hook end', hook);\n      next(++i);\n    });\n  }\n\n  Runner.immediately(function(){\n    next(0);\n  });\n};\n\n/**\n * Run hook `name` for the given array of `suites`\n * in order, and callback `fn(err)`.\n *\n * @param {String} name\n * @param {Array} suites\n * @param {Function} fn\n * @api private\n */\n\nRunner.prototype.hooks = function(name, suites, fn){\n  var self = this\n    , orig = this.suite;\n\n  function next(suite) {\n    self.suite = suite;\n\n    if (!suite) {\n      self.suite = orig;\n      return fn();\n    }\n\n    self.hook(name, function(err){\n      if (err) {\n        self.suite = orig;\n        return fn(err);\n      }\n\n      next(suites.pop());\n    });\n  }\n\n  next(suites.pop());\n};\n\n/**\n * Run hooks from the top level down.\n *\n * @param {String} name\n * @param {Function} fn\n * @api private\n */\n\nRunner.prototype.hookUp = function(name, fn){\n  var suites = [this.suite].concat(this.parents()).reverse();\n  this.hooks(name, suites, fn);\n};\n\n/**\n * Run hooks from the bottom up.\n *\n * @param {String} name\n * @param {Function} fn\n * @api private\n */\n\nRunner.prototype.hookDown = function(name, fn){\n  var suites = [this.suite].concat(this.parents());\n  this.hooks(name, suites, fn);\n};\n\n/**\n * Return an array of parent Suites from\n * closest to furthest.\n *\n * @return {Array}\n * @api private\n */\n\nRunner.prototype.parents = function(){\n  var suite = this.suite\n    , suites = [];\n  while (suite = suite.parent) suites.push(suite);\n  return suites;\n};\n\n/**\n * Run the current test and callback `fn(err)`.\n *\n * @param {Function} fn\n * @api private\n */\n\nRunner.prototype.runTest = function(fn){\n  var test = this.test\n    , self = this;\n\n  if (this.asyncOnly) test.asyncOnly = true;\n\n  try {\n    test.on('error', function(err){\n      self.fail(test, err);\n    });\n    test.run(fn);\n  } catch (err) {\n    fn(err);\n  }\n};\n\n/**\n * Run tests in the given `suite` and invoke\n * the callback `fn()` when complete.\n *\n * @param {Suite} suite\n * @param {Function} fn\n * @api private\n */\n\nRunner.prototype.runTests = function(suite, fn){\n  var self = this\n    , tests = suite.tests.slice()\n    , test;\n\n  function next(err) {\n    // if we bail after first err\n    if (self.failures && suite._bail) return fn();\n\n    // next test\n    test = tests.shift();\n\n    // all done\n    if (!test) return fn();\n\n    // grep\n    var match = self._grep.test(test.fullTitle());\n    if (self._invert) match = !match;\n    if (!match) return next();\n\n    // pending\n    if (test.pending) {\n      self.emit('pending', test);\n      self.emit('test end', test);\n      return next();\n    }\n\n    // execute test and hook(s)\n    self.emit('test', self.test = test);\n    self.hookDown('beforeEach', function(){\n      self.currentRunnable = self.test;\n      self.runTest(function(err){\n        test = self.test;\n\n        if (err) {\n          self.fail(test, err);\n          self.emit('test end', test);\n          return self.hookUp('afterEach', next);\n        }\n\n        test.state = 'passed';\n        self.emit('pass', test);\n        self.emit('test end', test);\n        self.hookUp('afterEach', next);\n      });\n    });\n  }\n\n  this.next = next;\n  next();\n};\n\n/**\n * Run the given `suite` and invoke the\n * callback `fn()` when complete.\n *\n * @param {Suite} suite\n * @param {Function} fn\n * @api private\n */\n\nRunner.prototype.runSuite = function(suite, fn){\n  var total = this.grepTotal(suite)\n    , self = this\n    , i = 0;\n\n  debug('run suite %s', suite.fullTitle());\n\n  if (!total) return fn();\n\n  this.emit('suite', this.suite = suite);\n\n  function next() {\n    var curr = suite.suites[i++];\n    if (!curr) return done();\n    self.runSuite(curr, next);\n  }\n\n  function done() {\n    self.suite = suite;\n    self.hook('afterAll', function(){\n      self.emit('suite end', suite);\n      fn();\n    });\n  }\n\n  this.hook('beforeAll', function(){\n    self.runTests(suite, next);\n  });\n};\n\n/**\n * Handle uncaught exceptions.\n *\n * @param {Error} err\n * @api private\n */\n\nRunner.prototype.uncaught = function(err){\n  debug('uncaught exception %s', err.message);\n  var runnable = this.currentRunnable;\n  if (!runnable || 'failed' == runnable.state) return;\n  runnable.clearTimeout();\n  err.uncaught = true;\n  this.fail(runnable, err);\n\n  // recover from test\n  if ('test' == runnable.type) {\n    this.emit('test end', runnable);\n    this.hookUp('afterEach', this.next);\n    return;\n  }\n\n  // bail on hooks\n  this.emit('end');\n};\n\n/**\n * Run the root suite and invoke `fn(failures)`\n * on completion.\n *\n * @param {Function} fn\n * @return {Runner} for chaining\n * @api public\n */\n\nRunner.prototype.run = function(fn){\n  var self = this\n    , fn = fn || function(){};\n\n  function uncaught(err){\n    self.uncaught(err);\n  }\n\n  debug('start');\n\n  // callback\n  this.on('end', function(){\n    debug('end');\n    process.removeListener('uncaughtException', uncaught);\n    fn(self.failures);\n  });\n\n  // run suites\n  this.emit('start');\n  this.runSuite(this.suite, function(){\n    debug('finished running');\n    self.emit('end');\n  });\n\n  // uncaught exception\n  process.on('uncaughtException', uncaught);\n\n  return this;\n};\n\n/**\n * Filter leaks with the given globals flagged as `ok`.\n *\n * @param {Array} ok\n * @param {Array} globals\n * @return {Array}\n * @api private\n */\n\nfunction filterLeaks(ok, globals) {\n  return filter(globals, function(key){\n    // Firefox and Chrome exposes iframes as index inside the window object\n    if (/^d+/.test(key)) return false;\n    var matched = filter(ok, function(ok){\n      if (~ok.indexOf('*')) return 0 == key.indexOf(ok.split('*')[0]);\n      // Opera and IE expose global variables for HTML element IDs (issue #243)\n      if (/^mocha-/.test(key)) return true;\n      return key == ok;\n    });\n    return matched.length == 0 && (!global.navigator || 'onerror' !== key);\n  });\n}\n\n}); // module: runner.js\n\nrequire.register(\"suite.js\", function(module, exports, require){\n\n/**\n * Module dependencies.\n */\n\nvar EventEmitter = require('browser/events').EventEmitter\n  , debug = require('browser/debug')('mocha:suite')\n  , milliseconds = require('./ms')\n  , utils = require('./utils')\n  , Hook = require('./hook');\n\n/**\n * Expose `Suite`.\n */\n\nexports = module.exports = Suite;\n\n/**\n * Create a new `Suite` with the given `title`\n * and parent `Suite`. When a suite with the\n * same title is already present, that suite\n * is returned to provide nicer reporter\n * and more flexible meta-testing.\n *\n * @param {Suite} parent\n * @param {String} title\n * @return {Suite}\n * @api public\n */\n\nexports.create = function(parent, title){\n  var suite = new Suite(title, parent.ctx);\n  suite.parent = parent;\n  if (parent.pending) suite.pending = true;\n  title = suite.fullTitle();\n  parent.addSuite(suite);\n  return suite;\n};\n\n/**\n * Initialize a new `Suite` with the given\n * `title` and `ctx`.\n *\n * @param {String} title\n * @param {Context} ctx\n * @api private\n */\n\nfunction Suite(title, ctx) {\n  this.title = title;\n  this.ctx = ctx;\n  this.suites = [];\n  this.tests = [];\n  this.pending = false;\n  this._beforeEach = [];\n  this._beforeAll = [];\n  this._afterEach = [];\n  this._afterAll = [];\n  this.root = !title;\n  this._timeout = 2000;\n  this._slow = 75;\n  this._bail = false;\n}\n\n/**\n * Inherit from `EventEmitter.prototype`.\n */\n\nfunction F(){};\nF.prototype = EventEmitter.prototype;\nSuite.prototype = new F;\nSuite.prototype.constructor = Suite;\n\n\n/**\n * Return a clone of this `Suite`.\n *\n * @return {Suite}\n * @api private\n */\n\nSuite.prototype.clone = function(){\n  var suite = new Suite(this.title);\n  debug('clone');\n  suite.ctx = this.ctx;\n  suite.timeout(this.timeout());\n  suite.slow(this.slow());\n  suite.bail(this.bail());\n  return suite;\n};\n\n/**\n * Set timeout `ms` or short-hand such as \"2s\".\n *\n * @param {Number|String} ms\n * @return {Suite|Number} for chaining\n * @api private\n */\n\nSuite.prototype.timeout = function(ms){\n  if (0 == arguments.length) return this._timeout;\n  if ('string' == typeof ms) ms = milliseconds(ms);\n  debug('timeout %d', ms);\n  this._timeout = parseInt(ms, 10);\n  return this;\n};\n\n/**\n * Set slow `ms` or short-hand such as \"2s\".\n *\n * @param {Number|String} ms\n * @return {Suite|Number} for chaining\n * @api private\n */\n\nSuite.prototype.slow = function(ms){\n  if (0 === arguments.length) return this._slow;\n  if ('string' == typeof ms) ms = milliseconds(ms);\n  debug('slow %d', ms);\n  this._slow = ms;\n  return this;\n};\n\n/**\n * Sets whether to bail after first error.\n *\n * @parma {Boolean} bail\n * @return {Suite|Number} for chaining\n * @api private\n */\n\nSuite.prototype.bail = function(bail){\n  if (0 == arguments.length) return this._bail;\n  debug('bail %s', bail);\n  this._bail = bail;\n  return this;\n};\n\n/**\n * Run `fn(test[, done])` before running tests.\n *\n * @param {Function} fn\n * @return {Suite} for chaining\n * @api private\n */\n\nSuite.prototype.beforeAll = function(fn){\n  if (this.pending) return this;\n  var hook = new Hook('\"before all\" hook', fn);\n  hook.parent = this;\n  hook.timeout(this.timeout());\n  hook.slow(this.slow());\n  hook.ctx = this.ctx;\n  this._beforeAll.push(hook);\n  this.emit('beforeAll', hook);\n  return this;\n};\n\n/**\n * Run `fn(test[, done])` after running tests.\n *\n * @param {Function} fn\n * @return {Suite} for chaining\n * @api private\n */\n\nSuite.prototype.afterAll = function(fn){\n  if (this.pending) return this;\n  var hook = new Hook('\"after all\" hook', fn);\n  hook.parent = this;\n  hook.timeout(this.timeout());\n  hook.slow(this.slow());\n  hook.ctx = this.ctx;\n  this._afterAll.push(hook);\n  this.emit('afterAll', hook);\n  return this;\n};\n\n/**\n * Run `fn(test[, done])` before each test case.\n *\n * @param {Function} fn\n * @return {Suite} for chaining\n * @api private\n */\n\nSuite.prototype.beforeEach = function(fn){\n  if (this.pending) return this;\n  var hook = new Hook('\"before each\" hook', fn);\n  hook.parent = this;\n  hook.timeout(this.timeout());\n  hook.slow(this.slow());\n  hook.ctx = this.ctx;\n  this._beforeEach.push(hook);\n  this.emit('beforeEach', hook);\n  return this;\n};\n\n/**\n * Run `fn(test[, done])` after each test case.\n *\n * @param {Function} fn\n * @return {Suite} for chaining\n * @api private\n */\n\nSuite.prototype.afterEach = function(fn){\n  if (this.pending) return this;\n  var hook = new Hook('\"after each\" hook', fn);\n  hook.parent = this;\n  hook.timeout(this.timeout());\n  hook.slow(this.slow());\n  hook.ctx = this.ctx;\n  this._afterEach.push(hook);\n  this.emit('afterEach', hook);\n  return this;\n};\n\n/**\n * Add a test `suite`.\n *\n * @param {Suite} suite\n * @return {Suite} for chaining\n * @api private\n */\n\nSuite.prototype.addSuite = function(suite){\n  suite.parent = this;\n  suite.timeout(this.timeout());\n  suite.slow(this.slow());\n  suite.bail(this.bail());\n  this.suites.push(suite);\n  this.emit('suite', suite);\n  return this;\n};\n\n/**\n * Add a `test` to this suite.\n *\n * @param {Test} test\n * @return {Suite} for chaining\n * @api private\n */\n\nSuite.prototype.addTest = function(test){\n  test.parent = this;\n  test.timeout(this.timeout());\n  test.slow(this.slow());\n  test.ctx = this.ctx;\n  this.tests.push(test);\n  this.emit('test', test);\n  return this;\n};\n\n/**\n * Return the full title generated by recursively\n * concatenating the parent's full title.\n *\n * @return {String}\n * @api public\n */\n\nSuite.prototype.fullTitle = function(){\n  if (this.parent) {\n    var full = this.parent.fullTitle();\n    if (full) return full + ' ' + this.title;\n  }\n  return this.title;\n};\n\n/**\n * Return the total number of tests.\n *\n * @return {Number}\n * @api public\n */\n\nSuite.prototype.total = function(){\n  return utils.reduce(this.suites, function(sum, suite){\n    return sum + suite.total();\n  }, 0) + this.tests.length;\n};\n\n/**\n * Iterates through each suite recursively to find\n * all tests. Applies a function in the format\n * `fn(test)`.\n *\n * @param {Function} fn\n * @return {Suite}\n * @api private\n */\n\nSuite.prototype.eachTest = function(fn){\n  utils.forEach(this.tests, fn);\n  utils.forEach(this.suites, function(suite){\n    suite.eachTest(fn);\n  });\n  return this;\n};\n\n}); // module: suite.js\n\nrequire.register(\"test.js\", function(module, exports, require){\n\n/**\n * Module dependencies.\n */\n\nvar Runnable = require('./runnable');\n\n/**\n * Expose `Test`.\n */\n\nmodule.exports = Test;\n\n/**\n * Initialize a new `Test` with the given `title` and callback `fn`.\n *\n * @param {String} title\n * @param {Function} fn\n * @api private\n */\n\nfunction Test(title, fn) {\n  Runnable.call(this, title, fn);\n  this.pending = !fn;\n  this.type = 'test';\n}\n\n/**\n * Inherit from `Runnable.prototype`.\n */\n\nfunction F(){};\nF.prototype = Runnable.prototype;\nTest.prototype = new F;\nTest.prototype.constructor = Test;\n\n\n}); // module: test.js\n\nrequire.register(\"utils.js\", function(module, exports, require){\n\n/**\n * Module dependencies.\n */\n\nvar fs = require('browser/fs')\n  , path = require('browser/path')\n  , join = path.join\n  , debug = require('browser/debug')('mocha:watch');\n\n/**\n * Ignored directories.\n */\n\nvar ignore = ['node_modules', '.git'];\n\n/**\n * Escape special characters in the given string of html.\n *\n * @param  {String} html\n * @return {String}\n * @api private\n */\n\nexports.escape = function(html){\n  return String(html)\n    .replace(/&/g, '&amp;')\n    .replace(/\"/g, '&quot;')\n    .replace(/</g, '&lt;')\n    .replace(/>/g, '&gt;');\n};\n\n/**\n * Array#forEach (<=IE8)\n *\n * @param {Array} array\n * @param {Function} fn\n * @param {Object} scope\n * @api private\n */\n\nexports.forEach = function(arr, fn, scope){\n  for (var i = 0, l = arr.length; i < l; i++)\n    fn.call(scope, arr[i], i);\n};\n\n/**\n * Array#indexOf (<=IE8)\n *\n * @parma {Array} arr\n * @param {Object} obj to find index of\n * @param {Number} start\n * @api private\n */\n\nexports.indexOf = function(arr, obj, start){\n  for (var i = start || 0, l = arr.length; i < l; i++) {\n    if (arr[i] === obj)\n      return i;\n  }\n  return -1;\n};\n\n/**\n * Array#reduce (<=IE8)\n *\n * @param {Array} array\n * @param {Function} fn\n * @param {Object} initial value\n * @api private\n */\n\nexports.reduce = function(arr, fn, val){\n  var rval = val;\n\n  for (var i = 0, l = arr.length; i < l; i++) {\n    rval = fn(rval, arr[i], i, arr);\n  }\n\n  return rval;\n};\n\n/**\n * Array#filter (<=IE8)\n *\n * @param {Array} array\n * @param {Function} fn\n * @api private\n */\n\nexports.filter = function(arr, fn){\n  var ret = [];\n\n  for (var i = 0, l = arr.length; i < l; i++) {\n    var val = arr[i];\n    if (fn(val, i, arr)) ret.push(val);\n  }\n\n  return ret;\n};\n\n/**\n * Object.keys (<=IE8)\n *\n * @param {Object} obj\n * @return {Array} keys\n * @api private\n */\n\nexports.keys = Object.keys || function(obj) {\n  var keys = []\n    , has = Object.prototype.hasOwnProperty // for `window` on <=IE8\n\n  for (var key in obj) {\n    if (has.call(obj, key)) {\n      keys.push(key);\n    }\n  }\n\n  return keys;\n};\n\n/**\n * Watch the given `files` for changes\n * and invoke `fn(file)` on modification.\n *\n * @param {Array} files\n * @param {Function} fn\n * @api private\n */\n\nexports.watch = function(files, fn){\n  var options = { interval: 100 };\n  files.forEach(function(file){\n    debug('file %s', file);\n    fs.watchFile(file, options, function(curr, prev){\n      if (prev.mtime < curr.mtime) fn(file);\n    });\n  });\n};\n\n/**\n * Ignored files.\n */\n\nfunction ignored(path){\n  return !~ignore.indexOf(path);\n}\n\n/**\n * Lookup files in the given `dir`.\n *\n * @return {Array}\n * @api private\n */\n\nexports.files = function(dir, ret){\n  ret = ret || [];\n\n  fs.readdirSync(dir)\n  .filter(ignored)\n  .forEach(function(path){\n    path = join(dir, path);\n    if (fs.statSync(path).isDirectory()) {\n      exports.files(path, ret);\n    } else if (path.match(/\\.(js|coffee)$/)) {\n      ret.push(path);\n    }\n  });\n\n  return ret;\n};\n\n/**\n * Compute a slug from the given `str`.\n *\n * @param {String} str\n * @return {String}\n * @api private\n */\n\nexports.slug = function(str){\n  return str\n    .toLowerCase()\n    .replace(/ +/g, '-')\n    .replace(/[^-\\w]/g, '');\n};\n\n/**\n * Strip the function definition from `str`,\n * and re-indent for pre whitespace.\n */\n\nexports.clean = function(str) {\n  str = str\n    .replace(/^function *\\(.*\\) *{/, '')\n    .replace(/\\s+\\}$/, '');\n\n  var spaces = str.match(/^\\n?( *)/)[1].length\n    , re = new RegExp('^ {' + spaces + '}', 'gm');\n\n  str = str.replace(re, '');\n\n  return exports.trim(str);\n};\n\n/**\n * Escape regular expression characters in `str`.\n *\n * @param {String} str\n * @return {String}\n * @api private\n */\n\nexports.escapeRegexp = function(str){\n  return str.replace(/[-\\\\^$*+?.()|[\\]{}]/g, \"\\\\$&\");\n};\n\n/**\n * Trim the given `str`.\n *\n * @param {String} str\n * @return {String}\n * @api private\n */\n\nexports.trim = function(str){\n  return str.replace(/^\\s+|\\s+$/g, '');\n};\n\n/**\n * Parse the given `qs`.\n *\n * @param {String} qs\n * @return {Object}\n * @api private\n */\n\nexports.parseQuery = function(qs){\n  return exports.reduce(qs.replace('?', '').split('&'), function(obj, pair){\n    var i = pair.indexOf('=')\n      , key = pair.slice(0, i)\n      , val = pair.slice(++i);\n\n    obj[key] = decodeURIComponent(val);\n    return obj;\n  }, {});\n};\n\n/**\n * Highlight the given string of `js`.\n *\n * @param {String} js\n * @return {String}\n * @api private\n */\n\nfunction highlight(js) {\n  return js\n    .replace(/</g, '&lt;')\n    .replace(/>/g, '&gt;')\n    .replace(/\\/\\/(.*)/gm, '<span class=\"comment\">//$1</span>')\n    .replace(/('.*?')/gm, '<span class=\"string\">$1</span>')\n    .replace(/(\\d+\\.\\d+)/gm, '<span class=\"number\">$1</span>')\n    .replace(/(\\d+)/gm, '<span class=\"number\">$1</span>')\n    .replace(/\\bnew *(\\w+)/gm, '<span class=\"keyword\">new</span> <span class=\"init\">$1</span>')\n    .replace(/\\b(function|new|throw|return|var|if|else)\\b/gm, '<span class=\"keyword\">$1</span>')\n}\n\n/**\n * Highlight the contents of tag `name`.\n *\n * @param {String} name\n * @api private\n */\n\nexports.highlightTags = function(name) {\n  var code = document.getElementsByTagName(name);\n  for (var i = 0, len = code.length; i < len; ++i) {\n    code[i].innerHTML = highlight(code[i].innerHTML);\n  }\n};\n\n}); // module: utils.js\n\n/**\n * Save timer references to avoid Sinon interfering (see GH-237).\n */\n\nvar Date = window.Date;\nvar setTimeout = window.setTimeout;\nvar setInterval = window.setInterval;\nvar clearTimeout = window.clearTimeout;\nvar clearInterval = window.clearInterval;\n\n/**\n * Node shims.\n *\n * These are meant only to allow\n * mocha.js to run untouched, not\n * to allow running node code in\n * the browser.\n */\n\nvar process = {};\nprocess.exit = function(status){};\nprocess.stdout = {};\nglobal = window;\n\n/**\n * Remove uncaughtException listener.\n */\n\nprocess.removeListener = function(e){\n  if ('uncaughtException' == e) {\n    window.onerror = null;\n  }\n};\n\n/**\n * Implements uncaughtException listener.\n */\n\nprocess.on = function(e, fn){\n  if ('uncaughtException' == e) {\n    window.onerror = function(err, url, line){\n      fn(new Error(err + ' (' + url + ':' + line + ')'));\n    };\n  }\n};\n\n/**\n * Expose mocha.\n */\n\nvar Mocha = window.Mocha = require('mocha'),\n    mocha = window.mocha = new Mocha({ reporter: 'html' });\n\nvar immediateQueue = []\n  , immediateTimeout;\n\nfunction timeslice() {\n  var immediateStart = new Date().getTime();\n  while (immediateQueue.length && (new Date().getTime() - immediateStart) < 100) {\n    immediateQueue.shift()();\n  }\n  if (immediateQueue.length) {\n    immediateTimeout = setTimeout(timeslice, 0);\n  } else {\n    immediateTimeout = null;\n  }\n}\n\n/**\n * High-performance override of Runner.immediately.\n */\n\nMocha.Runner.immediately = function(callback) {\n  immediateQueue.push(callback);\n  if (!immediateTimeout) {\n    immediateTimeout = setTimeout(timeslice, 0);\n  }\n};\n\n/**\n * Override ui to ensure that the ui functions are initialized.\n * Normally this would happen in Mocha.prototype.loadFiles.\n */\n\nmocha.ui = function(ui){\n  Mocha.prototype.ui.call(this, ui);\n  this.suite.emit('pre-require', window, null, this);\n  return this;\n};\n\n/**\n * Setup mocha with the given setting options.\n */\n\nmocha.setup = function(opts){\n  if ('string' == typeof opts) opts = { ui: opts };\n  for (var opt in opts) this[opt](opts[opt]);\n  return this;\n};\n\n/**\n * Run mocha, returning the Runner.\n */\n\nmocha.run = function(fn){\n  var options = mocha.options;\n  mocha.globals('location');\n\n  var query = Mocha.utils.parseQuery(window.location.search || '');\n  if (query.grep) mocha.grep(query.grep);\n  if (query.invert) mocha.invert();\n\n  return Mocha.prototype.run.call(mocha, function(){\n    Mocha.utils.highlightTags('code');\n    if (fn) fn();\n  });\n};\n})();"
  },
  {
    "path": "test/static/third-party/purl/purl.js",
    "content": "/*\n * Purl (A JavaScript URL parser) v2.3.1\n * Developed and maintained by Mark Perkins, mark@allmarkedup.com\n * Source repository: https://github.com/allmarkedup/jQuery-URL-Parser\n * Licensed under an MIT-style license. See https://github.com/allmarkedup/jQuery-URL-Parser/blob/master/LICENSE for details.\n */\n\n;(function(factory) {\n    if (typeof define === 'function' && define.amd) {\n        define(factory);\n    } else {\n        window.purl = factory();\n    }\n})(function() {\n\n    var tag2attr = {\n            a       : 'href',\n            img     : 'src',\n            form    : 'action',\n            base    : 'href',\n            script  : 'src',\n            iframe  : 'src',\n            link    : 'href'\n        },\n\n        key = ['source', 'protocol', 'authority', 'userInfo', 'user', 'password', 'host', 'port', 'relative', 'path', 'directory', 'file', 'query', 'fragment'], // keys available to query\n\n        aliases = { 'anchor' : 'fragment' }, // aliases for backwards compatability\n\n        parser = {\n            strict : /^(?:([^:\\/?#]+):)?(?:\\/\\/((?:(([^:@]*):?([^:@]*))?@)?([^:\\/?#]*)(?::(\\d*))?))?((((?:[^?#\\/]*\\/)*)([^?#]*))(?:\\?([^#]*))?(?:#(.*))?)/,  //less intuitive, more accurate to the specs\n            loose :  /^(?:(?![^:@]+:[^:@\\/]*@)([^:\\/?#.]+):)?(?:\\/\\/)?((?:(([^:@]*):?([^:@]*))?@)?([^:\\/?#]*)(?::(\\d*))?)(((\\/(?:[^?#](?![^?#\\/]*\\.[^?#\\/.]+(?:[?#]|$)))*\\/?)?([^?#\\/]*))(?:\\?([^#]*))?(?:#(.*))?)/ // more intuitive, fails on relative paths and deviates from specs\n        },\n\n        isint = /^[0-9]+$/;\n\n    function parseUri( url, strictMode ) {\n        var str = decodeURI( url ),\n        res   = parser[ strictMode || false ? 'strict' : 'loose' ].exec( str ),\n        uri = { attr : {}, param : {}, seg : {} },\n        i   = 14;\n\n        while ( i-- ) {\n            uri.attr[ key[i] ] = res[i] || '';\n        }\n\n        // build query and fragment parameters\n        uri.param['query'] = parseString(uri.attr['query']);\n        uri.param['fragment'] = parseString(uri.attr['fragment']);\n\n        // split path and fragment into segments\n        uri.seg['path'] = uri.attr.path.replace(/^\\/+|\\/+$/g,'').split('/');\n        uri.seg['fragment'] = uri.attr.fragment.replace(/^\\/+|\\/+$/g,'').split('/');\n\n        // compile a 'base' domain attribute\n        uri.attr['base'] = uri.attr.host ? (uri.attr.protocol ?  uri.attr.protocol+'://'+uri.attr.host : uri.attr.host) + (uri.attr.port ? ':'+uri.attr.port : '') : '';\n\n        return uri;\n    }\n\n    function getAttrName( elm ) {\n        var tn = elm.tagName;\n        if ( typeof tn !== 'undefined' ) return tag2attr[tn.toLowerCase()];\n        return tn;\n    }\n\n    function promote(parent, key) {\n        if (parent[key].length === 0) return parent[key] = {};\n        var t = {};\n        for (var i in parent[key]) t[i] = parent[key][i];\n        parent[key] = t;\n        return t;\n    }\n\n    function parse(parts, parent, key, val) {\n        var part = parts.shift();\n        if (!part) {\n            if (isArray(parent[key])) {\n                parent[key].push(val);\n            } else if ('object' == typeof parent[key]) {\n                parent[key] = val;\n            } else if ('undefined' == typeof parent[key]) {\n                parent[key] = val;\n            } else {\n                parent[key] = [parent[key], val];\n            }\n        } else {\n            var obj = parent[key] = parent[key] || [];\n            if (']' == part) {\n                if (isArray(obj)) {\n                    if ('' !== val) obj.push(val);\n                } else if ('object' == typeof obj) {\n                    obj[keys(obj).length] = val;\n                } else {\n                    obj = parent[key] = [parent[key], val];\n                }\n            } else if (~part.indexOf(']')) {\n                part = part.substr(0, part.length - 1);\n                if (!isint.test(part) && isArray(obj)) obj = promote(parent, key);\n                parse(parts, obj, part, val);\n                // key\n            } else {\n                if (!isint.test(part) && isArray(obj)) obj = promote(parent, key);\n                parse(parts, obj, part, val);\n            }\n        }\n    }\n\n    function merge(parent, key, val) {\n        if (~key.indexOf(']')) {\n            var parts = key.split('[');\n            parse(parts, parent, 'base', val);\n        } else {\n            if (!isint.test(key) && isArray(parent.base)) {\n                var t = {};\n                for (var k in parent.base) t[k] = parent.base[k];\n                parent.base = t;\n            }\n            if (key !== '') {\n                set(parent.base, key, val);\n            }\n        }\n        return parent;\n    }\n\n    function parseString(str) {\n        return reduce(String(str).split(/&|;/), function(ret, pair) {\n            try {\n                pair = decodeURIComponent(pair.replace(/\\+/g, ' '));\n            } catch(e) {\n                // ignore\n            }\n            var eql = pair.indexOf('='),\n                brace = lastBraceInKey(pair),\n                key = pair.substr(0, brace || eql),\n                val = pair.substr(brace || eql, pair.length);\n\n            val = val.substr(val.indexOf('=') + 1, val.length);\n\n            if (key === '') {\n                key = pair;\n                val = '';\n            }\n\n            return merge(ret, key, val);\n        }, { base: {} }).base;\n    }\n\n    function set(obj, key, val) {\n        var v = obj[key];\n        if (typeof v === 'undefined') {\n            obj[key] = val;\n        } else if (isArray(v)) {\n            v.push(val);\n        } else {\n            obj[key] = [v, val];\n        }\n    }\n\n    function lastBraceInKey(str) {\n        var len = str.length,\n            brace,\n            c;\n        for (var i = 0; i < len; ++i) {\n            c = str[i];\n            if (']' == c) brace = false;\n            if ('[' == c) brace = true;\n            if ('=' == c && !brace) return i;\n        }\n    }\n\n    function reduce(obj, accumulator){\n        var i = 0,\n            l = obj.length >> 0,\n            curr = arguments[2];\n        while (i < l) {\n            if (i in obj) curr = accumulator.call(undefined, curr, obj[i], i, obj);\n            ++i;\n        }\n        return curr;\n    }\n\n    function isArray(vArg) {\n        return Object.prototype.toString.call(vArg) === \"[object Array]\";\n    }\n\n    function keys(obj) {\n        var key_array = [];\n        for ( var prop in obj ) {\n            if ( obj.hasOwnProperty(prop) ) key_array.push(prop);\n        }\n        return key_array;\n    }\n\n    function purl( url, strictMode ) {\n        if ( arguments.length === 1 && url === true ) {\n            strictMode = true;\n            url = undefined;\n        }\n        strictMode = strictMode || false;\n        url = url || window.location.toString();\n\n        return {\n\n            data : parseUri(url, strictMode),\n\n            // get various attributes from the URI\n            attr : function( attr ) {\n                attr = aliases[attr] || attr;\n                return typeof attr !== 'undefined' ? this.data.attr[attr] : this.data.attr;\n            },\n\n            // return query string parameters\n            param : function( param ) {\n                return typeof param !== 'undefined' ? this.data.param.query[param] : this.data.param.query;\n            },\n\n            // return fragment parameters\n            fparam : function( param ) {\n                return typeof param !== 'undefined' ? this.data.param.fragment[param] : this.data.param.fragment;\n            },\n\n            // return path segments\n            segment : function( seg ) {\n                if ( typeof seg === 'undefined' ) {\n                    return this.data.seg.path;\n                } else {\n                    seg = seg < 0 ? this.data.seg.path.length + seg : seg - 1; // negative segments count from the end\n                    return this.data.seg.path[seg];\n                }\n            },\n\n            // return fragment segments\n            fsegment : function( seg ) {\n                if ( typeof seg === 'undefined' ) {\n                    return this.data.seg.fragment;\n                } else {\n                    seg = seg < 0 ? this.data.seg.fragment.length + seg : seg - 1; // negative segments count from the end\n                    return this.data.seg.fragment[seg];\n                }\n            }\n\n        };\n\n    }\n    \n    purl.jQuery = function($){\n        if ($ != null) {\n            $.fn.url = function( strictMode ) {\n                var url = '';\n                if ( this.length ) {\n                    url = $(this).attr( getAttrName(this[0]) ) || '';\n                }\n                return purl( url, strictMode );\n            };\n\n            $.url = purl;\n        }\n    };\n\n    purl.jQuery(window.jQuery);\n\n    return purl;\n\n});\n"
  },
  {
    "path": "test/static/third-party/q/q-1.0.1.js",
    "content": "// vim:ts=4:sts=4:sw=4:\n/*!\n *\n * Copyright 2009-2012 Kris Kowal under the terms of the MIT\n * license found at http://github.com/kriskowal/q/raw/master/LICENSE\n *\n * With parts by Tyler Close\n * Copyright 2007-2009 Tyler Close under the terms of the MIT X license found\n * at http://www.opensource.org/licenses/mit-license.html\n * Forked at ref_send.js version: 2009-05-11\n *\n * With parts by Mark Miller\n * Copyright (C) 2011 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\n(function (definition) {\n    // Turn off strict mode for this function so we can assign to global.Q\n    /* jshint strict: false */\n\n    // This file will function properly as a <script> tag, or a module\n    // using CommonJS and NodeJS or RequireJS module formats.  In\n    // Common/Node/RequireJS, the module exports the Q API and when\n    // executed as a simple <script>, it creates a Q global instead.\n\n    // Montage Require\n    if (typeof bootstrap === \"function\") {\n        bootstrap(\"promise\", definition);\n\n    // CommonJS\n    } else if (typeof exports === \"object\") {\n        module.exports = definition();\n\n    // RequireJS\n    } else if (typeof define === \"function\" && define.amd) {\n        define(definition);\n\n    // SES (Secure EcmaScript)\n    } else if (typeof ses !== \"undefined\") {\n        if (!ses.ok()) {\n            return;\n        } else {\n            ses.makeQ = definition;\n        }\n\n    // <script>\n    } else {\n        Q = definition();\n    }\n\n})(function () {\n\"use strict\";\n\nvar hasStacks = false;\ntry {\n    throw new Error();\n} catch (e) {\n    hasStacks = !!e.stack;\n}\n\n// All code after this point will be filtered from stack traces reported\n// by Q.\nvar qStartingLine = captureLine();\nvar qFileName;\n\n// shims\n\n// used for fallback in \"allResolved\"\nvar noop = function () {};\n\n// Use the fastest possible means to execute a task in a future turn\n// of the event loop.\nvar nextTick =(function () {\n    // linked list of tasks (single, with head node)\n    var head = {task: void 0, next: null};\n    var tail = head;\n    var flushing = false;\n    var requestTick = void 0;\n    var isNodeJS = false;\n\n    function flush() {\n        /* jshint loopfunc: true */\n\n        while (head.next) {\n            head = head.next;\n            var task = head.task;\n            head.task = void 0;\n            var domain = head.domain;\n\n            if (domain) {\n                head.domain = void 0;\n                domain.enter();\n            }\n\n            try {\n                task();\n\n            } catch (e) {\n                if (isNodeJS) {\n                    // In node, uncaught exceptions are considered fatal errors.\n                    // Re-throw them synchronously to interrupt flushing!\n\n                    // Ensure continuation if the uncaught exception is suppressed\n                    // listening \"uncaughtException\" events (as domains does).\n                    // Continue in next event to avoid tick recursion.\n                    if (domain) {\n                        domain.exit();\n                    }\n                    setTimeout(flush, 0);\n                    if (domain) {\n                        domain.enter();\n                    }\n\n                    throw e;\n\n                } else {\n                    // In browsers, uncaught exceptions are not fatal.\n                    // Re-throw them asynchronously to avoid slow-downs.\n                    setTimeout(function() {\n                       throw e;\n                    }, 0);\n                }\n            }\n\n            if (domain) {\n                domain.exit();\n            }\n        }\n\n        flushing = false;\n    }\n\n    nextTick = function (task) {\n        tail = tail.next = {\n            task: task,\n            domain: isNodeJS && process.domain,\n            next: null\n        };\n\n        if (!flushing) {\n            flushing = true;\n            requestTick();\n        }\n    };\n\n    if (typeof process !== \"undefined\" && process.nextTick) {\n        // Node.js before 0.9. Note that some fake-Node environments, like the\n        // Mocha test runner, introduce a `process` global without a `nextTick`.\n        isNodeJS = true;\n\n        requestTick = function () {\n            process.nextTick(flush);\n        };\n\n    } else if (typeof setImmediate === \"function\") {\n        // In IE10, Node.js 0.9+, or https://github.com/NobleJS/setImmediate\n        if (typeof window !== \"undefined\") {\n            requestTick = setImmediate.bind(window, flush);\n        } else {\n            requestTick = function () {\n                setImmediate(flush);\n            };\n        }\n\n    } else if (typeof MessageChannel !== \"undefined\") {\n        // modern browsers\n        // http://www.nonblocking.io/2011/06/windownexttick.html\n        var channel = new MessageChannel();\n        // At least Safari Version 6.0.5 (8536.30.1) intermittently cannot create\n        // working message ports the first time a page loads.\n        channel.port1.onmessage = function () {\n            requestTick = requestPortTick;\n            channel.port1.onmessage = flush;\n            flush();\n        };\n        var requestPortTick = function () {\n            // Opera requires us to provide a message payload, regardless of\n            // whether we use it.\n            channel.port2.postMessage(0);\n        };\n        requestTick = function () {\n            setTimeout(flush, 0);\n            requestPortTick();\n        };\n\n    } else {\n        // old browsers\n        requestTick = function () {\n            setTimeout(flush, 0);\n        };\n    }\n\n    return nextTick;\n})();\n\n// Attempt to make generics safe in the face of downstream\n// modifications.\n// There is no situation where this is necessary.\n// If you need a security guarantee, these primordials need to be\n// deeply frozen anyway, and if you don’t need a security guarantee,\n// this is just plain paranoid.\n// However, this **might** have the nice side-effect of reducing the size of\n// the minified code by reducing x.call() to merely x()\n// See Mark Miller’s explanation of what this does.\n// http://wiki.ecmascript.org/doku.php?id=conventions:safe_meta_programming\nvar call = Function.call;\nfunction uncurryThis(f) {\n    return function () {\n        return call.apply(f, arguments);\n    };\n}\n// This is equivalent, but slower:\n// uncurryThis = Function_bind.bind(Function_bind.call);\n// http://jsperf.com/uncurrythis\n\nvar array_slice = uncurryThis(Array.prototype.slice);\n\nvar array_reduce = uncurryThis(\n    Array.prototype.reduce || function (callback, basis) {\n        var index = 0,\n            length = this.length;\n        // concerning the initial value, if one is not provided\n        if (arguments.length === 1) {\n            // seek to the first value in the array, accounting\n            // for the possibility that is is a sparse array\n            do {\n                if (index in this) {\n                    basis = this[index++];\n                    break;\n                }\n                if (++index >= length) {\n                    throw new TypeError();\n                }\n            } while (1);\n        }\n        // reduce\n        for (; index < length; index++) {\n            // account for the possibility that the array is sparse\n            if (index in this) {\n                basis = callback(basis, this[index], index);\n            }\n        }\n        return basis;\n    }\n);\n\nvar array_indexOf = uncurryThis(\n    Array.prototype.indexOf || function (value) {\n        // not a very good shim, but good enough for our one use of it\n        for (var i = 0; i < this.length; i++) {\n            if (this[i] === value) {\n                return i;\n            }\n        }\n        return -1;\n    }\n);\n\nvar array_map = uncurryThis(\n    Array.prototype.map || function (callback, thisp) {\n        var self = this;\n        var collect = [];\n        array_reduce(self, function (undefined, value, index) {\n            collect.push(callback.call(thisp, value, index, self));\n        }, void 0);\n        return collect;\n    }\n);\n\nvar object_create = Object.create || function (prototype) {\n    function Type() { }\n    Type.prototype = prototype;\n    return new Type();\n};\n\nvar object_hasOwnProperty = uncurryThis(Object.prototype.hasOwnProperty);\n\nvar object_keys = Object.keys || function (object) {\n    var keys = [];\n    for (var key in object) {\n        if (object_hasOwnProperty(object, key)) {\n            keys.push(key);\n        }\n    }\n    return keys;\n};\n\nvar object_toString = uncurryThis(Object.prototype.toString);\n\nfunction isObject(value) {\n    return value === Object(value);\n}\n\n// generator related shims\n\n// FIXME: Remove this function once ES6 generators are in SpiderMonkey.\nfunction isStopIteration(exception) {\n    return (\n        object_toString(exception) === \"[object StopIteration]\" ||\n        exception instanceof QReturnValue\n    );\n}\n\n// FIXME: Remove this helper and Q.return once ES6 generators are in\n// SpiderMonkey.\nvar QReturnValue;\nif (typeof ReturnValue !== \"undefined\") {\n    QReturnValue = ReturnValue;\n} else {\n    QReturnValue = function (value) {\n        this.value = value;\n    };\n}\n\n// long stack traces\n\nvar STACK_JUMP_SEPARATOR = \"From previous event:\";\n\nfunction makeStackTraceLong(error, promise) {\n    // If possible, transform the error stack trace by removing Node and Q\n    // cruft, then concatenating with the stack trace of `promise`. See #57.\n    if (hasStacks &&\n        promise.stack &&\n        typeof error === \"object\" &&\n        error !== null &&\n        error.stack &&\n        error.stack.indexOf(STACK_JUMP_SEPARATOR) === -1\n    ) {\n        var stacks = [];\n        for (var p = promise; !!p; p = p.source) {\n            if (p.stack) {\n                stacks.unshift(p.stack);\n            }\n        }\n        stacks.unshift(error.stack);\n\n        var concatedStacks = stacks.join(\"\\n\" + STACK_JUMP_SEPARATOR + \"\\n\");\n        error.stack = filterStackString(concatedStacks);\n    }\n}\n\nfunction filterStackString(stackString) {\n    var lines = stackString.split(\"\\n\");\n    var desiredLines = [];\n    for (var i = 0; i < lines.length; ++i) {\n        var line = lines[i];\n\n        if (!isInternalFrame(line) && !isNodeFrame(line) && line) {\n            desiredLines.push(line);\n        }\n    }\n    return desiredLines.join(\"\\n\");\n}\n\nfunction isNodeFrame(stackLine) {\n    return stackLine.indexOf(\"(module.js:\") !== -1 ||\n           stackLine.indexOf(\"(node.js:\") !== -1;\n}\n\nfunction getFileNameAndLineNumber(stackLine) {\n    // Named functions: \"at functionName (filename:lineNumber:columnNumber)\"\n    // In IE10 function name can have spaces (\"Anonymous function\") O_o\n    var attempt1 = /at .+ \\((.+):(\\d+):(?:\\d+)\\)$/.exec(stackLine);\n    if (attempt1) {\n        return [attempt1[1], Number(attempt1[2])];\n    }\n\n    // Anonymous functions: \"at filename:lineNumber:columnNumber\"\n    var attempt2 = /at ([^ ]+):(\\d+):(?:\\d+)$/.exec(stackLine);\n    if (attempt2) {\n        return [attempt2[1], Number(attempt2[2])];\n    }\n\n    // Firefox style: \"function@filename:lineNumber or @filename:lineNumber\"\n    var attempt3 = /.*@(.+):(\\d+)$/.exec(stackLine);\n    if (attempt3) {\n        return [attempt3[1], Number(attempt3[2])];\n    }\n}\n\nfunction isInternalFrame(stackLine) {\n    var fileNameAndLineNumber = getFileNameAndLineNumber(stackLine);\n\n    if (!fileNameAndLineNumber) {\n        return false;\n    }\n\n    var fileName = fileNameAndLineNumber[0];\n    var lineNumber = fileNameAndLineNumber[1];\n\n    return fileName === qFileName &&\n        lineNumber >= qStartingLine &&\n        lineNumber <= qEndingLine;\n}\n\n// discover own file name and line number range for filtering stack\n// traces\nfunction captureLine() {\n    if (!hasStacks) {\n        return;\n    }\n\n    try {\n        throw new Error();\n    } catch (e) {\n        var lines = e.stack.split(\"\\n\");\n        var firstLine = lines[0].indexOf(\"@\") > 0 ? lines[1] : lines[2];\n        var fileNameAndLineNumber = getFileNameAndLineNumber(firstLine);\n        if (!fileNameAndLineNumber) {\n            return;\n        }\n\n        qFileName = fileNameAndLineNumber[0];\n        return fileNameAndLineNumber[1];\n    }\n}\n\nfunction deprecate(callback, name, alternative) {\n    return function () {\n        if (typeof console !== \"undefined\" &&\n            typeof console.warn === \"function\") {\n            console.warn(name + \" is deprecated, use \" + alternative +\n                         \" instead.\", new Error(\"\").stack);\n        }\n        return callback.apply(callback, arguments);\n    };\n}\n\n// end of shims\n// beginning of real work\n\n/**\n * Constructs a promise for an immediate reference, passes promises through, or\n * coerces promises from different systems.\n * @param value immediate reference or promise\n */\nfunction Q(value) {\n    // If the object is already a Promise, return it directly.  This enables\n    // the resolve function to both be used to created references from objects,\n    // but to tolerably coerce non-promises to promises.\n    if (isPromise(value)) {\n        return value;\n    }\n\n    // assimilate thenables\n    if (isPromiseAlike(value)) {\n        return coerce(value);\n    } else {\n        return fulfill(value);\n    }\n}\nQ.resolve = Q;\n\n/**\n * Performs a task in a future turn of the event loop.\n * @param {Function} task\n */\nQ.nextTick = nextTick;\n\n/**\n * Controls whether or not long stack traces will be on\n */\nQ.longStackSupport = false;\n\n/**\n * Constructs a {promise, resolve, reject} object.\n *\n * `resolve` is a callback to invoke with a more resolved value for the\n * promise. To fulfill the promise, invoke `resolve` with any value that is\n * not a thenable. To reject the promise, invoke `resolve` with a rejected\n * thenable, or invoke `reject` with the reason directly. To resolve the\n * promise to another thenable, thus putting it in the same state, invoke\n * `resolve` with that other thenable.\n */\nQ.defer = defer;\nfunction defer() {\n    // if \"messages\" is an \"Array\", that indicates that the promise has not yet\n    // been resolved.  If it is \"undefined\", it has been resolved.  Each\n    // element of the messages array is itself an array of complete arguments to\n    // forward to the resolved promise.  We coerce the resolution value to a\n    // promise using the `resolve` function because it handles both fully\n    // non-thenable values and other thenables gracefully.\n    var messages = [], progressListeners = [], resolvedPromise;\n\n    var deferred = object_create(defer.prototype);\n    var promise = object_create(Promise.prototype);\n\n    promise.promiseDispatch = function (resolve, op, operands) {\n        var args = array_slice(arguments);\n        if (messages) {\n            messages.push(args);\n            if (op === \"when\" && operands[1]) { // progress operand\n                progressListeners.push(operands[1]);\n            }\n        } else {\n            nextTick(function () {\n                resolvedPromise.promiseDispatch.apply(resolvedPromise, args);\n            });\n        }\n    };\n\n    // XXX deprecated\n    promise.valueOf = function () {\n        if (messages) {\n            return promise;\n        }\n        var nearerValue = nearer(resolvedPromise);\n        if (isPromise(nearerValue)) {\n            resolvedPromise = nearerValue; // shorten chain\n        }\n        return nearerValue;\n    };\n\n    promise.inspect = function () {\n        if (!resolvedPromise) {\n            return { state: \"pending\" };\n        }\n        return resolvedPromise.inspect();\n    };\n\n    if (Q.longStackSupport && hasStacks) {\n        try {\n            throw new Error();\n        } catch (e) {\n            // NOTE: don't try to use `Error.captureStackTrace` or transfer the\n            // accessor around; that causes memory leaks as per GH-111. Just\n            // reify the stack trace as a string ASAP.\n            //\n            // At the same time, cut off the first line; it's always just\n            // \"[object Promise]\\n\", as per the `toString`.\n            promise.stack = e.stack.substring(e.stack.indexOf(\"\\n\") + 1);\n        }\n    }\n\n    // NOTE: we do the checks for `resolvedPromise` in each method, instead of\n    // consolidating them into `become`, since otherwise we'd create new\n    // promises with the lines `become(whatever(value))`. See e.g. GH-252.\n\n    function become(newPromise) {\n        resolvedPromise = newPromise;\n        promise.source = newPromise;\n\n        array_reduce(messages, function (undefined, message) {\n            nextTick(function () {\n                newPromise.promiseDispatch.apply(newPromise, message);\n            });\n        }, void 0);\n\n        messages = void 0;\n        progressListeners = void 0;\n    }\n\n    deferred.promise = promise;\n    deferred.resolve = function (value) {\n        if (resolvedPromise) {\n            return;\n        }\n\n        become(Q(value));\n    };\n\n    deferred.fulfill = function (value) {\n        if (resolvedPromise) {\n            return;\n        }\n\n        become(fulfill(value));\n    };\n    deferred.reject = function (reason) {\n        if (resolvedPromise) {\n            return;\n        }\n\n        become(reject(reason));\n    };\n    deferred.notify = function (progress) {\n        if (resolvedPromise) {\n            return;\n        }\n\n        array_reduce(progressListeners, function (undefined, progressListener) {\n            nextTick(function () {\n                progressListener(progress);\n            });\n        }, void 0);\n    };\n\n    return deferred;\n}\n\n/**\n * Creates a Node-style callback that will resolve or reject the deferred\n * promise.\n * @returns a nodeback\n */\ndefer.prototype.makeNodeResolver = function () {\n    var self = this;\n    return function (error, value) {\n        if (error) {\n            self.reject(error);\n        } else if (arguments.length > 2) {\n            self.resolve(array_slice(arguments, 1));\n        } else {\n            self.resolve(value);\n        }\n    };\n};\n\n/**\n * @param resolver {Function} a function that returns nothing and accepts\n * the resolve, reject, and notify functions for a deferred.\n * @returns a promise that may be resolved with the given resolve and reject\n * functions, or rejected by a thrown exception in resolver\n */\nQ.Promise = promise; // ES6\nQ.promise = promise;\nfunction promise(resolver) {\n    if (typeof resolver !== \"function\") {\n        throw new TypeError(\"resolver must be a function.\");\n    }\n    var deferred = defer();\n    try {\n        resolver(deferred.resolve, deferred.reject, deferred.notify);\n    } catch (reason) {\n        deferred.reject(reason);\n    }\n    return deferred.promise;\n}\n\npromise.race = race; // ES6\npromise.all = all; // ES6\npromise.reject = reject; // ES6\npromise.resolve = Q; // ES6\n\n// XXX experimental.  This method is a way to denote that a local value is\n// serializable and should be immediately dispatched to a remote upon request,\n// instead of passing a reference.\nQ.passByCopy = function (object) {\n    //freeze(object);\n    //passByCopies.set(object, true);\n    return object;\n};\n\nPromise.prototype.passByCopy = function () {\n    //freeze(object);\n    //passByCopies.set(object, true);\n    return this;\n};\n\n/**\n * If two promises eventually fulfill to the same value, promises that value,\n * but otherwise rejects.\n * @param x {Any*}\n * @param y {Any*}\n * @returns {Any*} a promise for x and y if they are the same, but a rejection\n * otherwise.\n *\n */\nQ.join = function (x, y) {\n    return Q(x).join(y);\n};\n\nPromise.prototype.join = function (that) {\n    return Q([this, that]).spread(function (x, y) {\n        if (x === y) {\n            // TODO: \"===\" should be Object.is or equiv\n            return x;\n        } else {\n            throw new Error(\"Can't join: not the same: \" + x + \" \" + y);\n        }\n    });\n};\n\n/**\n * Returns a promise for the first of an array of promises to become fulfilled.\n * @param answers {Array[Any*]} promises to race\n * @returns {Any*} the first promise to be fulfilled\n */\nQ.race = race;\nfunction race(answerPs) {\n    return promise(function(resolve, reject) {\n        // Switch to this once we can assume at least ES5\n        // answerPs.forEach(function(answerP) {\n        //     Q(answerP).then(resolve, reject);\n        // });\n        // Use this in the meantime\n        for (var i = 0, len = answerPs.length; i < len; i++) {\n            Q(answerPs[i]).then(resolve, reject);\n        }\n    });\n}\n\nPromise.prototype.race = function () {\n    return this.then(Q.race);\n};\n\n/**\n * Constructs a Promise with a promise descriptor object and optional fallback\n * function.  The descriptor contains methods like when(rejected), get(name),\n * set(name, value), post(name, args), and delete(name), which all\n * return either a value, a promise for a value, or a rejection.  The fallback\n * accepts the operation name, a resolver, and any further arguments that would\n * have been forwarded to the appropriate method above had a method been\n * provided with the proper name.  The API makes no guarantees about the nature\n * of the returned object, apart from that it is usable wherever promises are\n * bought and sold.\n */\nQ.makePromise = Promise;\nfunction Promise(descriptor, fallback, inspect) {\n    if (fallback === void 0) {\n        fallback = function (op) {\n            return reject(new Error(\n                \"Promise does not support operation: \" + op\n            ));\n        };\n    }\n    if (inspect === void 0) {\n        inspect = function () {\n            return {state: \"unknown\"};\n        };\n    }\n\n    var promise = object_create(Promise.prototype);\n\n    promise.promiseDispatch = function (resolve, op, args) {\n        var result;\n        try {\n            if (descriptor[op]) {\n                result = descriptor[op].apply(promise, args);\n            } else {\n                result = fallback.call(promise, op, args);\n            }\n        } catch (exception) {\n            result = reject(exception);\n        }\n        if (resolve) {\n            resolve(result);\n        }\n    };\n\n    promise.inspect = inspect;\n\n    // XXX deprecated `valueOf` and `exception` support\n    if (inspect) {\n        var inspected = inspect();\n        if (inspected.state === \"rejected\") {\n            promise.exception = inspected.reason;\n        }\n\n        promise.valueOf = function () {\n            var inspected = inspect();\n            if (inspected.state === \"pending\" ||\n                inspected.state === \"rejected\") {\n                return promise;\n            }\n            return inspected.value;\n        };\n    }\n\n    return promise;\n}\n\nPromise.prototype.toString = function () {\n    return \"[object Promise]\";\n};\n\nPromise.prototype.then = function (fulfilled, rejected, progressed) {\n    var self = this;\n    var deferred = defer();\n    var done = false;   // ensure the untrusted promise makes at most a\n                        // single call to one of the callbacks\n\n    function _fulfilled(value) {\n        try {\n            return typeof fulfilled === \"function\" ? fulfilled(value) : value;\n        } catch (exception) {\n            return reject(exception);\n        }\n    }\n\n    function _rejected(exception) {\n        if (typeof rejected === \"function\") {\n            makeStackTraceLong(exception, self);\n            try {\n                return rejected(exception);\n            } catch (newException) {\n                return reject(newException);\n            }\n        }\n        return reject(exception);\n    }\n\n    function _progressed(value) {\n        return typeof progressed === \"function\" ? progressed(value) : value;\n    }\n\n    nextTick(function () {\n        self.promiseDispatch(function (value) {\n            if (done) {\n                return;\n            }\n            done = true;\n\n            deferred.resolve(_fulfilled(value));\n        }, \"when\", [function (exception) {\n            if (done) {\n                return;\n            }\n            done = true;\n\n            deferred.resolve(_rejected(exception));\n        }]);\n    });\n\n    // Progress propagator need to be attached in the current tick.\n    self.promiseDispatch(void 0, \"when\", [void 0, function (value) {\n        var newValue;\n        var threw = false;\n        try {\n            newValue = _progressed(value);\n        } catch (e) {\n            threw = true;\n            if (Q.onerror) {\n                Q.onerror(e);\n            } else {\n                throw e;\n            }\n        }\n\n        if (!threw) {\n            deferred.notify(newValue);\n        }\n    }]);\n\n    return deferred.promise;\n};\n\n/**\n * Registers an observer on a promise.\n *\n * Guarantees:\n *\n * 1. that fulfilled and rejected will be called only once.\n * 2. that either the fulfilled callback or the rejected callback will be\n *    called, but not both.\n * 3. that fulfilled and rejected will not be called in this turn.\n *\n * @param value      promise or immediate reference to observe\n * @param fulfilled  function to be called with the fulfilled value\n * @param rejected   function to be called with the rejection exception\n * @param progressed function to be called on any progress notifications\n * @return promise for the return value from the invoked callback\n */\nQ.when = when;\nfunction when(value, fulfilled, rejected, progressed) {\n    return Q(value).then(fulfilled, rejected, progressed);\n}\n\nPromise.prototype.thenResolve = function (value) {\n    return this.then(function () { return value; });\n};\n\nQ.thenResolve = function (promise, value) {\n    return Q(promise).thenResolve(value);\n};\n\nPromise.prototype.thenReject = function (reason) {\n    return this.then(function () { throw reason; });\n};\n\nQ.thenReject = function (promise, reason) {\n    return Q(promise).thenReject(reason);\n};\n\n/**\n * If an object is not a promise, it is as \"near\" as possible.\n * If a promise is rejected, it is as \"near\" as possible too.\n * If it’s a fulfilled promise, the fulfillment value is nearer.\n * If it’s a deferred promise and the deferred has been resolved, the\n * resolution is \"nearer\".\n * @param object\n * @returns most resolved (nearest) form of the object\n */\n\n// XXX should we re-do this?\nQ.nearer = nearer;\nfunction nearer(value) {\n    if (isPromise(value)) {\n        var inspected = value.inspect();\n        if (inspected.state === \"fulfilled\") {\n            return inspected.value;\n        }\n    }\n    return value;\n}\n\n/**\n * @returns whether the given object is a promise.\n * Otherwise it is a fulfilled value.\n */\nQ.isPromise = isPromise;\nfunction isPromise(object) {\n    return isObject(object) &&\n        typeof object.promiseDispatch === \"function\" &&\n        typeof object.inspect === \"function\";\n}\n\nQ.isPromiseAlike = isPromiseAlike;\nfunction isPromiseAlike(object) {\n    return isObject(object) && typeof object.then === \"function\";\n}\n\n/**\n * @returns whether the given object is a pending promise, meaning not\n * fulfilled or rejected.\n */\nQ.isPending = isPending;\nfunction isPending(object) {\n    return isPromise(object) && object.inspect().state === \"pending\";\n}\n\nPromise.prototype.isPending = function () {\n    return this.inspect().state === \"pending\";\n};\n\n/**\n * @returns whether the given object is a value or fulfilled\n * promise.\n */\nQ.isFulfilled = isFulfilled;\nfunction isFulfilled(object) {\n    return !isPromise(object) || object.inspect().state === \"fulfilled\";\n}\n\nPromise.prototype.isFulfilled = function () {\n    return this.inspect().state === \"fulfilled\";\n};\n\n/**\n * @returns whether the given object is a rejected promise.\n */\nQ.isRejected = isRejected;\nfunction isRejected(object) {\n    return isPromise(object) && object.inspect().state === \"rejected\";\n}\n\nPromise.prototype.isRejected = function () {\n    return this.inspect().state === \"rejected\";\n};\n\n//// BEGIN UNHANDLED REJECTION TRACKING\n\n// This promise library consumes exceptions thrown in handlers so they can be\n// handled by a subsequent promise.  The exceptions get added to this array when\n// they are created, and removed when they are handled.  Note that in ES6 or\n// shimmed environments, this would naturally be a `Set`.\nvar unhandledReasons = [];\nvar unhandledRejections = [];\nvar trackUnhandledRejections = true;\n\nfunction resetUnhandledRejections() {\n    unhandledReasons.length = 0;\n    unhandledRejections.length = 0;\n\n    if (!trackUnhandledRejections) {\n        trackUnhandledRejections = true;\n    }\n}\n\nfunction trackRejection(promise, reason) {\n    if (!trackUnhandledRejections) {\n        return;\n    }\n\n    unhandledRejections.push(promise);\n    if (reason && typeof reason.stack !== \"undefined\") {\n        unhandledReasons.push(reason.stack);\n    } else {\n        unhandledReasons.push(\"(no stack) \" + reason);\n    }\n}\n\nfunction untrackRejection(promise) {\n    if (!trackUnhandledRejections) {\n        return;\n    }\n\n    var at = array_indexOf(unhandledRejections, promise);\n    if (at !== -1) {\n        unhandledRejections.splice(at, 1);\n        unhandledReasons.splice(at, 1);\n    }\n}\n\nQ.resetUnhandledRejections = resetUnhandledRejections;\n\nQ.getUnhandledReasons = function () {\n    // Make a copy so that consumers can't interfere with our internal state.\n    return unhandledReasons.slice();\n};\n\nQ.stopUnhandledRejectionTracking = function () {\n    resetUnhandledRejections();\n    trackUnhandledRejections = false;\n};\n\nresetUnhandledRejections();\n\n//// END UNHANDLED REJECTION TRACKING\n\n/**\n * Constructs a rejected promise.\n * @param reason value describing the failure\n */\nQ.reject = reject;\nfunction reject(reason) {\n    var rejection = Promise({\n        \"when\": function (rejected) {\n            // note that the error has been handled\n            if (rejected) {\n                untrackRejection(this);\n            }\n            return rejected ? rejected(reason) : this;\n        }\n    }, function fallback() {\n        return this;\n    }, function inspect() {\n        return { state: \"rejected\", reason: reason };\n    });\n\n    // Note that the reason has not been handled.\n    trackRejection(rejection, reason);\n\n    return rejection;\n}\n\n/**\n * Constructs a fulfilled promise for an immediate reference.\n * @param value immediate reference\n */\nQ.fulfill = fulfill;\nfunction fulfill(value) {\n    return Promise({\n        \"when\": function () {\n            return value;\n        },\n        \"get\": function (name) {\n            return value[name];\n        },\n        \"set\": function (name, rhs) {\n            value[name] = rhs;\n        },\n        \"delete\": function (name) {\n            delete value[name];\n        },\n        \"post\": function (name, args) {\n            // Mark Miller proposes that post with no name should apply a\n            // promised function.\n            if (name === null || name === void 0) {\n                return value.apply(void 0, args);\n            } else {\n                return value[name].apply(value, args);\n            }\n        },\n        \"apply\": function (thisp, args) {\n            return value.apply(thisp, args);\n        },\n        \"keys\": function () {\n            return object_keys(value);\n        }\n    }, void 0, function inspect() {\n        return { state: \"fulfilled\", value: value };\n    });\n}\n\n/**\n * Converts thenables to Q promises.\n * @param promise thenable promise\n * @returns a Q promise\n */\nfunction coerce(promise) {\n    var deferred = defer();\n    nextTick(function () {\n        try {\n            promise.then(deferred.resolve, deferred.reject, deferred.notify);\n        } catch (exception) {\n            deferred.reject(exception);\n        }\n    });\n    return deferred.promise;\n}\n\n/**\n * Annotates an object such that it will never be\n * transferred away from this process over any promise\n * communication channel.\n * @param object\n * @returns promise a wrapping of that object that\n * additionally responds to the \"isDef\" message\n * without a rejection.\n */\nQ.master = master;\nfunction master(object) {\n    return Promise({\n        \"isDef\": function () {}\n    }, function fallback(op, args) {\n        return dispatch(object, op, args);\n    }, function () {\n        return Q(object).inspect();\n    });\n}\n\n/**\n * Spreads the values of a promised array of arguments into the\n * fulfillment callback.\n * @param fulfilled callback that receives variadic arguments from the\n * promised array\n * @param rejected callback that receives the exception if the promise\n * is rejected.\n * @returns a promise for the return value or thrown exception of\n * either callback.\n */\nQ.spread = spread;\nfunction spread(value, fulfilled, rejected) {\n    return Q(value).spread(fulfilled, rejected);\n}\n\nPromise.prototype.spread = function (fulfilled, rejected) {\n    return this.all().then(function (array) {\n        return fulfilled.apply(void 0, array);\n    }, rejected);\n};\n\n/**\n * The async function is a decorator for generator functions, turning\n * them into asynchronous generators.  Although generators are only part\n * of the newest ECMAScript 6 drafts, this code does not cause syntax\n * errors in older engines.  This code should continue to work and will\n * in fact improve over time as the language improves.\n *\n * ES6 generators are currently part of V8 version 3.19 with the\n * --harmony-generators runtime flag enabled.  SpiderMonkey has had them\n * for longer, but under an older Python-inspired form.  This function\n * works on both kinds of generators.\n *\n * Decorates a generator function such that:\n *  - it may yield promises\n *  - execution will continue when that promise is fulfilled\n *  - the value of the yield expression will be the fulfilled value\n *  - it returns a promise for the return value (when the generator\n *    stops iterating)\n *  - the decorated function returns a promise for the return value\n *    of the generator or the first rejected promise among those\n *    yielded.\n *  - if an error is thrown in the generator, it propagates through\n *    every following yield until it is caught, or until it escapes\n *    the generator function altogether, and is translated into a\n *    rejection for the promise returned by the decorated generator.\n */\nQ.async = async;\nfunction async(makeGenerator) {\n    return function () {\n        // when verb is \"send\", arg is a value\n        // when verb is \"throw\", arg is an exception\n        function continuer(verb, arg) {\n            var result;\n\n            // Until V8 3.19 / Chromium 29 is released, SpiderMonkey is the only\n            // engine that has a deployed base of browsers that support generators.\n            // However, SM's generators use the Python-inspired semantics of\n            // outdated ES6 drafts.  We would like to support ES6, but we'd also\n            // like to make it possible to use generators in deployed browsers, so\n            // we also support Python-style generators.  At some point we can remove\n            // this block.\n\n            if (typeof StopIteration === \"undefined\") {\n                // ES6 Generators\n                try {\n                    result = generator[verb](arg);\n                } catch (exception) {\n                    return reject(exception);\n                }\n                if (result.done) {\n                    return result.value;\n                } else {\n                    return when(result.value, callback, errback);\n                }\n            } else {\n                // SpiderMonkey Generators\n                // FIXME: Remove this case when SM does ES6 generators.\n                try {\n                    result = generator[verb](arg);\n                } catch (exception) {\n                    if (isStopIteration(exception)) {\n                        return exception.value;\n                    } else {\n                        return reject(exception);\n                    }\n                }\n                return when(result, callback, errback);\n            }\n        }\n        var generator = makeGenerator.apply(this, arguments);\n        var callback = continuer.bind(continuer, \"next\");\n        var errback = continuer.bind(continuer, \"throw\");\n        return callback();\n    };\n}\n\n/**\n * The spawn function is a small wrapper around async that immediately\n * calls the generator and also ends the promise chain, so that any\n * unhandled errors are thrown instead of forwarded to the error\n * handler. This is useful because it's extremely common to run\n * generators at the top-level to work with libraries.\n */\nQ.spawn = spawn;\nfunction spawn(makeGenerator) {\n    Q.done(Q.async(makeGenerator)());\n}\n\n// FIXME: Remove this interface once ES6 generators are in SpiderMonkey.\n/**\n * Throws a ReturnValue exception to stop an asynchronous generator.\n *\n * This interface is a stop-gap measure to support generator return\n * values in older Firefox/SpiderMonkey.  In browsers that support ES6\n * generators like Chromium 29, just use \"return\" in your generator\n * functions.\n *\n * @param value the return value for the surrounding generator\n * @throws ReturnValue exception with the value.\n * @example\n * // ES6 style\n * Q.async(function* () {\n *      var foo = yield getFooPromise();\n *      var bar = yield getBarPromise();\n *      return foo + bar;\n * })\n * // Older SpiderMonkey style\n * Q.async(function () {\n *      var foo = yield getFooPromise();\n *      var bar = yield getBarPromise();\n *      Q.return(foo + bar);\n * })\n */\nQ[\"return\"] = _return;\nfunction _return(value) {\n    throw new QReturnValue(value);\n}\n\n/**\n * The promised function decorator ensures that any promise arguments\n * are settled and passed as values (`this` is also settled and passed\n * as a value).  It will also ensure that the result of a function is\n * always a promise.\n *\n * @example\n * var add = Q.promised(function (a, b) {\n *     return a + b;\n * });\n * add(Q(a), Q(B));\n *\n * @param {function} callback The function to decorate\n * @returns {function} a function that has been decorated.\n */\nQ.promised = promised;\nfunction promised(callback) {\n    return function () {\n        return spread([this, all(arguments)], function (self, args) {\n            return callback.apply(self, args);\n        });\n    };\n}\n\n/**\n * sends a message to a value in a future turn\n * @param object* the recipient\n * @param op the name of the message operation, e.g., \"when\",\n * @param args further arguments to be forwarded to the operation\n * @returns result {Promise} a promise for the result of the operation\n */\nQ.dispatch = dispatch;\nfunction dispatch(object, op, args) {\n    return Q(object).dispatch(op, args);\n}\n\nPromise.prototype.dispatch = function (op, args) {\n    var self = this;\n    var deferred = defer();\n    nextTick(function () {\n        self.promiseDispatch(deferred.resolve, op, args);\n    });\n    return deferred.promise;\n};\n\n/**\n * Gets the value of a property in a future turn.\n * @param object    promise or immediate reference for target object\n * @param name      name of property to get\n * @return promise for the property value\n */\nQ.get = function (object, key) {\n    return Q(object).dispatch(\"get\", [key]);\n};\n\nPromise.prototype.get = function (key) {\n    return this.dispatch(\"get\", [key]);\n};\n\n/**\n * Sets the value of a property in a future turn.\n * @param object    promise or immediate reference for object object\n * @param name      name of property to set\n * @param value     new value of property\n * @return promise for the return value\n */\nQ.set = function (object, key, value) {\n    return Q(object).dispatch(\"set\", [key, value]);\n};\n\nPromise.prototype.set = function (key, value) {\n    return this.dispatch(\"set\", [key, value]);\n};\n\n/**\n * Deletes a property in a future turn.\n * @param object    promise or immediate reference for target object\n * @param name      name of property to delete\n * @return promise for the return value\n */\nQ.del = // XXX legacy\nQ[\"delete\"] = function (object, key) {\n    return Q(object).dispatch(\"delete\", [key]);\n};\n\nPromise.prototype.del = // XXX legacy\nPromise.prototype[\"delete\"] = function (key) {\n    return this.dispatch(\"delete\", [key]);\n};\n\n/**\n * Invokes a method in a future turn.\n * @param object    promise or immediate reference for target object\n * @param name      name of method to invoke\n * @param value     a value to post, typically an array of\n *                  invocation arguments for promises that\n *                  are ultimately backed with `resolve` values,\n *                  as opposed to those backed with URLs\n *                  wherein the posted value can be any\n *                  JSON serializable object.\n * @return promise for the return value\n */\n// bound locally because it is used by other methods\nQ.mapply = // XXX As proposed by \"Redsandro\"\nQ.post = function (object, name, args) {\n    return Q(object).dispatch(\"post\", [name, args]);\n};\n\nPromise.prototype.mapply = // XXX As proposed by \"Redsandro\"\nPromise.prototype.post = function (name, args) {\n    return this.dispatch(\"post\", [name, args]);\n};\n\n/**\n * Invokes a method in a future turn.\n * @param object    promise or immediate reference for target object\n * @param name      name of method to invoke\n * @param ...args   array of invocation arguments\n * @return promise for the return value\n */\nQ.send = // XXX Mark Miller's proposed parlance\nQ.mcall = // XXX As proposed by \"Redsandro\"\nQ.invoke = function (object, name /*...args*/) {\n    return Q(object).dispatch(\"post\", [name, array_slice(arguments, 2)]);\n};\n\nPromise.prototype.send = // XXX Mark Miller's proposed parlance\nPromise.prototype.mcall = // XXX As proposed by \"Redsandro\"\nPromise.prototype.invoke = function (name /*...args*/) {\n    return this.dispatch(\"post\", [name, array_slice(arguments, 1)]);\n};\n\n/**\n * Applies the promised function in a future turn.\n * @param object    promise or immediate reference for target function\n * @param args      array of application arguments\n */\nQ.fapply = function (object, args) {\n    return Q(object).dispatch(\"apply\", [void 0, args]);\n};\n\nPromise.prototype.fapply = function (args) {\n    return this.dispatch(\"apply\", [void 0, args]);\n};\n\n/**\n * Calls the promised function in a future turn.\n * @param object    promise or immediate reference for target function\n * @param ...args   array of application arguments\n */\nQ[\"try\"] =\nQ.fcall = function (object /* ...args*/) {\n    return Q(object).dispatch(\"apply\", [void 0, array_slice(arguments, 1)]);\n};\n\nPromise.prototype.fcall = function (/*...args*/) {\n    return this.dispatch(\"apply\", [void 0, array_slice(arguments)]);\n};\n\n/**\n * Binds the promised function, transforming return values into a fulfilled\n * promise and thrown errors into a rejected one.\n * @param object    promise or immediate reference for target function\n * @param ...args   array of application arguments\n */\nQ.fbind = function (object /*...args*/) {\n    var promise = Q(object);\n    var args = array_slice(arguments, 1);\n    return function fbound() {\n        return promise.dispatch(\"apply\", [\n            this,\n            args.concat(array_slice(arguments))\n        ]);\n    };\n};\nPromise.prototype.fbind = function (/*...args*/) {\n    var promise = this;\n    var args = array_slice(arguments);\n    return function fbound() {\n        return promise.dispatch(\"apply\", [\n            this,\n            args.concat(array_slice(arguments))\n        ]);\n    };\n};\n\n/**\n * Requests the names of the owned properties of a promised\n * object in a future turn.\n * @param object    promise or immediate reference for target object\n * @return promise for the keys of the eventually settled object\n */\nQ.keys = function (object) {\n    return Q(object).dispatch(\"keys\", []);\n};\n\nPromise.prototype.keys = function () {\n    return this.dispatch(\"keys\", []);\n};\n\n/**\n * Turns an array of promises into a promise for an array.  If any of\n * the promises gets rejected, the whole array is rejected immediately.\n * @param {Array*} an array (or promise for an array) of values (or\n * promises for values)\n * @returns a promise for an array of the corresponding values\n */\n// By Mark Miller\n// http://wiki.ecmascript.org/doku.php?id=strawman:concurrency&rev=1308776521#allfulfilled\nQ.all = all;\nfunction all(promises) {\n    return when(promises, function (promises) {\n        var countDown = 0;\n        var deferred = defer();\n        array_reduce(promises, function (undefined, promise, index) {\n            var snapshot;\n            if (\n                isPromise(promise) &&\n                (snapshot = promise.inspect()).state === \"fulfilled\"\n            ) {\n                promises[index] = snapshot.value;\n            } else {\n                ++countDown;\n                when(\n                    promise,\n                    function (value) {\n                        promises[index] = value;\n                        if (--countDown === 0) {\n                            deferred.resolve(promises);\n                        }\n                    },\n                    deferred.reject,\n                    function (progress) {\n                        deferred.notify({ index: index, value: progress });\n                    }\n                );\n            }\n        }, void 0);\n        if (countDown === 0) {\n            deferred.resolve(promises);\n        }\n        return deferred.promise;\n    });\n}\n\nPromise.prototype.all = function () {\n    return all(this);\n};\n\n/**\n * Waits for all promises to be settled, either fulfilled or\n * rejected.  This is distinct from `all` since that would stop\n * waiting at the first rejection.  The promise returned by\n * `allResolved` will never be rejected.\n * @param promises a promise for an array (or an array) of promises\n * (or values)\n * @return a promise for an array of promises\n */\nQ.allResolved = deprecate(allResolved, \"allResolved\", \"allSettled\");\nfunction allResolved(promises) {\n    return when(promises, function (promises) {\n        promises = array_map(promises, Q);\n        return when(all(array_map(promises, function (promise) {\n            return when(promise, noop, noop);\n        })), function () {\n            return promises;\n        });\n    });\n}\n\nPromise.prototype.allResolved = function () {\n    return allResolved(this);\n};\n\n/**\n * @see Promise#allSettled\n */\nQ.allSettled = allSettled;\nfunction allSettled(promises) {\n    return Q(promises).allSettled();\n}\n\n/**\n * Turns an array of promises into a promise for an array of their states (as\n * returned by `inspect`) when they have all settled.\n * @param {Array[Any*]} values an array (or promise for an array) of values (or\n * promises for values)\n * @returns {Array[State]} an array of states for the respective values.\n */\nPromise.prototype.allSettled = function () {\n    return this.then(function (promises) {\n        return all(array_map(promises, function (promise) {\n            promise = Q(promise);\n            function regardless() {\n                return promise.inspect();\n            }\n            return promise.then(regardless, regardless);\n        }));\n    });\n};\n\n/**\n * Captures the failure of a promise, giving an opportunity to recover\n * with a callback.  If the given promise is fulfilled, the returned\n * promise is fulfilled.\n * @param {Any*} promise for something\n * @param {Function} callback to fulfill the returned promise if the\n * given promise is rejected\n * @returns a promise for the return value of the callback\n */\nQ.fail = // XXX legacy\nQ[\"catch\"] = function (object, rejected) {\n    return Q(object).then(void 0, rejected);\n};\n\nPromise.prototype.fail = // XXX legacy\nPromise.prototype[\"catch\"] = function (rejected) {\n    return this.then(void 0, rejected);\n};\n\n/**\n * Attaches a listener that can respond to progress notifications from a\n * promise's originating deferred. This listener receives the exact arguments\n * passed to ``deferred.notify``.\n * @param {Any*} promise for something\n * @param {Function} callback to receive any progress notifications\n * @returns the given promise, unchanged\n */\nQ.progress = progress;\nfunction progress(object, progressed) {\n    return Q(object).then(void 0, void 0, progressed);\n}\n\nPromise.prototype.progress = function (progressed) {\n    return this.then(void 0, void 0, progressed);\n};\n\n/**\n * Provides an opportunity to observe the settling of a promise,\n * regardless of whether the promise is fulfilled or rejected.  Forwards\n * the resolution to the returned promise when the callback is done.\n * The callback can return a promise to defer completion.\n * @param {Any*} promise\n * @param {Function} callback to observe the resolution of the given\n * promise, takes no arguments.\n * @returns a promise for the resolution of the given promise when\n * ``fin`` is done.\n */\nQ.fin = // XXX legacy\nQ[\"finally\"] = function (object, callback) {\n    return Q(object)[\"finally\"](callback);\n};\n\nPromise.prototype.fin = // XXX legacy\nPromise.prototype[\"finally\"] = function (callback) {\n    callback = Q(callback);\n    return this.then(function (value) {\n        return callback.fcall().then(function () {\n            return value;\n        });\n    }, function (reason) {\n        // TODO attempt to recycle the rejection with \"this\".\n        return callback.fcall().then(function () {\n            throw reason;\n        });\n    });\n};\n\n/**\n * Terminates a chain of promises, forcing rejections to be\n * thrown as exceptions.\n * @param {Any*} promise at the end of a chain of promises\n * @returns nothing\n */\nQ.done = function (object, fulfilled, rejected, progress) {\n    return Q(object).done(fulfilled, rejected, progress);\n};\n\nPromise.prototype.done = function (fulfilled, rejected, progress) {\n    var onUnhandledError = function (error) {\n        // forward to a future turn so that ``when``\n        // does not catch it and turn it into a rejection.\n        nextTick(function () {\n            makeStackTraceLong(error, promise);\n            if (Q.onerror) {\n                Q.onerror(error);\n            } else {\n                throw error;\n            }\n        });\n    };\n\n    // Avoid unnecessary `nextTick`ing via an unnecessary `when`.\n    var promise = fulfilled || rejected || progress ?\n        this.then(fulfilled, rejected, progress) :\n        this;\n\n    if (typeof process === \"object\" && process && process.domain) {\n        onUnhandledError = process.domain.bind(onUnhandledError);\n    }\n\n    promise.then(void 0, onUnhandledError);\n};\n\n/**\n * Causes a promise to be rejected if it does not get fulfilled before\n * some milliseconds time out.\n * @param {Any*} promise\n * @param {Number} milliseconds timeout\n * @param {String} custom error message (optional)\n * @returns a promise for the resolution of the given promise if it is\n * fulfilled before the timeout, otherwise rejected.\n */\nQ.timeout = function (object, ms, message) {\n    return Q(object).timeout(ms, message);\n};\n\nPromise.prototype.timeout = function (ms, message) {\n    var deferred = defer();\n    var timeoutId = setTimeout(function () {\n        deferred.reject(new Error(message || \"Timed out after \" + ms + \" ms\"));\n    }, ms);\n\n    this.then(function (value) {\n        clearTimeout(timeoutId);\n        deferred.resolve(value);\n    }, function (exception) {\n        clearTimeout(timeoutId);\n        deferred.reject(exception);\n    }, deferred.notify);\n\n    return deferred.promise;\n};\n\n/**\n * Returns a promise for the given value (or promised value), some\n * milliseconds after it resolved. Passes rejections immediately.\n * @param {Any*} promise\n * @param {Number} milliseconds\n * @returns a promise for the resolution of the given promise after milliseconds\n * time has elapsed since the resolution of the given promise.\n * If the given promise rejects, that is passed immediately.\n */\nQ.delay = function (object, timeout) {\n    if (timeout === void 0) {\n        timeout = object;\n        object = void 0;\n    }\n    return Q(object).delay(timeout);\n};\n\nPromise.prototype.delay = function (timeout) {\n    return this.then(function (value) {\n        var deferred = defer();\n        setTimeout(function () {\n            deferred.resolve(value);\n        }, timeout);\n        return deferred.promise;\n    });\n};\n\n/**\n * Passes a continuation to a Node function, which is called with the given\n * arguments provided as an array, and returns a promise.\n *\n *      Q.nfapply(FS.readFile, [__filename])\n *      .then(function (content) {\n *      })\n *\n */\nQ.nfapply = function (callback, args) {\n    return Q(callback).nfapply(args);\n};\n\nPromise.prototype.nfapply = function (args) {\n    var deferred = defer();\n    var nodeArgs = array_slice(args);\n    nodeArgs.push(deferred.makeNodeResolver());\n    this.fapply(nodeArgs).fail(deferred.reject);\n    return deferred.promise;\n};\n\n/**\n * Passes a continuation to a Node function, which is called with the given\n * arguments provided individually, and returns a promise.\n * @example\n * Q.nfcall(FS.readFile, __filename)\n * .then(function (content) {\n * })\n *\n */\nQ.nfcall = function (callback /*...args*/) {\n    var args = array_slice(arguments, 1);\n    return Q(callback).nfapply(args);\n};\n\nPromise.prototype.nfcall = function (/*...args*/) {\n    var nodeArgs = array_slice(arguments);\n    var deferred = defer();\n    nodeArgs.push(deferred.makeNodeResolver());\n    this.fapply(nodeArgs).fail(deferred.reject);\n    return deferred.promise;\n};\n\n/**\n * Wraps a NodeJS continuation passing function and returns an equivalent\n * version that returns a promise.\n * @example\n * Q.nfbind(FS.readFile, __filename)(\"utf-8\")\n * .then(console.log)\n * .done()\n */\nQ.nfbind =\nQ.denodeify = function (callback /*...args*/) {\n    var baseArgs = array_slice(arguments, 1);\n    return function () {\n        var nodeArgs = baseArgs.concat(array_slice(arguments));\n        var deferred = defer();\n        nodeArgs.push(deferred.makeNodeResolver());\n        Q(callback).fapply(nodeArgs).fail(deferred.reject);\n        return deferred.promise;\n    };\n};\n\nPromise.prototype.nfbind =\nPromise.prototype.denodeify = function (/*...args*/) {\n    var args = array_slice(arguments);\n    args.unshift(this);\n    return Q.denodeify.apply(void 0, args);\n};\n\nQ.nbind = function (callback, thisp /*...args*/) {\n    var baseArgs = array_slice(arguments, 2);\n    return function () {\n        var nodeArgs = baseArgs.concat(array_slice(arguments));\n        var deferred = defer();\n        nodeArgs.push(deferred.makeNodeResolver());\n        function bound() {\n            return callback.apply(thisp, arguments);\n        }\n        Q(bound).fapply(nodeArgs).fail(deferred.reject);\n        return deferred.promise;\n    };\n};\n\nPromise.prototype.nbind = function (/*thisp, ...args*/) {\n    var args = array_slice(arguments, 0);\n    args.unshift(this);\n    return Q.nbind.apply(void 0, args);\n};\n\n/**\n * Calls a method of a Node-style object that accepts a Node-style\n * callback with a given array of arguments, plus a provided callback.\n * @param object an object that has the named method\n * @param {String} name name of the method of object\n * @param {Array} args arguments to pass to the method; the callback\n * will be provided by Q and appended to these arguments.\n * @returns a promise for the value or error\n */\nQ.nmapply = // XXX As proposed by \"Redsandro\"\nQ.npost = function (object, name, args) {\n    return Q(object).npost(name, args);\n};\n\nPromise.prototype.nmapply = // XXX As proposed by \"Redsandro\"\nPromise.prototype.npost = function (name, args) {\n    var nodeArgs = array_slice(args || []);\n    var deferred = defer();\n    nodeArgs.push(deferred.makeNodeResolver());\n    this.dispatch(\"post\", [name, nodeArgs]).fail(deferred.reject);\n    return deferred.promise;\n};\n\n/**\n * Calls a method of a Node-style object that accepts a Node-style\n * callback, forwarding the given variadic arguments, plus a provided\n * callback argument.\n * @param object an object that has the named method\n * @param {String} name name of the method of object\n * @param ...args arguments to pass to the method; the callback will\n * be provided by Q and appended to these arguments.\n * @returns a promise for the value or error\n */\nQ.nsend = // XXX Based on Mark Miller's proposed \"send\"\nQ.nmcall = // XXX Based on \"Redsandro's\" proposal\nQ.ninvoke = function (object, name /*...args*/) {\n    var nodeArgs = array_slice(arguments, 2);\n    var deferred = defer();\n    nodeArgs.push(deferred.makeNodeResolver());\n    Q(object).dispatch(\"post\", [name, nodeArgs]).fail(deferred.reject);\n    return deferred.promise;\n};\n\nPromise.prototype.nsend = // XXX Based on Mark Miller's proposed \"send\"\nPromise.prototype.nmcall = // XXX Based on \"Redsandro's\" proposal\nPromise.prototype.ninvoke = function (name /*...args*/) {\n    var nodeArgs = array_slice(arguments, 1);\n    var deferred = defer();\n    nodeArgs.push(deferred.makeNodeResolver());\n    this.dispatch(\"post\", [name, nodeArgs]).fail(deferred.reject);\n    return deferred.promise;\n};\n\n/**\n * If a function would like to support both Node continuation-passing-style and\n * promise-returning-style, it can end its internal promise chain with\n * `nodeify(nodeback)`, forwarding the optional nodeback argument.  If the user\n * elects to use a nodeback, the result will be sent there.  If they do not\n * pass a nodeback, they will receive the result promise.\n * @param object a result (or a promise for a result)\n * @param {Function} nodeback a Node.js-style callback\n * @returns either the promise or nothing\n */\nQ.nodeify = nodeify;\nfunction nodeify(object, nodeback) {\n    return Q(object).nodeify(nodeback);\n}\n\nPromise.prototype.nodeify = function (nodeback) {\n    if (nodeback) {\n        this.then(function (value) {\n            nextTick(function () {\n                nodeback(null, value);\n            });\n        }, function (error) {\n            nextTick(function () {\n                nodeback(error);\n            });\n        });\n    } else {\n        return this;\n    }\n};\n\n// All code before this point will be filtered from stack traces.\nvar qEndingLine = captureLine();\n\nreturn Q;\n\n});\n"
  },
  {
    "path": "test/static/third-party/sinon/event.js",
    "content": "/*jslint eqeqeq: false, onevar: false*/\n/*global sinon, module, require, ActiveXObject, XMLHttpRequest, DOMParser*/\n/**\n * Minimal Event interface implementation\n *\n * Original implementation by Sven Fuchs: https://gist.github.com/995028\n * Modifications and tests by Christian Johansen.\n *\n * @author Sven Fuchs (svenfuchs@artweb-design.de)\n * @author Christian Johansen (christian@cjohansen.no)\n * @license BSD\n *\n * Copyright (c) 2011 Sven Fuchs, Christian Johansen\n */\n\"use strict\";\n\nif (typeof sinon == \"undefined\") {\n    this.sinon = {};\n}\n\n(function () {\n    var push = [].push;\n\n    sinon.Event = function Event(type, bubbles, cancelable, target) {\n        this.initEvent(type, bubbles, cancelable, target);\n    };\n\n    sinon.Event.prototype = {\n        initEvent: function(type, bubbles, cancelable, target) {\n            this.type = type;\n            this.bubbles = bubbles;\n            this.cancelable = cancelable;\n            this.target = target;\n        },\n\n        stopPropagation: function () {},\n\n        preventDefault: function () {\n            this.defaultPrevented = true;\n        }\n    };\n\n    sinon.EventTarget = {\n        addEventListener: function addEventListener(event, listener, useCapture) {\n            this.eventListeners = this.eventListeners || {};\n            this.eventListeners[event] = this.eventListeners[event] || [];\n            push.call(this.eventListeners[event], listener);\n        },\n\n        removeEventListener: function removeEventListener(event, listener, useCapture) {\n            var listeners = this.eventListeners && this.eventListeners[event] || [];\n\n            for (var i = 0, l = listeners.length; i < l; ++i) {\n                if (listeners[i] == listener) {\n                    return listeners.splice(i, 1);\n                }\n            }\n        },\n\n        dispatchEvent: function dispatchEvent(event) {\n            var type = event.type;\n            var listeners = this.eventListeners && this.eventListeners[type] || [];\n\n            for (var i = 0; i < listeners.length; i++) {\n                if (typeof listeners[i] == \"function\") {\n                    listeners[i].call(this, event);\n                } else {\n                    listeners[i].handleEvent(event);\n                }\n            }\n\n            return !!event.defaultPrevented;\n        }\n    };\n}());\n"
  },
  {
    "path": "test/static/third-party/sinon/fake_xml_http_request.js",
    "content": "/**\n * @depend ../../sinon.js\n * @depend event.js\n */\n/*jslint eqeqeq: false, onevar: false*/\n/*global sinon, module, require, ActiveXObject, XMLHttpRequest, DOMParser*/\n/**\n * Fake XMLHttpRequest object\n *\n * @author Christian Johansen (christian@cjohansen.no)\n * @license BSD\n *\n * Copyright (c) 2010-2013 Christian Johansen\n */\n\"use strict\";\n\n// wrapper for global\n(function(global) {\n\n    if (typeof sinon === \"undefined\") {\n        global.sinon = {};\n    }\n    sinon.xhr = { XMLHttpRequest: global.XMLHttpRequest };\n\n    var xhr = sinon.xhr;\n    xhr.GlobalXMLHttpRequest = global.XMLHttpRequest;\n    xhr.GlobalActiveXObject = global.ActiveXObject;\n    xhr.supportsActiveX = typeof xhr.GlobalActiveXObject != \"undefined\";\n    xhr.supportsXHR = typeof xhr.GlobalXMLHttpRequest != \"undefined\";\n    xhr.workingXHR = xhr.supportsXHR ? xhr.GlobalXMLHttpRequest : xhr.supportsActiveX\n                                     ? function() { return new xhr.GlobalActiveXObject(\"MSXML2.XMLHTTP.3.0\") } : false;\n\n    /*jsl:ignore*/\n    var unsafeHeaders = {\n        \"Accept-Charset\": true,\n        \"Accept-Encoding\": true,\n        \"Connection\": true,\n        \"Content-Length\": true,\n        \"Cookie\": true,\n        \"Cookie2\": true,\n        \"Content-Transfer-Encoding\": true,\n        \"Date\": true,\n        \"Expect\": true,\n        \"Host\": true,\n        \"Keep-Alive\": true,\n        \"Referer\": true,\n        \"TE\": true,\n        \"Trailer\": true,\n        \"Transfer-Encoding\": true,\n        \"Upgrade\": true,\n        \"User-Agent\": true,\n        \"Via\": true\n    };\n    /*jsl:end*/\n\n    function FakeXMLHttpRequest() {\n        this.readyState = FakeXMLHttpRequest.UNSENT;\n        this.requestHeaders = {};\n        this.requestBody = null;\n        this.status = 0;\n        this.statusText = \"\";\n        this.withCredentials = false;\n        this.upload = new UploadProgress();\n\n        var xhr = this;\n        var events = [\"loadstart\", \"load\", \"abort\", \"loadend\"];\n\n        function addEventListener(eventName) {\n            xhr.addEventListener(eventName, function (event) {\n                var listener = xhr[\"on\" + eventName];\n\n                if (listener && typeof listener == \"function\") {\n                    listener(event);\n                }\n            });\n        }\n\n        for (var i = events.length - 1; i >= 0; i--) {\n            addEventListener(events[i]);\n        }\n\n        if (typeof FakeXMLHttpRequest.onCreate == \"function\") {\n            FakeXMLHttpRequest.onCreate(this);\n        }\n    }\n\n    // An upload object is created for each\n    // FakeXMLHttpRequest and allows upload\n    // events to be simulated using uploadProgress\n    // and uploadError.\n    function UploadProgress() {\n        this.eventListeners = {\n            \"progress\": [],\n            \"load\": [],\n            \"abort\": [],\n            \"error\": []\n        }\n    };\n\n    UploadProgress.prototype.addEventListener = function(listenerName, callback) {\n        this.eventListeners[listenerName].push(callback);\n    };\n\n    UploadProgress.prototype.dispatchEvent = function(event) {\n        var listeners = this.eventListeners[event.type] || [];\n\n        for (var i = 0, listener; (listener = listeners[i]) != null; i++) {\n            listener(event);\n        }\n    };\n\n    function verifyState(xhr) {\n        if (xhr.readyState !== FakeXMLHttpRequest.OPENED) {\n            throw new Error(\"INVALID_STATE_ERR\");\n        }\n\n        if (xhr.sendFlag) {\n            throw new Error(\"INVALID_STATE_ERR\");\n        }\n    }\n\n    // filtering to enable a white-list version of Sinon FakeXhr,\n    // where whitelisted requests are passed through to real XHR\n    function each(collection, callback) {\n        if (!collection) return;\n        for (var i = 0, l = collection.length; i < l; i += 1) {\n            callback(collection[i]);\n        }\n    }\n    function some(collection, callback) {\n        for (var index = 0; index < collection.length; index++) {\n            if(callback(collection[index]) === true) return true;\n        };\n        return false;\n    }\n    // largest arity in XHR is 5 - XHR#open\n    var apply = function(obj,method,args) {\n        switch(args.length) {\n        case 0: return obj[method]();\n        case 1: return obj[method](args[0]);\n        case 2: return obj[method](args[0],args[1]);\n        case 3: return obj[method](args[0],args[1],args[2]);\n        case 4: return obj[method](args[0],args[1],args[2],args[3]);\n        case 5: return obj[method](args[0],args[1],args[2],args[3],args[4]);\n        };\n    };\n\n    FakeXMLHttpRequest.filters = [];\n    FakeXMLHttpRequest.addFilter = function(fn) {\n        this.filters.push(fn)\n    };\n    var IE6Re = /MSIE 6/;\n    FakeXMLHttpRequest.defake = function(fakeXhr,xhrArgs) {\n        var xhr = new sinon.xhr.workingXHR();\n        each([\"open\",\"setRequestHeader\",\"send\",\"abort\",\"getResponseHeader\",\n              \"getAllResponseHeaders\",\"addEventListener\",\"overrideMimeType\",\"removeEventListener\"],\n             function(method) {\n                 fakeXhr[method] = function() {\n                   return apply(xhr,method,arguments);\n                 };\n             });\n\n        var copyAttrs = function(args) {\n            each(args, function(attr) {\n              try {\n                fakeXhr[attr] = xhr[attr]\n              } catch(e) {\n                if(!IE6Re.test(navigator.userAgent)) throw e;\n              }\n            });\n        };\n\n        var stateChange = function() {\n            fakeXhr.readyState = xhr.readyState;\n            if(xhr.readyState >= FakeXMLHttpRequest.HEADERS_RECEIVED) {\n                copyAttrs([\"status\",\"statusText\"]);\n            }\n            if(xhr.readyState >= FakeXMLHttpRequest.LOADING) {\n                copyAttrs([\"responseText\"]);\n            }\n            if(xhr.readyState === FakeXMLHttpRequest.DONE) {\n                copyAttrs([\"responseXML\"]);\n            }\n            if(fakeXhr.onreadystatechange) fakeXhr.onreadystatechange.call(fakeXhr);\n        };\n        if(xhr.addEventListener) {\n          for(var event in fakeXhr.eventListeners) {\n              if(fakeXhr.eventListeners.hasOwnProperty(event)) {\n                  each(fakeXhr.eventListeners[event],function(handler) {\n                      xhr.addEventListener(event, handler);\n                  });\n              }\n          }\n          xhr.addEventListener(\"readystatechange\",stateChange);\n        } else {\n          xhr.onreadystatechange = stateChange;\n        }\n        apply(xhr,\"open\",xhrArgs);\n    };\n    FakeXMLHttpRequest.useFilters = false;\n\n    function verifyRequestSent(xhr) {\n        if (xhr.readyState == FakeXMLHttpRequest.DONE) {\n            throw new Error(\"Request done\");\n        }\n    }\n\n    function verifyHeadersReceived(xhr) {\n        if (xhr.async && xhr.readyState != FakeXMLHttpRequest.HEADERS_RECEIVED) {\n            throw new Error(\"No headers received\");\n        }\n    }\n\n    function verifyResponseBodyType(body) {\n        if (typeof body != \"string\") {\n            var error = new Error(\"Attempted to respond to fake XMLHttpRequest with \" +\n                                 body + \", which is not a string.\");\n            error.name = \"InvalidBodyException\";\n            throw error;\n        }\n    }\n\n    sinon.extend(FakeXMLHttpRequest.prototype, sinon.EventTarget, {\n        async: true,\n\n        open: function open(method, url, async, username, password) {\n            this.method = method;\n            this.url = url;\n            this.async = typeof async == \"boolean\" ? async : true;\n            this.username = username;\n            this.password = password;\n            this.responseText = null;\n            this.responseXML = null;\n            this.requestHeaders = {};\n            this.sendFlag = false;\n            if(sinon.FakeXMLHttpRequest.useFilters === true) {\n                var xhrArgs = arguments;\n                var defake = some(FakeXMLHttpRequest.filters,function(filter) {\n                    return filter.apply(this,xhrArgs)\n                });\n                if (defake) {\n                  return sinon.FakeXMLHttpRequest.defake(this,arguments);\n                }\n            }\n            this.readyStateChange(FakeXMLHttpRequest.OPENED);\n        },\n\n        readyStateChange: function readyStateChange(state) {\n            this.readyState = state;\n\n            if (typeof this.onreadystatechange == \"function\") {\n                try {\n                    this.onreadystatechange();\n                } catch (e) {\n                    sinon.logError(\"Fake XHR onreadystatechange handler\", e);\n                }\n            }\n\n            this.dispatchEvent(new sinon.Event(\"readystatechange\"));\n\n            switch (this.readyState) {\n                case FakeXMLHttpRequest.DONE:\n                    this.dispatchEvent(new sinon.Event(\"load\", false, false, this));\n                    this.dispatchEvent(new sinon.Event(\"loadend\", false, false, this));\n                    this.upload.dispatchEvent(new sinon.Event(\"load\", false, false, this));\n                    try {\n                        window.ProgressEvent && this.upload.dispatchEvent(new ProgressEvent(\"progress\", {loaded: 100, total: 100}));\n                    }\n                    catch (ex) {\n                        this.upload.dispatchEvent(document.createEvent(\"ProgressEvent\"));\n                    }\n                    break;\n            }\n        },\n\n        setRequestHeader: function setRequestHeader(header, value) {\n            verifyState(this);\n\n            if (unsafeHeaders[header] || /^(Sec-|Proxy-)/.test(header)) {\n                throw new Error(\"Refused to set unsafe header \\\"\" + header + \"\\\"\");\n            }\n\n            if (this.requestHeaders[header]) {\n                this.requestHeaders[header] += \",\" + value;\n            } else {\n                this.requestHeaders[header] = value;\n            }\n        },\n\n        // Helps testing\n        setResponseHeaders: function setResponseHeaders(headers) {\n            this.responseHeaders = {};\n\n            for (var header in headers) {\n                if (headers.hasOwnProperty(header)) {\n                    this.responseHeaders[header] = headers[header];\n                }\n            }\n\n            if (this.async) {\n                this.readyStateChange(FakeXMLHttpRequest.HEADERS_RECEIVED);\n            } else {\n                this.readyState = FakeXMLHttpRequest.HEADERS_RECEIVED;\n            }\n        },\n\n        // Currently treats ALL data as a DOMString (i.e. no Document)\n        send: function send(data) {\n            verifyState(this);\n\n            if (!/^(get|head|put)$/i.test(this.method)) {\n                if (this.requestHeaders[\"Content-Type\"]) {\n                    var value = this.requestHeaders[\"Content-Type\"].split(\";\");\n                    this.requestHeaders[\"Content-Type\"] = value[0] + \";charset=utf-8\";\n                } else {\n                    this.requestHeaders[\"Content-Type\"] = \"text/plain;charset=utf-8\";\n                }\n\n                this.requestBody = data;\n            }\n\n            this.errorFlag = false;\n            this.sendFlag = this.async;\n            this.readyStateChange(FakeXMLHttpRequest.OPENED);\n\n            if (typeof this.onSend == \"function\") {\n                this.onSend(this);\n            }\n\n            this.dispatchEvent(new sinon.Event(\"loadstart\", false, false, this));\n        },\n\n        abort: function abort() {\n            this.aborted = true;\n            this.responseText = null;\n            this.errorFlag = true;\n            this.requestHeaders = {};\n\n            if (this.readyState > sinon.FakeXMLHttpRequest.UNSENT && this.sendFlag) {\n                this.readyStateChange(sinon.FakeXMLHttpRequest.DONE);\n                this.sendFlag = false;\n            }\n\n            this.readyState = sinon.FakeXMLHttpRequest.UNSENT;\n\n            this.dispatchEvent(new sinon.Event(\"abort\", false, false, this));\n\n            this.upload.dispatchEvent(new sinon.Event(\"abort\", false, false, this));\n\n            if (typeof this.onerror === \"function\") {\n                this.onerror();\n            }\n        },\n\n        getResponseHeader: function getResponseHeader(header) {\n            if (this.readyState < FakeXMLHttpRequest.HEADERS_RECEIVED) {\n                return null;\n            }\n\n            if (/^Set-Cookie2?$/i.test(header)) {\n                return null;\n            }\n\n            header = header.toLowerCase();\n\n            for (var h in this.responseHeaders) {\n                if (h.toLowerCase() == header) {\n                    return this.responseHeaders[h];\n                }\n            }\n\n            return null;\n        },\n\n        getAllResponseHeaders: function getAllResponseHeaders() {\n            if (this.readyState < FakeXMLHttpRequest.HEADERS_RECEIVED) {\n                return \"\";\n            }\n\n            var headers = \"\";\n\n            for (var header in this.responseHeaders) {\n                if (this.responseHeaders.hasOwnProperty(header) &&\n                    !/^Set-Cookie2?$/i.test(header)) {\n                    headers += header + \": \" + this.responseHeaders[header] + \"\\r\\n\";\n                }\n            }\n\n            return headers;\n        },\n\n        setResponseBody: function setResponseBody(body) {\n            verifyRequestSent(this);\n            verifyHeadersReceived(this);\n            verifyResponseBodyType(body);\n\n            var chunkSize = this.chunkSize || 10;\n            var index = 0;\n            this.responseText = \"\";\n\n            do {\n                if (this.async) {\n                    this.readyStateChange(FakeXMLHttpRequest.LOADING);\n                }\n\n                this.responseText += body.substring(index, index + chunkSize);\n                index += chunkSize;\n            } while (index < body.length);\n\n            var type = this.getResponseHeader(\"Content-Type\");\n\n            if (this.responseText &&\n                (!type || /(text\\/xml)|(application\\/xml)|(\\+xml)/.test(type))) {\n                try {\n                    this.responseXML = FakeXMLHttpRequest.parseXML(this.responseText);\n                } catch (e) {\n                    // Unable to parse XML - no biggie\n                }\n            }\n\n            if (this.async) {\n                this.readyStateChange(FakeXMLHttpRequest.DONE);\n            } else {\n                this.readyState = FakeXMLHttpRequest.DONE;\n            }\n        },\n\n        respond: function respond(status, headers, body) {\n            this.setResponseHeaders(headers || {});\n            this.status = typeof status == \"number\" ? status : 200;\n            this.statusText = FakeXMLHttpRequest.statusCodes[this.status];\n            this.setResponseBody(body || \"\");\n        },\n\n        uploadProgress: function uploadProgress(progressEventRaw) {\n            this.upload.dispatchEvent(new ProgressEvent(\"progress\", progressEventRaw));\n        },\n\n        uploadError: function uploadError(error) {\n            this.upload.dispatchEvent(new CustomEvent(\"error\", {\"detail\": error}));\n        }\n    });\n\n    sinon.extend(FakeXMLHttpRequest, {\n        UNSENT: 0,\n        OPENED: 1,\n        HEADERS_RECEIVED: 2,\n        LOADING: 3,\n        DONE: 4\n    });\n\n    // Borrowed from JSpec\n    FakeXMLHttpRequest.parseXML = function parseXML(text) {\n        var xmlDoc;\n\n        if (typeof DOMParser != \"undefined\") {\n            var parser = new DOMParser();\n            xmlDoc = parser.parseFromString(text, \"text/xml\");\n        } else {\n            xmlDoc = new ActiveXObject(\"Microsoft.XMLDOM\");\n            xmlDoc.async = \"false\";\n            xmlDoc.loadXML(text);\n        }\n\n        return xmlDoc;\n    };\n\n    FakeXMLHttpRequest.statusCodes = {\n        100: \"Continue\",\n        101: \"Switching Protocols\",\n        200: \"OK\",\n        201: \"Created\",\n        202: \"Accepted\",\n        203: \"Non-Authoritative Information\",\n        204: \"No Content\",\n        205: \"Reset Content\",\n        206: \"Partial Content\",\n        300: \"Multiple Choice\",\n        301: \"Moved Permanently\",\n        302: \"Found\",\n        303: \"See Other\",\n        304: \"Not Modified\",\n        305: \"Use Proxy\",\n        307: \"Temporary Redirect\",\n        400: \"Bad Request\",\n        401: \"Unauthorized\",\n        402: \"Payment Required\",\n        403: \"Forbidden\",\n        404: \"Not Found\",\n        405: \"Method Not Allowed\",\n        406: \"Not Acceptable\",\n        407: \"Proxy Authentication Required\",\n        408: \"Request Timeout\",\n        409: \"Conflict\",\n        410: \"Gone\",\n        411: \"Length Required\",\n        412: \"Precondition Failed\",\n        413: \"Request Entity Too Large\",\n        414: \"Request-URI Too Long\",\n        415: \"Unsupported Media Type\",\n        416: \"Requested Range Not Satisfiable\",\n        417: \"Expectation Failed\",\n        422: \"Unprocessable Entity\",\n        500: \"Internal Server Error\",\n        501: \"Not Implemented\",\n        502: \"Bad Gateway\",\n        503: \"Service Unavailable\",\n        504: \"Gateway Timeout\",\n        505: \"HTTP Version Not Supported\"\n    };\n\n    sinon.useFakeXMLHttpRequest = function () {\n        sinon.FakeXMLHttpRequest.restore = function restore(keepOnCreate) {\n            if (xhr.supportsXHR) {\n                global.XMLHttpRequest = xhr.GlobalXMLHttpRequest;\n            }\n\n            if (xhr.supportsActiveX) {\n                global.ActiveXObject = xhr.GlobalActiveXObject;\n            }\n\n            delete sinon.FakeXMLHttpRequest.restore;\n\n            if (keepOnCreate !== true) {\n                delete sinon.FakeXMLHttpRequest.onCreate;\n            }\n        };\n        if (xhr.supportsXHR) {\n            global.XMLHttpRequest = sinon.FakeXMLHttpRequest;\n        }\n\n        if (xhr.supportsActiveX) {\n            global.ActiveXObject = function ActiveXObject(objId) {\n                if (objId == \"Microsoft.XMLHTTP\" || /^Msxml2\\.XMLHTTP/i.test(objId)) {\n\n                    return new sinon.FakeXMLHttpRequest();\n                }\n\n                return new xhr.GlobalActiveXObject(objId);\n            };\n        }\n\n        return sinon.FakeXMLHttpRequest;\n    };\n\n    sinon.FakeXMLHttpRequest = FakeXMLHttpRequest;\n\n})(typeof global === \"object\" ? global : this);\n\nif (typeof module !== 'undefined' && module.exports) {\n    module.exports = sinon;\n}\n"
  },
  {
    "path": "test/static/third-party/sinon/sinon.js",
    "content": "/*jslint eqeqeq: false, onevar: false, forin: true, nomen: false, regexp: false, plusplus: false*/\n/*global module, require, __dirname, document*/\n/**\n * Sinon core utilities. For internal use only.\n *\n * @author Christian Johansen (christian@cjohansen.no)\n * @license BSD\n *\n * Copyright (c) 2010-2013 Christian Johansen\n */\n\"use strict\";\n\nvar sinon = (function (buster) {\n    var div = typeof document != \"undefined\" && document.createElement(\"div\");\n    var hasOwn = Object.prototype.hasOwnProperty;\n\n    function isDOMNode(obj) {\n        var success = false;\n\n        try {\n            obj.appendChild(div);\n            success = div.parentNode == obj;\n        } catch (e) {\n            return false;\n        } finally {\n            try {\n                obj.removeChild(div);\n            } catch (e) {\n                // Remove failed, not much we can do about that\n            }\n        }\n\n        return success;\n    }\n\n    function isElement(obj) {\n        return div && obj && obj.nodeType === 1 && isDOMNode(obj);\n    }\n\n    function isFunction(obj) {\n        return typeof obj === \"function\" || !!(obj && obj.constructor && obj.call && obj.apply);\n    }\n\n    function mirrorProperties(target, source) {\n        for (var prop in source) {\n            if (!hasOwn.call(target, prop)) {\n                target[prop] = source[prop];\n            }\n        }\n    }\n\n    function isRestorable (obj) {\n        return typeof obj === \"function\" && typeof obj.restore === \"function\" && obj.restore.sinon;\n    }\n\n    var sinon = {\n        wrapMethod: function wrapMethod(object, property, method) {\n            if (!object) {\n                throw new TypeError(\"Should wrap property of object\");\n            }\n\n            if (typeof method != \"function\") {\n                throw new TypeError(\"Method wrapper should be function\");\n            }\n\n            var wrappedMethod = object[property],\n                error;\n\n            if (!isFunction(wrappedMethod)) {\n                error = new TypeError(\"Attempted to wrap \" + (typeof wrappedMethod) + \" property \" +\n                                    property + \" as function\");\n            }\n\n            if (wrappedMethod.restore && wrappedMethod.restore.sinon) {\n                error = new TypeError(\"Attempted to wrap \" + property + \" which is already wrapped\");\n            }\n\n            if (wrappedMethod.calledBefore) {\n                var verb = !!wrappedMethod.returns ? \"stubbed\" : \"spied on\";\n                error = new TypeError(\"Attempted to wrap \" + property + \" which is already \" + verb);\n            }\n\n            if (error) {\n                if (wrappedMethod._stack) {\n                    error.stack += '\\n--------------\\n' + wrappedMethod._stack;\n                }\n                throw error;\n            }\n\n            // IE 8 does not support hasOwnProperty on the window object.\n            var owned = hasOwn.call(object, property);\n            object[property] = method;\n            method.displayName = property;\n            // Set up a stack trace which can be used later to find what line of\n            // code the original method was created on.\n            method._stack = (new Error('Stack Trace for original')).stack;\n\n            method.restore = function () {\n                // For prototype properties try to reset by delete first.\n                // If this fails (ex: localStorage on mobile safari) then force a reset\n                // via direct assignment.\n                if (!owned) {\n                    delete object[property];\n                }\n                if (object[property] === method) {\n                    object[property] = wrappedMethod;\n                }\n            };\n\n            method.restore.sinon = true;\n            mirrorProperties(method, wrappedMethod);\n\n            return method;\n        },\n\n        extend: function extend(target) {\n            for (var i = 1, l = arguments.length; i < l; i += 1) {\n                for (var prop in arguments[i]) {\n                    if (arguments[i].hasOwnProperty(prop)) {\n                        target[prop] = arguments[i][prop];\n                    }\n\n                    // DON'T ENUM bug, only care about toString\n                    if (arguments[i].hasOwnProperty(\"toString\") &&\n                        arguments[i].toString != target.toString) {\n                        target.toString = arguments[i].toString;\n                    }\n                }\n            }\n\n            return target;\n        },\n\n        create: function create(proto) {\n            var F = function () {};\n            F.prototype = proto;\n            return new F();\n        },\n\n        deepEqual: function deepEqual(a, b) {\n            if (sinon.match && sinon.match.isMatcher(a)) {\n                return a.test(b);\n            }\n            if (typeof a != \"object\" || typeof b != \"object\") {\n                return a === b;\n            }\n\n            if (isElement(a) || isElement(b)) {\n                return a === b;\n            }\n\n            if (a === b) {\n                return true;\n            }\n\n            if ((a === null && b !== null) || (a !== null && b === null)) {\n                return false;\n            }\n\n            var aString = Object.prototype.toString.call(a);\n            if (aString != Object.prototype.toString.call(b)) {\n                return false;\n            }\n\n            if (aString == \"[object Date]\") {\n                return a.valueOf() === b.valueOf();\n            }\n\n            var prop, aLength = 0, bLength = 0;\n\n            if (aString == \"[object Array]\" && a.length !== b.length) {\n                return false;\n            }\n\n            for (prop in a) {\n                aLength += 1;\n\n                if (!deepEqual(a[prop], b[prop])) {\n                    return false;\n                }\n            }\n\n            for (prop in b) {\n                bLength += 1;\n            }\n\n            return aLength == bLength;\n        },\n\n        functionName: function functionName(func) {\n            var name = func.displayName || func.name;\n\n            // Use function decomposition as a last resort to get function\n            // name. Does not rely on function decomposition to work - if it\n            // doesn't debugging will be slightly less informative\n            // (i.e. toString will say 'spy' rather than 'myFunc').\n            if (!name) {\n                var matches = func.toString().match(/function ([^\\s\\(]+)/);\n                name = matches && matches[1];\n            }\n\n            return name;\n        },\n\n        functionToString: function toString() {\n            if (this.getCall && this.callCount) {\n                var thisValue, prop, i = this.callCount;\n\n                while (i--) {\n                    thisValue = this.getCall(i).thisValue;\n\n                    for (prop in thisValue) {\n                        if (thisValue[prop] === this) {\n                            return prop;\n                        }\n                    }\n                }\n            }\n\n            return this.displayName || \"sinon fake\";\n        },\n\n        getConfig: function (custom) {\n            var config = {};\n            custom = custom || {};\n            var defaults = sinon.defaultConfig;\n\n            for (var prop in defaults) {\n                if (defaults.hasOwnProperty(prop)) {\n                    config[prop] = custom.hasOwnProperty(prop) ? custom[prop] : defaults[prop];\n                }\n            }\n\n            return config;\n        },\n\n        format: function (val) {\n            return \"\" + val;\n        },\n\n        defaultConfig: {\n            injectIntoThis: true,\n            injectInto: null,\n            properties: [\"spy\", \"stub\", \"mock\", \"clock\", \"server\", \"requests\"],\n            useFakeTimers: true,\n            useFakeServer: true\n        },\n\n        timesInWords: function timesInWords(count) {\n            return count == 1 && \"once\" ||\n                count == 2 && \"twice\" ||\n                count == 3 && \"thrice\" ||\n                (count || 0) + \" times\";\n        },\n\n        calledInOrder: function (spies) {\n            for (var i = 1, l = spies.length; i < l; i++) {\n                if (!spies[i - 1].calledBefore(spies[i]) || !spies[i].called) {\n                    return false;\n                }\n            }\n\n            return true;\n        },\n\n        orderByFirstCall: function (spies) {\n            return spies.sort(function (a, b) {\n                // uuid, won't ever be equal\n                var aCall = a.getCall(0);\n                var bCall = b.getCall(0);\n                var aId = aCall && aCall.callId || -1;\n                var bId = bCall && bCall.callId || -1;\n\n                return aId < bId ? -1 : 1;\n            });\n        },\n\n        log: function () {},\n\n        logError: function (label, err) {\n            var msg = label + \" threw exception: \"\n            sinon.log(msg + \"[\" + err.name + \"] \" + err.message);\n            if (err.stack) { sinon.log(err.stack); }\n\n            setTimeout(function () {\n                err.message = msg + err.message;\n                throw err;\n            }, 0);\n        },\n\n        typeOf: function (value) {\n            if (value === null) {\n                return \"null\";\n            }\n            else if (value === undefined) {\n                return \"undefined\";\n            }\n            var string = Object.prototype.toString.call(value);\n            return string.substring(8, string.length - 1).toLowerCase();\n        },\n\n        createStubInstance: function (constructor) {\n            if (typeof constructor !== \"function\") {\n                throw new TypeError(\"The constructor should be a function.\");\n            }\n            return sinon.stub(sinon.create(constructor.prototype));\n        },\n\n        restore: function (object) {\n            if (object !== null && typeof object === \"object\") {\n                for (var prop in object) {\n                    if (isRestorable(object[prop])) {\n                        object[prop].restore();\n                    }\n                }\n            }\n            else if (isRestorable(object)) {\n                object.restore();\n            }\n        }\n    };\n\n    var isNode = typeof module !== \"undefined\" && module.exports;\n    var isAMD = typeof define === 'function' && typeof define.amd === 'object' && define.amd;\n\n    if (isAMD) {\n        define(function(){\n            return sinon;\n        });\n    } else if (isNode) {\n        try {\n            buster = { format: require(\"buster-format\") };\n        } catch (e) {}\n        module.exports = sinon;\n        module.exports.spy = require(\"./../../../../../../Downloads/Sinon.JS-master/lib/sinon/spy\");\n        module.exports.spyCall = require(\"./../../../../../../Downloads/Sinon.JS-master/lib/sinon/call\");\n        module.exports.behavior = require(\"./../../../../../../Downloads/Sinon.JS-master/lib/sinon/behavior\");\n        module.exports.stub = require(\"./../../../../../../Downloads/Sinon.JS-master/lib/sinon/stub\");\n        module.exports.mock = require(\"./../../../../../../Downloads/Sinon.JS-master/lib/sinon/mock\");\n        module.exports.collection = require(\"./../../../../../../Downloads/Sinon.JS-master/lib/sinon/collection\");\n        module.exports.assert = require(\"./../../../../../../Downloads/Sinon.JS-master/lib/sinon/assert\");\n        module.exports.sandbox = require(\"./../../../../../../Downloads/Sinon.JS-master/lib/sinon/sandbox\");\n        module.exports.test = require(\"./../../../../../../Downloads/Sinon.JS-master/lib/sinon/test\");\n        module.exports.testCase = require(\"./../../../../../../Downloads/Sinon.JS-master/lib/sinon/test_case\");\n        module.exports.assert = require(\"./../../../../../../Downloads/Sinon.JS-master/lib/sinon/assert\");\n        module.exports.match = require(\"./../../../../../../Downloads/Sinon.JS-master/lib/sinon/match\");\n    }\n\n    if (buster) {\n        var formatter = sinon.create(buster.format);\n        formatter.quoteStrings = false;\n        sinon.format = function () {\n            return formatter.ascii.apply(formatter, arguments);\n        };\n    } else if (isNode) {\n        try {\n            var util = require(\"util\");\n            sinon.format = function (value) {\n                return typeof value == \"object\" && value.toString === Object.prototype.toString ? util.inspect(value) : value;\n            };\n        } catch (e) {\n            /* Node, but no util module - would be very old, but better safe than\n             sorry */\n        }\n    }\n\n    return sinon;\n}(typeof buster == \"object\" && buster));\n"
  },
  {
    "path": "test/unit/azure/chunked-uploads.js",
    "content": "/* globals describe, beforeEach, $fixture, qq, assert, it, qqtest, helpme, purl */\ndescribe(\"azure chunked upload tests\", function() {\n    \"use strict\";\n\n    if (qqtest.canDownloadFileAsBlob) {\n        var fileTestHelper = helpme.setupFileTests(),\n            testContainerEndpoint = \"https://azureaccount.blob.core.windows.net/testContainer\",\n            expectedFileSize = 3266,\n            expectedChunks = 2,\n            chunkSize = Math.round(expectedFileSize / expectedChunks),\n            typicalChunkingOption = {\n                enabled: true,\n                partSize: chunkSize,\n                minFileSize: expectedFileSize\n            };\n\n        describe(\"server-side signature-based chunked Azure upload tests\", function() {\n            var startTypicalTest = function(uploader, callback) {\n                qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function (blob) {\n                    var signatureRequest, signatureRequestPurl;\n\n                    fileTestHelper.mockXhr();\n                    uploader.addFiles({name: \"test.jpg\", blob: blob});\n\n                    assert.equal(fileTestHelper.getRequests().length, 1, \"Wrong # of requests\");\n\n                    signatureRequest = fileTestHelper.getRequests()[0];\n                    assert.equal(signatureRequest.method, \"GET\");\n\n                    signatureRequestPurl = purl(signatureRequest.url);\n                    assert.equal(signatureRequestPurl.attr(\"path\"), testSignatureEndoint);\n\n                    callback(signatureRequest, signatureRequestPurl);\n                });\n            },\n            testSignatureEndoint = \"/signature\",\n            typicalRequestOption = {\n                endpoint: testContainerEndpoint\n            },\n            typicalSignatureOption = {\n                endpoint: testSignatureEndoint\n            };\n\n            it(\"does not chunk if file is too small\", function(done) {\n                assert.expect(8, done);\n\n                var uploader = new qq.azure.FineUploaderBasic({\n                        request: typicalRequestOption,\n                        signature: typicalSignatureOption,\n                        chunking: {\n                            enabled: true,\n                            partSize: typicalChunkingOption.partSize,\n                            minFileSize: expectedFileSize + 1\n                        }\n                    }\n                );\n\n                startTypicalTest(uploader, function(signatureRequest, signatureRequestPurl) {\n                    var expectedSasUri = \"http://sasuri.com\",\n                        uploadRequest;\n\n                    // signature request for upload part 1\n                    assert.equal(signatureRequestPurl.param(\"_method\"), \"PUT\");\n                    assert.equal(signatureRequestPurl.param(\"bloburi\"), testContainerEndpoint + \"/\" + uploader.getBlobName(0));\n                    signatureRequest.respond(200, null, expectedSasUri);\n\n                    // upload request\n                    assert.equal(fileTestHelper.getRequests().length, 2);\n                    uploadRequest = fileTestHelper.getRequests()[1];\n                    assert.equal(uploadRequest.method, \"PUT\");\n                    assert.equal(uploadRequest.url, expectedSasUri);\n                });\n            });\n\n            it(\"handles a basic chunked upload\", function(done) {\n                assert.expect(57, done);\n\n                var uploadChunkCalled = false,\n                    uploadChunkSuccessCalled = false,\n                    verifyChunkData = function(onUploadChunkSuccess, chunkData) {\n                        if (onUploadChunkSuccess && uploadChunkSuccessCalled || !onUploadChunkSuccess && uploadChunkCalled) {\n                            assert.equal(chunkData.partIndex, 1);\n                            assert.equal(chunkData.startByte, chunkSize + 1);\n                            assert.equal(chunkData.endByte,  expectedFileSize);\n                            assert.equal(chunkData.totalParts, 2);\n                        }\n                        else {\n                            if (onUploadChunkSuccess) {\n                                uploadChunkSuccessCalled = true;\n                            }\n                            else {\n                                uploadChunkCalled = true;\n                            }\n\n                            assert.equal(chunkData.partIndex, 0);\n                            assert.equal(chunkData.startByte, 1);\n                            assert.equal(chunkData.endByte,  chunkSize);\n                            assert.equal(chunkData.totalParts, 2);\n                        }\n                    },\n                    uploader = new qq.azure.FineUploaderBasic({\n                        request: typicalRequestOption,\n                        signature: typicalSignatureOption,\n                        chunking: typicalChunkingOption,\n                        callbacks: {\n                            onComplete: function(id, name, response, xhr) {\n                                assert.equal(id, 0, \"Wrong ID passed to onComplete\");\n                                assert.equal(name, uploader.getName(0), \"Wrong name passed to onComplete\");\n                                assert.ok(response, \"Null response passed to onComplete\");\n                                assert.ok(xhr, \"Null XHR passed to onComplete\");\n                            },\n                            onUploadChunk: function(id, name, chunkData) {\n                                //should be called twice each (1 for each chunk)\n                                assert.equal(id, 0, \"Wrong ID passed to onUploadChunk\");\n                                assert.equal(name, uploader.getName(0), \"Wrong name passed to onUploadChunk\");\n\n                                verifyChunkData(false, chunkData);\n                            },\n                            onUploadChunkSuccess: function(id, chunkData, response, xhr) {\n                                //should be called twice each (1 for each chunk)\n                                assert.equal(id, 0, \"Wrong ID passed to onUploadChunkSuccess\");\n                                assert.ok(response, \"Null response passed to onUploadChunkSuccess\");\n                                assert.ok(xhr, \"Null XHR passed to onUploadChunkSuccess\");\n\n                                verifyChunkData(true, chunkData);\n                            }\n                        }\n                    }\n                );\n\n                startTypicalTest(uploader, function(signatureRequest, signatureRequestPurl) {\n                    var expectedSasUri = \"http://sasuri.com\",\n                        uploadRequest, putBlockListRequest;\n\n                    // signature request for upload part 1\n                    assert.equal(signatureRequestPurl.param(\"_method\"), \"PUT\");\n                    assert.equal(signatureRequestPurl.param(\"bloburi\"), testContainerEndpoint + \"/\" + uploader.getBlobName(0));\n                    signatureRequest.respond(200, null, expectedSasUri);\n\n                    // upload part 1 request\n                    assert.equal(fileTestHelper.getRequests().length, 2);\n                    uploadRequest = fileTestHelper.getRequests()[1];\n                    assert.ok(!uploadRequest.requestHeaders[\"x-ms-meta-qqfilename\"]);\n                    assert.equal(uploadRequest.method, \"PUT\");\n                    assert.equal(uploadRequest.url, expectedSasUri + \"&comp=block&blockid=\" + encodeURIComponent(btoa(\"00000\")));\n                    uploadRequest.respond(201, null, null);\n\n                    // signature request for upload part 2\n                    assert.equal(fileTestHelper.getRequests().length, 3);\n                    signatureRequest = fileTestHelper.getRequests()[2];\n                    assert.equal(signatureRequest.method, \"GET\");\n                    signatureRequestPurl = purl(signatureRequest.url);\n                    assert.equal(signatureRequestPurl.param(\"_method\"), \"PUT\");\n                    assert.equal(signatureRequestPurl.param(\"bloburi\"), testContainerEndpoint + \"/\" + uploader.getBlobName(0));\n                    signatureRequest.respond(200, null, expectedSasUri);\n\n                    // upload part 2 request\n                    assert.equal(fileTestHelper.getRequests().length, 4);\n                    uploadRequest = fileTestHelper.getRequests()[3];\n                    assert.ok(!uploadRequest.requestHeaders[\"x-ms-meta-qqfilename\"]);\n                    assert.equal(uploadRequest.method, \"PUT\");\n                    assert.equal(uploadRequest.url, expectedSasUri + \"&comp=block&blockid=\" + encodeURIComponent(btoa(\"00001\")));\n                    uploadRequest.respond(201, null, null);\n\n                    // signature request for put block list\n                    assert.equal(fileTestHelper.getRequests().length, 5);\n                    signatureRequest = fileTestHelper.getRequests()[4];\n                    assert.equal(signatureRequest.method, \"GET\");\n                    signatureRequestPurl = purl(signatureRequest.url);\n                    assert.equal(signatureRequestPurl.param(\"_method\"), \"PUT\");\n                    assert.equal(signatureRequestPurl.param(\"bloburi\"), testContainerEndpoint + \"/\" + uploader.getBlobName(0));\n                    signatureRequest.respond(200, null, expectedSasUri);\n\n                    // put block list request\n                    assert.equal(fileTestHelper.getRequests().length, 6);\n                    putBlockListRequest = fileTestHelper.getRequests()[5];\n                    assert.equal(putBlockListRequest.method, \"PUT\");\n                    assert.equal(putBlockListRequest.url, expectedSasUri + \"&comp=blocklist\");\n                    assert.equal(putBlockListRequest.requestHeaders[\"x-ms-blob-content-type\"], \"image/jpeg\");\n                    assert.equal(putBlockListRequest.requestHeaders[\"x-ms-meta-qqfilename\"], uploader.getName(0));\n                    putBlockListRequest.respond(201, null, null);\n\n                    assert.equal(uploader.getUploads()[0].status, qq.status.UPLOAD_SUCCESSFUL);\n                });\n            });\n\n            it(\"handles failures at every step of a chunked upload\", function(done) {\n                assert.expect(71, done);\n\n                var uploader = new qq.azure.FineUploaderBasic({\n                        request: typicalRequestOption,\n                        signature: typicalSignatureOption,\n                        chunking: typicalChunkingOption,\n                        callbacks: {\n                            onComplete: function(id, name, response) {\n                                onCompleteCallbacks++;\n\n                                if (onCompleteCallbacks < 7) {\n                                    assert.ok(!response.success);\n                                }\n                                else {\n                                    assert.ok(response.success);\n                                }\n                            },\n                            onManualRetry: function(id, name) {\n                                //expected to be called once for each retry\n                                assert.equal(id, 0);\n                                assert.equal(name, uploader.getName(0));\n                            }\n                        }\n                    }\n                ),\n                    onCompleteCallbacks = 0;\n\n                startTypicalTest(uploader, function(signatureRequest, signatureRequestPurl) {\n                    var expectedSasUri = \"http://sasuri.com\",\n                        uploadRequest, putBlockListRequest;\n\n                    assert.equal(uploader.getUploads()[0].status, qq.status.UPLOADING);\n\n                    // failing signature request for upload part 1\n                    assert.equal(signatureRequestPurl.param(\"bloburi\"), testContainerEndpoint + \"/\" + uploader.getBlobName(0));\n                    signatureRequest.respond(500, null, null);\n                    assert.equal(fileTestHelper.getRequests().length, 1);\n                    assert.equal(uploader.getUploads()[0].status, qq.status.UPLOAD_FAILED);\n                    uploader.retry(0);\n                    assert.equal(fileTestHelper.getRequests().length, 2);\n                    assert.equal(uploader.getUploads()[0].status, qq.status.UPLOADING);\n\n                    // successful signature request for upload part 1\n                    signatureRequest = fileTestHelper.getRequests()[1];\n                    signatureRequestPurl = purl(signatureRequest.url);\n                    assert.equal(signatureRequestPurl.param(\"bloburi\"), testContainerEndpoint + \"/\" + uploader.getBlobName(0));\n                    signatureRequest.respond(200, null, expectedSasUri);\n\n                    // failing upload part 1 request\n                    assert.equal(fileTestHelper.getRequests().length, 3);\n                    uploadRequest = fileTestHelper.getRequests()[2];\n                    assert.equal(uploadRequest.url, expectedSasUri + \"&comp=block&blockid=\" + encodeURIComponent(btoa(\"00000\")));\n                    uploadRequest.respond(404, null, null);\n                    assert.equal(uploader.getUploads()[0].status, qq.status.UPLOAD_FAILED);\n                    uploader.retry(0);\n                    assert.equal(fileTestHelper.getRequests().length, 4);\n                    assert.equal(uploader.getUploads()[0].status, qq.status.UPLOADING);\n\n                    // successful signature request for upload part 1\n                    signatureRequest = fileTestHelper.getRequests()[3];\n                    signatureRequestPurl = purl(signatureRequest.url);\n                    assert.equal(signatureRequestPurl.param(\"bloburi\"), testContainerEndpoint + \"/\" + uploader.getBlobName(0));\n                    signatureRequest.respond(200, null, expectedSasUri);\n\n                    // successful upload part 1 request\n                    assert.equal(fileTestHelper.getRequests().length, 5);\n                    uploadRequest = fileTestHelper.getRequests()[4];\n                    assert.equal(uploadRequest.url, expectedSasUri + \"&comp=block&blockid=\" + encodeURIComponent(btoa(\"00000\")));\n                    uploadRequest.respond(201, null, null);\n\n                    // failing signature request for upload part 2\n                    assert.equal(fileTestHelper.getRequests().length, 6);\n                    signatureRequest = fileTestHelper.getRequests()[5];\n                    signatureRequestPurl = purl(signatureRequest.url);\n                    assert.equal(signatureRequestPurl.param(\"bloburi\"), testContainerEndpoint + \"/\" + uploader.getBlobName(0));\n                    signatureRequest.respond(500, null, null);\n                    assert.equal(fileTestHelper.getRequests().length, 6);\n                    assert.equal(uploader.getUploads()[0].status, qq.status.UPLOAD_FAILED);\n                    uploader.retry(0);\n                    assert.equal(fileTestHelper.getRequests().length, 7);\n                    assert.equal(uploader.getUploads()[0].status, qq.status.UPLOADING);\n\n                    // successful signature request for upload part 2\n                    signatureRequest = fileTestHelper.getRequests()[6];\n                    signatureRequestPurl = purl(signatureRequest.url);\n                    assert.equal(signatureRequestPurl.param(\"bloburi\"), testContainerEndpoint + \"/\" + uploader.getBlobName(0));\n                    signatureRequest.respond(200, null, expectedSasUri);\n\n                    // failing upload part 2 request\n                    assert.equal(fileTestHelper.getRequests().length, 8);\n                    uploadRequest = fileTestHelper.getRequests()[7];\n                    assert.equal(uploadRequest.url, expectedSasUri + \"&comp=block&blockid=\" + encodeURIComponent(btoa(\"00001\")));\n                    uploadRequest.respond(404, null, null);\n                    assert.equal(uploader.getUploads()[0].status, qq.status.UPLOAD_FAILED);\n                    uploader.retry(0);\n                    assert.equal(fileTestHelper.getRequests().length, 9);\n                    assert.equal(uploader.getUploads()[0].status, qq.status.UPLOADING);\n\n                    // successful signature request for upload part 1\n                    signatureRequest = fileTestHelper.getRequests()[8];\n                    signatureRequestPurl = purl(signatureRequest.url);\n                    assert.equal(signatureRequestPurl.param(\"bloburi\"), testContainerEndpoint + \"/\" + uploader.getBlobName(0));\n                    signatureRequest.respond(200, null, expectedSasUri);\n\n                    // successful upload part 2 request\n                    assert.equal(fileTestHelper.getRequests().length, 10);\n                    uploadRequest = fileTestHelper.getRequests()[9];\n                    assert.equal(uploadRequest.method, \"PUT\");\n                    assert.equal(uploadRequest.url, expectedSasUri + \"&comp=block&blockid=\" + encodeURIComponent(btoa(\"00001\")));\n                    uploadRequest.respond(201, null, null);\n\n                    // failing signature request for put block list\n                    assert.equal(fileTestHelper.getRequests().length, 11);\n                    signatureRequest = fileTestHelper.getRequests()[10];\n                    signatureRequestPurl = purl(signatureRequest.url);\n                    assert.equal(signatureRequestPurl.param(\"bloburi\"), testContainerEndpoint + \"/\" + uploader.getBlobName(0));\n                    signatureRequest.respond(500, null, null);\n                    assert.equal(fileTestHelper.getRequests().length, 11);\n                    assert.equal(uploader.getUploads()[0].status, qq.status.UPLOAD_FAILED);\n                    uploader.retry(0);\n                    assert.equal(fileTestHelper.getRequests().length, 12);\n                    assert.equal(uploader.getUploads()[0].status, qq.status.UPLOADING);\n\n                    // successful signature request for put block list\n                    signatureRequest = fileTestHelper.getRequests()[11];\n                    signatureRequestPurl = purl(signatureRequest.url);\n                    assert.equal(signatureRequestPurl.param(\"bloburi\"), testContainerEndpoint + \"/\" + uploader.getBlobName(0));\n                    signatureRequest.respond(200, null, expectedSasUri);\n\n                    // failing put block list request\n                    assert.equal(fileTestHelper.getRequests().length, 13);\n                    putBlockListRequest = fileTestHelper.getRequests()[12];\n                    assert.equal(putBlockListRequest.url, expectedSasUri + \"&comp=blocklist\");\n                    assert.equal(putBlockListRequest.requestHeaders[\"x-ms-blob-content-type\"], \"image/jpeg\");\n                    putBlockListRequest.respond(404, null, null);\n                    assert.equal(uploader.getUploads()[0].status, qq.status.UPLOAD_FAILED);\n                    uploader.retry(0);\n                    assert.equal(fileTestHelper.getRequests().length, 14);\n                    assert.equal(uploader.getUploads()[0].status, qq.status.UPLOADING);\n\n                    // successful signature request for put block list\n                    signatureRequest = fileTestHelper.getRequests()[13];\n                    signatureRequestPurl = purl(signatureRequest.url);\n                    assert.equal(signatureRequestPurl.param(\"bloburi\"), testContainerEndpoint + \"/\" + uploader.getBlobName(0));\n                    signatureRequest.respond(200, null, expectedSasUri);\n\n                    // successful put block list request\n                    assert.equal(fileTestHelper.getRequests().length, 15);\n                    putBlockListRequest = fileTestHelper.getRequests()[14];\n                    assert.equal(putBlockListRequest.url, expectedSasUri + \"&comp=blocklist\");\n                    assert.equal(putBlockListRequest.requestHeaders[\"x-ms-blob-content-type\"], \"image/jpeg\");\n                    putBlockListRequest.respond(201, null, null);\n\n                    assert.equal(uploader.getUploads()[0].status, qq.status.UPLOAD_SUCCESSFUL);\n                });\n            });\n        });\n    }\n});\n"
  },
  {
    "path": "test/unit/azure/delete-files.js",
    "content": "/* globals describe, beforeEach, $fixture, qq, assert, it, qqtest, helpme, purl */\nif (qqtest.canDownloadFileAsBlob) {\n    describe(\"test the delete file feature for Azure\", function() {\n        \"use strict\";\n\n        var fileTestHelper = helpme.setupFileTests(),\n            testEndpoint = \"https://testcontainer.com\",\n            testSignatureEndoint = \"http://signature-server.com/signature\",\n            startTypicalTest = function(uploader, callback) {\n                qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function (blob) {\n                    var signatureRequest;\n\n                    fileTestHelper.mockXhr();\n                    uploader.addFiles({name: \"test.jpg\", blob: blob});\n\n                    assert.equal(fileTestHelper.getRequests().length, 1, \"Wrong # of requests\");\n\n                    signatureRequest = fileTestHelper.getRequests()[0];\n                    signatureRequest.respond(200, null, \"http://sasuri.com\");\n\n                    setTimeout(function() {\n                        var uploadRequest = fileTestHelper.getRequests()[1];\n                        uploadRequest.respond(201, null, \"\");\n\n                        callback();\n                    }, 0);\n                });\n            };\n\n        it(\"does not attempt to delete a file if feature is disabled\", function(done) {\n            assert.expect(2, done);\n\n            var uploader = new qq.azure.FineUploaderBasic({\n                    request: {endpoint: testEndpoint},\n                    signature: {endpoint: testSignatureEndoint}\n                }\n            );\n\n            startTypicalTest(uploader, function() {\n                assert.ok(!uploader.deleteFile(0));\n            });\n        });\n\n        it(\"properly deletes a file if the feature is enabled\", function(done) {\n            assert.expect(16, done);\n\n            var expectedCallbackOrder = [\"delete\", \"deleteComplete\"],\n                actualCallbackOrder = [],\n                uploader = new qq.azure.FineUploaderBasic({\n                    request: {endpoint: testEndpoint},\n                    signature: {endpoint: testSignatureEndoint},\n                    deleteFile: {\n                        enabled: true\n                    },\n                    callbacks: {\n                        onDelete: function(id) {\n                            actualCallbackOrder.push(\"delete\");\n                            assert.equal(id, 0);\n                        },\n                        onDeleteComplete: function(id, xhr, isError) {\n                            actualCallbackOrder.push(\"deleteComplete\");\n                            assert.equal(id, 0);\n                            assert.ok(xhr);\n                            assert.ok(!isError);\n                        }\n                    }\n                }\n            );\n\n            startTypicalTest(uploader, function() {\n                var deleteFileSignatureRequest, deleteFileRequest, purlSignatureRequest,\n                    blobName = uploader.getBlobName(0),\n                    blobUri = testEndpoint + \"/\" + blobName;\n\n                assert.ok(uploader.deleteFile(0));\n                assert.equal(fileTestHelper.getRequests().length, 3);\n                deleteFileSignatureRequest = fileTestHelper.getRequests()[2];\n                purlSignatureRequest = purl(deleteFileSignatureRequest.url);\n\n                assert.equal(deleteFileSignatureRequest.method, \"GET\");\n\n                assert.equal(purlSignatureRequest.param(\"bloburi\"), blobUri);\n                assert.equal(purlSignatureRequest.param(\"_method\"), \"DELETE\");\n                assert.ok(purlSignatureRequest.param(\"qqtimestamp\"));\n\n                deleteFileSignatureRequest.respond(200, null, \"http://sasuri.com\");\n\n                assert.equal(fileTestHelper.getRequests().length, 4);\n                deleteFileRequest = fileTestHelper.getRequests()[3];\n\n                assert.equal(deleteFileRequest.method, \"DELETE\");\n                assert.equal(deleteFileRequest.url, \"http://sasuri.com\");\n\n                deleteFileRequest.respond(202, null, null);\n\n                assert.equal(uploader.getUploads()[0].status, qq.status.DELETED);\n                assert.deepEqual(actualCallbackOrder, expectedCallbackOrder);\n            });\n        });\n\n        it(\"properly fails a delete operation if the signature request fails\", function(done) {\n            assert.expect(6, done);\n\n            var uploader = new qq.azure.FineUploaderBasic({\n                    request: {endpoint: testEndpoint},\n                    signature: {endpoint: testSignatureEndoint},\n                    deleteFile: {\n                        enabled: true\n                    },\n                    callbacks: {\n                        onDeleteComplete: function(id, xhr, isError) {\n                            assert.equal(id, 0);\n                            assert.ok(xhr);\n                            assert.ok(isError);\n                        }\n                    }\n                }\n            );\n\n            startTypicalTest(uploader, function() {\n                var deleteFileSignatureRequest;\n\n                uploader.deleteFile(0);\n                deleteFileSignatureRequest = fileTestHelper.getRequests()[2];\n                deleteFileSignatureRequest.respond(500, null, null);\n\n                assert.equal(fileTestHelper.getRequests().length, 3);\n                assert.equal(uploader.getUploads()[0].status, qq.status.DELETE_FAILED);\n            });\n        });\n\n        it(\"properly fails a delete operation if the delete request to Azure fails\", function(done) {\n            assert.expect(6, done);\n\n            var uploader = new qq.azure.FineUploaderBasic({\n                    request: {endpoint: testEndpoint},\n                    signature: {endpoint: testSignatureEndoint},\n                    deleteFile: {\n                        enabled: true\n                    },\n                    callbacks: {\n                        onDeleteComplete: function(id, xhr, isError) {\n                            assert.equal(id, 0);\n                            assert.ok(xhr);\n                            assert.ok(isError);\n                        }\n                    }\n                }\n            );\n\n            startTypicalTest(uploader, function() {\n                var deleteFileSignatureRequest, deleteFileRequest;\n\n                uploader.deleteFile(0);\n                deleteFileSignatureRequest = fileTestHelper.getRequests()[2];\n                deleteFileSignatureRequest.respond(200, null, \"http://sasuri.com\");\n\n                assert.equal(fileTestHelper.getRequests().length, 4);\n                deleteFileRequest = fileTestHelper.getRequests()[3];\n                deleteFileRequest.respond(500, null, null);\n\n                assert.equal(uploader.getUploads()[0].status, qq.status.DELETE_FAILED);\n            });\n        });\n    });\n}\n"
  },
  {
    "path": "test/unit/azure/simple-file-uploads.js",
    "content": "/* globals describe, beforeEach, $fixture, qq, assert, it, qqtest, helpme, purl, Q */\nif (qqtest.canDownloadFileAsBlob) {\n    describe(\"simple Azure upload tests\", function() {\n        \"use strict\";\n\n        var fileTestHelper = helpme.setupFileTests(),\n            testEndpoint = \"https://testcontainer.com\",\n            testSignatureEndoint = \"http://signature-server.com/signature\",\n            startTypicalTest = function(uploader, callback, filename) {\n                filename = filename || \"test.jpg\";\n                qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function (blob) {\n                    var signatureRequest;\n\n                    fileTestHelper.mockXhr();\n                    uploader.addFiles({name: filename, blob: blob});\n\n                    setTimeout(function() {\n                        assert.equal(fileTestHelper.getRequests().length, 1, \"Wrong # of requests\");\n\n                        signatureRequest = fileTestHelper.getRequests()[0];\n\n                        callback(signatureRequest);\n                    }, 10);\n                });\n            };\n\n        it(\"test most basic upload w/ signature request\", function(done) {\n            assert.expect(12, done);\n\n            var expectedSasUri = \"http://sasuri.com\",\n                uploader = new qq.azure.FineUploaderBasic({\n                    request: {endpoint: testEndpoint},\n                    signature: {endpoint: testSignatureEndoint}\n                }\n            );\n\n            startTypicalTest(uploader, function(signatureRequest) {\n                var uploadRequest,\n                    blobName = uploader.getBlobName(0),\n                    blobUri = testEndpoint + \"/\" + blobName,\n                    purlSignatureUrl = purl(signatureRequest.url);\n\n                assert.equal(blobName, uploader.getUuid(0) + \".\" + qq.getExtension(uploader.getName(0)));\n                assert.equal(signatureRequest.method, \"GET\");\n\n                assert.equal(purlSignatureUrl.param(\"bloburi\"), blobUri);\n                assert.equal(purlSignatureUrl.param(\"_method\"), \"PUT\");\n                assert.ok(purlSignatureUrl.param(\"qqtimestamp\"));\n\n                signatureRequest.respond(200, null, expectedSasUri);\n\n                setTimeout(function() {\n                    assert.equal(fileTestHelper.getRequests().length, 2);\n                    uploadRequest = fileTestHelper.getRequests()[1];\n\n                    assert.equal(uploadRequest.url, expectedSasUri);\n                    assert.equal(uploadRequest.method, \"PUT\");\n                    assert.equal(uploadRequest.requestHeaders[\"x-ms-blob-type\"], \"BlockBlob\");\n                    assert.equal(uploadRequest.requestHeaders[\"x-ms-meta-qqfilename\"], uploader.getName(0));\n                    assert.equal(uploadRequest.requestHeaders[\"Content-Type\"].indexOf(\"image/jpeg\"), 0);\n                }, 0);\n            });\n        });\n\n        it(\"test most basic upload w/ signature request that includes custom headers\", function(done) {\n            assert.expect(2, done);\n\n            var expectedSignatureHeaders = {\n                    foo: \"bar\"\n                },\n                uploader = new qq.azure.FineUploaderBasic({\n                    request: {endpoint: testEndpoint},\n                    signature: {\n                        endpoint: testSignatureEndoint,\n                        customHeaders: expectedSignatureHeaders\n                    }\n                }\n            );\n\n            startTypicalTest(uploader, function(signatureRequest) {\n                assert.equal(signatureRequest.requestHeaders.foo, expectedSignatureHeaders.foo);\n            });\n        });\n\n        it(\"test most basic upload w/ signature request uses the uuid as the blob name - original filename has no extension\", function(done) {\n            assert.expect(3, done);\n\n            var uploader = new qq.azure.FineUploaderBasic({\n                    request: {endpoint: testEndpoint},\n                    signature: {endpoint: testSignatureEndoint}\n                }\n            );\n\n            startTypicalTest(uploader, function(signatureRequest) {\n                var blobName = uploader.getBlobName(0),\n                    blobUri = testEndpoint + \"/\" + blobName,\n                    purlSignatureUrl = purl(signatureRequest.url);\n\n                assert.equal(blobName, uploader.getUuid(0));\n                assert.equal(purlSignatureUrl.param(\"bloburi\"), blobUri);\n            }, \"test\");\n        });\n\n        it(\"test most basic upload w/ signature request uses the filename as the blob name\", function(done) {\n            assert.expect(3, done);\n\n            var uploader = new qq.azure.FineUploaderBasic({\n                    request: {endpoint: testEndpoint},\n                    signature: {endpoint: testSignatureEndoint},\n                    blobProperties: {name: \"filename\"}\n                }\n            );\n\n            startTypicalTest(uploader, function(signatureRequest) {\n                var blobName = uploader.getBlobName(0),\n                    blobUri = testEndpoint + \"/\" + blobName,\n                    purlSignatureUrl = purl(signatureRequest.url);\n\n                assert.equal(blobName, uploader.getName(0));\n                assert.equal(purlSignatureUrl.param(\"bloburi\"), blobUri);\n            });\n        });\n\n        describe(\"test most basic upload w/ signature request uses a promissory function to determine the blob name\", function() {\n            function runTest(uploader, done) {\n                assert.expect(3, done);\n\n                startTypicalTest(uploader, function(signatureRequest) {\n                    var blobName = uploader.getBlobName(0),\n                        blobUri = testEndpoint + \"/\" + blobName,\n                        purlSignatureUrl = purl(signatureRequest.url);\n\n                    assert.equal(blobName, \"0_blobname\");\n                    assert.equal(purlSignatureUrl.param(\"bloburi\"), blobUri);\n                });\n            }\n\n            it(\"qq.Promise\", function(done) {\n                var uploader = new qq.azure.FineUploaderBasic({\n                        request: {endpoint: testEndpoint},\n                        signature: {endpoint: testSignatureEndoint},\n                        blobProperties: {\n                            name: function(id) {\n                                return new qq.Promise().success(id + \"_blobname\");\n                            }\n                        }\n                    }\n                );\n\n                runTest(uploader, done);\n            });\n\n            it(\"Q.js\", function(done) {\n                var uploader = new qq.azure.FineUploaderBasic({\n                        request: {endpoint: testEndpoint},\n                        signature: {endpoint: testSignatureEndoint},\n                        blobProperties: {\n                            name: function(id) {\n                                /* jshint newcap:false */\n                                return Q(id + \"_blobname\");\n                            }\n                        }\n                    }\n                );\n\n                runTest(uploader, done);\n            });\n        });\n\n        it(\"test basic upload w/ params\", function(done) {\n            assert.expect(6, done);\n\n            var expectedParams = {\n                    foo: \"bar\",\n                    one: 1,\n                    bool: true,\n                    func: function() {\n                        return \"thefunction\";\n                    },\n                    funky: \"ch@r&cters\"\n                },\n                uploader = new qq.azure.FineUploaderBasic({\n                    request: {\n                        endpoint: testEndpoint,\n                        params: expectedParams\n                    },\n                    signature: {endpoint: testSignatureEndoint}\n                }\n            );\n\n            startTypicalTest(uploader, function(signatureRequest) {\n                var uploadRequest,\n                    blobName = uploader.getBlobName(0),\n                    blobUri = testEndpoint + \"/\" + blobName;\n\n                signatureRequest.respond(200, null, \"http://sasuri.com\");\n\n                setTimeout(function() {\n                    uploadRequest = fileTestHelper.getRequests()[1];\n\n                    assert.equal(uploadRequest.requestHeaders[qq.azure.util.AZURE_PARAM_PREFIX + \"foo\"], expectedParams.foo);\n                    assert.equal(uploadRequest.requestHeaders[qq.azure.util.AZURE_PARAM_PREFIX + \"one\"], expectedParams.one);\n                    assert.equal(uploadRequest.requestHeaders[qq.azure.util.AZURE_PARAM_PREFIX + \"bool\"], String(expectedParams.bool));\n                    assert.equal(uploadRequest.requestHeaders[qq.azure.util.AZURE_PARAM_PREFIX + \"func\"], expectedParams.func());\n                    assert.equal(uploadRequest.requestHeaders[qq.azure.util.AZURE_PARAM_PREFIX + \"funky\"], encodeURIComponent(expectedParams.funky));\n                }, 0);\n            });\n        });\n\n        it(\"triggers expected callbacks at appropriate times\", function(done) {\n            assert.expect(17, done);\n\n            var expectedCallbackOrder = [\"validateBatch\", \"validate\", \"submit\", \"submitted\", \"upload\", \"complete\"],\n                actualCallbackOrder = [],\n                expectedStatusOrder = [qq.status.SUBMITTING, qq.status.SUBMITTED, qq.status.UPLOADING, qq.status.UPLOAD_SUCCESSFUL],\n                actualStatusOrder = [],\n                uploader = new qq.azure.FineUploaderBasic({\n                    request: {endpoint: testEndpoint},\n                    signature: {endpoint: testSignatureEndoint},\n                    callbacks: {\n                        onUpload: function(id, name) {\n                            actualCallbackOrder.push(\"upload\");\n                            assert.equal(id, 0);\n                            assert.equal(name, uploader.getName(0));\n                        },\n                        onValidate: function(id, name) {\n                            actualCallbackOrder.push(\"validate\");\n                        },\n                        onValidateBatch: function(id, name) {\n                            actualCallbackOrder.push(\"validateBatch\");\n                        },\n                        onSubmitted: function(id, name) {\n                            actualCallbackOrder.push(\"submitted\");\n                            assert.equal(id, 0);\n                            assert.equal(name, uploader.getName(0));\n                        },\n                        onSubmit: function(id, name) {\n                            actualCallbackOrder.push(\"submit\");\n                            assert.equal(id, 0);\n                            assert.equal(name, uploader.getName(0));\n                        },\n                        onComplete: function(id, name, response, xhr) {\n                            actualCallbackOrder.push(\"complete\");\n                            assert.equal(id, 0);\n                            assert.equal(name, uploader.getName(0));\n                            assert.deepEqual(response, {success: true});\n                            assert.ok(xhr);\n                            assert.deepEqual(actualCallbackOrder, expectedCallbackOrder);\n                        },\n                        onStatusChange: function(id, oldStatus, newStatus) {\n                            assert.equal(id, 0);\n                            actualStatusOrder.push(newStatus);\n                            if (newStatus === qq.status.UPLOAD_SUCCESSFUL) {\n                                assert.deepEqual(actualStatusOrder, expectedStatusOrder);\n                            }\n                        }\n                    }\n                }\n            );\n\n            startTypicalTest(uploader, function(signatureRequest) {\n                signatureRequest.respond(200, null, \"http://sasuri.com\");\n\n                setTimeout(function() {\n                    var uploadRequest = fileTestHelper.getRequests()[1];\n                    uploadRequest.respond(201, null, null);\n                }, 0);\n            });\n        });\n\n        it(\"reports error message from Azure to complete callback\", function(done) {\n            assert.expect(3, done);\n\n            var uploader = new qq.azure.FineUploaderBasic({\n                    request: {endpoint: testEndpoint},\n                    signature: {endpoint: testSignatureEndoint},\n                    callbacks: {\n                        onComplete: function(id, name, response, xhr) {\n                            assert.ok(response.error);\n                            assert.equal(response.azureError, \"string-value\");\n                        }\n                    }\n                }\n            );\n\n            startTypicalTest(uploader, function(signatureRequest) {\n                signatureRequest.respond(200, null, \"http://sasuri.com\");\n\n                setTimeout(function() {\n                    var uploadRequest = fileTestHelper.getRequests()[1];\n                    uploadRequest.respond(500, null, \"<?xml version=\\\"1.0\\\" encoding=\\\"utf-8\\\"?><Error><Code>string-value</Code><Message>string-value</Message></Error>\");\n                }, 0);\n            });\n        });\n\n        it(\"Sends uploadSuccess request after upload succeeds.  Also respects call to setUploadSuccessEndpoint method.\", function(done) {\n            var uploadSuccessUrl = \"/upload/success\",\n                uploadSuccessParams = {\"test-param-name\": \"test-param-value\"},\n                uploadSuccessHeaders = {\"test-header-name\": \"test-header-value\"},\n                uploader = new qq.azure.FineUploaderBasic({\n                    request: {endpoint: testEndpoint},\n                    signature: {endpoint: testSignatureEndoint},\n                    uploadSuccess: {\n                        endpoint: \"foo/bar\",\n                        params: uploadSuccessParams,\n                        customHeaders: uploadSuccessHeaders\n                    }\n                }\n            );\n\n            uploader.setUploadSuccessEndpoint(uploadSuccessUrl);\n            uploader.setParams({foo: \"bar\"});\n\n            startTypicalTest(uploader, function(signatureRequest) {\n                var uploadSuccessRequest, uploadSuccessRequestParsedBody;\n\n                signatureRequest.respond(200, null, \"http://sasuri.com\");\n\n                setTimeout(function() {\n                    var uploadRequest = fileTestHelper.getRequests()[1];\n                    uploadRequest.respond(201, null, \"\");\n\n                    assert.equal(fileTestHelper.getRequests().length, 3, \"Wrong # of requests\");\n                    uploadSuccessRequest = fileTestHelper.getRequests()[2];\n\n                    uploadSuccessRequestParsedBody = purl(\"http://test.com?\" + uploadSuccessRequest.requestBody).param();\n                    assert.equal(uploadSuccessRequest.url, uploadSuccessUrl);\n                    assert.equal(uploadSuccessRequest.method, \"POST\");\n                    assert.equal(uploadSuccessRequest.requestHeaders[\"Content-Type\"].indexOf(\"application/x-www-form-urlencoded\"), 0);\n                    assert.equal(uploadSuccessRequest.requestHeaders[\"test-header-name\"], uploadSuccessHeaders[\"test-header-name\"]);\n                    assert.equal(uploadSuccessRequestParsedBody[\"test-param-name\"], uploadSuccessParams[\"test-param-name\"]);\n                    assert.equal(uploadSuccessRequestParsedBody.foo, \"bar\");\n                    assert.equal(uploadSuccessRequestParsedBody.blob, uploader.getBlobName(0));\n                    assert.equal(uploadSuccessRequestParsedBody.uuid, uploader.getUuid(0));\n                    assert.equal(uploadSuccessRequestParsedBody.name, uploader.getName(0));\n                    assert.equal(uploadSuccessRequestParsedBody.container, testEndpoint);\n\n                    uploadSuccessRequest.respond(200, null, null);\n                    assert.equal(uploader.getUploads()[0].status, qq.status.UPLOAD_SUCCESSFUL);\n                    done();\n                }, 0);\n\n            });\n        });\n\n        it(\"declares an upload as a failure if uploadSuccess response indicates a problem with the file.  Also tests uploadSuccessRequest endpoint option.\", function(done) {\n            assert.expect(3, done);\n\n            var uploadSuccessUrl = \"/upload/success\",\n                uploader = new qq.azure.FineUploaderBasic({\n                    request: {endpoint: testEndpoint},\n                    signature: {endpoint: testSignatureEndoint},\n                    uploadSuccess: {\n                        endpoint: uploadSuccessUrl\n                    }\n                }\n            );\n\n            startTypicalTest(uploader, function(signatureRequest) {\n                var uploadSuccessRequest, uploadSuccessRequestParsedBody;\n\n                signatureRequest.respond(200, null, \"http://sasuri.com\");\n\n                setTimeout(function() {\n                    var uploadRequest = fileTestHelper.getRequests()[1];\n                    uploadRequest.respond(201, null, \"\");\n\n                    uploadSuccessRequest = fileTestHelper.getRequests()[2];\n                    assert.equal(uploadSuccessRequest.url, uploadSuccessUrl);\n                    uploadSuccessRequest.respond(200, null, JSON.stringify({success: false}));\n                    assert.equal(uploader.getUploads()[0].status, qq.status.UPLOAD_FAILED);\n                }, 0);\n\n            });\n        });\n\n        it(\"allows an upload success request to be sent using a method other than POST\", function(done) {\n            var uploadSuccessUrl = \"/upload/success\",\n                uploader = new qq.azure.FineUploaderBasic({\n                    request: {endpoint: testEndpoint},\n                    signature: {endpoint: testSignatureEndoint},\n                    uploadSuccess: {\n                        endpoint: uploadSuccessUrl,\n                        method: \"PATCH\"\n                    }\n                });\n\n            startTypicalTest(uploader, function(signatureRequest) {\n                var uploadSuccessRequest, uploadSuccessRequestParsedBody;\n\n                signatureRequest.respond(200, null, \"http://sasuri.com\");\n\n                setTimeout(function() {\n                    var uploadRequest = fileTestHelper.getRequests()[1];\n                    uploadRequest.respond(201, null, \"\");\n\n                    uploadSuccessRequest = fileTestHelper.getRequests()[2];\n                    assert.equal(uploadSuccessRequest.method, \"PATCH\");\n                    uploadSuccessRequest.respond(200, null, null);\n                    assert.equal(uploader.getUploads()[0].status, qq.status.UPLOAD_SUCCESSFUL);\n                    done();\n                }, 0);\n\n            });\n        });\n\n        it(\"test if azure specific header keys and their values remain as-is\", function(done) {\n            var uploader = new qq.azure.FineUploaderBasic({\n                    request: {endpoint: testEndpoint},\n                    signature: {endpoint: testSignatureEndoint}\n                }\n            );\n\n            var params = {\n                foo1: \"bar\",\n                \"Content-Encoding\": \"1rawvalue==\",\n                \"Content-Disposition\": \"2rawvalue==\",\n                \"Content-MD5\": \"3rawvalue==\",\n                \"Cache-Control\": \"4rawvalue==\",\n                \"x-ms-blob-content-encoding\": \"5rawvalue==\",\n                \"x-ms-blob-content-disposition\": \"6rawvalue==\",\n                \"x-ms-blob-content-md5\": function() { return \"7rawvalue==\"; },\n                \"x-ms-blob-cache-control\": \"8rawvalue==\"\n            };\n\n            uploader.setParams(params);\n\n            startTypicalTest(uploader, function(signatureRequest) {\n                signatureRequest.respond(200, null, \"http://sasuri.com\");\n\n                setTimeout(function() {\n                    var uploadRequest = fileTestHelper.getRequests()[1];\n                    uploadRequest.respond(201, null, \"\");\n\n                    assert.equal(uploadRequest.requestHeaders[\"x-ms-meta-foo1\"], \"bar\");\n\n                    assert.equal(uploadRequest.requestHeaders[\"Content-Encoding\"], params[\"Content-Encoding\"]);\n                    assert.equal(uploadRequest.requestHeaders[\"Content-Disposition\"], params[\"Content-Disposition\"]);\n                    assert.equal(uploadRequest.requestHeaders[\"Content-MD5\"], params[\"Content-MD5\"]);\n                    assert.equal(uploadRequest.requestHeaders[\"Cache-Control\"], params[\"Cache-Control\"]);\n                    assert.equal(uploadRequest.requestHeaders[\"x-ms-blob-content-encoding\"], params[\"x-ms-blob-content-encoding\"]);\n                    assert.equal(uploadRequest.requestHeaders[\"x-ms-blob-content-disposition\"], params[\"x-ms-blob-content-disposition\"]);\n                    assert.equal(uploadRequest.requestHeaders[\"x-ms-blob-content-md5\"], params[\"x-ms-blob-content-md5\"]());\n                    assert.equal(uploadRequest.requestHeaders[\"x-ms-blob-cache-control\"], params[\"x-ms-blob-cache-control\"]);\n                    done();\n                }, 0);\n            });\n        });\n    });\n}\n"
  },
  {
    "path": "test/unit/basic.js",
    "content": "/* globals describe, beforeEach, $fixture, qq, assert, it */\ndescribe(\"uploader.basic.js\", function () {\n    \"use strict\";\n\n    var $fineUploader, $button, $extraButton, $extraButton2, $extraButton3;\n\n    function getFileInput($containerEl) {\n        return $containerEl.find(\"INPUT\")[0];\n    }\n\n    beforeEach(function () {\n        $fixture.append(\"<div id='fine-uploader'></div>\");\n        $fineUploader = $fixture.find(\"#fine-uploader\");\n\n        $fixture.append(\"<div id='test-button'></div>\");\n        $button = $fixture.find(\"#test-button\");\n\n        $fixture.append(\"<div id='test-button2'></div>\");\n        $extraButton = $fixture.find(\"#test-button2\");\n\n        $fixture.append(\"<div id='test-button3'></div>\");\n        $extraButton2 = $fixture.find(\"#test-button3\");\n\n        $fixture.append(\"<div id='test-button4'></div>\");\n        $extraButton3 = $fixture.find(\"#test-button4\");\n    });\n\n    it(\"Includes the multiple attribute on the file input element by default in all supported browsers (even iOS7+) when MOV files cannot be submitted\", function() {\n        var uploader = new qq.FineUploaderBasic({\n            element: $fixture[0],\n            button: $button[0],\n            validation: {\n                allowedExtensions: [\"gif\", \"jpeg\"]\n            }\n        });\n\n        assert.equal(qq(getFileInput($button)).hasAttribute(\"multiple\"), qq.supportedFeatures.ajaxUploading);\n    });\n\n    it(\"Includes the multiple attribute on the file input element by default (where supported)\", function() {\n        var uploader = new qq.FineUploaderBasic({\n            element: $fixture[0],\n            button: $button[0]\n        });\n\n        assert.equal(qq(getFileInput($button)).hasAttribute(\"multiple\"), qq.supportedFeatures.ajaxUploading && !qq.ios());\n    });\n\n    it(\"Excludes the multiple attribute on the file input element if requested\", function() {\n        var uploader = new qq.FineUploaderBasic({\n            element: $fixture[0],\n            button: $button[0],\n            multiple: false,\n            workarounds: {\n                ios8BrowserCrash: false,\n                iosEmptyVideos: false\n            }\n        });\n\n        assert.ok(!qq(getFileInput($button)).hasAttribute(\"multiple\"));\n    });\n\n    qq.supportedFeatures.ajaxUploading && it(\"Excludes or includes the multiple attribute on 'extra' file input elements appropriately, taking extraButton properties into consideration\", function() {\n        var uploader = new qq.FineUploaderBasic({\n            element: $fixture[0],\n            button: $button[0],\n            workarounds: {\n                ios8BrowserCrash: false,\n                iosEmptyVideos: false\n            },\n            validation: {\n                allowedExtensions: [\"gif\", \"mov\"]\n            },\n            extraButtons: [\n                {\n                    element: $extraButton[0]\n                },\n                {\n                    element: $extraButton2[0],\n                    multiple: false\n                },\n                {\n                    element: $extraButton3[0],\n                    validation: {\n                        allowedExtensions: [\"gif\"]\n                    }\n                }\n            ]\n        });\n\n        assert.equal(qq(getFileInput($extraButton)).hasAttribute(\"multiple\"), true);\n        assert.equal(qq(getFileInput($extraButton2)).hasAttribute(\"multiple\"), false);\n        assert.equal(qq(getFileInput($extraButton3)).hasAttribute(\"multiple\"), true);\n    });\n\n    it(\"applies the correct title attribute to a file input\", function() {\n        var uploader = new qq.FineUploaderBasic({\n            text: {\n                fileInputTitle: \"default title\"\n            },\n            extraButtons: [\n                {\n                    element: $extraButton[0]\n                },\n                {\n                    element: $extraButton2[0],\n                    fileInputTitle: \"extrabutton2\"\n                }\n            ]\n        });\n\n        assert.equal(getFileInput($extraButton).title, \"default title\");\n        assert.equal(getFileInput($extraButton2).title, \"extrabutton2\");\n    });\n});\n"
  },
  {
    "path": "test/unit/button.js",
    "content": "/* globals describe, beforeEach, $fixture, qq, assert, it */\ndescribe(\"button.js\", function () {\n    \"use strict\";\n\n    it(\"constructor works\", function () {\n        $fixture.append(\"<div id='foo'></div>\");\n\n        var button = new qq.UploadButton({\n            element: $fixture.find(\"#foo\")[0],\n            multiple: true,\n            acceptFiles: \"image/*,video/*,.test\",\n            name: \"testFile\"\n        });\n\n        var input = button.getInput();\n        var $input = $(input);\n\n        assert.notEqual(input, null,\n            \"a newed up upload button should have a non-null input element\");\n        assert.equal($input.attr(\"type\"), \"file\",\n                     \"the input type should be `file`\");\n        assert.equal($input.attr(\"accept\"), \"image/*,video/*,.test\",\n                    \"uploader should valid which files are accepted\");\n        assert.equal($input.attr(\"name\"), \"testFile\",\n                    \"the name of the upload button should be set\");\n    });\n\n    it(\"reset works\", function () {\n        $fixture.append(\"<div id='foo'></div>\");\n\n        var button = new qq.UploadButton({\n            element: $fixture.find(\"#foo\")[0],\n            multiple: true,\n            ios8BrowserCrashWorkaround: false\n        });\n\n        var input = button.getInput();\n\n        button.reset();\n        assert.notEqual(input, button.getInput(),\n               \"resetting the button should clear the element from the DOM\");\n        assert.ok(qq(button.getInput()).hasAttribute(\"multiple\"), \"the multiple attribute should be added to new button after reset\");\n    });\n\n    it(\"respects the 'title' option\", function() {\n        $fixture.append(\"<div id='foo'></div>\");\n\n        var button = new qq.UploadButton({\n            element: $fixture.find(\"#foo\")[0],\n            title: \"foo-bar\"\n        });\n\n        var input = button.getInput();\n        assert.equal(button.getInput().title, \"foo-bar\");\n\n        button.reset();\n        assert.equal(button.getInput().title, \"foo-bar\");\n    });\n\n    it(\"does add an internal tracker ID to the input button, and re-adds it on reset\", function() {\n        $fixture.append(\"<div id='foo'></div>\");\n\n        var button = new qq.UploadButton({\n            element: $fixture.find(\"#foo\")[0]\n        }),\n            buttonId = button.getButtonId();\n\n        /* jshint eqnull:true */\n        assert.ok(buttonId != null);\n        assert.equal(button.getInput().getAttribute(qq.UploadButton.BUTTON_ID_ATTR_NAME), buttonId);\n\n        button.reset();\n        buttonId = button.getButtonId();\n        assert.ok(buttonId != null);\n        assert.equal(button.getInput().getAttribute(qq.UploadButton.BUTTON_ID_ATTR_NAME), buttonId);\n    });\n\n    it(\"sets and removes hover class\", function() {\n        var hoverclass = \"qq-upload-button-hover\";\n        var $button = $fixture.appendTo(\"<div id='button'></div>\");\n\n        var button = new qq.UploadButton({\n            element: $button[0],\n            hoverClass: hoverclass\n        });\n\n        $button.simulate(\"mouseenter\", function (e) {\n            var classes = $(this).attr(\"class\").split(\" \");\n            assert.ok($.inArray(hoverclass, classes));\n\n            $button.simulate(\"mouseleave\", function (e) {\n                classes = $(this).attr(\"class\").split(\" \");\n                assert.ok(!$.inArray(hoverclass, classes));\n            });\n        });\n\n    });\n\n    if (qq.supportedFeatures.ajaxUploading) {\n        it(\"sets multiple attribute\", function () {\n            var $button = $fixture.appendTo(\"<div></div>\");\n\n            var input;\n            var button = new qq.UploadButton({\n                element: $button[0],\n                multiple: false,\n                workarounds: {\n                    ios8BrowserCrash: false,\n                    iosEmptyVideos: false\n                }\n            });\n\n            input = button.getInput();\n            assert.ok(!input.hasAttribute(\"multiple\"));\n\n            button.setMultiple(true);\n            assert.ok(input.hasAttribute(\"multiple\"));\n        });\n    }\n\n    if (qq.supportedFeatures.ajaxUploading) {\n        it(\"sets accept files\", function () {\n            var $button = $fixture.appendTo(\"<div></div>\");\n\n            var input;\n            var button = new qq.UploadButton({\n                element: $button[0]\n            });\n\n            input = button.getInput();\n            assert.ok(!input.hasAttribute(\"accept\"));\n\n            button.setAcceptFiles(\"audio/*\");\n            assert.equal(input.getAttribute(\"accept\"), \"audio/*\");\n        });\n    }\n\n});\n"
  },
  {
    "path": "test/unit/chunked-uploads.js",
    "content": "/* globals describe, beforeEach, afterEach, $fixture, qq, assert, it, qqtest, helpme, purl */\nif (qqtest.canDownloadFileAsBlob) {\n    describe(\"chunked uploads\", function() {\n        \"use strict\";\n\n        var fileTestHelper = helpme.setupFileTests(),\n            testUploadEndpoint = \"/test/upload\",\n            expectedFileSize = 3266,\n            expectedChunks = 3,\n            chunkSize = Math.round(expectedFileSize / expectedChunks),\n            params = {\n                foo: \"bar\",\n                one: 2\n            },\n            overridenChunkingParamNames = {\n                chunkSize: \"testchunksize\",\n                partByteOffset: \"testpartbyteoffset\",\n                partIndex: \"testpartindex\",\n                totalParts: \"testtotalparts\"\n            };\n\n        function testChunkedUpload(spec) {\n            var customParams = spec.customParams || {},\n                chunkingParamNames = spec.chunkingParamNames || new qq.FineUploaderBasic({})._options.chunking.paramNames;\n\n            assert.expect(3 + (expectedChunks * (20 + (Object.keys(customParams).length))), spec.done);\n\n            var uploader = new qq.FineUploaderBasic({\n                    request: {\n                        endpoint: testUploadEndpoint,\n                        paramsInBody: !!spec.mpe,\n                        forceMultipart: !!spec.mpe,\n                        params: customParams\n                    },\n                    chunking: {\n                        enabled: true,\n                        partSize: chunkSize,\n                        paramNames: chunkingParamNames\n                    },\n                    resume: {\n                        enabled: !!spec.resume\n                    },\n                    callbacks: {\n                        onUploadChunk: function (id, name, chunkData) {\n                            chunksSent++;\n\n                            assert.equal(id, 0, \"Wrong ID passed to onUploadChunk\");\n                            assert.equal(name, uploader.getName(id), \"Wrong name passed to onUploadChunk\");\n                            assert.equal(chunkData.partIndex, chunksSent - 1, \"Wrong partIndex passed to onUploadChunk\");\n                            assert.equal(chunkData.startByte, (chunksSent - 1) * chunkSize + 1, \"Wrong startByte passed to onUploadChunk\");\n                            assert.equal(chunkData.endByte, chunksSent === expectedChunks ? expectedFileSize : chunkData.startByte + chunkSize - 1, \"Wrong startByte passed to onUploadChunk\");\n                            assert.equal(chunkData.totalParts, expectedChunks, \"Wrong totalParts passed to onUploadChunk\");\n\n                            setTimeout(function () {\n                                var request = fileTestHelper.getRequests()[fileTestHelper.getRequests().length - 1];\n                                request.respond(200, null, JSON.stringify({success: true, testParam: \"testVal\"}));\n                            }, 10);\n                        },\n                        onAutoRetry: function(id, name, attemptNumber) {\n                            assert.fail(\"This should not be called\");\n                        },\n                        onUploadChunkSuccess: function (id, chunkData, response, xhr) {\n                            var request = fileTestHelper.getRequests()[fileTestHelper.getRequests().length - 1],\n                                requestParams;\n\n                            chunksSucceeded++;\n\n                            if (spec.mpe) {\n                                requestParams = request.requestBody.fields;\n                            }\n                            else {\n                                requestParams = purl(request.url).param();\n                            }\n\n                            assert.equal(requestParams.qquuid, uploader.getUuid(id), \"Wrong uuid param\");\n                            assert.equal(requestParams[chunkingParamNames.partIndex], chunksSent - 1, \"Wrong part index param\");\n                            assert.equal(requestParams[chunkingParamNames.partByteOffset], (chunksSent - 1) * chunkSize, \"Wrong part byte offset param\");\n                            assert.equal(requestParams.qqtotalfilesize, expectedFileSize, \"Wrong total file size param\");\n                            assert.equal(requestParams[chunkingParamNames.totalParts], expectedChunks, \"Wrong total parts param\");\n                            assert.equal(requestParams.qqfilename, uploader.getName(id), \"Wrong filename param\");\n                            assert.equal(requestParams[chunkingParamNames.chunkSize], spec.mpe ? requestParams.qqfile.size : request.requestBody.size, \"Wrong chunk size param\");\n                            assert.equal(id, 0, \"Wrong ID passed to onUpoadChunkSuccess\");\n\n                            qq.each(customParams, function(key, val) {\n                                assert.equal(requestParams[key], val, qq.format(\"Wrong value for {} param\", key));\n                            });\n\n                            assert.equal(chunkData.partIndex, chunksSucceeded - 1, \"Wrong partIndex passed to onUploadChunkSuccess\");\n                            assert.equal(chunkData.startByte, (chunksSent - 1) * chunkSize + 1, \"Wrong startByte passed to onUploadChunkSuccess\");\n                            assert.equal(chunkData.endByte, chunksSucceeded === expectedChunks ? expectedFileSize : chunkData.startByte + chunkSize-1, \"Wrong startByte passed to onUploadChunk\");\n                            assert.equal(chunkData.totalParts, expectedChunks, \"Wrong totalParts passed to onUploadChunkSuccess\");\n\n                            assert.equal(response.testParam, \"testVal\");\n                            assert.ok(xhr);\n                        },\n                        onComplete: function (id, name, response) {\n                            assert.equal(expectedChunks, chunksSent, \"Wrong # of chunks sent.\");\n                            assert.equal(expectedChunks, chunksSucceeded, \"Wrong # of chunks succeeded\");\n                            assert.equal(response.testParam, \"testVal\");\n                        }\n                    }\n                }),\n                chunksSent = 0,\n                chunksSucceeded = 0;\n\n            qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function (blob) {\n                fileTestHelper.mockXhr();\n                uploader.addFiles({name: \"test\", blob: blob});\n            });\n        }\n\n        function testChunkedFailureAndRecovery(restartAfterFailure, done) {\n            if (restartAfterFailure) {\n                assert.expect(6 + (expectedChunks * 17) + (15 * (expectedChunks-1)), done);\n            }\n            else {\n                assert.expect(6 + (expectedChunks * 17), done);\n            }\n\n            var alreadyFailed = false,\n                uploader = new qq.FineUploaderBasic({\n                    request: {\n                        endpoint: testUploadEndpoint\n                    },\n                    chunking: {\n                        enabled: true,\n                        partSize: chunkSize\n                    },\n                    retry: {\n                        autoAttemptDelay: 0,\n                        enableAuto: true\n                    },\n                    callbacks: {\n                        onUploadChunk: function (id, name, chunkData) {\n                            chunksSent++;\n\n                            assert.equal(id, 0, \"Wrong ID passed to onUpoadChunk\");\n                            assert.equal(name, uploader.getName(id), \"Wrong name passed to onUploadChunk\");\n                            assert.equal(chunkData.partIndex, chunksSent - 1, \"Wrong partIndex passed to onUploadChunk\");\n                            assert.equal(chunkData.startByte, (chunksSent - 1) * chunkSize + 1, \"Wrong startByte passed to onUploadChunk\");\n                            assert.equal(chunkData.endByte, chunksSent === expectedChunks ? expectedFileSize : chunkData.startByte + chunkSize - 1, \"Wrong startByte passed to onUploadChunk\");\n                            assert.equal(chunkData.totalParts, expectedChunks, \"Wrong totalParts passed to onUploadChunk\");\n\n                            setTimeout(function () {\n                                var request = fileTestHelper.getRequests()[fileTestHelper.getRequests().length - 1];\n\n                                if (chunksSent === expectedChunks && !alreadyFailed) {\n                                    alreadyFailed = true;\n\n                                    if (restartAfterFailure) {\n                                        chunksSent = 0;\n                                        chunksSucceeded = 0;\n                                        request.respond(500, null, JSON.stringify({reset: true, testParam: \"testVal\"}));\n                                    }\n                                    else {\n                                        chunksSent--;\n                                        request.respond(500, null, JSON.stringify({testParam: \"testVal\"}));\n                                    }\n                                }\n                                else {\n                                    request.respond(200, null, JSON.stringify({success: true, testParam: \"testVal\"}));\n                                }\n                            }, 10);\n                        },\n                        onAutoRetry: function(id, name, attemptNumber) {\n                            assert.equal(id, 0, \"Wrong ID passed to onAutoRetry\");\n                            assert.equal(name, uploader.getName(id), \"Wrong name passed to onAutoRetry\");\n                            assert.equal(attemptNumber, 1, \"Wrong auto retry attempt #\");\n                        },\n                        onUploadChunkSuccess: function (id, chunkData, response, xhr) {\n                            var request = fileTestHelper.getRequests()[fileTestHelper.getRequests().length - 1],\n                                requestParams = request.requestBody.fields;\n\n                            chunksSucceeded++;\n\n                            assert.equal(requestParams.qquuid, uploader.getUuid(id), \"Wrong uuid param\");\n                            assert.equal(requestParams.qqpartindex, chunksSent - 1, \"Wrong part index param\");\n                            assert.equal(requestParams.qqpartbyteoffset, (chunksSent - 1) * chunkSize, \"Wrong part byte offset param\");\n                            assert.equal(requestParams.qqtotalfilesize, expectedFileSize, \"Wrong total file size param\");\n                            assert.equal(requestParams.qqtotalparts, expectedChunks, \"Wrong total parts param\");\n                            assert.equal(requestParams.qqfilename, uploader.getName(id), \"Wrong filename param\");\n                            assert.equal(requestParams.qqchunksize, requestParams.qqfile.size, \"Wrong chunk size param\");\n                            assert.equal(id, 0, \"Wrong ID passed to onUpoadChunkSuccess\");\n\n                            assert.equal(response.testParam, \"testVal\");\n                        },\n                        onComplete: function (id, name, response) {\n                            assert.equal(expectedChunks, chunksSent, \"Wrong # of chunks sent.\");\n                            assert.equal(expectedChunks, chunksSucceeded, \"Wrong # of chunks succeeded\");\n                            assert.equal(response.testParam, \"testVal\");\n                        }\n                    }\n                }),\n                chunksSent = 0,\n                chunksSucceeded = 0;\n\n            qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function (blob) {\n                fileTestHelper.mockXhr();\n                uploader.addFiles({name: \"test\", blob: blob});\n            });\n        }\n\n        function testChunkedEveryFailureAndRecovery(done) {\n            var alreadyFailed = false,\n                uploader = new qq.FineUploaderBasic({\n                    request: {\n                        endpoint: testUploadEndpoint\n                    },\n                    chunking: {\n                        enabled: true,\n                        partSize: chunkSize\n                    },\n                    retry: {\n                        autoAttemptDelay: 0,\n                        enableAuto: true\n                    },\n                    callbacks: {\n                        onUploadChunk: function (id, name, chunkData) {\n                            chunksSent++;\n\n                            assert.equal(id, 0, \"Wrong ID passed to onUpoadChunk\");\n                            assert.equal(name, uploader.getName(id), \"Wrong name passed to onUploadChunk\");\n                            assert.equal(chunkData.partIndex, chunksSent - 1, \"Wrong partIndex passed to onUploadChunk\");\n                            assert.equal(chunkData.startByte, (chunksSent - 1) * chunkSize + 1, \"Wrong startByte passed to onUploadChunk\");\n                            assert.equal(chunkData.endByte, chunksSent === expectedChunks ? expectedFileSize : chunkData.startByte + chunkSize - 1, \"Wrong startByte passed to onUploadChunk\");\n                            assert.equal(chunkData.totalParts, expectedChunks, \"Wrong totalParts passed to onUploadChunk\");\n\n                            setTimeout(function () {\n                                var request = fileTestHelper.getRequests()[fileTestHelper.getRequests().length - 1];\n\n                                if (!alreadyFailed) {\n                                    alreadyFailed = true;\n\n                                    chunksSent--;\n                                    request.respond(500, null, JSON.stringify({testParam: \"testVal\"}));\n                                }\n                                else {\n                                    alreadyFailed = false;\n                                    request.respond(200, null, JSON.stringify({success: true, testParam: \"testVal\"}));\n                                }\n                            }, 10);\n                        },\n                        onAutoRetry: function(id, name, attemptNumber) {\n                            assert.equal(id, 0, \"Wrong ID passed to onAutoRetry\");\n                            assert.equal(name, uploader.getName(id), \"Wrong name passed to onAutoRetry\");\n                            assert.equal(attemptNumber, 1, \"Wrong auto retry attempt #\");\n                        },\n                        onUploadChunkSuccess: function (id, chunkData, response, xhr) {\n                            var request = fileTestHelper.getRequests()[fileTestHelper.getRequests().length - 1],\n                                requestParams = request.requestBody.fields;\n\n                            chunksSucceeded++;\n\n                            assert.equal(requestParams.qquuid, uploader.getUuid(id), \"Wrong uuid param\");\n                            assert.equal(requestParams.qqpartindex, chunksSent - 1, \"Wrong part index param\");\n                            assert.equal(requestParams.qqpartbyteoffset, (chunksSent - 1) * chunkSize, \"Wrong part byte offset param\");\n                            assert.equal(requestParams.qqtotalfilesize, expectedFileSize, \"Wrong total file size param\");\n                            assert.equal(requestParams.qqtotalparts, expectedChunks, \"Wrong total parts param\");\n                            assert.equal(requestParams.qqfilename, uploader.getName(id), \"Wrong filename param\");\n                            assert.equal(requestParams.qqchunksize, requestParams.qqfile.size, \"Wrong chunk size param\");\n                            assert.equal(id, 0, \"Wrong ID passed to onUpoadChunkSuccess\");\n\n                            assert.equal(response.testParam, \"testVal\");\n                        },\n                        onComplete: function (id, name, response) {\n                            assert.equal(expectedChunks, chunksSent, \"Wrong # of chunks sent.\");\n                            assert.equal(expectedChunks, chunksSucceeded, \"Wrong # of chunks succeeded\");\n                            assert.equal(response.testParam, \"testVal\");\n                            assert.equal(response.success, true);\n\n                            done();\n                        }\n                    }\n                }),\n                chunksSent = 0,\n                chunksSucceeded = 0;\n\n            qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function (blob) {\n                fileTestHelper.mockXhr();\n                uploader.addFiles({name: \"test\", blob: blob});\n            });\n        }\n\n        it(\"sends proper number of chunks when chunking is enabled, MPE\", function(done) {\n            testChunkedUpload({\n                mpe: true,\n                done: done\n            });\n        });\n\n        it(\"sends proper number of chunks when chunking is enabled, non-MPE\", function(done) {\n            testChunkedUpload({done: done});\n        });\n\n        it(\"sends custom parameters along with each chunk, MPE\", function(done) {\n            testChunkedUpload({\n                mpe: true,\n                customParams: params,\n                done: done\n            });\n        });\n\n        it(\"sends custom parameters along with each chunk, non-MPE\", function(done) {\n            testChunkedUpload({\n                customParams: params,\n                done: done\n            });\n        });\n\n        it(\"specifies custom values for the various chunking parameters, MPE\", function(done) {\n            testChunkedUpload({\n                mpe: true,\n                chunkingParamNames: overridenChunkingParamNames,\n                done: done\n            });\n        });\n\n        it(\"specifies custom values for the various chunking parameters, non-MPE\", function(done) {\n            testChunkedUpload({\n                chunkingParamNames: overridenChunkingParamNames,\n                done: done\n            });\n        });\n\n        it(\"fails the last chunk once, then recovers\", function(done) {\n            testChunkedFailureAndRecovery(false, done);\n        });\n\n        it(\"fails the last chunk once, then restarts with the first chunk\", function(done) {\n            testChunkedFailureAndRecovery(true, done);\n        });\n\n        it(\"fails every chunk once, then recovers and ensure attemptNumber is 1\", function(done) {\n            testChunkedEveryFailureAndRecovery(done);\n        });\n\n        qq.supportedFeatures.resume && describe(\"resume feature tests\", function() {\n            var nativeLocalStorageSetItem = window.localStorage.setItem,\n                acknowledgeRequests = function(endpoint) {\n                    ackTimer = setTimeout(function() {\n                        qq.each(fileTestHelper.getRequests(), function(idx, req) {\n                            if (!req.ack && (!endpoint || endpoint === req.url)) {\n                                req.ack = true;\n                                req.respond(200, null, JSON.stringify({success: true, testParam: \"testVal\"}));\n                            }\n                        });\n                    }, 10);\n                }, ackTimer;\n\n            afterEach(function() {\n                window.localStorage.setItem = nativeLocalStorageSetItem;\n                clearTimeout(ackTimer);\n            });\n\n            it(\"ensures failure to use localStorage does not prevent uploading\", function(done) {\n                window.localStorage.setItem = function() {\n                    throw new qq.Error(\"Intentional localStorage error\");\n                };\n\n                testChunkedUpload({\n                    resume: true,\n                    done: done\n                });\n            });\n\n            it(\"getResumableFilesData\", function(done) {\n                var chunksUploaded = 0,\n                    uploader = new qq.FineUploaderBasic({\n                        request: {\n                            endpoint: testUploadEndpoint\n                        },\n                        resume: {\n                            enabled: true\n                        },\n                        chunking: {\n                            enabled: true,\n                            partSize: chunkSize\n                        },\n                        callbacks: {\n                            onUploadChunk: function() {\n                                acknowledgeRequests(testUploadEndpoint);\n                            },\n                            onUploadChunkSuccess: function(id) {\n                                if (chunksUploaded++ === 1) {\n                                    assert.ok(uploader.getResumableFilesData().length, \"Empty resumable files data!\");\n                                    done();\n                                }\n                            }\n                        }\n                    });\n\n                qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function (blob) {\n                    fileTestHelper.mockXhr();\n                    uploader.addFiles({name: \"test\", blob: blob});\n                });\n            });\n\n            describe(\"resume records\", function() {\n                var uploader;\n\n                function testResumeRecordsLogic(onUploadChunkSuccess, customKeys) {\n                    uploader = new qq.FineUploaderBasic({\n                        request: {\n                            endpoint: testUploadEndpoint\n                        },\n                        resume: {\n                            customKeys: customKeys || function() { return []; },\n                            enabled: true\n                        },\n                        chunking: {\n                            enabled: true,\n                            mandatory: true,\n                            partSize: expectedFileSize / 3\n                        },\n                        callbacks: {\n                            onUploadChunk: function() {\n                                acknowledgeRequests(testUploadEndpoint);\n                            },\n\n                            onUploadChunkSuccess: onUploadChunkSuccess\n                        }\n                    });\n\n                    qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function (blob) {\n                        fileTestHelper.mockXhr();\n                        uploader.addFiles({name: \"test\", blob: blob});\n                    });\n                }\n\n                it(\"stores custom resume data with resume record\", function(done){\n                    testResumeRecordsLogic(\n                        function(id, chunkData) {\n                            if (chunkData.partIndex === 1) {\n                                assert.deepEqual(uploader.getResumableFilesData()[0].customResumeData, { custom: \"resumedata\" });\n                                done();\n                            }\n                            else {\n                                uploader.setCustomResumeData(0, { custom: \"resumedata\" });\n                            }\n                        }\n                    );\n                });\n\n                it(\"uses custom keys (if supplied) to create resume record key\", function(done) {\n                    testResumeRecordsLogic(\n                        function(id, chunkData) {\n                            if (chunkData.partIndex === 1) {\n                                assert.ok(localStorage.key(0).indexOf(\"foo_customkey0\") >= 0);\n                                done();\n                            }\n                            else {\n                                uploader.setCustomResumeData(0, { custom: \"resumedata\" });\n                            }\n                        },\n                        function(id) {\n                            return [\n                                \"foo_customkey\" + id,\n                                \"bar_customkey\" + id\n                            ];\n                        }\n                    );\n                });\n            });\n        });\n\n        describe(\"chunking determination logic\", function() {\n            function testChunkingLogic(forceChunking, done) {\n                var actualChunks = 0,\n                    expectedChunks = forceChunking ? 1 : 0,\n                    uploader = new qq.FineUploaderBasic({\n                        request: {\n                            endpoint: testUploadEndpoint\n                        },\n                        chunking: {\n                            enabled: true,\n                            mandatory: forceChunking,\n                            partSize: expectedFileSize + 1\n                        },\n                        callbacks: {\n                            onUploadChunk: function() {\n                                actualChunks++;\n                            },\n                            onComplete: function() {\n                                assert.equal(actualChunks, expectedChunks, \"unexpected number of chunks!\");\n                                done();\n                            }\n                        }\n                    });\n\n                qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function (blob) {\n                    fileTestHelper.mockXhr();\n                    uploader.addFiles({name: \"test\", blob: blob});\n                    fileTestHelper.getRequests()[0].respond(200, null, JSON.stringify({success: true}));\n                });\n            }\n\n            it(\"does NOT chunk a file that is smaller than chunking.partSize\", function(done) {\n                testChunkingLogic(false, done);\n            });\n\n            it(\"DOES chunk a file that is smaller than chunking.partSize IFF chunking.mandatory == true\", function(done) {\n                testChunkingLogic(true, done);\n            });\n        });\n\n        describe(\"chunking.success option\", function() {\n            function testChunkingLogic(chunkingSuccess, onComplete) {\n                var uploader = new qq.FineUploaderBasic({\n                    request: {\n                        endpoint: testUploadEndpoint\n                    },\n                    chunking: {\n                        enabled: true,\n                        mandatory: true,\n                        partSize: expectedFileSize + 1,\n                        success: chunkingSuccess\n                    },\n                    callbacks: { onComplete: onComplete }\n                });\n\n                qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function (blob) {\n                    fileTestHelper.mockXhr();\n                    uploader.addFiles({name: \"test\", blob: blob});\n                    fileTestHelper.getRequests()[0].respond(200, null, JSON.stringify({success: true}));\n                    fileTestHelper.getRequests()[1].respond(200);\n                });\n            }\n\n            describe(\"endpoint\", function() {\n                it(\"string value - calls the endpoint after all chunks have been uploaded\", function(done) {\n                    testChunkingLogic(\n                        { endpoint: \"/test/chunkingsuccess\" },\n                        function() {\n                            assert.equal(fileTestHelper.getRequests().length, 2);\n                            assert.equal(fileTestHelper.getRequests()[1].url, \"/test/chunkingsuccess\");\n                            done();\n                        }\n                    );\n                });\n\n                it(\"function value - calls the endpoint after all chunks have been uploaded\", function(done) {\n                    testChunkingLogic(\n                        {\n                            endpoint: function(id) {\n                                return \"/test/\" + id;\n                            }\n                        },\n                        function() {\n                            assert.equal(fileTestHelper.getRequests().length, 2);\n                            assert.equal(fileTestHelper.getRequests()[1].url, \"/test/0\");\n                            done();\n                        }\n                    );\n                });\n            });\n\n            describe(\"headers\", function() {\n                it(\"calls the endpoint with the provided headers\", function(done) {\n                    testChunkingLogic(\n                        {\n                            endpoint: \"/test/chunkingsuccess\",\n                            headers: function(id) {\n                                return { Foo: \"bar\" + id };\n                            }\n                        },\n                        function() {\n                            assert.equal(fileTestHelper.getRequests()[1].requestHeaders.Foo, \"bar0\");\n                            done();\n                        }\n                    );\n                });\n            });\n\n            describe(\"jsonPayload + custom params\", function() {\n                it(\"true - calls the endpoint with params in the payload as application/json\", function(done) {\n                    testChunkingLogic(\n                        {\n                            endpoint: \"/test/chunkingsuccess\",\n                            jsonPayload: true,\n                            params: function(id) {\n                                return { Foo: \"bar\" + id };\n                            }\n                        },\n                        function() {\n                            assert.equal(fileTestHelper.getRequests()[1].requestHeaders[\"Content-Type\"], \"application/json;charset=utf-8\");\n                            assert.equal(fileTestHelper.getRequests()[1].requestBody, JSON.stringify({ Foo: \"bar0\" }));\n                            done();\n                        }\n                    );\n                });\n\n                it(\"false (default) - calls the endpoint with params in the payload as url-encoded\", function(done) {\n                    testChunkingLogic(\n                        {\n                            endpoint: \"/test/chunkingsuccess\",\n                            params: function(id) {\n                                return { Foo: \"bar@_\" + id };\n                            }\n                        },\n                        function() {\n                            assert.equal(fileTestHelper.getRequests()[1].requestHeaders[\"Content-Type\"], \"application/x-www-form-urlencoded;charset=utf-8\");\n                            assert.equal(fileTestHelper.getRequests()[1].requestBody, \"Foo=bar%40_0\");\n                            done();\n                        }\n                    );\n                });\n            });\n\n            describe(\"method\", function() {\n                it(\"(default) calls the endpoint using POST method\", function(done) {\n                    testChunkingLogic(\n                        {\n                            endpoint: \"/test/chunkingsuccess\"\n                        },\n                        function() {\n                            assert.equal(fileTestHelper.getRequests()[1].method, \"POST\");\n                            done();\n                        }\n                    );\n                });\n\n                it(\"calls the endpoint using custom method\", function(done) {\n                    testChunkingLogic(\n                        {\n                            endpoint: \"/test/chunkingsuccess\",\n                            method: \"PUT\"\n                        },\n                        function() {\n                            assert.equal(fileTestHelper.getRequests()[1].method, \"PUT\");\n                            done();\n                        }\n                    );\n                });\n            });\n\n            describe(\"resetOnStatus\", function() {\n                var uploader;\n\n                function testChunkingLogic(chunkingSuccess, onComplete, chunkingSuccessStatus) {\n                    uploader = new qq.FineUploaderBasic({\n                        request: {\n                            endpoint: testUploadEndpoint\n                        },\n                        resume: {\n                            enabled: true\n                        },\n                        chunking: {\n                            enabled: true,\n                            mandatory: true,\n                            partSize: expectedFileSize + 1,\n                            success: chunkingSuccess\n                        },\n                        callbacks: { onComplete: onComplete }\n                    });\n\n                    qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function (blob) {\n                        fileTestHelper.mockXhr();\n                        uploader.addFiles({name: \"test\", blob: blob});\n                        fileTestHelper.getRequests()[0].respond(200, null, JSON.stringify({success: true}));\n                        fileTestHelper.getRequests()[1].respond(chunkingSuccessStatus);\n                    });\n                }\n\n                it(\"resets the file to upload starting with the first chunk if the success endpoint responds with the provided status code\", function(done) {\n                    testChunkingLogic(\n                        {\n                            endpoint: \"/test/chunkingsuccess\",\n                            resetOnStatus: [404]\n                        },\n                        function(id, name, response) {\n                            assert.ok(!response.success);\n                            assert.ok(!uploader.isResumable(0));\n                            done();\n                        },\n                        404\n                    );\n                });\n\n                it(\"does not reset the file to upload starting with the first chunk if the success endpoint does not responds with the provided status code\", function(done) {\n                    testChunkingLogic(\n                        {\n                            endpoint: \"/test/chunkingsuccess\",\n                            resetOnStatus: [404]\n                        },\n                        function(id, name, response) {\n                            assert.ok(!response.success);\n                            assert.ok(uploader.isResumable(0));\n                            done();\n                        },\n                        500\n                    );\n                });\n            });\n        });\n\n        describe(\"request options\", function() {\n            var uploader;\n\n            function testChunkingLogic(request, onComplete, sinonResponse) {\n                uploader = new qq.FineUploaderBasic({\n                    request: request,\n                    chunking: {\n                        enabled: true,\n                        mandatory: true,\n                        partSize: expectedFileSize + 1\n                    },\n                    callbacks: { onComplete: onComplete }\n                });\n\n                qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function (blob) {\n                    fileTestHelper.mockXhr();\n                    uploader.addFiles({name: \"test\", blob: blob});\n\n                    if (sinonResponse) {\n                        var request = fileTestHelper.getRequests()[0];\n\n                        request.respond.apply(request, sinonResponse);\n                    }\n                    else {\n                        fileTestHelper.getRequests()[0].respond(200, null, JSON.stringify({success: true}));\n                    }\n                });\n            }\n\n            describe(\"request.omitDefaultParams\", function() {\n                it(\"(true) omits default params in upload requests\", function(done) {\n                    testChunkingLogic(\n                        {\n                            endpoint: testUploadEndpoint,\n                            omitDefaultParams: true,\n                            paramsInBody: false\n                        },\n                        function() {\n                            var chunkUploadRequest = fileTestHelper.getRequests()[0];\n                            assert.equal(chunkUploadRequest.url, \"/test/upload?\");\n                            done();\n                        }\n                    );\n                });\n\n                it(\"(default) includes default params in upload requests\", function(done) {\n                    testChunkingLogic(\n                        {\n                            endpoint: testUploadEndpoint,\n                            paramsInBody: false\n                        },\n                        function() {\n                            var chunkUploadRequest = fileTestHelper.getRequests()[0];\n                            var uuid = uploader.getUuid(0);\n\n                            assert.equal(chunkUploadRequest.url, \"/test/upload?qqpartindex=0&qqpartbyteoffset=0&qqchunksize=3266&qqtotalparts=1&qqtotalfilesize=3266&qqfilename=test&qquuid=\" + uuid);\n                            done();\n                        }\n                    );\n                });\n            });\n\n            describe(\"request.requireSuccessJson\", function() {\n                it(\"(false) fails if response status indicates failure but payload contains { 'success': true } in payload\", function(done) {\n                    testChunkingLogic(\n                        {\n                            endpoint: testUploadEndpoint,\n                            requireSuccessJson: false\n                        },\n                        function(id, name, response) {\n                            assert.ok(!response.success);\n                            done();\n                        },\n                        [\n                            500,\n                            null,\n                            JSON.stringify({ success: true })\n                        ]\n                    );\n                });\n\n                it(\"(false) succeeds if response status indicates success, even without JSON payload containing { 'success' true }\", function(done) {\n                    testChunkingLogic(\n                        {\n                            endpoint: testUploadEndpoint,\n                            requireSuccessJson: false\n                        },\n                        function(id, name, response) {\n                            assert.ok(response.success);\n                            done();\n                        },\n                        [\n                            200,\n                            null,\n                            null\n                        ]\n                    );\n                });\n\n                it(\"(default) fails if response status is 200 and does not contain { 'success': true } in payload\", function(done) {\n                    testChunkingLogic(\n                        {\n                            endpoint: testUploadEndpoint\n                        },\n                        function(id, name, response) {\n                            assert.ok(!response.success);\n                            done();\n                        },\n                        [\n                            200,\n                            null,\n                            null\n                        ]\n                    );\n                });\n\n                it(\"(false) succeeds if response payload contains { 'success': true }\", function(done) {\n                    testChunkingLogic(\n                        {\n                            endpoint: testUploadEndpoint\n                        },\n                        function(id, name, response) {\n                            assert.ok(response.success);\n                            done();\n                        },\n                        [\n                            200,\n                            null,\n                            JSON.stringify({ success: true })\n                        ]\n                    );\n                });\n            });\n        });\n\n        describe(\"onUploadChunk w/ Promise return value\", function() {\n            var uploader;\n\n            function testOnUploadChunkLogic(callbacks, options) {\n                var omitDefaultParams = !!(options && options.omitDefaultParams);\n\n                uploader = new qq.FineUploaderBasic({\n                    request: {\n                        endpoint: testUploadEndpoint,\n                        omitDefaultParams: omitDefaultParams,\n                        paramsInBody: false\n                    },\n                    chunking: {\n                        enabled: true,\n                        mandatory: true,\n                        partSize: expectedFileSize + 1\n                    },\n                    callbacks: callbacks\n                });\n\n                qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function (blob) {\n                    fileTestHelper.mockXhr();\n                    uploader.addFiles({name: \"test\", blob: blob});\n                });\n            }\n\n            it(\"fails the upload if the Promise is rejected\", function(done) {\n                testOnUploadChunkLogic({\n                    onComplete: function(id, name, result) {\n                        if (id === 0 && !result.success) {\n                            done();\n                        }\n                    },\n\n                    onUploadChunk: function() {\n                        return window.Promise.reject();\n                    }\n                });\n            });\n\n            it(\"uploads the next chunk if the Promise is resolved\", function(done) {\n                testOnUploadChunkLogic({\n                    onComplete: function(id, name, result) {\n                        if (id === 0 && result.success) {\n                            done();\n                        }\n                    },\n\n                    onUploadChunk: function() {\n                        setTimeout(function() {\n                            fileTestHelper.getRequests()[0].respond(200, null, JSON.stringify({success: true}));\n                        }, 10);\n\n                        return window.Promise.resolve();\n                    }\n                });\n            });\n\n            it(\"sends all headers passed to the resolved Promise for the upload chunk request\", function(done) {\n                var headersToSend = {\n                    \"X-Foo\": \"bar\"\n                };\n\n                testOnUploadChunkLogic({\n                    onComplete: function(id, name, result) {\n                        if (id === 0 && result.success) {\n                            done();\n                        }\n                    },\n\n                    onUploadChunk: function() {\n                        setTimeout(function() {\n                            var uploadChunkRequest = fileTestHelper.getRequests()[0];\n\n                            delete uploadChunkRequest.requestHeaders[\"Content-Type\"];\n                            assert.deepEqual(uploadChunkRequest.requestHeaders, headersToSend);\n\n                            uploadChunkRequest.respond(200, null, JSON.stringify({success: true}));\n                        }, 10);\n\n                        return window.Promise.resolve({ headers: headersToSend });\n                    }\n                });\n            });\n\n            it(\"sends only params passed to the resolved Promise for the upload chunk request\", function(done) {\n                var expectedUrlEncodedParams = \"Foo-Param=bar\",\n                    paramsToSend = {\n                        \"Foo-Param\": \"bar\"\n                    };\n\n                testOnUploadChunkLogic({\n                    onComplete: function(id, name, result) {\n                        if (id === 0 && result.success) {\n                            done();\n                        }\n                    },\n\n                    onUploadChunk: function() {\n                        setTimeout(function() {\n                            var uploadChunkRequest = fileTestHelper.getRequests()[0];\n\n                            assert.deepEqual(uploadChunkRequest.url, testUploadEndpoint + \"?\" + expectedUrlEncodedParams);\n\n                            uploadChunkRequest.respond(200, null, JSON.stringify({success: true}));\n                        }, 10);\n\n                        return window.Promise.resolve({ params: paramsToSend });\n                    }\n                }, { omitDefaultParams: true });\n            });\n\n            it(\"sends default params and params passed to the resolved Promise for the upload chunk request\", function(done) {\n                var expectedCustomUrlEncodedParams = \"Foo-Param=bar\",\n                    paramsToSend = {\n                        \"Foo-Param\": \"bar\"\n                    };\n\n                testOnUploadChunkLogic({\n                    onComplete: function(id, name, result) {\n                        if (id === 0 && result.success) {\n                            done();\n                        }\n                    },\n\n                    onUploadChunk: function(id, name, chunkData) {\n                        setTimeout(function() {\n                            var uploadChunkRequest = fileTestHelper.getRequests()[0],\n                                expectedUrlParams = expectedCustomUrlEncodedParams +\n                                    \"&qqpartindex=\" + chunkData.partIndex +\n                                    \"&qqpartbyteoffset=\" + (chunkData.startByte - 1) +\n                                    \"&qqchunksize=\" + (chunkData.endByte - chunkData.startByte + 1) +\n                                    \"&qqtotalparts=\" + chunkData.totalParts +\n                                    \"&qqtotalfilesize=\" + expectedFileSize +\n                                    \"&qqfilename=\" + name +\n                                    \"&qquuid=\" + uploader.getUuid(id);\n\n                            assert.deepEqual(uploadChunkRequest.url, testUploadEndpoint + \"?\" + expectedUrlParams);\n\n                            uploadChunkRequest.respond(200, null, JSON.stringify({success: true}));\n                        }, 10);\n\n                        return window.Promise.resolve({ params: paramsToSend });\n                    }\n                });\n            });\n\n            it(\"uses the method passed to the resolved Promise for the upload chunk request\", function(done) {\n                var requestMethod = \"PATCH\";\n\n                testOnUploadChunkLogic({\n                    onComplete: function(id, name, result) {\n                        if (id === 0 && result.success) {\n                            done();\n                        }\n                    },\n\n                    onUploadChunk: function() {\n                        setTimeout(function() {\n                            var uploadChunkRequest = fileTestHelper.getRequests()[0];\n\n                            assert.deepEqual(uploadChunkRequest.method, requestMethod);\n\n                            uploadChunkRequest.respond(200, null, JSON.stringify({success: true}));\n                        }, 10);\n\n                        return window.Promise.resolve({ method: requestMethod });\n                    }\n                });\n            });\n\n            it(\"uses the endpoint passed to the resolved Promise for the upload chunk request\", function(done) {\n                var requestUrl = \"/test/overriden/onuploadchunkendpoint\";\n\n                testOnUploadChunkLogic({\n                    onComplete: function(id, name, result) {\n                        if (id === 0 && result.success) {\n                            done();\n                        }\n                    },\n\n                    onUploadChunk: function() {\n                        setTimeout(function() {\n                            var uploadChunkRequest = fileTestHelper.getRequests()[0];\n\n                            assert.deepEqual(uploadChunkRequest.url, requestUrl + \"?\");\n\n                            uploadChunkRequest.respond(200, null, JSON.stringify({success: true}));\n                        }, 10);\n\n                        return window.Promise.resolve({ endpoint: requestUrl });\n                    }\n                }, { omitDefaultParams: true });\n            });\n        });\n\n        describe(\"variable chunk size\", function() {\n            it(\"allows an alternate chunk size to be specified for each file\", function(done) {\n                var uploader = new qq.FineUploaderBasic({\n                    maxConnections: 1,\n                    request: {\n                        endpoint: testUploadEndpoint\n                    },\n                    chunking: {\n                        enabled: true,\n                        partSize: function(id) {\n                            if (id === 0) {\n                                return expectedFileSize / 2;\n                            }\n\n                            return expectedFileSize / 3;\n                        }\n                    },\n                    callbacks: {\n                        onUploadChunk: function(id, name, chunkData) {\n                            setTimeout(function() {\n                                var uploadChunkRequest;\n\n                                if (id === 0) {\n                                    uploadChunkRequest = fileTestHelper.getRequests()[chunkData.partIndex];\n                                }\n                                else if (id === 1) {\n                                    uploadChunkRequest = fileTestHelper.getRequests()[2 + chunkData.partIndex];\n                                }\n\n                                uploadChunkRequest.respond(200, null, JSON.stringify({success: true}));\n                            }, 10);\n\n                            if (id === 0) {\n                                assert.equal(chunkData.totalParts, 2);\n                            }\n                            else if (id === 1) {\n                                assert.equal(chunkData.totalParts, 3);\n\n                                if (chunkData.partIndex === 2) {\n                                    done();\n                                }\n                            }\n                        }\n                    }\n                });\n\n                qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function (blob) {\n                    fileTestHelper.mockXhr();\n                    uploader.addFiles({name: \"test0\", blob: blob});\n                    uploader.addFiles({name: \"test1\", blob: blob});\n                });\n            });\n        });\n    });\n}\n"
  },
  {
    "path": "test/unit/concurrent-chunks.js",
    "content": "/* globals describe, beforeEach, $fixture, qq, assert, it, qqtest, helpme, purl, afterEach */\nif (qqtest.canDownloadFileAsBlob) {\n    describe(\"concurrent chunked uploads\", function() {\n        \"use strict\";\n\n        var fileTestHelper = helpme.setupFileTests(),\n            testUploadEndpoint = \"/test/upload\",\n            expectedFileSize = 3266,\n            expectedChunks = 3,\n            chunkSize = Math.round(expectedFileSize / expectedChunks),\n            ackTimer,\n            acknowledgeRequests = function(endpoint) {\n                ackTimer = setTimeout(function() {\n                    qq.each(fileTestHelper.getRequests(), function(idx, req) {\n                        if (!req.ack && (!endpoint || endpoint === req.url)) {\n                            req.ack = true;\n                            req.respond(200, null, JSON.stringify({success: true, testParam: \"testVal\"}));\n                        }\n                    });\n                }, 10);\n            };\n\n\n        afterEach(function() {\n            clearTimeout(ackTimer);\n        });\n\n        it(\"Make sure only `maxConnections` chunks are sent at once\", function(done) {\n            var chunksStarted = 0,\n                actualUploadsPerGroup = [0],\n                expectedUploadPerGroup = [2, 1],\n                uploader = new qq.FineUploaderBasic({\n                    maxConnections: 2,\n                    request: {\n                        endpoint: testUploadEndpoint\n                    },\n                    chunking: {\n                        enabled: true,\n                        partSize: chunkSize,\n                        concurrent: {\n                            enabled: true\n                        }\n                    },\n                    callbacks: {\n                        onUploadChunk: function() {\n                            actualUploadsPerGroup[actualUploadsPerGroup.length-1] += 1;\n                            chunksStarted++;\n                            acknowledgeRequests();\n                        },\n                        onUploadChunkSuccess: function(id, chunkData, response) {\n                            chunksStarted < expectedChunks && actualUploadsPerGroup.push(0);\n                            assert.equal(response.testParam, \"testVal\");\n                        },\n                        onAllComplete: function(succeeded, failed) {\n                            assert.deepEqual(actualUploadsPerGroup, expectedUploadPerGroup);\n                            done();\n                        },\n                        onComplete: function(id, name, response) {\n                            assert.equal(response.testParam, \"testVal\");\n                        }\n                    }\n                });\n\n            qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function (blob) {\n                fileTestHelper.mockXhr();\n                uploader.addFiles({name: \"test\", blob: blob});\n            });\n        });\n\n        it(\"Cancel terminates all in-progress requests\", function(done) {\n            assert.expect(2, done);\n\n            var chunksInProgress = 0,\n                xhrsAborted = 0,\n                cancelCalled = false,\n                uploader = new qq.FineUploaderBasic({\n                    debug: true,\n                    maxConnections: 3,\n                    request: {\n                        endpoint: testUploadEndpoint\n                    },\n                    chunking: {\n                        enabled: true,\n                        partSize: chunkSize,\n                        concurrent: {\n                            enabled: true\n                        }\n                    },\n                    callbacks: {\n                        onUploadChunk: function() {\n                            chunksInProgress++;\n\n                            setTimeout(function() {\n                                if (!cancelCalled && fileTestHelper.getRequests().length === expectedChunks) {\n                                    qq.each(fileTestHelper.getRequests(), function(idx, req) {\n                                        req.abort = function() {\n                                            xhrsAborted++;\n                                        };\n                                    });\n\n                                    cancelCalled = true;\n                                    uploader.cancel(0);\n                                }\n                            }, 10);\n                        },\n                        onUploadChunkSuccess: function(id, chunkData) {\n                            assert.fail(null, null, \"No chunks should have uploaded!\");\n                        },\n                        onCancel: function(id) {\n                            assert.equal(id, 0);\n                            return true;\n                        },\n                        onStatusChange: function(id, oldStatus, newStatus) {\n                            if (newStatus === qq.status.CANCELED) {\n                                assert.equal(xhrsAborted, expectedChunks);\n                            }\n                        }\n                    }\n                });\n\n            qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function (blob) {\n                fileTestHelper.mockXhr();\n                uploader.addFiles({name: \"test\", blob: blob});\n            });\n        });\n\n        it(\"ensure 'all chunks done' POST is sent when all chunks are complete & the upload is failed if this request fails\", function(done) {\n            assert.expect(10, done);\n\n            var foundAllChunksDoneReq = false,\n                uploader = new qq.FineUploaderBasic({\n                    request: {\n                        endpoint: testUploadEndpoint,\n                        customHeaders: {test_header: \"test\"},\n                        params: {test_param: \"test\"}\n                    },\n                    chunking: {\n                        enabled: true,\n                        partSize: chunkSize,\n                        concurrent: {\n                            enabled: true\n                        },\n                        success: {\n                            endpoint: \"/chunking/success\"\n                        }\n                    },\n                    callbacks: {\n                        onUploadChunk: function() {\n                            acknowledgeRequests(testUploadEndpoint);\n                            setTimeout(function() {\n                                qq.each(fileTestHelper.getRequests(), function(idx, req) {\n                                    var parsedBody;\n                                    if (!foundAllChunksDoneReq && \"/chunking/success\" === req.url) {\n                                        foundAllChunksDoneReq = true;\n\n                                        assert.equal(req.requestHeaders.test_header, \"test\");\n\n                                        parsedBody = purl(\"http://example.com?\" + req.requestBody).param();\n                                        assert.equal(parsedBody.test_param, \"test\");\n\n                                        req.respond(500, null, JSON.stringify({error: \"oops\", otherstuff: \"foobar\"}));\n                                    }\n                                });\n                            }, 10);\n                        },\n                        onComplete: function(id, name, response, xhr) {\n                            assert.equal(id, 0);\n                            assert.equal(name, \"test\");\n                            assert.equal(response.error, \"oops\");\n                            assert.equal(response.otherstuff, \"foobar\");\n                            assert.equal(response.success, false);\n                            assert.ok(xhr);\n                        },\n                        onAllComplete: function(succeeded, failed) {\n                            assert.ok(foundAllChunksDoneReq);\n                            assert.equal(failed.length, 1);\n                        }\n                    }\n                });\n\n            qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function (blob) {\n                fileTestHelper.mockXhr();\n                uploader.addFiles({name: \"test\", blob: blob});\n\n            });\n        });\n    });\n}\n"
  },
  {
    "path": "test/unit/delete-file.js",
    "content": "/* globals describe, beforeEach, $fixture, qq, assert, it, qqtest, helpme, purl */\nif (qqtest.canDownloadFileAsBlob) {\n    describe(\"deleting files\", function() {\n        \"use strict\";\n\n        var fileTestHelper = helpme.setupFileTests(),\n            testUploadEndpoint = \"/test/upload\",\n            testDeleteEndpoint = \"/test/deletefile\",\n            deleteParams = {\n                foo: \"bar\",\n                one: 2,\n                thefunc: function() {\n                    return \"thereturn\";\n                }\n            },\n            deleteCustomHeaders = {\n                one: \"1\",\n                two: \"2\"\n            };\n\n        function testDeleteFile(done, expectedMethod, deleteEnabled, successful, reject, expectedParams, setParamsViaOptions, expectedHeaders, setHeadersViaOptions) {\n            var expectedAssertions = 0;\n\n            expectedParams = expectedParams || {};\n            expectedHeaders = expectedHeaders || {};\n\n            if (!deleteEnabled) {\n                expectedAssertions = 2;\n            }\n            else if (reject) {\n                expectedAssertions = 3;\n            }\n            else {\n                if (expectedMethod === \"POST\") {\n                    expectedAssertions = 12 + Object.keys(expectedParams).length;\n                }\n                else {\n                    expectedAssertions = 10 + Object.keys(expectedParams).length;\n                }\n            }\n\n            if (Object.keys(expectedHeaders).length) {\n                expectedAssertions += 2;\n            }\n\n            assert.expect(expectedAssertions, done);\n\n            var uploader = new qq.FineUploaderBasic({\n                request: {\n                    endpoint: testUploadEndpoint\n                },\n                deleteFile: {\n                    enabled: deleteEnabled,\n                    endpoint: testDeleteEndpoint,\n                    method: expectedMethod,\n                    params: (function() {\n                        if (setParamsViaOptions && expectedParams) {\n                            return expectedParams;\n                        }\n\n                        return {};\n                    }()),\n                    customHeaders: (function() {\n                        if (setHeadersViaOptions && expectedHeaders) {\n                            return expectedHeaders;\n                        }\n\n                        return {};\n                    }())\n                },\n                callbacks: {\n                    onComplete: function(id) {\n                        var uuid = uploader.getUuid(id),\n                            deleteRequest, deleteRequestPurl, deleteRequestBodyPurl;\n\n                        uploader.deleteFile(id);\n\n                        deleteRequest = fileTestHelper.getRequests()[1];\n\n                        if (deleteEnabled && !reject) {\n                            deleteRequestPurl = purl(deleteRequest.url);\n\n                            assert.equal(fileTestHelper.getRequests().length, 2, \"Wrong # of requests\");\n                            assert.equal(deleteRequest.method, expectedMethod, \"Wrong method for delete request\");\n\n                            if (expectedMethod === \"DELETE\") {\n                                assert.equal(deleteRequestPurl.attr(\"path\"), testDeleteEndpoint + \"/\" + uuid, \"Wrong endpoint for delete request\");\n\n                                if (Object.keys(expectedParams).length) {\n                                    assert.equal(deleteRequestPurl.param(\"foo\"), expectedParams.foo, \"Wrong 'foo' parameter\");\n                                    assert.equal(deleteRequestPurl.param(\"one\"), expectedParams.one, \"Wrong 'one' parameter\");\n                                    assert.equal(deleteRequestPurl.param(\"thefunc\"), expectedParams.thefunc(), \"Wrong 'thefunc' parameter\");\n                                }\n                            }\n                            else {\n                                deleteRequestBodyPurl = purl(\"?\" + deleteRequest.requestBody);\n                                assert.equal(deleteRequestPurl.attr(\"path\"), testDeleteEndpoint, \"Wrong endpoint for delete request\");\n                                assert.equal(deleteRequestBodyPurl.param(\"_method\"), \"DELETE\", \"Wrong _method param\");\n                                assert.equal(deleteRequestBodyPurl.param(\"qquuid\"), uuid, \"Wrong qquuid param\");\n\n                                if (Object.keys(expectedParams).length) {\n                                    assert.equal(deleteRequestBodyPurl.param(\"foo\"), expectedParams.foo, \"Wrong 'foo' parameter\");\n                                    assert.equal(deleteRequestBodyPurl.param(\"one\"), expectedParams.one, \"Wrong 'one' parameter\");\n                                    assert.equal(deleteRequestBodyPurl.param(\"thefunc\"), expectedParams.thefunc(), \"Wrong 'thefunc' parameter\");\n                                }\n                            }\n\n                            if (Object.keys(expectedHeaders).length) {\n                                assert.equal(deleteRequest.requestHeaders.one, expectedHeaders.one, \"Wrong 'one' header\");\n                                assert.equal(deleteRequest.requestHeaders.two, expectedHeaders.two, \"Wrong 'two' header\");\n                            }\n\n                            deleteRequest.respond(successful ? 200 : 500, null, null);\n                        }\n                        else {\n                            /* jshint eqnull:true */\n                            assert.ok(deleteRequest == null, \"delete request may have been sent\");\n                        }\n                    },\n                    onSubmitDelete: function(id) {\n                        assert.equal(id, 0, \"Wrong ID passed to onSubmitDelete\");\n\n                        if (reject) {\n                            setTimeout(function() {\n                                assert.deepEqual(statuses, expectedStatusOrder, \"Unexpected status\");\n                            }, 10);\n                            return false;\n                        }\n\n                        !setParamsViaOptions && uploader.setDeleteFileParams(expectedParams);\n                        !setHeadersViaOptions && uploader.setDeleteFileCustomHeaders(expectedHeaders);\n                    },\n                    onDelete: function(id) {\n                        assert.equal(id, 0, \"Wrong ID passed to onDelete\");\n                    },\n                    onDeleteComplete: function(id, xhr, isError) {\n                        /* jshint eqnull:true */\n                        assert.equal(id, 0, \"Wrong ID passed to onDeleteComplete\");\n                        assert.ok(xhr != null, \"Invalid XHR passed to onDeleteComplete\");\n                        assert.ok(isError !== successful, \"Unexpected delete status\");\n                        assert.deepEqual(statuses, expectedStatusOrder, \"Unexpected status\");\n                    },\n                    onStatusChange: function(id, oldStatus, newStatus) {\n                        statuses.push(newStatus);\n                    }\n                }\n            }),\n                statuses = [],\n                expectedStatusOrder = [qq.status.SUBMITTING, qq.status.SUBMITTED, qq.status.UPLOADING, qq.status.UPLOAD_SUCCESSFUL, qq.status.DELETING];\n\n            if (successful && !reject) {\n                expectedStatusOrder.push(qq.status.DELETED);\n            }\n            else if (!reject) {\n                expectedStatusOrder.push(qq.status.DELETE_FAILED);\n            }\n            else {\n                expectedStatusOrder.pop();\n            }\n\n            qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function(blob) {\n                fileTestHelper.mockXhr();\n\n                var request;\n\n                uploader.addFiles({name: \"test\", blob: blob});\n\n                assert.equal(fileTestHelper.getRequests().length, 1, \"Wrong # of requests\");\n                request = fileTestHelper.getRequests()[0];\n                request.respond(200, null, JSON.stringify({success: true}));\n            });\n        }\n\n        it(\"ignores delete requests if the feature is disabled\", function(done) {\n            testDeleteFile(done, \"DELETE\", true, false, false);\n        });\n\n        it(\"handles simple delete of successfully uploaded file\", function(done) {\n            testDeleteFile(done, \"DELETE\", true, true, false);\n        });\n\n        it(\"handles simple failed delete of successfully uploaded file\", function(done) {\n            testDeleteFile(done, \"DELETE\", false, true);\n        });\n\n        it(\"ignores delete requests that are rejected via callback\", function(done) {\n            testDeleteFile(done, \"DELETE\", true, true, true);\n        });\n\n        it(\"handles simple delete w/ method changed to POST\", function(done) {\n            testDeleteFile(done, \"POST\", true, true, false);\n        });\n\n        it(\"properly passes parameters specified via options only for DELETE request\", function(done) {\n            testDeleteFile(done, \"DELETE\", true, true, false, deleteParams, true);\n        });\n\n        it(\"properly passes parameters specified via options only for POST request\", function(done) {\n            testDeleteFile(done, \"POST\", true, true, false, deleteParams, true);\n        });\n\n        it(\"properly passes parameters specified via API only for DELETE request\", function(done) {\n            testDeleteFile(done, \"DELETE\", true, true, false, deleteParams);\n        });\n\n        it(\"properly passes parameters specified via API only for POST request\", function(done) {\n            testDeleteFile(done, \"POST\", true, true, false, deleteParams);\n        });\n\n        it(\"properly passes headers specified via options\", function(done) {\n            testDeleteFile(done, \"POST\", true, true, false, null, null, deleteCustomHeaders, true);\n        });\n\n        it(\"properly passes headers specified via API\", function(done) {\n            testDeleteFile(done, \"POST\", true, true, false, null, null, deleteCustomHeaders, false);\n        });\n    });\n}\n"
  },
  {
    "path": "test/unit/dnd.js",
    "content": "/* globals describe, beforeEach, $fixture, qq, assert, it */\ndescribe(\"drag and drop\", function () {\n  \"use strict\";\n\n  // For IE, from https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/includes#Polyfill\n  var includesPolyfill = function(searchElement, fromIndex) {\n\n    // 1. Let O be ? ToObject(this value).\n    if (this == null) {\n      throw new TypeError(\"'this' is null or not defined\");\n    }\n\n    var o = Object(this);\n\n    // 2. Let len be ? ToLength(? Get(O, \"length\")).\n    /* jshint -W016 */\n    var len = o.length >>> 0;\n\n    // 3. If len is 0, return false.\n    if (len === 0) {\n      return false;\n    }\n\n    // 4. Let n be ? ToInteger(fromIndex).\n    //    (If fromIndex is undefined, this step produces the value 0.)\n    var n = fromIndex | 0;\n\n    // 5. If n ≥ 0, then\n    //  a. Let k be n.\n    // 6. Else n < 0,\n    //  a. Let k be len + n.\n    //  b. If k < 0, let k be 0.\n    var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);\n\n    function sameValueZero(x, y) {\n      return x === y || (typeof x === \"number\" && typeof y === \"number\" && isNaN(x) && isNaN(y));\n    }\n\n    // 7. Repeat, while k < len\n    while (k < len) {\n      // a. Let elementK be the result of ? Get(O, ! ToString(k)).\n      // b. If SameValueZero(searchElement, elementK) is true, return true.\n      // c. Increase k by 1.\n      if (sameValueZero(o[k], searchElement)) {\n        return true;\n      }\n      k++;\n    }\n\n    // 8. Return false\n    return false;\n  };\n\n  var createChromeDragEvent = function(overrides) {\n    return qq.extend({\n      type: \"dragover\",\n      dataTransfer: {\n        effectAllowed: \"all\",\n        files: [],\n        items: [],\n        types: []\n      }\n    }, overrides, true);\n  };\n\n  var createFirefoxDragEvent = function(overrides) {\n    return qq.extend({\n      type: \"dragover\",\n      dataTransfer: {\n        effectAllowed: \"all\",\n        files: [],\n        items: [],\n        types: []\n      }\n    }, overrides, true);\n  };\n\n  var createIeDragEvent = function(overrides) {\n    var e = qq.extend({\n      type: \"dragover\",\n      dataTransfer: {\n        effectAllowed: undefined, // This actually throws an error, but I'm not sure how to mock that\n        files: [],\n        items: undefined,\n        types: []\n      }\n    }, overrides, true);\n\n    e.dataTransfer.types.includes = undefined;\n    e.dataTransfer.types.contains = includesPolyfill.bind(e.dataTransfer.types);\n\n    return e;\n  };\n\n  it(\"determines non-file inputs as invalid drag candidates\", function() {\n    $fixture.append(\"<div id='fine-dropzone'></div>\");\n    var uploadDropZone = new qq.UploadDropZone({element: $fixture.find(\"#fine-dropzone\")});\n\n    // A mock event similar to the one generated by dragging plaintext into the browser\n    var chromeTextDragEvent = createChromeDragEvent({\n      dataTransfer: {\n        items: [\n          {\n            kind: \"string\",\n            type: \"text/plain\"\n          },\n          {\n            kind: \"string\",\n            type: \"text/html\"\n          }\n        ],\n        types: [\n          \"text/plain\",\n          \"text/html\"\n        ]\n      }\n    });\n\n    var firefoxTextDragEvent = createFirefoxDragEvent({\n      dataTransfer: {\n        items: [\n          {\n            kind: \"string\",\n            type: \"text/_moz_htmlcontext\"\n          },\n          {\n            kind: \"string\",\n            type: \"text/_moz_htmlinfo\"\n          },\n          {\n            kind: \"string\",\n            type: \"text/html\"\n          },\n          {\n            kind: \"string\",\n            type: \"text/plain\"\n          }\n        ],\n        types: [\n          \"text/_moz_htmlcontext\",\n          \"text/_moz_htmlinfo\",\n          \"text/html\",\n          \"text/plain\"\n        ]\n      }\n    });\n\n    var ieTextDragEvent = createIeDragEvent({\n      dataTransfer: {\n        types: [\n          \"Text\"\n        ]\n      }\n    });\n\n    assert(!uploadDropZone._testing.isValidFileDrag(chromeTextDragEvent), \"Chrome text drag events should not be valid file drags\");\n    assert(!uploadDropZone._testing.isValidFileDrag(firefoxTextDragEvent), \"Firefox text drag events should not be valid file drags\");\n    assert(!uploadDropZone._testing.isValidFileDrag(ieTextDragEvent), \"IE text drag events should not be valid file drags\");\n\n  });\n\n  it(\"determines file inputs as valid drag candidates\", function() {\n    $fixture.append(\"<div id='fine-dropzone'></div>\");\n    var uploadDropZone = new qq.UploadDropZone({element: $fixture.find(\"#fine-dropzone\")});\n\n    // A mock event similar to the one generated by dragging several files into the browser\n    var chromeFileDragEvent = createChromeDragEvent({\n      dataTransfer: {\n        items: [\n          {\n            kind: \"file\",\n            type: \"image/jpeg\"\n          },\n          {\n            kind: \"file\",\n            type: \"text/html\"\n          },\n          {\n            kind: \"file\",\n            type: \"\"\n          },\n          {\n            kind: \"file\",\n            type: \"application/javascript\"\n          }\n        ],\n        types: [\n          \"Files\"\n        ]\n      }\n    });\n\n    var firefoxFileDragEvent = createFirefoxDragEvent({\n      dataTransfer: {\n        items: [\n          {\n            kind: \"file\",\n            type: \"application/x-moz-file\"\n          },\n          {\n            kind: \"file\",\n            type: \"application/x-moz-file\"\n          },\n          {\n            kind: \"file\",\n            type: \"application/x-moz-file\"\n          },\n          {\n            kind: \"file\",\n            type: \"application/x-moz-file\"\n          }\n        ],\n        types: [\n          \"application/x-moz-file\",\n          \"Files\"\n        ]\n      }\n    });\n\n    var ieFileDragEvent = createIeDragEvent({\n      dataTransfer: {\n        types: [\n          \"Files\"\n        ]\n      }\n    });\n\n    assert(uploadDropZone._testing.isValidFileDrag(chromeFileDragEvent), \"Chrome file drag events are valid file drags\");\n    assert(uploadDropZone._testing.isValidFileDrag(firefoxFileDragEvent), \"Firefox file drag events are valid file drags\");\n    assert(uploadDropZone._testing.isValidFileDrag(ieFileDragEvent), \"IE file drag events are valid file drags\");\n  });\n\n  it(\"extracts directory path from entries\", function() {\n    var dnd = new qq.DragAndDrop();\n\n    var entry = {\n      name: \"a.txt\",\n      fullPath: \"/data/a.txt\"\n    };\n\n    var directoryPath = dnd._testing.extractDirectoryPath(entry);\n\n    assert.equal(directoryPath, \"data/\");\n  });\n\n  it(\"properly extracts directory path when file name occurs in parent directory names\", function() {\n    var dnd = new qq.DragAndDrop();\n\n    var entry = {\n      name: \"data\",\n      fullPath: \"/data/data\"\n    };\n\n    var directoryPath = dnd._testing.extractDirectoryPath(entry);\n\n    assert.equal(directoryPath, \"data/\");\n  });\n});\n"
  },
  {
    "path": "test/unit/exif.js",
    "content": "/* globals describe, beforeEach, $fixture, qq, assert, it, qqtest, helpme, purl */\ndescribe(\"exif.js\", function () {\n    \"use strict\";\n\n    describe(\"parseLittleEndian\", function() {\n        it(\"converts little endian hex string to big endian decimal\", function () {\n            var exif = new qq.Exif(),\n                maybeBigEndian = exif._testing.parseLittleEndian(\"012345AB\"),\n                expectedBigEndian = parseInt(\"AB452301\", 16);\n\n            assert.equal(maybeBigEndian, expectedBigEndian);\n        });\n    });\n\n    if (qq.supportedFeatures.imagePreviews && qqtest.canDownloadFileAsBlob) {\n        describe(\"JPEG Orientation tag extraction\", function() {\n\n            function testOrientation(key, expectedOrientation, done) {\n                qqtest.downloadFileAsBlob(key, \"image/jpeg\").then(function(blob) {\n                    new qq.Exif(blob, function() {}).parse().then(function(tagVals) {\n                        assert.equal(tagVals.Orientation, expectedOrientation);\n                        done();\n                    }, function() {\n                        assert.fail(\"Failed to extract EXIF data!\");\n                    });\n                }, function() {\n                    assert.fail(\"Problem downloading test file\");\n                });\n            }\n\n            it(\"Correctly parses Orientation for 1-oriented image\", function(done) {\n                testOrientation(\"up.jpg\", 1, done);\n            });\n\n            it(\"Correctly parses Orientation for 2-oriented image\", function(done) {\n                testOrientation(\"up-mirrored.jpg\", 2, done);\n            });\n\n            it(\"Correctly parses Orientation for 3-oriented image\", function(done) {\n                testOrientation(\"down.jpg\", 3, done);\n            });\n\n            it(\"Correctly parses Orientation for 4-oriented image\", function(done) {\n                testOrientation(\"down-mirrored.jpg\", 4, done);\n            });\n\n            it(\"Correctly parses Orientation for 5-oriented image\", function(done) {\n                testOrientation(\"left-mirrored.jpg\", 5, done);\n            });\n\n            it(\"Correctly parses Orientation for 6-oriented image\", function(done) {\n                testOrientation(\"left.jpg\", 6, done);\n            });\n\n            it(\"Correctly parses Orientation for 7-oriented image\", function(done) {\n                testOrientation(\"right-mirrored.jpg\", 7, done);\n            });\n\n            it(\"Correctly parses Orientation for 8-oriented image\", function(done) {\n                testOrientation(\"right.jpg\", 8, done);\n            });\n        });\n    }\n});\n"
  },
  {
    "path": "test/unit/file-upload-params-and-headers.js",
    "content": "/* globals describe, beforeEach, $fixture, qq, assert, it, qqtest, helpme, purl */\nif (qqtest.canDownloadFileAsBlob) {\n    describe(\"sending params and headers with upload requests\", function() {\n        \"use strict\";\n\n        var fileTestHelper = helpme.setupFileTests(),\n            testUploadEndpoint = \"/test/upload\",\n            params = {\n                foo: \"bar\",\n                one: 2,\n                thefunc: function() {\n                    return \"thereturn\";\n                }\n            },\n            headers = {\n                one: \"1\",\n                two: \"2\"\n            };\n\n        function getSimpleParamsUploader(mpe, paramsAsOptions) {\n            var uploader = new qq.FineUploaderBasic({\n                request: {\n                    endpoint: testUploadEndpoint,\n                    paramsInBody: mpe,\n                    forceMultipart: mpe,\n                    params: paramsAsOptions ? params : {},\n                    autoUpload: false\n                }\n            });\n\n            !paramsAsOptions && uploader.setParams(params);\n            return uploader;\n        }\n\n        function getSimpleHeadersUploader(headersAsOptions) {\n            var uploader = new qq.FineUploaderBasic({\n                request: {\n                    endpoint: testUploadEndpoint,\n                    customHeaders: headersAsOptions ? headers : {},\n                    autoUpload: false\n                }\n            });\n\n            !headersAsOptions && uploader.setCustomHeaders(headers);\n            return uploader;\n        }\n\n        function assertParamsInRequest(uploader, mpe, done, overrideParams) {\n            assert.expect(4, done);\n\n            qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function(blob) {\n                fileTestHelper.mockXhr();\n\n                var request,\n                    requestParams,\n                    purlUrl,\n                    theparams = overrideParams || params;\n\n                uploader.addFiles({name: \"test\", blob: blob});\n                uploader.uploadStoredFiles();\n\n                assert.equal(fileTestHelper.getRequests().length, 1, \"Wrong # of requests\");\n                request = fileTestHelper.getRequests()[0];\n                requestParams = request.requestBody.fields;\n                purlUrl = purl(request.url);\n\n                assert.equal(mpe ? requestParams.foo : purlUrl.param(\"foo\"), theparams.foo, \"'foo' param value incorrect\");\n                assert.equal(mpe ? requestParams.one : purlUrl.param(\"one\"), theparams.one, \"'one' param value incorrect\");\n                assert.equal(mpe ? requestParams.thefunc : purlUrl.param(\"thefunc\"), theparams.thefunc(), \"'thefunc' param value incorrect\");\n\n                fileTestHelper.getRequests()[0].respond(200, null, JSON.stringify({success: true}));\n            });\n        }\n\n\n        function assertHeadersInRequest(uploader, done) {\n            assert.expect(3, done);\n\n            qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function(blob) {\n                fileTestHelper.mockXhr();\n\n                var request;\n\n                uploader.addFiles({name: \"test\", blob: blob});\n                uploader.uploadStoredFiles();\n\n                assert.equal(fileTestHelper.getRequests().length, 1, \"Wrong # of requests\");\n                request = fileTestHelper.getRequests()[0];\n\n                assert.equal(request.requestHeaders.one, headers.one, \"Wrong 'one' header\");\n                assert.equal(request.requestHeaders.two, headers.two, \"Wrong 'two' header\");\n\n                fileTestHelper.getRequests()[0].respond(200, null, JSON.stringify({success: true}));\n            });\n        }\n\n        it(\"sends correct params in request for MPE uploads w/ params specified as options only\", function(done) {\n            var uploader = getSimpleParamsUploader(true, true);\n            assertParamsInRequest(uploader, true, done);\n        });\n\n        it(\"sends correct params in request for non-MPE uploads w/ params specified as options only\", function(done) {\n            var uploader = getSimpleParamsUploader(false, true);\n            assertParamsInRequest(uploader, false, done);\n        });\n\n        it(\"Sends correct params in request for MPE uploads w/ params specified via API only\", function(done) {\n            var uploader = getSimpleParamsUploader(true, false);\n            assertParamsInRequest(uploader, true, done);\n        });\n\n        it(\"sends correct params in request for non-MPE uploads w/ params specified as options only\", function(done) {\n            var uploader = getSimpleParamsUploader(false, false);\n            assertParamsInRequest(uploader, false, done);\n        });\n\n        it(\"sends correct params in request for MPE uploads w/ params initially specified via options then overriden via API\", function(done) {\n            var uploader = getSimpleParamsUploader(true, true),\n                overridenParams = qq.extend({one: 3}, params);\n\n            uploader.setParams(overridenParams);\n            assertParamsInRequest(uploader, true, done, overridenParams);\n        });\n\n        it(\"sends correct params in request for non-MPE uploads w/ params initially specified via options then overriden via API\", function(done) {\n            var uploader = getSimpleParamsUploader(false, true),\n                overridenParams = qq.extend({foo: \"abc\"}, params);\n\n            uploader.setParams(overridenParams);\n            assertParamsInRequest(uploader, false, done, overridenParams);\n        });\n\n        it(\"sends correct params in request for MPE uploads when params are overriden via API for specific files\", function(done) {\n            var uploader = getSimpleParamsUploader(true, true),\n                overridenParams = qq.extend({one: 3}, params);\n\n            uploader.setParams(overridenParams, 0);\n            uploader.setParams({}, 1);\n            assertParamsInRequest(uploader, true, done, overridenParams);\n        });\n\n        it(\"sends correct params in request for non-MPE uploads when params are overriden via API for specific files\", function(done) {\n            var uploader = getSimpleParamsUploader(false, true),\n                overridenParams = qq.extend({one: 3}, params);\n\n            uploader.setParams(overridenParams, 0);\n            uploader.setParams({}, 1);\n            assertParamsInRequest(uploader, false, done, overridenParams);\n        });\n\n        it(\"sends correct headers in request w/ headers specified as options\", function(done) {\n            var uploader = getSimpleHeadersUploader(true);\n            assertHeadersInRequest(uploader, done);\n        });\n\n        it(\"sends correct headers in request w/ headers specified via API\", function(done) {\n            var uploader = getSimpleHeadersUploader();\n            assertHeadersInRequest(uploader, done);\n        });\n    });\n}\n"
  },
  {
    "path": "test/unit/form-support.js",
    "content": "/* globals describe, assert, it, qq, qqtest, helpme, $fixture, before, after */\ndescribe(\"test form support\", function() {\n    \"use strict\";\n    var fakeLog = function() {};\n\n\n    describe(\"qq.FormSupport._form2obj\", function() {\n        it(\"should properly parse all standard input elements with values\", function() {\n            var form = $(\"<form></form>\"),\n                checkbox = $(\"<input type='checkbox' name='test_checkbox' checked='checked' value='test-checkbox'>\"),\n                hidden = $(\"<input type='hidden' name='test_hidden'>\"),\n                password = $(\"<input type='password' name='test_password'>\"),\n                radio = $(\"<input type='radio' name='test_radio' checked='checked' value='test-radio'>\"),\n                text = $(\"<input type='text' name='test_text'>\"),\n                select = $(\"<select name='test_select'><option value='one'></option><option value='two' selected></option></select>\"),\n                expectedObj = {\n                    test_checkbox: \"test-checkbox\",\n                    test_hidden: \"hidden_text\",\n                    test_password: \"password_text\",\n                    test_radio: \"test-radio\",\n                    test_text: \"text_text\",\n                    test_select: \"two\"\n                };\n\n            form.append(checkbox).append(hidden).append(password).append(radio).append(text).append(select);\n\n            checkbox.prop(\"checked\", expectedObj.test_checkbox);\n            hidden.val(\"hidden_text\", expectedObj.test_hidden);\n            password.val(\"password_text\", expectedObj.test_password);\n            radio.prop(\"checked\", expectedObj.test_radio);\n            text.val(\"text_text\", expectedObj.test_text);\n\n            assert.deepEqual(qq.FormSupport.prototype._form2Obj(form[0]), expectedObj);\n        });\n\n        it(\"should ignore all non-input elements\", function() {\n            var form = $(\"<form></form>\"),\n                text = $(\"<input type='text' name='test_text' value='test_text'>\"),\n                label = $(\"<label>test</label>\"),\n                span = $(\"<span>test2</span>\"),\n                expectedObj = {\n                    test_text: \"test_text\"\n                };\n\n            form.append(label).append(text).append(span);\n\n            assert.deepEqual(qq.FormSupport.prototype._form2Obj(form[0]), expectedObj);\n        });\n\n        it(\"should ignore only irrelevant input elements\", function() {\n            var form = $(\"<form></form>\"),\n                text = $(\"<input type='text' name='test_text' value='test_text'>\"),\n                checkbox = $(\"<input type='checkbox' name='test_checkbox'>\"),\n                radio = $(\"<input type='radio' name='test_radio'>\"),\n                button = $(\"<input type='button' name='test_button' value='button'>\"),\n                file = $(\"<input type='file' name='test_file'>\"),\n                image = $(\"<input type='image' name='test_image' scr='some/img'>\"),\n                reset = $(\"<input type='reset' name='test_reset'>\"),\n                submit = $(\"<input type='submit' name='test_submit' value='submit'>\"),\n                textarea = $(\"<textarea name='test_textarea'>test textarea text</textarea>\"),\n                disabledAndNotHidden = $(\"<input type='text' name='test_text_disabled' disabled=true>\"),\n                disabledAndHidden = $(\"<input type='hidden' name='test_hidden_disabled' value='foo' disabled=true>\"),\n                expectedObj = {\n                    test_text: \"test_text\",\n                    test_hidden_disabled: \"foo\",\n                    test_textarea: \"test textarea text\"\n                };\n\n            form.append(checkbox).append(radio).append(text).append(button).append(file).append(image).append(reset).append(submit).append(disabledAndNotHidden).append(disabledAndHidden).append(textarea);\n\n            assert.deepEqual(qq.FormSupport.prototype._form2Obj(form[0]), expectedObj);\n        });\n    });\n\n    it(\"switches to manual upload mode if a form is attached via options\", function() {\n        var uploader = new qq.FineUploaderBasic({\n            form: {\n                element: document.createElement(\"form\")\n            }\n        });\n\n        assert.ok(!uploader._options.autoUpload);\n    });\n\n    it(\"switches to manual upload mode if a form is attached via API\", function() {\n        var uploader = new qq.FineUploaderBasic();\n        uploader.setForm(document.createElement(\"form\"));\n\n        assert.ok(!uploader._options.autoUpload);\n    });\n\n    it(\"switches to auto upload mode if a form is attached & form.autoUpload is set to true\", function() {\n        var uploader = new qq.FineUploaderBasic({\n            form: {\n                element: document.createElement(\"form\"),\n                autoUpload: true\n            }\n        });\n\n        assert.ok(uploader._options.autoUpload);\n    });\n\n    it(\"uses action attribute as endpoint\", function() {\n        var uploader = new qq.FineUploaderBasic({\n            form: {\n                element: (function() {\n                    var form = document.createElement(\"form\");\n                    form.setAttribute(\"action\", \"form/action/endpoint\");\n                    return form;\n                }()),\n                autoUpload: true\n            }\n        });\n\n        assert.equal(uploader._options.request.endpoint, \"form/action/endpoint\");\n    });\n\n\n    it(\"uploads files on form submit by default\", function(done) {\n        assert.expect(1, done);\n\n        var startUpload = function() {\n                assert.ok(true);\n            },\n            form = document.createElement(\"form\"),\n            formSupport = new qq.FormSupport({interceptSubmit: true, autoUpload: false, element: form}, startUpload, fakeLog);\n\n        $(form).submit();\n    });\n\n    // Ignore test if Conditions API is not supported, or if this is Firefox,\n    // which will simply not trigger the submit event or allow the submit function to be invoked\n    // if the form is invalid.\n    if (document.createElement(\"form\").checkValidity && !qq.firefox()) {\n        it(\"doesn't upload file if form validation fails\", function(done) {\n            assert.expect(1, done);\n\n            var form = $(\"<form><input type='text' name='test_text' required></form>\")[0],\n                startUpload = function() {\n                    assert.fail(null, null, \"Files should not have been uploaded\");\n                },\n                formSupport;\n\n            form.submit = function() {\n                assert.ok(true);\n            };\n            formSupport = new qq.FormSupport({interceptSubmit: true, autoUpload: false, element: form}, startUpload, fakeLog);\n\n            $(form).submit();\n        });\n    }\n\n    if (qqtest.canDownloadFileAsBlob) {\n        describe(\"verify params sent with upload requests\", function() {\n\n            var fileTestHelper = helpme.setupFileTests(),\n                testUploadEndpoint = \"/test/upload\",\n\n                formHtml = \"<form id='qq-form'><input type='text' name='text_test' value='test'></form>\",\n                $form = $(formHtml),\n\n                dynamicFormHtml = \"<form id='qq-dynamic-form'><input type='text' name='text_test' value='test'></form>\",\n                $dynamicForm = $(dynamicFormHtml),\n\n                testUploadWithForm = function(uploader, endopint, dynamic, done) {\n                    assert.expect(4, done);\n\n                    qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function(blob) {\n                        fileTestHelper.mockXhr();\n\n                        var request, requestParams;\n\n                        if (dynamic) {\n                            uploader.setForm($dynamicForm[0]);\n                        }\n\n                        uploader.addFiles(blob);\n\n                        assert.equal(fileTestHelper.getRequests().length, 0, \"Wrong # of requests\");\n                        uploader.uploadStoredFiles();\n                        assert.equal(fileTestHelper.getRequests().length, 1, \"Wrong # of requests\");\n\n                        request = fileTestHelper.getRequests()[0];\n                        assert.equal(request.url, endopint);\n                        requestParams = request.requestBody.fields;\n                        assert.equal(requestParams.text_test, \"test\");\n                    });\n                };\n\n            it(\"initial form - attaches to form automatically if all conventions are used\", function(done) {\n                $fixture.append($form);\n\n                var uploader = new qq.FineUploaderBasic({\n                    request: {\n                        endpoint: testUploadEndpoint\n                    }\n                });\n\n                testUploadWithForm(uploader, testUploadEndpoint, false, done);\n            });\n\n            it(\"dynamic form - attaches to form automatically if all conventions are used\", function(done) {\n                $fixture.append($dynamicForm);\n\n                var uploader = new qq.FineUploaderBasic({\n                    request: {\n                        endpoint: testUploadEndpoint\n                    }\n                });\n\n                testUploadWithForm(uploader, testUploadEndpoint, true, done);\n            });\n\n            it(\"attaches to form automatically if an alternate form ID is specified\", function(done) {\n                var $newForm = $form.clone().attr(\"id\", \"qq-form-test\");\n                $fixture.append($newForm);\n\n                var uploader = new qq.FineUploaderBasic({\n                    request: {\n                        endpoint: testUploadEndpoint\n                    },\n                    form: {\n                        element: \"qq-form-test\"\n                    }\n                });\n\n                testUploadWithForm(uploader, testUploadEndpoint, false, done);\n            });\n\n            it(\"attaches to form automatically if an element is specified\", function(done) {\n                var $newForm = $form.clone().removeAttr(\"id\");\n                $fixture.append($newForm);\n\n                var uploader = new qq.FineUploaderBasic({\n                    request: {\n                        endpoint: testUploadEndpoint\n                    },\n                    form: {\n                        element: $newForm[0]\n                    }\n                });\n\n                testUploadWithForm(uploader, testUploadEndpoint, false, done);\n            });\n\n            it(\"initial form - uses action attribute as endpoint, if specified\", function(done) {\n                var $newForm = $form.clone().attr(\"action\", \"/form/action\");\n                $fixture.append($newForm);\n\n                var uploader = new qq.FineUploaderBasic({});\n\n                testUploadWithForm(uploader, \"/form/action\", false, done);\n            });\n\n            it(\"dynamic form - uses action attribute as endpoint, if specified\", function(done) {\n                $dynamicForm.attr(\"action\", \"/form/action\");\n\n                var uploader = new qq.FineUploaderBasic({});\n\n                testUploadWithForm(uploader, \"/form/action\", true, done);\n            });\n        });\n    }\n});\n"
  },
  {
    "path": "test/unit/identify.js",
    "content": "/* globals describe, beforeEach, $fixture, qq, assert, it, qqtest, helpme, purl */\nif (qq.supportedFeatures.imagePreviews && qqtest.canDownloadFileAsBlob) {\n    describe(\"identify.js\", function() {\n        \"use strict\";\n\n        function testPreviewability(expectedToBePreviewable, key, expectedMime, done) {\n            qqtest.downloadFileAsBlob(key, expectedMime).then(function(blob) {\n                new qq.Identify(blob, function() {}).isPreviewable().then(function(mime) {\n                    !expectedToBePreviewable && assert.fail();\n                    assert.equal(mime, expectedMime);\n                    done();\n                }, function() {\n                    expectedToBePreviewable && assert.fail();\n                    assert.ok(true);\n                    done();\n                });\n            }, function() {\n                assert.fail(\"Problem downloading test file\");\n            });\n        }\n\n        it(\"classifies gif as previewable\", function(done) {\n            testPreviewability(true, \"drop-background.gif\", \"image/gif\", done);\n        });\n\n        it(\"classifies jpeg as previewable\", function(done) {\n            testPreviewability(true, \"fearless.jpeg\", \"image/jpeg\", done);\n        });\n\n        it(\"classifies bmp as previewable\", function(done) {\n            testPreviewability(true, \"g04.bmp\", \"image/bmp\", done);\n        });\n\n        it(\"classifies png as previewable\", function(done) {\n            testPreviewability(true, \"not-available_l.png\", \"image/png\", done);\n        });\n\n        it(\"classifies tiff as previewable\", function(done) {\n            testPreviewability(qq.supportedFeatures.tiffPreviews, \"sample.tif\", \"image/tiff\", done);\n        });\n\n        it(\"marks a non-image as not previewable\", function(done) {\n            testPreviewability(false, \"simpletext.txt\", null, done);\n        });\n    });\n}\n"
  },
  {
    "path": "test/unit/iframe.xss.response.js",
    "content": "/* globals describe, beforeEach, afterEach, $fixture, qq, assert, it, qqtest, helpme, purl */\nif (window.postMessage) {\n    describe(\"iframe.xss.response\", function () {\n        \"use strict\";\n\n        var iframe, doc,\n            script = \"<scr\" + \"ipt type='text/javascript' src='http://localhost:4001/client/js/iframe.xss.response.js'></scr\" + \"ipt>\";\n\n        beforeEach(function () {\n            iframe = document.createElement(\"iframe\");\n            iframe.setAttribute(\"id\", \"iframe-fixture\");\n            iframe.setAttribute(\"src\", \"java\" + String.fromCharCode(115) + \"cript:false;\");\n            $(\"#mocha-fixture\").append(iframe);\n            doc = iframe.contentWindow || iframe.contentDocument;\n            if (doc.document) {\n                doc = doc.document;\n            }\n        });\n\n        afterEach(function () {\n            $(\"#iframe-fixture\").remove();\n            doc = null;\n            $(window).off(\"message\");\n        });\n\n        it(\"#913 - Correctly parses JSON nested more than 1 level (simple)\", function (done) {\n            var stuff = JSON.stringify({ hello: \"world\", my: \"name\", is: \"mark\" });\n\n            $(window).on(\"message\", function (event) {\n                var data = event.originalEvent.data;\n                assert.equal(data, stuff);\n                done();\n            });\n\n            doc.open();\n            doc.write(stuff + script);\n            doc.close();\n        });\n\n        it(\"#913 - Correctly parses JSON nested more than 1 level (deep)\", function (done) {\n            var stuff = JSON.stringify({ hello: { my: \"name\", is: { mark: \"?\" }}, foo: \"bar\"});\n\n            $(window).on(\"message\", function (event) {\n                var data = event.originalEvent.data;\n                assert.equal(data, stuff);\n                done();\n            });\n\n            doc.open();\n            doc.write(stuff + script);\n            doc.close();\n        });\n\n        it(\"#913 - Correctly parses JSON nested more than 1 level (test case)\", function (done) {\n            var stuff = \"{'images':{'id':2}, 'uuid':'bla'}\";\n\n            $(window).on(\"message\", function (event) {\n                var data = event.originalEvent.data;\n                assert.equal(data, stuff);\n                done();\n            });\n\n            doc.open();\n            doc.write(stuff + script);\n            doc.close();\n        });\n\n    });\n}\n"
  },
  {
    "path": "test/unit/image.js",
    "content": "/* globals describe, beforeEach, $fixture, qq, assert, it, qqtest, helpme, purl */\ndescribe(\"image.js\", function() {\n    \"use strict\";\n\n    var imageGenerator,\n        isIe7 = navigator.userAgent.indexOf(\"MSIE 7\") !== -1,\n        isIe8 = navigator.userAgent.indexOf(\"MSIE 8\") !== -1,\n        canvasSupported = (function() {\n            var elem = document.createElement(\"canvas\");\n            return !!(elem.getContext && elem.getContext(\"2d\"));\n        }());\n\n    beforeEach(function() {\n        imageGenerator = new qq.ImageGenerator({\n            log: function() {}\n        });\n    });\n\n    describe(\"isImg\", function() {\n        it(\"Properly identify img tag\", function() {\n            var img = document.createElement(\"img\"),\n                div = document.createElement(\"div\");\n\n            assert.ok(imageGenerator._testing.isImg(img));\n            assert.ok(!imageGenerator._testing.isImg(div));\n        });\n    });\n\n    if (canvasSupported) {\n        describe(\"isCanvas\", function() {\n            it(\"Properly identify canvas tag\", function() {\n                var canvas = document.createElement(\"canvas\"),\n                    div = document.createElement(\"div\");\n\n                assert.ok(imageGenerator._testing.isCanvas(canvas));\n                assert.ok(!imageGenerator._testing.isCanvas(div));\n            });\n        });\n    }\n\n    if (!isIe7 && !isIe8) {\n        describe(\"isCrossOrigin\", function() {\n            it(\"ensures a cross-origin URL is properly identified\", function() {\n                assert.ok(!imageGenerator._testing.isCrossOrigin(window.location.href));\n                assert.ok(imageGenerator._testing.isCrossOrigin(\"http://foobar.com\"));\n            });\n        });\n    }\n\n    describe(\"determineMimeOfFileName\", function() {\n        it(\"identifies all renderable image formats\", function() {\n            var pathsAndExpectedTypes = {\n                \"http://example.com/hmm/ha/test.jpg\": \"image/jpeg\",\n                \"http://example.com/hmm/ha/test.jpeg\": \"image/jpeg\",\n                \"test.jpg\": \"image/jpeg\",\n                \"test.jpeg\": \"image/jpeg\",\n                \"http://example.com/hmm/ha/test.png\": \"image/png\",\n                \"test.png\": \"image/png\",\n                \"http://example.com/hmm/ha/test.bmp\": \"image/bmp\",\n                \"test.bmp\": \"image/bmp\",\n                \"http://example.com/hmm/ha/test.gif\": \"image/gif\",\n                \"test.gif\": \"image/gif\",\n                \"http://example.com/hmm/ha/test.tiff\": \"image/tiff\",\n                \"http://example.com/hmm/ha/test.tif\": \"image/tiff\",\n                \"test.tif\": \"image/tiff\",\n                \"http://example.com/hmm/ha/test\": null,\n                \"test\": null\n            };\n\n            qq.each(pathsAndExpectedTypes, function(path, expectedType) {\n                assert.equal(imageGenerator._testing.determineMimeOfFileName(path), expectedType);\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "test/unit/on-all-complete.js",
    "content": "/* globals describe, beforeEach, $fixture, qq, assert, it, qqtest, helpme, purl */\nif (qqtest.canDownloadFileAsBlob) {\n    describe(\"onAllComplete callback tests\", function() {\n        \"use strict\";\n\n        var fileTestHelper = helpme.setupFileTests(),\n            testUploadEndpoint = \"/test/upload\",\n            runSingleUploadTest = function(autoUpload, success, done) {\n                assert.expect(4, done);\n\n                var callbackOrder = [],\n                    uploader = new qq.FineUploaderBasic({\n                    request: {\n                        endpoint: testUploadEndpoint\n                    },\n                    autoUpload: autoUpload,\n                    callbacks: {\n                        onComplete: function() {\n                            callbackOrder.push(\"complete\");\n                        },\n                        onAllComplete: function(succeeded, failed) {\n                            callbackOrder.push(\"allComplete\");\n\n                            if (success) {\n                                assert.equal(succeeded.length, 1);\n                                assert.equal(succeeded[0], 0);\n                                assert.equal(failed.length, 0);\n                            }\n                            else {\n                                assert.equal(failed.length, 1);\n                                assert.equal(failed[0], 0);\n                                assert.equal(succeeded.length, 0);\n                            }\n\n                            assert.deepEqual(callbackOrder, [\"complete\", \"allComplete\"]);\n                        }\n                    }\n                });\n\n                qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function (blob) {\n                    fileTestHelper.mockXhr();\n                    uploader.addFiles(blob);\n                    !autoUpload && uploader.uploadStoredFiles();\n\n                    if (success) {\n                        fileTestHelper.getRequests()[0].respond(200, null, JSON.stringify({success: true}));\n                    }\n                    else {\n                        fileTestHelper.getRequests()[0].respond(400, null, null);\n                    }\n                });\n            },\n            runMultipleUploadTest = function(success, done) {\n                assert.expect(5, done);\n\n                var callbackOrder = [],\n                    uploader = new qq.FineUploaderBasic({\n                    request: {\n                        endpoint: testUploadEndpoint\n                    },\n                    callbacks: {\n                        onComplete: function() {\n                            callbackOrder.push(\"complete\");\n                        },\n                        onUpload: function(id) {\n                            if (success) {\n                                setTimeout(function() {\n                                    fileTestHelper.getRequests()[id].respond(200, null, JSON.stringify({success: true}));\n                                }, 0);\n                            }\n                            else {\n                                setTimeout(function() {\n                                    fileTestHelper.getRequests()[id].respond(400, null, null);\n                                }, 0);\n                            }\n                        },\n                        onAllComplete: function(succeeded, failed) {\n                            callbackOrder.push(\"allComplete\");\n\n                            if (success) {\n                                assert.equal(succeeded.length, 2);\n                                assert.equal(succeeded[0], 0);\n                                assert.equal(succeeded[1], 1);\n                                assert.equal(failed.length, 0);\n                            }\n                            else {\n                                assert.equal(failed.length, 2);\n                                assert.equal(failed[0], 0);\n                                assert.equal(failed[1], 1);\n                                assert.equal(succeeded.length, 0);\n                            }\n\n                            assert.deepEqual(callbackOrder, [\"complete\", \"complete\", \"allComplete\"]);\n                        }\n                    }\n                });\n\n                qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function (blob) {\n                    fileTestHelper.mockXhr();\n                    uploader.addFiles([blob, blob]);\n                });\n            };\n\n\n        it(\"calls onAllComplete after a single file is submitted & completed - manual upload\", function(done) {\n            runSingleUploadTest(false, true, done);\n        });\n\n        it(\"calls onAllComplete after a single file is submitted & completed (failed) - manual upload\", function(done) {\n            runSingleUploadTest(false, false, done);\n        });\n\n        it(\"calls onAllComplete after a single file is submitted & completed - auto upload\", function(done) {\n            runSingleUploadTest(true, true, done);\n        });\n\n        it(\"calls onAllComplete after a single file is submitted & completed (failed) - auto upload\", function(done) {\n            runSingleUploadTest(true, false, done);\n        });\n\n        it(\"does not call onAllComplete after a single file is submitted and canceled\", function(done) {\n            assert.expect(1, done);\n\n            var callbackOrder = [],\n                uploader = new qq.FineUploaderBasic({\n                request: {\n                    endpoint: testUploadEndpoint\n                },\n                autoUpload: false,\n                callbacks: {\n                    onCancel: function() {\n                        callbackOrder.push(\"cancel\");\n                    },\n                    onAllComplete: function() {\n                        assert.fail(null, null, \"onAllComplete should not have been called\");\n                    }\n                }\n            });\n\n            qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function (blob) {\n                fileTestHelper.mockXhr();\n                uploader.addFiles(blob);\n                uploader.cancel(0);\n\n                setTimeout(function() {\n                    assert.deepEqual(callbackOrder, [\"cancel\"]);\n                }, 0);\n            });\n        });\n\n        it(\"calls onAllComplete after multiple files are submitted & completed (all succeeded)\", function(done) {\n            runMultipleUploadTest(true, done);\n        });\n\n        it(\"calls onAllComplete after multiple files are submitted & completed (all failed)\", function(done) {\n            runMultipleUploadTest(false, done);\n        });\n\n        it(\"calls onAllComplete after multiple files are submitted & completed (1 succeeded, 1 failed)\", function(done) {\n            assert.expect(5, done);\n\n            var failId = 1,\n                callbackOrder = [],\n                uploader = new qq.FineUploaderBasic({\n                request: {\n                    endpoint: testUploadEndpoint\n                },\n                callbacks: {\n                    onComplete: function() {\n                        callbackOrder.push(\"complete\");\n                    },\n                    onUpload: function(id) {\n                        if (id === failId) {\n                            setTimeout(function() {\n                                fileTestHelper.getRequests()[id].respond(400, null, null);\n                            }, 0);\n                        }\n                        else {\n                            setTimeout(function() {\n                                fileTestHelper.getRequests()[id].respond(200, null, JSON.stringify({success: true}));\n                            }, 0);\n                        }\n                    },\n                    onAllComplete: function(succeeded, failed) {\n                        callbackOrder.push(\"allComplete\");\n\n                        assert.equal(succeeded.length, 1);\n                        assert.equal(succeeded[0], 0);\n                        assert.equal(failed.length, 1);\n                        assert.equal(failed[0], failId);\n\n                        assert.deepEqual(callbackOrder, [\"complete\", \"complete\", \"allComplete\"]);\n                    }\n                }\n            });\n\n            qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function (blob) {\n                fileTestHelper.mockXhr();\n                uploader.addFiles([blob, blob]);\n            });\n        });\n\n        it(\"calls onAllComplete after multiple files are submitted (1 succeeded, 1 rejected)\", function(done) {\n            assert.expect(4, done);\n\n            var cancelId = 1,\n                callbackOrder = [],\n                uploader = new qq.FineUploaderBasic({\n                request: {\n                    endpoint: testUploadEndpoint\n                },\n                callbacks: {\n                    onComplete: function() {\n                        callbackOrder.push(\"complete\");\n                    },\n                    onSubmit: function(id) {\n                        return id !== cancelId;\n                    },\n                    onUpload: function(id) {\n                        setTimeout(function() {\n                            fileTestHelper.getRequests()[id].respond(200, null, JSON.stringify({success: true}));\n                        }, 0);\n                    },\n                    onAllComplete: function(succeeded, failed) {\n                        callbackOrder.push(\"allComplete\");\n\n                        assert.equal(succeeded.length, 1);\n                        assert.equal(succeeded[0], 0);\n                        assert.equal(failed.length, 0);\n\n                        assert.deepEqual(callbackOrder, [\"complete\", \"allComplete\"]);\n                    }\n                }\n            });\n\n            qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function (blob) {\n                fileTestHelper.mockXhr();\n                uploader.addFiles([blob, blob]);\n            });\n        });\n\n        it(\"does not call onAllComplete after multiple files are submitted (all rejected)\", function(done) {\n            assert.expect(1, done);\n\n            var callbackOrder = [],\n                uploader = new qq.FineUploaderBasic({\n                request: {\n                    endpoint: testUploadEndpoint\n                },\n                callbacks: {\n                    onSubmit: function() {\n                        callbackOrder.push(\"submit\");\n                        return false;\n                    },\n                    onAllComplete: function(succeeded, failed) {\n                        assert.fail(null, null, \"onAllComplete should not have been called\");\n                    }\n                }\n            });\n\n            qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function (blob) {\n                fileTestHelper.mockXhr();\n                uploader.addFiles([blob, blob]);\n\n                setTimeout(function() {\n                    assert.deepEqual(callbackOrder, [\"submit\", \"submit\"]);\n                }, 0);\n            });\n        });\n\n        it(\"calls onComplete correctly after for each batch of files\", function(done) {\n            assert.expect(10, done);\n\n            var uploadCount = 0,\n                callbackOrder = [],\n                uploader = new qq.FineUploaderBasic({\n                request: {\n                    endpoint: testUploadEndpoint\n                },\n                callbacks: {\n                    onComplete: function() {\n                        callbackOrder.push(\"complete\");\n                    },\n                    onUpload: function(id) {\n                        setTimeout(function() {\n                            fileTestHelper.getRequests()[id].respond(200, null, JSON.stringify({success: true}));\n                        }, 0);\n                    },\n                    onAllComplete: function(succeeded, failed) {\n                        callbackOrder.push(\"allComplete\");\n\n                        assert.equal(succeeded.length, 2);\n                        assert.equal(succeeded[0], uploadCount);\n                        assert.equal(succeeded[1], uploadCount + 1);\n                        assert.equal(failed.length, 0);\n\n                        assert.deepEqual(callbackOrder, [\"complete\", \"complete\", \"allComplete\"]);\n\n                        if (uploadCount < 2) {\n                            uploadCount+=2;\n                            setTimeout(uploadFiles, 0);\n                        }\n                    }\n                }\n            }),\n                uploadFiles = function() {\n                    callbackOrder = [];\n                    uploader.addFiles([blobToUpload, blobToUpload]);\n                },\n                blobToUpload;\n\n\n            qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function (blob) {\n                blobToUpload = blob;\n\n                fileTestHelper.mockXhr();\n                uploadFiles();\n            });\n        });\n    });\n}\n"
  },
  {
    "path": "test/unit/promise.js",
    "content": "/* globals describe, beforeEach, $fixture, qq, assert, it, qqtest, helpme, purl */\ndescribe(\"promise.js\", function () {\n    \"use strict\";\n\n    describe(\"isPromise\", function () {\n        it(\"returns true for a new'd promise\", function () {\n            var promise = new qq.Promise();\n            assert.ok(promise instanceof qq.Promise,\n               \"a `new qq.Promise()` should be a promise\");\n        });\n    });\n\n    describe(\"Promise API\", function () {\n\n        it(\"expects `success` callback\", function(finish){\n            var promise = new qq.Promise();\n            promise.then(function(value){\n                assert.ok(value);\n                finish();\n            });\n\n            promise.success(true);\n        });\n     \n        it(\"expects `failure` callback\", function(finish){\n            var promise = new qq.Promise();\n            promise.then(null, function(value){\n                assert.ok(value);\n                finish();\n            });\n\n            promise.failure(true);\n        });\n\n        it(\"success: expects `done` to be callback\", function (finish) {\n            var promise = new qq.Promise();\n            promise.done(function (value) {\n                assert.ok(value);\n                finish();\n            });\n\n            promise.success(true);\n        });\n\n        it(\"failure: expects `done` to be callback\", function (finish) {\n            var promise = new qq.Promise();\n            promise.done(function (value) {\n                assert.ok(value);\n                finish();\n            });\n\n            promise.failure(true);\n        });\n\n        it(\"ensures success callback is not called if promise fails before then is invoked\", function() {\n            var promise = new qq.Promise().failure();\n\n            promise.then(function() {\n                assert.ok(false, \"Success callback was unexpectedly invoked\");\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "test/unit/resources/empty.txt",
    "content": ""
  },
  {
    "path": "test/unit/resources/simpletext.txt",
    "content": "test"
  },
  {
    "path": "test/unit/s3/cdn/generic-chunked.js",
    "content": "/* globals describe, beforeEach, $fixture, qq, assert, it, qqtest, helpme, purl */\nif (qqtest.canDownloadFileAsBlob) {\n    describe(\"s3 chunked upload tests\", function() {\n        \"use strict\";\n\n        var fileTestHelper = helpme.setupFileTests(),\n            testS3Endpoint = \"http://some.cdn.com\",\n            testAccessKey = \"testAccessKey\",\n            expectedFileSize = 3266,\n            expectedChunks = 2,\n            chunkSize = Math.round(expectedFileSize / expectedChunks),\n            typicalChunkingOption = {\n                enabled: true,\n                partSize: chunkSize\n            };\n\n        describe(\"server-side signature-based chunked S3 upload tests for a CDN endpoint\", function() {\n            var startTypicalTest = function(uploader, done) {\n                    qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function (blob) {\n                        var uploadPartRequest,\n                            initiateRequest,\n                            uploadPartSignatureRequest1,\n                            uploadPartSignatureRequest2,\n                            uploadPartToSign1,\n                            uploadPartToSign2,\n                            uploadCompleteSignatureRequest,\n                            uploadCompleteToSign,\n                            multipartCompleteRequest,\n                            initiateSignatureRequest,\n                            uploadRequest,\n                            initiateToSign;\n\n                        fileTestHelper.mockXhr();\n                        uploader.addFiles({name: \"test.jpg\", blob: blob});\n\n                        assert.equal(fileTestHelper.getRequests().length, 1, \"Wrong # of requests\");\n\n                        initiateSignatureRequest = fileTestHelper.getRequests()[0];\n                        initiateToSign = JSON.parse(initiateSignatureRequest.requestBody);\n\n                        // signature request for initiate multipart upload\n                        assert.ok(initiateToSign.headers.indexOf(\"/mybucket/\" + uploader.getKey(0) + \"?uploads\") > 0);\n                        initiateSignatureRequest.respond(200, null, JSON.stringify({signature: \"thesignature\"}));\n\n                        // initiate multipart upload request\n                        initiateRequest = fileTestHelper.getRequests()[1];\n                        initiateRequest.respond(200, null, \"<UploadId>123</UploadId>\");\n\n                        // signature request for upload part 1\n                        uploadPartSignatureRequest1 = fileTestHelper.getRequests()[3];\n                        uploadPartToSign1 = JSON.parse(uploadPartSignatureRequest1.requestBody);\n                        assert.ok(uploadPartToSign1.headers.indexOf(\"/mybucket/\" + uploader.getKey(0) + \"?partNumber=1&uploadId=123\") > 0);\n                        uploadPartSignatureRequest1.respond(200, null, JSON.stringify({signature: \"thesignature\"}));\n\n                        // upload part 1 request\n                        uploadPartRequest = fileTestHelper.getRequests()[2];\n                        uploadPartRequest.respond(200, {ETag: \"etag1\"}, null);\n\n                        // signature request for upload part 2\n                        uploadPartSignatureRequest2 = fileTestHelper.getRequests()[5];\n                        uploadPartToSign2 = JSON.parse(uploadPartSignatureRequest2.requestBody);\n                        assert.ok(uploadPartToSign2.headers.indexOf(\"/mybucket/\" + uploader.getKey(0) + \"?partNumber=2&uploadId=123\") > 0);\n                        uploadPartSignatureRequest2.respond(200, null, JSON.stringify({signature: \"thesignature\"}));\n\n                        // upload part 2 request\n                        uploadPartRequest = fileTestHelper.getRequests()[4];\n                        uploadPartRequest.respond(200, {ETag: \"etag2\"}, null);\n\n                        // signature request for multipart complete\n                        uploadCompleteSignatureRequest = fileTestHelper.getRequests()[6];\n                        uploadCompleteToSign = JSON.parse(uploadCompleteSignatureRequest.requestBody);\n                        assert.ok(uploadCompleteToSign.headers.indexOf(\"/mybucket/\" + uploader.getKey(0) + \"?uploadId=123\") > 0);\n                        uploadCompleteSignatureRequest.respond(200, null, JSON.stringify({signature: \"thesignature\"}));\n\n                        // multipart complete request\n                        multipartCompleteRequest = fileTestHelper.getRequests()[7];\n                        multipartCompleteRequest.respond(200, null, \"<CompleteMultipartUploadResult><Bucket>mybucket</Bucket><Key>\" + uploader.getKey(0) + \"</Key></CompleteMultipartUploadResult>\");\n\n                        assert.equal(uploader.getUploads()[0].status, qq.status.UPLOAD_SUCCESSFUL);\n\n                        done();\n                    });\n                },\n                testSignatureEndoint = \"/signature\",\n                typicalRequestOption = {\n                    accessKey: testAccessKey,\n                    endpoint: testS3Endpoint\n                },\n                typicalSignatureOption = {\n                    endpoint: testSignatureEndoint\n                };\n\n            it(\"handles a chunked upload w/ bucket specified as string\", function(done) {\n                var uploader = new qq.s3.FineUploaderBasic({\n                        request: typicalRequestOption,\n                        signature: typicalSignatureOption,\n                        chunking: typicalChunkingOption,\n                        objectProperties: {\n                            bucket: \"mybucket\"\n                        }\n                    }\n                );\n\n                startTypicalTest(uploader, done);\n            });\n\n            it(\"handles a chunked upload w/ bucket specified via function\", function(done) {\n                var uploader = new qq.s3.FineUploaderBasic({\n                        request: typicalRequestOption,\n                        signature: typicalSignatureOption,\n                        chunking: typicalChunkingOption,\n                        objectProperties: {\n                            bucket: function() {\n                                return \"mybucket\";\n                            }\n                        }\n                    }\n                );\n\n                startTypicalTest(uploader, done);\n            });\n\n            it(\"handles a chunked upload w/ bucket specified via promissory function\", function(done) {\n                var uploader = new qq.s3.FineUploaderBasic({\n                        request: typicalRequestOption,\n                        signature: typicalSignatureOption,\n                        chunking: typicalChunkingOption,\n                        objectProperties: {\n                            bucket: function() {\n                                var promise = new qq.Promise();\n                                promise.success(\"mybucket\");\n                                return promise;\n                            }\n                        }\n                    }\n                );\n\n                startTypicalTest(uploader, done);\n            });\n        });\n    });\n}\n"
  },
  {
    "path": "test/unit/s3/cdn/generic-non-chunked.js",
    "content": "/* globals describe, beforeEach, $fixture, qq, assert, it, qqtest, helpme, purl, Q */\nif (qqtest.canDownloadFileAsBlob) {\n    describe(\"simple S3 upload tests vis a generic CDN\", function() {\n        \"use strict\";\n\n        var fileTestHelper = helpme.setupFileTests(),\n            testS3Endpoint = \"http://some.cdn.com\",\n            testSignatureEndoint = \"/signature\",\n            testAccessKey = \"testAccessKey\",\n            typicalRequestOption = {\n                accessKey: testAccessKey,\n                endpoint: testS3Endpoint\n            },\n            typicalSignatureOption = {\n                endpoint: testSignatureEndoint\n            },\n            startTypicalTest = function(uploader, done) {\n                qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function (blob) {\n                    var signatureRequest, uploadRequest, policyDoc,\n                        conditions = {},\n                        uploadSuccessRequest, uploadSuccessRequestParsedBody;\n\n                    fileTestHelper.mockXhr();\n                    uploader.addFiles({name: \"test.jpg\", blob: blob});\n\n                    setTimeout(function() {\n                        assert.equal(fileTestHelper.getRequests().length, 2, \"Wrong # of requests\");\n\n                        uploadRequest = fileTestHelper.getRequests()[0];\n                        signatureRequest = fileTestHelper.getRequests()[1];\n                        policyDoc = JSON.parse(signatureRequest.requestBody);\n\n                        qq.each(policyDoc.conditions, function(condIdx, condObj) {\n                            var condName = Object.getOwnPropertyNames(condObj)[0];\n                            conditions[condName] = condObj[condName];\n                        });\n\n                        assert.equal(conditions.bucket, \"mybucket\");\n                        signatureRequest.respond(200, null, JSON.stringify({policy: \"thepolicy\", signature: \"thesignature\"}));\n                        assert.equal(uploadRequest.url, testS3Endpoint);\n\n                        uploadRequest.respond(200, {ETag: \"123\"}, null);\n\n                        uploadSuccessRequest = fileTestHelper.getRequests()[2];\n                        uploadSuccessRequestParsedBody = purl(\"http://test.com?\" + uploadSuccessRequest.requestBody).param();\n                        assert.equal(uploadSuccessRequestParsedBody.bucket, \"mybucket\");\n\n                        done();\n                    }, 10);\n                });\n            };\n\n        it(\"test basic upload w/ string for bucket\", function(done) {\n            var uploader = new qq.s3.FineUploaderBasic({\n                    request: typicalRequestOption,\n                    signature: typicalSignatureOption,\n                    objectProperties: {\n                        bucket: \"mybucket\"\n                    },\n                    uploadSuccess: {\n                        endpoint: \"upload/success\"\n                    }\n                }\n            );\n\n            startTypicalTest(uploader, done);\n        });\n\n        it(\"test basic upload w/ simple function for bucket\", function(done) {\n            var uploader = new qq.s3.FineUploaderBasic({\n                    request: typicalRequestOption,\n                    signature: typicalSignatureOption,\n                    objectProperties: {\n                        bucket: function(id) {\n                            assert.equal(id, 0, \"unexpected ID passed to bucket function\");\n                            return \"mybucket\";\n                        }\n                    },\n                    uploadSuccess: {\n                        endpoint: \"upload/success\"\n                    }\n                }\n            );\n\n            startTypicalTest(uploader, done);\n        });\n\n        it(\"test basic upload w/ promissory function for bucket\", function(done) {\n            var uploader = new qq.s3.FineUploaderBasic({\n                    request: typicalRequestOption,\n                    signature: typicalSignatureOption,\n                    objectProperties: {\n                        bucket: function(id) {\n                            assert.equal(id, 0, \"unexpected ID passed to bucket function\");\n                            var promise = new qq.Promise();\n                            promise.success(\"mybucket\");\n                            return promise;\n                        }\n                    },\n                    uploadSuccess: {\n                        endpoint: \"upload/success\"\n                    }\n                }\n            );\n\n            startTypicalTest(uploader, done);\n        });\n    });\n}\n"
  },
  {
    "path": "test/unit/s3/chunked-uploads.js",
    "content": "/* globals describe, beforeEach, $fixture, qq, assert, it, qqtest, helpme, purl */\n//TODO test to ensure uploads are reset if CompleteMultipart error response contains specific keywords.  Do this as part of #1188\nif (qqtest.canDownloadFileAsBlob) {\n    describe(\"s3 chunked upload tests\", function() {\n        \"use strict\";\n\n        var fileTestHelper = helpme.setupFileTests(),\n            testS3Endpoint = \"https://mytestbucket.s3.amazonaws.com\",\n            testBucketName = \"mytestbucket\",\n            testAccessKey = \"testAccessKey\",\n            expectedFileSize = 3266,\n            expectedChunks = 2,\n            chunkSize = Math.round(expectedFileSize / expectedChunks),\n            typicalChunkingOption = {\n                enabled: true,\n                partSize: chunkSize\n            };\n\n        describe(\"server-side signature-based chunked S3 upload tests\", function() {\n            var startTypicalTest = function(uploader, callback) {\n                qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function (blob) {\n                    var initiateSignatureRequest, uploadRequest, initiateToSign;\n\n                    fileTestHelper.mockXhr();\n                    uploader.addFiles({name: \"test.jpg\", blob: blob});\n\n                    assert.equal(fileTestHelper.getRequests().length, 1, \"Wrong # of requests\");\n\n                    initiateSignatureRequest = fileTestHelper.getRequests()[0];\n                    initiateToSign = JSON.parse(initiateSignatureRequest.requestBody);\n\n                    callback(initiateSignatureRequest, initiateToSign, uploadRequest);\n                });\n            },\n            testSignatureEndoint = \"/signature\",\n            typicalRequestOption = {\n                accessKey: testAccessKey,\n                endpoint: testS3Endpoint\n            },\n            typicalSignatureOption = {\n                endpoint: testSignatureEndoint\n            };\n\n            describe(\"v4 signatures\", function() {\n                it(\"handles a basic chunked upload\", function(done) {\n                    var uploadChunkCalled = false,\n                        uploadChunkSuccessCalled = false,\n                        verifyChunkData = function(onUploadChunkSuccess, chunkData) {\n                            if (onUploadChunkSuccess && uploadChunkSuccessCalled || !onUploadChunkSuccess && uploadChunkCalled) {\n                                assert.equal(chunkData.partIndex, 1);\n                                assert.equal(chunkData.startByte, chunkSize + 1);\n                                assert.equal(chunkData.endByte,  expectedFileSize);\n                                assert.equal(chunkData.totalParts, 2);\n                            }\n                            else {\n                                if (onUploadChunkSuccess) {\n                                    uploadChunkSuccessCalled = true;\n                                }\n                                else {\n                                    uploadChunkCalled = true;\n                                }\n\n                                assert.equal(chunkData.partIndex, 0);\n                                assert.equal(chunkData.startByte, 1);\n                                assert.equal(chunkData.endByte,  chunkSize);\n                                assert.equal(chunkData.totalParts, 2);\n                            }\n                        },\n                        uploader = new qq.s3.FineUploaderBasic({\n                            request: typicalRequestOption,\n                            signature: {\n                                endpoint: testSignatureEndoint,\n                                version: 4\n                            },\n                            chunking: typicalChunkingOption,\n                            callbacks: {\n                                onComplete: function(id, name, response, xhr) {\n                                    assert.equal(id, 0, \"Wrong ID passed to onComplete\");\n                                    assert.equal(name, uploader.getName(0), \"Wrong name passed to onComplete\");\n                                    assert.ok(response, \"Null response passed to onComplete\");\n                                    assert.ok(xhr, \"Null XHR passed to onComplete\");\n                                },\n                                onUploadChunk: function(id, name, chunkData) {\n                                    //should be called twice each (1 for each chunk)\n                                    assert.equal(id, 0, \"Wrong ID passed to onUploadChunk\");\n                                    assert.equal(name, uploader.getName(0), \"Wrong name passed to onUploadChunk\");\n\n                                    verifyChunkData(false, chunkData);\n                                },\n                                onUploadChunkSuccess: function(id, chunkData, response, xhr) {\n                                    //should be called twice each (1 for each chunk)\n                                    assert.equal(id, 0, \"Wrong ID passed to onUploadChunkSuccess\");\n                                    assert.ok(response, \"Null response passed to onUploadChunkSuccess\");\n                                    assert.ok(xhr, \"Null XHR passed to onUploadChunkSuccess\");\n\n                                    verifyChunkData(true, chunkData);\n                                }\n                            }\n                        }\n                    );\n\n                    startTypicalTest(uploader, function(initiateSignatureRequest, initiateToSign) {\n                        var uploadPartRequest,\n                            initiateRequest,\n                            uploadPartSignatureRequest1,\n                            uploadPartSignatureRequest2,\n                            uploadPartToSign1,\n                            uploadPartToSign2,\n                            uploadCompleteSignatureRequest,\n                            uploadCompleteToSign,\n                            multipartCompleteRequest;\n\n                        // signature request for initiate multipart upload\n                        assert.equal(initiateSignatureRequest.url, testSignatureEndoint + \"?v4=true\");\n                        assert.equal(initiateSignatureRequest.method, \"POST\");\n                        assert.equal(initiateSignatureRequest.requestHeaders[\"Content-Type\"].indexOf(\"application/json;\"), 0);\n                        assert.ok(initiateToSign.headers);\n\n                        assert.equal(initiateToSign.headers.indexOf(\"AWS4-HMAC-SHA256\"), 0);\n                        assert.ok(initiateToSign.headers.indexOf(\"/us-east-1/s3/aws4_request\") > 0);\n                        assert.equal(initiateToSign.headers.split(\"\\n\").length, 14);\n                        assert.ok(initiateToSign.headers.indexOf(\"host:mytestbucket.s3.amazonaws.com\"));\n                        initiateSignatureRequest.respond(200, null, JSON.stringify({signature: \"thesignature\"}));\n\n                        // initiate multipart upload request\n                        assert.equal(fileTestHelper.getRequests().length, 2);\n                        initiateRequest = fileTestHelper.getRequests()[1];\n                        assert.equal(initiateRequest.method, \"POST\");\n                        assert.equal(initiateRequest.url, testS3Endpoint + \"/\" + uploader.getKey(0) + \"?uploads\");\n                        assert.equal(initiateRequest.requestHeaders[\"x-amz-meta-qqfilename\"], uploader.getName(0));\n                        assert.equal(initiateRequest.requestHeaders[\"x-amz-acl\"], \"private\");\n                        assert.ok(initiateRequest.requestHeaders[\"x-amz-date\"]);\n                        assert.equal(initiateRequest.requestHeaders.Authorization.indexOf(\"AWS4-HMAC-SHA256 Credential=testAccessKey/\"), 0);\n                        var authParts = initiateRequest.requestHeaders.Authorization.split(\";\");\n                        assert.equal(authParts.length, 5);\n                        assert.equal(authParts[0].split(\",\")[1], \"SignedHeaders=host\");\n                        assert.equal(authParts[1], \"x-amz-acl\");\n                        assert.equal(authParts[2], \"x-amz-content-sha256\");\n                        assert.equal(authParts[3], \"x-amz-date\");\n                        assert.equal(authParts[4], \"x-amz-meta-qqfilename,Signature=thesignature\");\n                        initiateRequest.respond(200, null, \"<UploadId>123</UploadId>\");\n\n                        setTimeout(function() {\n                            // signature request for upload part 1\n                            assert.equal(fileTestHelper.getRequests().length, 4);\n                            uploadPartSignatureRequest1 = fileTestHelper.getRequests()[3];\n                            assert.equal(uploadPartSignatureRequest1.method, \"POST\");\n                            assert.equal(uploadPartSignatureRequest1.url, testSignatureEndoint + \"?v4=true\");\n                            assert.equal(uploadPartSignatureRequest1.requestHeaders[\"Content-Type\"].indexOf(\"application/json;\"), 0);\n                            uploadPartToSign1 = JSON.parse(uploadPartSignatureRequest1.requestBody);\n                            assert.ok(uploadPartToSign1.headers);\n                            assert.equal(uploadPartToSign1.headers.indexOf(\"AWS4-HMAC-SHA256\"), 0);\n                            assert.ok(uploadPartToSign1.headers.indexOf(\"/us-east-1/s3/aws4_request\") > 0);\n                            assert.equal(uploadPartToSign1.headers.split(\"\\n\").length, 12);\n                            assert.ok(uploadPartToSign1.headers.indexOf(\"host:mytestbucket.s3.amazonaws.com\"));\n                            uploadPartSignatureRequest1.respond(200, null, JSON.stringify({signature: \"thesignature\"}));\n\n                            // upload part 1 request\n                            uploadPartRequest = fileTestHelper.getRequests()[2];\n                            assert.equal(uploadPartRequest.method, \"PUT\");\n                            assert.equal(uploadPartRequest.url, testS3Endpoint + \"/\" + uploader.getKey(0) + \"?partNumber=1&uploadId=123\");\n                            assert.ok(uploadPartRequest.requestHeaders[\"x-amz-date\"]);\n\n                            assert.equal(uploadPartRequest.requestHeaders[\"Content-Type\"], \"\");\n\n                            var authParts = uploadPartRequest.requestHeaders.Authorization.split(\";\");\n                            assert.equal(authParts.length, 3);\n                            assert.equal(authParts[0].split(\",\")[1], \"SignedHeaders=host\");\n                            assert.equal(authParts[1], \"x-amz-content-sha256\");\n                            assert.equal(authParts[2], \"x-amz-date,Signature=thesignature\");\n                            uploadPartRequest.respond(200, {ETag: \"etag1\"}, null);\n\n                            setTimeout(function() {\n                                // signature request for upload part 2\n                                assert.equal(fileTestHelper.getRequests().length, 6);\n                                uploadPartSignatureRequest2 = fileTestHelper.getRequests()[5];\n                                assert.equal(uploadPartSignatureRequest2.method, \"POST\");\n                                assert.equal(uploadPartSignatureRequest2.url, testSignatureEndoint + \"?v4=true\");\n                                assert.equal(uploadPartSignatureRequest2.requestHeaders[\"Content-Type\"].indexOf(\"application/json;\"), 0);\n                                uploadPartToSign2 = JSON.parse(uploadPartSignatureRequest2.requestBody);\n                                assert.ok(uploadPartToSign2.headers);\n                                assert.equal(uploadPartToSign2.headers.indexOf(\"AWS4-HMAC-SHA256\"), 0);\n                                assert.ok(uploadPartToSign2.headers.indexOf(\"/us-east-1/s3/aws4_request\") > 0);\n                                assert.equal(uploadPartToSign2.headers.split(\"\\n\").length, 12);\n                                assert.ok(uploadPartToSign2.headers.indexOf(\"host:mytestbucket.s3.amazonaws.com\"));\n                                uploadPartSignatureRequest2.respond(200, null, JSON.stringify({signature: \"thesignature\"}));\n\n                                // upload part 2 request\n                                uploadPartRequest = fileTestHelper.getRequests()[4];\n                                assert.equal(uploadPartRequest.method, \"PUT\");\n                                assert.equal(uploadPartRequest.url, testS3Endpoint + \"/\" + uploader.getKey(0) + \"?partNumber=2&uploadId=123\");\n                                assert.ok(uploadPartRequest.requestHeaders[\"x-amz-date\"]);\n\n                                assert.equal(uploadPartRequest.requestHeaders[\"Content-Type\"], \"\");\n\n                                var authParts = uploadPartRequest.requestHeaders.Authorization.split(\";\");\n                                assert.equal(authParts.length, 3);\n                                assert.equal(authParts[0].split(\",\")[1], \"SignedHeaders=host\");\n                                assert.equal(authParts[1], \"x-amz-content-sha256\");\n                                assert.equal(authParts[2], \"x-amz-date,Signature=thesignature\");\n                                uploadPartRequest.respond(200, {ETag: \"etag2\"}, null);\n\n                                // signature request for multipart complete\n                                assert.equal(fileTestHelper.getRequests().length, 7);\n                                uploadCompleteSignatureRequest = fileTestHelper.getRequests()[6];\n                                assert.equal(uploadCompleteSignatureRequest.method, \"POST\");\n                                assert.equal(uploadCompleteSignatureRequest.url, testSignatureEndoint + \"?v4=true\");\n                                assert.equal(uploadCompleteSignatureRequest.requestHeaders[\"Content-Type\"].indexOf(\"application/json;\"), 0);\n                                uploadCompleteToSign = JSON.parse(uploadCompleteSignatureRequest.requestBody);\n                                assert.ok(uploadCompleteToSign.headers);\n                                assert.equal(uploadCompleteToSign.headers.indexOf(\"AWS4-HMAC-SHA256\"), 0);\n                                assert.ok(uploadCompleteToSign.headers.indexOf(\"/us-east-1/s3/aws4_request\") > 0);\n                                assert.equal(uploadCompleteToSign.headers.split(\"\\n\").length, 12);\n                                assert.ok(uploadCompleteToSign.headers.indexOf(\"host:mytestbucket.s3.amazonaws.com\"));\n                                uploadCompleteSignatureRequest.respond(200, null, JSON.stringify({signature: \"thesignature\"}));\n\n                                // multipart complete request\n                                assert.equal(fileTestHelper.getRequests().length, 8);\n                                multipartCompleteRequest = fileTestHelper.getRequests()[7];\n                                assert.equal(multipartCompleteRequest.method, \"POST\");\n                                assert.equal(multipartCompleteRequest.url, testS3Endpoint + \"/\" + uploader.getKey(0) + \"?uploadId=123\");\n                                assert.ok(multipartCompleteRequest.requestHeaders[\"x-amz-date\"]);\n\n                                authParts = multipartCompleteRequest.requestHeaders.Authorization.split(\";\");\n                                assert.equal(authParts.length, 3);\n                                assert.equal(authParts[0].split(\",\")[1], \"SignedHeaders=host\");\n                                assert.equal(authParts[1], \"x-amz-content-sha256\");\n                                assert.equal(authParts[2], \"x-amz-date,Signature=thesignature\");\n                                multipartCompleteRequest.respond(200, null, \"<CompleteMultipartUploadResult><Bucket>\" + testBucketName + \"</Bucket><Key>\" + uploader.getKey(0) + \"</Key></CompleteMultipartUploadResult>\");\n\n                                assert.equal(uploader.getUploads()[0].status, qq.status.UPLOAD_SUCCESSFUL);\n\n                                done();\n                            }, 100);\n\n                        }, 100);\n                    });\n                });\n            });\n\n            it(\"handles a basic chunked upload\", function(done) {\n                assert.expect(87, done);\n\n                var uploadChunkCalled = false,\n                    uploadChunkSuccessCalled = false,\n                    verifyChunkData = function(onUploadChunkSuccess, chunkData) {\n                        if (onUploadChunkSuccess && uploadChunkSuccessCalled || !onUploadChunkSuccess && uploadChunkCalled) {\n                            assert.equal(chunkData.partIndex, 1);\n                            assert.equal(chunkData.startByte, chunkSize + 1);\n                            assert.equal(chunkData.endByte,  expectedFileSize);\n                            assert.equal(chunkData.totalParts, 2);\n                        }\n                        else {\n                            if (onUploadChunkSuccess) {\n                                uploadChunkSuccessCalled = true;\n                            }\n                            else {\n                                uploadChunkCalled = true;\n                            }\n\n                            assert.equal(chunkData.partIndex, 0);\n                            assert.equal(chunkData.startByte, 1);\n                            assert.equal(chunkData.endByte,  chunkSize);\n                            assert.equal(chunkData.totalParts, 2);\n                        }\n                    },\n                    uploader = new qq.s3.FineUploaderBasic({\n                        request: typicalRequestOption,\n                        signature: typicalSignatureOption,\n                        chunking: typicalChunkingOption,\n                        callbacks: {\n                            onComplete: function(id, name, response, xhr) {\n                                assert.equal(id, 0, \"Wrong ID passed to onComplete\");\n                                assert.equal(name, uploader.getName(0), \"Wrong name passed to onComplete\");\n                                assert.ok(response, \"Null response passed to onComplete\");\n                                assert.ok(xhr, \"Null XHR passed to onComplete\");\n                            },\n                            onUploadChunk: function(id, name, chunkData) {\n                                //should be called twice each (1 for each chunk)\n                                assert.equal(id, 0, \"Wrong ID passed to onUploadChunk\");\n                                assert.equal(name, uploader.getName(0), \"Wrong name passed to onUploadChunk\");\n\n                                verifyChunkData(false, chunkData);\n                            },\n                            onUploadChunkSuccess: function(id, chunkData, response, xhr) {\n                                //should be called twice each (1 for each chunk)\n                                assert.equal(id, 0, \"Wrong ID passed to onUploadChunkSuccess\");\n                                assert.ok(response, \"Null response passed to onUploadChunkSuccess\");\n                                assert.ok(xhr, \"Null XHR passed to onUploadChunkSuccess\");\n\n                                verifyChunkData(true, chunkData);\n                            }\n                        }\n                    }\n                );\n\n                startTypicalTest(uploader, function(initiateSignatureRequest, initiateToSign) {\n                    var uploadPartRequest,\n                        initiateRequest,\n                        uploadPartSignatureRequest1,\n                        uploadPartSignatureRequest2,\n                        uploadPartToSign1,\n                        uploadPartToSign2,\n                        uploadCompleteSignatureRequest,\n                        uploadCompleteToSign,\n                        multipartCompleteRequest;\n\n                    // signature request for initiate multipart upload\n                    assert.equal(initiateSignatureRequest.url, testSignatureEndoint);\n                    assert.equal(initiateSignatureRequest.method, \"POST\");\n                    assert.equal(initiateSignatureRequest.requestHeaders[\"Content-Type\"].indexOf(\"application/json;\"), 0);\n                    assert.ok(initiateToSign.headers);\n                    assert.equal(initiateToSign.headers.indexOf(\"POST\"), 0);\n                    assert.ok(initiateToSign.headers.indexOf(\"image/jpeg\") > 0);\n                    assert.ok(initiateToSign.headers.indexOf(\"x-amz-acl:private\") > 0);\n                    assert.ok(initiateToSign.headers.indexOf(\"x-amz-date:\") > 0);\n                    assert.ok(initiateToSign.headers.indexOf(\"x-amz-meta-qqfilename:\" + uploader.getName(0)) > 0);\n                    assert.ok(initiateToSign.headers.indexOf(\"/\" + testBucketName + \"/\" + uploader.getKey(0) + \"?uploads\") > 0);\n                    initiateSignatureRequest.respond(200, null, JSON.stringify({signature: \"thesignature\"}));\n\n                    // initiate multipart upload request\n                    assert.equal(fileTestHelper.getRequests().length, 2);\n                    initiateRequest = fileTestHelper.getRequests()[1];\n                    assert.equal(initiateRequest.method, \"POST\");\n                    assert.equal(initiateRequest.url, testS3Endpoint + \"/\" + uploader.getKey(0) + \"?uploads\");\n                    assert.equal(initiateRequest.requestHeaders[\"x-amz-meta-qqfilename\"], uploader.getName(0));\n                    assert.equal(initiateRequest.requestHeaders[\"x-amz-acl\"], \"private\");\n                    assert.ok(initiateRequest.requestHeaders[\"x-amz-date\"]);\n                    assert.equal(initiateRequest.requestHeaders.Authorization, \"AWS \" + testAccessKey + \":thesignature\");\n                    initiateRequest.respond(200, null, \"<UploadId>123</UploadId>\");\n\n                    // signature request for upload part 1\n                    assert.equal(fileTestHelper.getRequests().length, 4);\n                    uploadPartSignatureRequest1 = fileTestHelper.getRequests()[3];\n                    assert.equal(uploadPartSignatureRequest1.method, \"POST\");\n                    assert.equal(uploadPartSignatureRequest1.url, testSignatureEndoint);\n                    assert.equal(uploadPartSignatureRequest1.requestHeaders[\"Content-Type\"].indexOf(\"application/json;\"), 0);\n                    uploadPartToSign1 = JSON.parse(uploadPartSignatureRequest1.requestBody);\n                    assert.ok(uploadPartToSign1.headers);\n                    assert.equal(uploadPartToSign1.headers.indexOf(\"PUT\"), 0);\n                    assert.ok(uploadPartToSign1.headers.indexOf(\"x-amz-date:\") > 0);\n                    assert.ok(uploadPartToSign1.headers.indexOf(\"/\" + testBucketName + \"/\" + uploader.getKey(0) + \"?partNumber=1&uploadId=123\") > 0);\n                    uploadPartSignatureRequest1.respond(200, null, JSON.stringify({signature: \"thesignature\"}));\n\n                    // upload part 1 request\n                    uploadPartRequest = fileTestHelper.getRequests()[2];\n                    assert.equal(uploadPartRequest.method, \"PUT\");\n                    assert.equal(uploadPartRequest.url, testS3Endpoint + \"/\" + uploader.getKey(0) + \"?partNumber=1&uploadId=123\");\n                    assert.ok(uploadPartRequest.requestHeaders[\"x-amz-date\"]);\n\n                    assert.equal(uploadPartRequest.requestHeaders[\"Content-Type\"], \"\");\n\n                    assert.equal(uploadPartRequest.requestHeaders.Authorization, \"AWS \" + testAccessKey + \":thesignature\");\n                    uploadPartRequest.respond(200, {ETag: \"etag1\"}, null);\n\n                    // signature request for upload part 2\n                    assert.equal(fileTestHelper.getRequests().length, 6);\n                    uploadPartSignatureRequest2 = fileTestHelper.getRequests()[5];\n                    assert.equal(uploadPartSignatureRequest2.method, \"POST\");\n                    assert.equal(uploadPartSignatureRequest2.url, testSignatureEndoint);\n                    assert.equal(uploadPartSignatureRequest2.requestHeaders[\"Content-Type\"].indexOf(\"application/json;\"), 0);\n                    uploadPartToSign2 = JSON.parse(uploadPartSignatureRequest2.requestBody);\n                    assert.ok(uploadPartToSign2.headers);\n                    assert.equal(uploadPartToSign2.headers.indexOf(\"PUT\"), 0);\n                    assert.ok(uploadPartToSign2.headers.indexOf(\"x-amz-date:\") > 0);\n                    assert.ok(uploadPartToSign2.headers.indexOf(\"/\" + testBucketName + \"/\" + uploader.getKey(0) + \"?partNumber=2&uploadId=123\") > 0);\n                    uploadPartSignatureRequest2.respond(200, null, JSON.stringify({signature: \"thesignature\"}));\n\n                    // upload part 2 request\n                    uploadPartRequest = fileTestHelper.getRequests()[4];\n                    assert.equal(uploadPartRequest.method, \"PUT\");\n                    assert.equal(uploadPartRequest.url, testS3Endpoint + \"/\" + uploader.getKey(0) + \"?partNumber=2&uploadId=123\");\n                    assert.ok(uploadPartRequest.requestHeaders[\"x-amz-date\"]);\n\n                    assert.equal(uploadPartRequest.requestHeaders[\"Content-Type\"], \"\");\n\n                    assert.equal(uploadPartRequest.requestHeaders.Authorization, \"AWS \" + testAccessKey + \":thesignature\");\n                    uploadPartRequest.respond(200, {ETag: \"etag2\"}, null);\n\n                    // signature request for multipart complete\n                    assert.equal(fileTestHelper.getRequests().length, 7);\n                    uploadCompleteSignatureRequest = fileTestHelper.getRequests()[6];\n                    assert.equal(uploadCompleteSignatureRequest.method, \"POST\");\n                    assert.equal(uploadCompleteSignatureRequest.url, testSignatureEndoint);\n                    assert.equal(uploadCompleteSignatureRequest.requestHeaders[\"Content-Type\"].indexOf(\"application/json;\"), 0);\n                    uploadCompleteToSign = JSON.parse(uploadCompleteSignatureRequest.requestBody);\n                    assert.ok(uploadCompleteToSign.headers);\n                    assert.equal(uploadCompleteToSign.headers.indexOf(\"POST\"), 0);\n                    assert.ok(uploadCompleteToSign.headers.indexOf(\"x-amz-date:\") > 0);\n                    assert.ok(uploadCompleteToSign.headers.indexOf(\"/\" + testBucketName + \"/\" + uploader.getKey(0) + \"?uploadId=123\") > 0);\n                    uploadCompleteSignatureRequest.respond(200, null, JSON.stringify({signature: \"thesignature\"}));\n\n                    // multipart complete request\n                    assert.equal(fileTestHelper.getRequests().length, 8);\n                    multipartCompleteRequest = fileTestHelper.getRequests()[7];\n                    assert.equal(multipartCompleteRequest.method, \"POST\");\n                    assert.equal(multipartCompleteRequest.url, testS3Endpoint + \"/\" + uploader.getKey(0) + \"?uploadId=123\");\n                    assert.ok(multipartCompleteRequest.requestHeaders[\"x-amz-date\"]);\n                    assert.equal(multipartCompleteRequest.requestHeaders.Authorization, \"AWS \" + testAccessKey + \":thesignature\");\n                    assert.equal(multipartCompleteRequest.requestBody, \"<CompleteMultipartUpload><Part><PartNumber>1</PartNumber><ETag>etag1</ETag></Part><Part><PartNumber>2</PartNumber><ETag>etag2</ETag></Part></CompleteMultipartUpload>\");\n                    multipartCompleteRequest.respond(200, null, \"<CompleteMultipartUploadResult><Bucket>\" + testBucketName + \"</Bucket><Key>\" + uploader.getKey(0) + \"</Key></CompleteMultipartUploadResult>\");\n\n                    assert.equal(uploader.getUploads()[0].status, qq.status.UPLOAD_SUCCESSFUL);\n                });\n            });\n\n            it(\"ensures set reducedRedundancy and serverSideEncryption options result in proper headers/params\", function(done) {\n                assert.expect(18, done);\n\n                var uploader = new qq.s3.FineUploaderBasic({\n                        request: typicalRequestOption,\n                        signature: typicalSignatureOption,\n                        chunking: typicalChunkingOption,\n                        objectProperties: {\n                            serverSideEncryption: true,\n                            reducedRedundancy: true\n                        }\n                    }\n                );\n\n                startTypicalTest(uploader, function(initiateSignatureRequest, initiateToSign) {\n                    var uploadPartRequest,\n                        initiateRequest,\n                        uploadPartSignatureRequest1,\n                        uploadPartSignatureRequest2,\n                        uploadPartToSign1,\n                        uploadPartToSign2,\n                        uploadCompleteSignatureRequest,\n                        uploadCompleteToSign,\n                        multipartCompleteRequest;\n\n                    // signature request for initiate multipart upload\n                    assert.ok(initiateToSign.headers.indexOf(qq.s3.util.SERVER_SIDE_ENCRYPTION_PARAM_NAME + \":\" + qq.s3.util.SERVER_SIDE_ENCRYPTION_PARAM_VALUE) > 0);\n                    assert.ok(initiateToSign.headers.indexOf(qq.s3.util.REDUCED_REDUNDANCY_PARAM_NAME + \":\" + qq.s3.util.REDUCED_REDUNDANCY_PARAM_VALUE) > 0);\n                    initiateSignatureRequest.respond(200, null, JSON.stringify({signature: \"thesignature\"}));\n\n                    // initiate multipart upload request\n                    initiateRequest = fileTestHelper.getRequests()[1];\n                    assert.equal(initiateRequest.requestHeaders[qq.s3.util.SERVER_SIDE_ENCRYPTION_PARAM_NAME], qq.s3.util.SERVER_SIDE_ENCRYPTION_PARAM_VALUE);\n                    assert.equal(initiateRequest.requestHeaders[qq.s3.util.REDUCED_REDUNDANCY_PARAM_NAME], qq.s3.util.REDUCED_REDUNDANCY_PARAM_VALUE);\n                    initiateRequest.respond(200, null, \"<UploadId>123</UploadId>\");\n\n                    // signature request for upload part 1\n                    uploadPartSignatureRequest1 = fileTestHelper.getRequests()[3];\n                    uploadPartToSign1 = JSON.parse(uploadPartSignatureRequest1.requestBody);\n                    assert.ok(uploadPartToSign1.headers.indexOf(qq.s3.util.SERVER_SIDE_ENCRYPTION_PARAM_NAME + \":\" + qq.s3.util.SERVER_SIDE_ENCRYPTION_PARAM_VALUE) < 0);\n                    assert.ok(uploadPartToSign1.headers.indexOf(qq.s3.util.REDUCED_REDUNDANCY_PARAM_NAME + \":\" + qq.s3.util.REDUCED_REDUNDANCY_PARAM_VALUE) < 0);\n                    uploadPartSignatureRequest1.respond(200, null, JSON.stringify({signature: \"thesignature\"}));\n\n                    // upload part 1 request\n                    uploadPartRequest = fileTestHelper.getRequests()[2];\n                    assert.ok(!uploadPartRequest.requestHeaders[qq.s3.util.SERVER_SIDE_ENCRYPTION_PARAM_NAME]);\n                    assert.ok(!uploadPartRequest.requestHeaders[qq.s3.util.REDUCED_REDUNDANCY_PARAM_NAME]);\n                    uploadPartRequest.respond(200, {ETag: \"etag1\"}, null);\n\n                    // signature request for upload part 2\n                    uploadPartSignatureRequest2 = fileTestHelper.getRequests()[5];\n                    uploadPartToSign2 = JSON.parse(uploadPartSignatureRequest1.requestBody);\n                    assert.ok(uploadPartToSign2.headers.indexOf(qq.s3.util.SERVER_SIDE_ENCRYPTION_PARAM_NAME + \":\" + qq.s3.util.SERVER_SIDE_ENCRYPTION_PARAM_VALUE) < 0);\n                    assert.ok(uploadPartToSign2.headers.indexOf(qq.s3.util.REDUCED_REDUNDANCY_PARAM_NAME + \":\" + qq.s3.util.REDUCED_REDUNDANCY_PARAM_VALUE) < 0);\n                    uploadPartSignatureRequest2.respond(200, null, JSON.stringify({signature: \"thesignature\"}));\n\n                    // upload part 2 request\n                    uploadPartRequest = fileTestHelper.getRequests()[4];\n                    assert.ok(!uploadPartRequest.requestHeaders[qq.s3.util.SERVER_SIDE_ENCRYPTION_PARAM_NAME]);\n                    assert.ok(!uploadPartRequest.requestHeaders[qq.s3.util.REDUCED_REDUNDANCY_PARAM_NAME]);\n                    uploadPartRequest.respond(200, {ETag: \"etag1\"}, null);\n\n                    // signature request for multipart complete\n                    uploadCompleteSignatureRequest = fileTestHelper.getRequests()[6];\n                    uploadCompleteToSign = JSON.parse(uploadCompleteSignatureRequest.requestBody);\n                    assert.ok(uploadCompleteToSign.headers.indexOf(qq.s3.util.SERVER_SIDE_ENCRYPTION_PARAM_NAME + \":\" + qq.s3.util.SERVER_SIDE_ENCRYPTION_PARAM_VALUE) < 0);\n                    assert.ok(uploadCompleteToSign.headers.indexOf(qq.s3.util.REDUCED_REDUNDANCY_PARAM_NAME + \":\" + qq.s3.util.REDUCED_REDUNDANCY_PARAM_VALUE) < 0);\n                    uploadCompleteSignatureRequest.respond(200, null, JSON.stringify({signature: \"thesignature\"}));\n\n                    // multipart complete request\n                    multipartCompleteRequest = fileTestHelper.getRequests()[7];\n                    assert.ok(!multipartCompleteRequest.requestHeaders[qq.s3.util.SERVER_SIDE_ENCRYPTION_PARAM_NAME]);\n                    assert.ok(!multipartCompleteRequest.requestHeaders[qq.s3.util.REDUCED_REDUNDANCY_PARAM_NAME]);\n                    multipartCompleteRequest.respond(200, null, \"<CompleteMultipartUploadResult><Bucket>\" + testBucketName + \"</Bucket><Key>\" + uploader.getKey(0) + \"</Key></CompleteMultipartUploadResult>\");\n\n                    assert.equal(uploader.getUploads()[0].status, qq.status.UPLOAD_SUCCESSFUL);\n                });\n            });\n\n            it(\"handles failures at every step of a chunked upload\", function(done) {\n                assert.expect(99, done);\n\n                var uploader = new qq.s3.FineUploaderBasic({\n                        request: typicalRequestOption,\n                        signature: typicalSignatureOption,\n                        chunking: typicalChunkingOption,\n                        callbacks: {\n                            onComplete: function(id, name, response) {\n                                onCompleteCallbacks++;\n\n                                if (onCompleteCallbacks < 9) {\n                                    assert.ok(!response.success);\n                                }\n                                else {\n                                    assert.ok(response.success);\n                                }\n                            },\n                            onManualRetry: function(id, name) {\n                                //expected to be called once for each retry\n                                assert.equal(id, 0);\n                                assert.equal(name, uploader.getName(0));\n                            }\n                        }\n                    }\n                ),\n                    onCompleteCallbacks = 0;\n\n                startTypicalTest(uploader, function(initiateSignatureRequest, initiateToSign) {\n                    var uploadPartRequest,\n                        initiateRequest,\n                        uploadPartSignatureRequest1,\n                        uploadPartSignatureRequest2,\n                        uploadPartToSign1,\n                        uploadPartToSign2,\n                        uploadCompleteSignatureRequest,\n                        uploadCompleteToSign,\n                        multipartCompleteRequest;\n\n                    assert.equal(uploader.getUploads()[0].status, qq.status.UPLOADING);\n\n                    // failing signature request for initiate multipart upload\n                    assert.equal(initiateSignatureRequest.url, testSignatureEndoint);\n                    initiateSignatureRequest.respond(200, null, JSON.stringify({invalid: true}));\n                    assert.equal(fileTestHelper.getRequests().length, 1);\n\n                    assert.equal(uploader.getUploads()[0].status, qq.status.UPLOAD_FAILED);\n                    uploader.retry(0);\n                    assert.equal(fileTestHelper.getRequests().length, 2);\n                    assert.equal(uploader.getUploads()[0].status, qq.status.UPLOADING);\n\n                    // successful initiate signature request\n                    initiateSignatureRequest = fileTestHelper.getRequests()[1];\n                    assert.equal(initiateSignatureRequest.url, testSignatureEndoint);\n                    assert.ok(initiateToSign.headers.indexOf(\"/\" + testBucketName + \"/\" + uploader.getKey(0) + \"?uploads\") > 0);\n                    initiateSignatureRequest.respond(200, null, JSON.stringify({signature: \"thesignature\"}));\n\n                    // failing initiate multipart upload request\n                    assert.equal(fileTestHelper.getRequests().length, 3);\n                    initiateRequest = fileTestHelper.getRequests()[2];\n                    assert.equal(initiateRequest.url, testS3Endpoint + \"/\" + uploader.getKey(0) + \"?uploads\");\n                    initiateRequest.respond(200, null, \"\");\n\n                    assert.equal(uploader.getUploads()[0].status, qq.status.UPLOAD_FAILED);\n                    assert.equal(fileTestHelper.getRequests().length, 3);\n                    uploader.retry(0);\n                    assert.equal(uploader.getUploads()[0].status, qq.status.UPLOADING);\n\n                    // successful initiate signature request\n                    assert.equal(fileTestHelper.getRequests().length, 4);\n                    initiateSignatureRequest = fileTestHelper.getRequests()[3];\n                    assert.equal(initiateSignatureRequest.url, testSignatureEndoint);\n                    assert.ok(initiateToSign.headers.indexOf(\"/\" + testBucketName + \"/\" + uploader.getKey(0) + \"?uploads\") > 0);\n                    initiateSignatureRequest.respond(200, null, JSON.stringify({signature: \"thesignature\"}));\n\n                    // successful initiate multipart upload request\n                    assert.equal(fileTestHelper.getRequests().length, 5);\n                    initiateRequest = fileTestHelper.getRequests()[4];\n                    assert.equal(initiateRequest.url, testS3Endpoint + \"/\" + uploader.getKey(0) + \"?uploads\");\n                    initiateRequest.respond(200, null, \"<UploadId>123</UploadId>\");\n\n                    // failed signature request for upload part 1\n                    assert.equal(fileTestHelper.getRequests().length, 7);\n                    uploadPartSignatureRequest1 = fileTestHelper.getRequests()[6];\n                    assert.equal(uploadPartSignatureRequest1.url, testSignatureEndoint);\n                    uploadPartToSign1 = JSON.parse(uploadPartSignatureRequest1.requestBody);\n                    assert.ok(uploadPartToSign1.headers.indexOf(\"/\" + testBucketName + \"/\" + uploader.getKey(0) + \"?partNumber=1&uploadId=123\") > 0);\n                    uploadPartSignatureRequest1.respond(200, null, JSON.stringify({invalid: true}));\n\n                    assert.equal(uploader.getUploads()[0].status, qq.status.UPLOAD_FAILED);\n                    assert.equal(fileTestHelper.getRequests().length, 7);\n                    uploader.retry(0);\n                    assert.equal(uploader.getUploads()[0].status, qq.status.UPLOADING);\n\n                    // successful signature request for upload part 1\n                    assert.equal(fileTestHelper.getRequests().length, 9);\n                    uploadPartSignatureRequest1 = fileTestHelper.getRequests()[8];\n                    assert.equal(uploadPartSignatureRequest1.url, testSignatureEndoint);\n                    uploadPartToSign1 = JSON.parse(uploadPartSignatureRequest1.requestBody);\n                    assert.ok(uploadPartToSign1.headers.indexOf(\"/\" + testBucketName + \"/\" + uploader.getKey(0) + \"?partNumber=1&uploadId=123\") > 0);\n                    uploadPartSignatureRequest1.respond(200, null, JSON.stringify({signature: \"thesignature\"}));\n\n                    // failing upload part 1 request\n                    assert.equal(fileTestHelper.getRequests().length, 9);\n                    uploadPartRequest = fileTestHelper.getRequests()[7];\n                    assert.equal(uploadPartRequest.url, testS3Endpoint + \"/\" + uploader.getKey(0) + \"?partNumber=1&uploadId=123\");\n                    uploadPartRequest.respond(404, {ETag: \"etag1\"}, null);\n\n                    assert.equal(uploader.getUploads()[0].status, qq.status.UPLOAD_FAILED);\n                    assert.equal(fileTestHelper.getRequests().length, 9);\n                    uploader.retry(0);\n                    assert.equal(uploader.getUploads()[0].status, qq.status.UPLOADING);\n\n                    // successful signature request for upload part 1\n                    assert.equal(fileTestHelper.getRequests().length, 11);\n                    uploadPartSignatureRequest1 = fileTestHelper.getRequests()[10];\n                    assert.equal(uploadPartSignatureRequest1.url, testSignatureEndoint);\n                    uploadPartToSign1 = JSON.parse(uploadPartSignatureRequest1.requestBody);\n                    assert.ok(uploadPartToSign1.headers.indexOf(\"/\" + testBucketName + \"/\" + uploader.getKey(0) + \"?partNumber=1&uploadId=123\") > 0);\n                    uploadPartSignatureRequest1.respond(200, null, JSON.stringify({signature: \"thesignature\"}));\n\n                    // successful upload part 1 request\n                    assert.equal(fileTestHelper.getRequests().length, 11);\n                    uploadPartRequest = fileTestHelper.getRequests()[9];\n                    assert.equal(uploadPartRequest.url, testS3Endpoint + \"/\" + uploader.getKey(0) + \"?partNumber=1&uploadId=123\");\n                    uploadPartRequest.respond(200, {ETag: \"etag1_a\"}, null);\n\n                    // failing signature request for upload part 2\n                    assert.equal(fileTestHelper.getRequests().length, 13);\n                    uploadPartSignatureRequest2 = fileTestHelper.getRequests()[12];\n                    assert.equal(uploadPartSignatureRequest2.url, testSignatureEndoint);\n                    uploadPartToSign2 = JSON.parse(uploadPartSignatureRequest2.requestBody);\n                    assert.ok(uploadPartToSign2.headers.indexOf(\"/\" + testBucketName + \"/\" + uploader.getKey(0) + \"?partNumber=2&uploadId=123\") > 0);\n                    uploadPartSignatureRequest2.respond(404, null, JSON.stringify({signature: \"thesignature\"}));\n\n                    assert.equal(uploader.getUploads()[0].status, qq.status.UPLOAD_FAILED);\n                    assert.equal(fileTestHelper.getRequests().length, 13);\n                    uploader.retry(0);\n                    assert.equal(uploader.getUploads()[0].status, qq.status.UPLOADING);\n\n                    // successful signature request for upload part 2\n                    assert.equal(fileTestHelper.getRequests().length, 15);\n                    uploadPartSignatureRequest2 = fileTestHelper.getRequests()[14];\n                    assert.equal(uploadPartSignatureRequest2.url, testSignatureEndoint);\n                    uploadPartToSign2 = JSON.parse(uploadPartSignatureRequest2.requestBody);\n                    assert.ok(uploadPartToSign2.headers.indexOf(\"/\" + testBucketName + \"/\" + uploader.getKey(0) + \"?partNumber=2&uploadId=123\") > 0);\n                    uploadPartSignatureRequest2.respond(200, null, JSON.stringify({signature: \"thesignature\"}));\n\n                    // failing upload part 2 request\n                    uploadPartRequest = fileTestHelper.getRequests()[13];\n                    assert.equal(fileTestHelper.getRequests().length, 15);\n                    assert.equal(uploadPartRequest.url, testS3Endpoint + \"/\" + uploader.getKey(0) + \"?partNumber=2&uploadId=123\");\n                    uploadPartRequest.respond(404, {ETag: \"etag2\"}, null);\n\n                    assert.equal(uploader.getUploads()[0].status, qq.status.UPLOAD_FAILED);\n                    assert.equal(fileTestHelper.getRequests().length, 15);\n                    uploader.retry(0);\n                    assert.equal(uploader.getUploads()[0].status, qq.status.UPLOADING);\n\n                    // successful signature request for upload part 2\n                    assert.equal(fileTestHelper.getRequests().length, 17);\n                    uploadPartSignatureRequest2 = fileTestHelper.getRequests()[16];\n                    assert.equal(uploadPartSignatureRequest2.url, testSignatureEndoint);\n                    uploadPartToSign2 = JSON.parse(uploadPartSignatureRequest2.requestBody);\n                    assert.ok(uploadPartToSign2.headers.indexOf(\"/\" + testBucketName + \"/\" + uploader.getKey(0) + \"?partNumber=2&uploadId=123\") > 0);\n                    uploadPartSignatureRequest2.respond(200, null, JSON.stringify({signature: \"thesignature\"}));\n\n                    // successful upload part 2 request\n                    assert.equal(fileTestHelper.getRequests().length, 17);\n                    uploadPartRequest = fileTestHelper.getRequests()[15];\n                    assert.equal(uploadPartRequest.url, testS3Endpoint + \"/\" + uploader.getKey(0) + \"?partNumber=2&uploadId=123\");\n                    uploadPartRequest.respond(200, {ETag: \"etag2_a\"}, null);\n\n                    // failing signature request for multipart complete\n                    assert.equal(fileTestHelper.getRequests().length, 18);\n                    uploadCompleteSignatureRequest = fileTestHelper.getRequests()[17];\n                    assert.equal(uploadCompleteSignatureRequest.url, testSignatureEndoint);\n                    uploadCompleteToSign = JSON.parse(uploadCompleteSignatureRequest.requestBody);\n                    assert.ok(uploadCompleteToSign.headers.indexOf(\"/\" + testBucketName + \"/\" + uploader.getKey(0) + \"?uploadId=123\") > 0);\n                    uploadCompleteSignatureRequest.respond(400, null, JSON.stringify({signature: \"thesignature\"}));\n\n                    assert.equal(uploader.getUploads()[0].status, qq.status.UPLOAD_FAILED);\n                    assert.equal(fileTestHelper.getRequests().length, 18);\n                    uploader.retry(0);\n                    assert.equal(uploader.getUploads()[0].status, qq.status.UPLOADING);\n\n                    // successful signature request for multipart complete\n                    assert.equal(fileTestHelper.getRequests().length, 19);\n                    uploadCompleteSignatureRequest = fileTestHelper.getRequests()[18];\n                    assert.equal(uploadCompleteSignatureRequest.url, testSignatureEndoint);\n                    uploadCompleteToSign = JSON.parse(uploadCompleteSignatureRequest.requestBody);\n                    assert.ok(uploadCompleteToSign.headers.indexOf(\"/\" + testBucketName + \"/\" + uploader.getKey(0) + \"?uploadId=123\") > 0);\n                    uploadCompleteSignatureRequest.respond(200, null, JSON.stringify({signature: \"thesignature\"}));\n\n                    // failing multipart complete request\n                    assert.equal(fileTestHelper.getRequests().length, 20);\n                    multipartCompleteRequest = fileTestHelper.getRequests()[19];\n                    assert.equal(multipartCompleteRequest.url, testS3Endpoint + \"/\" + uploader.getKey(0) + \"?uploadId=123\");\n                    assert.equal(multipartCompleteRequest.requestBody, \"<CompleteMultipartUpload><Part><PartNumber>1</PartNumber><ETag>etag1_a</ETag></Part><Part><PartNumber>2</PartNumber><ETag>etag2_a</ETag></Part></CompleteMultipartUpload>\");\n                    multipartCompleteRequest.respond(200, null, \"<CompleteMultipartUploadResult><Key>\" + uploader.getKey(0) + \"</Key></CompleteMultipartUploadResult>\");\n\n                    assert.equal(uploader.getUploads()[0].status, qq.status.UPLOAD_FAILED);\n                    assert.equal(fileTestHelper.getRequests().length, 20);\n                    uploader.retry(0);\n                    assert.equal(uploader.getUploads()[0].status, qq.status.UPLOADING);\n\n                    // successful signature request for multipart complete\n                    assert.equal(fileTestHelper.getRequests().length, 21);\n                    uploadCompleteSignatureRequest = fileTestHelper.getRequests()[20];\n                    assert.equal(uploadCompleteSignatureRequest.url, testSignatureEndoint);\n                    uploadCompleteToSign = JSON.parse(uploadCompleteSignatureRequest.requestBody);\n                    assert.ok(uploadCompleteToSign.headers.indexOf(\"/\" + testBucketName + \"/\" + uploader.getKey(0) + \"?uploadId=123\") > 0);\n                    uploadCompleteSignatureRequest.respond(200, null, JSON.stringify({signature: \"thesignature\"}));\n\n                    // successful multipart complete request\n                    assert.equal(fileTestHelper.getRequests().length, 22);\n                    multipartCompleteRequest = fileTestHelper.getRequests()[21];\n                    assert.equal(multipartCompleteRequest.url, testS3Endpoint + \"/\" + uploader.getKey(0) + \"?uploadId=123\");\n                    assert.equal(multipartCompleteRequest.requestBody, \"<CompleteMultipartUpload><Part><PartNumber>1</PartNumber><ETag>etag1_a</ETag></Part><Part><PartNumber>2</PartNumber><ETag>etag2_a</ETag></Part></CompleteMultipartUpload>\");\n                    multipartCompleteRequest.respond(200, null, \"<CompleteMultipartUploadResult><Bucket>\" + testBucketName + \"</Bucket><Key>\" + uploader.getKey(0) + \"</Key></CompleteMultipartUploadResult>\");\n\n                    assert.equal(uploader.getUploads()[0].status, qq.status.UPLOAD_SUCCESSFUL);\n                });\n            });\n\n            it(\"converts all non-special parameters (metadata) to lower case before sending them to S3 and omits some specific params from string to sign\", function(done) {\n                assert.expect(5, done);\n\n                var uploader = new qq.s3.FineUploaderBasic({\n                        request: typicalRequestOption,\n                        signature: typicalSignatureOption,\n                        chunking: typicalChunkingOption\n                    }\n                );\n\n                uploader.setParams({\n                    mIxEdCaSe: \"value\",\n                    mIxEdCaSeFunc: function() {\n                        return \"value2\";\n                    },\n                    \"Content-Disposition\": \"attachment; filename=foo.bar;\",\n                    \"Cache-Control\": \"foo\",\n                    \"Content-Encoding\": \"bar\",\n                    \"Content-MD5\": \"something\"\n                });\n\n                startTypicalTest(uploader, function(initiateSignatureRequest, initiateToSign) {\n                    var initiateRequest;\n\n                    assert.ok(initiateToSign.headers.indexOf(\"something\") >= 0);\n                    assert.ok(initiateToSign.headers.indexOf(\"x-amz-meta-mixedcase:value\") >= 0);\n                    assert.ok(initiateToSign.headers.indexOf(\"x-amz-meta-mixedcasefunc:value2\") >= 0);\n                    assert.ok(initiateToSign.headers.indexOf(\"Cache-Control:foo\") < 0);\n                    assert.ok(initiateToSign.headers.indexOf(\"Content-Encoding:bar\") < 0);\n                    assert.ok(initiateToSign.headers.indexOf(\"Content-Disposition:attachment; filename=foo.bar;\") < 0);\n\n                    initiateSignatureRequest.respond(200, null, JSON.stringify({signature: \"thesignature\"}));\n                    initiateRequest = fileTestHelper.getRequests()[1];\n\n                    assert.equal(initiateRequest.requestHeaders[\"Content-MD5\"], \"something\");\n                    assert.equal(initiateRequest.requestHeaders[\"Content-Disposition\"], \"attachment; filename=foo.bar;\");\n                    assert.equal(initiateRequest.requestHeaders[\"Content-Encoding\"], \"bar\");\n                    assert.equal(initiateRequest.requestHeaders[\"Cache-Control\"], \"foo\");\n                    assert.equal(initiateRequest.requestHeaders[\"x-amz-meta-mixedcase\"], \"value\");\n                    assert.equal(initiateRequest.requestHeaders[\"x-amz-meta-mixedcasefunc\"], \"value2\");\n                });\n            });\n        });\n\n        describe(\"client-side signature-based chunked S3 upload tests\", function() {\n            var startTypicalTest = function(uploader, callback) {\n                    qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function (blob) {\n                        var uploadRequest;\n\n                        fileTestHelper.mockXhr();\n                        uploader.addFiles({name: \"test.jpg\", blob: blob});\n\n                        assert.equal(fileTestHelper.getRequests().length, 1, \"Wrong # of requests\");\n\n                        uploadRequest = fileTestHelper.getRequests()[0];\n\n                        callback(uploadRequest);\n                    });\n                },\n                testSecretKey = \"testSecrtKey\",\n                testExpiration = new Date(Date.now() + 60000),\n                typicalRequestOption = {\n                    endpoint: testS3Endpoint\n                },\n                typicalCredentialsOption = {\n                    accessKey: testAccessKey,\n                    secretKey: testSecretKey,\n                    expiration: testExpiration\n                };\n\n            it(\"handles a basic chunked upload\", function(done) {\n                assert.expect(24, done);\n\n                var uploader = new qq.s3.FineUploaderBasic({\n                        request: typicalRequestOption,\n                        chunking: typicalChunkingOption,\n                        credentials: typicalCredentialsOption\n                    }\n                );\n\n                startTypicalTest(uploader, function() {\n                    var uploadPartRequest,\n                        initiateRequest,\n                        multipartCompleteRequest;\n\n                    // initiate multipart upload request\n                    assert.equal(fileTestHelper.getRequests().length, 1);\n                    initiateRequest = fileTestHelper.getRequests()[0];\n                    assert.equal(initiateRequest.method, \"POST\");\n                    assert.equal(initiateRequest.url, testS3Endpoint + \"/\" + uploader.getKey(0) + \"?uploads\");\n                    assert.equal(initiateRequest.requestHeaders[\"x-amz-meta-qqfilename\"], uploader.getName(0));\n                    assert.equal(initiateRequest.requestHeaders[\"x-amz-acl\"], \"private\");\n                    assert.ok(initiateRequest.requestHeaders[\"x-amz-date\"]);\n                    assert.equal(initiateRequest.requestHeaders.Authorization.indexOf(\"AWS \" + testAccessKey + \":\"), 0, \"Initiate MP request Authorization header invalid\");\n                    initiateRequest.respond(200, null, \"<UploadId>123</UploadId>\");\n\n                    // upload part 1 request\n                    assert.equal(fileTestHelper.getRequests().length, 2);\n                    uploadPartRequest = fileTestHelper.getRequests()[1];\n                    assert.equal(uploadPartRequest.method, \"PUT\");\n                    assert.equal(uploadPartRequest.url, testS3Endpoint + \"/\" + uploader.getKey(0) + \"?partNumber=1&uploadId=123\");\n                    assert.ok(uploadPartRequest.requestHeaders[\"x-amz-date\"]);\n\n                    assert.equal(uploadPartRequest.requestHeaders[\"Content-Type\"], \"\");\n\n                    assert.equal(uploadPartRequest.requestHeaders.Authorization.indexOf(\"AWS \" + testAccessKey + \":\"), 0, \"Upload part 1 request Authorization header is invalid\");\n                    uploadPartRequest.respond(200, {ETag: \"etag1\"}, null);\n\n                    // upload part 2 request\n                    assert.equal(fileTestHelper.getRequests().length, 3);\n                    uploadPartRequest = fileTestHelper.getRequests()[2];\n                    assert.equal(uploadPartRequest.method, \"PUT\");\n                    assert.equal(uploadPartRequest.url, testS3Endpoint + \"/\" + uploader.getKey(0) + \"?partNumber=2&uploadId=123\");\n                    assert.ok(uploadPartRequest.requestHeaders[\"x-amz-date\"]);\n\n                    assert.equal(uploadPartRequest.requestHeaders[\"Content-Type\"], \"\");\n\n                    assert.equal(uploadPartRequest.requestHeaders.Authorization.indexOf(\"AWS \" + testAccessKey + \":\"), 0, \"Upload part 2 request Authorization header is invalid\");\n                    uploadPartRequest.respond(200, {ETag: \"etag2\"}, null);\n\n                    // multipart complete request\n                    assert.equal(fileTestHelper.getRequests().length, 4);\n                    multipartCompleteRequest = fileTestHelper.getRequests()[3];\n                    assert.equal(multipartCompleteRequest.method, \"POST\");\n                    assert.equal(multipartCompleteRequest.url, testS3Endpoint + \"/\" + uploader.getKey(0) + \"?uploadId=123\");\n                    assert.ok(multipartCompleteRequest.requestHeaders[\"x-amz-date\"]);\n                    assert.equal(multipartCompleteRequest.requestHeaders.Authorization.indexOf(\"AWS \" + testAccessKey + \":\"), 0, \"MP complete request Authorization header is invalid\");\n                    assert.equal(multipartCompleteRequest.requestBody, \"<CompleteMultipartUpload><Part><PartNumber>1</PartNumber><ETag>etag1</ETag></Part><Part><PartNumber>2</PartNumber><ETag>etag2</ETag></Part></CompleteMultipartUpload>\");\n                    multipartCompleteRequest.respond(200, null, \"<CompleteMultipartUploadResult><Bucket>\" + testBucketName + \"</Bucket><Key>\" + uploader.getKey(0) + \"</Key></CompleteMultipartUploadResult>\");\n\n                    assert.equal(uploader.getUploads()[0].status, qq.status.UPLOAD_SUCCESSFUL);\n                });\n            });\n\n            it(\"handles failures at every step of a chunked upload\", function(done) {\n                assert.expect(33, done);\n\n                var uploader = new qq.s3.FineUploaderBasic({\n                        request: typicalRequestOption,\n                        chunking: typicalChunkingOption,\n                        credentials: typicalCredentialsOption\n                    }\n                );\n\n                startTypicalTest(uploader, function() {\n                    var uploadPartRequest,\n                        initiateRequest,\n                        multipartCompleteRequest;\n\n                    assert.equal(uploader.getUploads()[0].status, qq.status.UPLOADING);\n\n                    // failing initiate multipart upload request\n                    assert.equal(fileTestHelper.getRequests().length, 1);\n                    initiateRequest = fileTestHelper.getRequests()[0];\n                    assert.equal(initiateRequest.url, testS3Endpoint + \"/\" + uploader.getKey(0) + \"?uploads\");\n                    initiateRequest.respond(200, null, \"\");\n\n                    assert.equal(uploader.getUploads()[0].status, qq.status.UPLOAD_FAILED);\n                    assert.equal(fileTestHelper.getRequests().length, 1);\n                    uploader.retry(0);\n                    assert.equal(uploader.getUploads()[0].status, qq.status.UPLOADING);\n\n                    // successful initiate multipart upload request\n                    assert.equal(fileTestHelper.getRequests().length, 2);\n                    initiateRequest = fileTestHelper.getRequests()[1];\n                    assert.equal(initiateRequest.url, testS3Endpoint + \"/\" + uploader.getKey(0) + \"?uploads\");\n                    initiateRequest.respond(200, null, \"<UploadId>123</UploadId>\");\n\n                    // failing upload part 1 request\n                    assert.equal(fileTestHelper.getRequests().length, 3);\n                    uploadPartRequest = fileTestHelper.getRequests()[2];\n                    assert.equal(uploadPartRequest.url, testS3Endpoint + \"/\" + uploader.getKey(0) + \"?partNumber=1&uploadId=123\");\n                    uploadPartRequest.respond(404, {ETag: \"etag1\"}, null);\n\n                    assert.equal(uploader.getUploads()[0].status, qq.status.UPLOAD_FAILED);\n                    assert.equal(fileTestHelper.getRequests().length, 3);\n                    uploader.retry(0);\n                    assert.equal(uploader.getUploads()[0].status, qq.status.UPLOADING);\n\n                    // successful upload part 1 request\n                    assert.equal(fileTestHelper.getRequests().length, 4);\n                    uploadPartRequest = fileTestHelper.getRequests()[3];\n                    assert.equal(uploadPartRequest.url, testS3Endpoint + \"/\" + uploader.getKey(0) + \"?partNumber=1&uploadId=123\");\n                    uploadPartRequest.respond(200, {ETag: \"etag1_a\"}, null);\n\n                    // failing upload part 2 request\n                    assert.equal(fileTestHelper.getRequests().length, 5);\n                    uploadPartRequest = fileTestHelper.getRequests()[4];\n                    assert.equal(uploadPartRequest.url, testS3Endpoint + \"/\" + uploader.getKey(0) + \"?partNumber=2&uploadId=123\");\n                    uploadPartRequest.respond(404, {ETag: \"etag2\"}, null);\n\n                    assert.equal(uploader.getUploads()[0].status, qq.status.UPLOAD_FAILED);\n                    assert.equal(fileTestHelper.getRequests().length, 5);\n                    uploader.retry(0);\n                    assert.equal(uploader.getUploads()[0].status, qq.status.UPLOADING);\n\n                    // successful upload part 2 request\n                    assert.equal(fileTestHelper.getRequests().length, 6);\n                    uploadPartRequest = fileTestHelper.getRequests()[5];\n                    assert.equal(uploadPartRequest.url, testS3Endpoint + \"/\" + uploader.getKey(0) + \"?partNumber=2&uploadId=123\");\n                    uploadPartRequest.respond(200, {ETag: \"etag2_a\"}, null);\n\n                    // failing multipart complete request\n                    assert.equal(fileTestHelper.getRequests().length, 7);\n                    multipartCompleteRequest = fileTestHelper.getRequests()[6];\n                    assert.equal(multipartCompleteRequest.url, testS3Endpoint + \"/\" + uploader.getKey(0) + \"?uploadId=123\");\n                    assert.equal(multipartCompleteRequest.requestBody, \"<CompleteMultipartUpload><Part><PartNumber>1</PartNumber><ETag>etag1_a</ETag></Part><Part><PartNumber>2</PartNumber><ETag>etag2_a</ETag></Part></CompleteMultipartUpload>\");\n                    multipartCompleteRequest.respond(200, null, \"<CompleteMultipartUploadResult><Key>\" + uploader.getKey(0) + \"</Key></CompleteMultipartUploadResult>\");\n\n                    assert.equal(uploader.getUploads()[0].status, qq.status.UPLOAD_FAILED);\n                    assert.equal(fileTestHelper.getRequests().length, 7);\n                    uploader.retry(0);\n                    assert.equal(uploader.getUploads()[0].status, qq.status.UPLOADING);\n\n                    // successful multipart complete request\n                    assert.equal(fileTestHelper.getRequests().length, 8);\n                    multipartCompleteRequest = fileTestHelper.getRequests()[7];\n                    assert.equal(multipartCompleteRequest.url, testS3Endpoint + \"/\" + uploader.getKey(0) + \"?uploadId=123\");\n                    assert.equal(multipartCompleteRequest.requestBody, \"<CompleteMultipartUpload><Part><PartNumber>1</PartNumber><ETag>etag1_a</ETag></Part><Part><PartNumber>2</PartNumber><ETag>etag2_a</ETag></Part></CompleteMultipartUpload>\");\n                    multipartCompleteRequest.respond(200, null, \"<CompleteMultipartUploadResult><Bucket>\" + testBucketName + \"</Bucket><Key>\" + uploader.getKey(0) + \"</Key></CompleteMultipartUploadResult>\");\n\n                    assert.equal(uploader.getUploads()[0].status, qq.status.UPLOAD_SUCCESSFUL);\n                });\n            });\n        });\n    });\n}\n"
  },
  {
    "path": "test/unit/s3/serverless-uploads.js",
    "content": "/* globals describe, beforeEach, $fixture, qq, assert, it, qqtest, helpme, purl, Q */\ndescribe(\"S3 serverless upload tests\", function() {\n    \"use strict\";\n\n    if (qqtest.canDownloadFileAsBlob) {\n        describe(\"no-server S3 upload tests\", function() {\n\n            var fileTestHelper = helpme.setupFileTests(),\n                testS3Endpoint = \"https://mytestbucket.s3.amazonaws.com\",\n                testAccessKey = \"testAccessKey\",\n                testSecretKey = \"testSecretKey\",\n                testSessionToken = \"testSessionToken\";\n\n            describe(\"v4 signatures\", function() {\n                it(\"test simple upload with only mandatory credentials specified as options\", function(done) {\n                    var testExpiration = new Date(Date.now() + 10000),\n                        uploader = new qq.s3.FineUploaderBasic({\n                            request: {\n                                endpoint: testS3Endpoint\n                            },\n                            signature: {\n                                version: 4\n                            },\n                            credentials: {\n                                accessKey: testAccessKey,\n                                secretKey: testSecretKey,\n                                expiration: testExpiration\n                            }\n                        });\n\n                    qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function (blob) {\n                        var request, requestParams;\n\n                        fileTestHelper.mockXhr();\n                        uploader.addFiles({name: \"test\", blob: blob});\n\n                        assert.equal(fileTestHelper.getRequests().length, 1, \"Wrong # of requests\");\n\n                        request = fileTestHelper.getRequests()[0];\n                        requestParams = request.requestBody.fields;\n\n                        assert.equal(request.url, testS3Endpoint);\n                        assert.equal(request.method, \"POST\");\n\n                        assert.equal(requestParams[\"Content-Type\"], \"image/jpeg\");\n                        assert.equal(requestParams.success_action_status, 200);\n                        assert.equal(requestParams[qq.s3.util.SESSION_TOKEN_PARAM_NAME], null);\n                        assert.equal(requestParams[\"x-amz-storage-class\"], null);\n                        assert.equal(requestParams[\"x-amz-meta-qqfilename\"], \"test\");\n                        assert.equal(requestParams.key, uploader.getKey(0));\n                        assert.equal(requestParams.acl, \"private\");\n                        assert.ok(requestParams.file);\n\n                        assert.equal(requestParams[\"x-amz-algorithm\"], \"AWS4-HMAC-SHA256\");\n                        assert.ok(new RegExp(testAccessKey + \"\\\\/\\\\d{8}\\\\/us-east-1\\\\/s3\\\\/aws4_request\").test(requestParams[\"x-amz-credential\"]));\n                        assert.ok(requestParams[\"x-amz-date\"]);\n                        assert.ok(requestParams[\"x-amz-signature\"]);\n                        assert.ok(requestParams.policy);\n\n                        done();\n                    });\n                });\n            });\n\n            it(\"test simple upload with only mandatory credentials specified as options\", function(done) {\n                assert.expect(14, done);\n\n                var testExpiration = new Date(Date.now() + 10000),\n                    uploader = new qq.s3.FineUploaderBasic({\n                        request: {\n                            endpoint: testS3Endpoint\n                        },\n                        credentials: {\n                            accessKey: testAccessKey,\n                            secretKey: testSecretKey,\n                            expiration: testExpiration\n                        }\n                    });\n\n                qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function (blob) {\n                    var request, requestParams;\n\n                    fileTestHelper.mockXhr();\n                    uploader.addFiles({name: \"test\", blob: blob});\n\n                    assert.equal(fileTestHelper.getRequests().length, 1, \"Wrong # of requests\");\n\n                    request = fileTestHelper.getRequests()[0];\n                    requestParams = request.requestBody.fields;\n\n                    assert.equal(request.url, testS3Endpoint);\n                    assert.equal(request.method, \"POST\");\n\n                    assert.equal(requestParams[\"Content-Type\"], \"image/jpeg\");\n                    assert.equal(requestParams.success_action_status, 200);\n                    assert.equal(requestParams[qq.s3.util.SESSION_TOKEN_PARAM_NAME], null);\n                    assert.equal(requestParams[\"x-amz-storage-class\"], null);\n                    assert.equal(requestParams[\"x-amz-meta-qqfilename\"], \"test\");\n                    assert.equal(requestParams.key, uploader.getKey(0));\n                    assert.equal(requestParams.AWSAccessKeyId, testAccessKey);\n                    assert.equal(requestParams.acl, \"private\");\n                    assert.ok(requestParams.file);\n\n                    assert.ok(requestParams.signature);\n                    assert.ok(requestParams.policy);\n                });\n            });\n\n            it(\"test simple upload with all credential options specified\", function(done) {\n                assert.expect(1, done);\n\n                var testExpiration = new Date(Date.now() + 10000).toISOString(),\n                    uploader = new qq.s3.FineUploaderBasic({\n                        request: {\n                            endpoint: testS3Endpoint\n                        },\n                        credentials: {\n                            accessKey: testAccessKey,\n                            secretKey: testSecretKey,\n                            expiration: testExpiration,\n                            sessionToken: testSessionToken\n                        }\n                    });\n\n                qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function (blob) {\n                    var request, requestParams;\n\n                    fileTestHelper.mockXhr();\n                    uploader.addFiles({name: \"test\", blob: blob});\n\n                    request = fileTestHelper.getRequests()[0];\n                    requestParams = request.requestBody.fields;\n\n                    assert.equal(requestParams[qq.s3.util.SESSION_TOKEN_PARAM_NAME], testSessionToken);\n                });\n            });\n\n            it(\"test simple upload with credentials only specified via API method\", function(done) {\n                assert.expect(14, done);\n\n                var testExpiration = new Date(Date.now() + 10000),\n                    uploader = new qq.s3.FineUploaderBasic({\n                        request: {\n                            endpoint: testS3Endpoint\n                        }\n                    });\n\n                uploader.setCredentials({\n                    accessKey: testAccessKey,\n                    secretKey: testSecretKey,\n                    expiration: testExpiration,\n                    sessionToken: testSessionToken\n                });\n\n                qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function (blob) {\n                    var request, requestParams;\n\n                    fileTestHelper.mockXhr();\n                    uploader.addFiles({name: \"test\", blob: blob});\n\n                    assert.equal(fileTestHelper.getRequests().length, 1, \"Wrong # of requests\");\n\n                    request = fileTestHelper.getRequests()[0];\n                    requestParams = request.requestBody.fields;\n\n                    assert.equal(request.url, testS3Endpoint);\n                    assert.equal(request.method, \"POST\");\n\n                    assert.equal(requestParams[\"Content-Type\"], \"image/jpeg\");\n                    assert.equal(requestParams.success_action_status, 200);\n                    assert.equal(requestParams[qq.s3.util.SESSION_TOKEN_PARAM_NAME], testSessionToken);\n                    assert.equal(requestParams[\"x-amz-storage-class\"], null);\n                    assert.equal(requestParams[\"x-amz-meta-qqfilename\"], \"test\");\n                    assert.equal(requestParams.key, uploader.getKey(0));\n                    assert.equal(requestParams.AWSAccessKeyId, testAccessKey);\n                    assert.equal(requestParams.acl, \"private\");\n                    assert.ok(requestParams.file);\n\n                    assert.ok(requestParams.signature);\n                    assert.ok(requestParams.policy);\n                });\n            });\n\n            describe(\"test credentialsExpired callback\", function() {\n                var testExpiration = new Date(Date.now() - 1000),\n                    testAccessKeyFromCallback = \"testAccessKeyFromCallback\",\n                    testSessionTokenFromCallback = \"testSessionTokenFromCallback\";\n\n                function runTest(callback, done) {\n                    assert.expect(15, done);\n\n                    var uploader = new qq.s3.FineUploaderBasic({\n                        request: {\n                            endpoint: testS3Endpoint\n                        },\n                        credentials: {\n                            accessKey: testAccessKey,\n                            secretKey: testSecretKey,\n                            expiration: testExpiration,\n                            sessionToken: testSessionToken\n                        },\n                        callbacks: {\n                            onCredentialsExpired: callback\n                        }\n                    });\n\n                    qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function (blob) {\n                        var request, requestParams;\n\n                        fileTestHelper.mockXhr();\n                        uploader.addFiles({name: \"test\", blob: blob});\n\n                        assert.equal(fileTestHelper.getRequests().length, 1, \"Wrong # of requests\");\n\n                        setTimeout(function() {\n                            request = fileTestHelper.getRequests()[0];\n                            requestParams = request.requestBody.fields;\n\n                            assert.equal(request.url, testS3Endpoint);\n                            assert.equal(request.method, \"POST\");\n\n                            assert.equal(requestParams[\"Content-Type\"], \"image/jpeg\");\n                            assert.equal(requestParams.success_action_status, 200);\n                            assert.equal(requestParams[qq.s3.util.SESSION_TOKEN_PARAM_NAME], testSessionTokenFromCallback);\n                            assert.equal(requestParams[\"x-amz-storage-class\"], null);\n                            assert.equal(requestParams[\"x-amz-meta-qqfilename\"], \"test\");\n                            assert.equal(requestParams.key, uploader.getKey(0));\n                            assert.equal(requestParams.AWSAccessKeyId, testAccessKeyFromCallback);\n                            assert.equal(requestParams.acl, \"private\");\n                            assert.ok(requestParams.file);\n\n                            assert.ok(requestParams.signature);\n                            assert.ok(requestParams.policy);\n                        }, 10);\n                    });\n                }\n\n                it(\"qq.Promise\", function(done) {\n                    var callback = function() {\n                        assert.ok(true);\n\n                        var promise = new qq.Promise();\n                        promise.success({\n                            accessKey: testAccessKeyFromCallback,\n                            secretKey: testSecretKey,\n                            expiration: new Date(Date.now() + 10000),\n                            sessionToken: testSessionTokenFromCallback\n                        });\n                        return promise;\n                    };\n\n                    runTest(callback, done);\n                });\n\n                it(\"Q.js\", function(done) {\n                    var callback = function() {\n                        assert.ok(true);\n\n                        /* jshint newcap:false */\n                        return Q({\n                            accessKey: testAccessKeyFromCallback,\n                            secretKey: testSecretKey,\n                            expiration: new Date(Date.now() + 10000),\n                            sessionToken: testSessionTokenFromCallback\n                        });\n                    };\n\n                    runTest(callback, done);\n                });\n            });\n        });\n    }\n\n    describe(\"non-file-based tests\", function() {\n        it(\"enforces mandatory credentials\", function() {\n            var uploader = new qq.s3.FineUploaderBasic({});\n\n            assert.doesNotThrow(\n                function() {\n                    uploader.setCredentials({\n                        accessKey: \"ak\",\n                        secretKey: \"sk\",\n                        expiration: new Date()\n                    });\n                }\n            );\n\n            assert.throws(\n                function() {\n                    uploader.setCredentials({\n                        accessKey: \"ak\",\n                        secretKey: \"sk\"\n                    });\n                }, qq.Error\n            );\n\n            assert.throws(\n                function() {\n                    uploader.setCredentials({\n                        accessKey: \"ak\",\n                        expiration: new Date()\n                    });\n                }, qq.Error\n            );\n\n            assert.throws(\n                function() {\n                    uploader.setCredentials({\n                        secretKey: \"sk\",\n                        expiration: new Date()\n                    });\n                }, qq.Error\n            );\n        });\n\n    });\n});\n"
  },
  {
    "path": "test/unit/s3/simple-file-uploads.js",
    "content": "/* globals describe, beforeEach, $fixture, qq, assert, it, qqtest, helpme, purl, Q */\nif (qqtest.canDownloadFileAsBlob) {\n    describe(\"simple S3 upload tests\", function() {\n        \"use strict\";\n\n        var fileTestHelper = helpme.setupFileTests(),\n            testS3Endpoint = \"https://mytestbucket.s3.amazonaws.com\",\n            testBucketName = \"mytestbucket\",\n            testSignatureEndoint = \"/signature\",\n            testAccessKey = \"testAccessKey\",\n            typicalRequestOption = {\n                accessKey: testAccessKey,\n                endpoint: testS3Endpoint\n            },\n            v2SignatureOption = {\n                endpoint: testSignatureEndoint\n            },\n            v4SignatureOption = {\n                endpoint: testSignatureEndoint,\n                version: 4\n            },\n            startTypicalTest = function(uploader, callback) {\n                qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function (blob) {\n                    var signatureRequest, uploadRequest, policyDoc,\n                        conditions = {};\n\n                    fileTestHelper.mockXhr();\n                    uploader.addFiles({name: \"test.jpg\", blob: blob});\n\n                    setTimeout(function() {\n                        assert.equal(fileTestHelper.getRequests().length, 2, \"Wrong # of requests\");\n\n                        uploadRequest = fileTestHelper.getRequests()[0];\n                        signatureRequest = fileTestHelper.getRequests()[1];\n                        policyDoc = JSON.parse(signatureRequest.requestBody);\n\n                        qq.each(policyDoc.conditions, function(condIdx, condObj) {\n                            var condName = Object.getOwnPropertyNames(condObj)[0];\n                            conditions[condName] = condObj[condName];\n                        });\n\n                        callback(signatureRequest, policyDoc, uploadRequest, conditions);\n                    }, 10);\n                });\n            };\n\n        describe(\"v4 signatures\", function() {\n            it(\"test most basic upload w/ signature request\", function(done) {\n                var uploader = new qq.s3.FineUploaderBasic({\n                        request: typicalRequestOption,\n                        signature: v4SignatureOption\n                    }\n                );\n\n                startTypicalTest(uploader, function(signatureRequest, policyDoc, uploadRequest, conditions) {\n                    var uploadRequestParams,\n                        now = new Date(),\n                        policyDate;\n\n                    assert.equal(signatureRequest.method, \"POST\");\n                    assert.equal(signatureRequest.url, testSignatureEndoint + \"?v4=true\");\n                    assert.equal(signatureRequest.requestHeaders[\"Content-Type\"].indexOf(\"application/json;\"), 0);\n\n                    assert.ok(new Date(policyDoc.expiration).getTime() > Date.now());\n                    assert.equal(policyDoc.conditions.length, 9);\n\n                    assert.equal(conditions.acl, \"private\");\n                    assert.equal(conditions.bucket, testBucketName);\n                    assert.equal(conditions[\"Content-Type\"], \"image/jpeg\");\n                    assert.equal(conditions.success_action_status, 200);\n                    assert.equal(conditions[\"x-amz-algorithm\"], \"AWS4-HMAC-SHA256\");\n                    assert.equal(conditions.key, uploader.getKey(0));\n                    assert.equal(conditions.key, uploader.getUuid(0) + \".jpg\");\n                    assert.equal(conditions[\"x-amz-credential\"], testAccessKey + \"/\" + now.getUTCFullYear() + (\"0\" + (now.getUTCMonth() + 1)).slice(-2) + (\"0\" + now.getUTCDate()).slice(-2) + \"/us-east-1/s3/aws4_request\");\n                    policyDate = conditions[\"x-amz-date\"];\n                    assert.ok(policyDate);\n                    assert.equal(conditions[\"x-amz-meta-qqfilename\"], \"test.jpg\");\n\n                    signatureRequest.respond(200, null, JSON.stringify({policy: \"thepolicy\", signature: \"thesignature\"}));\n\n                    uploadRequestParams = uploadRequest.requestBody.fields;\n\n                    assert.equal(uploadRequest.url, testS3Endpoint);\n                    assert.equal(uploadRequest.method, \"POST\");\n\n                    assert.equal(uploadRequestParams.key, uploader.getUuid(0) + \".jpg\");\n                    assert.equal(uploadRequestParams[\"Content-Type\"], \"image/jpeg\");\n                    assert.equal(uploadRequestParams.success_action_status, 200);\n                    assert.equal(uploadRequestParams.acl, \"private\");\n                    assert.equal(uploadRequestParams[\"x-amz-meta-qqfilename\"], \"test.jpg\");\n                    assert.equal(uploadRequestParams[\"x-amz-algorithm\"], \"AWS4-HMAC-SHA256\");\n                    assert.equal(uploadRequestParams[\"x-amz-credential\"], testAccessKey + \"/\" + now.getUTCFullYear() + (\"0\" + (now.getUTCMonth() + 1)).slice(-2) + (\"0\" + now.getUTCDate()).slice(-2) + \"/us-east-1/s3/aws4_request\");\n                    assert.equal(uploadRequestParams[\"x-amz-date\"], policyDate);\n\n                    assert.ok(uploadRequestParams.file);\n\n                    assert.equal(uploadRequestParams[\"x-amz-signature\"], \"thesignature\");\n                    assert.equal(uploadRequestParams.policy, \"thepolicy\");\n\n                    done();\n                });\n            });\n\n            it(\"handles slow browser system clock\", function(done) {\n                var clockDrift = 1000 * 60 * 60, // slow by 1 hour\n                    uploader = new qq.s3.FineUploaderBasic({\n                        request: {\n                            accessKey: testAccessKey,\n                            clockDrift: clockDrift,\n                            endpoint: testS3Endpoint\n                        },\n                        signature: v4SignatureOption\n                    });\n\n                startTypicalTest(uploader, function(signatureRequest, policyDoc, uploadRequest, conditions) {\n                    var uploadRequestParams,\n                        now = new Date(new Date().getTime() + clockDrift),\n                        policyDate;\n\n                    assert.ok(new Date(policyDoc.expiration).getTime() > now);\n                    policyDate = conditions[\"x-amz-date\"];\n                    signatureRequest.respond(200, null, JSON.stringify({policy: \"thepolicy\", signature: \"thesignature\"}));\n\n                    uploadRequestParams = uploadRequest.requestBody.fields;\n                    assert.equal(uploadRequestParams[\"x-amz-date\"], policyDate);\n                    done();\n                });\n            });\n\n            it(\"handles fast browser system clock\", function(done) {\n                var clockDrift = -1000 * 60 * 60, // fast by 1 hour\n                    uploader = new qq.s3.FineUploaderBasic({\n                        request: {\n                            accessKey: testAccessKey,\n                            clockDrift: clockDrift,\n                            endpoint: testS3Endpoint\n                        },\n                        signature: v4SignatureOption\n                    }\n                );\n\n                startTypicalTest(uploader, function(signatureRequest, policyDoc, uploadRequest, conditions) {\n                    var uploadRequestParams,\n                        now = new Date(new Date().getTime() + clockDrift),\n                        policyDate;\n\n                    assert.ok(new Date(policyDoc.expiration).getTime() > now);\n                    policyDate = conditions[\"x-amz-date\"];\n                    signatureRequest.respond(200, null, JSON.stringify({policy: \"thepolicy\", signature: \"thesignature\"}));\n\n                    uploadRequestParams = uploadRequest.requestBody.fields;\n                    assert.equal(uploadRequestParams[\"x-amz-date\"], policyDate);\n                    done();\n                });\n            });\n\n            it(\"uses the error field on the signature request response if provided\", function(done) {\n                assert.expect(2, done);\n\n                var uploader = new qq.s3.FineUploaderBasic({\n                        request: typicalRequestOption,\n                        signature: v4SignatureOption,\n                        callbacks: {\n                            onError: function(id, name, errorReason) {\n                                assert.equal(errorReason, \"error message\");\n                            }\n                        }\n                    }\n                );\n\n                startTypicalTest(uploader, function(signatureRequest, policyDoc, uploadRequest, conditions) {\n                    var s3RequestSigner = new qq.s3.RequestSigner({\n                        expectingPolicy: true,\n                        signatureSpec: v4SignatureOption,\n                    });\n\n                    signatureRequest.respond(500, null, JSON.stringify({error: \"error message\"}));\n                });\n            });\n        });\n\n        it(\"test most basic upload w/ signature request\", function(done) {\n            var uploader = new qq.s3.FineUploaderBasic({\n                    request: typicalRequestOption,\n                    signature: v2SignatureOption\n                }\n            );\n\n            startTypicalTest(uploader, function(signatureRequest, policyDoc, uploadRequest, conditions) {\n                var uploadRequestParams;\n\n                assert.equal(signatureRequest.method, \"POST\");\n                assert.equal(signatureRequest.url, testSignatureEndoint);\n                assert.equal(signatureRequest.requestHeaders[\"Content-Type\"].indexOf(\"application/json;\"), 0);\n\n                assert.ok(new Date(policyDoc.expiration).getTime() > Date.now());\n                assert.equal(policyDoc.conditions.length, 6);\n\n                assert.equal(conditions.acl, \"private\");\n                assert.equal(conditions.bucket, testBucketName);\n                assert.equal(conditions.success_action_status, 200);\n                assert.equal(conditions.key, uploader.getKey(0));\n                assert.equal(conditions.key, uploader.getUuid(0) + \".jpg\");\n                assert.equal(conditions[\"x-amz-meta-qqfilename\"], \"test.jpg\");\n\n                signatureRequest.respond(200, null, JSON.stringify({policy: \"thepolicy\", signature: \"thesignature\"}));\n\n                uploadRequestParams = uploadRequest.requestBody.fields;\n\n                assert.equal(uploadRequest.url, testS3Endpoint);\n                assert.equal(uploadRequest.method, \"POST\");\n\n                assert.equal(uploadRequestParams[\"Content-Type\"], \"image/jpeg\");\n                assert.equal(uploadRequestParams.success_action_status, 200);\n                assert.equal(uploadRequestParams[\"x-amz-storage-class\"], null);\n                assert.equal(uploadRequestParams[\"x-amz-meta-qqfilename\"], \"test.jpg\");\n                assert.equal(uploadRequestParams.key, uploader.getUuid(0) + \".jpg\");\n                assert.equal(uploadRequestParams.AWSAccessKeyId, testAccessKey);\n                assert.equal(uploadRequestParams.acl, \"private\");\n                assert.ok(uploadRequestParams.file);\n\n                assert.equal(uploadRequestParams.signature, \"thesignature\");\n                assert.equal(uploadRequestParams.policy, \"thepolicy\");\n\n                done();\n            });\n        });\n\n        it(\"uses the error field on the signature request response if provided\", function(done) {\n            assert.expect(2, done);\n\n            var uploader = new qq.s3.FineUploaderBasic({\n                    request: typicalRequestOption,\n                    signature: v2SignatureOption,\n                    callbacks: {\n                        onError: function(id, name, errorReason) {\n                            assert.equal(errorReason, \"error message\");\n                        }\n                    }\n                }\n            );\n\n            startTypicalTest(uploader, function(signatureRequest, policyDoc, uploadRequest, conditions) {\n                var s3RequestSigner = new qq.s3.RequestSigner({\n                    expectingPolicy: true,\n                    signatureSpec: v2SignatureOption,\n                });\n\n                signatureRequest.respond(500, null, JSON.stringify({error: \"error message\"}));\n            });\n        });\n\n        it(\"handles slow browser system clock\", function(done) {\n            var clockDrift = 1000 * 60 * 60, // slow by 1 hour\n                uploader = new qq.s3.FineUploaderBasic({\n                    request: {\n                        accessKey: testAccessKey,\n                        clockDrift: clockDrift,\n                        endpoint: testS3Endpoint\n                    },\n                    signature: v2SignatureOption\n                });\n\n            startTypicalTest(uploader, function(signatureRequest, policyDoc, uploadRequest, conditions) {\n                var uploadRequestParams,\n                    now = new Date(new Date().getTime() + clockDrift),\n                    policyDate;\n\n                assert.ok(new Date(policyDoc.expiration).getTime() > now);\n                policyDate = conditions[\"x-amz-date\"];\n                signatureRequest.respond(200, null, JSON.stringify({policy: \"thepolicy\", signature: \"thesignature\"}));\n\n                uploadRequestParams = uploadRequest.requestBody.fields;\n                assert.equal(uploadRequestParams[\"x-amz-date\"], policyDate);\n                done();\n            });\n        });\n\n        it(\"handles fast browser system clock\", function(done) {\n            var clockDrift = -1000 * 60 * 60, // fast by 1 hour\n                uploader = new qq.s3.FineUploaderBasic({\n                    request: {\n                        accessKey: testAccessKey,\n                        clockDrift: clockDrift,\n                        endpoint: testS3Endpoint\n                    },\n                    signature: v2SignatureOption\n                });\n\n            startTypicalTest(uploader, function(signatureRequest, policyDoc, uploadRequest, conditions) {\n                var uploadRequestParams,\n                    now = new Date(new Date().getTime() + clockDrift),\n                    policyDate;\n\n                assert.ok(new Date(policyDoc.expiration).getTime() > now);\n                policyDate = conditions[\"x-amz-date\"];\n                signatureRequest.respond(200, null, JSON.stringify({policy: \"thepolicy\", signature: \"thesignature\"}));\n\n                uploadRequestParams = uploadRequest.requestBody.fields;\n                assert.equal(uploadRequestParams[\"x-amz-date\"], policyDate);\n                done();\n            });\n        });\n\n        it(\"converts all parameters (metadata) to lower case before sending them to S3, except for special params\", function(done) {\n            var uploader = new qq.s3.FineUploaderBasic({\n                    request: typicalRequestOption,\n                    signature: v2SignatureOption\n                }\n            );\n\n            uploader.setParams({\n                mIxEdCaSe: \"value\",\n                mIxEdCaSeFunc: function() {\n                    return \"value2\";\n                },\n                \"Content-Disposition\": \"attachment; filename=foo.bar;\",\n                \"Cache-Control\": \"foo\",\n                \"Content-Encoding\": \"bar\"\n            });\n\n            startTypicalTest(uploader, function(signatureRequest, policyDoc, uploadRequest, conditions) {\n                var uploadRequestParams;\n\n                assert.equal(conditions[\"x-amz-meta-mixedcase\"], \"value\");\n                assert.equal(conditions[\"x-amz-meta-mixedcasefunc\"], \"value2\");\n                assert.equal(conditions[\"Content-Disposition\"], \"attachment; filename=foo.bar;\");\n                assert.equal(conditions[\"Cache-Control\"], \"foo\");\n                assert.equal(conditions[\"Content-Encoding\"], \"bar\");\n                signatureRequest.respond(200, null, JSON.stringify({policy: \"thepolicy\", signature: \"thesignature\"}));\n\n                uploadRequestParams = uploadRequest.requestBody.fields;\n\n                assert.equal(uploadRequestParams[\"x-amz-meta-mixedcase\"], \"value\");\n                assert.equal(uploadRequestParams[\"x-amz-meta-mixedcasefunc\"], \"value2\");\n                assert.equal(uploadRequestParams[\"Content-Disposition\"], \"attachment; filename=foo.bar;\");\n                assert.equal(uploadRequestParams[\"Cache-Control\"], \"foo\");\n                assert.equal(uploadRequestParams[\"Content-Encoding\"], \"bar\");\n\n                done();\n            });\n        });\n\n        it(\"respects the objectProperties.key option w/ a value of 'filename'\", function(done) {\n            var uploader = new qq.s3.FineUploaderBasic({\n                request: typicalRequestOption,\n                signature: v2SignatureOption,\n                objectProperties: {\n                    key: \"filename\"\n                }\n            });\n\n            startTypicalTest(uploader, function(signatureRequest, policyDoc, uploadRequest, conditions) {\n                var uploadRequestParams;\n\n                assert.equal(conditions.key, \"test.jpg\");\n                assert.equal(uploader.getKey(0), \"test.jpg\");\n                assert.equal(conditions[\"x-amz-meta-qqfilename\"], \"test.jpg\");\n\n                signatureRequest.respond(200, null, JSON.stringify({policy: \"thepolicy\", signature: \"thesignature\"}));\n\n                uploadRequestParams = uploadRequest.requestBody.fields;\n\n                assert.equal(uploadRequestParams[\"x-amz-meta-qqfilename\"], \"test.jpg\");\n\n                done();\n            });\n        });\n\n        it(\"respects the objectProperties.key option w/ a custom key generation function\", function(done) {\n            var customKeyPrefix = \"testcustomkey_\",\n                uploader = new qq.s3.FineUploaderBasic({\n                    request: typicalRequestOption,\n                    signature: v2SignatureOption,\n                    objectProperties: {\n                        key: function(id) {\n                            return customKeyPrefix + this.getName(id);\n                        }\n                    }\n                }\n            );\n\n            startTypicalTest(uploader, function(signatureRequest, policyDoc, uploadRequest, conditions) {\n                var uploadRequestParams;\n\n                assert.equal(conditions.key, customKeyPrefix + \"test.jpg\");\n                assert.equal(uploader.getKey(0), customKeyPrefix + \"test.jpg\");\n                assert.equal(conditions[\"x-amz-meta-qqfilename\"], \"test.jpg\");\n                signatureRequest.respond(200, null, JSON.stringify({policy: \"thepolicy\", signature: \"thesignature\"}));\n\n                uploadRequestParams = uploadRequest.requestBody.fields;\n                assert.equal(uploadRequestParams[\"x-amz-meta-qqfilename\"], \"test.jpg\");\n\n                done();\n            });\n        });\n\n        describe(\"respects the objectProperties.key option w/ a custom key generation function that returns a promise\", function() {\n            var customKeyPrefix = \"testcustomkey_\";\n\n            function runTest(keyFunc, done) {\n                var uploader = new qq.s3.FineUploaderBasic({\n                        request: typicalRequestOption,\n                        signature: v2SignatureOption,\n                        objectProperties: {\n                            key: keyFunc\n                        }\n                    }\n                );\n\n                startTypicalTest(uploader, function(signatureRequest, policyDoc, uploadRequest, conditions) {\n                    var uploadRequestParams;\n\n                    assert.equal(conditions.key, customKeyPrefix + \"test.jpg\");\n                    assert.equal(uploader.getKey(0), customKeyPrefix + \"test.jpg\");\n                    assert.equal(conditions[\"x-amz-meta-qqfilename\"], \"test.jpg\");\n                    signatureRequest.respond(200, null, JSON.stringify({policy: \"thepolicy\", signature: \"thesignature\"}));\n\n                    uploadRequestParams = uploadRequest.requestBody.fields;\n                    assert.equal(uploadRequestParams[\"x-amz-meta-qqfilename\"], \"test.jpg\");\n\n                    done();\n                });\n            }\n\n            it(\"qq.Promise\", function(done) {\n                var keyFunc = function(id) {\n                    return new qq.Promise().success(customKeyPrefix + this.getName(id));\n                };\n\n                runTest(keyFunc, done);\n            });\n\n            it(\"Q.js\", function(done) {\n                var keyFunc = function(id) {\n                    /* jshint newcap:false */\n                    return Q(customKeyPrefix + this.getName(id));\n                };\n\n                runTest(keyFunc, done);\n            });\n        });\n\n        describe(\"respects the objectProperties.key option w/ a custom key generation function that returns a failed promise (no reason)\", function() {\n            function runTest(keyFunc, done) {\n                var uploader = new qq.s3.FineUploaderBasic({\n                        request: typicalRequestOption,\n                        signature: v2SignatureOption,\n                        objectProperties: {\n                            key: keyFunc\n                        }\n                    }\n                );\n\n                qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function (blob) {\n                    uploader.addFiles({name: \"test.jpg\", blob: blob});\n\n                    assert.equal(fileTestHelper.getRequests().length, 0, \"Wrong # of requests\");\n\n                    done();\n                });\n            }\n\n            it(\"qq.Promise\", function(done) {\n                var keyFunc = function() {\n                    return new qq.Promise().failure();\n                };\n\n                runTest(keyFunc, done);\n            });\n\n            it(\"Q.js\", function(done) {\n                var keyFunc = function() {\n                    return Q.reject();\n                };\n\n                runTest(keyFunc, done);\n            });\n        });\n\n        describe(\"respects the objectProperties.key option w/ a custom key generation function that returns a failed promise (w/ reason)\", function() {\n            function runTest(keyFunc, done) {\n                var uploader = new qq.s3.FineUploaderBasic({\n                        request: typicalRequestOption,\n                        signature: v2SignatureOption,\n                        objectProperties: {\n                            key: keyFunc\n                        },\n                        callbacks: {\n                            onComplete: function(id, name, response, xhr) {\n                                assert.equal(response.error, \"oops\");\n                                assert.ok(!response.success);\n                                done();\n                            }\n                        }\n                    }\n                );\n\n                qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function (blob) {\n                    uploader.addFiles({name: \"test.jpg\", blob: blob});\n\n                    assert.equal(fileTestHelper.getRequests().length, 0, \"Wrong # of requests\");\n                });\n            }\n\n            it(\"qq.Promise\", function(done) {\n                var keyFunc = function() {\n                    return new qq.Promise().failure(\"oops\");\n                };\n\n                runTest(keyFunc, done);\n            });\n\n            it(\"qq.Promise\", function(done) {\n                var keyFunc = function() {\n                    return Q.reject(\"oops\");\n                };\n\n                runTest(keyFunc, done);\n            });\n        });\n\n        it(\"respects the objectProperties.acl option w/ a custom value set via option\", function(done) {\n            var uploader = new qq.s3.FineUploaderBasic({\n                    request: typicalRequestOption,\n                    signature: v2SignatureOption,\n                    objectProperties: {\n                        acl: \"public-read\"\n                    }\n                }\n            );\n\n            startTypicalTest(uploader, function(signatureRequest, policyDoc, uploadRequest, conditions) {\n                var uploadRequestParams;\n\n                assert.equal(conditions.acl, \"public-read\");\n                signatureRequest.respond(200, null, JSON.stringify({policy: \"thepolicy\", signature: \"thesignature\"}));\n\n                uploadRequestParams = uploadRequest.requestBody.fields;\n                assert.equal(uploadRequestParams.acl, \"public-read\");\n\n                done();\n            });\n        });\n\n        it(\"respects the objectProperties.acl option w/ a custom value set via API\", function(done) {\n            var uploader = new qq.s3.FineUploaderBasic({\n                    request: typicalRequestOption,\n                    signature: v2SignatureOption,\n                    objectProperties: {\n                        acl: \"public-read\"\n                    }\n                }\n            );\n\n            uploader.setAcl(\"test-acl\", 0);\n\n            startTypicalTest(uploader, function(signatureRequest, policyDoc, uploadRequest, conditions) {\n                var uploadRequestParams;\n\n                assert.equal(conditions.acl, \"test-acl\");\n                signatureRequest.respond(200, null, JSON.stringify({policy: \"thepolicy\", signature: \"thesignature\"}));\n\n                uploadRequestParams = uploadRequest.requestBody.fields;\n                assert.equal(uploadRequestParams.acl, \"test-acl\");\n\n                done();\n            });\n        });\n\n        it(\"respects the objectProperties.reducedRedundancy option w/ a value of true\", function(done) {\n            var uploader = new qq.s3.FineUploaderBasic({\n                    request: typicalRequestOption,\n                    signature: v2SignatureOption,\n                    objectProperties: {\n                        reducedRedundancy: true\n                    }\n                }\n            );\n\n            startTypicalTest(uploader, function(signatureRequest, policyDoc, uploadRequest, conditions) {\n                var uploadRequestParams;\n\n                assert.equal(conditions[qq.s3.util.REDUCED_REDUNDANCY_PARAM_NAME], qq.s3.util.REDUCED_REDUNDANCY_PARAM_VALUE);\n                signatureRequest.respond(200, null, JSON.stringify({policy: \"thepolicy\", signature: \"thesignature\"}));\n\n                uploadRequestParams = uploadRequest.requestBody.fields;\n                assert.equal(uploadRequestParams[qq.s3.util.REDUCED_REDUNDANCY_PARAM_NAME], qq.s3.util.REDUCED_REDUNDANCY_PARAM_VALUE);\n\n                done();\n            });\n        });\n\n        it(\"respects the objectProperties.serverSideEncryption option w/ a value of true\", function(done) {\n            var uploader = new qq.s3.FineUploaderBasic({\n                    request: typicalRequestOption,\n                    signature: v2SignatureOption,\n                    objectProperties: {\n                        serverSideEncryption: true\n                    }\n                }\n            );\n\n            startTypicalTest(uploader, function(signatureRequest, policyDoc, uploadRequest, conditions) {\n                var uploadRequestParams;\n\n                assert.equal(conditions[qq.s3.util.SERVER_SIDE_ENCRYPTION_PARAM_NAME], qq.s3.util.SERVER_SIDE_ENCRYPTION_PARAM_VALUE);\n                signatureRequest.respond(200, null, JSON.stringify({policy: \"thepolicy\", signature: \"thesignature\"}));\n\n                uploadRequestParams = uploadRequest.requestBody.fields;\n                assert.equal(uploadRequestParams[qq.s3.util.SERVER_SIDE_ENCRYPTION_PARAM_NAME], qq.s3.util.SERVER_SIDE_ENCRYPTION_PARAM_VALUE);\n\n                done();\n            });\n        });\n\n        it(\"respects custom headers to be sent with signature request\", function(done) {\n            var customHeader = {\"test-header-name\": \"test-header-value\"},\n                customSignatureOptions = qq.extend({}, v2SignatureOption),\n                uploader = new qq.s3.FineUploaderBasic({\n                    request: typicalRequestOption,\n                    signature: qq.extend(customSignatureOptions, {customHeaders: customHeader})\n                }\n            );\n\n            startTypicalTest(uploader, function(signatureRequest, policyDoc, uploadRequest, conditions) {\n                assert.equal(signatureRequest.requestHeaders[\"test-header-name\"], customHeader[\"test-header-name\"]);\n\n                done();\n            });\n        });\n\n        it(\"Sends uploadSuccess request after upload succeeds.  Also respects call to setUploadSuccessEndpoint method.\", function(done) {\n            var uploadSuccessUrl = \"/upload/success\",\n                uploadSuccessParams = {\"test-param-name\": \"test-param-value\"},\n                uploadSuccessHeaders = {\"test-header-name\": \"test-header-value\"},\n                uploader = new qq.s3.FineUploaderBasic({\n                    request: typicalRequestOption,\n                    signature: v2SignatureOption,\n                    uploadSuccess: {\n                        endpoint: \"foo/bar\",\n                        params: uploadSuccessParams,\n                        customHeaders: uploadSuccessHeaders\n                    }\n                }\n            );\n\n            uploader.setUploadSuccessEndpoint(uploadSuccessUrl);\n            uploader.setParams({foo: \"bar\"});\n\n            startTypicalTest(uploader, function(signatureRequest, policyDoc, uploadRequest) {\n                var uploadSuccessRequest, uploadSuccessRequestParsedBody;\n\n                signatureRequest.respond(200, null, JSON.stringify({policy: \"thepolicy\", signature: \"thesignature\"}));\n                uploadRequest.respond(200, {ETag: \"123\"}, null);\n\n                assert.equal(fileTestHelper.getRequests().length, 3, \"Wrong # of requests\");\n                uploadSuccessRequest = fileTestHelper.getRequests()[2];\n                uploadSuccessRequestParsedBody = purl(\"http://test.com?\" + uploadSuccessRequest.requestBody).param();\n                assert.equal(uploadSuccessRequest.url, uploadSuccessUrl);\n                assert.equal(uploadSuccessRequest.method, \"POST\");\n                assert.equal(uploadSuccessRequest.requestHeaders[\"Content-Type\"].indexOf(\"application/x-www-form-urlencoded\"), 0);\n                assert.equal(uploadSuccessRequest.requestHeaders[\"test-header-name\"], uploadSuccessHeaders[\"test-header-name\"]);\n                assert.equal(uploadSuccessRequestParsedBody[\"test-param-name\"], uploadSuccessParams[\"test-param-name\"]);\n                assert.equal(uploadSuccessRequestParsedBody.foo, \"bar\");\n                assert.equal(uploadSuccessRequestParsedBody.key, uploader.getKey(0));\n                assert.equal(uploadSuccessRequestParsedBody.uuid, uploader.getUuid(0));\n                assert.equal(uploadSuccessRequestParsedBody.name, uploader.getName(0));\n                assert.equal(uploadSuccessRequestParsedBody.bucket, testBucketName);\n                assert.equal(uploadSuccessRequestParsedBody.etag, \"123\");\n\n                uploadSuccessRequest.respond(200, null, null);\n                assert.equal(uploader.getUploads()[0].status, qq.status.UPLOAD_SUCCESSFUL);\n\n                done();\n            });\n        });\n\n        it(\"Declares an upload as a failure if uploadSuccess response indicates a problem with the file.  Also tests uploadSuccessRequest endpoint option.\", function(done) {\n            var uploadSuccessUrl = \"/upload/success\",\n                uploader = new qq.s3.FineUploaderBasic({\n                    request: typicalRequestOption,\n                    signature: v2SignatureOption,\n                    uploadSuccess: {\n                        endpoint: uploadSuccessUrl\n                    }\n                }\n            );\n\n            startTypicalTest(uploader, function(signatureRequest, policyDoc, uploadRequest, conditions) {\n                var uploadSuccessRequest;\n\n                signatureRequest.respond(200, null, JSON.stringify({policy: \"thepolicy\", signature: \"thesignature\"}));\n                uploadRequest.respond(200, null, null);\n\n                uploadSuccessRequest = fileTestHelper.getRequests()[2];\n                assert.equal(uploadSuccessRequest.url, uploadSuccessUrl);\n                uploadSuccessRequest.respond(200, null, JSON.stringify({success: false}));\n                assert.equal(uploader.getUploads()[0].status, qq.status.UPLOAD_FAILED);\n\n                done();\n            });\n        });\n\n        it(\"Allows upload success to be sent as something other than a POST.\", function(done) {\n            var uploadSuccessUrl = \"/upload/success\",\n                uploader = new qq.s3.FineUploaderBasic({\n                    request: typicalRequestOption,\n                    signature: v2SignatureOption,\n                    uploadSuccess: {\n                        endpoint: uploadSuccessUrl,\n                        method: \"PUT\"\n                    }\n                }\n            );\n\n            startTypicalTest(uploader, function(signatureRequest, policyDoc, uploadRequest, conditions) {\n                var uploadSuccessRequest, uploadSuccessRequestParsedBody;\n\n                signatureRequest.respond(200, null, JSON.stringify({policy: \"thepolicy\", signature: \"thesignature\"}));\n                uploadRequest.respond(200, null, null);\n\n                uploadSuccessRequest = fileTestHelper.getRequests()[2];\n                assert.equal(uploadSuccessRequest.method, \"PUT\");\n                uploadSuccessRequest.respond(200, null, null);\n                assert.equal(uploader.getUploads()[0].status, qq.status.UPLOAD_SUCCESSFUL);\n\n                done();\n            });\n        });\n    });\n}\n"
  },
  {
    "path": "test/unit/s3/util.js",
    "content": "/* globals describe, beforeEach, qq, assert, it, purl */\ndescribe(\"s3/util.js\", function () {\n    \"use strict\";\n\n    describe(\"getBucket\", function () {\n        it(\"extract bucket from an accepted S3 endpoint\", function () {\n            var endpointsAndBuckets = {\n                \"http://foo.s3.amazonaws.com\": \"foo\",\n                \"foo.s3.amazonaws.com\": \"foo\",\n\n                \"http://foo_bar.s3.amazonaws.com\": \"foo_bar\",\n                \"https://foo_bar.s3.amazonaws.com\": \"foo_bar\",\n                \"foo_bar.s3.amazonaws.com\": \"foo_bar\",\n\n                \"http://foo.s3-ap-northeast-1.amazonaws.com\": \"foo\",\n                \"foo.s3-ap-northeast-1.amazonaws.com\": \"foo\",\n\n                \"https://foo.s3.amazonaws.com\": \"foo\",\n                \"https://foo.s3-ap-northeast-1.amazonaws.com\": \"foo\",\n\n                \"http://foo-bar.s3.amazonaws.com\": \"foo-bar\",\n                \"foo-bar.s3.amazonaws.com\": \"foo-bar\",\n\n                \"http://foo-bar.s3-northeast-1.amazonaws.com\": \"foo-bar\",\n                \"foo-bar.s3-northeast-1.amazonaws.com\": \"foo-bar\",\n\n                \"http://foo.bar.s3.amazonaws.com\": \"foo.bar\",\n                \"foo.bar.s3.amazonaws.com\": \"foo.bar\",\n\n                \"http://foo.bar.s3-northeast-1.amazonaws.com\": \"foo.bar\",\n                \"foo.bar.s3-northeast-1.amazonaws.com\": \"foo.bar\",\n\n                \"http://s3.amazonaws.com/foo\": \"foo\",\n                \"https://s3.amazonaws.com/foo\": \"foo\",\n                \"s3.amazonaws.com/foo\": \"foo\",\n\n                \"http://s3.amazonaws.com/foo-bar\": \"foo-bar\",\n                \"https://s3.amazonaws.com/foo-bar\": \"foo-bar\",\n                \"s3.amazonaws.com/foo-bar\": \"foo-bar\",\n\n                \"http://s3.amazonaws.com/foo.bar.com\": \"foo.bar.com\",\n                \"https://s3.amazonaws.com/foo.bar.com\": \"foo.bar.com\",\n                \"s3.amazonaws.com/foo.bar.com\": \"foo.bar.com\",\n\n                \"http://s3.amazonaws.com/foo_bar_com\": \"foo_bar_com\",\n                \"https://s3.amazonaws.com/foo_bar_com\": \"foo_bar_com\",\n                \"s3.amazonaws.com/foo_bar_com\": \"foo_bar_com\",\n\n                \"http://foo.bar.com\": \"foo.bar.com\",\n                \"https://foo.bar.com\": \"foo.bar.com\",\n                \"foo.bar.com\": \"foo.bar.com\",\n\n                \"http://foo.bar.com/\": \"foo.bar.com\",\n                \"https://foo.bar.com/\": \"foo.bar.com\",\n                \"foo.bar.com/\": \"foo.bar.com\",\n\n                \"http://foo_bar.example.com/\": \"foo_bar.example.com\",\n                \"https://foo_bar.example.com/\": \"foo_bar.example.com\",\n                \"foo_bar.example.com/\": \"foo_bar.example.com\"\n\n            };\n\n            $.each(endpointsAndBuckets, function(endpoint, bucket) {\n                var extractedBucket = qq.s3.util.getBucket(endpoint);\n                assert.equal(extractedBucket, bucket, \"Failed to extract bucket from \" + endpoint);\n            });\n        });\n    });\n\n    describe(\"enforceSizeLimits\", function () {\n        var policy;\n\n        beforeEach(function () {\n            policy = {conditions: []};\n        });\n\n        it(\"Only add content-length-range param if necessary\", function () {\n            qq.s3.util.enforceSizeLimits(policy, 0, 0);\n            assert.ok(policy.conditions[0] === undefined);\n        });\n\n        it(\"non-zero min and max\", function () {\n            qq.s3.util.enforceSizeLimits(policy, 100, 102);\n            assert.equal(policy.conditions[0][1], \"100\");\n            assert.equal(policy.conditions[0][2], \"102\");\n        });\n\n        it(\"zero min, non-zero max\", function () {\n            qq.s3.util.enforceSizeLimits(policy, 0, 100);\n            assert.equal(policy.conditions[0][1], \"0\");\n            assert.equal(policy.conditions[0][2], \"100\");\n        });\n\n        it(\"non-zero min, zero max\", function () {\n            qq.s3.util.enforceSizeLimits(policy, 100, 0);\n            assert.equal(policy.conditions[0][1], \"100\");\n            assert.equal(policy.conditions[0][2], \"9007199254740992\");\n        });\n    });\n\n    describe(\"getSuccessRedirectAbsoluteUrl\", function() {\n        var purlUrl, protocol, host, dir;\n\n        beforeEach(function() {\n            purlUrl = purl(window.location.href);\n            protocol = purlUrl.attr(\"protocol\");\n            host = purlUrl.attr(\"host\") + \":\" + purlUrl.attr(\"port\");\n            dir = purlUrl.attr(\"directory\");\n        });\n\n        it(\"relative url input\", function() {\n            var derivedAbsoluteUrl = qq.s3.util.getSuccessRedirectAbsoluteUrl(\"server/upload\");\n            assert.equal(derivedAbsoluteUrl, protocol + \"://\" + host + dir + \"server/upload\");\n        });\n\n        it(\"relative url input - root\", function() {\n            var derivedAbsoluteUrl = qq.s3.util.getSuccessRedirectAbsoluteUrl(\"/server/upload\");\n            assert.equal(derivedAbsoluteUrl, protocol + \"://\" + host + \"/server/upload\");\n        });\n\n        it (\"absolute url input\", function() {\n            assert.equal(qq.s3.util.getSuccessRedirectAbsoluteUrl(\"http://1.2.3.4:8080/foo/bar\"), \"http://1.2.3.4:8080/foo/bar\");\n        });\n    });\n\n    describe(\"parseIframeResponse\", function() {\n        it(\"invalid iframe location\", function() {\n            var fakeIframe = {\n                contentDocument: {\n                    location: {\n                        search: \"foo=bar\"\n                    }\n                }\n            };\n\n            assert.ok(qq.s3.util.parseIframeResponse(fakeIframe) === undefined);\n        });\n\n        it(\"valid iframe location\", function() {\n            var fakeIframe = {\n                    contentDocument: {\n                        location: {\n                            search: \"bucket=123&key=456&etag=%22789%22\"\n                        }\n                    }\n                },\n                response;\n\n            response = qq.s3.util.parseIframeResponse(fakeIframe);\n\n            assert.equal(response.bucket, \"123\");\n            assert.equal(response.key, \"456\");\n            assert.equal(response.etag, \"789\");\n        });\n    });\n    \n    describe(\"uriEscapePath\",function(){\n        it(\"encodes params following s3 directives\",function(){\n            assert.equal(qq.s3.util.uriEscapePath(\"pippo/pluto e topolino.jpg\"),\"pippo/pluto%20e%20topolino.jpg\");\n            assert.equal(qq.s3.util.uriEscapePath(\"pippo/pluto & mickey+mouse.jpg\"),\"pippo/pluto%20%26%20mickey%2Bmouse.jpg\");\n            assert.equal(qq.s3.util.uriEscapePath(\"pluto & àòè.jpg\"),\"pluto%20%26%20a%CC%80o%CC%80e%CC%80.jpg\");\n            assert.equal(qq.s3.util.uriEscapePath(\"pluto & micke#22.jpg\"),\"pluto%20%26%20micke%2322.jpg\");\n            assert.equal(qq.s3.util.uriEscapePath(\"pluto_lkjhàò=23£\"),\"pluto_lkjha%CC%80o%CC%80%3D23%C2%A3\");\n        });\n    });\n\n    describe(\"encodeQueryStringParam\", function() {\n        it(\"handles params with spaces correctly\", function() {\n            assert.equal(qq.s3.util.encodeQueryStringParam(\"one two three\"), \"one+two+three\");\n            assert.equal(qq.s3.util.encodeQueryStringParam(\"&hi, how are you?\"), \"%26hi%2C+how+are+you%3F\");\n        });\n\n        it(\"handles params without spaces correctly\", function() {\n            assert.equal(qq.s3.util.encodeQueryStringParam(\"onetwothree\"), \"onetwothree\");\n            assert.equal(qq.s3.util.encodeQueryStringParam(\"&hi,howareyou?\"), \"%26hi%2Chowareyou%3F\");\n        });\n\n        it(\"follows RFC 3986 exactly\", function() {\n            assert.equal(qq.s3.util.encodeQueryStringParam(\"Are you 'Ray'?  If so, back to work (*now*)!\"),\n            \"Are+you+%27Ray%27%3F++If+so%2C+back+to+work+%28%2Anow%2A%29%21\");\n        });\n    });\n});\n\n"
  },
  {
    "path": "test/unit/scaling.js",
    "content": "/* globals describe, it, qq, assert, qqtest, helpme, pica */\nif (qq.supportedFeatures.scaling) {\n    describe(\"scaling module tests\", function() {\n        \"use strict\";\n\n        var fileTestHelper = helpme.setupFileTests(),\n            acknowledgeRequests = function() {\n                setTimeout(function() {\n                    qq.each(fileTestHelper.getRequests(), function(idx, req) {\n                        if (!req.ack) {\n                            req.ack = true;\n                            req.respond(200, null, JSON.stringify({success: true}));\n                        }\n                    });\n                }, 10);\n            },\n            typicalCustomResizer = function(resizeInfo) {\n                var promise = new qq.Promise();\n\n                pica.resizeCanvas(resizeInfo.sourceCanvas, resizeInfo.targetCanvas, {}, function() {\n                    promise.success();\n                });\n\n                return promise;\n            };\n\n        it(\"is disabled if no sizes are specified\", function() {\n            var scaler = new qq.Scaler({sizes: [], sendOriginal: true});\n\n            assert.ok(!scaler.enabled);\n        });\n\n        it(\"is disabled if the current browsers doesn't support the File API\", function() {\n            var scaler = new qq.Scaler({sizes: [{maxSize: 100}], sendOriginal: true}),\n                supportsPreviews = qq.supportedFeatures.imagePreviews;\n\n            assert.equal(scaler.enabled, supportsPreviews);\n        });\n\n        describe(\"generate records tests\", function() {\n            function runTestWithImage(includeOriginal) {\n                var sizes = [\n                        {\n                            name: \"small\",\n                            maxSize: 100,\n                            type: \"image/jpeg\"\n                        },\n                        {\n                            name: \"large\",\n                            maxSize: 300,\n                            type: \"image/jpeg\"\n                        },\n                        {\n                            name: \"medium\",\n                            maxSize: 200,\n                            type: \"image/jpeg\"\n                        }\n                    ],\n                    originalFile = {dummy: \"blob\", type: \"image/jpeg\"},\n                    scaler = new qq.Scaler(({sizes: sizes, sendOriginal: includeOriginal})),\n                    records = scaler.getFileRecords(\"originalUuid\", \"originalName.jpeg\", originalFile);\n\n                assert.equal(records.length, 4);\n\n                assert.equal(records[0].name, \"originalName (small).jpeg\");\n                assert.notEqual(records[0].uuid, \"originalUuid\");\n                assert.ok(records[0].blob instanceof qq.BlobProxy);\n\n                assert.equal(records[1].name, \"originalName (medium).jpeg\");\n                assert.notEqual(records[1].uuid, \"originalUuid\");\n                assert.ok(records[1].blob instanceof qq.BlobProxy);\n\n                assert.equal(records[2].name, \"originalName (large).jpeg\");\n                assert.notEqual(records[2].uuid, \"originalUuid\");\n                assert.ok(records[2].blob instanceof qq.BlobProxy);\n\n                assert.equal(records[3].name, \"originalName.jpeg\");\n                assert.equal(records[3].uuid, \"originalUuid\");\n                assert.equal(records[3].size, originalFile.size);\n                assert.equal(records[3].blob, includeOriginal ? originalFile : null);\n            }\n\n            function runTestWithNonImage(includeOriginal) {\n                var sizes = [\n                        {\n                            name: \"small\",\n                            maxSize: 100,\n                            type: \"image/jpeg\"\n                        },\n                        {\n                            name: \"large\",\n                            maxSize: 300,\n                            type: \"image/jpeg\"\n                        }\n                    ],\n                    originalFile = {dummy: \"blob\", type: \"text/plain\"},\n                    scaler = new qq.Scaler(({sizes: sizes, sendOriginal: includeOriginal}), function dummyLogger(){}),\n                    records = scaler.getFileRecords(\"originalUuid\", \"plain.txt\", originalFile);\n\n                assert.equal(records.length, 1);\n\n                assert.equal(records[0].name, \"plain.txt\");\n                assert.equal(records[0].uuid, \"originalUuid\");\n                assert.equal(records[0].blob, originalFile);\n            }\n\n            it(\"creates properly ordered and constructed file records on demand\", function() {\n                runTestWithImage(true);\n            });\n\n            it(\"creates properly ordered and constructed file records on demand (ignoring original)\", function() {\n                runTestWithImage(false);\n            });\n\n            it(\"ensure non-images are not ignored\", function() {\n                runTestWithNonImage(true);\n            });\n\n            it(\"ensure non-images are not ignored, even if `sendOriginal` is set to false\", function() {\n                runTestWithNonImage(false);\n            });\n        });\n\n        it(\"handles extensionless filenames correctly\", function() {\n            var sizes = [\n                    {\n                        name: \"small\",\n                        maxSize: 100\n                    }\n                ],\n                scaler = new qq.Scaler(({sizes: sizes, sendOriginal: true})),\n                records = scaler.getFileRecords(\"originalUuid\", \"originalName\", {type: \"image/jpeg\"});\n\n            assert.equal(records[0].name, \"originalName (small)\");\n        });\n\n        it(\"handles empty name property correctly\", function() {\n            var sizes = [\n                    {\n                        maxSize: 100\n                    },\n                    {\n                        name: null,\n                        maxSize: 101\n                    },\n                    {\n                        name: \"\",\n                        maxSize: 102\n                    }\n                ],\n                scaler = new qq.Scaler(({sizes: sizes})),\n                records = scaler.getFileRecords(\"originalUuid\", \"originalName\", {type: \"image/jpeg\"});\n\n            assert.equal(records[0].name, \"originalName\");\n            assert.equal(records[1].name, \"originalName\");\n            assert.equal(records[2].name, \"originalName\");\n        });\n\n        describe(\"generates simple scaled image tests\", function() {\n            function runScaleTest(orient, customResizer, done) {\n                var scalerContext = qq.extend({}, qq.Scaler.prototype),\n                    scale = qq.bind(qq.Scaler.prototype._generateScaledImage, scalerContext);\n\n                qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function(blob) {\n                    scale({\n                        maxSize: 50,\n                        orient: orient,\n                        log: function(){},\n                        customResizeFunction: customResizer\n                    }, blob).then(function(scaledBlob) {\n                        var URL = window.URL && window.URL.createObjectURL ? window.URL :\n                                  window.webkitURL && window.webkitURL.createObjectURL ? window.webkitURL :\n                                  null,\n                            img = document.createElement(\"img\");\n\n\n                        assert.ok(qq.isBlob(scaledBlob));\n\n                        img.onload = function() {\n                            assert.ok(this.width <= 50);\n                            assert.ok(this.height <= 50);\n                            done();\n                        };\n\n                        img.onerror = function() {\n                            assert.fail(null, null, \"Image failed to render!\");\n                        };\n\n                        img.src = URL.createObjectURL(scaledBlob);\n                    });\n                });\n            }\n\n            describe(\"using built-in resizer code\", function() {\n                it(\"generates a properly scaled & oriented image for a reference image\", function(done) {\n                    runScaleTest(true, null, done);\n                });\n\n                it(\"generates a properly scaled image for a reference image\", function(done) {\n                    runScaleTest(false, null, done);\n                });\n            });\n\n            if (!qq.ios()) {\n                describe(\"using third-party resizer code\", function() {\n                    it(\"generates a properly scaled & oriented image for a reference image\", function(done) {\n                        runScaleTest(true, typicalCustomResizer, done);\n                    });\n\n                    it(\"generates a properly scaled image for a reference image\", function(done) {\n                        runScaleTest(false, typicalCustomResizer, done);\n                    });\n                });\n            }\n        });\n\n\n        it(\"renames the scaled files only if their MIME type differs from the reference file\", function(done) {\n            assert.expect(8, done);\n\n            var sizes = [\n                    {\n                        name: \"small\",\n                        maxSize: 100,\n                        type: \"image/jpeg\"\n                    },\n                    {\n                        name: \"large\",\n                        maxSize: 300\n                    },\n                    {\n                        name: \"medium\",\n                        maxSize: 200,\n                        type: \"image/bmp\"\n                    }\n                ],\n                scaler = new qq.Scaler(({sizes: sizes, sendOriginal: true}));\n\n            qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function(blob) {\n                var records = scaler.getFileRecords(\"originalUuid\", \"originalName.jpEg\", blob);\n\n                assert.equal(records[0].name, \"originalName (small).jpEg\");\n                assert.equal(records[1].name, \"originalName (medium).bmp\");\n                assert.equal(records[2].name, \"originalName (large).jpEg\");\n                assert.equal(records[3].name, \"originalName.jpEg\");\n\n                // leave extension-less file names alone\n                records = scaler.getFileRecords(\"originalUuid\", \"originalName\", blob);\n                assert.equal(records[0].name, \"originalName (small)\");\n                assert.equal(records[1].name, \"originalName (medium)\");\n                assert.equal(records[2].name, \"originalName (large)\");\n                assert.equal(records[3].name, \"originalName\");\n            });\n        });\n\n        it(\"by default, all output files are PNGs, unless the original file type is a JPEG\", function(done) {\n            assert.expect(6, done);\n\n            var sizes = [\n                    {\n                        name: \"small\",\n                        maxSize: 100\n                    }\n                ],\n                scaler = new qq.Scaler(({sizes: sizes, sendOriginal: true}));\n\n            qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function(up) {\n                qqtest.downloadFileAsBlob(\"star.png\", \"image/png\").then(function(star) {\n                    qqtest.downloadFileAsBlob(\"drop-background.gif\", \"image/gif\").then(function(drop) {\n                        var records = scaler.getFileRecords(\"uuid1\", \"up.jpeg\", up);\n                        assert.equal(records[0].name, \"up (small).jpeg\");\n                        assert.equal(records[1].name, \"up.jpeg\");\n\n                        records = scaler.getFileRecords(\"uuid2\", \"star.png\", star);\n                        assert.equal(records[0].name, \"star (small).png\");\n                        assert.equal(records[1].name, \"star.png\");\n\n\n                        records = scaler.getFileRecords(\"uuid3\", \"drop.gif\", drop);\n                        assert.equal(records[0].name, \"drop (small).png\");\n                        assert.equal(records[1].name, \"drop.gif\");\n                    });\n                });\n            });\n        });\n\n        describe(\"scaled files uploads (non-chunked, default options)\", function() {\n            function runTest(customResizer, done) {\n                assert.expect(39, done);\n\n                var referenceFileSize,\n                    sizes = [\n                        {\n                            name: \"small\",\n                            maxSize: 50\n                        },\n                        {\n                            name: \"medium\",\n                            maxSize: 400\n                        }\n                    ],\n                    expectedUploadCallbacks = [\n                        {id: 0, name: \"up (small).jpeg\"},\n                        {id: 1, name: \"up (medium).jpeg\"},\n                        {id: 2, name: \"up.jpeg\"},\n                        {id: 3, name: \"up2 (small).jpeg\"},\n                        {id: 4, name: \"up2 (medium).jpeg\"},\n                        {id: 5, name: \"up2.jpeg\"}\n                    ],\n                    actualUploadCallbacks = [],\n                    uploader = new qq.FineUploaderBasic({\n                        request: {endpoint: \"test/uploads\"},\n                        scaling: {\n                            sizes: sizes,\n                            customResizer: customResizer\n                        },\n                        callbacks: {\n                            onUpload: function(id, name) {\n                                assert.ok(uploader.getSize(id) > 0, \"Blob size is not greater than 0\");\n                                assert.ok(qq.isBlob(uploader.getFile(id)), \"file is not a Blob\");\n                                assert.equal(uploader.getFile(id).size, referenceFileSize);\n\n                                actualUploadCallbacks.push({id: id, name: name});\n                                setTimeout(function() {\n                                    var req = fileTestHelper.getRequests()[id],\n                                        parentUuid = req.requestBody.fields.qqparentuuid,\n                                        parentSize = req.requestBody.fields.qqparentsize,\n                                        parentId = uploader.getParentId(id),\n                                        file = req.requestBody.fields.qqfile;\n\n                                    assert.equal(file.type, \"image/jpeg\");\n\n                                    if (parentId !== null) {\n                                        assert.equal(parentUuid, uploader.getUuid(parentId));\n                                        assert.equal(parentSize, uploader.getSize(parentId));\n                                    }\n                                    else {\n                                        assert.equal(parentUuid, undefined);\n                                        assert.equal(parentSize, undefined);\n                                    }\n\n                                    req.respond(200, null, JSON.stringify({success: true}));\n                                }, 100);\n                            },\n                            onAllComplete: function(successful, failed) {\n                                assert.equal(successful.length, 6);\n                                assert.equal(failed.length, 0);\n                                assert.deepEqual(actualUploadCallbacks, expectedUploadCallbacks);\n                            }\n                        }\n                    });\n\n                qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function(blob) {\n                    fileTestHelper.mockXhr();\n                    referenceFileSize = blob.size;\n                    uploader.addFiles([{blob: blob, name: \"up.jpeg\"}, {blob: blob, name: \"up2.jpeg\"}]);\n                });\n            }\n\n            it(\"uploads as expected with internal resizer code\", function(done) {\n                runTest(null, done);\n            });\n\n            if (!qq.ios()) {\n                it(\"uploads as expected with third-party resizer code\", function (done) {\n                    runTest(typicalCustomResizer, done);\n                });\n            }\n        });\n\n        describe(\"jpeg to PNG conversion behavior\", function() {\n            function runTest(customResizer, done) {\n                assert.expect(4, done);\n\n                var expectedOutputTypes = [\n                        \"image/png\",\n                        \"image/png\",\n                        \"image/png\",\n                        \"image/gif\"\n                    ],\n                    sizes = [\n                        {\n                            name: \"small\",\n                            maxSize: 50\n                        }\n                    ],\n                    actualUploadCallbacks = [],\n                    uploader = new qq.FineUploaderBasic({\n                        request: {endpoint: \"test/uploads\"},\n                        scaling: {\n                            customResizer: customResizer,\n                            sizes: sizes\n                        },\n                        callbacks: {\n                            onUpload: function(id, name) {\n                                actualUploadCallbacks.push({id: id, name: name});\n                                setTimeout(function() {\n                                    var req = fileTestHelper.getRequests()[id],\n                                        file = req.requestBody.fields.qqfile;\n\n                                    assert.equal(file.type, expectedOutputTypes[id]);\n\n                                    req.respond(200, null, JSON.stringify({success: true}));\n                                }, 100);\n                            }\n                        }\n                    });\n\n                qqtest.downloadFileAsBlob(\"star.png\", \"image/png\").then(function(star) {\n                    qqtest.downloadFileAsBlob(\"drop-background.gif\", \"image/gif\").then(function(drop) {\n                        fileTestHelper.mockXhr();\n                        uploader.addFiles([{blob: star, name: \"star.png\"}, {blob: drop, name: \"drop.gif\"}]);\n                    });\n                });\n            }\n\n            it(\"behaves as expected with internal resizer\", function(done) {\n                runTest(null, done);\n            });\n\n            if (!qq.ios()) {\n                it(\"behaves as expected with custom resizer\", function (done) {\n                    runTest(typicalCustomResizer, done);\n                });\n            }\n        });\n\n        it(\"uploads scaled files as expected: chunked, default options\", function(done) {\n            assert.expect(15, done);\n\n            var referenceFileSize,\n                sizes = [\n                    {\n                        name: \"medium\",\n                        maxSize: 400,\n                        type: \"image/jpeg\"\n                    }\n                ],\n                expectedUploadCallbacks = [\n                    {id: 0, name: \"up (medium).jpeg\"},\n                    {id: 1, name: \"up.jpeg\"},\n                    {id: 2, name: \"up2 (medium).jpeg\"},\n                    {id: 3, name: \"up2.jpeg\"}\n                ],\n                actualUploadCallbacks = [],\n                uploader = new qq.FineUploaderBasic({\n                    request: {endpoint: \"test/uploads\"},\n                    chunking: {\n                        enabled: true,\n                        partSize: 500\n                    },\n                    scaling: {\n                        sizes: sizes\n                    },\n                    callbacks: {\n                        onUploadChunk: function(id) {\n                            acknowledgeRequests();\n                        },\n                        onUpload: function(id, name) {\n                            actualUploadCallbacks.push({id: id, name: name});\n\n                            assert.ok(uploader.getSize(id) > 0);\n                            assert.ok(qq.isBlob(uploader.getFile(id)));\n                            assert.equal(uploader.getFile(id).size, referenceFileSize);\n                        },\n                        onAllComplete: function(successful, failed) {\n                            assert.equal(successful.length, 4);\n                            assert.equal(failed.length, 0);\n                            assert.deepEqual(actualUploadCallbacks, expectedUploadCallbacks);\n                        }\n                    }\n                });\n\n            qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function(blob) {\n                fileTestHelper.mockXhr();\n                referenceFileSize = blob.size;\n                uploader.addFiles([{blob: blob, name: \"up.jpeg\"}, {blob: blob, name: \"up2.jpeg\"}]);\n            });\n        });\n\n        it(\"skips the scaling workflow for files that cannot be scaled\", function(done) {\n            assert.expect(7, done);\n\n            var referenceFileSize,\n                sizes = [\n                    {\n                        name: \"small\",\n                        maxSize: 50,\n                        type: \"image/jpeg\"\n                    }\n                ],\n                expectedUploadCallbacks = [\n                    {id: 0, name: \"one.txt\"},\n                    {id: 1, name: \"two.txt\"}\n                ],\n                actualUploadCallbacks = [],\n                uploader = new qq.FineUploaderBasic({\n                    request: {endpoint: \"test/uploads\"},\n                    scaling: {\n                        sizes: sizes\n                    },\n                    callbacks: {\n                        onUpload: function(id, name) {\n                            assert.ok(qq.isBlob(uploader.getFile(id)));\n                            assert.equal(uploader.getFile(id).size, referenceFileSize);\n                            actualUploadCallbacks.push({id: id, name: name});\n                            acknowledgeRequests();\n                        },\n                        onAllComplete: function(successful, failed) {\n                            assert.equal(successful.length, 2);\n                            assert.equal(failed.length, 0);\n                            assert.deepEqual(actualUploadCallbacks, expectedUploadCallbacks);\n                        }\n                    }\n                });\n\n            qqtest.downloadFileAsBlob(\"simpletext.txt\", \"text/plain\").then(function(blob) {\n                fileTestHelper.mockXhr();\n                referenceFileSize = blob.size;\n                uploader.addFiles([{blob: blob, name: \"one.txt\"}, {blob: blob, name: \"two.txt\"}]);\n            });\n        });\n\n        it(\"skips the scaling workflow for files that cannot be scaled but still uploads scaled versions where possible\", function(done) {\n            assert.expect(6, done);\n\n            var sizes = [\n                    {\n                        name: \"small\",\n                        maxSize: 50,\n                        type: \"image/jpeg\"\n                    }\n                ],\n                expectedUploadCallbacks = [\n                    {id: 0, name: \"one.txt\"},\n                    {id: 1, name: \"two (small).jpg\"},\n                    {id: 2, name: \"two.jpg\"}\n                ],\n                actualUploadCallbacks = [],\n                uploader = new qq.FineUploaderBasic({\n                    request: {endpoint: \"test/uploads\"},\n                    scaling: {\n                        sizes: sizes\n                    },\n                    callbacks: {\n                        onUpload: function(id, name) {\n                            assert.ok(qq.isBlob(uploader.getFile(id)));\n\n                            actualUploadCallbacks.push({id: id, name: name});\n                            acknowledgeRequests();\n                        },\n                        onAllComplete: function(successful, failed) {\n                            assert.equal(successful.length, 3);\n                            assert.equal(failed.length, 0);\n                            assert.deepEqual(actualUploadCallbacks, expectedUploadCallbacks);\n                        }\n                    }\n                });\n\n            qqtest.downloadFileAsBlob(\"simpletext.txt\", \"text/plain\").then(function(textFile) {\n                qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function(jpegFile) {\n                    fileTestHelper.mockXhr();\n                    uploader.addFiles([{blob: textFile, name: \"one.txt\"}, {blob: jpegFile, name: \"two.jpg\"}]);\n                });\n            });\n        });\n\n        describe(\"generating a scaled Blob of the original file's type if the requested type is not specified or is not valid\", function() {\n            function runTest(customResizer, done) {\n                assert.expect(7, done);\n\n                var sizes = [\n                        {\n                            name: \"one\",\n                            maxSize: 100,\n                            type: \"image/jpeg\"\n                        },\n                        {\n                            name: \"two\",\n                            maxSize: 101,\n                            type: \"image/blah\"\n                        },\n                        {\n                            name: \"three\",\n                            maxSize: 102\n                        }\n                    ],\n                    expectedUploadCallbacks = [\n                        {id: 0, name: \"test (one).jpeg\"},\n                        {id: 1, name: \"test (two).png\"},\n                        {id: 2, name: \"test (three).png\"},\n                        {id: 3, name: \"test.png\"}\n                    ],\n                    expectedScaledBlobType = [\n                        \"image/jpeg\",\n                        \"image/png\",\n                        \"image/png\",\n                        \"image/png\"\n                    ],\n                    actualUploadCallbacks = [],\n                    uploader = new qq.FineUploaderBasic({\n                        request: {endpoint: \"test/uploads\"},\n                        scaling: {\n                            customResizer: customResizer,\n                            defaultType: \"image/png\",\n                            sizes: sizes\n                        },\n                        callbacks: {\n                            onUpload: function(id, name) {\n                                actualUploadCallbacks.push({id: id, name: name});\n                                setTimeout(function() {\n                                    var req = fileTestHelper.getRequests()[id],\n                                        actualType = req.requestBody.fields.qqfile.type;\n\n                                    assert.equal(actualType, expectedScaledBlobType[id], \"(\" + id + \") Scaled blob type (\" + actualType + \")  is incorrect.  Expected \" + expectedScaledBlobType[id]);\n                                    req.respond(200, null, JSON.stringify({success: true}));\n                                }, 10);\n                            },\n                            onAllComplete: function(successful, failed) {\n                                assert.equal(successful.length, 4);\n                                assert.equal(failed.length, 0);\n                                assert.deepEqual(actualUploadCallbacks, expectedUploadCallbacks);\n                            }\n                        }\n                    });\n\n                qqtest.downloadFileAsBlob(\"star.png\", \"image/png\").then(function(blob) {\n                    fileTestHelper.mockXhr();\n                    uploader.addFiles({blob: blob, name: \"test.png\"});\n                });\n            }\n\n            it(\"behaves as expected with internal resizer\", function(done) {\n                runTest(null, done);\n            });\n\n            if (!qq.ios()) {\n                it(\"behaves as expected with custom resizer\", function (done) {\n                    runTest(typicalCustomResizer, done);\n                });\n            }\n        });\n\n        it(\"uploads scaled files as expected, excluding the original: non-chunked, default options\", function(done) {\n            var referenceFileSize,\n                sizes = [\n                    {\n                        name: \"small\",\n                        maxSize: 50,\n                        type: \"image/jpeg\"\n                    },\n                    {\n                        name: \"medium\",\n                        maxSize: 400,\n                        type: \"image/jpeg\"\n                    }\n                ],\n                expectedUploadCallbacks = [\n                    {id: 0, name: \"up (small).jpeg\"},\n                    {id: 1, name: \"up (medium).jpeg\"},\n                    {id: 3, name: \"up2 (small).jpeg\"},\n                    {id: 4, name: \"up2 (medium).jpeg\"}\n                ],\n                actualUploadCallbacks = [],\n                uploader = new qq.FineUploaderBasic({\n                    request: {endpoint: \"test/uploads\"},\n                    scaling: {\n                        sendOriginal: false,\n                        sizes: sizes\n                    },\n                    callbacks: {\n                        onUpload: function(id, name) {\n                            assert.ok(uploader.getSize(id) > 0);\n                            assert.ok(qq.isBlob(uploader.getFile(id)));\n                            assert.equal(uploader.getFile(id).size, referenceFileSize);\n\n                            actualUploadCallbacks.push({id: id, name: name});\n                            setTimeout(function() {\n                                var requestIndex = (function() {\n                                        if (id > 2) {\n                                            return id-1;\n                                        }\n                                        return id;\n                                    }()),\n                                    req = fileTestHelper.getRequests()[requestIndex],\n                                    blob = req.requestBody.fields.qqfile,\n                                    parentUuid = req.requestBody.fields.qqparentuuid,\n                                    parentSize = req.requestBody.fields.qqparentsize,\n                                    parentId = uploader.getParentId(id),\n                                    file = req.requestBody.fields.qqfile;\n\n                                if (parentId !== null) {\n                                    assert.equal(parentUuid, uploader.getUuid(parentId));\n                                    assert.equal(parentSize, uploader.getSize(parentId));\n                                }\n                                else {\n                                    assert.equal(parentUuid, undefined);\n                                    assert.equal(parentSize, undefined);\n                                }\n\n\n                                new qq.Exif(blob, function(){}).parse().then(function(tags) {\n                                    // Some versions of Safari insert some EXIF data back into the scaled version\n                                    if (!qq.safari() || tags.Orientation) {\n                                        assert.fail(null, null, id + \" contains EXIF data, unexpectedly\");\n                                    }\n                                    else {\n                                        assert.ok(true);\n                                    }\n                                }, function() {\n                                    assert.ok(true);\n                                });\n\n\n                                req.respond(200, null, JSON.stringify({success: true}));\n                            }, 10);\n                        },\n                        onAllComplete: function(successful, failed) {\n                            assert.equal(uploader.getUploads({id: 2}).status, qq.status.REJECTED);\n                            assert.equal(uploader.getUploads({id: 5}).status, qq.status.REJECTED);\n                            assert.equal(successful.length, 4);\n                            assert.equal(failed.length, 0);\n                            assert.deepEqual(actualUploadCallbacks, expectedUploadCallbacks);\n                            done();\n                        }\n                    }\n                });\n\n            qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function(blob) {\n                fileTestHelper.mockXhr();\n                referenceFileSize = blob.size;\n                uploader.addFiles([{blob: blob, name: \"up.jpeg\"}, {blob: blob, name: \"up2.jpeg\"}]);\n            });\n        });\n\n        it(\"does not attempt to upload scaled file groups that fail validation\", function(done) {\n            assert.expect(9, done);\n\n            var referenceFileSize,\n                sizes = [\n                    {\n                        name: \"small\",\n                        maxSize: 50\n                    },\n                    {\n                        name: \"medium\",\n                        maxSize: 400\n                    }\n                ],\n                expectedUploadCallbacks = [\n                    {id: 3, name: \"star (small).png\"},\n                    {id: 4, name: \"star (medium).png\"},\n                    {id: 5, name: \"star.png\"}\n                ],\n                actualUploadCallbacks = [],\n                uploader = new qq.FineUploaderBasic({\n                    request: {endpoint: \"test/uploads\"},\n                    validation: {\n                        sizeLimit: 856,\n                        stopOnFirstInvalidFile: false\n                    },\n                    scaling: {\n                        sizes: sizes\n                    },\n                    callbacks: {\n                        onUpload: function(id, name) {\n                            assert.ok(uploader.getSize(id) > 0);\n                            assert.ok(qq.isBlob(uploader.getFile(id)));\n\n                            actualUploadCallbacks.push({id: id, name: name});\n                            acknowledgeRequests();\n                        },\n                        onAllComplete: function(successful, failed) {\n                            assert.equal(successful.length, 3);\n                            assert.equal(failed.length, 0);\n                            assert.deepEqual(actualUploadCallbacks, expectedUploadCallbacks);\n                        }\n                    }\n                });\n\n            qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function(up) {\n                qqtest.downloadFileAsBlob(\"star.png\", \"image/png\").then(function(star) {\n                    fileTestHelper.mockXhr();\n                    uploader.addFiles([{blob: up, name: \"up.jpg\"}, {blob: star, name: \"star.png\"}]);\n                });\n            });\n        });\n\n        describe(\"scaleImage API method tests\", function() {\n            function runTest(customResizer, done) {\n                assert.expect(6, done);\n\n                var referenceFileSize,\n                    uploader = new qq.FineUploaderBasic({\n                        request: {endpoint: \"test/uploads\"},\n                        callbacks: {\n                            onUpload: acknowledgeRequests,\n\n                            onAllComplete: function(successful, failed) {\n                                uploader.scaleImage(0, {customResizer: customResizer, maxSize: 10}).then(function(scaledBlob) {\n                                    assert.ok(qq.isBlob(scaledBlob));\n                                    assert.ok(scaledBlob.size < referenceFileSize);\n                                    assert.equal(scaledBlob.type, \"image/jpeg\");\n\n                                    new qq.Exif(scaledBlob, function(){}).parse().then(function(tags) {\n                                        assert.fail(null, null, \"scaled blob contains EXIF data, unexpectedly\");\n                                    }, function() {\n                                        assert.ok(true);\n                                    });\n                                });\n\n                                // not an image\n                                uploader.scaleImage(1, {customResizer: customResizer, maxSize: 10}).then(function() {},\n                                function() {\n                                    assert.ok(true);\n                                });\n\n                                //missing\n                                uploader.scaleImage(2, {customResizer: customResizer, maxSize: 10}).then(function() {},\n                                function() {\n                                    assert.ok(true);\n                                });\n                            }\n                        }\n                    });\n\n                qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function(up) {\n                    referenceFileSize = up.size;\n\n                    qqtest.downloadFileAsBlob(\"simpletext.txt\", \"text/plain\").then(function(text) {\n                        fileTestHelper.mockXhr();\n                        uploader.addFiles([{blob: up, name: \"up.jpg\"}, {blob: text, name: \"text.txt\"}]);\n                    });\n                });\n            }\n\n            it(\"return a scaled version of an existing image file, fail a request for a missing file, fail a request for a non-image file - internal resizer\", function(done) {\n                runTest(null, done);\n            });\n\n            it(\"return a scaled version of an existing image file, fail a request for a missing file, fail a request for a non-image file - custom resizer\", function(done) {\n                runTest(typicalCustomResizer, done);\n            });\n        });\n\n        describe(\"EXIF data inclusion in scaled images\", function() {\n            function runTest(customResizer, done) {\n                assert.expect(8, done);\n\n                var getReqFor = function (uuid) {\n                        var theReq;\n\n                        qq.each(fileTestHelper.getRequests(), function (idx, req) {\n                            if (req.requestBody.fields.qquuid === uuid) {\n                                theReq = req;\n                                return false;\n                            }\n                        });\n\n                        return theReq;\n                    },\n                    uploader = new qq.FineUploaderBasic({\n                        request: {endpoint: \"test/uploads\"},\n                        scaling: {\n                            customResizer: customResizer,\n                            includeExif: true,\n                            sizes: [{name: \"scaled\", maxSize: 50}]\n                        },\n                        callbacks: {\n                            onUpload: function (id) {\n                                setTimeout(function () {\n                                    var req = getReqFor(uploader.getUuid(id)),\n                                        blob = req.requestBody.fields.qqfile,\n                                        name = req.requestBody.fields.qqfilename;\n\n                                    assert.ok(qq.isBlob(blob));\n                                    new qq.Exif(blob, function () {\n                                    }).parse().then(function (tags) {\n                                        if (name.indexOf(\"left\") === 0) {\n                                            assert.equal(tags.Orientation, 6);\n                                        }\n                                        else {\n                                            assert.fail(null, null, name + \" contains EXIF data, unexpectedly\");\n                                        }\n                                    }, function () {\n                                        if (name.indexOf(\"star\") === 0) {\n                                            assert.ok(true);\n                                        }\n                                        else {\n                                            assert.fail(null, null, name + \" does not contains EXIF data, unexpectedly\");\n                                        }\n                                    });\n                                    req.respond(200, null, JSON.stringify({success: true}));\n                                }, 10);\n                            }\n                        }\n                    });\n\n                qqtest.downloadFileAsBlob(\"left.jpg\", \"image/jpeg\").then(function (left) {\n                    qqtest.downloadFileAsBlob(\"star.png\", \"image/png\").then(function (star) {\n                        fileTestHelper.mockXhr();\n                        uploader.addFiles([{blob: left, name: \"left.jpg\"}, {blob: star, name: \"star.png\"}]);\n                    });\n                });\n            }\n\n            it(\"includes EXIF data only if requested & appropriate - internal resizer\", function(done) {\n                runTest(null, done);\n            });\n\n            it(\"includes EXIF data only if requested & appropriate - custom resizer\", function(done) {\n                runTest(typicalCustomResizer, done);\n            });\n        });\n    });\n}\n"
  },
  {
    "path": "test/unit/session.js",
    "content": "/* globals describe, it, helpme, qq, assert, purl, beforeEach, $fixture */\ndescribe(\"file list initialization tests\", function() {\n    \"use strict\";\n\n    var fileHelper = helpme.setupFileTests(),\n        sessionEndpoint = \"/uploads/initial\",\n        thumbnailSrc = \"http://\" + window.location.hostname + \":4000/up.jpg\";\n\n\n    beforeEach(function() {\n        fileHelper.mockXhr();\n    });\n\n    it(\"adds valid items to the initial file list via API\", function() {\n        var uploader = new qq.FineUploaderBasic({\n            validation: {\n                itemLimit: 5\n            }\n        });\n\n\n        uploader.addInitialFiles([\n            {\n                name: \"up.jpg\",\n                uuid: \"123\",\n                size: 456\n            },\n            {\n                name: \"up2.jpg\",\n                uuid: \"abc\"\n            }\n        ]);\n\n        assert.equal(uploader.getUploads().length, 2, \"wrong number of pre-populated uploads recorded\");\n        assert.equal(uploader.getUploads({status: qq.status.UPLOAD_SUCCESSFUL}).length, 2, \"wrong status for one or more recorded files\");\n\n        assert.equal(uploader.getUuid(0), \"123\", \"123 UUID was not recorded\");\n        assert.equal(uploader.getUuid(1), \"abc\", \"abc UUID was not recorded\");\n\n        assert.equal(uploader.getSize(0), 456, \"wrong size for first file\");\n        assert.equal(uploader.getSize(1), -1, \"wrong size for second file\");\n\n        assert.equal(uploader.getName(0), \"up.jpg\", \"wrong name for first file\");\n        assert.equal(uploader.getName(1), \"up2.jpg\", \"wrong name for second file\");\n\n        assert.equal(uploader.getFile(0), null, \"unexpected return value for getFile\");\n        assert.equal(uploader.getFile(1), null, \"unexpected return value for getFile\");\n\n        assert.equal(uploader.getInProgress(), 0, \"unexpected getInProgress value\");\n        assert.equal(uploader.getNetUploads(), 2, \"unexpected getNetUploads value\");\n\n        assert.equal(uploader.getRemainingAllowedItems(), 3, \"wrong number of remaining allowed items\");\n    });\n\n    it(\"adds valid items to the initial file list via GET request\", function(done) {\n        var expectedSessionResponse = [\n                {\n                    name: \"up.jpg\",\n                    uuid: \"123\",\n                    size: 456\n                },\n                {\n                    name: \"up2.jpg\",\n                    uuid: \"abc\"\n                }\n            ],\n            uploader = new qq.FineUploaderBasic({\n                validation: {\n                    itemLimit: 5\n                },\n                session: {\n                    endpoint: sessionEndpoint\n                },\n                callbacks: {\n                    onSessionRequestComplete: function(response, success, xhr) {\n                        assert.deepEqual(response, expectedSessionResponse, \"unexpected callback response\");\n                        assert.ok(success, \"session request deemed failure\");\n                    }\n                }\n            }\n        ),\n            request;\n\n        setTimeout(function() {\n            request = fileHelper.getRequests()[0];\n\n            assert.equal(fileHelper.getRequests().length, 1, \"unexpected # of requests\");\n            assert.equal(request.method, \"GET\", \"wrong request method\");\n            assert.equal(purl(request.url).attr(\"path\"), sessionEndpoint, \"wrong session endpoint\");\n            /* jshint eqnull:true */\n            assert.ok(purl(request.url).param(\"qqtimestamp\") != null, \"cache buster query param missing\");\n\n            request.respond(200, null, JSON.stringify(expectedSessionResponse));\n\n            assert.equal(uploader.getUploads().length, 2, \"wrong number of pre-populated uploads recorded\");\n            assert.equal(uploader.getUploads({status: qq.status.UPLOAD_SUCCESSFUL}).length, 2, \"wrong status for one or more recorded files\");\n\n            assert.equal(uploader.getUuid(0), \"123\", \"123 UUID was not recorded\");\n            assert.equal(uploader.getUuid(1), \"abc\", \"abc UUID was not recorded\");\n\n            assert.equal(uploader.getSize(0), 456, \"wrong size for first file\");\n            assert.equal(uploader.getSize(1), -1, \"wrong size for second file\");\n\n            assert.equal(uploader.getName(0), \"up.jpg\", \"wrong name for first file\");\n            assert.equal(uploader.getName(1), \"up2.jpg\", \"wrong name for second file\");\n\n            assert.equal(uploader.getFile(0), null, \"unexpected return value for getFile\");\n            assert.equal(uploader.getFile(1), null, \"unexpected return value for getFile\");\n\n            assert.equal(uploader.getInProgress(), 0, \"unexpected getInProgress value\");\n            assert.equal(uploader.getNetUploads(), 2, \"unexpected getNetUploads value\");\n\n            assert.equal(uploader.getRemainingAllowedItems(), 3, \"wrong number of remaining allowed items\");\n\n            uploader.setUuid(1, \"foobar\");\n            assert.equal(uploader.getUuid(0), \"123\", \"first UUID was changed unexpectedly\");\n            assert.equal(uploader.getUuid(1), \"foobar\", \"UUID was not changed correctly\");\n\n            uploader.setName(0, \"raynicholus\");\n            assert.equal(uploader.getName(0), \"raynicholus\", \"name was not changed correctly\");\n            assert.equal(uploader.getName(1), \"up2.jpg\", \"second file name was changed unexpectedly\");\n\n            done();\n        }, 0);\n    });\n\n    // The <img> fails to load sometimes in older versions of IE for some unknown reason, so we have to exclude this test\n    if (!qq.ie() || qq.ie10() || qq.ie11()) {\n        it(\"drawThumbnail renders image properly if session response includes thumbnailUrl\", function(done) {\n            assert.expect(3, done);\n\n            var img = document.createElement(\"img\");\n\n            var expectedSessionResponse = [\n                    {\n                        name: \"up.jpg\",\n                        uuid: \"123\",\n                        thumbnailUrl: thumbnailSrc\n                    }\n                ],\n                uploader = new qq.FineUploaderBasic({\n                    session: {\n                        endpoint: sessionEndpoint\n                    },\n                    callbacks: {\n                        onSessionRequestComplete: function(response, success, xhr) {\n                            assert.deepEqual(response, expectedSessionResponse, \"unexpected callback response\");\n                            assert.ok(success, \"session request deemed failure\");\n\n                            uploader.drawThumbnail(0, img, 0, true).then(function(container) {\n                                assert.equal(container.src, thumbnailSrc, \"wrong thumbnail src\");\n                            }, function() {\n                                assert.fail(null, null, \"Thumbnail generation failed\");\n                            });\n                        }\n                    }\n                }\n            );\n\n            setTimeout(function() {\n                fileHelper.getRequests()[0].respond(200, null, JSON.stringify(expectedSessionResponse));\n            }, 0);\n        });\n    }\n\n    it(\"ignores response items that do not contain a valid UUID or name\", function(done) {\n        assert.expect(3, done);\n\n        var expectedSessionResponse = [\n                {\n                    uuid: \"123\",\n                    size: 456\n                },\n                {\n                    name: \"up2.jpg\"\n                }\n            ],\n            uploader = new qq.FineUploaderBasic({\n                session: {\n                    endpoint: sessionEndpoint\n                },\n                callbacks: {\n                    onSessionRequestComplete: function(response, success, xhr) {\n                        assert.deepEqual(response, expectedSessionResponse, \"unexpected callback response\");\n                        assert.ok(!success, \"session request deemed success unexpectedly\");\n                    }\n                }\n            }\n        ),\n            request;\n\n        setTimeout(function() {\n            request = fileHelper.getRequests()[0];\n            request.respond(200, null, JSON.stringify(expectedSessionResponse));\n            assert.equal(uploader.getUploads().length, 0, \"wrong number of pre-populated uploads recorded\");\n        }, 0);\n    });\n\n    it(\"properly handles non-200 response status\", function(done) {\n        assert.expect(3, done);\n\n        var uploader = new qq.FineUploaderBasic({\n                session: {\n                    endpoint: sessionEndpoint\n                },\n                callbacks: {\n                    onSessionRequestComplete: function(response, success, xhr) {\n                        assert.deepEqual(response, [], \"unexpected callback response\");\n                        assert.ok(!success, \"session request deemed success unexpectedly\");\n                    }\n                }\n            }\n        ),\n            request;\n\n        setTimeout(function() {\n            request = fileHelper.getRequests()[0];\n            request.respond(400, null, \"[]\");\n            assert.equal(uploader.getUploads().length, 0, \"wrong number of pre-populated uploads recorded\");\n        }, 0);\n    });\n\n    it(\"does not cause problems if the response is empty or invalid\", function(done) {\n        assert.expect(3, done);\n\n        var uploader = new qq.FineUploaderBasic({\n                session: {\n                    endpoint: sessionEndpoint\n                },\n                callbacks: {\n                    onSessionRequestComplete: function(response, success, xhr) {\n                        assert.deepEqual(response, null, \"unexpected callback response\");\n                        assert.ok(!success, \"session request deemed success unexpectedly\");\n                    }\n                }\n            }\n        ),\n            request;\n\n        setTimeout(function() {\n            request = fileHelper.getRequests()[0];\n            request.respond(200, null, \"hi\");\n            assert.equal(uploader.getUploads().length, 0, \"wrong number of pre-populated uploads recorded\");\n        }, 0);\n    });\n\n    it(\"re-queries for session data on reset if refreshOnReset is set to true\", function(done) {\n        assert.expect(7, done);\n\n        var sessionRequestCount = 0,\n            expectedSessionResponse1 = [\n                {\n                    name: \"up.jpg\",\n                    uuid: \"123\"\n                }\n            ],\n            expectedSessionResponse2 = [\n                {\n                    name: \"up1.jpg\",\n                    uuid: \"1234\"\n                }\n            ],\n            uploader = new qq.FineUploaderBasic({\n                session: {\n                    endpoint: sessionEndpoint,\n                    refreshOnReset: true\n                },\n                callbacks: {\n                    onSessionRequestComplete: function(response, success, xhr) {\n                        sessionRequestCount++;\n\n                        if (sessionRequestCount === 1) {\n                            assert.deepEqual(response, expectedSessionResponse1, \"unexpected callback response\");\n                        }\n                        else {\n                            assert.deepEqual(response, expectedSessionResponse2, \"unexpected callback response\");\n                        }\n\n                        assert.ok(success, \"session request deemed success unexpectedly\");\n                    }\n                }\n            }\n        ),\n            request;\n\n        setTimeout(function() {\n            request = fileHelper.getRequests()[0];\n            request.respond(200, null, JSON.stringify(expectedSessionResponse1));\n            assert.equal(uploader.getUploads().length, 1, \"wrong number of pre-populated uploads recorded\");\n\n            uploader.reset();\n            setTimeout(function() {\n                assert.equal(fileHelper.getRequests().length, 2, \"wrong number of requests\");\n                request = fileHelper.getRequests()[1];\n                request.respond(200, null, JSON.stringify(expectedSessionResponse2));\n                assert.equal(uploader.getUploads().length, 1, \"wrong number of pre-populated uploads recorded\");\n            }, 0);\n        }, 0);\n    });\n\n    it(\"sets proper delete endpoints & parameters based on response\", function(done) {\n        assert.expect(8, done);\n\n        var expectedSessionResponse = [\n                {\n                    name: \"up.jpg\",\n                    uuid: \"123\",\n                    deleteFileParams: {foo: \"bar\"}\n                },\n                {\n                    name: \"up2.jpg\",\n                    uuid: \"abc\",\n                    deleteFileEndpoint: \"/deletefile/override1\"\n                }\n            ],\n            uploader = new qq.FineUploaderBasic({\n                deleteFile: {\n                    enabled: true,\n                    endpoint: \"/deletefile/original\",\n                    params: {ray: \"nicholus\"}\n                },\n                session: {\n                    endpoint: sessionEndpoint\n                },\n                callbacks: {\n                    onSessionRequestComplete: function(response, success, xhr) {\n                        assert.deepEqual(response, expectedSessionResponse, \"unexpected callback response\");\n                        assert.ok(success, \"session request deemed failure\");\n                    }\n                }\n            }\n        ),\n            request;\n\n        setTimeout(function() {\n            request = fileHelper.getRequests()[0];\n            request.respond(200, null, JSON.stringify(expectedSessionResponse));\n\n            uploader.deleteFile(0);\n            assert.equal(fileHelper.getRequests().length, 2, \"1st delete request did not register\");\n            request = fileHelper.getRequests()[1];\n            assert.equal(request.method, \"DELETE\", \"1st delete request has unexpected method\");\n            assert.equal(request.url, \"/deletefile/original/123?foo=bar\", \"wrong deleteFile url for first file\");\n\n            uploader.deleteFile(1);\n            assert.equal(fileHelper.getRequests().length, 3, \"2nd delete request did not register\");\n            request = fileHelper.getRequests()[2];\n            assert.equal(request.method, \"DELETE\", \"2nd delete request has unexpected method\");\n            assert.equal(request.url, \"/deletefile/override1/abc?ray=nicholus\", \"wrong deleteFile url for first file\");\n        }, 0);\n    });\n\n    it(\"sends custom params and headers along with the GET request\", function(done) {\n        assert.expect(3, done);\n\n        var uploader = new qq.FineUploaderBasic({\n                session: {\n                    endpoint: sessionEndpoint,\n                    params: {foo: \"bar\"},\n                    customHeaders: {\"x-ray\": \"nicholus\"}\n                }\n            }\n        ),\n            request;\n\n        setTimeout(function() {\n            assert.equal(fileHelper.getRequests().length, 1, \"wrong # of requests\");\n            request = fileHelper.getRequests()[0];\n\n            assert.equal(request.requestHeaders[\"x-ray\"], \"nicholus\", \"custom header invalid\");\n            assert.equal(purl(request.url).param(\"foo\"), \"bar\", \"custom param invalid\");\n        }, 0);\n    });\n\n    describe(\"non-traditional endpoint tests\", function() {\n        function runTest(namespace, requiredKeyName, done) {\n            assert.expect(3, done);\n\n            var expectedSessionResponse = [\n                    {\n                        uuid: \"123\",\n                        name: \"hi\"\n                    },\n                    {\n                        name: \"up2.jpg\",\n                        uuid: \"abc\"\n                    }\n                ],\n                uploader,\n                request;\n\n\n            expectedSessionResponse[0][requiredKeyName] = \"raynicholus\";\n\n            uploader = new qq[namespace].FineUploaderBasic({\n                session: {\n                    endpoint: sessionEndpoint\n                },\n                callbacks: {\n                    onSessionRequestComplete: function(response, success, xhr) {\n                        assert.deepEqual(response, expectedSessionResponse, \"unexpected callback response\");\n                        assert.ok(!success, \"session request deemed success unexpectedly\");\n                    }\n                }\n            });\n\n            setTimeout(function() {\n                request = fileHelper.getRequests()[0];\n                request.respond(200, null, JSON.stringify(expectedSessionResponse));\n                assert.equal(uploader.getUploads().length, 1, \"wrong number of pre-populated uploads recorded\");\n            }, 0);\n        }\n\n        it(\"ignores S3 response items that do not contain a valid key\", function(done) {\n            runTest(\"s3\", \"s3Key\", done);\n        });\n\n        // Azure-based tests are irrelevant in \"older\" browsers since they don't support uploading to Azure\n        if (qq.supportedFeatures.ajaxUploading) {\n            it(\"ignores Azure response items that do not contain a valid blob name\", function(done) {\n                runTest(\"azure\", \"blobName\", done);\n            });\n        }\n    });\n});\n"
  },
  {
    "path": "test/unit/set-status.js",
    "content": "/* globals describe, beforeEach, qq, qqtest, assert, helpme, it */\n\ndescribe(\"set-status.js\", function() {\n    \"use strict\";\n\n    var testUploadEndpoint = \"/test/upload\",\n        fileTestHelper = helpme.setupFileTests();\n\n    var initialFiles = [{\n        name: \"left.jpg\",\n        uuid: \"e109af57-848b-4c2a-bca8-051374d01db1\"\n    }, {\n        name: \"right.jpg\",\n        uuid: \"949d16c3-727a-4c3c-8c0f-23404dcd6f3b\"\n    }];\n\n    it(\"testing status change of DELETED with initialFiles\", function() {\n        var uploader = new qq.FineUploaderBasic();\n        uploader.addInitialFiles(initialFiles);\n\n        var uploaderFiles = uploader.getUploads();\n        var file = uploaderFiles[0];\n\n        uploader.setStatus(file.id, qq.status.DELETED);\n\n        uploaderFiles = uploader.getUploads();\n        file = uploaderFiles[0];\n\n        assert.equal(1, uploader.getNetUploads());\n        assert.equal(qq.status.DELETED, file.status);\n\n        // ensure same file can't be \"deleted\" twice\n        uploader.setStatus(file.id, qq.status.DELETED);\n        assert.equal(1, uploader.getNetUploads());\n    });\n\n    it(\"testing status change of DELETE_FAILED with initialFiles\", function() {\n        var uploader = new qq.FineUploaderBasic();\n        uploader.addInitialFiles(initialFiles);\n\n        var uploaderFiles = uploader.getUploads();\n        var file = uploaderFiles[1];\n\n        uploader.setStatus(file.id, qq.status.DELETE_FAILED);\n\n        uploaderFiles = uploader.getUploads();\n        file = uploaderFiles[1];\n\n        assert.equal(2, uploader.getNetUploads());\n        assert.equal(qq.status.DELETE_FAILED, file.status);\n    });\n\n    it(\"testing status change of DELETED with mock uploader\", function(done) {\n        var uploader = new qq.FineUploaderBasic({\n            autoUpload: true,\n            request: {\n                endpoint: testUploadEndpoint\n            }\n        });\n\n        qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function(blob) {\n            fileTestHelper.mockXhr();\n\n            uploader.addFiles({name: \"test\", blob: blob});\n            uploader.uploadStoredFiles();\n            fileTestHelper.getRequests()[0].respond(201, null, JSON.stringify({success: true}));\n\n            var uploaderFiles = uploader.getUploads();\n            var file = uploaderFiles[0];\n\n            uploader.setStatus(file.id, qq.status.DELETED);\n\n            uploaderFiles = uploader.getUploads();\n            file = uploaderFiles[0];\n\n            assert.equal(0, uploader.getNetUploads());\n            assert.equal(qq.status.DELETED, file.status);\n            done();\n        });\n\n    });\n\n    it(\"testing status change of DELETED with mock uploader\", function(done) {\n        var uploader = new qq.FineUploaderBasic({\n            autoUpload: true,\n            request: {\n                endpoint: testUploadEndpoint\n            }\n        });\n\n        qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function(blob) {\n            fileTestHelper.mockXhr();\n\n            uploader.addFiles({name: \"test\", blob: blob});\n            uploader.uploadStoredFiles();\n            fileTestHelper.getRequests()[0].respond(201, null, JSON.stringify({success: true}));\n\n            var uploaderFiles = uploader.getUploads();\n            var file = uploaderFiles[0];\n\n            uploader.setStatus(file.id, qq.status.DELETE_FAILED);\n\n            uploaderFiles = uploader.getUploads();\n            file = uploaderFiles[0];\n\n            assert.equal(1, uploader.getNetUploads());\n            assert.equal(qq.status.DELETE_FAILED, file.status);\n            done();\n        });\n\n    });\n\n});\n"
  },
  {
    "path": "test/unit/simple-file-uploads.js",
    "content": "/* globals describe, beforeEach, afterEach, $fixture, qq, assert, it, qqtest, helpme, purl */\nif (qqtest.canDownloadFileAsBlob) {\n    describe(\"simple file uploads, mocked server/XHR\", function() {\n        \"use strict\";\n\n        var testUploadEndpoint = \"/test/upload\",\n            fileTestHelper = helpme.setupFileTests();\n\n        function getSimpleUploader(autoUpload, mpe) {\n            var uploader = new qq.FineUploaderBasic({\n                autoUpload: autoUpload,\n                request: {\n                    endpoint: testUploadEndpoint,\n                    paramsInBody: mpe,\n                    forceMultipart: mpe\n                },\n                callbacks: {\n                    onUpload: function(id, name) {\n                        assert.equal(id, 0, \"Wrong ID sent to onUpload\");\n                        assert.equal(name, \"test\", \"Wrong name sent to onUpload\");\n                    },\n                    onComplete: function(id, name, response, xhr) {\n                        assert.deepEqual(response, {success: true}, \"Server response parsing failed\");\n                        assert.equal(uploader.getUploads().length, 1, \"Expected only 1 file\");\n                        assert.equal(uploader.getUploads({status: qq.status.UPLOAD_SUCCESSFUL}).length, 1, \"Expected 1 successful file\");\n                        /* jshint eqnull:true */\n                        assert.ok(xhr != null, \"XHR not passed to onComplete\");\n                        assert.equal(uploader.getNetUploads(), 1, \"Wrong # of net uploads\");\n                    },\n                    onProgress: function(id, name, uploaded, total) {\n                        assert.equal(id, 0, \"Wrong ID sent to onProgress\");\n                        assert.equal(name, \"test\", \"Wrong name sent to onProgress\");\n                        assert.ok(uploaded > 0, \"Invalid onProgress uploaded param\");\n                        assert.ok(total > 0, \"Invalid onProgress total param\");\n                    }\n                }\n            });\n\n            return uploader;\n        }\n\n        it(\"respects custom request.method\", function(done) {\n            var uploader = new qq.FineUploaderBasic({\n                autoUpload: false,\n                request: {\n                    endpoint: testUploadEndpoint,\n                    method: \"PUT\"\n                }\n            });\n\n            qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function(blob) {\n                fileTestHelper.mockXhr();\n\n                var request,\n                    requestParams;\n\n                uploader.addFiles({name: \"test\", blob: blob});\n                uploader.uploadStoredFiles();\n\n                assert.equal(fileTestHelper.getRequests().length, 1, \"Wrong # of requests\");\n                request = fileTestHelper.getRequests()[0];\n\n                assert.equal(request.method, \"PUT\", \"Wrong request method\");\n\n                fileTestHelper.getRequests()[0].respond(200, null, JSON.stringify({success: true}));\n\n                done();\n            });\n        });\n\n        it(\"treats upload w/ status of 201 as success\", function(done) {\n            assert.expect(12, done);\n\n            var uploader = getSimpleUploader(false, true);\n\n            qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function(blob) {\n                fileTestHelper.mockXhr();\n\n                var request;\n\n                uploader.addFiles({name: \"test\", blob: blob});\n                uploader.uploadStoredFiles();\n\n                assert.equal(fileTestHelper.getRequests().length, 1, \"Wrong # of requests\");\n\n                fileTestHelper.getRequests()[0].respond(201, null, JSON.stringify({success: true}));\n            });\n        });\n\n        it(\"treats upload w/ status of 202 as success\", function(done) {\n            assert.expect(12, done);\n\n            var uploader = getSimpleUploader(false, true);\n\n            qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function(blob) {\n                fileTestHelper.mockXhr();\n\n                var request;\n\n                uploader.addFiles({name: \"test\", blob: blob});\n                uploader.uploadStoredFiles();\n\n                assert.equal(fileTestHelper.getRequests().length, 1, \"Wrong # of requests\");\n\n                fileTestHelper.getRequests()[0].respond(202, null, JSON.stringify({success: true}));\n            });\n        });\n\n        it(\"treats upload w/ status of 203 as success\", function(done) {\n            assert.expect(12, done);\n\n            var uploader = getSimpleUploader(false, true);\n\n            qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function(blob) {\n                fileTestHelper.mockXhr();\n\n                var request;\n\n                uploader.addFiles({name: \"test\", blob: blob});\n                uploader.uploadStoredFiles();\n\n                assert.equal(fileTestHelper.getRequests().length, 1, \"Wrong # of requests\");\n\n                fileTestHelper.getRequests()[0].respond(203, null, JSON.stringify({success: true}));\n            });\n        });\n\n        it(\"treats upload w/ status of 204 as success\", function(done) {\n            assert.expect(12, done);\n\n            var uploader = getSimpleUploader(false, true);\n\n            qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function(blob) {\n                fileTestHelper.mockXhr();\n\n                var request;\n\n                uploader.addFiles({name: \"test\", blob: blob});\n                uploader.uploadStoredFiles();\n\n                assert.equal(fileTestHelper.getRequests().length, 1, \"Wrong # of requests\");\n\n                fileTestHelper.getRequests()[0].respond(204, null, JSON.stringify({success: true}));\n            });\n        });\n\n        it(\"treats upload w/ status of 204 as success\", function(done) {\n            assert.expect(12, done);\n\n            var uploader = getSimpleUploader(false, true);\n\n            qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function(blob) {\n                fileTestHelper.mockXhr();\n\n                var request;\n\n                uploader.addFiles({name: \"test\", blob: blob});\n                uploader.uploadStoredFiles();\n\n                assert.equal(fileTestHelper.getRequests().length, 1, \"Wrong # of requests\");\n\n                fileTestHelper.getRequests()[0].respond(204, null, JSON.stringify({success: true}));\n            });\n        });\n\n        it(\"handles a simple successful single file upload request correctly where a file input element is passed into addFiles\", function(done) {\n            assert.expect(18, done);\n\n            var uploader = getSimpleUploader(false, true);\n\n            qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function(blob) {\n                fileTestHelper.mockXhr();\n\n                var request,\n                    requestParams,\n                    fakeFileInput = {\n                        tagName: \"input\",\n                        type: \"file\",\n                        files: [{name: \"test\", blob: blob}]\n                    };\n\n                uploader.addFiles(fakeFileInput);\n                uploader.uploadStoredFiles();\n\n                assert.equal(fileTestHelper.getRequests().length, 1, \"Wrong # of requests\");\n                request = fileTestHelper.getRequests()[0];\n                requestParams = request.requestBody.fields;\n\n                assert.equal(requestParams.qquuid, uploader.getUuid(0), \"Wrong UUID param sent with request\");\n                assert.equal(requestParams.qqfilename, uploader.getName(0), \"Wrong filename param sent with request\");\n                assert.equal(requestParams.qqtotalfilesize, uploader.getSize(0), \"Wrong file size param sent with request\");\n                assert.ok(qq.isBlob(requestParams.qqfile), \"File is incorrect\");\n                assert.equal(request.method, \"POST\", \"Wrong request method\");\n                assert.equal(request.url, testUploadEndpoint, \"Wrong request url\");\n\n                fileTestHelper.getRequests()[0].respond(200, null, JSON.stringify({success: true}));\n            });\n        });\n\n        it(\"handles a simple successful single MPE file upload request correctly\", function(done) {\n            assert.expect(18, done);\n\n            var uploader = getSimpleUploader(false, true);\n\n            qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function(blob) {\n                fileTestHelper.mockXhr();\n\n                var request,\n                    requestParams;\n\n                uploader.addFiles({name: \"test\", blob: blob});\n                uploader.uploadStoredFiles();\n\n                assert.equal(fileTestHelper.getRequests().length, 1, \"Wrong # of requests\");\n                request = fileTestHelper.getRequests()[0];\n                requestParams = request.requestBody.fields;\n\n                assert.equal(requestParams.qquuid, uploader.getUuid(0), \"Wrong UUID param sent with request\");\n                assert.equal(requestParams.qqfilename, uploader.getName(0), \"Wrong filename param sent with request\");\n                assert.equal(requestParams.qqtotalfilesize, uploader.getSize(0), \"Wrong file size param sent with request\");\n                assert.ok(qq.isBlob(requestParams.qqfile), \"File is incorrect\");\n                assert.equal(request.method, \"POST\", \"Wrong request method\");\n                assert.equal(request.url, testUploadEndpoint, \"Wrong request url\");\n\n                fileTestHelper.getRequests()[0].respond(200, null, JSON.stringify({success: true}));\n            });\n        });\n\n        it(\"handles a simple successful single non-MPE file upload request correctly\", function(done) {\n            assert.expect(17, done);\n\n            var uploader = getSimpleUploader(true, false);\n\n            qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function(blob) {\n                fileTestHelper.mockXhr();\n\n                var request, purlUrl;\n\n                uploader.addFiles({name: \"test\", blob: blob});\n\n                assert.equal(fileTestHelper.getRequests().length, 1, \"Wrong # of requests\");\n                request = fileTestHelper.getRequests()[0];\n                purlUrl = purl(request.url);\n\n                assert.equal(request.requestHeaders[\"X-Mime-Type\"], \"image/jpeg\", \"Wrong X-Mime-Type\");\n                assert.equal(purlUrl.param(\"qquuid\"), uploader.getUuid(0), \"Wrong UUID param sent with request\");\n                assert.equal(purlUrl.param(\"qqfilename\"), uploader.getName(0), \"Wrong filename param sent with request\");\n                assert.equal(request.method, \"POST\", \"Wrong request method\");\n                assert.equal(purlUrl.attr(\"path\"), testUploadEndpoint, \"Wrong request url\");\n\n                fileTestHelper.getRequests()[0].respond(200, null, JSON.stringify({success: true}));\n            });\n        });\n\n        it(\"properly passes overridden default param names along with the request\", function(done) {\n            var inputParamName = \"testinputname\",\n                uuidParamName = \"testuuidname\",\n                totalFileSizeParamName = \"testtotalfilesize\",\n                filenameParamName = \"testfilename\",\n                uploader = new qq.FineUploaderBasic({\n                    request: {\n                        endpoint: testUploadEndpoint,\n                        inputName: inputParamName,\n                        uuidName: uuidParamName,\n                        totalFileSizeName: totalFileSizeParamName,\n                        filenameParam: filenameParamName\n                    }\n                }\n            );\n\n            qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function(blob) {\n                fileTestHelper.mockXhr();\n\n                var request, requestParams;\n\n                uploader.addFiles(blob);\n\n                assert.equal(fileTestHelper.getRequests().length, 1, \"Wrong # of requests\");\n                request = fileTestHelper.getRequests()[0];\n                requestParams = request.requestBody.fields;\n\n                assert.equal(requestParams[uuidParamName], uploader.getUuid(0), \"Wrong UUID param sent with request\");\n                assert.equal(requestParams[filenameParamName], uploader.getName(0), \"Wrong filename param sent with request\");\n                assert.equal(requestParams[totalFileSizeParamName], uploader.getSize(0), \"Wrong file size param sent with request\");\n                assert.ok(qq.isBlob(requestParams[inputParamName]), \"File is incorrect\");\n                done();\n            });\n        });\n\n        it(\"handles overriden UUID via API\", function(done) {\n            assert.expect(1, done);\n\n            var uploader = new qq.FineUploaderBasic({\n                autoUpload: false\n            });\n\n            qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function(blob) {\n                uploader.addFiles(blob);\n                uploader.setUuid(0, \"123\");\n                assert.equal(uploader.getUuid(0), \"123\");\n            });\n        });\n\n        it(\"handles overriden UUID via response\", function(done) {\n            var newUuid = \"12345\";\n\n            assert.expect(1, done);\n\n            var uploader = new qq.FineUploaderBasic({\n                request: {\n                    endpoint: testUploadEndpoint\n                },\n                callbacks: {\n                    onComplete: function(id, name, response, xhr) {\n                        assert.equal(uploader.getUuid(0), newUuid);\n                    }\n                }\n            });\n\n            qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function(blob) {\n                fileTestHelper.mockXhr();\n\n                uploader.addFiles(blob);\n\n                fileTestHelper.getRequests()[0].respond(200, null, JSON.stringify({success: true, newUuid: newUuid}));\n            });\n        });\n\n        it(\"handles overriden name via API\", function(done) {\n            var newName = \"newname123\";\n\n            assert.expect(1, done);\n\n            var uploader = new qq.FineUploaderBasic({\n                autoUpload: false\n            });\n\n            qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function(blob) {\n                uploader.addFiles(blob);\n                uploader.setName(0, newName);\n                assert.equal(uploader.getName(0), newName);\n            });\n        });\n\n        describe(\"QUEUED status tests to cover #1104\", function() {\n            function runQueuedStatusTest(autoUpload, done) {\n                assert.expect(1, done);\n\n                var uploader = new qq.FineUploaderBasic({\n                    maxConnections: 1,\n                    autoUpload: autoUpload,\n                    request: {\n                        endpoint: testUploadEndpoint\n                    },\n                    callbacks: {\n                        onStatusChange: function(id, oldStatus, newStatus) {\n                            if (id === 1 && oldStatus === qq.status.SUBMITTED) {\n                                assert.equal(newStatus, qq.status.QUEUED);\n                            }\n                        }\n                    }\n                });\n\n                qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function(blob) {\n                    fileTestHelper.mockXhr();\n\n                    uploader.addFiles([blob, blob]);\n                    setTimeout(function() {\n                        autoUpload || uploader.uploadStoredFiles();\n                    }, 0);\n                });\n            }\n\n            it(\"reports 'waiting' files as QUEUED in auto upload mode\", function(done) {\n                runQueuedStatusTest(true, done);\n            });\n\n            it(\"reports 'waiting' files as QUEUED in manual upload mode after call to uploadStoredFiles()\", function(done) {\n                runQueuedStatusTest(false, done);\n            });\n        });\n\n        it(\"handles an empty array of files or blobs appropriately\", function(done) {\n            assert.expect(2, done);\n\n            var uploader = new qq.FineUploaderBasic({\n                callbacks: {\n                    onError: function(id) {\n                        assert.ok(true);\n                    }\n                }\n            });\n\n            uploader.addFiles([]);\n            uploader.addFiles([]);\n        });\n\n        it(\"marks an upload as failed if the status indicates failure, even if the response body indicates success\", function(done) {\n            assert.expect(2, done);\n\n            var uploader = new qq.FineUploaderBasic({\n                request: {\n                    endpoint: \"/test/endpoint\"\n                },\n                callbacks: {\n                    onComplete: function(id, name, response, xhr) {\n                        assert.ok(!response.success);\n                        assert.equal(uploader.getUploads()[0].status, qq.status.UPLOAD_FAILED);\n                    }\n                }\n            });\n\n            qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function(blob) {\n                fileTestHelper.mockXhr();\n\n                uploader.addFiles(blob);\n                fileTestHelper.getRequests()[0].respond(500, null, JSON.stringify({success: true}));\n            });\n        });\n\n        describe(\"canvas uploading\", function() {\n            var origCanvasToBlob;\n\n            beforeEach(function() {\n                origCanvasToBlob = qq.canvasToBlob;\n            });\n\n            afterEach(function() {\n                qq.canvasToBlob = origCanvasToBlob;\n            });\n\n            it(\"attempts to convert the passed canvas to a blob\", function(done) {\n                var uploader = new qq.FineUploaderBasic({\n                        request: {\n                            endpoint: \"/test/endpoint\"\n                        }\n                    }),\n                    canvas = document.createElement(\"canvas\");\n\n                qq.canvasToBlob = function(passedCanvas, mime, quality) {\n                    assert.equal(passedCanvas, canvas);\n                    assert.ok(!mime);\n                    assert.ok(!quality);\n\n                    uploader._handleNewFile = function(file) {\n                        done();\n                    };\n                };\n\n                uploader.addFiles(canvas);\n            });\n\n            it(\"attempts to convert the passed canvas to a blob, respecting type, quality, and name properties\", function(done) {\n                var uploader = new qq.FineUploaderBasic({\n                        request: {\n                            endpoint: \"/test/endpoint\"\n                        }\n                    }),\n                    canvas = document.createElement(\"canvas\"),\n                    canvasWrapper = {\n                        canvas: canvas,\n                        type: \"foobar\",\n                        quality: 3,\n                        name: \"mycanvas\"\n                    };\n\n                qq.canvasToBlob = function(passedCanvas, mime, quality) {\n                    assert.equal(passedCanvas, canvas);\n                    assert.equal(mime, \"foobar\");\n                    assert.equal(quality, 0.03);\n\n                    uploader._handleNewFile = function(file) {\n                        assert.equal(file.name, \"mycanvas\");\n                        done();\n                    };\n                };\n\n                uploader.addFiles(canvasWrapper);\n            });\n        });\n        \n        it(\"removes reference to a Blob via API\", function(done) {\n            qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function(blob) {\n                fileTestHelper.mockXhr();\n\n                var request,\n                    uploader = new qq.FineUploaderBasic({\n                        autoUpload: false,\n                        request: { endpoint: testUploadEndpoint },\n                        callbacks: {\n                            onComplete: function(id) {\n                                assert.ok(uploader.getFile(id));\n                                uploader.removeFileRef(id);\n                                assert.ok(!uploader.getFile(id));\n                                done();\n                            }\n                        }\n                    });\n\n                uploader.addFiles({name: \"test\", blob: blob});\n                uploader.uploadStoredFiles();\n\n                fileTestHelper.getRequests()[0].respond(200, null, JSON.stringify({success: true}));\n            });\n        });\n\n        describe(\"onUpload w/ Promise return value\", function() {\n            function testOnUploadLogic(callbacks) {\n                var uploader = new qq.FineUploaderBasic({\n                    request: {\n                        endpoint: testUploadEndpoint\n                    },\n                    callbacks: callbacks\n                });\n\n                qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function (blob) {\n                    fileTestHelper.mockXhr();\n                    uploader.addFiles({name: \"test\", blob: blob});\n                });\n            }\n\n            it(\"pauses upload if Promise resolves with { pause: true }\", function(done) {\n                testOnUploadLogic({\n                    onUpload: function () {\n                        return window.Promise.resolve({ pause: true });\n                    },\n\n                    onStatusChange: function (id, oldStatus, newStatus) {\n                        if (id === 0 &&\n                            oldStatus === qq.status.UPLOADING &&\n                            newStatus === qq.status.PAUSED\n                        ) {\n                            done();\n                        }\n                    }\n                });\n            });\n\n            it(\"fails upload if Promise is rejected\", function(done) {\n                testOnUploadLogic({\n                    onUpload: function () {\n                        return window.Promise.reject();\n                    },\n\n                    onComplete: function (id, name, response) {\n                        if (id === 0 && !response.success) {\n                            done();\n                        }\n                    }\n                });\n            });\n\n            it(\"sends upload request when Promise is resolved\", function(done) {\n                testOnUploadLogic({\n                    onUpload: function () {\n                        setTimeout(function() {\n                            fileTestHelper.getRequests()[0].respond(200, null, JSON.stringify({success: true}));\n                        }, 10);\n\n                        return window.Promise.resolve();\n                    },\n\n                    onComplete: function (id, name, response) {\n                        if (id === 0 && response.success) {\n                            done();\n                        }\n                    }\n                });\n            });\n        });\n    });\n}\n"
  },
  {
    "path": "test/unit/submit-validate-cancel.js",
    "content": "/* globals describe, before, after, beforeEach, $fixture, qq, assert, it, qqtest, helpme, purl, Q */\nif (qqtest.canDownloadFileAsBlob) {\n    describe(\"autoUpload = false tests for validation, submission, and cancel features\", function() {\n        \"use strict\";\n\n        var oldWrapCallbacks,\n            testImgKey = \"up.jpg\",\n            testImgType = \"image/jpeg\",\n            testBlob;\n\n        before(function() {\n            oldWrapCallbacks = qq.FineUploaderBasic.prototype._wrapCallbacks;\n\n            // \"Turn off\" wrapping of callbacks that squelches errors.  We need AssertionErrors in callbacks to bubble.\n            qq.FineUploaderBasic.prototype._wrapCallbacks = function() {};\n        });\n\n        after(function() {\n            qq.FineUploaderBasic.prototype._wrapCallbacks = oldWrapCallbacks;\n        });\n\n        it(\"handles a simple blob submission correctly (autoUpload = false)\", function(done) {\n            var uploader = new qq.FineUploaderBasic({\n                autoUpload: false,\n                callbacks: {\n                    onSubmit: function(id, name) {\n                        callbackOrder.push(\"submit\");\n                        assert.equal(id, 0);\n                        assert.equal(name, expectedName);\n                    },\n                    onSubmitted: function(id, name) {\n                        callbackOrder.push(\"submitted\");\n                        assert.equal(id, 0);\n                        assert.equal(name, expectedName);\n                    },\n                    onValidate: function(blobData) {\n                        callbackOrder.push(\"validate\");\n                        assert.equal(blobData.name, expectedName);\n                        assert.equal(blobData.size, expectedFileSize);\n                    },\n                    onValidateBatch: function(blobDataArray) {\n                        callbackOrder.push(\"validateBatch\");\n                        assert.equal(blobDataArray.length, 1);\n                        assert.equal(blobDataArray[0].name, expectedName);\n                        assert.equal(blobDataArray[0].size, expectedFileSize);\n                    },\n                    onStatusChange: function(id, oldStatus, newStatus) {\n                        assert.equal(id, 0);\n                        statusChangeOrder.push(newStatus);\n                    }\n                }\n            }),\n                callbackOrder = [],\n                statusChangeOrder = [],\n                expectedFileSize = 3266,\n                expectedName = \"testname\",\n                expectedCallbackOrder = [\"validateBatch\", \"validate\", \"submit\", \"submitted\"],\n                expectedStatusChangeOrder = [qq.status.SUBMITTING, qq.status.SUBMITTED];\n\n            qqtest.downloadFileAsBlob(testImgKey, testImgType).then(function(blob) {\n                uploader.addFiles({blob: blob, name: expectedName});\n\n                assert.deepEqual(callbackOrder, expectedCallbackOrder);\n                assert.deepEqual(statusChangeOrder, expectedStatusChangeOrder);\n                assert.equal(uploader.getName(0), expectedName);\n                assert.equal(uploader.getNetUploads(), 0);\n                assert.equal(uploader.getSize(0), expectedFileSize);\n                assert.equal(uploader.getUploads().length, 1);\n                assert.equal(uploader.getUploads({id: 0}).name, expectedName);\n                assert.equal(uploader.getUploads({id: 0}).originalName, expectedName);\n                assert.equal(uploader.getUploads({id: 0}).size, expectedFileSize);\n                assert.equal(uploader.getUploads({id: 0}).status, qq.status.SUBMITTED);\n\n                done();\n            });\n        });\n\n        it(\"only ever passes name and size to onValidate callbacks\", function(done) {\n            var uploader = new qq.FineUploaderBasic({\n                callbacks: {\n                    onValidate: function(blobData, button) {\n                        assert.ok(blobData.name);\n                        assert.ok(blobData.size);\n                        assert.equal(undefined, blobData.foo);\n                    }\n                }\n            });\n\n            qqtest.downloadFileAsBlob(testImgKey, testImgType).then(function(blob) {\n                blob.foo = \"bar\";\n\n                uploader.addFiles([\n                    {blob: blob, name: \"name1\"},\n                    {blob: blob, name: \"name2\"}\n                ]);\n                done();\n            });\n        });\n\n        it(\"handles a file rejected via onValidate callback\", function(done) {\n            var filesValidated = 0,\n                uploader = new qq.FineUploaderBasic({\n                    validation: {\n                        stopOnFirstInvalidFile: false\n                    },\n                    callbacks: {\n                        onAllComplete: function(succeeded, failed) {\n                            assert.equal(succeeded.length, 0, \"wrong succeeded count\");\n                            assert.equal(failed.length, 1, \"wrong failed count\");\n                            assert.equal(uploader.getUploads({id: 0}).status, qq.status.UPLOAD_FAILED);\n                            assert.equal(uploader.getUploads({id: 1}).status, qq.status.REJECTED);\n                            done();\n                        },\n                        onValidate: function(blobData, button) {\n                            filesValidated++;\n\n                            if (filesValidated === 2) {\n                                return false;\n                            }\n                        }\n                    }\n                });\n\n            qqtest.downloadFileAsBlob(testImgKey, testImgType).then(function(blob) {\n                uploader.addFiles([\n                    {blob: blob, name: \"name1\"},\n                    {blob: blob, name: \"name2\"}\n                ]);\n            });\n        });\n\n        describe(\"file rejection via callback\", function() {\n            function setupUploader(callback, blob, done, useQ) {\n                var uploader = new qq.FineUploaderBasic({\n                    autoUpload: false,\n                    callbacks: (function() {\n                        var callbacks = {},\n                            callbackName = \"on\" + callback.charAt(0).toUpperCase() + callback.substr(1);\n\n                        if (done) {\n                            callbacks[callbackName] = function() {\n                                if (useQ) {\n                                    return Q.Promise(function(resolve, reject) {\n                                        setTimeout(function() {\n                                            reject();\n                                        },100);\n                                    });\n                                }\n                                else {\n                                    var promise = new qq.Promise();\n                                    setTimeout(function() {\n                                        promise.failure();\n                                    },100);\n                                    return promise;\n                                }\n                            };\n\n                            callbacks.onStatusChange = function(id, oldStatus, newStatus) {\n                                if (newStatus === qq.status.REJECTED) {\n                                    assert.equal(uploader.getUploads().length, 1, \"Wrong number of uploads\");\n                                    assert.equal(uploader.getUploads({id: 0}).status, qq.status.REJECTED, \"Wrong status\");\n\n                                    done();\n                                }\n                            };\n                        }\n                        else {\n                            callbacks[callbackName] = function() {\n                                return false;\n                            };\n                        }\n\n                        return callbacks;\n                    }())\n                });\n\n                uploader.addFiles(blob);\n\n                return uploader;\n            }\n\n            it(\"Ignores a submitted file that is rejected by returning false in a submit callback\", function(done) {\n                qqtest.downloadFileAsBlob(testImgKey, testImgType).then(function(blob) {\n                    var uploader = setupUploader(\"submit\", blob);\n\n                    assert.equal(uploader.getUploads().length, 1, \"Wrong number of uploads\");\n                    assert.equal(uploader.getUploads({id: 0}).status, qq.status.REJECTED, \"Wrong status\");\n\n                    done();\n                });\n            });\n\n            it(\"Ignores a submitted file that is rejected by returning a promise and failing it in a submit callback\", function(done) {\n                qqtest.downloadFileAsBlob(testImgKey, testImgType).then(function(blob) {\n                    setupUploader(\"submit\", blob, done);\n                });\n            });\n\n            it(\"Ignores a submitted file that is rejected by returning false in a validate callback\", function(done) {\n                qqtest.downloadFileAsBlob(testImgKey, testImgType).then(function(blob) {\n                    var uploader = setupUploader(\"validate\", blob);\n\n                    assert.equal(uploader.getUploads().length, 1, \"Wrong number of uploads\");\n                    assert.equal(uploader.getUploads({id: 0}).status, qq.status.REJECTED, \"Wrong status\");\n\n                    done();\n                });\n            });\n\n            it(\"Ignores a submitted file that is rejected by returning a promise and failing it in a validate callback\", function(done) {\n                qqtest.downloadFileAsBlob(testImgKey, testImgType).then(function(blob) {\n                    setupUploader(\"validate\", blob, done);\n                });\n            });\n\n            it(\"Q.js: Ignores a submitted file that is rejected by returning a promise and failing it in a validate callback\", function(done) {\n                qqtest.downloadFileAsBlob(testImgKey, testImgType).then(function(blob) {\n                    setupUploader(\"validate\", blob, done, true);\n                });\n            });\n\n            it(\"Ignores a submitted file that is rejected by returning false in a validateBatch callback\", function(done) {\n                qqtest.downloadFileAsBlob(testImgKey, testImgType).then(function(blob) {\n                    var uploader = setupUploader(\"validateBatch\", blob);\n\n                    assert.equal(uploader.getUploads().length, 1, \"Wrong number of uploads\");\n                    assert.equal(uploader.getUploads({id: 0}).status, qq.status.REJECTED, \"Wrong status\");\n\n                    done();\n                });\n            });\n\n            it(\"Ignores a submitted file that is rejected by returning a promise and failing it in a validateBatch callback\", function(done) {\n                qqtest.downloadFileAsBlob(testImgKey, testImgType).then(function(blob) {\n                    setupUploader(\"validateBatch\", blob, done);\n                });\n            });\n\n            it(\"Q.js: Ignores a submitted file that is rejected by returning a promise and failing it in a validateBatch callback\", function(done) {\n                qqtest.downloadFileAsBlob(testImgKey, testImgType).then(function(blob) {\n                    setupUploader(\"validateBatch\", blob, done, true);\n                });\n            });\n        });\n\n        describe(\"file rejection via internal validation\", function() {\n            function setupUploader(limits, numBlobsOrTheBlob, statusChangeLogic) {\n                var uploader = new qq.FineUploaderBasic({\n                    autoUpload: false,\n                    validation: limits,\n                    callbacks: {\n                        onStatusChange: function() {\n                            statusChangeLogic.apply(uploader, arguments);\n                        }\n                    }\n                });\n\n                if (qq.isBlob(numBlobsOrTheBlob)) {\n                    uploader.addFiles(numBlobsOrTheBlob);\n                }\n                else {\n                    qqtest.downloadFileAsBlob(testImgKey, testImgType).then(function(blob) {\n                        numBlobsOrTheBlob = [].concat(numBlobsOrTheBlob);\n                        qq.each(numBlobsOrTheBlob, function(idx, num) {\n                            var blobs = [],\n                                i;\n\n                            for (i = 0; i < num; i++) {\n                                blobs.push(blob);\n                            }\n\n                            uploader.addFiles(blobs);\n                        });\n                    });\n                }\n            }\n\n            it(\"prevents too many items from being submitted at once\", function(done) {\n                var rejectedFiles = 0;\n\n                setupUploader({itemLimit: 2}, 3, function(id, oldStatus, newStatus) {\n                    newStatus === qq.status.REJECTED && rejectedFiles++;\n\n                    if (rejectedFiles === 3) {\n                        assert.equal(this.getUploads().length, 3);\n                        assert.equal(this.getUploads({status: qq.status.REJECTED}).length, 3);\n                        done();\n                    }\n                });\n            });\n\n            it(\"prevents too many items from being submitted over multiple submissions\", function(done) {\n                var submitted = 0,\n                    rejected = 0;\n\n                setupUploader({itemLimit: 2}, [2, 1], function(id, oldStatus, newStatus) {\n                    newStatus === qq.status.SUBMITTED && submitted++;\n                    newStatus === qq.status.REJECTED && rejected++;\n\n                    if (submitted === 2 && rejected === 1) {\n                        assert.equal(this.getUploads().length, 3);\n                        assert.equal(this.getUploads({status: qq.status.SUBMITTED}).length, 2);\n                        assert.equal(this.getUploads({status: qq.status.REJECTED}).length, 1);\n                        done();\n                    }\n                });\n            });\n\n            it(\"itemLimit sanity check - ensure internal item count is properly decremented after files have been rejected\", function(done) {\n                var submitted = 0,\n                    rejected = 0;\n\n                setupUploader({itemLimit: 2}, [1, 2, 1], function(id, oldStatus, newStatus) {\n                    newStatus === qq.status.SUBMITTED && submitted++;\n                    newStatus === qq.status.REJECTED && rejected++;\n\n                    if (submitted === 2 && rejected === 2) {\n                        assert.equal(this.getUploads().length, 4);\n                        assert.equal(this.getUploads({status: qq.status.SUBMITTED}).length, 2);\n                        assert.equal(this.getUploads({status: qq.status.REJECTED}).length, 2);\n                        done();\n                    }\n                });\n            });\n\n            it(\"prevents empty files from being submitted\", function(done) {\n                qqtest.downloadFileAsBlob(\"empty.txt\", \"text/plain\").then(function(emptyFile) {\n                    setupUploader({}, emptyFile, function(id, oldStatus, newStatus) {\n                        if (newStatus === qq.status.REJECTED) {\n                            assert.equal(this.getUploads().length, 1);\n                            assert.equal(this.getUploads({status: qq.status.REJECTED}).length, 1);\n                            done();\n                        }\n                    });\n                });\n            });\n\n            it(\"prevents files that are too large from being submitted\", function(done) {\n                setupUploader({sizeLimit: 3265}, 1, function(id, oldStatus, newStatus) {\n                    if (newStatus === qq.status.REJECTED) {\n                        assert.equal(this.getUploads().length, 1);\n                        assert.equal(this.getUploads({status: qq.status.REJECTED}).length, 1);\n                        done();\n                    }\n                });\n            });\n\n            it(\"allow files that are not too large to be submitted\", function(done) {\n                setupUploader({sizeLimit: 3266}, 1, function(id, oldStatus, newStatus) {\n                    if (newStatus === qq.status.SUBMITTED) {\n                        assert.equal(this.getUploads().length, 1);\n                        assert.equal(this.getUploads({status: qq.status.SUBMITTED}).length, 1);\n                        done();\n                    }\n                });\n            });\n\n            it(\"don't allow files that are too small to be submitted\", function(done) {\n                setupUploader({minSizeLimit: 3267}, 1, function(id, oldStatus, newStatus) {\n                    if (newStatus === qq.status.REJECTED) {\n                        assert.equal(this.getUploads().length, 1);\n                        assert.equal(this.getUploads({status: qq.status.REJECTED}).length, 1);\n                        done();\n                    }\n                });\n            });\n\n            it(\"allow files that are not too small to be submitted\", function(done) {\n                setupUploader({minSizeLimit: 3266}, 1, function(id, oldStatus, newStatus) {\n                    if (newStatus === qq.status.SUBMITTED) {\n                        assert.equal(this.getUploads().length, 1);\n                        assert.equal(this.getUploads({status: qq.status.SUBMITTED}).length, 1);\n                        done();\n                    }\n                });\n            });\n\n            describe(\"stopOnFirstInvalidFile tests\", function() {\n                function setupStopOnFirstInvalidFileUploader(stopOnFirstInvalidFile, done) {\n                    var expectedSubmitted = stopOnFirstInvalidFile ? 1 : 2,\n                        expectedRejected = stopOnFirstInvalidFile ? 2 : 1,\n                        uploader = new qq.FineUploaderBasic({\n                        autoUpload: false,\n                        validation: {\n                            sizeLimit: 3266,\n                            stopOnFirstInvalidFile: stopOnFirstInvalidFile\n                        },\n                        callbacks: {\n                            onStatusChange: function(id, oldStatus, newStatus) {\n                                if (uploader.getUploads({status: qq.status.SUBMITTED}).length === expectedSubmitted &&\n                                    uploader.getUploads({status: qq.status.REJECTED}).length === expectedRejected) {\n\n                                    assert.ok(true);\n                                    done();\n                                }\n                            }\n                        }\n                    });\n\n                    qqtest.downloadFileAsBlob(testImgKey, testImgType).then(function(blob1) {\n                        qqtest.downloadFileAsBlob(\"down.jpg\", \"image/jpeg\").then(function(blob2) {\n                            uploader.addFiles([blob1, blob2, blob1]);\n                        });\n                    });\n                }\n\n                it(\"Rejects all files after (and including) the invalid file by default\", function(done) {\n                    setupStopOnFirstInvalidFileUploader(true, done);\n                });\n\n                it(\"Rejects only files that are invalid\", function(done) {\n                    setupStopOnFirstInvalidFileUploader(false, done);\n                });\n            });\n        });\n\n        describe(\"cancelling uploads\", function() {\n            function runCancelTest(batch, done) {\n                var canceledIds = [],\n                    uploader = new qq.FineUploaderBasic({\n                    autoUpload: false,\n                    callbacks: {\n                        onCancel: function(id) {\n                            canceledIds.push(id);\n                        },\n                        onStatusChange: function() {\n                            if (uploader.getUploads({status: qq.status.SUBMITTED}).length === 2) {\n                                // We need to wait until just after this status is set before we can cancel\n                                // uploads due to the way FU internal code handles this.  This is probably\n                                // ok as we would not expect this to be called programmatically in a\n                                // callback handler like this.\n                                setTimeout(function() {\n                                    if (batch) {\n                                        uploader.cancelAll();\n                                    }\n                                    else {\n                                        uploader.cancel(0);\n                                        uploader.cancel(1);\n                                    }\n                                }, 0);\n                            }\n\n                            if (uploader.getUploads({status: qq.status.CANCELED}).length === 2) {\n                                assert.deepEqual(canceledIds, [0, 1]);\n                                assert.equal(uploader.getUploads().length, 2);\n                                assert.equal(uploader.getUploads({status: qq.status.CANCELED}).length, 2);\n                                done();\n                            }\n                        }\n                    }\n                }\n            );\n\n                qqtest.downloadFileAsBlob(testImgKey, testImgType).then(function(blob) {\n                    uploader.addFiles([blob, blob]);\n                });\n            }\n\n            it(\"properly cancels files via cancel method\", function(done) {\n                runCancelTest(false, done);\n            });\n\n            it(\"properly cancels files via cancelAll method\", function(done) {\n                runCancelTest(true, done);\n            });\n        });\n    });\n}\n"
  },
  {
    "path": "test/unit/templating.js",
    "content": "/* globals describe, beforeEach, afterEach, $fixture, qq, assert, it, qqtest, helpme, purl */\ndescribe(\"templating.js\", function() {\n    \"use strict\";\n\n    var $template, templating,\n        PAUSED_TEXT = \"Testing - Paused\",\n        HIDE_CSS = \"test-hide\",\n        EDITABLE_CSS = \"test-editable\",\n        /* jshint quotmark:false */\n        emptyTemplate = '<div class=\"qq-uploader-selector qq-uploader\">' +\n                            '<ul class=\"qq-upload-list-selector qq-upload-list\">' +\n                                '<li></li>' +\n                            '</ul>' +\n                        '</div>',\n        defaultTemplate = '<div class=\"qq-uploader-selector qq-uploader\">' +\n                            '<div class=\"qq-total-progress-bar-container-selector qq-total-progress-bar-container\">' +\n                                '<div class=\"qq-total-progress-bar-selector qq-progress-bar qq-total-progress-bar\"></div>' +\n                            '</div>' +\n                            '<div class=\"qq-upload-drop-area-selector qq-upload-drop-area\" qq-hide-dropzone>' +\n                                '<span>Drop files here to upload</span>' +\n                            '</div>' +\n                            '<div class=\"qq-upload-button-selector qq-upload-button\">' +\n                                '<div>Upload a file</div>' +\n                            '</div>' +\n                            '<span class=\"qq-drop-processing-selector qq-drop-processing\">' +\n                                '<span>Processing dropped files...</span>' +\n                                '<span class=\"qq-drop-processing-spinner-selector qq-drop-processing-spinner\"></span>' +\n                            '</span>' +\n                            '<ul class=\"qq-upload-list-selector qq-upload-list\">' +\n                                '<li>' +\n                                    '<div class=\"qq-progress-bar-container-selector\">' +\n                                        '<div class=\"qq-progress-bar-selector qq-progress-bar\"></div>' +\n                                    '</div>' +\n                                    '<span class=\"qq-upload-spinner-selector qq-upload-spinner\"></span>' +\n                                    '<span class=\"qq-edit-filename-icon-selector qq-edit-filename-icon\"></span>' +\n                                    '<span class=\"qq-upload-file-selector qq-upload-file\"></span>' +\n                                    '<input class=\"qq-edit-filename-selector qq-edit-filename\" tabindex=\"0\" type=\"text\">' +\n                                    '<span class=\"qq-upload-size-selector qq-upload-size\"></span>' +\n                                    '<a class=\"qq-upload-cancel-selector qq-upload-cancel\" href=\"#\">Cancel</a>' +\n                                    '<a class=\"qq-upload-retry-selector qq-upload-retry\" href=\"#\">Retry</a>' +\n                                    '<a class=\"qq-upload-delete-selector qq-upload-delete\" href=\"#\">Delete</a>' +\n                                    '<a class=\"qq-upload-pause-selector\" href=\"#\">Pause</a>' +\n                                    '<a class=\"qq-upload-continue-selector\" href=\"#\">Continue</a>' +\n                                    '<span class=\"qq-upload-status-text-selector qq-upload-status-text\"></span>' +\n                                '</li>' +\n                            '</ul>' +\n                        '</div>',\n        simpleTwoLevelFilesTemplate = '<div class=\"qq-uploader-selector qq-uploader\">' +\n                            '<ul class=\"qq-upload-list-selector qq-upload-list\">' +\n                                '<li>' +\n                                    '<a class=\"qq-upload-delete-selector qq-upload-delete\" href=\"#\">Delete</a>' +\n                                    '<div>' +\n                                        '<span class=\"qq-edit-filename-icon-selector qq-edit-filename-icon\"></span>' +\n                                        '<span class=\"qq-upload-file-selector qq-upload-file\"></span>' +\n                                        '<input class=\"qq-edit-filename-selector qq-edit-filename\" tabindex=\"0\" type=\"text\">' +\n                                        '<span class=\"qq-upload-size-selector qq-upload-size\"></span>' +\n                                        '<a class=\"qq-upload-cancel-selector qq-upload-cancel\" href=\"#\">Cancel</a>' +\n                                        '<div>' +\n                                            '<a class=\"qq-upload-retry-selector qq-upload-retry\" href=\"#\">Retry</a>' +\n                                        '</div>' +\n                                        '<span class=\"qq-upload-status-text-selector qq-upload-status-text\"></span>' +\n                                    '</div>' +\n                                '</li>' +\n                            '</ul>' +\n                        '</div>',\n        tableTemplate = '<div class=\"qq-uploader-selector qq-uploader\">' +\n                            '<div class=\"qq-total-progress-bar-container-selector qq-total-progress-bar-container\">' +\n                                '<div class=\"qq-total-progress-bar-selector qq-progress-bar qq-total-progress-bar\"></div>' +\n                            '</div>' +\n                            '<div class=\"qq-upload-drop-area-selector qq-upload-drop-area\" qq-hide-dropzone>' +\n                                '<span>Drop files here to upload</span>' +\n                            '</div>' +\n                            '<div class=\"qq-upload-button-selector qq-upload-button\">' +\n                                '<div>Upload a file</div>' +\n                            '</div>' +\n                            '<span class=\"qq-drop-processing-selector qq-drop-processing\">' +\n                                '<span>Processing dropped files...</span>' +\n                                '<span class=\"qq-drop-processing-spinner-selector qq-drop-processing-spinner\"></span>' +\n                            '</span>' +\n                            '<table>' +\n                                '<thead>' +\n                                    '<tr>' +\n                                        '<th>File</th>' +\n                                        '<th>Actions</th>' +\n                                    '</tr>' +\n                                '</thead>' +\n                                '<tbody class=\"qq-upload-list-selector\">' +\n                                    '<tr>' +\n                                        '<td>' +\n                                            '<div class=\"qq-progress-bar-container-selector\">' +\n                                                '<div class=\"qq-progress-bar-selector qq-progress-bar\"></div>' +\n                                            '</div>' +\n                                            '<span class=\"qq-upload-spinner-selector qq-upload-spinner\"></span>' +\n                                            '<span class=\"qq-edit-filename-icon-selector qq-edit-filename-icon\"></span>' +\n                                            '<span class=\"qq-upload-file-selector qq-upload-file\"></span>' +\n                                            '<input class=\"qq-edit-filename-selector qq-edit-filename\" tabindex=\"0\" type=\"text\">' +\n                                            '<span class=\"qq-upload-size-selector qq-upload-size\"></span>' +\n                                        '</td>' +\n                                        '<td>' +\n                                            '<a class=\"qq-upload-cancel-selector qq-upload-cancel\" href=\"#\">Cancel</a>' +\n                                            '<a class=\"qq-upload-retry-selector qq-upload-retry\" href=\"#\">Retry</a>' +\n                                            '<a class=\"qq-upload-delete-selector qq-upload-delete\" href=\"#\">Delete</a>' +\n                                            '<a class=\"qq-upload-pause-selector\" href=\"#\">Pause</a>' +\n                                            '<a class=\"qq-upload-continue-selector\" href=\"#\">Continue</a>' +\n                                            '<span class=\"qq-upload-status-text-selector qq-upload-status-text\"></span>' +\n                                        '</td>' +\n                                    '</tr>' +\n                                '</tbody>' +\n                            '</table>' +\n                        '</div>';\n\n\n    function renderTemplate(content) {\n        $template = $('<script id=\"qq-template\" type=\"text/template\"></script>');\n        $template[0].text = content;\n        $fixture.append($template);\n        templating = new qq.Templating({\n            log: function() {},\n            containerEl: $fixture[0],\n            classes: {\n                hide: HIDE_CSS,\n                editable: EDITABLE_CSS\n            },\n            text: {\n                paused: PAUSED_TEXT\n            }\n        });\n        templating.render();\n    }\n\n    afterEach(function() {\n        $(\"#qq-template\").remove();\n    });\n\n    describe(\"test with empty template\", function() {\n        it(\"ensure missing elements do not cause exceptions\", function() {\n            renderTemplate(emptyTemplate);\n\n            templating.clearFiles();\n            templating.addFile(0, \"foobar\");\n            /* jshint eqnull:true */\n            assert.ok(templating.getFileList() != null);\n            templating.markFilenameEditable(0);\n            templating.updateFilename(0, \"test\");\n            templating.hideFilename(0);\n            templating.showFilename(0);\n            assert.ok(templating.getButton() == null);\n            templating.hideDropProcessing();\n            templating.showDropProcessing();\n            assert.ok(templating.getDropZone() == null);\n            assert.ok(!templating.isEditFilenamePossible());\n            assert.ok(!templating.isRetryPossible());\n            assert.ok(templating.getFileContainer(0) != null);\n            templating.showEditIcon(0);\n            templating.hideEditIcon(0);\n            assert.ok(templating.getEditInput(0) == null);\n            templating.updateProgress(0, 50, 100);\n            templating.hideProgress(0);\n            templating.resetProgress(0);\n            templating.showCancel(0);\n            templating.hideCancel(0);\n            templating.showDeleteButton(0);\n            templating.hideDeleteButton(0);\n            templating.updateSize(0, \"100MB\");\n            templating.setStatusText(0, \"test\");\n            templating.hideSpinner(0);\n            templating.showSpinner(0);\n            templating.allowPause(0);\n            templating.allowContinueButton(0);\n            templating.hidePause(0);\n            templating.uploadContinued(0);\n            templating.uploadPaused(0);\n            templating.removeFile(0);\n        });\n    });\n\n    describe(\"test with default template\", function() {\n        var fileContainer0;\n\n\n        beforeEach(function() {\n            renderTemplate(defaultTemplate);\n            templating.addFile(0, \"foobar\");\n            fileContainer0 = templating.getFileContainer(0);\n        });\n\n        afterEach(function() {\n            templating.clearFiles();\n        });\n\n        it(\"only displays relevant elements initially\", function() {\n            assert.ok($(fileContainer0).find(\".qq-upload-delete-selector\").hasClass(HIDE_CSS));\n            assert.ok($(fileContainer0).find(\".qq-progress-bar-container-selector\").hasClass(HIDE_CSS));\n            assert.ok($(fileContainer0).find(\".qq-upload-size-selector\").hasClass(HIDE_CSS));\n            assert.ok($(fileContainer0).find(\".qq-upload-retry-selector\").hasClass(HIDE_CSS));\n\n            qq.supportedFeatures.fileDrop && assert.ok($fixture.find(\".qq-drop-processing-selector\").hasClass(HIDE_CSS));\n        });\n\n        it(\"has edit filename & retry features enabled, also button present if applicable\", function() {\n            assert.ok(templating.isEditFilenamePossible());\n            assert.ok(templating.isRetryPossible());\n            /* jshint eqnull:true */\n            assert.ok(templating.getButton() != null);\n        });\n\n        it(\"adds & removes file entries\", function() {\n            /* jshint eqnull:true */\n            assert.ok(templating.getFileContainer(0) != null);\n            templating.removeFile(0);\n            assert.ok(templating.getFileContainer(0) == null);\n            templating.addFile(0, \"test\");\n            templating.clearFiles();\n            assert.ok(templating.getFileContainer(0) == null);\n        });\n\n        it(\"embeds the file ID correctly\", function() {\n            assert.ok(templating.getFileId(fileContainer0) === 0);\n        });\n\n        it(\"hides and shows spinner\", function() {\n            templating.hideSpinner(0);\n            assert.ok($(fileContainer0).find(\".qq-upload-spinner-selector\").hasClass(HIDE_CSS));\n            assert.ok(!$(fileContainer0).hasClass(\"qq-in-progress\"));\n\n            templating.showSpinner(0);\n            assert.ok(!$(fileContainer0).find(\".qq-upload-spinner-selector\").hasClass(HIDE_CSS));\n            assert.ok($(fileContainer0).hasClass(\"qq-in-progress\"));\n        });\n\n        it(\"updates status text\", function() {\n            templating.setStatusText(0, \"foobar\");\n            assert.equal($(fileContainer0).find(\".qq-upload-status-text-selector\").text(), \"foobar\");\n        });\n\n        it(\"updates file name\", function() {\n            templating.updateFilename(0, \"123abc\");\n            assert.equal($(fileContainer0).find(\".qq-upload-file-selector\").text(), \"123abc\");\n        });\n\n        it(\"updates size text\", function() {\n            templating.updateSize(0, \"123MB\");\n            assert.equal($(fileContainer0).find(\".qq-upload-size-selector\").text(), \"123MB\");\n        });\n\n        it(\"hides and shows delete link\", function() {\n            templating.hideDeleteButton(0);\n            assert.ok($(fileContainer0).find(\".qq-upload-delete-selector\").hasClass(HIDE_CSS));\n\n            templating.showDeleteButton(0);\n            assert.ok(!$(fileContainer0).find(\".qq-upload-delete-selector\").hasClass(HIDE_CSS));\n        });\n\n        it(\"hides and shows cancel link\", function() {\n            templating.hideCancel(0);\n            assert.ok($(fileContainer0).find(\".qq-upload-cancel-selector\").hasClass(HIDE_CSS));\n\n            templating.showCancel(0);\n            assert.ok(!$(fileContainer0).find(\".qq-upload-cancel-selector\").hasClass(HIDE_CSS));\n        });\n\n        it(\"hides and shows edit icon\", function() {\n            templating.hideEditIcon(0);\n            assert.ok(!$(fileContainer0).find(\".qq-edit-filename-icon-selector\").hasClass(EDITABLE_CSS));\n\n            templating.showEditIcon(0);\n            assert.ok($(fileContainer0).find(\".qq-edit-filename-icon-selector\").hasClass(EDITABLE_CSS));\n        });\n\n        it(\"hides and shows total progress bar\", function() {\n            var totalProgressEl = $fixture.find(\".qq-total-progress-bar-container-selector\"),\n                totalProgressBar = totalProgressEl.find('.qq-total-progress-bar-selector');\n\n            // we're in progress, show total progress\n            templating.updateTotalProgress(50, 100);\n            assert.ok(!totalProgressEl.hasClass(HIDE_CSS));\n            assert.equal(totalProgressBar.attr(\"aria-valuenow\"), \"50\");\n\n            // we've ended, hide total progress\n            templating.updateTotalProgress(100, 100);\n            assert.ok(totalProgressEl.hasClass(HIDE_CSS));\n            assert.equal(totalProgressBar.attr(\"aria-valuenow\"), \"100\");\n\n            // we're back in progress, show total progress\n            templating.updateTotalProgress(1, 100);\n            assert.ok(!totalProgressEl.hasClass(HIDE_CSS));\n            assert.equal(totalProgressBar.attr(\"aria-valuenow\"), \"1\");\n\n            // hide even if we are in progress\n            templating.hideTotalProgress();\n            assert.ok(totalProgressEl.hasClass(HIDE_CSS));\n\n        });\n\n        if (qq.supportedFeatures.fileDrop) {\n            it(\"hides and shows drop processing spinner\", function() {\n                templating.hideDropProcessing(0);\n                assert.ok($fixture.find(\".qq-drop-processing-selector\").hasClass(HIDE_CSS));\n\n                templating.showDropProcessing(0);\n                assert.ok(!$fixture.find(\".qq-drop-processing-selector\").hasClass(HIDE_CSS));\n            });\n        }\n\n        it(\"toggles visibility of pause/continue buttons correctly\", function() {\n            templating.allowPause(0);\n            assert.ok(!$fixture.find(\".qq-upload-pause-selector\").hasClass(HIDE_CSS));\n            assert.ok($fixture.find(\".qq-upload-continue-selector\").hasClass(HIDE_CSS));\n\n            templating.allowContinueButton(0);\n            assert.ok($fixture.find(\".qq-upload-pause-selector\").hasClass(HIDE_CSS));\n            assert.ok(!$fixture.find(\".qq-upload-continue-selector\").hasClass(HIDE_CSS));\n        });\n\n        it(\"Updates status text, buttons, & spinner on pause & continue correctly\", function() {\n            var $status = $fixture.find(\".qq-upload-status-text-selector\");\n\n            templating.uploadPaused(0);\n            assert.equal($status.text(), PAUSED_TEXT);\n            assert.ok($fixture.find(\".qq-upload-pause-selector\").hasClass(HIDE_CSS));\n            assert.ok(!$fixture.find(\".qq-upload-continue-selector\").hasClass(HIDE_CSS));\n            assert.ok($fixture.find(\".qq-upload-spinner-selector\").hasClass(HIDE_CSS));\n\n\n            templating.uploadContinued(0);\n            assert.equal($status.text(), \"\");\n            assert.ok($fixture.find(\".qq-upload-continue-selector\").hasClass(HIDE_CSS));\n            assert.ok(!$fixture.find(\".qq-upload-pause-selector\").hasClass(HIDE_CSS));\n            assert.ok(!$fixture.find(\".qq-upload-spinner-selector\").hasClass(HIDE_CSS));\n        });\n\n        it(\"reset clears contents before appending new render\", function() {\n            templating.reset();\n            assert.equal($fixture.find(\".qq-uploader\").length, 1);\n        });\n    });\n\n    describe(\"permanently hidden files tests\", function() {\n        var fileContainer0;\n\n        beforeEach(function () {\n            renderTemplate(defaultTemplate);\n            templating.addFile(0, \"foobar\", false, true);\n            fileContainer0 = templating.getFileContainer(0);\n        });\n\n        afterEach(function () {\n            templating.clearFiles();\n        });\n\n        it(\"adds permanently hidden files to the DOM, but ensures they are never visible\", function() {\n            assert.equal(templating.getFileContainer(0).style.display, \"none\");\n            assert.ok(templating.isHiddenForever(0));\n        });\n    });\n\n\n    describe(\"file elements are two levels below the file container\", function() {\n        var fileContainer, deleteButtonEl, cancelButtonEl, retryButtonEl;\n\n        it(\"is able to find the file ID given a button element\", function() {\n            renderTemplate(simpleTwoLevelFilesTemplate);\n            templating.addFile(0, \"foobar\");\n            fileContainer = templating.getFileContainer(0);\n            deleteButtonEl = $(fileContainer).find(\".qq-upload-delete-selector\")[0];\n            cancelButtonEl = $(fileContainer).find(\".qq-upload-cancel-selector\")[0];\n            retryButtonEl = $(fileContainer).find(\".qq-upload-retry-selector\")[0];\n\n            assert.equal(templating.getFileId(deleteButtonEl), 0, \"Button 1 level deep\");\n            assert.equal(templating.getFileId(cancelButtonEl), 0, \"Button 2 levels deep\");\n            assert.equal(templating.getFileId(retryButtonEl), 0, \"Button 3 levels deep\");\n        });\n    });\n\n    if (qqtest.canDownloadFileAsBlob) {\n        it(\"updates the name in the UI after a call to setName API method\", function(done) {\n            assert.expect(2, done);\n\n            helpme.setupFileTests();\n\n            var uploader;\n\n            var template = $('<script id=\"qq-template\" type=\"text/template\">' + defaultTemplate + '</script>');\n            $fixture.append(template);\n\n            uploader = new qq.FineUploader({\n                element: $fixture[0],\n                autoUpload: false,\n\n                callbacks: {\n                    onSubmitted: function(id) {\n                        assert.equal($(\".qq-upload-file-selector\").text(), \"up.jpg\");\n\n                        uploader.setName(id, \"newname.test\");\n                        setTimeout(function() {\n                            assert.equal($(\".qq-upload-file-selector\").text(), \"newname.test\");\n                        }, 0);\n                    }\n                }\n            });\n\n            qqtest.downloadFileAsBlob(\"up.jpg\", \"image/jpeg\").then(function(blob) {\n                uploader.addFiles({blob: blob, name: \"up.jpg\"});\n            });\n        });\n    }\n\n    describe(\"test with table template\", function() {\n        var fileContainer0;\n\n        beforeEach(function() {\n            renderTemplate(tableTemplate);\n            templating.addFile(0, \"foobar\");\n            fileContainer0 = templating.getFileContainer(0);\n        });\n\n        afterEach(function() {\n            templating.clearFiles();\n        });\n\n\n        it(\"adds & removes file entries\", function() {\n            /* jshint eqnull:true */\n            assert.ok(templating.getFileContainer(0) != null);\n            templating.removeFile(0);\n            assert.ok(templating.getFileContainer(0) == null);\n            templating.addFile(0, \"test\");\n            templating.clearFiles();\n            assert.ok(templating.getFileContainer(0) == null);\n        });\n\n        it(\"embeds the file ID correctly\", function() {\n            assert.ok(templating.getFileId(fileContainer0) === 0);\n        });\n\n        it(\"hides and shows spinner\", function() {\n            templating.hideSpinner(0);\n            assert.ok($(fileContainer0).find(\".qq-upload-spinner-selector\").hasClass(HIDE_CSS));\n            assert.ok(!$(fileContainer0).hasClass(\"qq-in-progress\"));\n\n            templating.showSpinner(0);\n            assert.ok(!$(fileContainer0).find(\".qq-upload-spinner-selector\").hasClass(HIDE_CSS));\n            assert.ok($(fileContainer0).hasClass(\"qq-in-progress\"));\n        });\n\n        it(\"updates status text\", function() {\n            templating.setStatusText(0, \"foobar\");\n            assert.equal($(fileContainer0).find(\".qq-upload-status-text-selector\").text(), \"foobar\");\n        });\n\n        it(\"updates file name\", function() {\n            templating.updateFilename(0, \"123abc\");\n            assert.equal($(fileContainer0).find(\".qq-upload-file-selector\").text(), \"123abc\");\n        });\n\n        it(\"updates size text\", function() {\n            templating.updateSize(0, \"123MB\");\n            assert.equal($(fileContainer0).find(\".qq-upload-size-selector\").text(), \"123MB\");\n        });\n\n        it(\"hides and shows delete link\", function() {\n            templating.hideDeleteButton(0);\n            assert.ok($(fileContainer0).find(\".qq-upload-delete-selector\").hasClass(HIDE_CSS));\n\n            templating.showDeleteButton(0);\n            assert.ok(!$(fileContainer0).find(\".qq-upload-delete-selector\").hasClass(HIDE_CSS));\n        });\n\n        it(\"hides and shows cancel link\", function() {\n            templating.hideCancel(0);\n            assert.ok($(fileContainer0).find(\".qq-upload-cancel-selector\").hasClass(HIDE_CSS));\n\n            templating.showCancel(0);\n            assert.ok(!$(fileContainer0).find(\".qq-upload-cancel-selector\").hasClass(HIDE_CSS));\n        });\n\n        it(\"hides and shows edit icon\", function() {\n            templating.hideEditIcon(0);\n            assert.ok(!$(fileContainer0).find(\".qq-edit-filename-icon-selector\").hasClass(EDITABLE_CSS));\n\n            templating.showEditIcon(0);\n            assert.ok($(fileContainer0).find(\".qq-edit-filename-icon-selector\").hasClass(EDITABLE_CSS));\n        });\n\n        it(\"is able to find the file ID given a button element\", function() {\n            var deleteButtonEl, cancelButtonEl, retryButtonEl;\n            deleteButtonEl = $(fileContainer0).find(\".qq-upload-delete-selector\")[0];\n            cancelButtonEl = $(fileContainer0).find(\".qq-upload-cancel-selector\")[0];\n            retryButtonEl = $(fileContainer0).find(\".qq-upload-retry-selector\")[0];\n\n            assert.equal(templating.getFileId(deleteButtonEl), 0, \"Button 1 level deep\");\n            assert.equal(templating.getFileId(cancelButtonEl), 0, \"Button 2 levels deep\");\n            assert.equal(templating.getFileId(retryButtonEl), 0, \"Button 3 levels deep\");\n        });\n\n    });\n});\n"
  },
  {
    "path": "test/unit/total-progress.js",
    "content": "/* globals describe, it, assert */\nif (qq.supportedFeatures.progressBar) {\n    describe(\"total progress tests\", function() {\n        \"use strict\";\n\n        describe(\"module tests\", function() {\n            it(\"updates total progress when a file has been added or when size has changed\", function() {\n                var actualFileSizes = {\n                        0: 123,\n                        1: 456,\n                        2: 789,\n                        3: -1\n                    },\n                    actualTotalProgressUpdates = [],\n                    expectedTotalProgressUpdates = [\n                        {loaded: 0, total: actualFileSizes[0]},\n                        {loaded: 0, total: actualFileSizes[0] + actualFileSizes[1]},\n                        {loaded: 0, total: actualFileSizes[0]},\n                        {loaded: 0, total: actualFileSizes[0] + actualFileSizes[2]}\n\n                    ],\n\n                    onTotalProgress = function(loaded, total) {\n                        actualTotalProgressUpdates.push({loaded: loaded, total: total});\n                    },\n\n                    getSize = function(id) {\n                        return actualFileSizes[id];\n                    },\n\n                    tp = new qq.TotalProgress(onTotalProgress, getSize);\n\n                tp.onStatusChange(0, null, qq.status.SUBMITTING);\n                tp.onStatusChange(0, qq.status.SUBMITTING, qq.status.SUBMITTED);\n\n                tp.onStatusChange(1, null, qq.status.SUBMITTING);\n                tp.onStatusChange(1, qq.status.SUBMITTING, qq.status.REJECTED);\n\n                tp.onStatusChange(2, null, qq.status.SUBMITTING);\n                tp.onStatusChange(2, qq.status.SUBMITTING, qq.status.SUBMITTED);\n\n                tp.onStatusChange(3, null, qq.status.SUBMITTING);\n                tp.onStatusChange(3, qq.status.SUBMITTING, qq.status.SUBMITTED);\n\n                actualFileSizes[3] = 333;\n                tp.onNewSize(3);\n                expectedTotalProgressUpdates.push(\n                    {loaded: 0, total: actualFileSizes[0] + actualFileSizes[2] + actualFileSizes[3]}\n                );\n\n                assert.deepEqual(actualTotalProgressUpdates, expectedTotalProgressUpdates);\n            });\n\n            it(\"updates total progress when a file has been canceled\", function() {\n                var actualFileSizes = {\n                        0: 123,\n                        1: 456\n                    },\n                    actualTotalProgressUpdates = [],\n                    expectedTotalProgressUpdates = [\n                        {loaded: 0, total: actualFileSizes[0]},\n                        {loaded: 10, total: actualFileSizes[0]},\n                        {loaded: 10, total: actualFileSizes[0] + actualFileSizes[1]},\n                        {loaded: 0, total: actualFileSizes[1]}\n                    ],\n\n                    onTotalProgress = function(loaded, total) {\n                        actualTotalProgressUpdates.push({loaded: loaded, total: total});\n                    },\n\n                    getSize = function(id) {\n                        return actualFileSizes[id];\n                    },\n\n                    tp = new qq.TotalProgress(onTotalProgress, getSize);\n\n                tp.onStatusChange(0, null, qq.status.SUBMITTING);\n                tp.onStatusChange(0, qq.status.SUBMITTING, qq.status.SUBMITTED);\n                tp.onIndividualProgress(0, 10, actualFileSizes[0]);\n\n                tp.onStatusChange(1, null, qq.status.SUBMITTING);\n                tp.onStatusChange(1, qq.status.SUBMITTING, qq.status.SUBMITTED);\n\n                tp.onStatusChange(0, qq.status.SUBMITTED, qq.status.CANCELED);\n\n                assert.deepEqual(actualTotalProgressUpdates, expectedTotalProgressUpdates);\n            });\n\n            it(\"updates total progress when all files have been canceled\", function() {\n                var actualFileSizes = {\n                        0: 123,\n                        1: 456\n                    },\n                    actualTotalProgressUpdates = [],\n                    expectedTotalProgressUpdates = [\n                        {loaded: 0, total: actualFileSizes[0]},\n                        {loaded: 10, total: actualFileSizes[0]},\n                        {loaded: 10, total: actualFileSizes[0] + actualFileSizes[1]},\n                        {loaded: 0, total: actualFileSizes[1]},\n                        {loaded: 0, total: 0}\n                    ],\n\n                    onTotalProgress = function(loaded, total) {\n                        actualTotalProgressUpdates.push({loaded: loaded, total: total});\n                    },\n\n                    getSize = function(id) {\n                        return actualFileSizes[id];\n                    },\n\n                    tp = new qq.TotalProgress(onTotalProgress, getSize);\n\n                tp.onStatusChange(0, null, qq.status.SUBMITTING);\n                tp.onStatusChange(0, qq.status.SUBMITTING, qq.status.SUBMITTED);\n                tp.onIndividualProgress(0, 10, actualFileSizes[0]);\n\n                tp.onStatusChange(1, null, qq.status.SUBMITTING);\n                tp.onStatusChange(1, qq.status.SUBMITTING, qq.status.SUBMITTED);\n\n                tp.onStatusChange(0, qq.status.SUBMITTED, qq.status.CANCELED);\n                tp.onStatusChange(1, qq.status.SUBMITTED, qq.status.CANCELED);\n\n                assert.deepEqual(actualTotalProgressUpdates, expectedTotalProgressUpdates);\n            });\n        });\n\n        it(\"updates total progress when individual files progress\", function() {\n            var actualFileSizes = {\n                    0: 123,\n                    1: 456\n                },\n                actualTotalProgressUpdates = [],\n                expectedTotalProgressUpdates = [\n                    {loaded: 0, total: actualFileSizes[0]},\n                    {loaded: 0, total: actualFileSizes[0] + actualFileSizes[1]},\n                    {loaded: 10, total: actualFileSizes[0] + actualFileSizes[1]},\n                    {loaded: 20, total: actualFileSizes[0] + actualFileSizes[1]},\n                    {loaded: 35, total: actualFileSizes[0] + actualFileSizes[1]}\n                ],\n\n                onTotalProgress = function(loaded, total) {\n                    actualTotalProgressUpdates.push({loaded: loaded, total: total});\n                },\n\n                getSize = function(id) {\n                    return actualFileSizes[id];\n                },\n\n                tp = new qq.TotalProgress(onTotalProgress, getSize);\n\n            tp.onStatusChange(0, null, qq.status.SUBMITTING);\n            tp.onStatusChange(1, null, qq.status.SUBMITTING);\n\n            tp.onStatusChange(0, qq.status.SUBMITTING, qq.status.SUBMITTING);\n            tp.onIndividualProgress(0, 10, actualFileSizes[0]);\n            tp.onIndividualProgress(0, 20, actualFileSizes[0]);\n\n            tp.onStatusChange(1, qq.status.SUBMITTING, qq.status.SUBMITTING);\n            tp.onIndividualProgress(1, 15, actualFileSizes[1]);\n\n            assert.deepEqual(actualTotalProgressUpdates, expectedTotalProgressUpdates);\n        });\n\n        it(\"limits total progress to specific batches of files\", function() {\n            var actualFileSizes = {\n                    0: 123,\n                    1: 456,\n                    2: 333\n                },\n                actualTotalProgressUpdates = [],\n                expectedTotalProgressUpdates = [\n                    {loaded: 0, total: actualFileSizes[0]},\n                    {loaded: actualFileSizes[0] - 1, total: actualFileSizes[0]},\n                    {loaded: actualFileSizes[0] - 1, total: actualFileSizes[0] + actualFileSizes[1]},\n                    {loaded: actualFileSizes[0] - 1 + actualFileSizes[1] - 1, total: actualFileSizes[0] + actualFileSizes[1]},\n                    {loaded: actualFileSizes[0] + actualFileSizes[1], total: actualFileSizes[0] + actualFileSizes[1]},\n                    {loaded: 0, total: actualFileSizes[2]}\n                ],\n\n                onTotalProgress = function(loaded, total) {\n                    actualTotalProgressUpdates.push({loaded: loaded, total: total});\n                },\n\n                getSize = function(id) {\n                    return actualFileSizes[id];\n                },\n\n                tp = new qq.TotalProgress(onTotalProgress, getSize);\n\n            tp.onStatusChange(0, null, qq.status.SUBMITTING);\n            tp.onStatusChange(0, qq.status.SUBMITTING, qq.status.SUBMITTED);\n            tp.onIndividualProgress(0, actualFileSizes[0] - 1, actualFileSizes[0]);\n\n            tp.onStatusChange(1, null, qq.status.SUBMITTING);\n            tp.onStatusChange(1, qq.status.SUBMITTING, qq.status.SUBMITTED);\n            tp.onIndividualProgress(1, actualFileSizes[1] - 1, actualFileSizes[1]);\n\n            tp.onAllComplete([0], [1], []);\n\n            tp.onStatusChange(2, null, qq.status.SUBMITTING);\n            tp.onStatusChange(2, qq.status.SUBMITTING, qq.status.SUBMITTED);\n\n            assert.deepEqual(actualTotalProgressUpdates, expectedTotalProgressUpdates);\n        });\n    });\n}\n"
  },
  {
    "path": "test/unit/ui.handler.click.filebuttons.js",
    "content": "/* globals describe, beforeEach, afterEach, $fixture, qq, assert, it, qqtest, helpme, purl */\ndescribe(\"ui.handler.click.filebuttons.js\", function () {\n    \"use strict\";\n\n    describe(\"click handlers\", function () {\n    \n        it(\"delete, retry, and cancel click handlers\", function (done) {\n            var fileId = 123,\n                $container, $fileItem, $cancelLink, $deleteLink, $retryLink, $pauseLink, $continueLink, templating;\n\n            $fixture.append(\"<div class='testcontainer'></div>\");\n\n            $container = $fixture.find(\".testcontainer\");\n            $container.append(\"<div class='fileitem'></div>\");\n\n            $fileItem = $container.find(\".fileitem\");\n\n            $fileItem.append(\"<a class='test-cancel'></a>\");\n            $cancelLink = $fileItem.find(\".test-cancel\");\n\n            $fileItem.append(\"<a class='test-delete'></a>\");\n            $deleteLink = $fileItem.find(\".test-delete\");\n\n            $fileItem.append(\"<a class='test-retry'></a>\");\n            $retryLink = $fileItem.find(\".test-retry\");\n\n            $fileItem.append(\"<a class='test-pause'></a>\");\n            $pauseLink = $fileItem.find(\".test-pause\");\n\n            $fileItem.append(\"<a class='test-continue'></a>\");\n            $continueLink = $fileItem.find(\".test-continue\");\n\n            $fileItem[0].qqFileId = fileId;\n\n            templating = {\n                getFileList: function() {\n                    return $container[0];\n                },\n                isCancel: function(el) {\n\n                },\n                isRetry: function(el) {\n\n                },\n                isDeleteButton: function(el) {\n\n                },\n                isPause: function(el) {\n\n                },\n                isContinueButton: function(el) {\n\n                }\n            };\n\n            var handler = new qq.FileButtonsClickHandler({\n                templating: templating,\n                classes: {\n                    cancel: \"test-cancel\",\n                    deleteButton: \"test-delete\",\n                    retry: \"test-retry\"\n                },\n                onGetName: function() { return \"test\"; },\n                onDeleteFile: function(id) {\n                    assert.equal(id, fileId, \"deleted file\");\n                },\n                onCancel: function(id) {\n                    assert.equal(id, fileId, \"canceled upload\");\n                },\n                onRetry: function(id) {\n                    assert.equal(id, fileId, \"retried upload\");\n                },\n                onPause: function(id) {\n                    assert.equal(id, fileId, \"paused upload\");\n                },\n                onContinue: function(id) {\n                    assert.equal(id, fileId, \"continued upload\");\n                }\n            });\n\n\n            //these should result in callbacks\n            $cancelLink.simulate(\"click\");\n            $deleteLink.simulate(\"click\");\n            $retryLink.simulate(\"click\");\n\n            //these should not result in callbacks\n            $container.simulate(\"click\");\n            $fileItem.simulate(\"click\");\n            done();\n        });\n    });\n});\n"
  },
  {
    "path": "test/unit/ui.handler.click.filename.js",
    "content": "/* globals describe, beforeEach, afterEach, $fixture, qq, assert, it, qqtest, helpme, purl */\ndescribe(\"ui.handler.click.filename.js\", function () {\n    \"use strict\";\n\n    var fileId = 123,\n        $container, $fileItem, $filenameDiv, templating;\n\n    beforeEach(function () {\n        $fixture.append(\"<div class='testcontainer'></div>\");\n\n        $container = $fixture.find(\".testcontainer\");\n        $container.append(\"<div class='fileitem'></div>\");\n\n        $fileItem = $container.find(\".fileitem\");\n        $fileItem.append(\"<div class='test-file' file-id='123'></div>\");\n        $fileItem.append(\"<input class='test-input'>\");\n\n        $filenameDiv = $fileItem.find(\".test-file\");\n\n        templating = {\n            getFileList: function() {\n                return $container[0];\n            },\n            isFileName: function(el) {\n                return $(el).hasClass(\"test-file\");\n            },\n            isEditIcon: function(el) {\n            },\n            getFileId: function(el) {\n                return $(el).attr(\"file-id\");\n            },\n            getEditInput: function(id) {\n                return $container.find(\"INPUT\")[0];\n            }\n        };\n    });\n\n    it(\"does not allow editing when upload is in progress\", function () {\n\n        var handler = new qq.FilenameClickHandler({\n            templating: templating,\n            classes: {\n                file: \"test-file\"\n            },\n            onGetName: function() { return \"test\"; },\n            onSetName: function(fileId, newName) {},\n            onGetUploadStatus: function(fileId) { return qq.status.UPLOADING; },\n            onEditingStatusChange: function(id, isEditing) {\n                assert.ok(false, \"onEditingStatusChange should never be called\");\n            }\n        });\n\n        $filenameDiv.simulate(\"click\");\n    });\n\n    function testFilenameInputBlur(origName, origNameSansExt, newName, newNameSansExt, simulateArgs) {\n\n        it(\"filename click handler - file submitted w/ blur event = \" + simulateArgs, function (done) {\n\n            var actualName = origName,\n                $input = $container.find(\"INPUT\"),\n                editing;\n\n            var handler = new qq.FilenameClickHandler({\n                templating: templating,\n                classes: {\n                    file: \"test-file\"\n                },\n                onGetName: function() { return actualName; },\n                onSetName: function(fileId, name) {\n                    // IE fires a blur event after a key event in some cases,\n                    // let's ignore that as it will cause the tests to fail and doesn't cause us any harm.\n                    if (actualName !== name) {\n                        assert.equal(name, newName, \"new name should have the original extension appended\");\n                        actualName = name;\n                    }\n                },\n                onGetUploadStatus: function(fileId) { return qq.status.SUBMITTED; },\n                onGetInput: function(item) { return $input[0]; },\n                onEditingStatusChange: function(id, isEditing) {\n                    // IE fires a blur event after a key event in some cases,\n                    // let's ignore that as it will cause the tests to fail and doesn't cause us any harm.\n                    if (editing !== false) {\n                        assert.equal(id, fileId, \"onEditingStatusChange fileId should be correct\");\n\n                        if (editing === undefined) {\n                            assert.ok(isEditing, \"We should be in edit mode\");\n                        }\n                        else {\n                            assert.ok(!isEditing, \"We should not be in edit mode\");\n                        }\n\n                        editing = isEditing;\n                    }\n                }\n            });\n\n            //Fine Uploader would normally already have this set\n            $filenameDiv.text(actualName);\n\n            //this shouldn't allow the user to change the filename\n            $container.simulate(\"click\");\n\n            $filenameDiv.simulate(\"click\");\n\n            assert.equal($input.val(), origNameSansExt, \"filename input should equal original filename sans extension initially\");\n\n            $input.val(newNameSansExt);\n            $input.simulate.apply($input, simulateArgs);\n\n            setTimeout(function() {\n                done();\n            }, 0);\n\n        });\n    }\n\n    // Can't get this test to pass in FF on SauceLabs only.  Fails sometimes in Safari as well.\n    if (!qq.firefox() && !qq.safari()) {\n        testFilenameInputBlur(\"test.foo.bar\", \"test.foo\", \"blahblah.bar\", \"blahblah\", [\"blur\"]);\n        testFilenameInputBlur(\"test\", \"test\", \"blahblah\", \"blahblah\", [\"blur\"]);\n    }\n\n    testFilenameInputBlur(\"test.foo.bar\", \"test.foo\", \"blahblah.bar\", \"blahblah\", [\"keyup\", {keyCode: $.simulate.keyCode.ENTER}]);\n    testFilenameInputBlur(\"test\", \"test\", \"blahblah\", \"blahblah\", [\"keyup\", {keyCode: $.simulate.keyCode.ENTER}]);\n\n\n    it(\"handles undefined or empty filename submitted\", function () {\n\n        var actualName = \"test.foo.bar\",\n            origNameSansExt = \"test.foo\",\n            $input = $container.find(\"INPUT\");\n\n        var handler = new qq.FilenameClickHandler({\n            templating: templating,\n            classes: {\n                file: \"test-file\"\n            },\n            onGetName: function() { return actualName; },\n            onGetUploadStatus: function(fileId) { return qq.status.SUBMITTED; },\n            onGetInput: function(item) { return $input[0]; }\n        });\n\n        //Fine Uploader would normally already have this set\n        $filenameDiv.text(actualName);\n\n        $filenameDiv.simulate(\"click\");\n\n        assert.equal($input.val(), origNameSansExt, \"filename input should equal original filename sans extension initially\");\n\n        $input.val(\"\");\n        $input.simulate(\"blur\");\n    });\n});\n"
  },
  {
    "path": "test/unit/upload-data.js",
    "content": "/* globals describe, beforeEach, afterEach, $fixture, qq, assert, it, qqtest, helpme, purl */\ndescribe(\"upload-data.js\", function () {\n    \"use strict\";\n\n    it(\"allows overriden uuids\", function() {\n        var uploadData = helpme.createUploadData();\n\n        uploadData.addFile({uuid:\"0_uuid\", name: \"name\"});\n        assert.equal(uploadData.retrieve({id: 0}).uuid, \"0_uuid\", \"checking initial uuid\");\n\n        uploadData.uuidChanged(0, \"foobar\");\n        assert.equal(uploadData.retrieve({id: 0}).uuid, \"foobar\", \"checking new uuid\");\n    });\n\n    it(\"allows override name\", function() {\n        var uploadData = helpme.createUploadData(),\n            id = uploadData.addFile({uuid: \"0_uuid\", name: \"0_name\"});\n\n        assert.equal(uploadData.retrieve({id: id}).name, \"0_name\", \"checking initial name\");\n\n        uploadData.updateName(id, \"foobar\");\n        assert.equal(uploadData.retrieve({id: id}).name, \"foobar\", \"checking new name\");\n        assert.equal(uploadData.retrieve({id: id}).originalName, \"0_name\", \"checking original name\");\n    });\n\n    it(\"resets properly\", function() {\n        var uploadData = helpme.createUploadData();\n\n        uploadData.addFile({});\n        uploadData.reset();\n        assert.equal(uploadData.retrieve().length, 0, \"ensuring upload data has been cleared\");\n    });\n\n    it(\"retrieves by id and ids\", function() {\n        var uploadData = helpme.createUploadData(),\n            id1 = uploadData.addFile({uuid: \"0_uuid\", name: \"0_name\", size: 1980}),\n            uploadDataItem = uploadData.retrieve({id: id1}),\n            uploadDataItems, id2;\n\n        assert.equal(uploadDataItem.id, id1);\n        assert.equal(uploadDataItem.uuid, \"0_uuid\");\n        assert.equal(uploadDataItem.name, \"0_name\");\n        assert.equal(uploadDataItem.size, 1980);\n\n        assert.equal(uploadData.retrieve({id: 999}), undefined);\n\n        id2 = uploadData.addFile({uuid: \"1_uuid\", name: \"1_name\"});\n        uploadDataItems = uploadData.retrieve({id: [id1, id2]});\n        assert.equal(uploadDataItems.length, 2);\n        assert.equal(uploadDataItems[0].id, id1);\n        assert.equal(uploadDataItems[1].id, id2);\n        assert.equal(uploadDataItems[0].name, \"0_name\");\n        assert.equal(uploadDataItems[1].name, \"1_name\");\n    });\n\n    it(\"test retrieve by uuid and uuids\", function() {\n        var uploadData = helpme.createUploadData(),\n            id1 = uploadData.addFile({uuid: \"0_uuid\", name: \"0_name\", size: 1980}),\n            id2, uploadDataItems;\n\n        var uploadDataItem = uploadData.retrieve({uuid: \"0_uuid\"});\n        assert.equal(uploadDataItem.id, id1);\n        assert.equal(uploadDataItem.uuid, \"0_uuid\");\n        assert.equal(uploadDataItem.name, \"0_name\");\n        assert.equal(uploadDataItem.size, 1980);\n\n        assert.equal(uploadData.retrieve({uuid: \"foobar\"}), undefined);\n\n        id2 = uploadData.addFile({uuid: \"1_uuid\", name: \"1_name\"});\n        uploadDataItems = uploadData.retrieve({uuid: [\"0_uuid\", \"1_uuid\"]});\n        assert.equal(uploadDataItems.length, 2);\n        assert.equal(uploadDataItems[0].id, id1);\n        assert.equal(uploadDataItems[1].id, id2);\n        assert.equal(uploadDataItems[0].name, \"0_name\");\n        assert.equal(uploadDataItems[1].name, \"1_name\");\n    });\n\n    it(\"retrieves by status and statuses\", function() {\n        var uploadData = helpme.createUploadData(),\n            id1 = uploadData.addFile({uuid: \"0_uuid\", name: \"0_name\", size: 1980}),\n            uploadDataItems = uploadData.retrieve({status: qq.status.SUBMITTING}),\n            id2;\n\n        id2 = uploadData.addFile({uuid: \"1_uuid\", name: \"1_name\"});\n        uploadData.setStatus(id2, qq.status.CANCELED);\n\n        assert.equal(uploadDataItems.length, 1);\n        assert.equal(uploadDataItems[0].id, id1);\n        assert.equal(uploadDataItems[0].uuid, \"0_uuid\");\n        assert.equal(uploadDataItems[0].name, \"0_name\");\n        assert.equal(uploadDataItems[0].size, 1980);\n\n        assert.equal(uploadData.retrieve({status: \"foobar\"}).length, 0);\n\n\n        uploadDataItems = uploadData.retrieve({status: [qq.status.SUBMITTING, qq.status.CANCELED]});\n        assert.equal(uploadDataItems.length, 2);\n        assert.equal(uploadDataItems[0].id, id1);\n        assert.equal(uploadDataItems[1].id, id2);\n        assert.equal(uploadDataItems[0].name, \"0_name\");\n        assert.equal(uploadDataItems[1].name, \"1_name\");\n    });\n\n    it(\"retrieves without filter\", function() {\n        var uploadData = helpme.createUploadData(),\n            id1 = uploadData.addFile({uuid: \"uuid\", name: \"name\"}),\n            id2 = uploadData.addFile({uuid: \"uuid2\", name: \"name2\"}),\n            uploadDataItems = uploadData.retrieve();\n\n        assert.equal(uploadDataItems.length, 2);\n        assert.equal(uploadDataItems[0].id, id1);\n        assert.equal(uploadDataItems[1].id, id2);\n    });\n\n    it(\"Uses passed status if available on addFile\", function() {\n        var uploadData = helpme.createUploadData(),\n            id = uploadData.addFile({uuid: \"uuid\", name: \"name\", status: qq.status.UPLOAD_SUCCESSFUL}),\n            uploadDataItems = uploadData.retrieve();\n\n        assert.equal(uploadDataItems[0].status, qq.status.UPLOAD_SUCCESSFUL);\n\n        uploadDataItems = uploadData.retrieve({status: qq.status.UPLOAD_SUCCESSFUL});\n        assert.equal(uploadDataItems[0].id, id);\n    });\n\n    it(\"Tracks proxy group files correctly\", function() {\n        var uploadData = helpme.createUploadData(),\n            groupId1 = \"a\",\n            groupId2 = \"b\",\n            actualGroup1 = [],\n            expectedGroup1 = [0, 2],\n            actualGroup2 = [],\n            expectedGroup2 = [1, 3];\n\n        actualGroup1.push(uploadData.addFile({uuid: \"uuid0\", proxyGroupId: groupId1}));\n        actualGroup2.push(uploadData.addFile({uuid: \"uuid1\", proxyGroupId: groupId2}));\n        actualGroup1.push(uploadData.addFile({uuid: \"uuid2\", proxyGroupId: groupId1}));\n        actualGroup2.push(uploadData.addFile({uuid: \"uuid3\", proxyGroupId: groupId2}));\n\n        qq.each(expectedGroup1, function(idx, id) {\n            assert.deepEqual(uploadData.getIdsInProxyGroup(id), expectedGroup1);\n        });\n\n        qq.each(expectedGroup2, function(idx, id) {\n            assert.deepEqual(uploadData.getIdsInProxyGroup(id), expectedGroup2);\n        });\n    });\n\n    it(\"Tracks batched files correctly\", function() {\n        var uploadData = helpme.createUploadData(),\n            batchId1 = \"a\",\n            batchId2 = \"b\",\n            actualBatch1 = [],\n            expectedBatch1 = [0, 2],\n            actualBatch2 = [],\n            expectedBatch2 = [1, 3];\n\n        actualBatch1.push(uploadData.addFile({uuid: \"uuid0\", batchId: batchId1}));\n        actualBatch2.push(uploadData.addFile({uuid: \"uuid1\", batchId: batchId2}));\n        actualBatch1.push(uploadData.addFile({uuid: \"uuid2\", batchId: batchId1}));\n        actualBatch2.push(uploadData.addFile({uuid: \"uuid3\", batchId: batchId2}));\n\n        qq.each(expectedBatch1, function(idx, id) {\n            assert.deepEqual(uploadData.getIdsInBatch(id), expectedBatch1);\n        });\n\n        qq.each(expectedBatch2, function(idx, id) {\n            assert.deepEqual(uploadData.getIdsInBatch(id), expectedBatch2);\n        });\n    });\n});\n\n"
  },
  {
    "path": "test/unit/uploader.basic.api.js",
    "content": "/* globals describe, beforeEach, afterEach, $fixture, qq, assert, it, qqtest, helpme, purl, Q */\ndescribe(\"uploader.basic.api.js\", function () {\n    \"use strict\";\n\n    var $uploader, fineuploader;\n\n    beforeEach(function () {\n        $fixture.append(\"<div id='fine-uploader'></div>\");\n        $uploader = $fixture.find(\"#fine-uploader\");\n    });\n\n    it(\"resets multiple buttons to their original states\", function () {\n        var $btn1 = $fixture.appendTo(\"<div></div>\"),\n            $btn2 = $fixture.appendTo(\"<div></div>\"),\n            i, button;\n\n        var fineuploader = new qq.FineUploaderBasic({\n            element: $uploader[0],\n            extraButtons: [\n                {\n                    element: $btn1[0],\n                    multiple: false,\n                    accept: \"\",\n                    fileInputTitle: \"title1\"\n                },\n                {\n                    element: $btn2[0],\n                    multiple: false,\n                    accept: \"\",\n                    fileInputTitle: \"title2\"\n                }\n            ]\n        });\n\n        for (i = 0; i < fineuploader._buttons.length - 1; i++) {\n            button = fineuploader._buttons[i];\n            button.setMultiple(true);\n            button.setAcceptFiles(\"audio/*\");\n        }\n\n        fineuploader.reset();\n\n        if (qq.supportedFeatures.ajaxUploading) {\n            for (i = 0; i < fineuploader._buttons.length - 1; i++) {\n                var input = button.getInput();\n                button = fineuploader._buttons[i];\n                assert.ok(!input.hasAttribute(\"multiple\"));\n                assert.ok(!input.hasAttribute(\"accept\"));\n            }\n            assert.equal(fineuploader._buttons[0].getInput().title, \"title1\");\n            assert.equal(fineuploader._buttons[1].getInput().title, \"title2\");\n        }\n    });\n\n    describe(\"setParams\", function () {\n\n        beforeEach(function () {\n            fineuploader = new qq.FineUploaderBasic({\n                element: $uploader[0]\n            });\n        });\n\n        it(\"resets\", function () {\n            var params = {\"hello\": \"world\"};\n            fineuploader.setParams(params, \"foo\");\n            assert.deepEqual(fineuploader._paramsStore.get(\"foo\"), params,\n                \"the request parameters should be set\");\n            fineuploader._paramsStore.reset();\n            assert.deepEqual(fineuploader._paramsStore.get(\"foo\"), {},\n                \"the request parameters should be reset\");\n        });\n\n        it(\"allows changing parameters for a specific file id\", function () {\n            var params = {\"hello\": \"world\"};\n            fineuploader.setParams(params, \"foo\");\n            assert.deepEqual(fineuploader._paramsStore.get(\"foo\"), params,\n                \"the request parameters should be set\");\n\n        });\n\n        it(\"allows changing parameters for all files\", function () {\n            var params = {\"hello\": \"world\"};\n            fineuploader.setParams(params);\n            assert.deepEqual(fineuploader._paramsStore.get(), params,\n                \"the request parameters should be set\");\n        });\n\n    });\n\n    describe(\"setEndpoint\", function () {\n        var defaultEndpoint = \"a/b/c\";\n\n        beforeEach(function () {\n\n            fineuploader = new qq.FineUploaderBasic({\n                element: $uploader[0],\n                request: {\n                    endpoint: defaultEndpoint\n                }\n            });\n        });\n\n        it(\"resets\", function () {\n            var endpoint = \"/endpoint\";\n            fineuploader.setEndpoint(endpoint, 0);\n            var ep = fineuploader._endpointStore.get(0);\n            assert.deepEqual(ep,\n                endpoint,\n                \"the endpoint should be set\");\n            fineuploader._endpointStore.reset();\n            ep = fineuploader._endpointStore.get(0);\n            assert.deepEqual(ep, fineuploader._options.request.endpoint, \"the endpoint should be reset\");\n        });\n\n        it(\"set a new endpoint\", function () {\n            var endpoint = \"/endpoint\";\n            fineuploader.setEndpoint(endpoint, 0);\n            var ep = fineuploader._endpointStore.get(0);\n\n            assert.deepEqual(ep, endpoint, \"the endpoint should be set\");\n\n            qq.each(fineuploader._extraButtonSpecs, function(id, spec) {\n                assert.equal(spec.endpoint, defaultEndpoint, \"endpoint for extra button was changed unexpectedly!\");\n            });\n        });\n    });\n\n    describe(\"_isAllowedExtension\", function() {\n        beforeEach(function () {\n            fineuploader = new qq.FineUploaderBasic();\n        });\n\n        it(\"allows files if no restrictions are in place\", function() {\n            var allowedExtensions = [];\n\n            assert.ok(fineuploader._isAllowedExtension(allowedExtensions, \"foo.bar\"));\n            assert.ok(fineuploader._isAllowedExtension(allowedExtensions, \"foo.bar.bat\"));\n            assert.ok(fineuploader._isAllowedExtension(allowedExtensions, \"foo\"));\n        });\n\n        it(\"doesn't choke if allowed extensions are not valid (i.e. not strings)\", function() {\n            var allowedExtensions = [{}];\n\n            assert.ok(!fineuploader._isAllowedExtension(allowedExtensions, \"foo.bar\"));\n            assert.ok(!fineuploader._isAllowedExtension(allowedExtensions, \"foo.bar.bat\"));\n            assert.ok(!fineuploader._isAllowedExtension(allowedExtensions, \"foo\"));\n        });\n\n        it(\"only allows valid extensions\", function() {\n            var allowedExtensions = [\"bar\", \"exe\", \"png\"];\n\n            assert.ok(fineuploader._isAllowedExtension(allowedExtensions, \"foo.bar\"));\n            assert.ok(fineuploader._isAllowedExtension(allowedExtensions, \"foo.fee.exe\"));\n            assert.ok(fineuploader._isAllowedExtension(allowedExtensions, \"png.png\"));\n            assert.ok(!fineuploader._isAllowedExtension(allowedExtensions, \"foo.bar.bat\"));\n            assert.ok(!fineuploader._isAllowedExtension(allowedExtensions, \"foo\"));\n            assert.ok(!fineuploader._isAllowedExtension(allowedExtensions, \"png\"));\n        });\n    });\n\n    describe(\"_handleCheckedCallback\", function() {\n        beforeEach(function () {\n            fineuploader = new qq.FineUploaderBasic();\n        });\n\n        it(\"handles successful non-promissory callbacks (undefined return value)\", function() {\n            var callback = function() {},\n                spec = {\n                    callback: callback,\n                    onSuccess: function(callbackRetVal) {\n                        assert.deepEqual(callbackRetVal, undefined);\n                    },\n                    onFailure: function() {\n                        assert.failure();\n                    }\n                };\n\n            fineuploader._handleCheckedCallback(spec);\n        });\n\n        it(\"handles successful non-promissory callbacks (defined return value)\", function() {\n            var callback = function() {\n                    return \"foobar\";\n                },\n                spec = {\n                    callback: callback,\n                    onSuccess: function(callbackRetVal) {\n                        assert.deepEqual(callbackRetVal, \"foobar\");\n                    }\n                };\n\n            assert.deepEqual(fineuploader._handleCheckedCallback(spec), \"foobar\");\n        });\n\n        it(\"handles failed non-promissory callbacks (defined onFailure)\", function(done) {\n            var callback = function() {\n                    return false;\n                },\n                spec = {\n                    callback: callback,\n                    onSuccess: function() {\n                        assert.fail();\n                        done();\n                    },\n                    onFailure: function() {\n                        done();\n                    }\n                };\n\n            fineuploader._handleCheckedCallback(spec);\n        });\n\n        it(\"handles failed non-promissory callbacks (undefined onFailure)\", function(done) {\n            var callback = function() {\n                    return false;\n                },\n                spec = {\n                    callback: callback,\n                    onSuccess: function() {\n                        assert.fail();\n                    }\n                };\n\n            fineuploader._handleCheckedCallback(spec);\n            done();\n        });\n\n        describe(\"handles successful promissory callbacks\", function() {\n            function runTest(callback, done) {\n                var spec = {\n                    callback: callback,\n                    onSuccess: function(passedVal) {\n                        assert.deepEqual(passedVal, \"foobar\");\n                        done();\n                    },\n                    onFailure: function() {\n                        assert.fail();\n                        done();\n                    }\n                };\n\n                fineuploader._handleCheckedCallback(spec);\n            }\n\n            it (\"qq.Promise\", function(done) {\n                var callback = function() {\n                        var promise = new qq.Promise();\n\n                        setTimeout(function() {\n                            promise.success(\"foobar\");\n                        }, 100);\n\n                        return promise;\n                    };\n\n                runTest(callback, done);\n            });\n\n            it (\"Q.js\", function(done) {\n                var callback = function() {\n                        return Q.Promise(function(resolve) {\n                            setTimeout(function() {\n                                resolve(\"foobar\");\n                            }, 100);\n                        });\n                    };\n\n                runTest(callback, done);\n            });\n        });\n\n        describe(\"handles failed promissory callbacks\", function() {\n            function runTest(callback, done) {\n                var spec = {\n                    callback: callback,\n                    onSuccess: function() {\n                        assert.fail();\n                        done();\n                    },\n                    onFailure: function() {\n                        done();\n                    }\n                };\n\n                fineuploader._handleCheckedCallback(spec);\n            }\n\n            it (\"qq.Promise\", function(done) {\n                var callback = function() {\n                        var promise = new qq.Promise();\n\n                        setTimeout(function() {\n                            promise.failure();\n                        }, 100);\n\n                        return promise;\n                    };\n\n                runTest(callback, done);\n            });\n\n            it (\"Q.js\", function(done) {\n                var callback = function() {\n                    return Q.Promise(function (resolve, reject) {\n                        setTimeout(function () {\n                            reject();\n                        }, 100);\n                    });\n                };\n\n                runTest(callback, done);\n            });\n        });\n\n        it(\"does auto retry if upload is not paused\", function() {\n            fineuploader = new qq.FineUploaderBasic({\n                element: $uploader[0],\n                retry: {\n                    enableAuto: true\n                }\n            });\n\n            fineuploader._uploadData = {\n                retrieve: function() {\n                    return {\n                        status: qq.status.UPLOADING\n                    };\n                }\n            };\n\n            assert.ok(fineuploader._shouldAutoRetry(0));\n        });\n\n        it(\"does not auto retry if upload is paused\", function() {\n            fineuploader = new qq.FineUploaderBasic({\n                element: $uploader[0],\n                retry: {\n                    enableAuto: true\n                }\n            });\n\n            fineuploader._uploadData = {\n                retrieve: function() {\n                    return {\n                        status: qq.status.PAUSED\n                    };\n                }\n            };\n\n            assert.ok(!fineuploader._shouldAutoRetry(0));\n        });\n    });\n\n    describe(\"getRemainingAllowedItems\", function() {\n        var uploader;\n\n        function setupUploader(allowedItems) {\n            uploader = new qq.FineUploaderBasic({\n                validation: {\n                    itemLimit: allowedItems\n                }\n            });\n        }\n\n        it(\"reports the correct number of remaining allowed items w/out item limit\", function() {\n            setupUploader(0);\n            uploader._netUploadedOrQueued = 3;\n\n            assert.equal(uploader.getRemainingAllowedItems(), null);\n        });\n\n        it(\"reports the correct number of remaining items w/ an item limit\", function() {\n            setupUploader(3);\n            uploader._netUploadedOrQueued = 2;\n\n            assert.equal(uploader.getRemainingAllowedItems(), 1);\n        });\n\n        it(\"allows the itemLimit to be adjusted via the API\", function() {\n            setupUploader(3);\n            uploader._netUploadedOrQueued = 2;\n\n            uploader.setItemLimit(5);\n\n            assert.equal(uploader.getRemainingAllowedItems(), 3);\n        });\n    });\n\n    describe(\"_createStore\", function() {\n        var uploader = new qq.FineUploaderBasic({});\n\n        it(\"handles non-object stores properly\", function() {\n            var initVal = \"foo\",\n                store = uploader._createStore(initVal);\n\n            assert.equal(store.get(100), initVal);\n            assert.equal(store.get(), initVal);\n\n            store.set(\"bar\", 2);\n            store.set(\"three\", 3);\n            assert.equal(store.get(2), \"bar\");\n            assert.equal(store.get(3), \"three\");\n            assert.equal(store.get(100), initVal);\n            assert.equal(store.get(), initVal);\n\n            store.remove(3);\n            assert.equal(store.get(2), \"bar\");\n            assert.equal(store.get(3), initVal);\n            assert.equal(store.get(100), initVal);\n            assert.equal(store.get(), initVal);\n\n            store.set(\"foobar\");\n            assert.equal(store.get(2), \"foobar\");\n            assert.equal(store.get(3), \"foobar\");\n            assert.equal(store.get(100), \"foobar\");\n            assert.equal(store.get(), \"foobar\");\n\n            store.reset();\n            assert.equal(store.get(2), initVal);\n            assert.equal(store.get(3), initVal);\n            assert.equal(store.get(100), initVal);\n            assert.equal(store.get(), initVal);\n        });\n\n        it(\"handles object stores properly\", function() {\n            var initVal = {foo: \"bar\"},\n                a = {a: \"a\"},\n                two = {two: \"two\"},\n                three = {three: \"three\"},\n                store = uploader._createStore(initVal);\n\n            assert.deepEqual(store.get(100), initVal);\n            assert.deepEqual(store.get(), initVal);\n\n            store.set(two, 2);\n            store.set(three, 3);\n            assert.deepEqual(store.get(2), two);\n            assert.deepEqual(store.get(3), three);\n            assert.deepEqual(store.get(100), initVal);\n            assert.deepEqual(store.get(), initVal);\n\n            three.test = \"123\";\n            assert.notDeepEqual(store.get(3), three);\n\n            store.remove(3);\n            assert.deepEqual(store.get(2), two);\n            assert.deepEqual(store.get(3), initVal);\n            assert.deepEqual(store.get(100), initVal);\n            assert.deepEqual(store.get(), initVal);\n\n            store.set(a);\n            assert.deepEqual(store.get(2), a);\n            assert.deepEqual(store.get(3), a);\n            assert.deepEqual(store.get(100), a);\n            assert.deepEqual(store.get(), a);\n\n            a.test = \"123\";\n            assert.notDeepEqual(store.get(2), a);\n\n            store.reset();\n            assert.deepEqual(store.get(2), initVal);\n            assert.deepEqual(store.get(3), initVal);\n            assert.deepEqual(store.get(100), initVal);\n            assert.deepEqual(store.get(), initVal);\n        });\n    });\n\n    describe(\"_handleNewFile\", function() {\n        it(\"ignores size property if passing a file input\", function() {\n            var uploader = new qq.FineUploaderBasic({}),\n                fileInput = document.createElement(\"input\");\n\n            uploader._customNewFileHandler = function(actualFile, name, uuid, size) {\n                assert.equal(size, -1);\n            };\n\n            fileInput.type = \"file\";\n\n            uploader._handleNewFile(fileInput, 0, []);\n        });\n    });\n\n    describe(\"_formatSize\", function() {\n        beforeEach(function () {\n            fineuploader = new qq.FineUploaderBasic();\n        });\n\n        it(\"formats 0 bytes properly\", function() {\n            var formattedSize = fineuploader._formatSize(0);\n            assert.equal(formattedSize, \"0kB\");\n        });\n\n        it(\"formats kB properly\", function() {\n            var formattedSize = fineuploader._formatSize(789);\n            assert.equal(formattedSize, \"0.8kB\");\n        });\n\n        it(\"formats MB properly\", function() {\n            var formattedSize = fineuploader._formatSize(2123456);\n            assert.equal(formattedSize, \"2.1MB\");\n        });\n\n        it(\"formats GB properly\", function() {\n            var formattedSize = fineuploader._formatSize(9602123456);\n            assert.equal(formattedSize, \"9.6GB\");\n        });\n    });\n\n    describe(\"_validateFileOrBlobData\", function() {\n        var originalFileOrInput = qq.isFileOrInput;\n        beforeEach(function () {\n            fineuploader = new qq.FineUploaderBasic();\n        });\n        afterEach(function() {\n            qq.isFileOrInput = originalFileOrInput;\n        });\n\n        it(\"fails if file is empty and allowEmpty is false\", function(done) {\n            qq.isFileOrInput = function() { return true; };\n            fineuploader._fileOrBlobRejected = function() {};\n            var validationDescriptor = { size: 0 };\n\n            fineuploader._validateFileOrBlobData({}, validationDescriptor)\n                .then(function() { assert.fail(); }, function() { done(); });\n        });\n\n        it(\"passes if file is empty and allowEmpty is true\", function(done) {\n            fineuploader._options.validation.allowEmpty = true;\n            qq.isFileOrInput = function() { return true; };\n            var validationDescriptor = { size: 0 };\n\n            fineuploader._validateFileOrBlobData({}, validationDescriptor)\n                .then(function() { done(); }, function() { assert.fail(); });\n        });\n    });\n});\n"
  },
  {
    "path": "test/unit/util.js",
    "content": "/* globals describe, beforeEach, afterEach, $fixture, qq, assert, it, qqtest, helpme, purl, File */\ndescribe(\"util.js\", function () {\n    \"use strict\";\n\n    var el, $el;\n\n    describe(\"hide\", function () {\n        it(\"properly hide an element\", function () {\n            $fixture.append(\"<div id='foo'></div>\");\n            $el = $fixture.find(\"#foo\");\n            el = qq($el[0]);\n\n            //assert.notEqual($el.css(display), \"none\", \"element should not be hidden\");\n            el.hide();\n            assert.equal($el.css(\"display\"), \"none\", \"element should be hidden\");\n        });\n    }); // hide\n\n    describe(\"detach\",function () {\n        it(\"detaches an event from an element\", function () {\n            $fixture.append(\"<div id='foo'></div>\");\n            $el = $fixture.find(\"#foo\");\n            el = qq($el[0]);\n\n            var detacher = el.attach(\"click\", function () {\n                return false;\n            });\n\n            $el.trigger(\"click\");\n        });\n    }); // detach\n\n    describe(\"contains\", function () {\n        it(\"returns true if the element contains itself\", function () {\n            $fixture.append(\"<div id='foo'></div>\");\n            $el = $fixture.find(\"#foo\");\n            el = $el[0];\n\n            assert.ok(qq(el).contains(el), \"the element should contain itself\");\n        });\n\n        it(\"return true if the element contains the descendant\", function () {\n            $fixture.append(\"<div id='foo'></div>\");\n            $el = $fixture.find(\"#foo\");\n            el = $el[0];\n\n            assert.ok(qq($fixture[0]).contains(el), \"$el is a descendant of $fixture\");\n        });\n\n        it(\"returns false if the element does not contain the descendant\", function () {\n            $fixture.append(\"<div id='foo'></div>\");\n            $el = $fixture.find(\"#bar\");\n            el = $el[0];\n\n            assert.ok(!qq($fixture[0]).contains(el), \"$el is not a descendant of $fixture\");\n        });\n\n        it(\"#887 - accounts for IE7 bug in Node.contains\", function () {\n            $fixture.append(\"<div id='foo'></div>\");\n            $el = $fixture.find(\"#foo\");\n            el = $el[0];\n\n            assert.ok(!qq(el).contains(null), \"should return false when passed a `null` parameter\");\n            assert.ok(!qq(el).contains(undefined), \"should return false when passed an `undefined` parameter\");\n        });\n    }); // contains\n\n    describe(\"insertBefore\", function () {\n        it(\"inserts an element before another\", function () {\n            var elB, $elB;\n\n            $elB = $(\"<div id='foo'></div>\").appendTo($fixture);\n            elB = $elB[0];\n\n            $el = $(\"<div/>\").html(\"<div id='bar'></div>\").contents();\n            el = $el[0];\n\n            // insert `el` before `elB`\n            qq(el).insertBefore(elB);\n\n            assert.ok($fixture.find(\"#bar\").length > 0, \"should have inserted #bar before #foo\");\n        });\n    }); // insertBefore\n\n    describe(\"remove\", function () {\n        it(\"removes an element from the DOM\", function () {\n            var elB, $elB;\n\n            $fixture.append(\"<div id='foo'></div>\");\n\n            $el = $fixture.find(\"#foo\");\n            $el.append(\"<div id='bar'></div>\");\n            el = $el[0];\n\n            $elB = $el.find(\"#bar\");\n            elB = $elB[0];\n\n            qq(el).remove(elB);\n            assert.equal($fixture.find(\"#bar\").length, 0, \"#bar should have been removed\");\n        });\n    });\n\n    describe(\"hasClass\", function () {\n        it(\"asks an element whether it has a class\", function () {\n            el = document.createElement(\"div\");\n            $(el).addClass(\"derp\");\n\n            assert.ok(qq(el).hasClass(\"derp\"), \"el should have the 'derp' class\");\n            assert.ok(!qq(el).hasClass(\"herp\"), \"el should not have the class 'herp'\");\n        });\n    }); // hasClass\n\n    describe(\"addClass\", function () {\n        it(\"adds a class to an element\", function () {\n            el = document.createElement(\"div\");\n\n            assert.ok(!$(el).hasClass(\"derp\"), \"element should NOT have the 'derp' class\");\n\n            qq(el).addClass(\"derp\");\n\n            assert.ok($(el).hasClass(\"derp\"), \"element should have the 'derp' class\");\n        });\n    }); // addClass\n\n    describe(\"removeClass\", function () {\n        it(\"removes a class from an element\", function () {\n            el = document.createElement(\"div\");\n            $(el).addClass(\"derp\");\n            qq(el).removeClass(\"derp\");\n\n            assert.ok(!$(el).hasClass(\"derp\"), \"class should have been removed from the element\");\n        });\n    }); // hasClass\n\n    describe(\"getByClass\", function () {\n        it(\"gets a list of elements by class\", function () {\n            var results, q;\n\n            results = [];\n            $fixture.empty();\n            q = qq($fixture[0]);\n            $fixture.append(\"<div class='foo'></div>\");\n            $fixture.append(\"<div class='bar'></div>\");\n            $fixture.append(\"<div class='foo bar'></div>\");\n\n            results = q.getByClass(\"foo\");\n\n            assert.equal(results.length, 2, \"getting the wrong number of classes\");\n\n            results = q.getByClass(\"bar\");\n\n            assert.equal(results.length, 2, \"getting the wrong number of classes\");\n        });\n\n    }); // getByClass\n\n    describe(\"getFirstByClass\", function () {\n        it(\"gets the first element that matches a specific class\", function () {\n            var result, q;\n\n            $fixture.empty();\n            q = qq($fixture[0]);\n            $fixture.append(\"<div class='foo one'></div>\");\n            $fixture.append(\"<div class='bar two'></div>\");\n            $fixture.append(\"<div class='foo bar three'></div>\");\n\n            result = q.getFirstByClass(\"foo\");\n            assert.ok(qq(result).hasClass(\"one\"), \"wrong element\");\n\n            result = q.getFirstByClass(\"bar\");\n            assert.ok(qq(result).hasClass(\"two\"), \"wrong element\");\n        });\n\n    });\n\n    describe(\"children\", function () {\n        it(\"returns a list of children of an element\", function () {\n            var results, q;\n\n            results = [];\n            $fixture.empty();\n            q = qq($fixture[0]);\n            $fixture.append(\"<div class='foo'></div>\");\n            $fixture.append(\"<div class='bar'></div>\");\n            $fixture.append(\"<div class='foo bar'></div>\");\n\n            results = q.children();\n            assert.equal(results.length, 3, \"was expecting 3 children\");\n        });\n    }); // children\n\n    describe(\"setText\", function () {\n        it(\"sets the inner text of an element\", function () {\n            var text = \"Herp Derp\";\n            el = document.createElement(\"p\");\n            qq(el).setText(text);\n\n            assert.equal($(el).text(), text, \"text should have been set to \" + text);\n        });\n    }); // setText\n\n    describe(\"clearText\", function () {\n        it(\"clears any text set on an element\", function () {\n            var text = \"Herp Derp\";\n            el = document.createElement(\"p\");\n            qq(el).setText(text);\n            qq(el).clearText();\n\n            assert.equal($(el).text(), \"\", \"text was not cleared\");\n        });\n    }); // clearText\n\n    describe(\"isObject\", function () {\n        it(\"returns true for an empty object\", function () {\n            assert.ok(qq.isObject({}), \"empty objects are objects\");\n        });\n\n        it(\"returns true for a simple object\", function () {\n            assert.ok(qq.isObject({ foo: \"bar\" }), \"simple objects are objects\");\n        });\n\n        it(\"should return true for a newed up Object\", function() {\n            /* jshint -W010 */\n            assert.ok(qq.isObject(new Object()), \"new objects are objects\");\n        });\n\n        it(\"should return false for a function\", function () {\n            assert.ok(!qq.isObject(function(){}), \"This is not Ruby. Functions are not objects\");\n        });\n\n        it(\"should return false for null\", function () {\n            assert.ok(!qq.isObject(null), \"the null is not an object\");\n        });\n\n        it(\"should return false for an array\", function () {\n            assert.ok(!qq.isObject([]), \"arrays are not objects\");\n        });\n\n        it(\"should return undefined for an undefined\", function () {\n            assert.ok(!qq.isObject(undefined), \"\");\n        });\n    }); // isObject\n\n    describe(\"isFunction\", function () {\n        it (\"returns true for an empty simple function\", function () {\n            assert.ok(qq.isFunction(function() {}));\n        });\n\n        it(\"returns false for an Object\", function () {\n            assert.ok(!qq.isFunction({}));\n        });\n    }); // isFunction\n\n    describe(\"isArray\", function () {\n        it(\"returns true for an empty array\", function () {\n            assert.ok(qq.isArray([]));\n        });\n\n        it(\"returns true for a basic array\", function () {\n            assert.ok(qq.isArray([1, \"foo\", { herp: \"derp\" }]));\n        });\n\n        it(\"returns false for a string\", function () {\n            assert.ok(!qq.isArray(\"Herp derp\"));\n        });\n\n        if (window.Uint8Array) {\n            it(\"returns true for an ArrayBuffer\", function() {\n                var uint8array = new Uint8Array();\n                assert.ok(qq.isArray(uint8array));\n            });\n        }\n\n    }); // isArray\n\n    // template\n    describe(\"isString\", function () {\n        it(\"returns true for the empty string\", function () {\n            assert.ok(qq.isString(\"\"), \"the empty string IS a string\");\n        });\n\n        it(\"should return true for a string with characters\", function () {\n            assert.ok(qq.isString(\"Herp derp\"), \"strings are strings\");\n        });\n    }); // isString\n\n    describe(\"trimStr\", function () {\n        it(\"trims around string\", function () {\n            assert.equal(qq.trimStr(\" blah \"), \"blah\");\n        });\n\n        it(\"trims after string\", function () {\n            assert.equal(qq.trimStr(\"blah \"), \"blah\");\n        });\n\n        it(\"trims before string\", function () {\n            assert.equal(qq.trimStr(\" blah\"), \"blah\");\n        });\n\n        it(\"trims with nothing to trim\", function () {\n            assert.equal(qq.trimStr(\"blah\"), \"blah\");\n        });\n\n        it(\"trimStr - can trim a string with many spaces everywhere\", function () {\n            assert.equal(qq.trimStr(\"bl a h\"), \"bl a h\");\n        });\n\n        it(\"trimStr - can trim the empty string\", function () {\n            assert.equal(qq.trimStr(\"\"), \"\");\n        });\n    }); // trimStr\n\n    describe(\"isInput\", function () {\n        it(\"detects and identifies an input of type file\", function () {\n            var input;\n\n            $fixture.append(\"<input id='foo' type='file'>\");\n            input = $fixture.find(\"#foo\")[0];\n            assert.ok(qq.isInput(input));\n        });\n    }); // isInput\n\n    describe(\"extend\", function () {\n        it(\"extends simple objects\", function () {\n            var testy =\n            {   one: \"one\",\n                two: \"two\",\n                three: \"three\",\n                four: {\n                    a: \"a\",\n                    b: \"b\"\n                }};\n\n            var five = { five: \"five\" };\n            var four_1 = { four: { c: \"c\" }};\n            var four_2 = { four: { d: \"d\" }};\n            var new_testy = qq.extend(testy, five);\n            assert.deepEqual(new_testy.one, testy.one);\n            assert.deepEqual(new_testy.two, testy.two);\n            assert.deepEqual(new_testy.three, testy.three);\n            assert.deepEqual(new_testy.four, testy.four);\n            assert.deepEqual(new_testy.five, testy.five);\n        });\n\n        it(\"extends nested objects\", function () {\n            var testy =\n            {   one: \"one\",\n                two: \"two\",\n                three: \"three\",\n                four: {\n                    a: \"a\",\n                    b: \"b\"\n                }};\n\n            var five = { five: \"five\" };\n            var four_1 = { four: { c: \"c\" }};\n            var four_2 = { four: { d: \"d\" }};\n            var new_testy = qq.extend(testy, four_1, true);\n            assert.deepEqual(new_testy.one, testy.one);\n            assert.deepEqual(new_testy.two, testy.two);\n            assert.deepEqual(new_testy.three, testy.three);\n            assert.deepEqual(new_testy.four.a, testy.four.a);\n            assert.deepEqual(new_testy.four.b, testy.four.b);\n            assert.deepEqual(new_testy.four.c, testy.four.c);\n        });\n\n        it(\"extends non-nested objects\", function () {\n            var testy =\n            {   one: \"one\",\n                two: \"two\",\n                three: \"three\",\n                four: {\n                    a: \"a\",\n                    b: \"b\"\n                }};\n\n            var five = { five: \"five\" };\n            var four_1 = { four: { c: \"c\" }};\n            var four_2 = { four: { d: \"d\" }};\n            var new_testy = qq.extend(testy, four_2);\n            assert.deepEqual(new_testy.one, testy.one);\n            assert.deepEqual(new_testy.two, testy.two);\n            assert.deepEqual(new_testy.three, testy.three);\n            assert.deepEqual(new_testy.four.d, testy.four.d);\n        });\n    }); // extend\n\n    describe(\"indexOf\", function () {\n        it(\"returns true for a string that is present\", function () {\n            var obj = { foo: \"bar\" };\n            var arr = [\"a\", obj, 3];\n            assert.equal(qq.indexOf(arr, \"a\"), 0);\n        });\n\n        it(\"returns true for an object that is present\", function () {\n            var obj = { foo: \"bar\" };\n            var arr = [\"a\", obj, 3];\n            assert.equal(qq.indexOf(arr, obj), 1);\n        });\n\n        it(\"returns true for a number that is present\", function () {\n            var obj = { foo: \"bar\" };\n            var arr = [\"a\", obj, 3];\n            assert.equal(qq.indexOf(arr, 3), 2);\n        });\n\n        it(\"returns false for an object that is not present due to strict assert.equals\", function () {\n            var obj = { foo: \"bar\" };\n            var arr = [\"a\", obj, 3];\n            assert.equal(qq.indexOf(arr, { foo: \"bar\" }), -1);\n        });\n\n        it(\"returns false for an object that is not present at all\", function () {\n            var obj = { foo: \"bar\" };\n            var arr = [\"a\", obj, 3];\n            assert.equal(qq.indexOf(arr, 4), -1);\n        });\n    }); // indexOf\n\n    describe(\"getUniqueId\", function () {\n        it(\"no collisions for 10000 generations\", function () {\n            var bucket = [];\n            // generate a bucket of 1000 unique ids\n            for (var i = 0; i < 10000; i++) {\n                bucket[i] = qq.getUniqueId();\n            }\n\n            // check for duplicates\n            bucket.sort();\n            var last = bucket[0];\n            for (var j = 1; j < bucket.length; j++) {\n                assert.notEqual(bucket[j], last);\n                last = bucket[j];\n            }\n        });\n    }); // getUniqueId\n\n    describe(\"each\", function () {\n        it(\"provides value and iteration count \", function () {\n            qq.each([0, 1, 2], function (i, num) {\n                assert.equal(i, num);\n            });\n        });\n\n        it(\"allows iterating over objects\", function () {\n            var answers = [];\n            var obj = { one: 1, two: 2, three: 3 };\n\n            qq.each(obj, function (key, value) { answers.push(key); });\n            assert.equal(answers.join(\", \"), \"one, two, three\");\n\n            answers = [];\n            qq.each(obj, function (key, value) { answers.push(value); });\n            assert.equal(answers.join(\", \"), \"1, 2, 3\");\n        });\n\n        it(\"allows iterating over arrays\", function () {\n            var answers = [];\n            var arr = [\"one\", \"two\", \"three\"];\n\n            qq.each(arr, function (key, value) { answers.push(value); });\n            assert.equal(answers.join(\", \"), \"one, two, three\");\n        });\n\n        it(\"handles a null properly\", function () {\n            var answers = 0;\n            qq.each(null, function () { ++answers; });\n            assert.equal(0, answers);\n        });\n\n        it(\"handle strings properly\", function() {\n            var answers = [];\n            var str = \"hello!\";\n\n            qq.each(str, function (key, value) { answers.push(value); });\n            assert.equal(answers.join(\", \"), \"h, e, l, l, o, !\");\n        });\n    }); // each\n\n    describe(\"bind\", function () {\n        it(\"binds a function to a context\", function () {\n            var context = { foo: \"bar\" };\n            var func = function (arg) { return \"foo: \" + (this.foo || arg); };\n            var bound = qq.bind(func, context);\n            assert.equal(bound(), \"foo: bar\");\n        });\n\n        it(\"binds a function without a context\", function () {\n            var context = { foo: \"bar\" };\n            var func = function (arg) { return \"foo: \" + (this.foo || arg); };\n            var bound = qq.bind(func, context, \"bar\");\n            assert.equal(bound(), \"foo: bar\");\n        });\n    }); // bind\n\n    describe(\"obj2url\", function () {\n        it(\"obj2url - can construct a URL with a basic object as param\", function () {\n            var baseUrl = \"http://mydomain.com/upload\";\n            var urlWithEncodedPath = \"http://mydomain.com/upload%20me\";\n            var params1 = { one: \"one\", two: \"two\", three: \"three\" };\n            var params2 = { a: \"this is a it\" };\n            var params3 = { a : { b: \"innerProp\" }};\n            var params4 = { a: function () { return \"funky\"; }};\n            var varUrl = qq.obj2url(params2, baseUrl);\n            var controlUrl = purl(varUrl);\n\n            assert.equal(controlUrl.param(\"a\"), \"this is a it\");\n        });\n\n        it(\"obj2url - can construct a URL with a basic object as params\", function () {\n            var baseUrl = \"http://mydomain.com/upload\";\n            var urlWithEncodedPath = \"http://mydomain.com/upload%20me\";\n            var params1 = { one: \"one\", two: \"two\", three: \"three\" };\n            var params2 = { a: \"this is a it\" };\n            var params3 = { a : { b: \"innerProp\" }};\n            var params4 = { a: function () { return \"funky\"; }};\n            var varUrl = qq.obj2url(params1, baseUrl);\n            var controlUrl = purl(varUrl);\n\n            assert.equal(controlUrl.param(\"one\"), \"one\");\n            assert.equal(controlUrl.param(\"two\"), \"two\");\n            assert.equal(controlUrl.param(\"three\"), \"three\");\n        });\n\n        it(\"obj2url - can construct a URL with an embedded object as a param value\", function () {\n            var baseUrl = \"http://mydomain.com/upload\";\n            var urlWithEncodedPath = \"http://mydomain.com/upload%20me\";\n            var params1 = { one: \"one\", two: \"two\", three: \"three\" };\n            var params2 = { a: \"this is a it\" };\n            var params3 = { a : { b: \"innerProp\" }};\n            var params4 = { a: function () { return \"funky\"; }};\n            var varUrl = qq.obj2url(params3, baseUrl);\n            var controlUrl = purl(varUrl);\n\n            assert.equal(controlUrl.param(\"a\").b, \"innerProp\");\n        });\n\n        it(\"obj2url - can construct a URL with a function as a param value\", function () {\n            var baseUrl = \"http://mydomain.com/upload\";\n            var urlWithEncodedPath = \"http://mydomain.com/upload%20me\";\n            var params1 = { one: \"one\", two: \"two\", three: \"three\" };\n            var params2 = { a: \"this is a it\" };\n            var params3 = { a : { b: \"innerProp\" }};\n            var params4 = { a: function () { return \"funky\"; }};\n            var varUrl = qq.obj2url(params4, baseUrl);\n            var controlUrl = purl(varUrl);\n\n            assert.equal(controlUrl.param(\"a\"), \"funky\");\n        });\n\n        it(\"obj2url - can construct an empty URL with params\", function () {\n            var baseUrl = \"http://mydomain.com/upload\";\n            var urlWithEncodedPath = \"http://mydomain.com/upload%20me\";\n            var params1 = { one: \"one\", two: \"two\", three: \"three\" };\n            var params2 = { a: \"this is a it\" };\n            var params3 = { a : { b: \"innerProp\" }};\n            var params4 = { a: function () { return \"funky\"; }};\n            var varUrl = qq.obj2url(params1, \"\");\n\n            assert.equal(varUrl, \"one=one&two=two&three=three\");\n        });\n\n        it(\"obj2url - will leave encoded paths alone\", function () {\n            var baseUrl = \"http://mydomain.com/upload\";\n            var urlWithEncodedPath = \"http://mydomain.com/upload%20me\";\n            var params1 = { one: \"one\", two: \"two\", three: \"three\" };\n            var params2 = { a: \"this is a it\" };\n            var params3 = { a : { b: \"innerProp\" }};\n            var params4 = { a: function () { return \"funky\"; }};\n            var varUrl = qq.obj2url(params1, urlWithEncodedPath);\n            var regex = new RegExp(\"^\" + urlWithEncodedPath);\n\n            assert.ok(varUrl.match(regex));\n        });\n    }); // obj2url\n\n    describe(\"obj2FormData\", function () {\n        it(\"constructs a URL with a basic object as param\", function () {\n            var formData = (function () {\n                var data = {};\n                return {\n                    append: function (k, v) {\n                        data[decodeURIComponent(k)] = decodeURIComponent(v);\n                    },\n                    get: function (k) {\n                        return data[k];\n                    },\n                    clear: function() {\n                        /* jshint boss:true */\n                        return (data = []);\n                    }\n                };\n            }());\n\n            var params1 = { one: \"one\", two: \"two\", three: \"three\" };\n            var params2 = { a : { b: \"innerProp\" }};\n            var params3 = { a: function () { return \"funky\"; }};\n\n            assert.equal(qq.obj2FormData(params1, formData).get(\"one\"), \"one\");\n            assert.equal(qq.obj2FormData(params1, formData).get(\"two\"), \"two\");\n            assert.equal(qq.obj2FormData(params1, formData).get(\"three\"), \"three\");\n\n            formData.clear();\n        });\n\n        it(\"constructs a URL with an embedded object as param\", function () {\n            var formData = (function () {\n                var data = {};\n                return {\n                    append: function (k, v) {\n                        data[decodeURIComponent(k)] = decodeURIComponent(v);\n                    },\n                    get: function (k) {\n                        return data[k];\n                    },\n                    clear: function() {\n                        /* jshint boss:true */\n                        return (data = []);\n                    }\n                };\n            }());\n            var params1 = { one: \"one\", two: \"two\", three: \"three\" };\n            var params2 = { a : { b: \"innerProp\" }};\n            var params3 = { a: function () { return \"funky\"; }};\n\n            assert.equal(qq.obj2FormData(params2, formData).get(\"a[b]\"), \"innerProp\");\n        });\n\n        it(\"constructs a URL with a function as param\", function () {\n            var formData = (function () {\n                var data = {};\n                return {\n                    append: function (k, v) {\n                        data[decodeURIComponent(k)] = decodeURIComponent(v);\n                    },\n                    get: function (k) {\n                        return data[k];\n                    },\n                    clear: function() {\n                        /* jshint boss:true */\n                        return (data = []);\n                    }\n                };\n            }());\n            var params1 = { one: \"one\", two: \"two\", three: \"three\" };\n            var params2 = { a : { b: \"innerProp\" }};\n            var params3 = { a: function () { return \"funky\"; }};\n\n            assert.equal(qq.obj2FormData(params3, formData).get(\"a\"), \"funky\");\n        });\n    }); // obj2FormData\n\n    describe(\"parseJson\", function () {\n        it(\"parses JSON\", function () {\n            var object = { a: \"a\", b: \"b\"},\n                json = JSON.stringify(object),\n                parsedJson = JSON.parse(json);\n\n            assert.deepEqual(qq.parseJson(json), parsedJson);\n        });\n    }); // parseJson\n\n    describe(\"isFileOrInput\", function () {\n        it(\"detects and identifies an input of type file\", function () {\n            var input;\n\n            $fixture.append(\"<input id='foo' type='file'>\");\n            input = $fixture.find(\"#foo\")[0];\n\n            assert.ok(qq.isFileOrInput(input));\n        });\n        it(\"returns false on a regular input element\", function () {\n            var $input;\n\n            $fixture.append(\"<input id='bar'>\");\n            $input = $fixture.find(\"#bar\");\n\n            assert.ok(!qq.isFileOrInput($input[0]), \"must be a file input\");\n        });\n\n        it(\"returns true for a file-input field\", function () {\n            var $input;\n\n            $fixture.append(\"<input id='bar2' type='file'>\");\n            $input = $fixture.find(\"#bar2\");\n\n            assert.ok(qq.isFileOrInput($input[0]), \"this is a file input\");\n        });\n\n        it(\"isFileOrInput - should return false on a div element\", function () {\n            var $input;\n\n            $fixture.append(\"<div id='foo'></div>\");\n            $input = $fixture.find(\"#foo\");\n\n            assert.ok(!qq.isFileOrInput($input[0]), \"div is not an input\");\n        });\n    }); // isFileOrInput\n\n    describe(\"isInput\", function () {\n        it(\"returns true on an input element\", function () {\n            var $el = $(\"<input id='foo' type='file'>\").appendTo($fixture);\n            var el = $el[0];\n\n            assert.ok(qq.isInput(el), \"inputs are inputs\");\n        });\n\n        it(\"isInput - should return false on a div\", function () {\n            var $el = $(\"<div id='foo'></div>\").appendTo($fixture);\n            var el = $el[0];\n\n            assert.ok(!qq.isInput(el), \"divs are not inputs\");\n        });\n    }); //\n\n    describe(\"getExtension\", function() {\n        it(\"extract extension from file when an extension exists\", function() {\n            var filename = \"foo.bar.txt\";\n            assert.equal(qq.getExtension(filename), \"txt\");\n        });\n\n        it(\"extract extension from file when an extension does not exist\", function() {\n            var filename = \"foo\";\n            assert.equal(qq.getExtension(filename), undefined);\n        });\n    });\n\n    describe(\"DisposeSupport\", function () {\n        it(\"adds disposers and dispose of them\", function () {\n            var disposer = new qq.DisposeSupport();\n            disposer.addDisposer(function () {\n                assert.ok(true);\n            });\n\n            disposer.dispose();\n        });\n\n        it(\"attaches event handler and register de-attacher as disposer\", function () {\n            var disposer;\n            disposer = new qq.DisposeSupport();\n            el = document.createElement(\"div\");\n            $fixture.append(el);\n\n            disposer.attach(el, \"click\", function () { assert.ok(true); });\n            $(el).click();\n        });\n    }); //\n\n    describe(\"hasAttribute\", function() {\n        it(\"should return true if the attribute exists w/ no specified value\", function() {\n            var el = $(\"<div test-attr></div>\")[0];\n            assert.ok(qq(el).hasAttribute(\"test-attr\"));\n        });\n\n        it(\"should return true if the attribute exists w/ anything other than false as the value\", function() {\n            var el1 = $(\"<div test-attr=''></div>\")[0],\n                el2 = $(\"<div test-attr='foobar'></div>\")[0],\n                el3 = $(\"<div test-attr='true'></div>\")[0],\n                el4 = $(\"<div test-attr='truefalse'></div>\")[0];\n\n            assert.ok(qq(el1).hasAttribute(\"test-attr\"), \"empty string test\");\n            assert.ok(qq(el2).hasAttribute(\"test-attr\"), \"not false value test\");\n            assert.ok(qq(el3).hasAttribute(\"test-attr\"), \"true value test\");\n            assert.ok(qq(el4).hasAttribute(\"test-attr\"), \"whole string should be 'false'\");\n\n        });\n\n        it(\"should return false if the attribute does not exist\", function() {\n            var el = $(\"<div></div>\")[0];\n            assert.ok(!qq(el).hasAttribute(\"test-attr\"));\n        });\n\n        it(\"should return false if the value is false\", function() {\n            var el1 = $(\"<div test-attr='false'></div>\")[0],\n                el2 = $(\"<div test-attr='fAlse'></div>\")[0];\n\n            assert.ok(!qq(el1).hasAttribute(\"test-attr\"), \"all lowercase false test\");\n            assert.ok(!qq(el2).hasAttribute(\"test-attr\"), \"mixed case false test\");\n        });\n    });\n\n    describe(\"qq.format\", function() {\n        it(\"should format a simple string correctly\", function() {\n            assert.equal(qq.format(\"hello {}, how are {}?\", \"ray\", \"you\"), \"hello ray, how are you?\");\n        });\n\n        it (\"should format a string correctly even if it contains  injected values with curly brackets\", function() {\n            assert.equal(qq.format(\"hello {}, how are {}?\", \"{} ray\", \"you\"), \"hello {} ray, how are you?\");\n            assert.equal(qq.format(\"hello {}, how are {}?\", \"{} ray{}\", \"{you}\"), \"hello {} ray{}, how are {you}?\");\n        });\n\n        it(\"should fail gracefully if not enough tokens to replace\", function() {\n            assert.equal(qq.format(\"hello {}, how are {}?\", \"ray\"), \"hello ray, how are {}?\");\n        });\n\n        it(\"should format a string correctly with tokens at start or end\", function() {\n            assert.equal(qq.format(\"{} was here {}\", \"ray\", \"today\"), \"ray was here today\");\n        });\n\n        it(\"should format a string correctly with non-strings as parameters\", function() {\n            assert.equal(qq.format(\"Number: {}, boolean: {}, Object: {}, Array: {}\", 1, true, {one: \"two\"}, [1,2,3]), \"Number: 1, boolean: true, Object: [object Object], Array: 1,2,3\");\n        });\n    });\n\n    describe(\"qq.isFolderDropSupported\", function() {\n        it(\"should return false if DataTransfer doesn't contain an items array\", function() {\n            assert.ok(!qq.isFolderDropSupported({}));\n        });\n\n        it(\"should return false if DataTransfer does contain an items array, but it's empty\", function() {\n            assert.ok(!qq.isFolderDropSupported({items: []}));\n        });\n    });\n}); // Util\n\n"
  },
  {
    "path": "test/unit/validation.image.js",
    "content": "/* globals describe, beforeEach, afterEach, $fixture, qq, assert, it, qqtest, helpme, purl */\n// The file fails to download when using the Android emulator via Selenium, so we have to exclude it here.\nif (qq.supportedFeatures.imageValidation && qqtest.canDownloadFileAsBlob) {\n    describe(\"validation.image.js\", function() {\n        \"use strict\";\n\n        this.timeout(4000);\n\n        var testImgKey = \"GPN-2000-001635.jpg\",\n            testImgType = \"image/jpeg\",\n            testImgWidth = 615,\n            testImgHeight = 484,\n            log = function() {};\n\n        function validate(done, expectedErrorCode, limits) {\n            qqtest.downloadFileAsBlob(testImgKey, testImgType).then(function(blob) {\n                var imageValidator = new qq.ImageValidation(blob, log),\n                    result = imageValidator.validate(limits);\n\n                result.then(function() {\n                    if (expectedErrorCode) {\n                        assert.ok(false);\n                    }\n                    else {\n                        assert.ok(true);\n                    }\n\n                    done();\n                },\n                function(code) {\n                    assert.equal(code, expectedErrorCode);\n                    done();\n                });\n            });\n        }\n\n        it(\"Accepts an image with no limits\", function(done) {\n            validate(done, null, {});\n        });\n\n        it(\"Accepts an image with all zero limits\", function(done) {\n            validate(done, null, {\n                maxWidth: 0,\n                maxHeight: 0,\n                minWidth: 0,\n                minHeight: 0\n            });\n        });\n\n        it(\"Rejects an image that is too tall\", function(done) {\n            validate(done, \"maxHeight\", {\n                maxHeight: testImgHeight - 1,\n                maxWidth: testImgWidth\n            });\n        });\n\n        it(\"Rejects an image that is too wide\", function(done) {\n            validate(done, \"maxWidth\", {\n                maxHeight: testImgHeight,\n                maxWidth: testImgWidth - 1\n            });\n        });\n\n        it(\"Rejects an image that is not tall enough\", function(done) {\n            validate(done, \"minHeight\", {\n                minHeight: testImgHeight + 1,\n                minWidth: testImgWidth\n            });\n        });\n\n        it(\"Rejects an image that is not wide enough\", function(done) {\n            validate(done, \"minWidth\", {\n                minHeight: testImgHeight,\n                minWidth: testImgWidth + 1\n            });\n        });\n    });\n}\n"
  },
  {
    "path": "test/unit/workarounds.js",
    "content": "/* globals describe, beforeEach, afterEach, qq, assert, it, $fixture */\ndescribe(\"browser-specific workarounds\", function() {\n    \"use strict\";\n\n    var $fineUploader, $button, $extraButton, $extraButton2, $extraButton3;\n\n    function getFileInput($containerEl) {\n        return $containerEl.find(\"INPUT\")[0];\n    }\n\n    beforeEach(function () {\n        $fixture.append(\"<div id='fine-uploader'></div>\");\n        $fineUploader = $fixture.find(\"#fine-uploader\");\n\n        $fixture.append(\"<div id='test-button'></div>\");\n        $button = $fixture.find(\"#test-button\");\n    });\n\n    describe(\"iOS8 WebView & Chrome browser crash\", function() {\n        var origIos8 = qq.ios8,\n            origIosChrome = qq.iosChrome,\n            origSafariWebView = qq.iosSafariWebView;\n\n        beforeEach(function() {\n            qq.ios8 = function() {return true;};\n        });\n\n        afterEach(function() {\n            qq.ios8 = origIos8;\n            qq.iosChrome = origIosChrome;\n            qq.iosSafariWebView = origSafariWebView;\n        });\n\n        it(\"ensures the file input always contains a multiple attr in iOS8 Chrome\", function() {\n            qq.iosChrome = function() {return true;};\n\n            var uploader = new qq.FineUploaderBasic({\n                element: $fixture[0],\n                button: $button[0],\n                multiple: false,\n                workarounds: {\n                    ios8BrowserCrash: true,\n                    iosEmptyVideos: false\n                }\n            });\n\n            assert.equal(qq(getFileInput($button)).hasAttribute(\"multiple\"), true);\n        });\n\n        it(\"ensures the file input does not have a multiple attr if the multiple option is not set in iOS8 Chrome & the workaround is disabled\", function() {\n            qq.iosChrome = function() {return true;};\n\n            var uploader = new qq.FineUploaderBasic({\n                element: $fixture[0],\n                button: $button[0],\n                multiple: false,\n                workarounds: {\n                    ios8BrowserCrash: false,\n                    iosEmptyVideos: false\n                }\n            });\n\n            assert.equal(qq(getFileInput($button)).hasAttribute(\"multiple\"), false);\n        });\n\n        it(\"ensures the file input always contains a multiple attr in iOS8 UIWebView\", function() {\n            qq.iosSafariWebView = function() {return true;};\n\n            var uploader = new qq.FineUploaderBasic({\n                element: $fixture[0],\n                button: $button[0],\n                multiple: false,\n                workarounds: {\n                    ios8BrowserCrash: true,\n                    iosEmptyVideos: false\n                }\n            });\n\n            assert.equal(qq(getFileInput($button)).hasAttribute(\"multiple\"), true);\n        });\n\n        it(\"ensures the file input does not have a multiple attr if the multiple option is not set in iOS8 UIWebView & the workaround is disabled\", function() {\n            qq.iosSafariWebView = function() {return true;};\n\n            var uploader = new qq.FineUploaderBasic({\n                element: $fixture[0],\n                button: $button[0],\n                multiple: false,\n                workarounds: {\n                    ios8BrowserCrash: false,\n                    iosEmptyVideos: false\n                }\n            });\n\n            assert.equal(qq(getFileInput($button)).hasAttribute(\"multiple\"), false);\n        });\n    });\n\n    describe(\"iOS7+ 0-sized videos\", function() {\n        var origIos = qq.ios,\n            origIos6 = qq.ios6;\n\n        beforeEach(function() {\n            qq.ios = function() {return true;};\n        });\n\n        afterEach(function() {\n            qq.ios = origIos;\n            qq.ios6 = origIos6;\n        });\n\n        it(\"ensures the file input never contains a multiple attr in iOS7 or 8\", function() {\n            qq.ios6 = function() {return false;};\n\n            var uploader = new qq.FineUploaderBasic({\n                element: $fixture[0],\n                button: $button[0],\n                multiple: true,\n                workarounds: {\n                    ios8BrowserCrash: false,\n                    iosEmptyVideos: true\n                }\n            });\n\n            assert.equal(qq(getFileInput($button)).hasAttribute(\"multiple\"), false);\n        });\n\n        qq.supportedFeatures.ajaxUploading && it(\"ensures the file input does have a multiple attr if the multiple option is set in iOS6\", function() {\n            qq.ios6 = function() {return true;};\n\n            var uploader = new qq.FineUploaderBasic({\n                element: $fixture[0],\n                button: $button[0],\n                multiple: true,\n                workarounds: {\n                    ios8BrowserCrash: false,\n                    iosEmptyVideos: false\n                }\n            });\n\n            assert.equal(qq(getFileInput($button)).hasAttribute(\"multiple\"), true);\n        });\n\n        qq.supportedFeatures.ajaxUploading && it(\"ensures the file input does have a multiple attr if the multiple option is set in iOS8 & the workaround is disabled\", function() {\n            qq.ios6 = function() {return false;};\n\n            var uploader = new qq.FineUploaderBasic({\n                element: $fixture[0],\n                button: $button[0],\n                multiple: true,\n                workarounds: {\n                    ios8BrowserCrash: false,\n                    iosEmptyVideos: false\n                }\n            });\n\n            assert.equal(qq(getFileInput($button)).hasAttribute(\"multiple\"), true);\n        });\n    });\n\n    describe(\"iOS7+ 0-sized videos & Chrome browser crash\", function() {\n        var origIos8 = qq.ios8,\n            origIosChrome = qq.iosChrome,\n            origSafariWebView = qq.iosSafariWebView,\n            origIos = qq.ios,\n            origIosSafari = qq.iosSafari,\n            origIos7 = qq.ios7;\n\n        beforeEach(function() {\n            qq.ios = function() {return true;};\n            qq.ios8 = function() {return true;};\n        });\n\n        afterEach(function() {\n            qq.ios8 = origIos8;\n            qq.ios7 = origIos7;\n            qq.iosChrome = origIosChrome;\n            qq.iosSafari = origIosSafari;\n            qq.iosSafariWebView = origSafariWebView;\n            qq.ios = origIos;\n        });\n\n        it(\"ensures the file input always contains a multiple attr in iOS8 Chrome\", function() {\n            qq.iosChrome = function() {return true;};\n\n            var uploader = new qq.FineUploaderBasic({\n                element: $fixture[0],\n                button: $button[0],\n                multiple: false,\n                workarounds: {\n                    ios8BrowserCrash: true,\n                    iosEmptyVideos: true\n                }\n            });\n\n            assert.equal(qq(getFileInput($button)).hasAttribute(\"multiple\"), true);\n        });\n\n        it(\"ensures the file input always contains a multiple attr in iOS8 UIWebView\", function() {\n            qq.iosSafariWebView = function() {return true;};\n\n            var uploader = new qq.FineUploaderBasic({\n                element: $fixture[0],\n                button: $button[0],\n                multiple: false,\n                workarounds: {\n                    ios8BrowserCrash: true,\n                    iosEmptyVideos: true\n                }\n            });\n\n            assert.equal(qq(getFileInput($button)).hasAttribute(\"multiple\"), true);\n        });\n\n        it(\"ensures the file input never contains a multiple attr in iOS7\", function() {\n            qq.ios8 = function() {return false;};\n            qq.ios7 = function() {return true;};\n\n            var uploader = new qq.FineUploaderBasic({\n                element: $fixture[0],\n                button: $button[0],\n                multiple: true,\n                workarounds: {\n                    ios8BrowserCrash: true,\n                    iosEmptyVideos: true\n                }\n            });\n\n            assert.equal(qq(getFileInput($button)).hasAttribute(\"multiple\"), false);\n        });\n\n        it(\"ensures the file input never contains a multiple attr in iOS8 Safari\", function() {\n            qq.iosSafari = function() {return true;};\n\n            var uploader = new qq.FineUploaderBasic({\n                element: $fixture[0],\n                button: $button[0],\n                multiple: true,\n                workarounds: {\n                    ios8BrowserCrash: true,\n                    iosEmptyVideos: true\n                }\n            });\n\n            assert.equal(qq(getFileInput($button)).hasAttribute(\"multiple\"), false);\n        });\n    });\n\n    describe(\"iOS 8.0.0 Safari uploads impossible\", function() {\n        var origIos800 = qq.ios800,\n            origIosSafari = qq.iosSafari,\n            origWindowAlert = window.alert;\n\n        beforeEach(function() {\n            qq.ios800 = function() {return true;};\n            qq.iosSafari = function() {return true;};\n        });\n\n        afterEach(function() {\n            qq.ios800 = origIos800;\n            qq.iosSafari = origIosSafari;\n            window.alert = origWindowAlert;\n        });\n\n        it(\"throws an error and pops up an alert if addFiles is called in iOS 8.0.0 Safari\", function(done) {\n            var uploader = new qq.FineUploaderBasic({\n                element: $fixture[0],\n                button: $button[0],\n                multiple: true,\n                workarounds: {\n                    ios8SafariUploads: true\n                }\n            });\n\n            window.alert = function() {\n                done();\n            };\n\n            assert.throws(\n                function() {\n                    uploader.addFiles();\n                }, qq.Error\n            );\n        });\n    });\n});\n"
  }
]