kms 0.9.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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);