kms 0.9.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (409) hide show
  1. checksums.yaml +4 -4
  2. data/{vendor/assets/bower_components/Responsive-Dashboard/src/img → app/assets/images}/avatar.jpg +0 -0
  3. data/app/assets/javascripts/kms/application.js +3 -0
  4. data/app/assets/javascripts/kms/application/controllers/assets_controller.coffee.erb +14 -7
  5. data/app/assets/javascripts/kms/application/controllers/pages_controller.coffee.erb +11 -3
  6. data/app/assets/javascripts/kms/application/controllers/snippets_controller.coffee.erb +3 -0
  7. data/app/assets/javascripts/kms/application/controllers/templates_controller.coffee.erb +3 -0
  8. data/app/assets/javascripts/kms/application/controllers/users_controller.coffee +4 -4
  9. data/app/assets/javascripts/kms/application/routes.coffee.erb +1 -1
  10. data/app/assets/javascripts/kms/application/services/transliteration_service.js +160 -0
  11. data/app/assets/javascripts/templates/assets/edit.html.slim +24 -2
  12. data/app/assets/javascripts/templates/assets/form.html.slim +23 -0
  13. data/app/assets/javascripts/templates/assets/index.html.slim +13 -12
  14. data/app/assets/javascripts/templates/assets/new.html.slim +1 -15
  15. data/app/assets/javascripts/templates/help.html.slim +1 -1
  16. data/app/assets/javascripts/templates/pages/index.html.slim +16 -5
  17. data/app/assets/javascripts/templates/snippets/index.html.slim +16 -5
  18. data/app/assets/javascripts/templates/templates/index.html.slim +16 -6
  19. data/app/assets/stylesheets/kms/application.css +2 -2
  20. data/app/assets/stylesheets/kms/custom.css.scss +38 -0
  21. data/app/controllers/kms/assets_controller.rb +8 -9
  22. data/app/controllers/kms/pages_controller.rb +10 -11
  23. data/app/controllers/kms/public/pages_controller.rb +5 -4
  24. data/app/controllers/kms/public/search_controller.rb +2 -0
  25. data/app/controllers/kms/settings_controller.rb +1 -1
  26. data/app/controllers/kms/snippets_controller.rb +2 -1
  27. data/app/controllers/kms/templates_controller.rb +6 -5
  28. data/app/controllers/kms/users_controller.rb +11 -8
  29. data/app/models/ability.rb +1 -0
  30. data/app/models/concerns/kms/permalinkable.rb +1 -1
  31. data/app/models/kms/page.rb +13 -4
  32. data/app/serializers/kms/asset_serializer.rb +6 -0
  33. data/app/serializers/kms/page_serializer.rb +5 -0
  34. data/app/serializers/kms/template_serializer.rb +5 -0
  35. data/app/serializers/kms/user_serializer.rb +5 -0
  36. data/app/services/kms/page_fetcher.rb +30 -0
  37. data/app/services/kms/search_service.rb +28 -3
  38. data/app/services/kms/template_processor.rb +19 -0
  39. data/app/views/devise/passwords/edit.html.erb +1 -1
  40. data/app/views/devise/passwords/new.html.erb +1 -1
  41. data/app/views/devise/registrations/new.html.erb +1 -1
  42. data/app/views/devise/sessions/new.html.erb +1 -1
  43. data/app/views/layouts/kms/devise.html.erb +1 -1
  44. data/app/views/layouts/kms/kms.html.erb +47 -18
  45. data/config/initializers/externals.rb +6 -32
  46. data/config/initializers/search.rb +1 -0
  47. data/config/locales/en.yml +17 -1
  48. data/config/locales/ru.yml +17 -1
  49. data/config/routes.rb +7 -6
  50. data/lib/kms/dependencies.rb +1 -4
  51. data/lib/kms/engine.rb +1 -2
  52. data/lib/kms/search_item.rb +18 -0
  53. data/lib/kms/version.rb +1 -1
  54. data/spec/controllers/kms/assets_controller_spec.rb +51 -0
  55. data/spec/controllers/kms/pages_controller_spec.rb +82 -0
  56. data/spec/controllers/kms/public/pages_controller_spec.rb +30 -0
  57. data/spec/controllers/kms/snippets_controller_spec.rb +61 -47
  58. data/spec/controllers/kms/templates_controller_spec.rb +51 -0
  59. data/spec/controllers/kms/users_controller_spec.rb +56 -0
  60. data/spec/factories/assets.rb +8 -0
  61. data/spec/factories/pages.rb +33 -0
  62. data/spec/factories/templates.rb +20 -0
  63. data/spec/factories/users.rb +9 -4
  64. data/spec/internal/config/routes.rb +12 -1
  65. data/spec/internal/log/test.log +81239 -0
  66. data/spec/internal/public/404.html +67 -0
  67. data/spec/internal/public/avatar.jpg +0 -0
  68. data/spec/internal/public/style.css +1 -0
  69. data/spec/internal/public/uploads/kms/asset/file/1/avatar.jpg +0 -0
  70. data/spec/internal/public/uploads/kms/asset/file/2/avatar.jpg +0 -0
  71. data/spec/internal/public/uploads/kms/asset/file/2/style.css +1 -0
  72. data/spec/internal/public/uploads/kms/asset/file/3/style.css +1 -0
  73. data/spec/internal/public/uploads/kms/asset/file/4/style.css +1 -0
  74. data/spec/internal/public/uploads/tmp/1500976987-41025-0002-0883/style.css +1 -0
  75. data/spec/internal/public/uploads/tmp/1500977082-41195-0002-6495/style.css +1 -0
  76. data/spec/internal/public/uploads/tmp/1500977109-41364-0002-4518/style.css +1 -0
  77. data/spec/internal/public/uploads/tmp/1500977152-41405-0002-2345/style.css +1 -0
  78. data/spec/internal/public/uploads/tmp/1500977327-41694-0002-5448/style.css +1 -0
  79. data/spec/internal/public/uploads/tmp/1500977376-41732-0002-7916/style.css +1 -0
  80. data/spec/internal/public/uploads/tmp/1500977392-41759-0002-7593/style.css +1 -0
  81. data/spec/internal/public/uploads/tmp/1500977410-42259-0002-7527/style.css +1 -0
  82. data/spec/internal/public/uploads/tmp/1500977429-42306-0002-5937/style.css +1 -0
  83. data/spec/internal/public/uploads/tmp/1500977437-42324-0002-5880/style.css +1 -0
  84. data/spec/internal/public/uploads/tmp/1500983228-53594-0002-4559/style.css +1 -0
  85. data/spec/internal/public/uploads/tmp/1500983284-53632-0002-6590/style.css +1 -0
  86. data/spec/internal/public/uploads/tmp/1500983360-53784-0002-7289/style.css +1 -0
  87. data/spec/internal/public/uploads/tmp/1500983469-54321-0002-0386/avatar.jpg +0 -0
  88. data/spec/internal/public/uploads/tmp/1500983469-54321-0004-5691/style.css +1 -0
  89. data/spec/internal/public/uploads/tmp/1500983511-54352-0002-5720/avatar.jpg +0 -0
  90. data/spec/internal/public/uploads/tmp/1500983511-54352-0004-1399/style.css +1 -0
  91. data/spec/internal/public/uploads/tmp/1500983610-54507-0002-4280/avatar.jpg +0 -0
  92. data/spec/internal/public/uploads/tmp/1500983610-54507-0004-9758/style.css +1 -0
  93. data/spec/internal/public/uploads/tmp/1500984466-57012-0002-4146/avatar.jpg +0 -0
  94. data/spec/internal/public/uploads/tmp/1500984466-57012-0004-5895/style.css +1 -0
  95. data/spec/internal/public/uploads/tmp/1500984509-57158-0002-9657/avatar.jpg +0 -0
  96. data/spec/internal/public/uploads/tmp/1500984509-57158-0004-5003/style.css +1 -0
  97. data/spec/internal/public/uploads/tmp/1500984616-57697-0002-7201/avatar.jpg +0 -0
  98. data/spec/internal/public/uploads/tmp/1500984616-57697-0004-6255/style.css +1 -0
  99. data/spec/internal/public/uploads/tmp/1500985257-58947-0002-3629/avatar.jpg +0 -0
  100. data/spec/internal/public/uploads/tmp/1500985257-58947-0004-5338/style.css +1 -0
  101. data/spec/internal/public/uploads/tmp/1500985407-58947-0006-5929/style.css +1 -0
  102. data/spec/internal/public/uploads/tmp/1500985473-59264-0002-0397/avatar.jpg +0 -0
  103. data/spec/internal/public/uploads/tmp/1500985473-59264-0004-6493/style.css +1 -0
  104. data/spec/internal/public/uploads/tmp/1500985475-59264-0007-8674/style.css +1 -0
  105. data/spec/internal/public/uploads/tmp/1500985538-59468-0002-9206/avatar.jpg +0 -0
  106. data/spec/internal/public/uploads/tmp/1500985538-59468-0004-2586/style.css +1 -0
  107. data/spec/internal/public/uploads/tmp/1500985538-59468-0007-6200/style.css +1 -0
  108. data/spec/internal/public/uploads/tmp/1500988358-65877-0002-4528/avatar.jpg +0 -0
  109. data/spec/internal/public/uploads/tmp/1500988358-65877-0004-5904/style.css +1 -0
  110. data/spec/internal/public/uploads/tmp/1500988358-65877-0007-7320/style.css +1 -0
  111. data/spec/internal/public/uploads/tmp/1500988407-65916-0002-3138/avatar.jpg +0 -0
  112. data/spec/internal/public/uploads/tmp/1500988407-65916-0004-5400/style.css +1 -0
  113. data/spec/internal/public/uploads/tmp/1500988407-65916-0007-1655/style.css +1 -0
  114. data/spec/internal/public/uploads/tmp/1500988421-65950-0002-9415/avatar.jpg +0 -0
  115. data/spec/internal/public/uploads/tmp/1500988421-65950-0004-7130/style.css +1 -0
  116. data/spec/internal/public/uploads/tmp/1500988421-65950-0007-9886/style.css +1 -0
  117. data/spec/internal/public/uploads/tmp/1500988435-65981-0002-3228/avatar.jpg +0 -0
  118. data/spec/internal/public/uploads/tmp/1500988435-65981-0004-3682/style.css +1 -0
  119. data/spec/internal/public/uploads/tmp/1500988435-65981-0007-1582/style.css +1 -0
  120. data/spec/internal/public/uploads/tmp/1500988475-66122-0002-9516/avatar.jpg +0 -0
  121. data/spec/internal/public/uploads/tmp/1500988475-66122-0004-5634/style.css +1 -0
  122. data/spec/internal/public/uploads/tmp/1500988530-66122-0007-2272/style.css +1 -0
  123. data/spec/internal/public/uploads/tmp/1500988554-66315-0002-6262/avatar.jpg +0 -0
  124. data/spec/internal/public/uploads/tmp/1500988554-66315-0004-6099/style.css +1 -0
  125. data/spec/internal/public/uploads/tmp/1500988554-66315-0007-1632/style.css +1 -0
  126. data/spec/internal/public/uploads/tmp/1500991751-73722-0002-9937/avatar.jpg +0 -0
  127. data/spec/internal/public/uploads/tmp/1500991751-73722-0004-8034/style.css +1 -0
  128. data/spec/internal/public/uploads/tmp/1500991751-73722-0007-7763/style.css +1 -0
  129. data/spec/internal/public/uploads/tmp/1501233238-34385-0002-3210/avatar.jpg +0 -0
  130. data/spec/internal/public/uploads/tmp/1501233238-34385-0004-5881/style.css +1 -0
  131. data/spec/internal/public/uploads/tmp/1501233238-34385-0007-6280/style.css +1 -0
  132. data/spec/models/kms/page_spec.rb +31 -0
  133. data/spec/models/kms/template_spec.rb +15 -0
  134. data/spec/services/kms/page_fetcher_spec.rb +36 -0
  135. data/spec/services/kms/search_service_spec.rb +53 -0
  136. data/spec/services/kms/template_processor_spec.rb +20 -0
  137. data/spec/support/controller_macros.rb +9 -1
  138. data/vendor/assets/bower.json +3 -2
  139. data/vendor/assets/bower_components/angular-ui-select/CHANGELOG.md +169 -0
  140. data/vendor/assets/bower_components/angular-ui-select/CONTRIBUTING.md +161 -0
  141. data/vendor/assets/bower_components/angular-ui-select/README.md +27 -13
  142. data/vendor/assets/bower_components/angular-ui-select/bower.json +6 -3
  143. data/vendor/assets/bower_components/angular-ui-select/composer.json +29 -0
  144. data/vendor/assets/bower_components/angular-ui-select/deploy-docs.sh +31 -0
  145. data/vendor/assets/bower_components/angular-ui-select/dist/select.css +48 -2
  146. data/vendor/assets/bower_components/angular-ui-select/dist/select.js +454 -111
  147. data/vendor/assets/bower_components/angular-ui-select/dist/select.min.css +2 -2
  148. data/vendor/assets/bower_components/angular-ui-select/dist/select.min.js +2 -2
  149. data/vendor/assets/bower_components/angular-ui-select/docs/assets/app.js +1 -0
  150. data/vendor/assets/bower_components/angular-ui-select/docs/assets/demo.js +461 -0
  151. data/vendor/assets/bower_components/angular-ui-select/docs/assets/docs.css +339 -0
  152. data/vendor/assets/bower_components/angular-ui-select/docs/assets/plunkr.js +110 -0
  153. data/vendor/assets/bower_components/angular-ui-select/docs/index.html +191 -0
  154. data/vendor/assets/bower_components/angular-ui-select/docs/partials/_footer.html +2 -0
  155. data/vendor/assets/bower_components/angular-ui-select/docs/partials/_header.html +64 -0
  156. data/vendor/assets/bower_components/angular-ui-select/index.js +2 -0
  157. data/vendor/assets/bower_components/angular-ui-select/package.json +36 -15
  158. data/vendor/assets/bower_components/bootstrap/CHANGELOG.md +5 -0
  159. data/vendor/assets/bower_components/bootstrap/Gemfile +6 -0
  160. data/vendor/assets/bower_components/bootstrap/Gemfile.lock +43 -0
  161. data/vendor/assets/bower_components/bootstrap/Gruntfile.js +176 -145
  162. data/vendor/assets/bower_components/bootstrap/ISSUE_TEMPLATE.md +22 -0
  163. data/vendor/assets/bower_components/bootstrap/LICENSE +1 -1
  164. data/vendor/assets/bower_components/bootstrap/README.md +55 -40
  165. data/vendor/assets/bower_components/bootstrap/bower.json +5 -9
  166. data/vendor/assets/bower_components/bootstrap/dist/css/bootstrap-theme.css +204 -59
  167. data/vendor/assets/bower_components/bootstrap/dist/css/bootstrap-theme.css.map +1 -1
  168. data/vendor/assets/bower_components/bootstrap/dist/css/bootstrap-theme.min.css +4 -3
  169. data/vendor/assets/bower_components/bootstrap/dist/css/bootstrap-theme.min.css.map +1 -0
  170. data/vendor/assets/bower_components/bootstrap/dist/css/{bootstrap.less → bootstrap.css} +881 -327
  171. data/vendor/assets/bower_components/bootstrap/dist/css/bootstrap.css.map +1 -1
  172. data/vendor/assets/bower_components/bootstrap/dist/css/bootstrap.min.css +6 -0
  173. data/vendor/assets/bower_components/bootstrap/dist/css/bootstrap.min.css.map +1 -0
  174. data/vendor/assets/bower_components/bootstrap/dist/fonts/glyphicons-halflings-regular.eot +0 -0
  175. data/vendor/assets/bower_components/bootstrap/dist/fonts/glyphicons-halflings-regular.svg +272 -213
  176. data/vendor/assets/bower_components/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf +0 -0
  177. data/vendor/assets/bower_components/bootstrap/dist/fonts/glyphicons-halflings-regular.woff +0 -0
  178. data/vendor/assets/bower_components/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2 +0 -0
  179. data/vendor/assets/bower_components/bootstrap/dist/js/bootstrap.js +549 -286
  180. data/vendor/assets/bower_components/bootstrap/dist/js/bootstrap.min.js +5 -4
  181. data/vendor/assets/bower_components/bootstrap/dist/js/npm.js +13 -0
  182. data/vendor/assets/bower_components/bootstrap/fonts/glyphicons-halflings-regular.eot +0 -0
  183. data/vendor/assets/bower_components/bootstrap/fonts/glyphicons-halflings-regular.svg +272 -213
  184. data/vendor/assets/bower_components/bootstrap/fonts/glyphicons-halflings-regular.ttf +0 -0
  185. data/vendor/assets/bower_components/bootstrap/fonts/glyphicons-halflings-regular.woff +0 -0
  186. data/vendor/assets/bower_components/bootstrap/fonts/glyphicons-halflings-regular.woff2 +0 -0
  187. data/vendor/assets/bower_components/bootstrap/grunt/bs-commonjs-generator.js +30 -0
  188. data/vendor/assets/bower_components/bootstrap/grunt/bs-glyphicons-data-generator.js +5 -4
  189. data/vendor/assets/bower_components/bootstrap/grunt/bs-lessdoc-parser.js +13 -12
  190. data/vendor/assets/bower_components/bootstrap/grunt/bs-raw-files-generator.js +6 -8
  191. data/vendor/assets/bower_components/bootstrap/grunt/change-version.js +109 -0
  192. data/vendor/assets/bower_components/bootstrap/grunt/configBridge.json +46 -0
  193. data/vendor/assets/bower_components/bootstrap/grunt/npm-shrinkwrap.json +2679 -0
  194. data/vendor/assets/bower_components/bootstrap/grunt/sauce_browsers.yml +5 -5
  195. data/vendor/assets/bower_components/bootstrap/js/affix.js +46 -26
  196. data/vendor/assets/bower_components/bootstrap/js/alert.js +8 -6
  197. data/vendor/assets/bower_components/bootstrap/js/button.js +34 -19
  198. data/vendor/assets/bower_components/bootstrap/js/carousel.js +35 -21
  199. data/vendor/assets/bower_components/bootstrap/js/collapse.js +69 -27
  200. data/vendor/assets/bower_components/bootstrap/js/dropdown.js +55 -41
  201. data/vendor/assets/bower_components/bootstrap/js/modal.js +91 -32
  202. data/vendor/assets/bower_components/bootstrap/js/popover.js +5 -10
  203. data/vendor/assets/bower_components/bootstrap/js/scrollspy.js +28 -26
  204. data/vendor/assets/bower_components/bootstrap/js/tab.js +45 -18
  205. data/vendor/assets/bower_components/bootstrap/js/tooltip.js +117 -54
  206. data/vendor/assets/bower_components/bootstrap/js/transition.js +2 -2
  207. data/vendor/assets/bower_components/bootstrap/less/alerts.less +5 -0
  208. data/vendor/assets/bower_components/bootstrap/less/badges.less +14 -3
  209. data/vendor/assets/bower_components/bootstrap/less/bootstrap.less +6 -0
  210. data/vendor/assets/bower_components/bootstrap/less/button-groups.less +22 -18
  211. data/vendor/assets/bower_components/bootstrap/less/buttons.less +19 -10
  212. data/vendor/assets/bower_components/bootstrap/less/carousel.less +34 -7
  213. data/vendor/assets/bower_components/bootstrap/less/close.less +1 -0
  214. data/vendor/assets/bower_components/bootstrap/less/code.less +1 -0
  215. data/vendor/assets/bower_components/bootstrap/less/component-animations.less +3 -1
  216. data/vendor/assets/bower_components/bootstrap/less/dropdowns.less +9 -8
  217. data/vendor/assets/bower_components/bootstrap/less/forms.less +123 -50
  218. data/vendor/assets/bower_components/bootstrap/less/glyphicons.less +75 -3
  219. data/vendor/assets/bower_components/bootstrap/less/input-groups.less +8 -3
  220. data/vendor/assets/bower_components/bootstrap/less/jumbotron.less +10 -4
  221. data/vendor/assets/bower_components/bootstrap/less/list-group.less +10 -11
  222. data/vendor/assets/bower_components/bootstrap/less/media.less +40 -30
  223. data/vendor/assets/bower_components/bootstrap/less/mixins.less +1 -0
  224. data/vendor/assets/bower_components/bootstrap/less/mixins/background-variant.less +2 -1
  225. data/vendor/assets/bower_components/bootstrap/less/mixins/buttons.less +19 -4
  226. data/vendor/assets/bower_components/bootstrap/less/mixins/forms.less +5 -1
  227. data/vendor/assets/bower_components/bootstrap/less/mixins/grid-framework.less +4 -4
  228. data/vendor/assets/bower_components/bootstrap/less/mixins/grid.less +4 -4
  229. data/vendor/assets/bower_components/bootstrap/less/mixins/hide-text.less +2 -2
  230. data/vendor/assets/bower_components/bootstrap/less/mixins/image.less +0 -1
  231. data/vendor/assets/bower_components/bootstrap/less/mixins/labels.less +1 -1
  232. data/vendor/assets/bower_components/bootstrap/less/mixins/list-group.less +2 -1
  233. data/vendor/assets/bower_components/bootstrap/less/mixins/pagination.less +2 -1
  234. data/vendor/assets/bower_components/bootstrap/less/mixins/reset-text.less +18 -0
  235. data/vendor/assets/bower_components/bootstrap/less/mixins/responsive-visibility.less +1 -1
  236. data/vendor/assets/bower_components/bootstrap/less/mixins/tab-focus.less +3 -3
  237. data/vendor/assets/bower_components/bootstrap/less/mixins/text-emphasis.less +2 -1
  238. data/vendor/assets/bower_components/bootstrap/less/mixins/vendor-prefixes.less +8 -5
  239. data/vendor/assets/bower_components/bootstrap/less/modals.less +3 -3
  240. data/vendor/assets/bower_components/bootstrap/less/navbar.less +30 -25
  241. data/vendor/assets/bower_components/bootstrap/less/navs.less +1 -1
  242. data/vendor/assets/bower_components/bootstrap/less/normalize.less +12 -13
  243. data/vendor/assets/bower_components/bootstrap/less/pager.less +1 -2
  244. data/vendor/assets/bower_components/bootstrap/less/pagination.less +5 -4
  245. data/vendor/assets/bower_components/bootstrap/less/panels.less +33 -5
  246. data/vendor/assets/bower_components/bootstrap/less/popovers.less +5 -7
  247. data/vendor/assets/bower_components/bootstrap/less/print.less +96 -96
  248. data/vendor/assets/bower_components/bootstrap/less/progress-bars.less +1 -19
  249. data/vendor/assets/bower_components/bootstrap/less/responsive-embed.less +10 -9
  250. data/vendor/assets/bower_components/bootstrap/less/scaffolding.less +13 -2
  251. data/vendor/assets/bower_components/bootstrap/less/tables.less +14 -13
  252. data/vendor/assets/bower_components/bootstrap/less/theme.less +49 -16
  253. data/vendor/assets/bower_components/bootstrap/less/thumbnails.less +1 -1
  254. data/vendor/assets/bower_components/bootstrap/less/tooltip.less +13 -7
  255. data/vendor/assets/bower_components/bootstrap/less/type.less +2 -13
  256. data/vendor/assets/bower_components/bootstrap/less/utilities.less +0 -2
  257. data/vendor/assets/bower_components/bootstrap/less/variables.less +45 -22
  258. data/vendor/assets/bower_components/bootstrap/nuget/MyGet.ps1 +8 -0
  259. data/vendor/assets/bower_components/bootstrap/nuget/bootstrap.less.nuspec +28 -0
  260. data/vendor/assets/bower_components/bootstrap/nuget/bootstrap.nuspec +28 -0
  261. data/vendor/assets/bower_components/bootstrap/package.js +32 -0
  262. data/vendor/assets/bower_components/bootstrap/package.json +49 -41
  263. data/vendor/assets/bower_components/flow.js/README.md +79 -64
  264. data/vendor/assets/bower_components/flow.js/bower.json +6 -2
  265. data/vendor/assets/bower_components/flow.js/dist/flow.js +1635 -1532
  266. data/vendor/assets/bower_components/flow.js/dist/flow.min.js +2 -2
  267. data/vendor/assets/bower_components/flow.js/package.js +24 -0
  268. data/vendor/assets/bower_components/flow.js/travis.sh +19 -0
  269. data/vendor/assets/bower_components/font-awesome/scss/_path.scss +5 -5
  270. data/vendor/assets/bower_components/font-awesome/scss/_variables.scss +1 -2
  271. data/vendor/assets/bower_components/ng-flow/bower.json +1 -1
  272. data/vendor/assets/bower_components/ng-flow/dist/ng-flow-standalone.js +1655 -1542
  273. data/vendor/assets/bower_components/ng-flow/dist/ng-flow-standalone.min.js +2 -2
  274. data/vendor/assets/bower_components/ng-flow/dist/ng-flow.js +19 -9
  275. data/vendor/assets/bower_components/ng-flow/dist/ng-flow.min.js +2 -2
  276. data/vendor/assets/bower_components/ng-flow/index.js +3 -0
  277. data/vendor/assets/bower_components/ng-flow/package.js +25 -0
  278. data/vendor/assets/bower_components/ng-flow/src/directives/drag-events.js +2 -1
  279. data/vendor/assets/bower_components/ng-flow/src/directives/events.js +2 -1
  280. data/vendor/assets/bower_components/ng-flow/src/directives/init.js +7 -1
  281. data/vendor/assets/bower_components/ng-flow/travis.sh +19 -0
  282. data/vendor/assets/bower_components/{Responsive-Dashboard → rdash-ui}/LICENSE +21 -21
  283. data/vendor/assets/bower_components/rdash-ui/README.md +55 -0
  284. data/vendor/assets/bower_components/rdash-ui/bower.json +33 -0
  285. data/vendor/assets/bower_components/rdash-ui/dist/css/rdash.min.css +1 -0
  286. data/vendor/assets/bower_components/rdash-ui/dist/css/rdash.scss +489 -0
  287. data/vendor/assets/bower_components/{Responsive-Dashboard/src → rdash-ui/dist}/fonts/montserrat-regular-webfont.eot +0 -0
  288. data/vendor/assets/bower_components/{Responsive-Dashboard/src → rdash-ui/dist}/fonts/montserrat-regular-webfont.svg +1316 -1316
  289. data/vendor/assets/bower_components/{Responsive-Dashboard/src → rdash-ui/dist}/fonts/montserrat-regular-webfont.ttf +0 -0
  290. data/vendor/assets/bower_components/{Responsive-Dashboard/src → rdash-ui/dist}/fonts/montserrat-regular-webfont.woff +0 -0
  291. metadata +227 -180
  292. data/config/initializers/bower_rails.rb +0 -16
  293. data/vendor/assets/bower_components/Responsive-Dashboard/CONTRIBUTING.md +0 -21
  294. data/vendor/assets/bower_components/Responsive-Dashboard/README.md +0 -119
  295. data/vendor/assets/bower_components/Responsive-Dashboard/bower.json +0 -32
  296. data/vendor/assets/bower_components/Responsive-Dashboard/gulpfile.js +0 -81
  297. data/vendor/assets/bower_components/Responsive-Dashboard/package.json +0 -21
  298. data/vendor/assets/bower_components/Responsive-Dashboard/src/index.html +0 -413
  299. data/vendor/assets/bower_components/Responsive-Dashboard/src/js/dashboard/controllers/alerts-ctrl.js +0 -19
  300. data/vendor/assets/bower_components/Responsive-Dashboard/src/js/dashboard/controllers/master-ctrl.js +0 -51
  301. data/vendor/assets/bower_components/Responsive-Dashboard/src/js/dashboard/directives/loading.js +0 -13
  302. data/vendor/assets/bower_components/Responsive-Dashboard/src/js/dashboard/directives/widget-body.js +0 -16
  303. data/vendor/assets/bower_components/Responsive-Dashboard/src/js/dashboard/directives/widget-header.js +0 -17
  304. data/vendor/assets/bower_components/Responsive-Dashboard/src/js/dashboard/directives/widget.js +0 -16
  305. data/vendor/assets/bower_components/Responsive-Dashboard/src/js/dashboard/module.js +0 -1
  306. data/vendor/assets/bower_components/Responsive-Dashboard/src/js/dashboard/routes.js +0 -22
  307. data/vendor/assets/bower_components/Responsive-Dashboard/src/less/dashboard/content.less +0 -19
  308. data/vendor/assets/bower_components/Responsive-Dashboard/src/less/dashboard/hamburg.less +0 -29
  309. data/vendor/assets/bower_components/Responsive-Dashboard/src/less/dashboard/header.less +0 -92
  310. data/vendor/assets/bower_components/Responsive-Dashboard/src/less/dashboard/loading.less +0 -40
  311. data/vendor/assets/bower_components/Responsive-Dashboard/src/less/dashboard/main.less +0 -73
  312. data/vendor/assets/bower_components/Responsive-Dashboard/src/less/dashboard/mixins.less +0 -5
  313. data/vendor/assets/bower_components/Responsive-Dashboard/src/less/dashboard/sidebar.less +0 -122
  314. data/vendor/assets/bower_components/Responsive-Dashboard/src/less/dashboard/variables.less +0 -10
  315. data/vendor/assets/bower_components/Responsive-Dashboard/src/less/dashboard/widgets.less +0 -95
  316. data/vendor/assets/bower_components/bootstrap/dist/css/bootstrap.min.css.erb +0 -5
  317. data/vendor/assets/bower_components/flow.js/CHANGELOG.md +0 -37
  318. data/vendor/assets/bower_components/flow.js/Gruntfile.js +0 -132
  319. data/vendor/assets/bower_components/flow.js/karma.conf.js +0 -114
  320. data/vendor/assets/bower_components/flow.js/package.json +0 -50
  321. data/vendor/assets/bower_components/flow.js/src/flow.js +0 -1532
  322. data/vendor/assets/bower_components/jquery/MIT-LICENSE.txt +0 -21
  323. data/vendor/assets/bower_components/jquery/bower.json +0 -27
  324. data/vendor/assets/bower_components/jquery/dist/jquery.js +0 -9190
  325. data/vendor/assets/bower_components/jquery/dist/jquery.min.js +0 -5
  326. data/vendor/assets/bower_components/jquery/dist/jquery.min.map +0 -1
  327. data/vendor/assets/bower_components/jquery/src/ajax.js +0 -806
  328. data/vendor/assets/bower_components/jquery/src/ajax/jsonp.js +0 -89
  329. data/vendor/assets/bower_components/jquery/src/ajax/load.js +0 -75
  330. data/vendor/assets/bower_components/jquery/src/ajax/parseJSON.js +0 -13
  331. data/vendor/assets/bower_components/jquery/src/ajax/parseXML.js +0 -28
  332. data/vendor/assets/bower_components/jquery/src/ajax/script.js +0 -64
  333. data/vendor/assets/bower_components/jquery/src/ajax/var/nonce.js +0 -5
  334. data/vendor/assets/bower_components/jquery/src/ajax/var/rquery.js +0 -3
  335. data/vendor/assets/bower_components/jquery/src/ajax/xhr.js +0 -135
  336. data/vendor/assets/bower_components/jquery/src/attributes.js +0 -11
  337. data/vendor/assets/bower_components/jquery/src/attributes/attr.js +0 -143
  338. data/vendor/assets/bower_components/jquery/src/attributes/classes.js +0 -158
  339. data/vendor/assets/bower_components/jquery/src/attributes/prop.js +0 -96
  340. data/vendor/assets/bower_components/jquery/src/attributes/support.js +0 -35
  341. data/vendor/assets/bower_components/jquery/src/attributes/val.js +0 -163
  342. data/vendor/assets/bower_components/jquery/src/callbacks.js +0 -205
  343. data/vendor/assets/bower_components/jquery/src/core.js +0 -498
  344. data/vendor/assets/bower_components/jquery/src/core/access.js +0 -60
  345. data/vendor/assets/bower_components/jquery/src/core/init.js +0 -123
  346. data/vendor/assets/bower_components/jquery/src/core/parseHTML.js +0 -39
  347. data/vendor/assets/bower_components/jquery/src/core/ready.js +0 -97
  348. data/vendor/assets/bower_components/jquery/src/core/var/rsingleTag.js +0 -4
  349. data/vendor/assets/bower_components/jquery/src/css.js +0 -451
  350. data/vendor/assets/bower_components/jquery/src/css/addGetHookIf.js +0 -24
  351. data/vendor/assets/bower_components/jquery/src/css/curCSS.js +0 -57
  352. data/vendor/assets/bower_components/jquery/src/css/defaultDisplay.js +0 -70
  353. data/vendor/assets/bower_components/jquery/src/css/hiddenVisibleSelectors.js +0 -15
  354. data/vendor/assets/bower_components/jquery/src/css/support.js +0 -91
  355. data/vendor/assets/bower_components/jquery/src/css/swap.js +0 -28
  356. data/vendor/assets/bower_components/jquery/src/css/var/cssExpand.js +0 -3
  357. data/vendor/assets/bower_components/jquery/src/css/var/getStyles.js +0 -5
  358. data/vendor/assets/bower_components/jquery/src/css/var/isHidden.js +0 -13
  359. data/vendor/assets/bower_components/jquery/src/css/var/rmargin.js +0 -3
  360. data/vendor/assets/bower_components/jquery/src/css/var/rnumnonpx.js +0 -5
  361. data/vendor/assets/bower_components/jquery/src/data.js +0 -179
  362. data/vendor/assets/bower_components/jquery/src/data/Data.js +0 -181
  363. data/vendor/assets/bower_components/jquery/src/data/accepts.js +0 -20
  364. data/vendor/assets/bower_components/jquery/src/data/var/data_priv.js +0 -5
  365. data/vendor/assets/bower_components/jquery/src/data/var/data_user.js +0 -5
  366. data/vendor/assets/bower_components/jquery/src/deferred.js +0 -149
  367. data/vendor/assets/bower_components/jquery/src/deprecated.js +0 -13
  368. data/vendor/assets/bower_components/jquery/src/dimensions.js +0 -50
  369. data/vendor/assets/bower_components/jquery/src/effects.js +0 -649
  370. data/vendor/assets/bower_components/jquery/src/effects/Tween.js +0 -114
  371. data/vendor/assets/bower_components/jquery/src/effects/animatedSelector.js +0 -13
  372. data/vendor/assets/bower_components/jquery/src/event.js +0 -868
  373. data/vendor/assets/bower_components/jquery/src/event/alias.js +0 -39
  374. data/vendor/assets/bower_components/jquery/src/event/support.js +0 -9
  375. data/vendor/assets/bower_components/jquery/src/exports/amd.js +0 -24
  376. data/vendor/assets/bower_components/jquery/src/exports/global.js +0 -32
  377. data/vendor/assets/bower_components/jquery/src/intro.js +0 -44
  378. data/vendor/assets/bower_components/jquery/src/jquery.js +0 -36
  379. data/vendor/assets/bower_components/jquery/src/manipulation.js +0 -582
  380. data/vendor/assets/bower_components/jquery/src/manipulation/_evalUrl.js +0 -18
  381. data/vendor/assets/bower_components/jquery/src/manipulation/support.js +0 -31
  382. data/vendor/assets/bower_components/jquery/src/manipulation/var/rcheckableType.js +0 -3
  383. data/vendor/assets/bower_components/jquery/src/offset.js +0 -204
  384. data/vendor/assets/bower_components/jquery/src/outro.js +0 -1
  385. data/vendor/assets/bower_components/jquery/src/queue.js +0 -142
  386. data/vendor/assets/bower_components/jquery/src/queue/delay.js +0 -22
  387. data/vendor/assets/bower_components/jquery/src/selector-native.js +0 -172
  388. data/vendor/assets/bower_components/jquery/src/selector-sizzle.js +0 -14
  389. data/vendor/assets/bower_components/jquery/src/selector.js +0 -1
  390. data/vendor/assets/bower_components/jquery/src/serialize.js +0 -111
  391. data/vendor/assets/bower_components/jquery/src/sizzle/dist/sizzle.js +0 -2044
  392. data/vendor/assets/bower_components/jquery/src/sizzle/dist/sizzle.min.js +0 -3
  393. data/vendor/assets/bower_components/jquery/src/sizzle/dist/sizzle.min.map +0 -1
  394. data/vendor/assets/bower_components/jquery/src/traversing.js +0 -200
  395. data/vendor/assets/bower_components/jquery/src/traversing/findFilter.js +0 -100
  396. data/vendor/assets/bower_components/jquery/src/traversing/var/rneedsContext.js +0 -6
  397. data/vendor/assets/bower_components/jquery/src/var/arr.js +0 -3
  398. data/vendor/assets/bower_components/jquery/src/var/class2type.js +0 -4
  399. data/vendor/assets/bower_components/jquery/src/var/concat.js +0 -5
  400. data/vendor/assets/bower_components/jquery/src/var/hasOwn.js +0 -5
  401. data/vendor/assets/bower_components/jquery/src/var/indexOf.js +0 -5
  402. data/vendor/assets/bower_components/jquery/src/var/pnum.js +0 -3
  403. data/vendor/assets/bower_components/jquery/src/var/push.js +0 -5
  404. data/vendor/assets/bower_components/jquery/src/var/rnotwhite.js +0 -3
  405. data/vendor/assets/bower_components/jquery/src/var/slice.js +0 -5
  406. data/vendor/assets/bower_components/jquery/src/var/strundefined.js +0 -3
  407. data/vendor/assets/bower_components/jquery/src/var/support.js +0 -4
  408. data/vendor/assets/bower_components/jquery/src/var/toString.js +0 -5
  409. data/vendor/assets/bower_components/jquery/src/wrap.js +0 -78
@@ -1,6 +1,5 @@
1
1
  {
2
2
  "name": "flow.js",
3
- "version": "2.6.2",
4
3
  "main": "./dist/flow.js",
5
4
  "ignore": [
6
5
  "**/.*",
@@ -8,6 +7,11 @@
8
7
  "bower_components",
9
8
  "test",
10
9
  "tests",
11
- "samples"
10
+ "samples",
11
+ "CHANGELOG.md",
12
+ "karma.conf.js",
13
+ "package.json",
14
+ "src/*",
15
+ "Gruntfile.js"
12
16
  ]
13
17
  }
@@ -1,1532 +1,1635 @@
1
- /**
2
- * @license MIT
3
- */
4
- (function(window, document, undefined) {'use strict';
5
-
6
- /**
7
- * Flow.js is a library providing multiple simultaneous, stable and
8
- * resumable uploads via the HTML5 File API.
9
- * @param [opts]
10
- * @param {number} [opts.chunkSize]
11
- * @param {bool} [opts.forceChunkSize]
12
- * @param {number} [opts.simultaneousUploads]
13
- * @param {bool} [opts.singleFile]
14
- * @param {string} [opts.fileParameterName]
15
- * @param {number} [opts.progressCallbacksInterval]
16
- * @param {number} [opts.speedSmoothingFactor]
17
- * @param {Object|Function} [opts.query]
18
- * @param {Object|Function} [opts.headers]
19
- * @param {bool} [opts.withCredentials]
20
- * @param {Function} [opts.preprocess]
21
- * @param {string} [opts.method]
22
- * @param {bool} [opts.prioritizeFirstAndLastChunk]
23
- * @param {string|Function} [opts.target]
24
- * @param {number} [opts.maxChunkRetries]
25
- * @param {number} [opts.chunkRetryInterval]
26
- * @param {Array.<number>} [opts.permanentErrors]
27
- * @param {Function} [opts.generateUniqueIdentifier]
28
- * @constructor
29
- */
30
- function Flow(opts) {
31
- /**
32
- * Supported by browser?
33
- * @type {boolean}
34
- */
35
- this.support = (
36
- typeof File !== 'undefined' &&
37
- typeof Blob !== 'undefined' &&
38
- typeof FileList !== 'undefined' &&
39
- (
40
- !!Blob.prototype.slice || !!Blob.prototype.webkitSlice || !!Blob.prototype.mozSlice ||
41
- false
42
- ) // slicing files support
43
- );
44
-
45
- if (!this.support) {
46
- return ;
47
- }
48
-
49
- /**
50
- * Check if directory upload is supported
51
- * @type {boolean}
52
- */
53
- this.supportDirectory = /WebKit/.test(window.navigator.userAgent);
54
-
55
- /**
56
- * List of FlowFile objects
57
- * @type {Array.<FlowFile>}
58
- */
59
- this.files = [];
60
-
61
- /**
62
- * Default options for flow.js
63
- * @type {Object}
64
- */
65
- this.defaults = {
66
- chunkSize: 1024 * 1024,
67
- forceChunkSize: false,
68
- simultaneousUploads: 3,
69
- singleFile: false,
70
- fileParameterName: 'file',
71
- progressCallbacksInterval: 500,
72
- speedSmoothingFactor: 0.1,
73
- query: {},
74
- headers: {},
75
- withCredentials: false,
76
- preprocess: null,
77
- method: 'multipart',
78
- prioritizeFirstAndLastChunk: false,
79
- target: '/',
80
- testChunks: true,
81
- generateUniqueIdentifier: null,
82
- maxChunkRetries: 0,
83
- chunkRetryInterval: null,
84
- permanentErrors: [404, 415, 500, 501],
85
- onDropStopPropagation: false
86
- };
87
-
88
- /**
89
- * Current options
90
- * @type {Object}
91
- */
92
- this.opts = {};
93
-
94
- /**
95
- * List of events:
96
- * key stands for event name
97
- * value array list of callbacks
98
- * @type {}
99
- */
100
- this.events = {};
101
-
102
- var $ = this;
103
-
104
- /**
105
- * On drop event
106
- * @function
107
- * @param {MouseEvent} event
108
- */
109
- this.onDrop = function (event) {
110
- if ($.opts.onDropStopPropagation) {
111
- event.stopPropagation();
112
- }
113
- event.preventDefault();
114
- var dataTransfer = event.dataTransfer;
115
- if (dataTransfer.items && dataTransfer.items[0] &&
116
- dataTransfer.items[0].webkitGetAsEntry) {
117
- $.webkitReadDataTransfer(event);
118
- } else {
119
- $.addFiles(dataTransfer.files, event);
120
- }
121
- };
122
-
123
- /**
124
- * Prevent default
125
- * @function
126
- * @param {MouseEvent} event
127
- */
128
- this.preventEvent = function (event) {
129
- event.preventDefault();
130
- };
131
-
132
-
133
- /**
134
- * Current options
135
- * @type {Object}
136
- */
137
- this.opts = Flow.extend({}, this.defaults, opts || {});
138
- }
139
-
140
- Flow.prototype = {
141
- /**
142
- * Set a callback for an event, possible events:
143
- * fileSuccess(file), fileProgress(file), fileAdded(file, event),
144
- * fileRetry(file), fileError(file, message), complete(),
145
- * progress(), error(message, file), pause()
146
- * @function
147
- * @param {string} event
148
- * @param {Function} callback
149
- */
150
- on: function (event, callback) {
151
- event = event.toLowerCase();
152
- if (!this.events.hasOwnProperty(event)) {
153
- this.events[event] = [];
154
- }
155
- this.events[event].push(callback);
156
- },
157
-
158
- /**
159
- * Remove event callback
160
- * @function
161
- * @param {string} [event] removes all events if not specified
162
- * @param {Function} [fn] removes all callbacks of event if not specified
163
- */
164
- off: function (event, fn) {
165
- if (event !== undefined) {
166
- event = event.toLowerCase();
167
- if (fn !== undefined) {
168
- if (this.events.hasOwnProperty(event)) {
169
- arrayRemove(this.events[event], fn);
170
- }
171
- } else {
172
- delete this.events[event];
173
- }
174
- } else {
175
- this.events = {};
176
- }
177
- },
178
-
179
- /**
180
- * Fire an event
181
- * @function
182
- * @param {string} event event name
183
- * @param {...} args arguments of a callback
184
- * @return {bool} value is false if at least one of the event handlers which handled this event
185
- * returned false. Otherwise it returns true.
186
- */
187
- fire: function (event, args) {
188
- // `arguments` is an object, not array, in FF, so:
189
- args = Array.prototype.slice.call(arguments);
190
- event = event.toLowerCase();
191
- var preventDefault = false;
192
- if (this.events.hasOwnProperty(event)) {
193
- each(this.events[event], function (callback) {
194
- preventDefault = callback.apply(this, args.slice(1)) === false || preventDefault;
195
- });
196
- }
197
- if (event != 'catchall') {
198
- args.unshift('catchAll');
199
- preventDefault = this.fire.apply(this, args) === false || preventDefault;
200
- }
201
- return !preventDefault;
202
- },
203
-
204
- /**
205
- * Read webkit dataTransfer object
206
- * @param event
207
- */
208
- webkitReadDataTransfer: function (event) {
209
- var $ = this;
210
- var queue = event.dataTransfer.items.length;
211
- var files = [];
212
- each(event.dataTransfer.items, function (item) {
213
- var entry = item.webkitGetAsEntry();
214
- if (!entry) {
215
- decrement();
216
- return ;
217
- }
218
- if (entry.isFile) {
219
- // due to a bug in Chrome's File System API impl - #149735
220
- fileReadSuccess(item.getAsFile(), entry.fullPath);
221
- } else {
222
- entry.createReader().readEntries(readSuccess, readError);
223
- }
224
- });
225
- function readSuccess(entries) {
226
- queue += entries.length;
227
- each(entries, function(entry) {
228
- if (entry.isFile) {
229
- var fullPath = entry.fullPath;
230
- entry.file(function (file) {
231
- fileReadSuccess(file, fullPath);
232
- }, readError);
233
- } else if (entry.isDirectory) {
234
- entry.createReader().readEntries(readSuccess, readError);
235
- }
236
- });
237
- decrement();
238
- }
239
- function fileReadSuccess(file, fullPath) {
240
- // relative path should not start with "/"
241
- file.relativePath = fullPath.substring(1);
242
- files.push(file);
243
- decrement();
244
- }
245
- function readError(fileError) {
246
- throw fileError;
247
- }
248
- function decrement() {
249
- if (--queue == 0) {
250
- $.addFiles(files, event);
251
- }
252
- }
253
- },
254
-
255
- /**
256
- * Generate unique identifier for a file
257
- * @function
258
- * @param {FlowFile} file
259
- * @returns {string}
260
- */
261
- generateUniqueIdentifier: function (file) {
262
- var custom = this.opts.generateUniqueIdentifier;
263
- if (typeof custom === 'function') {
264
- return custom(file);
265
- }
266
- // Some confusion in different versions of Firefox
267
- var relativePath = file.relativePath || file.webkitRelativePath || file.fileName || file.name;
268
- return file.size + '-' + relativePath.replace(/[^0-9a-zA-Z_-]/img, '');
269
- },
270
-
271
- /**
272
- * Upload next chunk from the queue
273
- * @function
274
- * @returns {boolean}
275
- * @private
276
- */
277
- uploadNextChunk: function (preventEvents) {
278
- // In some cases (such as videos) it's really handy to upload the first
279
- // and last chunk of a file quickly; this let's the server check the file's
280
- // metadata and determine if there's even a point in continuing.
281
- var found = false;
282
- if (this.opts.prioritizeFirstAndLastChunk) {
283
- each(this.files, function (file) {
284
- if (!file.paused && file.chunks.length &&
285
- file.chunks[0].status() === 'pending' &&
286
- file.chunks[0].preprocessState === 0) {
287
- file.chunks[0].send();
288
- found = true;
289
- return false;
290
- }
291
- if (!file.paused && file.chunks.length > 1 &&
292
- file.chunks[file.chunks.length - 1].status() === 'pending' &&
293
- file.chunks[0].preprocessState === 0) {
294
- file.chunks[file.chunks.length - 1].send();
295
- found = true;
296
- return false;
297
- }
298
- });
299
- if (found) {
300
- return found;
301
- }
302
- }
303
-
304
- // Now, simply look for the next, best thing to upload
305
- each(this.files, function (file) {
306
- if (!file.paused) {
307
- each(file.chunks, function (chunk) {
308
- if (chunk.status() === 'pending' && chunk.preprocessState === 0) {
309
- chunk.send();
310
- found = true;
311
- return false;
312
- }
313
- });
314
- }
315
- if (found) {
316
- return false;
317
- }
318
- });
319
- if (found) {
320
- return true;
321
- }
322
-
323
- // The are no more outstanding chunks to upload, check is everything is done
324
- var outstanding = false;
325
- each(this.files, function (file) {
326
- if (!file.isComplete()) {
327
- outstanding = true;
328
- return false;
329
- }
330
- });
331
- if (!outstanding && !preventEvents) {
332
- // All chunks have been uploaded, complete
333
- async(function () {
334
- this.fire('complete');
335
- }, this);
336
- }
337
- return false;
338
- },
339
-
340
-
341
- /**
342
- * Assign a browse action to one or more DOM nodes.
343
- * @function
344
- * @param {Element|Array.<Element>} domNodes
345
- * @param {boolean} isDirectory Pass in true to allow directories to
346
- * @param {boolean} singleFile prevent multi file upload
347
- * @param {Object} attributes set custom attributes:
348
- * http://www.w3.org/TR/html-markup/input.file.html#input.file-attributes
349
- * eg: accept: 'image/*'
350
- * be selected (Chrome only).
351
- */
352
- assignBrowse: function (domNodes, isDirectory, singleFile, attributes) {
353
- if (typeof domNodes.length === 'undefined') {
354
- domNodes = [domNodes];
355
- }
356
-
357
- each(domNodes, function (domNode) {
358
- var input;
359
- if (domNode.tagName === 'INPUT' && domNode.type === 'file') {
360
- input = domNode;
361
- } else {
362
- input = document.createElement('input');
363
- input.setAttribute('type', 'file');
364
- // display:none - not working in opera 12
365
- extend(input.style, {
366
- visibility: 'hidden',
367
- position: 'absolute'
368
- });
369
- // for opera 12 browser, input must be assigned to a document
370
- domNode.appendChild(input);
371
- // https://developer.mozilla.org/en/using_files_from_web_applications)
372
- // event listener is executed two times
373
- // first one - original mouse click event
374
- // second - input.click(), input is inside domNode
375
- domNode.addEventListener('click', function() {
376
- input.click();
377
- }, false);
378
- }
379
- if (!this.opts.singleFile && !singleFile) {
380
- input.setAttribute('multiple', 'multiple');
381
- }
382
- if (isDirectory) {
383
- input.setAttribute('webkitdirectory', 'webkitdirectory');
384
- }
385
- each(attributes, function (value, key) {
386
- input.setAttribute(key, value);
387
- });
388
- // When new files are added, simply append them to the overall list
389
- var $ = this;
390
- input.addEventListener('change', function (e) {
391
- $.addFiles(e.target.files, e);
392
- e.target.value = '';
393
- }, false);
394
- }, this);
395
- },
396
-
397
- /**
398
- * Assign one or more DOM nodes as a drop target.
399
- * @function
400
- * @param {Element|Array.<Element>} domNodes
401
- */
402
- assignDrop: function (domNodes) {
403
- if (typeof domNodes.length === 'undefined') {
404
- domNodes = [domNodes];
405
- }
406
- each(domNodes, function (domNode) {
407
- domNode.addEventListener('dragover', this.preventEvent, false);
408
- domNode.addEventListener('dragenter', this.preventEvent, false);
409
- domNode.addEventListener('drop', this.onDrop, false);
410
- }, this);
411
- },
412
-
413
- /**
414
- * Un-assign drop event from DOM nodes
415
- * @function
416
- * @param domNodes
417
- */
418
- unAssignDrop: function (domNodes) {
419
- if (typeof domNodes.length === 'undefined') {
420
- domNodes = [domNodes];
421
- }
422
- each(domNodes, function (domNode) {
423
- domNode.removeEventListener('dragover', this.preventEvent);
424
- domNode.removeEventListener('dragenter', this.preventEvent);
425
- domNode.removeEventListener('drop', this.onDrop);
426
- }, this);
427
- },
428
-
429
- /**
430
- * Returns a boolean indicating whether or not the instance is currently
431
- * uploading anything.
432
- * @function
433
- * @returns {boolean}
434
- */
435
- isUploading: function () {
436
- var uploading = false;
437
- each(this.files, function (file) {
438
- if (file.isUploading()) {
439
- uploading = true;
440
- return false;
441
- }
442
- });
443
- return uploading;
444
- },
445
-
446
- /**
447
- * should upload next chunk
448
- * @function
449
- * @returns {boolean|number}
450
- */
451
- _shouldUploadNext: function () {
452
- var num = 0;
453
- var should = true;
454
- var simultaneousUploads = this.opts.simultaneousUploads;
455
- each(this.files, function (file) {
456
- each(file.chunks, function(chunk) {
457
- if (chunk.status() === 'uploading') {
458
- num++;
459
- if (num >= simultaneousUploads) {
460
- should = false;
461
- return false;
462
- }
463
- }
464
- });
465
- });
466
- // if should is true then return uploading chunks's length
467
- return should && num;
468
- },
469
-
470
- /**
471
- * Start or resume uploading.
472
- * @function
473
- */
474
- upload: function () {
475
- // Make sure we don't start too many uploads at once
476
- var ret = this._shouldUploadNext();
477
- if (ret === false) {
478
- return;
479
- }
480
- // Kick off the queue
481
- this.fire('uploadStart');
482
- var started = false;
483
- for (var num = 1; num <= this.opts.simultaneousUploads - ret; num++) {
484
- started = this.uploadNextChunk(true) || started;
485
- }
486
- if (!started) {
487
- async(function () {
488
- this.fire('complete');
489
- }, this);
490
- }
491
- },
492
-
493
- /**
494
- * Resume uploading.
495
- * @function
496
- */
497
- resume: function () {
498
- each(this.files, function (file) {
499
- file.resume();
500
- });
501
- },
502
-
503
- /**
504
- * Pause uploading.
505
- * @function
506
- */
507
- pause: function () {
508
- each(this.files, function (file) {
509
- file.pause();
510
- });
511
- },
512
-
513
- /**
514
- * Cancel upload of all FlowFile objects and remove them from the list.
515
- * @function
516
- */
517
- cancel: function () {
518
- for (var i = this.files.length - 1; i >= 0; i--) {
519
- this.files[i].cancel();
520
- }
521
- },
522
-
523
- /**
524
- * Returns a number between 0 and 1 indicating the current upload progress
525
- * of all files.
526
- * @function
527
- * @returns {number}
528
- */
529
- progress: function () {
530
- var totalDone = 0;
531
- var totalSize = 0;
532
- // Resume all chunks currently being uploaded
533
- each(this.files, function (file) {
534
- totalDone += file.progress() * file.size;
535
- totalSize += file.size;
536
- });
537
- return totalSize > 0 ? totalDone / totalSize : 0;
538
- },
539
-
540
- /**
541
- * Add a HTML5 File object to the list of files.
542
- * @function
543
- * @param {File} file
544
- * @param {Event} [event] event is optional
545
- */
546
- addFile: function (file, event) {
547
- this.addFiles([file], event);
548
- },
549
-
550
- /**
551
- * Add a HTML5 File object to the list of files.
552
- * @function
553
- * @param {FileList|Array} fileList
554
- * @param {Event} [event] event is optional
555
- */
556
- addFiles: function (fileList, event) {
557
- var files = [];
558
- each(fileList, function (file) {
559
- // Directories have size `0` and name `.`
560
- // Ignore already added files
561
- if (!(file.size % 4096 === 0 && (file.name === '.' || file.fileName === '.')) &&
562
- !this.getFromUniqueIdentifier(this.generateUniqueIdentifier(file))) {
563
- var f = new FlowFile(this, file);
564
- if (this.fire('fileAdded', f, event)) {
565
- files.push(f);
566
- }
567
- }
568
- }, this);
569
- if (this.fire('filesAdded', files, event)) {
570
- each(files, function (file) {
571
- if (this.opts.singleFile && this.files.length > 0) {
572
- this.removeFile(this.files[0]);
573
- }
574
- this.files.push(file);
575
- }, this);
576
- }
577
- this.fire('filesSubmitted', files, event);
578
- },
579
-
580
-
581
- /**
582
- * Cancel upload of a specific FlowFile object from the list.
583
- * @function
584
- * @param {FlowFile} file
585
- */
586
- removeFile: function (file) {
587
- for (var i = this.files.length - 1; i >= 0; i--) {
588
- if (this.files[i] === file) {
589
- this.files.splice(i, 1);
590
- file.abort();
591
- }
592
- }
593
- },
594
-
595
- /**
596
- * Look up a FlowFile object by its unique identifier.
597
- * @function
598
- * @param {string} uniqueIdentifier
599
- * @returns {boolean|FlowFile} false if file was not found
600
- */
601
- getFromUniqueIdentifier: function (uniqueIdentifier) {
602
- var ret = false;
603
- each(this.files, function (file) {
604
- if (file.uniqueIdentifier === uniqueIdentifier) {
605
- ret = file;
606
- }
607
- });
608
- return ret;
609
- },
610
-
611
- /**
612
- * Returns the total size of all files in bytes.
613
- * @function
614
- * @returns {number}
615
- */
616
- getSize: function () {
617
- var totalSize = 0;
618
- each(this.files, function (file) {
619
- totalSize += file.size;
620
- });
621
- return totalSize;
622
- },
623
-
624
- /**
625
- * Returns the total size uploaded of all files in bytes.
626
- * @function
627
- * @returns {number}
628
- */
629
- sizeUploaded: function () {
630
- var size = 0;
631
- each(this.files, function (file) {
632
- size += file.sizeUploaded();
633
- });
634
- return size;
635
- },
636
-
637
- /**
638
- * Returns remaining time to upload all files in seconds. Accuracy is based on average speed.
639
- * If speed is zero, time remaining will be equal to positive infinity `Number.POSITIVE_INFINITY`
640
- * @function
641
- * @returns {number}
642
- */
643
- timeRemaining: function () {
644
- var sizeDelta = 0;
645
- var averageSpeed = 0;
646
- each(this.files, function (file) {
647
- if (!file.paused && !file.error) {
648
- sizeDelta += file.size - file.sizeUploaded();
649
- averageSpeed += file.averageSpeed;
650
- }
651
- });
652
- if (sizeDelta && !averageSpeed) {
653
- return Number.POSITIVE_INFINITY;
654
- }
655
- if (!sizeDelta && !averageSpeed) {
656
- return 0;
657
- }
658
- return Math.floor(sizeDelta / averageSpeed);
659
- }
660
- };
661
-
662
-
663
-
664
-
665
-
666
-
667
- /**
668
- * FlowFile class
669
- * @name FlowFile
670
- * @param {Flow} flowObj
671
- * @param {File} file
672
- * @constructor
673
- */
674
- function FlowFile(flowObj, file) {
675
-
676
- /**
677
- * Reference to parent Flow instance
678
- * @type {Flow}
679
- */
680
- this.flowObj = flowObj;
681
-
682
- /**
683
- * Reference to file
684
- * @type {File}
685
- */
686
- this.file = file;
687
-
688
- /**
689
- * File name. Some confusion in different versions of Firefox
690
- * @type {string}
691
- */
692
- this.name = file.fileName || file.name;
693
-
694
- /**
695
- * File size
696
- * @type {number}
697
- */
698
- this.size = file.size;
699
-
700
- /**
701
- * Relative file path
702
- * @type {string}
703
- */
704
- this.relativePath = file.relativePath || file.webkitRelativePath || this.name;
705
-
706
- /**
707
- * File unique identifier
708
- * @type {string}
709
- */
710
- this.uniqueIdentifier = flowObj.generateUniqueIdentifier(file);
711
-
712
- /**
713
- * List of chunks
714
- * @type {Array.<FlowChunk>}
715
- */
716
- this.chunks = [];
717
-
718
- /**
719
- * Indicated if file is paused
720
- * @type {boolean}
721
- */
722
- this.paused = false;
723
-
724
- /**
725
- * Indicated if file has encountered an error
726
- * @type {boolean}
727
- */
728
- this.error = false;
729
-
730
- /**
731
- * Average upload speed
732
- * @type {number}
733
- */
734
- this.averageSpeed = 0;
735
-
736
- /**
737
- * Current upload speed
738
- * @type {number}
739
- */
740
- this.currentSpeed = 0;
741
-
742
- /**
743
- * Date then progress was called last time
744
- * @type {number}
745
- * @private
746
- */
747
- this._lastProgressCallback = Date.now();
748
-
749
- /**
750
- * Previously uploaded file size
751
- * @type {number}
752
- * @private
753
- */
754
- this._prevUploadedSize = 0;
755
-
756
- /**
757
- * Holds previous progress
758
- * @type {number}
759
- * @private
760
- */
761
- this._prevProgress = 0;
762
-
763
- this.bootstrap();
764
- }
765
-
766
- FlowFile.prototype = {
767
- /**
768
- * Update speed parameters
769
- * @link http://stackoverflow.com/questions/2779600/how-to-estimate-download-time-remaining-accurately
770
- * @function
771
- */
772
- measureSpeed: function () {
773
- var timeSpan = Date.now() - this._lastProgressCallback;
774
- if (!timeSpan) {
775
- return ;
776
- }
777
- var smoothingFactor = this.flowObj.opts.speedSmoothingFactor;
778
- var uploaded = this.sizeUploaded();
779
- // Prevent negative upload speed after file upload resume
780
- this.currentSpeed = Math.max((uploaded - this._prevUploadedSize) / timeSpan * 1000, 0);
781
- this.averageSpeed = smoothingFactor * this.currentSpeed + (1 - smoothingFactor) * this.averageSpeed;
782
- this._prevUploadedSize = uploaded;
783
- },
784
-
785
- /**
786
- * For internal usage only.
787
- * Callback when something happens within the chunk.
788
- * @function
789
- * @param {string} event can be 'progress', 'success', 'error' or 'retry'
790
- * @param {string} [message]
791
- */
792
- chunkEvent: function (event, message) {
793
- switch (event) {
794
- case 'progress':
795
- if (Date.now() - this._lastProgressCallback <
796
- this.flowObj.opts.progressCallbacksInterval) {
797
- break;
798
- }
799
- this.measureSpeed();
800
- this.flowObj.fire('fileProgress', this);
801
- this.flowObj.fire('progress');
802
- this._lastProgressCallback = Date.now();
803
- break;
804
- case 'error':
805
- this.error = true;
806
- this.abort(true);
807
- this.flowObj.fire('fileError', this, message);
808
- this.flowObj.fire('error', message, this);
809
- break;
810
- case 'success':
811
- if (this.error) {
812
- return;
813
- }
814
- this.measureSpeed();
815
- this.flowObj.fire('fileProgress', this);
816
- this.flowObj.fire('progress');
817
- this._lastProgressCallback = Date.now();
818
- if (this.isComplete()) {
819
- this.currentSpeed = 0;
820
- this.averageSpeed = 0;
821
- this.flowObj.fire('fileSuccess', this, message);
822
- }
823
- break;
824
- case 'retry':
825
- this.flowObj.fire('fileRetry', this);
826
- break;
827
- }
828
- },
829
-
830
- /**
831
- * Pause file upload
832
- * @function
833
- */
834
- pause: function() {
835
- this.paused = true;
836
- this.abort();
837
- },
838
-
839
- /**
840
- * Resume file upload
841
- * @function
842
- */
843
- resume: function() {
844
- this.paused = false;
845
- this.flowObj.upload();
846
- },
847
-
848
- /**
849
- * Abort current upload
850
- * @function
851
- */
852
- abort: function (reset) {
853
- this.currentSpeed = 0;
854
- this.averageSpeed = 0;
855
- var chunks = this.chunks;
856
- if (reset) {
857
- this.chunks = [];
858
- }
859
- each(chunks, function (c) {
860
- if (c.status() === 'uploading') {
861
- c.abort();
862
- this.flowObj.uploadNextChunk();
863
- }
864
- }, this);
865
- },
866
-
867
- /**
868
- * Cancel current upload and remove from a list
869
- * @function
870
- */
871
- cancel: function () {
872
- this.flowObj.removeFile(this);
873
- },
874
-
875
- /**
876
- * Retry aborted file upload
877
- * @function
878
- */
879
- retry: function () {
880
- this.bootstrap();
881
- this.flowObj.upload();
882
- },
883
-
884
- /**
885
- * Clear current chunks and slice file again
886
- * @function
887
- */
888
- bootstrap: function () {
889
- this.abort(true);
890
- this.error = false;
891
- // Rebuild stack of chunks from file
892
- this._prevProgress = 0;
893
- var round = this.flowObj.opts.forceChunkSize ? Math.ceil : Math.floor;
894
- var chunks = Math.max(
895
- round(this.file.size / this.flowObj.opts.chunkSize), 1
896
- );
897
- for (var offset = 0; offset < chunks; offset++) {
898
- this.chunks.push(
899
- new FlowChunk(this.flowObj, this, offset)
900
- );
901
- }
902
- },
903
-
904
- /**
905
- * Get current upload progress status
906
- * @function
907
- * @returns {number} from 0 to 1
908
- */
909
- progress: function () {
910
- if (this.error) {
911
- return 1;
912
- }
913
- if (this.chunks.length === 1) {
914
- this._prevProgress = Math.max(this._prevProgress, this.chunks[0].progress());
915
- return this._prevProgress;
916
- }
917
- // Sum up progress across everything
918
- var bytesLoaded = 0;
919
- each(this.chunks, function (c) {
920
- // get chunk progress relative to entire file
921
- bytesLoaded += c.progress() * (c.endByte - c.startByte);
922
- });
923
- var percent = bytesLoaded / this.size;
924
- // We don't want to lose percentages when an upload is paused
925
- this._prevProgress = Math.max(this._prevProgress, percent > 0.999 ? 1 : percent);
926
- return this._prevProgress;
927
- },
928
-
929
- /**
930
- * Indicates if file is being uploaded at the moment
931
- * @function
932
- * @returns {boolean}
933
- */
934
- isUploading: function () {
935
- var uploading = false;
936
- each(this.chunks, function (chunk) {
937
- if (chunk.status() === 'uploading') {
938
- uploading = true;
939
- return false;
940
- }
941
- });
942
- return uploading;
943
- },
944
-
945
- /**
946
- * Indicates if file is has finished uploading and received a response
947
- * @function
948
- * @returns {boolean}
949
- */
950
- isComplete: function () {
951
- var outstanding = false;
952
- each(this.chunks, function (chunk) {
953
- var status = chunk.status();
954
- if (status === 'pending' || status === 'uploading' || chunk.preprocessState === 1) {
955
- outstanding = true;
956
- return false;
957
- }
958
- });
959
- return !outstanding;
960
- },
961
-
962
- /**
963
- * Count total size uploaded
964
- * @function
965
- * @returns {number}
966
- */
967
- sizeUploaded: function () {
968
- var size = 0;
969
- each(this.chunks, function (chunk) {
970
- size += chunk.sizeUploaded();
971
- });
972
- return size;
973
- },
974
-
975
- /**
976
- * Returns remaining time to finish upload file in seconds. Accuracy is based on average speed.
977
- * If speed is zero, time remaining will be equal to positive infinity `Number.POSITIVE_INFINITY`
978
- * @function
979
- * @returns {number}
980
- */
981
- timeRemaining: function () {
982
- if (this.paused || this.error) {
983
- return 0;
984
- }
985
- var delta = this.size - this.sizeUploaded();
986
- if (delta && !this.averageSpeed) {
987
- return Number.POSITIVE_INFINITY;
988
- }
989
- if (!delta && !this.averageSpeed) {
990
- return 0;
991
- }
992
- return Math.floor(delta / this.averageSpeed);
993
- },
994
-
995
- /**
996
- * Get file type
997
- * @function
998
- * @returns {string}
999
- */
1000
- getType: function () {
1001
- return this.file.type && this.file.type.split('/')[1];
1002
- },
1003
-
1004
- /**
1005
- * Get file extension
1006
- * @function
1007
- * @returns {string}
1008
- */
1009
- getExtension: function () {
1010
- return this.name.substr((~-this.name.lastIndexOf(".") >>> 0) + 2).toLowerCase();
1011
- }
1012
- };
1013
-
1014
-
1015
-
1016
-
1017
-
1018
-
1019
-
1020
-
1021
- /**
1022
- * Class for storing a single chunk
1023
- * @name FlowChunk
1024
- * @param {Flow} flowObj
1025
- * @param {FlowFile} fileObj
1026
- * @param {number} offset
1027
- * @constructor
1028
- */
1029
- function FlowChunk(flowObj, fileObj, offset) {
1030
-
1031
- /**
1032
- * Reference to parent flow object
1033
- * @type {Flow}
1034
- */
1035
- this.flowObj = flowObj;
1036
-
1037
- /**
1038
- * Reference to parent FlowFile object
1039
- * @type {FlowFile}
1040
- */
1041
- this.fileObj = fileObj;
1042
-
1043
- /**
1044
- * File size
1045
- * @type {number}
1046
- */
1047
- this.fileObjSize = fileObj.size;
1048
-
1049
- /**
1050
- * File offset
1051
- * @type {number}
1052
- */
1053
- this.offset = offset;
1054
-
1055
- /**
1056
- * Indicates if chunk existence was checked on the server
1057
- * @type {boolean}
1058
- */
1059
- this.tested = false;
1060
-
1061
- /**
1062
- * Number of retries performed
1063
- * @type {number}
1064
- */
1065
- this.retries = 0;
1066
-
1067
- /**
1068
- * Pending retry
1069
- * @type {boolean}
1070
- */
1071
- this.pendingRetry = false;
1072
-
1073
- /**
1074
- * Preprocess state
1075
- * @type {number} 0 = unprocessed, 1 = processing, 2 = finished
1076
- */
1077
- this.preprocessState = 0;
1078
-
1079
- /**
1080
- * Bytes transferred from total request size
1081
- * @type {number}
1082
- */
1083
- this.loaded = 0;
1084
-
1085
- /**
1086
- * Total request size
1087
- * @type {number}
1088
- */
1089
- this.total = 0;
1090
-
1091
- /**
1092
- * Size of a chunk
1093
- * @type {number}
1094
- */
1095
- var chunkSize = this.flowObj.opts.chunkSize;
1096
-
1097
- /**
1098
- * Chunk start byte in a file
1099
- * @type {number}
1100
- */
1101
- this.startByte = this.offset * chunkSize;
1102
-
1103
- /**
1104
- * Chunk end byte in a file
1105
- * @type {number}
1106
- */
1107
- this.endByte = Math.min(this.fileObjSize, (this.offset + 1) * chunkSize);
1108
-
1109
- /**
1110
- * XMLHttpRequest
1111
- * @type {XMLHttpRequest}
1112
- */
1113
- this.xhr = null;
1114
-
1115
- if (this.fileObjSize - this.endByte < chunkSize &&
1116
- !this.flowObj.opts.forceChunkSize) {
1117
- // The last chunk will be bigger than the chunk size,
1118
- // but less than 2*chunkSize
1119
- this.endByte = this.fileObjSize;
1120
- }
1121
-
1122
- var $ = this;
1123
-
1124
- /**
1125
- * Catch progress event
1126
- * @param {ProgressEvent} event
1127
- */
1128
- this.progressHandler = function(event) {
1129
- if (event.lengthComputable) {
1130
- $.loaded = event.loaded ;
1131
- $.total = event.total;
1132
- }
1133
- $.fileObj.chunkEvent('progress');
1134
- };
1135
-
1136
- /**
1137
- * Catch test event
1138
- * @param {Event} event
1139
- */
1140
- this.testHandler = function(event) {
1141
- var status = $.status();
1142
- if (status === 'success') {
1143
- $.tested = true;
1144
- $.fileObj.chunkEvent(status, $.message());
1145
- $.flowObj.uploadNextChunk();
1146
- } else if (!$.fileObj.paused) {// Error might be caused by file pause method
1147
- $.tested = true;
1148
- $.send();
1149
- }
1150
- };
1151
-
1152
- /**
1153
- * Upload has stopped
1154
- * @param {Event} event
1155
- */
1156
- this.doneHandler = function(event) {
1157
- var status = $.status();
1158
- if (status === 'success' || status === 'error') {
1159
- $.fileObj.chunkEvent(status, $.message());
1160
- $.flowObj.uploadNextChunk();
1161
- } else {
1162
- $.fileObj.chunkEvent('retry', $.message());
1163
- $.pendingRetry = true;
1164
- $.abort();
1165
- $.retries++;
1166
- var retryInterval = $.flowObj.opts.chunkRetryInterval;
1167
- if (retryInterval !== null) {
1168
- setTimeout(function () {
1169
- $.send();
1170
- }, retryInterval);
1171
- } else {
1172
- $.send();
1173
- }
1174
- }
1175
- };
1176
- }
1177
-
1178
- FlowChunk.prototype = {
1179
- /**
1180
- * Get params for a request
1181
- * @function
1182
- */
1183
- getParams: function () {
1184
- return {
1185
- flowChunkNumber: this.offset + 1,
1186
- flowChunkSize: this.flowObj.opts.chunkSize,
1187
- flowCurrentChunkSize: this.endByte - this.startByte,
1188
- flowTotalSize: this.fileObjSize,
1189
- flowIdentifier: this.fileObj.uniqueIdentifier,
1190
- flowFilename: this.fileObj.name,
1191
- flowRelativePath: this.fileObj.relativePath,
1192
- flowTotalChunks: this.fileObj.chunks.length
1193
- };
1194
- },
1195
-
1196
- /**
1197
- * Get target option with query params
1198
- * @function
1199
- * @param params
1200
- * @returns {string}
1201
- */
1202
- getTarget: function(target, params){
1203
- if(target.indexOf('?') < 0) {
1204
- target += '?';
1205
- } else {
1206
- target += '&';
1207
- }
1208
- return target + params.join('&');
1209
- },
1210
-
1211
- /**
1212
- * Makes a GET request without any data to see if the chunk has already
1213
- * been uploaded in a previous session
1214
- * @function
1215
- */
1216
- test: function () {
1217
- // Set up request and listen for event
1218
- this.xhr = new XMLHttpRequest();
1219
- this.xhr.addEventListener("load", this.testHandler, false);
1220
- this.xhr.addEventListener("error", this.testHandler, false);
1221
- var data = this.prepareXhrRequest('GET', true);
1222
- this.xhr.send(data);
1223
- },
1224
-
1225
- /**
1226
- * Finish preprocess state
1227
- * @function
1228
- */
1229
- preprocessFinished: function () {
1230
- this.preprocessState = 2;
1231
- this.send();
1232
- },
1233
-
1234
- /**
1235
- * Uploads the actual data in a POST call
1236
- * @function
1237
- */
1238
- send: function () {
1239
- var preprocess = this.flowObj.opts.preprocess;
1240
- if (typeof preprocess === 'function') {
1241
- switch (this.preprocessState) {
1242
- case 0:
1243
- this.preprocessState = 1;
1244
- preprocess(this);
1245
- return;
1246
- case 1:
1247
- return;
1248
- }
1249
- }
1250
- if (this.flowObj.opts.testChunks && !this.tested) {
1251
- this.test();
1252
- return;
1253
- }
1254
-
1255
- this.loaded = 0;
1256
- this.total = 0;
1257
- this.pendingRetry = false;
1258
-
1259
- var func = (this.fileObj.file.slice ? 'slice' :
1260
- (this.fileObj.file.mozSlice ? 'mozSlice' :
1261
- (this.fileObj.file.webkitSlice ? 'webkitSlice' :
1262
- 'slice')));
1263
- var bytes = this.fileObj.file[func](this.startByte, this.endByte, this.fileObj.file.type);
1264
-
1265
- // Set up request and listen for event
1266
- this.xhr = new XMLHttpRequest();
1267
- this.xhr.upload.addEventListener('progress', this.progressHandler, false);
1268
- this.xhr.addEventListener("load", this.doneHandler, false);
1269
- this.xhr.addEventListener("error", this.doneHandler, false);
1270
-
1271
- var data = this.prepareXhrRequest('POST', false, this.flowObj.opts.method, bytes);
1272
- this.xhr.send(data);
1273
- },
1274
-
1275
- /**
1276
- * Abort current xhr request
1277
- * @function
1278
- */
1279
- abort: function () {
1280
- // Abort and reset
1281
- var xhr = this.xhr;
1282
- this.xhr = null;
1283
- if (xhr) {
1284
- xhr.abort();
1285
- }
1286
- },
1287
-
1288
- /**
1289
- * Retrieve current chunk upload status
1290
- * @function
1291
- * @returns {string} 'pending', 'uploading', 'success', 'error'
1292
- */
1293
- status: function () {
1294
- if (this.pendingRetry || this.preprocessState === 1) {
1295
- // if pending retry then that's effectively the same as actively uploading,
1296
- // there might just be a slight delay before the retry starts
1297
- return 'uploading';
1298
- } else if (!this.xhr) {
1299
- return 'pending';
1300
- } else if (this.xhr.readyState < 4) {
1301
- // Status is really 'OPENED', 'HEADERS_RECEIVED'
1302
- // or 'LOADING' - meaning that stuff is happening
1303
- return 'uploading';
1304
- } else {
1305
- if (this.xhr.status == 200) {
1306
- // HTTP 200, perfect
1307
- return 'success';
1308
- } else if (this.flowObj.opts.permanentErrors.indexOf(this.xhr.status) > -1 ||
1309
- this.retries >= this.flowObj.opts.maxChunkRetries) {
1310
- // HTTP 415/500/501, permanent error
1311
- return 'error';
1312
- } else {
1313
- // this should never happen, but we'll reset and queue a retry
1314
- // a likely case for this would be 503 service unavailable
1315
- this.abort();
1316
- return 'pending';
1317
- }
1318
- }
1319
- },
1320
-
1321
- /**
1322
- * Get response from xhr request
1323
- * @function
1324
- * @returns {String}
1325
- */
1326
- message: function () {
1327
- return this.xhr ? this.xhr.responseText : '';
1328
- },
1329
-
1330
- /**
1331
- * Get upload progress
1332
- * @function
1333
- * @returns {number}
1334
- */
1335
- progress: function () {
1336
- if (this.pendingRetry) {
1337
- return 0;
1338
- }
1339
- var s = this.status();
1340
- if (s === 'success' || s === 'error') {
1341
- return 1;
1342
- } else if (s === 'pending') {
1343
- return 0;
1344
- } else {
1345
- return this.total > 0 ? this.loaded / this.total : 0;
1346
- }
1347
- },
1348
-
1349
- /**
1350
- * Count total size uploaded
1351
- * @function
1352
- * @returns {number}
1353
- */
1354
- sizeUploaded: function () {
1355
- var size = this.endByte - this.startByte;
1356
- // can't return only chunk.loaded value, because it is bigger than chunk size
1357
- if (this.status() !== 'success') {
1358
- size = this.progress() * size;
1359
- }
1360
- return size;
1361
- },
1362
-
1363
- /**
1364
- * Prepare Xhr request. Set query, headers and data
1365
- * @param {string} method GET or POST
1366
- * @param {bool} isTest is this a test request
1367
- * @param {string} [paramsMethod] octet or form
1368
- * @param {Blob} [blob] to send
1369
- * @returns {FormData|Blob|Null} data to send
1370
- */
1371
- prepareXhrRequest: function(method, isTest, paramsMethod, blob) {
1372
- // Add data from the query options
1373
- var query = evalOpts(this.flowObj.opts.query, this.fileObj, this, isTest);
1374
- query = extend(this.getParams(), query);
1375
-
1376
- var target = evalOpts(this.flowObj.opts.target, this.fileObj, this, isTest);
1377
- var data = null;
1378
- if (method === 'GET' || paramsMethod === 'octet') {
1379
- // Add data from the query options
1380
- var params = [];
1381
- each(query, function (v, k) {
1382
- params.push([encodeURIComponent(k), encodeURIComponent(v)].join('='));
1383
- });
1384
- target = this.getTarget(target, params);
1385
- data = blob || null;
1386
- } else {
1387
- // Add data from the query options
1388
- data = new FormData();
1389
- each(query, function (v, k) {
1390
- data.append(k, v);
1391
- });
1392
- data.append(this.flowObj.opts.fileParameterName, blob, this.fileObj.file.name);
1393
- }
1394
-
1395
- this.xhr.open(method, target, true);
1396
- this.xhr.withCredentials = this.flowObj.opts.withCredentials;
1397
-
1398
- // Add data from header options
1399
- each(evalOpts(this.flowObj.opts.headers, this.fileObj, this, isTest), function (v, k) {
1400
- this.xhr.setRequestHeader(k, v);
1401
- }, this);
1402
-
1403
- return data;
1404
- }
1405
- };
1406
-
1407
- /**
1408
- * Remove value from array
1409
- * @param array
1410
- * @param value
1411
- */
1412
- function arrayRemove(array, value) {
1413
- var index = array.indexOf(value);
1414
- if (index > -1) {
1415
- array.splice(index, 1);
1416
- }
1417
- }
1418
-
1419
- /**
1420
- * If option is a function, evaluate it with given params
1421
- * @param {*} data
1422
- * @param {...} args arguments of a callback
1423
- * @returns {*}
1424
- */
1425
- function evalOpts(data, args) {
1426
- if (typeof data === "function") {
1427
- // `arguments` is an object, not array, in FF, so:
1428
- args = Array.prototype.slice.call(arguments);
1429
- data = data.apply(null, args.slice(1));
1430
- }
1431
- return data;
1432
- }
1433
- Flow.evalOpts = evalOpts;
1434
-
1435
- /**
1436
- * Execute function asynchronously
1437
- * @param fn
1438
- * @param context
1439
- */
1440
- function async(fn, context) {
1441
- setTimeout(fn.bind(context), 0);
1442
- }
1443
-
1444
- /**
1445
- * Extends the destination object `dst` by copying all of the properties from
1446
- * the `src` object(s) to `dst`. You can specify multiple `src` objects.
1447
- * @function
1448
- * @param {Object} dst Destination object.
1449
- * @param {...Object} src Source object(s).
1450
- * @returns {Object} Reference to `dst`.
1451
- */
1452
- function extend(dst, src) {
1453
- each(arguments, function(obj) {
1454
- if (obj !== dst) {
1455
- each(obj, function(value, key){
1456
- dst[key] = value;
1457
- });
1458
- }
1459
- });
1460
- return dst;
1461
- }
1462
- Flow.extend = extend;
1463
-
1464
- /**
1465
- * Iterate each element of an object
1466
- * @function
1467
- * @param {Array|Object} obj object or an array to iterate
1468
- * @param {Function} callback first argument is a value and second is a key.
1469
- * @param {Object=} context Object to become context (`this`) for the iterator function.
1470
- */
1471
- function each(obj, callback, context) {
1472
- if (!obj) {
1473
- return ;
1474
- }
1475
- var key;
1476
- // Is Array?
1477
- if (typeof(obj.length) !== 'undefined') {
1478
- for (key = 0; key < obj.length; key++) {
1479
- if (callback.call(context, obj[key], key) === false) {
1480
- return ;
1481
- }
1482
- }
1483
- } else {
1484
- for (key in obj) {
1485
- if (obj.hasOwnProperty(key) && callback.call(context, obj[key], key) === false) {
1486
- return ;
1487
- }
1488
- }
1489
- }
1490
- }
1491
- Flow.each = each;
1492
-
1493
- /**
1494
- * FlowFile constructor
1495
- * @type {FlowFile}
1496
- */
1497
- Flow.FlowFile = FlowFile;
1498
-
1499
- /**
1500
- * FlowFile constructor
1501
- * @type {FlowChunk}
1502
- */
1503
- Flow.FlowChunk = FlowChunk;
1504
-
1505
- /**
1506
- * Library version
1507
- * @type {string}
1508
- */
1509
- Flow.version = '2.6.2';
1510
-
1511
- if ( typeof module === "object" && module && typeof module.exports === "object" ) {
1512
- // Expose Flow as module.exports in loaders that implement the Node
1513
- // module pattern (including browserify). Do not create the global, since
1514
- // the user will be storing it themselves locally, and globals are frowned
1515
- // upon in the Node module world.
1516
- module.exports = Flow;
1517
- } else {
1518
- // Otherwise expose Flow to the global object as usual
1519
- window.Flow = Flow;
1520
-
1521
- // Register as a named AMD module, since Flow can be concatenated with other
1522
- // files that may use define, but not via a proper concatenation script that
1523
- // understands anonymous AMD modules. A named AMD is safest and most robust
1524
- // way to register. Lowercase flow is used because AMD module names are
1525
- // derived from file names, and Flow is normally delivered in a lowercase
1526
- // file name. Do this after creating the global so that if an AMD module wants
1527
- // to call noConflict to hide this version of Flow, it will work.
1528
- if ( typeof define === "function" && define.amd ) {
1529
- define( "flow", [], function () { return Flow; } );
1530
- }
1531
- }
1532
- })(window, document);
1
+ /**
2
+ * @license MIT
3
+ */
4
+ (function(window, document, undefined) {'use strict';
5
+ // ie10+
6
+ var ie10plus = window.navigator.msPointerEnabled;
7
+ /**
8
+ * Flow.js is a library providing multiple simultaneous, stable and
9
+ * resumable uploads via the HTML5 File API.
10
+ * @param [opts]
11
+ * @param {number} [opts.chunkSize]
12
+ * @param {bool} [opts.forceChunkSize]
13
+ * @param {number} [opts.simultaneousUploads]
14
+ * @param {bool} [opts.singleFile]
15
+ * @param {string} [opts.fileParameterName]
16
+ * @param {number} [opts.progressCallbacksInterval]
17
+ * @param {number} [opts.speedSmoothingFactor]
18
+ * @param {Object|Function} [opts.query]
19
+ * @param {Object|Function} [opts.headers]
20
+ * @param {bool} [opts.withCredentials]
21
+ * @param {Function} [opts.preprocess]
22
+ * @param {string} [opts.method]
23
+ * @param {string|Function} [opts.testMethod]
24
+ * @param {string|Function} [opts.uploadMethod]
25
+ * @param {bool} [opts.prioritizeFirstAndLastChunk]
26
+ * @param {bool} [opts.allowDuplicateUploads]
27
+ * @param {string|Function} [opts.target]
28
+ * @param {number} [opts.maxChunkRetries]
29
+ * @param {number} [opts.chunkRetryInterval]
30
+ * @param {Array.<number>} [opts.permanentErrors]
31
+ * @param {Array.<number>} [opts.successStatuses]
32
+ * @param {Function} [opts.initFileFn]
33
+ * @param {Function} [opts.readFileFn]
34
+ * @param {Function} [opts.generateUniqueIdentifier]
35
+ * @constructor
36
+ */
37
+ function Flow(opts) {
38
+ /**
39
+ * Supported by browser?
40
+ * @type {boolean}
41
+ */
42
+ this.support = (
43
+ typeof File !== 'undefined' &&
44
+ typeof Blob !== 'undefined' &&
45
+ typeof FileList !== 'undefined' &&
46
+ (
47
+ !!Blob.prototype.slice || !!Blob.prototype.webkitSlice || !!Blob.prototype.mozSlice ||
48
+ false
49
+ ) // slicing files support
50
+ );
51
+
52
+ if (!this.support) {
53
+ return ;
54
+ }
55
+
56
+ /**
57
+ * Check if directory upload is supported
58
+ * @type {boolean}
59
+ */
60
+ this.supportDirectory = (
61
+ /Chrome/.test(window.navigator.userAgent) ||
62
+ /Firefox/.test(window.navigator.userAgent) ||
63
+ /Edge/.test(window.navigator.userAgent)
64
+ );
65
+
66
+ /**
67
+ * List of FlowFile objects
68
+ * @type {Array.<FlowFile>}
69
+ */
70
+ this.files = [];
71
+
72
+ /**
73
+ * Default options for flow.js
74
+ * @type {Object}
75
+ */
76
+ this.defaults = {
77
+ chunkSize: 1024 * 1024,
78
+ forceChunkSize: false,
79
+ simultaneousUploads: 3,
80
+ singleFile: false,
81
+ fileParameterName: 'file',
82
+ progressCallbacksInterval: 500,
83
+ speedSmoothingFactor: 0.1,
84
+ query: {},
85
+ headers: {},
86
+ withCredentials: false,
87
+ preprocess: null,
88
+ method: 'multipart',
89
+ testMethod: 'GET',
90
+ uploadMethod: 'POST',
91
+ prioritizeFirstAndLastChunk: false,
92
+ allowDuplicateUploads: false,
93
+ target: '/',
94
+ testChunks: true,
95
+ generateUniqueIdentifier: null,
96
+ maxChunkRetries: 0,
97
+ chunkRetryInterval: null,
98
+ permanentErrors: [404, 413, 415, 500, 501],
99
+ successStatuses: [200, 201, 202],
100
+ onDropStopPropagation: false,
101
+ initFileFn: null,
102
+ readFileFn: webAPIFileRead
103
+ };
104
+
105
+ /**
106
+ * Current options
107
+ * @type {Object}
108
+ */
109
+ this.opts = {};
110
+
111
+ /**
112
+ * List of events:
113
+ * key stands for event name
114
+ * value array list of callbacks
115
+ * @type {}
116
+ */
117
+ this.events = {};
118
+
119
+ var $ = this;
120
+
121
+ /**
122
+ * On drop event
123
+ * @function
124
+ * @param {MouseEvent} event
125
+ */
126
+ this.onDrop = function (event) {
127
+ if ($.opts.onDropStopPropagation) {
128
+ event.stopPropagation();
129
+ }
130
+ event.preventDefault();
131
+ var dataTransfer = event.dataTransfer;
132
+ if (dataTransfer.items && dataTransfer.items[0] &&
133
+ dataTransfer.items[0].webkitGetAsEntry) {
134
+ $.webkitReadDataTransfer(event);
135
+ } else {
136
+ $.addFiles(dataTransfer.files, event);
137
+ }
138
+ };
139
+
140
+ /**
141
+ * Prevent default
142
+ * @function
143
+ * @param {MouseEvent} event
144
+ */
145
+ this.preventEvent = function (event) {
146
+ event.preventDefault();
147
+ };
148
+
149
+
150
+ /**
151
+ * Current options
152
+ * @type {Object}
153
+ */
154
+ this.opts = Flow.extend({}, this.defaults, opts || {});
155
+
156
+ }
157
+
158
+ Flow.prototype = {
159
+ /**
160
+ * Set a callback for an event, possible events:
161
+ * fileSuccess(file), fileProgress(file), fileAdded(file, event),
162
+ * fileRemoved(file), fileRetry(file), fileError(file, message),
163
+ * complete(), progress(), error(message, file), pause()
164
+ * @function
165
+ * @param {string} event
166
+ * @param {Function} callback
167
+ */
168
+ on: function (event, callback) {
169
+ event = event.toLowerCase();
170
+ if (!this.events.hasOwnProperty(event)) {
171
+ this.events[event] = [];
172
+ }
173
+ this.events[event].push(callback);
174
+ },
175
+
176
+ /**
177
+ * Remove event callback
178
+ * @function
179
+ * @param {string} [event] removes all events if not specified
180
+ * @param {Function} [fn] removes all callbacks of event if not specified
181
+ */
182
+ off: function (event, fn) {
183
+ if (event !== undefined) {
184
+ event = event.toLowerCase();
185
+ if (fn !== undefined) {
186
+ if (this.events.hasOwnProperty(event)) {
187
+ arrayRemove(this.events[event], fn);
188
+ }
189
+ } else {
190
+ delete this.events[event];
191
+ }
192
+ } else {
193
+ this.events = {};
194
+ }
195
+ },
196
+
197
+ /**
198
+ * Fire an event
199
+ * @function
200
+ * @param {string} event event name
201
+ * @param {...} args arguments of a callback
202
+ * @return {bool} value is false if at least one of the event handlers which handled this event
203
+ * returned false. Otherwise it returns true.
204
+ */
205
+ fire: function (event, args) {
206
+ // `arguments` is an object, not array, in FF, so:
207
+ args = Array.prototype.slice.call(arguments);
208
+ event = event.toLowerCase();
209
+ var preventDefault = false;
210
+ if (this.events.hasOwnProperty(event)) {
211
+ each(this.events[event], function (callback) {
212
+ preventDefault = callback.apply(this, args.slice(1)) === false || preventDefault;
213
+ }, this);
214
+ }
215
+ if (event != 'catchall') {
216
+ args.unshift('catchAll');
217
+ preventDefault = this.fire.apply(this, args) === false || preventDefault;
218
+ }
219
+ return !preventDefault;
220
+ },
221
+
222
+ /**
223
+ * Read webkit dataTransfer object
224
+ * @param event
225
+ */
226
+ webkitReadDataTransfer: function (event) {
227
+ var $ = this;
228
+ var queue = event.dataTransfer.items.length;
229
+ var files = [];
230
+ each(event.dataTransfer.items, function (item) {
231
+ var entry = item.webkitGetAsEntry();
232
+ if (!entry) {
233
+ decrement();
234
+ return ;
235
+ }
236
+ if (entry.isFile) {
237
+ // due to a bug in Chrome's File System API impl - #149735
238
+ fileReadSuccess(item.getAsFile(), entry.fullPath);
239
+ } else {
240
+ readDirectory(entry.createReader());
241
+ }
242
+ });
243
+ function readDirectory(reader) {
244
+ reader.readEntries(function (entries) {
245
+ if (entries.length) {
246
+ queue += entries.length;
247
+ each(entries, function(entry) {
248
+ if (entry.isFile) {
249
+ var fullPath = entry.fullPath;
250
+ entry.file(function (file) {
251
+ fileReadSuccess(file, fullPath);
252
+ }, readError);
253
+ } else if (entry.isDirectory) {
254
+ readDirectory(entry.createReader());
255
+ }
256
+ });
257
+ readDirectory(reader);
258
+ } else {
259
+ decrement();
260
+ }
261
+ }, readError);
262
+ }
263
+ function fileReadSuccess(file, fullPath) {
264
+ // relative path should not start with "/"
265
+ file.relativePath = fullPath.substring(1);
266
+ files.push(file);
267
+ decrement();
268
+ }
269
+ function readError(fileError) {
270
+ throw fileError;
271
+ }
272
+ function decrement() {
273
+ if (--queue == 0) {
274
+ $.addFiles(files, event);
275
+ }
276
+ }
277
+ },
278
+
279
+ /**
280
+ * Generate unique identifier for a file
281
+ * @function
282
+ * @param {FlowFile} file
283
+ * @returns {string}
284
+ */
285
+ generateUniqueIdentifier: function (file) {
286
+ var custom = this.opts.generateUniqueIdentifier;
287
+ if (typeof custom === 'function') {
288
+ return custom(file);
289
+ }
290
+ // Some confusion in different versions of Firefox
291
+ var relativePath = file.relativePath || file.webkitRelativePath || file.fileName || file.name;
292
+ return file.size + '-' + relativePath.replace(/[^0-9a-zA-Z_-]/img, '');
293
+ },
294
+
295
+ /**
296
+ * Upload next chunk from the queue
297
+ * @function
298
+ * @returns {boolean}
299
+ * @private
300
+ */
301
+ uploadNextChunk: function (preventEvents) {
302
+ // In some cases (such as videos) it's really handy to upload the first
303
+ // and last chunk of a file quickly; this let's the server check the file's
304
+ // metadata and determine if there's even a point in continuing.
305
+ var found = false;
306
+ if (this.opts.prioritizeFirstAndLastChunk) {
307
+ each(this.files, function (file) {
308
+ if (!file.paused && file.chunks.length &&
309
+ file.chunks[0].status() === 'pending') {
310
+ file.chunks[0].send();
311
+ found = true;
312
+ return false;
313
+ }
314
+ if (!file.paused && file.chunks.length > 1 &&
315
+ file.chunks[file.chunks.length - 1].status() === 'pending') {
316
+ file.chunks[file.chunks.length - 1].send();
317
+ found = true;
318
+ return false;
319
+ }
320
+ });
321
+ if (found) {
322
+ return found;
323
+ }
324
+ }
325
+
326
+ // Now, simply look for the next, best thing to upload
327
+ each(this.files, function (file) {
328
+ if (!file.paused) {
329
+ each(file.chunks, function (chunk) {
330
+ if (chunk.status() === 'pending') {
331
+ chunk.send();
332
+ found = true;
333
+ return false;
334
+ }
335
+ });
336
+ }
337
+ if (found) {
338
+ return false;
339
+ }
340
+ });
341
+ if (found) {
342
+ return true;
343
+ }
344
+
345
+ // The are no more outstanding chunks to upload, check is everything is done
346
+ var outstanding = false;
347
+ each(this.files, function (file) {
348
+ if (!file.isComplete()) {
349
+ outstanding = true;
350
+ return false;
351
+ }
352
+ });
353
+ if (!outstanding && !preventEvents) {
354
+ // All chunks have been uploaded, complete
355
+ async(function () {
356
+ this.fire('complete');
357
+ }, this);
358
+ }
359
+ return false;
360
+ },
361
+
362
+
363
+ /**
364
+ * Assign a browse action to one or more DOM nodes.
365
+ * @function
366
+ * @param {Element|Array.<Element>} domNodes
367
+ * @param {boolean} isDirectory Pass in true to allow directories to
368
+ * @param {boolean} singleFile prevent multi file upload
369
+ * @param {Object} attributes set custom attributes:
370
+ * http://www.w3.org/TR/html-markup/input.file.html#input.file-attributes
371
+ * eg: accept: 'image/*'
372
+ * be selected (Chrome only).
373
+ */
374
+ assignBrowse: function (domNodes, isDirectory, singleFile, attributes) {
375
+ if (domNodes instanceof Element) {
376
+ domNodes = [domNodes];
377
+ }
378
+
379
+ each(domNodes, function (domNode) {
380
+ var input;
381
+ if (domNode.tagName === 'INPUT' && domNode.type === 'file') {
382
+ input = domNode;
383
+ } else {
384
+ input = document.createElement('input');
385
+ input.setAttribute('type', 'file');
386
+ // display:none - not working in opera 12
387
+ extend(input.style, {
388
+ visibility: 'hidden',
389
+ position: 'absolute',
390
+ width: '1px',
391
+ height: '1px'
392
+ });
393
+ // for opera 12 browser, input must be assigned to a document
394
+ domNode.appendChild(input);
395
+ // https://developer.mozilla.org/en/using_files_from_web_applications)
396
+ // event listener is executed two times
397
+ // first one - original mouse click event
398
+ // second - input.click(), input is inside domNode
399
+ domNode.addEventListener('click', function() {
400
+ input.click();
401
+ }, false);
402
+ }
403
+ if (!this.opts.singleFile && !singleFile) {
404
+ input.setAttribute('multiple', 'multiple');
405
+ }
406
+ if (isDirectory) {
407
+ input.setAttribute('webkitdirectory', 'webkitdirectory');
408
+ }
409
+ each(attributes, function (value, key) {
410
+ input.setAttribute(key, value);
411
+ });
412
+ // When new files are added, simply append them to the overall list
413
+ var $ = this;
414
+ input.addEventListener('change', function (e) {
415
+ if (e.target.value) {
416
+ $.addFiles(e.target.files, e);
417
+ e.target.value = '';
418
+ }
419
+ }, false);
420
+ }, this);
421
+ },
422
+
423
+ /**
424
+ * Assign one or more DOM nodes as a drop target.
425
+ * @function
426
+ * @param {Element|Array.<Element>} domNodes
427
+ */
428
+ assignDrop: function (domNodes) {
429
+ if (typeof domNodes.length === 'undefined') {
430
+ domNodes = [domNodes];
431
+ }
432
+ each(domNodes, function (domNode) {
433
+ domNode.addEventListener('dragover', this.preventEvent, false);
434
+ domNode.addEventListener('dragenter', this.preventEvent, false);
435
+ domNode.addEventListener('drop', this.onDrop, false);
436
+ }, this);
437
+ },
438
+
439
+ /**
440
+ * Un-assign drop event from DOM nodes
441
+ * @function
442
+ * @param domNodes
443
+ */
444
+ unAssignDrop: function (domNodes) {
445
+ if (typeof domNodes.length === 'undefined') {
446
+ domNodes = [domNodes];
447
+ }
448
+ each(domNodes, function (domNode) {
449
+ domNode.removeEventListener('dragover', this.preventEvent);
450
+ domNode.removeEventListener('dragenter', this.preventEvent);
451
+ domNode.removeEventListener('drop', this.onDrop);
452
+ }, this);
453
+ },
454
+
455
+ /**
456
+ * Returns a boolean indicating whether or not the instance is currently
457
+ * uploading anything.
458
+ * @function
459
+ * @returns {boolean}
460
+ */
461
+ isUploading: function () {
462
+ var uploading = false;
463
+ each(this.files, function (file) {
464
+ if (file.isUploading()) {
465
+ uploading = true;
466
+ return false;
467
+ }
468
+ });
469
+ return uploading;
470
+ },
471
+
472
+ /**
473
+ * should upload next chunk
474
+ * @function
475
+ * @returns {boolean|number}
476
+ */
477
+ _shouldUploadNext: function () {
478
+ var num = 0;
479
+ var should = true;
480
+ var simultaneousUploads = this.opts.simultaneousUploads;
481
+ each(this.files, function (file) {
482
+ each(file.chunks, function(chunk) {
483
+ if (chunk.status() === 'uploading') {
484
+ num++;
485
+ if (num >= simultaneousUploads) {
486
+ should = false;
487
+ return false;
488
+ }
489
+ }
490
+ });
491
+ });
492
+ // if should is true then return uploading chunks's length
493
+ return should && num;
494
+ },
495
+
496
+ /**
497
+ * Start or resume uploading.
498
+ * @function
499
+ */
500
+ upload: function () {
501
+ // Make sure we don't start too many uploads at once
502
+ var ret = this._shouldUploadNext();
503
+ if (ret === false) {
504
+ return;
505
+ }
506
+ // Kick off the queue
507
+ this.fire('uploadStart');
508
+ var started = false;
509
+ for (var num = 1; num <= this.opts.simultaneousUploads - ret; num++) {
510
+ started = this.uploadNextChunk(true) || started;
511
+ }
512
+ if (!started) {
513
+ async(function () {
514
+ this.fire('complete');
515
+ }, this);
516
+ }
517
+ },
518
+
519
+ /**
520
+ * Resume uploading.
521
+ * @function
522
+ */
523
+ resume: function () {
524
+ each(this.files, function (file) {
525
+ if (!file.isComplete()) {
526
+ file.resume();
527
+ }
528
+ });
529
+ },
530
+
531
+ /**
532
+ * Pause uploading.
533
+ * @function
534
+ */
535
+ pause: function () {
536
+ each(this.files, function (file) {
537
+ file.pause();
538
+ });
539
+ },
540
+
541
+ /**
542
+ * Cancel upload of all FlowFile objects and remove them from the list.
543
+ * @function
544
+ */
545
+ cancel: function () {
546
+ for (var i = this.files.length - 1; i >= 0; i--) {
547
+ this.files[i].cancel();
548
+ }
549
+ },
550
+
551
+ /**
552
+ * Returns a number between 0 and 1 indicating the current upload progress
553
+ * of all files.
554
+ * @function
555
+ * @returns {number}
556
+ */
557
+ progress: function () {
558
+ var totalDone = 0;
559
+ var totalSize = 0;
560
+ // Resume all chunks currently being uploaded
561
+ each(this.files, function (file) {
562
+ totalDone += file.progress() * file.size;
563
+ totalSize += file.size;
564
+ });
565
+ return totalSize > 0 ? totalDone / totalSize : 0;
566
+ },
567
+
568
+ /**
569
+ * Add a HTML5 File object to the list of files.
570
+ * @function
571
+ * @param {File} file
572
+ * @param {Event} [event] event is optional
573
+ */
574
+ addFile: function (file, event) {
575
+ this.addFiles([file], event);
576
+ },
577
+
578
+ /**
579
+ * Add a HTML5 File object to the list of files.
580
+ * @function
581
+ * @param {FileList|Array} fileList
582
+ * @param {Event} [event] event is optional
583
+ */
584
+ addFiles: function (fileList, event) {
585
+ var files = [];
586
+ each(fileList, function (file) {
587
+ // https://github.com/flowjs/flow.js/issues/55
588
+ if ((!ie10plus || ie10plus && file.size > 0) && !(file.size % 4096 === 0 && (file.name === '.' || file.fileName === '.'))) {
589
+ var uniqueIdentifier = this.generateUniqueIdentifier(file);
590
+ if (this.opts.allowDuplicateUploads || !this.getFromUniqueIdentifier(uniqueIdentifier)) {
591
+ var f = new FlowFile(this, file, uniqueIdentifier);
592
+ if (this.fire('fileAdded', f, event)) {
593
+ files.push(f);
594
+ }
595
+ }
596
+ }
597
+ }, this);
598
+ if (this.fire('filesAdded', files, event)) {
599
+ each(files, function (file) {
600
+ if (this.opts.singleFile && this.files.length > 0) {
601
+ this.removeFile(this.files[0]);
602
+ }
603
+ this.files.push(file);
604
+ }, this);
605
+ this.fire('filesSubmitted', files, event);
606
+ }
607
+ },
608
+
609
+
610
+ /**
611
+ * Cancel upload of a specific FlowFile object from the list.
612
+ * @function
613
+ * @param {FlowFile} file
614
+ */
615
+ removeFile: function (file) {
616
+ for (var i = this.files.length - 1; i >= 0; i--) {
617
+ if (this.files[i] === file) {
618
+ this.files.splice(i, 1);
619
+ file.abort();
620
+ this.fire('fileRemoved', file);
621
+ }
622
+ }
623
+ },
624
+
625
+ /**
626
+ * Look up a FlowFile object by its unique identifier.
627
+ * @function
628
+ * @param {string} uniqueIdentifier
629
+ * @returns {boolean|FlowFile} false if file was not found
630
+ */
631
+ getFromUniqueIdentifier: function (uniqueIdentifier) {
632
+ var ret = false;
633
+ each(this.files, function (file) {
634
+ if (file.uniqueIdentifier === uniqueIdentifier) {
635
+ ret = file;
636
+ }
637
+ });
638
+ return ret;
639
+ },
640
+
641
+ /**
642
+ * Returns the total size of all files in bytes.
643
+ * @function
644
+ * @returns {number}
645
+ */
646
+ getSize: function () {
647
+ var totalSize = 0;
648
+ each(this.files, function (file) {
649
+ totalSize += file.size;
650
+ });
651
+ return totalSize;
652
+ },
653
+
654
+ /**
655
+ * Returns the total size uploaded of all files in bytes.
656
+ * @function
657
+ * @returns {number}
658
+ */
659
+ sizeUploaded: function () {
660
+ var size = 0;
661
+ each(this.files, function (file) {
662
+ size += file.sizeUploaded();
663
+ });
664
+ return size;
665
+ },
666
+
667
+ /**
668
+ * Returns remaining time to upload all files in seconds. Accuracy is based on average speed.
669
+ * If speed is zero, time remaining will be equal to positive infinity `Number.POSITIVE_INFINITY`
670
+ * @function
671
+ * @returns {number}
672
+ */
673
+ timeRemaining: function () {
674
+ var sizeDelta = 0;
675
+ var averageSpeed = 0;
676
+ each(this.files, function (file) {
677
+ if (!file.paused && !file.error) {
678
+ sizeDelta += file.size - file.sizeUploaded();
679
+ averageSpeed += file.averageSpeed;
680
+ }
681
+ });
682
+ if (sizeDelta && !averageSpeed) {
683
+ return Number.POSITIVE_INFINITY;
684
+ }
685
+ if (!sizeDelta && !averageSpeed) {
686
+ return 0;
687
+ }
688
+ return Math.floor(sizeDelta / averageSpeed);
689
+ }
690
+ };
691
+
692
+
693
+
694
+
695
+
696
+
697
+ /**
698
+ * FlowFile class
699
+ * @name FlowFile
700
+ * @param {Flow} flowObj
701
+ * @param {File} file
702
+ * @param {string} uniqueIdentifier
703
+ * @constructor
704
+ */
705
+ function FlowFile(flowObj, file, uniqueIdentifier) {
706
+
707
+ /**
708
+ * Reference to parent Flow instance
709
+ * @type {Flow}
710
+ */
711
+ this.flowObj = flowObj;
712
+
713
+ /**
714
+ * Used to store the bytes read
715
+ * @type {Blob|string}
716
+ */
717
+ this.bytes = null;
718
+
719
+ /**
720
+ * Reference to file
721
+ * @type {File}
722
+ */
723
+ this.file = file;
724
+
725
+ /**
726
+ * File name. Some confusion in different versions of Firefox
727
+ * @type {string}
728
+ */
729
+ this.name = file.fileName || file.name;
730
+
731
+ /**
732
+ * File size
733
+ * @type {number}
734
+ */
735
+ this.size = file.size;
736
+
737
+ /**
738
+ * Relative file path
739
+ * @type {string}
740
+ */
741
+ this.relativePath = file.relativePath || file.webkitRelativePath || this.name;
742
+
743
+ /**
744
+ * File unique identifier
745
+ * @type {string}
746
+ */
747
+ this.uniqueIdentifier = (uniqueIdentifier === undefined ? flowObj.generateUniqueIdentifier(file) : uniqueIdentifier);
748
+
749
+ /**
750
+ * List of chunks
751
+ * @type {Array.<FlowChunk>}
752
+ */
753
+ this.chunks = [];
754
+
755
+ /**
756
+ * Indicated if file is paused
757
+ * @type {boolean}
758
+ */
759
+ this.paused = false;
760
+
761
+ /**
762
+ * Indicated if file has encountered an error
763
+ * @type {boolean}
764
+ */
765
+ this.error = false;
766
+
767
+ /**
768
+ * Average upload speed
769
+ * @type {number}
770
+ */
771
+ this.averageSpeed = 0;
772
+
773
+ /**
774
+ * Current upload speed
775
+ * @type {number}
776
+ */
777
+ this.currentSpeed = 0;
778
+
779
+ /**
780
+ * Date then progress was called last time
781
+ * @type {number}
782
+ * @private
783
+ */
784
+ this._lastProgressCallback = Date.now();
785
+
786
+ /**
787
+ * Previously uploaded file size
788
+ * @type {number}
789
+ * @private
790
+ */
791
+ this._prevUploadedSize = 0;
792
+
793
+ /**
794
+ * Holds previous progress
795
+ * @type {number}
796
+ * @private
797
+ */
798
+ this._prevProgress = 0;
799
+
800
+ this.bootstrap();
801
+ }
802
+
803
+ FlowFile.prototype = {
804
+ /**
805
+ * Update speed parameters
806
+ * @link http://stackoverflow.com/questions/2779600/how-to-estimate-download-time-remaining-accurately
807
+ * @function
808
+ */
809
+ measureSpeed: function () {
810
+ var timeSpan = Date.now() - this._lastProgressCallback;
811
+ if (!timeSpan) {
812
+ return ;
813
+ }
814
+ var smoothingFactor = this.flowObj.opts.speedSmoothingFactor;
815
+ var uploaded = this.sizeUploaded();
816
+ // Prevent negative upload speed after file upload resume
817
+ this.currentSpeed = Math.max((uploaded - this._prevUploadedSize) / timeSpan * 1000, 0);
818
+ this.averageSpeed = smoothingFactor * this.currentSpeed + (1 - smoothingFactor) * this.averageSpeed;
819
+ this._prevUploadedSize = uploaded;
820
+ },
821
+
822
+ /**
823
+ * For internal usage only.
824
+ * Callback when something happens within the chunk.
825
+ * @function
826
+ * @param {FlowChunk} chunk
827
+ * @param {string} event can be 'progress', 'success', 'error' or 'retry'
828
+ * @param {string} [message]
829
+ */
830
+ chunkEvent: function (chunk, event, message) {
831
+ switch (event) {
832
+ case 'progress':
833
+ if (Date.now() - this._lastProgressCallback <
834
+ this.flowObj.opts.progressCallbacksInterval) {
835
+ break;
836
+ }
837
+ this.measureSpeed();
838
+ this.flowObj.fire('fileProgress', this, chunk);
839
+ this.flowObj.fire('progress');
840
+ this._lastProgressCallback = Date.now();
841
+ break;
842
+ case 'error':
843
+ this.error = true;
844
+ this.abort(true);
845
+ this.flowObj.fire('fileError', this, message, chunk);
846
+ this.flowObj.fire('error', message, this, chunk);
847
+ break;
848
+ case 'success':
849
+ if (this.error) {
850
+ return;
851
+ }
852
+ this.measureSpeed();
853
+ this.flowObj.fire('fileProgress', this, chunk);
854
+ this.flowObj.fire('progress');
855
+ this._lastProgressCallback = Date.now();
856
+ if (this.isComplete()) {
857
+ this.currentSpeed = 0;
858
+ this.averageSpeed = 0;
859
+ this.flowObj.fire('fileSuccess', this, message, chunk);
860
+ }
861
+ break;
862
+ case 'retry':
863
+ this.flowObj.fire('fileRetry', this, chunk);
864
+ break;
865
+ }
866
+ },
867
+
868
+ /**
869
+ * Pause file upload
870
+ * @function
871
+ */
872
+ pause: function() {
873
+ this.paused = true;
874
+ this.abort();
875
+ },
876
+
877
+ /**
878
+ * Resume file upload
879
+ * @function
880
+ */
881
+ resume: function() {
882
+ this.paused = false;
883
+ this.flowObj.upload();
884
+ },
885
+
886
+ /**
887
+ * Abort current upload
888
+ * @function
889
+ */
890
+ abort: function (reset) {
891
+ this.currentSpeed = 0;
892
+ this.averageSpeed = 0;
893
+ var chunks = this.chunks;
894
+ if (reset) {
895
+ this.chunks = [];
896
+ }
897
+ each(chunks, function (c) {
898
+ if (c.status() === 'uploading') {
899
+ c.abort();
900
+ this.flowObj.uploadNextChunk();
901
+ }
902
+ }, this);
903
+ },
904
+
905
+ /**
906
+ * Cancel current upload and remove from a list
907
+ * @function
908
+ */
909
+ cancel: function () {
910
+ this.flowObj.removeFile(this);
911
+ },
912
+
913
+ /**
914
+ * Retry aborted file upload
915
+ * @function
916
+ */
917
+ retry: function () {
918
+ this.bootstrap();
919
+ this.flowObj.upload();
920
+ },
921
+
922
+ /**
923
+ * Clear current chunks and slice file again
924
+ * @function
925
+ */
926
+ bootstrap: function () {
927
+ if (typeof this.flowObj.opts.initFileFn === "function") {
928
+ this.flowObj.opts.initFileFn(this);
929
+ }
930
+
931
+ this.abort(true);
932
+ this.error = false;
933
+ // Rebuild stack of chunks from file
934
+ this._prevProgress = 0;
935
+ var round = this.flowObj.opts.forceChunkSize ? Math.ceil : Math.floor;
936
+ var chunks = Math.max(
937
+ round(this.size / this.flowObj.opts.chunkSize), 1
938
+ );
939
+ for (var offset = 0; offset < chunks; offset++) {
940
+ this.chunks.push(
941
+ new FlowChunk(this.flowObj, this, offset)
942
+ );
943
+ }
944
+ },
945
+
946
+ /**
947
+ * Get current upload progress status
948
+ * @function
949
+ * @returns {number} from 0 to 1
950
+ */
951
+ progress: function () {
952
+ if (this.error) {
953
+ return 1;
954
+ }
955
+ if (this.chunks.length === 1) {
956
+ this._prevProgress = Math.max(this._prevProgress, this.chunks[0].progress());
957
+ return this._prevProgress;
958
+ }
959
+ // Sum up progress across everything
960
+ var bytesLoaded = 0;
961
+ each(this.chunks, function (c) {
962
+ // get chunk progress relative to entire file
963
+ bytesLoaded += c.progress() * (c.endByte - c.startByte);
964
+ });
965
+ var percent = bytesLoaded / this.size;
966
+ // We don't want to lose percentages when an upload is paused
967
+ this._prevProgress = Math.max(this._prevProgress, percent > 0.9999 ? 1 : percent);
968
+ return this._prevProgress;
969
+ },
970
+
971
+ /**
972
+ * Indicates if file is being uploaded at the moment
973
+ * @function
974
+ * @returns {boolean}
975
+ */
976
+ isUploading: function () {
977
+ var uploading = false;
978
+ each(this.chunks, function (chunk) {
979
+ if (chunk.status() === 'uploading') {
980
+ uploading = true;
981
+ return false;
982
+ }
983
+ });
984
+ return uploading;
985
+ },
986
+
987
+ /**
988
+ * Indicates if file is has finished uploading and received a response
989
+ * @function
990
+ * @returns {boolean}
991
+ */
992
+ isComplete: function () {
993
+ var outstanding = false;
994
+ each(this.chunks, function (chunk) {
995
+ var status = chunk.status();
996
+ if (status === 'pending' || status === 'uploading' || status === 'reading' || chunk.preprocessState === 1 || chunk.readState === 1) {
997
+ outstanding = true;
998
+ return false;
999
+ }
1000
+ });
1001
+ return !outstanding;
1002
+ },
1003
+
1004
+ /**
1005
+ * Count total size uploaded
1006
+ * @function
1007
+ * @returns {number}
1008
+ */
1009
+ sizeUploaded: function () {
1010
+ var size = 0;
1011
+ each(this.chunks, function (chunk) {
1012
+ size += chunk.sizeUploaded();
1013
+ });
1014
+ return size;
1015
+ },
1016
+
1017
+ /**
1018
+ * Returns remaining time to finish upload file in seconds. Accuracy is based on average speed.
1019
+ * If speed is zero, time remaining will be equal to positive infinity `Number.POSITIVE_INFINITY`
1020
+ * @function
1021
+ * @returns {number}
1022
+ */
1023
+ timeRemaining: function () {
1024
+ if (this.paused || this.error) {
1025
+ return 0;
1026
+ }
1027
+ var delta = this.size - this.sizeUploaded();
1028
+ if (delta && !this.averageSpeed) {
1029
+ return Number.POSITIVE_INFINITY;
1030
+ }
1031
+ if (!delta && !this.averageSpeed) {
1032
+ return 0;
1033
+ }
1034
+ return Math.floor(delta / this.averageSpeed);
1035
+ },
1036
+
1037
+ /**
1038
+ * Get file type
1039
+ * @function
1040
+ * @returns {string}
1041
+ */
1042
+ getType: function () {
1043
+ return this.file.type && this.file.type.split('/')[1];
1044
+ },
1045
+
1046
+ /**
1047
+ * Get file extension
1048
+ * @function
1049
+ * @returns {string}
1050
+ */
1051
+ getExtension: function () {
1052
+ return this.name.substr((~-this.name.lastIndexOf(".") >>> 0) + 2).toLowerCase();
1053
+ }
1054
+ };
1055
+
1056
+ /**
1057
+ * Default read function using the webAPI
1058
+ *
1059
+ * @function webAPIFileRead(fileObj, startByte, endByte, fileType, chunk)
1060
+ *
1061
+ */
1062
+ function webAPIFileRead(fileObj, startByte, endByte, fileType, chunk) {
1063
+ var function_name = 'slice';
1064
+
1065
+ if (fileObj.file.slice)
1066
+ function_name = 'slice';
1067
+ else if (fileObj.file.mozSlice)
1068
+ function_name = 'mozSlice';
1069
+ else if (fileObj.file.webkitSlice)
1070
+ function_name = 'webkitSlice';
1071
+
1072
+ chunk.readFinished(fileObj.file[function_name](startByte, endByte, fileType));
1073
+ }
1074
+
1075
+
1076
+ /**
1077
+ * Class for storing a single chunk
1078
+ * @name FlowChunk
1079
+ * @param {Flow} flowObj
1080
+ * @param {FlowFile} fileObj
1081
+ * @param {number} offset
1082
+ * @constructor
1083
+ */
1084
+ function FlowChunk(flowObj, fileObj, offset) {
1085
+
1086
+ /**
1087
+ * Reference to parent flow object
1088
+ * @type {Flow}
1089
+ */
1090
+ this.flowObj = flowObj;
1091
+
1092
+ /**
1093
+ * Reference to parent FlowFile object
1094
+ * @type {FlowFile}
1095
+ */
1096
+ this.fileObj = fileObj;
1097
+
1098
+ /**
1099
+ * File offset
1100
+ * @type {number}
1101
+ */
1102
+ this.offset = offset;
1103
+
1104
+ /**
1105
+ * Indicates if chunk existence was checked on the server
1106
+ * @type {boolean}
1107
+ */
1108
+ this.tested = false;
1109
+
1110
+ /**
1111
+ * Number of retries performed
1112
+ * @type {number}
1113
+ */
1114
+ this.retries = 0;
1115
+
1116
+ /**
1117
+ * Pending retry
1118
+ * @type {boolean}
1119
+ */
1120
+ this.pendingRetry = false;
1121
+
1122
+ /**
1123
+ * Preprocess state
1124
+ * @type {number} 0 = unprocessed, 1 = processing, 2 = finished
1125
+ */
1126
+ this.preprocessState = 0;
1127
+
1128
+ /**
1129
+ * Read state
1130
+ * @type {number} 0 = not read, 1 = reading, 2 = finished
1131
+ */
1132
+ this.readState = 0;
1133
+
1134
+
1135
+ /**
1136
+ * Bytes transferred from total request size
1137
+ * @type {number}
1138
+ */
1139
+ this.loaded = 0;
1140
+
1141
+ /**
1142
+ * Total request size
1143
+ * @type {number}
1144
+ */
1145
+ this.total = 0;
1146
+
1147
+ /**
1148
+ * Size of a chunk
1149
+ * @type {number}
1150
+ */
1151
+ this.chunkSize = this.flowObj.opts.chunkSize;
1152
+
1153
+ /**
1154
+ * Chunk start byte in a file
1155
+ * @type {number}
1156
+ */
1157
+ this.startByte = this.offset * this.chunkSize;
1158
+
1159
+ /**
1160
+ * Compute the endbyte in a file
1161
+ *
1162
+ */
1163
+ this.computeEndByte = function() {
1164
+ var endByte = Math.min(this.fileObj.size, (this.offset + 1) * this.chunkSize);
1165
+ if (this.fileObj.size - endByte < this.chunkSize && !this.flowObj.opts.forceChunkSize) {
1166
+ // The last chunk will be bigger than the chunk size,
1167
+ // but less than 2 * this.chunkSize
1168
+ endByte = this.fileObj.size;
1169
+ }
1170
+ return endByte;
1171
+ }
1172
+
1173
+ /**
1174
+ * Chunk end byte in a file
1175
+ * @type {number}
1176
+ */
1177
+ this.endByte = this.computeEndByte();
1178
+
1179
+ /**
1180
+ * XMLHttpRequest
1181
+ * @type {XMLHttpRequest}
1182
+ */
1183
+ this.xhr = null;
1184
+
1185
+ var $ = this;
1186
+
1187
+ /**
1188
+ * Send chunk event
1189
+ * @param event
1190
+ * @param {...} args arguments of a callback
1191
+ */
1192
+ this.event = function (event, args) {
1193
+ args = Array.prototype.slice.call(arguments);
1194
+ args.unshift($);
1195
+ $.fileObj.chunkEvent.apply($.fileObj, args);
1196
+ };
1197
+ /**
1198
+ * Catch progress event
1199
+ * @param {ProgressEvent} event
1200
+ */
1201
+ this.progressHandler = function(event) {
1202
+ if (event.lengthComputable) {
1203
+ $.loaded = event.loaded ;
1204
+ $.total = event.total;
1205
+ }
1206
+ $.event('progress', event);
1207
+ };
1208
+
1209
+ /**
1210
+ * Catch test event
1211
+ * @param {Event} event
1212
+ */
1213
+ this.testHandler = function(event) {
1214
+ var status = $.status(true);
1215
+ if (status === 'error') {
1216
+ $.event(status, $.message());
1217
+ $.flowObj.uploadNextChunk();
1218
+ } else if (status === 'success') {
1219
+ $.tested = true;
1220
+ $.event(status, $.message());
1221
+ $.flowObj.uploadNextChunk();
1222
+ } else if (!$.fileObj.paused) {
1223
+ // Error might be caused by file pause method
1224
+ // Chunks does not exist on the server side
1225
+ $.tested = true;
1226
+ $.send();
1227
+ }
1228
+ };
1229
+
1230
+ /**
1231
+ * Upload has stopped
1232
+ * @param {Event} event
1233
+ */
1234
+ this.doneHandler = function(event) {
1235
+ var status = $.status();
1236
+ if (status === 'success' || status === 'error') {
1237
+ delete this.data;
1238
+ $.event(status, $.message());
1239
+ $.flowObj.uploadNextChunk();
1240
+ } else {
1241
+ $.event('retry', $.message());
1242
+ $.pendingRetry = true;
1243
+ $.abort();
1244
+ $.retries++;
1245
+ var retryInterval = $.flowObj.opts.chunkRetryInterval;
1246
+ if (retryInterval !== null) {
1247
+ setTimeout(function () {
1248
+ $.send();
1249
+ }, retryInterval);
1250
+ } else {
1251
+ $.send();
1252
+ }
1253
+ }
1254
+ };
1255
+ }
1256
+
1257
+ FlowChunk.prototype = {
1258
+ /**
1259
+ * Get params for a request
1260
+ * @function
1261
+ */
1262
+ getParams: function () {
1263
+ return {
1264
+ flowChunkNumber: this.offset + 1,
1265
+ flowChunkSize: this.flowObj.opts.chunkSize,
1266
+ flowCurrentChunkSize: this.endByte - this.startByte,
1267
+ flowTotalSize: this.fileObj.size,
1268
+ flowIdentifier: this.fileObj.uniqueIdentifier,
1269
+ flowFilename: this.fileObj.name,
1270
+ flowRelativePath: this.fileObj.relativePath,
1271
+ flowTotalChunks: this.fileObj.chunks.length
1272
+ };
1273
+ },
1274
+
1275
+ /**
1276
+ * Get target option with query params
1277
+ * @function
1278
+ * @param params
1279
+ * @returns {string}
1280
+ */
1281
+ getTarget: function(target, params){
1282
+ if(target.indexOf('?') < 0) {
1283
+ target += '?';
1284
+ } else {
1285
+ target += '&';
1286
+ }
1287
+ return target + params.join('&');
1288
+ },
1289
+
1290
+ /**
1291
+ * Makes a GET request without any data to see if the chunk has already
1292
+ * been uploaded in a previous session
1293
+ * @function
1294
+ */
1295
+ test: function () {
1296
+ // Set up request and listen for event
1297
+ this.xhr = new XMLHttpRequest();
1298
+ this.xhr.addEventListener("load", this.testHandler, false);
1299
+ this.xhr.addEventListener("error", this.testHandler, false);
1300
+ var testMethod = evalOpts(this.flowObj.opts.testMethod, this.fileObj, this);
1301
+ var data = this.prepareXhrRequest(testMethod, true);
1302
+ this.xhr.send(data);
1303
+ },
1304
+
1305
+ /**
1306
+ * Finish preprocess state
1307
+ * @function
1308
+ */
1309
+ preprocessFinished: function () {
1310
+ // Re-compute the endByte after the preprocess function to allow an
1311
+ // implementer of preprocess to set the fileObj size
1312
+ this.endByte = this.computeEndByte();
1313
+
1314
+ this.preprocessState = 2;
1315
+ this.send();
1316
+ },
1317
+
1318
+ /**
1319
+ * Finish read state
1320
+ * @function
1321
+ */
1322
+ readFinished: function (bytes) {
1323
+ this.readState = 2;
1324
+ this.bytes = bytes;
1325
+ this.send();
1326
+ },
1327
+
1328
+
1329
+ /**
1330
+ * Uploads the actual data in a POST call
1331
+ * @function
1332
+ */
1333
+ send: function () {
1334
+ var preprocess = this.flowObj.opts.preprocess;
1335
+ var read = this.flowObj.opts.readFileFn;
1336
+ if (typeof preprocess === 'function') {
1337
+ switch (this.preprocessState) {
1338
+ case 0:
1339
+ this.preprocessState = 1;
1340
+ preprocess(this);
1341
+ return;
1342
+ case 1:
1343
+ return;
1344
+ }
1345
+ }
1346
+ switch (this.readState) {
1347
+ case 0:
1348
+ this.readState = 1;
1349
+ read(this.fileObj, this.startByte, this.endByte, this.fileObj.file.type, this);
1350
+ return;
1351
+ case 1:
1352
+ return;
1353
+ }
1354
+ if (this.flowObj.opts.testChunks && !this.tested) {
1355
+ this.test();
1356
+ return;
1357
+ }
1358
+
1359
+ this.loaded = 0;
1360
+ this.total = 0;
1361
+ this.pendingRetry = false;
1362
+
1363
+ // Set up request and listen for event
1364
+ this.xhr = new XMLHttpRequest();
1365
+ this.xhr.upload.addEventListener('progress', this.progressHandler, false);
1366
+ this.xhr.addEventListener("load", this.doneHandler, false);
1367
+ this.xhr.addEventListener("error", this.doneHandler, false);
1368
+
1369
+ var uploadMethod = evalOpts(this.flowObj.opts.uploadMethod, this.fileObj, this);
1370
+ var data = this.prepareXhrRequest(uploadMethod, false, this.flowObj.opts.method, this.bytes);
1371
+ this.xhr.send(data);
1372
+ },
1373
+
1374
+ /**
1375
+ * Abort current xhr request
1376
+ * @function
1377
+ */
1378
+ abort: function () {
1379
+ // Abort and reset
1380
+ var xhr = this.xhr;
1381
+ this.xhr = null;
1382
+ if (xhr) {
1383
+ xhr.abort();
1384
+ }
1385
+ },
1386
+
1387
+ /**
1388
+ * Retrieve current chunk upload status
1389
+ * @function
1390
+ * @returns {string} 'pending', 'uploading', 'success', 'error'
1391
+ */
1392
+ status: function (isTest) {
1393
+ if (this.readState === 1) {
1394
+ return 'reading';
1395
+ } else if (this.pendingRetry || this.preprocessState === 1) {
1396
+ // if pending retry then that's effectively the same as actively uploading,
1397
+ // there might just be a slight delay before the retry starts
1398
+ return 'uploading';
1399
+ } else if (!this.xhr) {
1400
+ return 'pending';
1401
+ } else if (this.xhr.readyState < 4) {
1402
+ // Status is really 'OPENED', 'HEADERS_RECEIVED'
1403
+ // or 'LOADING' - meaning that stuff is happening
1404
+ return 'uploading';
1405
+ } else {
1406
+ if (this.flowObj.opts.successStatuses.indexOf(this.xhr.status) > -1) {
1407
+ // HTTP 200, perfect
1408
+ // HTTP 202 Accepted - The request has been accepted for processing, but the processing has not been completed.
1409
+ return 'success';
1410
+ } else if (this.flowObj.opts.permanentErrors.indexOf(this.xhr.status) > -1 ||
1411
+ !isTest && this.retries >= this.flowObj.opts.maxChunkRetries) {
1412
+ // HTTP 413/415/500/501, permanent error
1413
+ return 'error';
1414
+ } else {
1415
+ // this should never happen, but we'll reset and queue a retry
1416
+ // a likely case for this would be 503 service unavailable
1417
+ this.abort();
1418
+ return 'pending';
1419
+ }
1420
+ }
1421
+ },
1422
+
1423
+ /**
1424
+ * Get response from xhr request
1425
+ * @function
1426
+ * @returns {String}
1427
+ */
1428
+ message: function () {
1429
+ return this.xhr ? this.xhr.responseText : '';
1430
+ },
1431
+
1432
+ /**
1433
+ * Get upload progress
1434
+ * @function
1435
+ * @returns {number}
1436
+ */
1437
+ progress: function () {
1438
+ if (this.pendingRetry) {
1439
+ return 0;
1440
+ }
1441
+ var s = this.status();
1442
+ if (s === 'success' || s === 'error') {
1443
+ return 1;
1444
+ } else if (s === 'pending') {
1445
+ return 0;
1446
+ } else {
1447
+ return this.total > 0 ? this.loaded / this.total : 0;
1448
+ }
1449
+ },
1450
+
1451
+ /**
1452
+ * Count total size uploaded
1453
+ * @function
1454
+ * @returns {number}
1455
+ */
1456
+ sizeUploaded: function () {
1457
+ var size = this.endByte - this.startByte;
1458
+ // can't return only chunk.loaded value, because it is bigger than chunk size
1459
+ if (this.status() !== 'success') {
1460
+ size = this.progress() * size;
1461
+ }
1462
+ return size;
1463
+ },
1464
+
1465
+ /**
1466
+ * Prepare Xhr request. Set query, headers and data
1467
+ * @param {string} method GET or POST
1468
+ * @param {bool} isTest is this a test request
1469
+ * @param {string} [paramsMethod] octet or form
1470
+ * @param {Blob} [blob] to send
1471
+ * @returns {FormData|Blob|Null} data to send
1472
+ */
1473
+ prepareXhrRequest: function(method, isTest, paramsMethod, blob) {
1474
+ // Add data from the query options
1475
+ var query = evalOpts(this.flowObj.opts.query, this.fileObj, this, isTest);
1476
+ query = extend(query, this.getParams());
1477
+
1478
+ var target = evalOpts(this.flowObj.opts.target, this.fileObj, this, isTest);
1479
+ var data = null;
1480
+ if (method === 'GET' || paramsMethod === 'octet') {
1481
+ // Add data from the query options
1482
+ var params = [];
1483
+ each(query, function (v, k) {
1484
+ params.push([encodeURIComponent(k), encodeURIComponent(v)].join('='));
1485
+ });
1486
+ target = this.getTarget(target, params);
1487
+ data = blob || null;
1488
+ } else {
1489
+ // Add data from the query options
1490
+ data = new FormData();
1491
+ each(query, function (v, k) {
1492
+ data.append(k, v);
1493
+ });
1494
+ if (typeof blob !== "undefined") data.append(this.flowObj.opts.fileParameterName, blob, this.fileObj.file.name);
1495
+ }
1496
+
1497
+ this.xhr.open(method, target, true);
1498
+ this.xhr.withCredentials = this.flowObj.opts.withCredentials;
1499
+
1500
+ // Add data from header options
1501
+ each(evalOpts(this.flowObj.opts.headers, this.fileObj, this, isTest), function (v, k) {
1502
+ this.xhr.setRequestHeader(k, v);
1503
+ }, this);
1504
+
1505
+ return data;
1506
+ }
1507
+ };
1508
+
1509
+ /**
1510
+ * Remove value from array
1511
+ * @param array
1512
+ * @param value
1513
+ */
1514
+ function arrayRemove(array, value) {
1515
+ var index = array.indexOf(value);
1516
+ if (index > -1) {
1517
+ array.splice(index, 1);
1518
+ }
1519
+ }
1520
+
1521
+ /**
1522
+ * If option is a function, evaluate it with given params
1523
+ * @param {*} data
1524
+ * @param {...} args arguments of a callback
1525
+ * @returns {*}
1526
+ */
1527
+ function evalOpts(data, args) {
1528
+ if (typeof data === "function") {
1529
+ // `arguments` is an object, not array, in FF, so:
1530
+ args = Array.prototype.slice.call(arguments);
1531
+ data = data.apply(null, args.slice(1));
1532
+ }
1533
+ return data;
1534
+ }
1535
+ Flow.evalOpts = evalOpts;
1536
+
1537
+ /**
1538
+ * Execute function asynchronously
1539
+ * @param fn
1540
+ * @param context
1541
+ */
1542
+ function async(fn, context) {
1543
+ setTimeout(fn.bind(context), 0);
1544
+ }
1545
+
1546
+ /**
1547
+ * Extends the destination object `dst` by copying all of the properties from
1548
+ * the `src` object(s) to `dst`. You can specify multiple `src` objects.
1549
+ * @function
1550
+ * @param {Object} dst Destination object.
1551
+ * @param {...Object} src Source object(s).
1552
+ * @returns {Object} Reference to `dst`.
1553
+ */
1554
+ function extend(dst, src) {
1555
+ each(arguments, function(obj) {
1556
+ if (obj !== dst) {
1557
+ each(obj, function(value, key){
1558
+ dst[key] = value;
1559
+ });
1560
+ }
1561
+ });
1562
+ return dst;
1563
+ }
1564
+ Flow.extend = extend;
1565
+
1566
+ /**
1567
+ * Iterate each element of an object
1568
+ * @function
1569
+ * @param {Array|Object} obj object or an array to iterate
1570
+ * @param {Function} callback first argument is a value and second is a key.
1571
+ * @param {Object=} context Object to become context (`this`) for the iterator function.
1572
+ */
1573
+ function each(obj, callback, context) {
1574
+ if (!obj) {
1575
+ return ;
1576
+ }
1577
+ var key;
1578
+ // Is Array?
1579
+ // Array.isArray won't work, not only arrays can be iterated by index https://github.com/flowjs/ng-flow/issues/236#
1580
+ if (typeof(obj.length) !== 'undefined') {
1581
+ for (key = 0; key < obj.length; key++) {
1582
+ if (callback.call(context, obj[key], key) === false) {
1583
+ return ;
1584
+ }
1585
+ }
1586
+ } else {
1587
+ for (key in obj) {
1588
+ if (obj.hasOwnProperty(key) && callback.call(context, obj[key], key) === false) {
1589
+ return ;
1590
+ }
1591
+ }
1592
+ }
1593
+ }
1594
+ Flow.each = each;
1595
+
1596
+ /**
1597
+ * FlowFile constructor
1598
+ * @type {FlowFile}
1599
+ */
1600
+ Flow.FlowFile = FlowFile;
1601
+
1602
+ /**
1603
+ * FlowFile constructor
1604
+ * @type {FlowChunk}
1605
+ */
1606
+ Flow.FlowChunk = FlowChunk;
1607
+
1608
+ /**
1609
+ * Library version
1610
+ * @type {string}
1611
+ */
1612
+ Flow.version = '2.13.0';
1613
+
1614
+ if ( typeof module === "object" && module && typeof module.exports === "object" ) {
1615
+ // Expose Flow as module.exports in loaders that implement the Node
1616
+ // module pattern (including browserify). Do not create the global, since
1617
+ // the user will be storing it themselves locally, and globals are frowned
1618
+ // upon in the Node module world.
1619
+ module.exports = Flow;
1620
+ } else {
1621
+ // Otherwise expose Flow to the global object as usual
1622
+ window.Flow = Flow;
1623
+
1624
+ // Register as a named AMD module, since Flow can be concatenated with other
1625
+ // files that may use define, but not via a proper concatenation script that
1626
+ // understands anonymous AMD modules. A named AMD is safest and most robust
1627
+ // way to register. Lowercase flow is used because AMD module names are
1628
+ // derived from file names, and Flow is normally delivered in a lowercase
1629
+ // file name. Do this after creating the global so that if an AMD module wants
1630
+ // to call noConflict to hide this version of Flow, it will work.
1631
+ if ( typeof define === "function" && define.amd ) {
1632
+ define( "flow", [], function () { return Flow; } );
1633
+ }
1634
+ }
1635
+ })(window, document);