uploader.js 44 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610
  1. /*!
  2. * Uploader - Uploader library implements html5 file upload and provides multiple simultaneous, stable, fault tolerant and resumable uploads
  3. * @version v0.5.6
  4. * @author dolymood <dolymood@gmail.com>
  5. * @link https://github.com/simple-uploader/Uploader
  6. * @license MIT
  7. */
  8. !function(e){if("object"==typeof exports)module.exports=e();else if("function"==typeof define&&define.amd)define(e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.Uploader=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){
  9. var utils = _dereq_('./utils')
  10. function Chunk (uploader, file, offset) {
  11. utils.defineNonEnumerable(this, 'uploader', uploader)
  12. utils.defineNonEnumerable(this, 'file', file)
  13. utils.defineNonEnumerable(this, 'bytes', null)
  14. this.offset = offset
  15. this.tested = false
  16. this.retries = 0
  17. this.pendingRetry = false
  18. this.preprocessState = 0
  19. this.readState = 0
  20. this.loaded = 0
  21. this.total = 0
  22. this.chunkSize = this.uploader.opts.chunkSize
  23. this.startByte = this.offset * this.chunkSize
  24. this.endByte = this.computeEndByte()
  25. this.xhr = null
  26. }
  27. var STATUS = Chunk.STATUS = {
  28. PENDING: 'pending',
  29. UPLOADING: 'uploading',
  30. READING: 'reading',
  31. SUCCESS: 'success',
  32. ERROR: 'error',
  33. COMPLETE: 'complete',
  34. PROGRESS: 'progress',
  35. RETRY: 'retry'
  36. }
  37. utils.extend(Chunk.prototype, {
  38. _event: function (evt, args) {
  39. args = utils.toArray(arguments)
  40. args.unshift(this)
  41. this.file._chunkEvent.apply(this.file, args)
  42. },
  43. computeEndByte: function () {
  44. var endByte = Math.min(this.file.size, (this.offset + 1) * this.chunkSize)
  45. if (this.file.size - endByte < this.chunkSize && !this.uploader.opts.forceChunkSize) {
  46. // The last chunk will be bigger than the chunk size,
  47. // but less than 2 * this.chunkSize
  48. endByte = this.file.size
  49. }
  50. return endByte
  51. },
  52. getParams: function () {
  53. return {
  54. chunkNumber: this.offset + 1,
  55. chunkSize: this.uploader.opts.chunkSize,
  56. currentChunkSize: this.endByte - this.startByte,
  57. totalSize: this.file.size,
  58. identifier: this.file.uniqueIdentifier,
  59. filename: this.file.name,
  60. relativePath: this.file.relativePath,
  61. totalChunks: this.file.chunks.length
  62. }
  63. },
  64. getTarget: function (target, params) {
  65. if (!params.length) {
  66. return target
  67. }
  68. if (target.indexOf('?') < 0) {
  69. target += '?'
  70. } else {
  71. target += '&'
  72. }
  73. return target + params.join('&')
  74. },
  75. test: function () {
  76. this.xhr = new XMLHttpRequest()
  77. this.xhr.addEventListener('load', testHandler, false)
  78. this.xhr.addEventListener('error', testHandler, false)
  79. var testMethod = utils.evalOpts(this.uploader.opts.testMethod, this.file, this)
  80. var data = this.prepareXhrRequest(testMethod, true)
  81. this.xhr.send(data)
  82. var $ = this
  83. function testHandler (event) {
  84. var status = $.status(true)
  85. if (status === STATUS.ERROR) {
  86. $._event(status, $.message())
  87. $.uploader.uploadNextChunk()
  88. } else if (status === STATUS.SUCCESS) {
  89. $._event(status, $.message())
  90. $.tested = true
  91. } else if (!$.file.paused) {
  92. // Error might be caused by file pause method
  93. // Chunks does not exist on the server side
  94. $.tested = true
  95. $.send()
  96. }
  97. }
  98. },
  99. preprocessFinished: function () {
  100. // Compute the endByte after the preprocess function to allow an
  101. // implementer of preprocess to set the fileObj size
  102. this.endByte = this.computeEndByte()
  103. this.preprocessState = 2
  104. this.send()
  105. },
  106. readFinished: function (bytes) {
  107. this.readState = 2
  108. this.bytes = bytes
  109. this.send()
  110. },
  111. send: function () {
  112. var preprocess = this.uploader.opts.preprocess
  113. var read = this.uploader.opts.readFileFn
  114. if (utils.isFunction(preprocess)) {
  115. switch (this.preprocessState) {
  116. case 0:
  117. this.preprocessState = 1
  118. preprocess(this)
  119. return
  120. case 1:
  121. return
  122. }
  123. }
  124. switch (this.readState) {
  125. case 0:
  126. this.readState = 1
  127. read(this.file, this.file.fileType, this.startByte, this.endByte, this)
  128. return
  129. case 1:
  130. return
  131. }
  132. if (this.uploader.opts.testChunks && !this.tested) {
  133. this.test()
  134. return
  135. }
  136. this.loaded = 0
  137. this.total = 0
  138. this.pendingRetry = false
  139. // Set up request and listen for event
  140. this.xhr = new XMLHttpRequest()
  141. this.xhr.upload.addEventListener('progress', progressHandler, false)
  142. this.xhr.addEventListener('load', doneHandler, false)
  143. this.xhr.addEventListener('error', doneHandler, false)
  144. var uploadMethod = utils.evalOpts(this.uploader.opts.uploadMethod, this.file, this)
  145. var data = this.prepareXhrRequest(uploadMethod, false, this.uploader.opts.method, this.bytes)
  146. this.xhr.send(data)
  147. var $ = this
  148. function progressHandler (event) {
  149. if (event.lengthComputable) {
  150. $.loaded = event.loaded
  151. $.total = event.total
  152. }
  153. $._event(STATUS.PROGRESS, event)
  154. }
  155. function doneHandler (event) {
  156. var msg = $.message()
  157. $.processingResponse = true
  158. $.uploader.opts.processResponse(msg, function (err, res) {
  159. $.processingResponse = false
  160. if (!$.xhr) {
  161. return
  162. }
  163. $.processedState = {
  164. err: err,
  165. res: res
  166. }
  167. var status = $.status()
  168. if (status === STATUS.SUCCESS || status === STATUS.ERROR) {
  169. // delete this.data
  170. $._event(status, res)
  171. status === STATUS.ERROR && $.uploader.uploadNextChunk()
  172. } else {
  173. $._event(STATUS.RETRY, res)
  174. $.pendingRetry = true
  175. $.abort()
  176. $.retries++
  177. var retryInterval = $.uploader.opts.chunkRetryInterval
  178. if (retryInterval !== null) {
  179. setTimeout(function () {
  180. $.send()
  181. }, retryInterval)
  182. } else {
  183. $.send()
  184. }
  185. }
  186. }, $.file, $)
  187. }
  188. },
  189. abort: function () {
  190. var xhr = this.xhr
  191. this.xhr = null
  192. this.processingResponse = false
  193. this.processedState = null
  194. if (xhr) {
  195. xhr.abort()
  196. }
  197. },
  198. status: function (isTest) {
  199. if (this.readState === 1) {
  200. return STATUS.READING
  201. } else if (this.pendingRetry || this.preprocessState === 1) {
  202. // if pending retry then that's effectively the same as actively uploading,
  203. // there might just be a slight delay before the retry starts
  204. return STATUS.UPLOADING
  205. } else if (!this.xhr) {
  206. return STATUS.PENDING
  207. } else if (this.xhr.readyState < 4 || this.processingResponse) {
  208. // Status is really 'OPENED', 'HEADERS_RECEIVED'
  209. // or 'LOADING' - meaning that stuff is happening
  210. return STATUS.UPLOADING
  211. } else {
  212. var _status
  213. if (this.uploader.opts.successStatuses.indexOf(this.xhr.status) > -1) {
  214. // HTTP 200, perfect
  215. // HTTP 202 Accepted - The request has been accepted for processing, but the processing has not been completed.
  216. _status = STATUS.SUCCESS
  217. } else if (this.uploader.opts.permanentErrors.indexOf(this.xhr.status) > -1 ||
  218. !isTest && this.retries >= this.uploader.opts.maxChunkRetries) {
  219. // HTTP 415/500/501, permanent error
  220. _status = STATUS.ERROR
  221. } else {
  222. // this should never happen, but we'll reset and queue a retry
  223. // a likely case for this would be 503 service unavailable
  224. this.abort()
  225. _status = STATUS.PENDING
  226. }
  227. var processedState = this.processedState
  228. if (processedState && processedState.err) {
  229. _status = STATUS.ERROR
  230. }
  231. return _status
  232. }
  233. },
  234. message: function () {
  235. return this.xhr ? this.xhr.responseText : ''
  236. },
  237. progress: function () {
  238. if (this.pendingRetry) {
  239. return 0
  240. }
  241. var s = this.status()
  242. if (s === STATUS.SUCCESS || s === STATUS.ERROR) {
  243. return 1
  244. } else if (s === STATUS.PENDING) {
  245. return 0
  246. } else {
  247. return this.total > 0 ? this.loaded / this.total : 0
  248. }
  249. },
  250. sizeUploaded: function () {
  251. var size = this.endByte - this.startByte
  252. // can't return only chunk.loaded value, because it is bigger than chunk size
  253. if (this.status() !== STATUS.SUCCESS) {
  254. size = this.progress() * size
  255. }
  256. return size
  257. },
  258. prepareXhrRequest: function (method, isTest, paramsMethod, blob) {
  259. // Add data from the query options
  260. var query = utils.evalOpts(this.uploader.opts.query, this.file, this, isTest)
  261. query = utils.extend(this.getParams(), query)
  262. // processParams
  263. query = this.uploader.opts.processParams(query, this.file, this, isTest)
  264. var target = utils.evalOpts(this.uploader.opts.target, this.file, this, isTest)
  265. var data = null
  266. if (method === 'GET' || paramsMethod === 'octet') {
  267. // Add data from the query options
  268. var params = []
  269. utils.each(query, function (v, k) {
  270. params.push([encodeURIComponent(k), encodeURIComponent(v)].join('='))
  271. })
  272. target = this.getTarget(target, params)
  273. data = blob || null
  274. } else {
  275. // Add data from the query options
  276. data = new FormData()
  277. utils.each(query, function (v, k) {
  278. data.append(k, v)
  279. })
  280. if (typeof blob !== 'undefined') {
  281. data.append(this.uploader.opts.fileParameterName, blob, this.file.name)
  282. }
  283. }
  284. this.xhr.open(method, target, true)
  285. this.xhr.withCredentials = this.uploader.opts.withCredentials
  286. // Add data from header options
  287. utils.each(utils.evalOpts(this.uploader.opts.headers, this.file, this, isTest), function (v, k) {
  288. this.xhr.setRequestHeader(k, v)
  289. }, this)
  290. return data
  291. }
  292. })
  293. module.exports = Chunk
  294. },{"./utils":5}],2:[function(_dereq_,module,exports){
  295. var each = _dereq_('./utils').each
  296. var event = {
  297. _eventData: null,
  298. on: function (name, func) {
  299. if (!this._eventData) this._eventData = {}
  300. if (!this._eventData[name]) this._eventData[name] = []
  301. var listened = false
  302. each(this._eventData[name], function (fuc) {
  303. if (fuc === func) {
  304. listened = true
  305. return false
  306. }
  307. })
  308. if (!listened) {
  309. this._eventData[name].push(func)
  310. }
  311. },
  312. off: function (name, func) {
  313. if (!this._eventData) this._eventData = {}
  314. if (!this._eventData[name] || !this._eventData[name].length) return
  315. if (func) {
  316. each(this._eventData[name], function (fuc, i) {
  317. if (fuc === func) {
  318. this._eventData[name].splice(i, 1)
  319. return false
  320. }
  321. }, this)
  322. } else {
  323. this._eventData[name] = []
  324. }
  325. },
  326. trigger: function (name) {
  327. if (!this._eventData) this._eventData = {}
  328. if (!this._eventData[name]) return true
  329. var args = this._eventData[name].slice.call(arguments, 1)
  330. var preventDefault = false
  331. each(this._eventData[name], function (fuc) {
  332. preventDefault = fuc.apply(this, args) === false || preventDefault
  333. }, this)
  334. return !preventDefault
  335. }
  336. }
  337. module.exports = event
  338. },{"./utils":5}],3:[function(_dereq_,module,exports){
  339. var utils = _dereq_('./utils')
  340. var event = _dereq_('./event')
  341. var File = _dereq_('./file')
  342. var Chunk = _dereq_('./chunk')
  343. var version = '0.5.6'
  344. var isServer = typeof window === 'undefined'
  345. // ie10+
  346. var ie10plus = isServer ? false : window.navigator.msPointerEnabled
  347. var support = (function () {
  348. if (isServer) {
  349. return false
  350. }
  351. var sliceName = 'slice'
  352. var _support = utils.isDefined(window.File) && utils.isDefined(window.Blob) &&
  353. utils.isDefined(window.FileList)
  354. var bproto = null
  355. if (_support) {
  356. bproto = window.Blob.prototype
  357. utils.each(['slice', 'webkitSlice', 'mozSlice'], function (n) {
  358. if (bproto[n]) {
  359. sliceName = n
  360. return false
  361. }
  362. })
  363. _support = !!bproto[sliceName]
  364. }
  365. if (_support) Uploader.sliceName = sliceName
  366. bproto = null
  367. return _support
  368. })()
  369. var supportDirectory = (function () {
  370. if (isServer) {
  371. return false
  372. }
  373. var input = window.document.createElement('input')
  374. input.type = 'file'
  375. var sd = 'webkitdirectory' in input || 'directory' in input
  376. input = null
  377. return sd
  378. })()
  379. function Uploader (opts) {
  380. this.support = support
  381. /* istanbul ignore if */
  382. if (!this.support) {
  383. return
  384. }
  385. this.supportDirectory = supportDirectory
  386. utils.defineNonEnumerable(this, 'filePaths', {})
  387. this.opts = utils.extend({}, Uploader.defaults, opts || {})
  388. this.preventEvent = utils.bind(this._preventEvent, this)
  389. File.call(this, this)
  390. }
  391. /**
  392. * Default read function using the webAPI
  393. *
  394. * @function webAPIFileRead(fileObj, fileType, startByte, endByte, chunk)
  395. *
  396. */
  397. var webAPIFileRead = function (fileObj, fileType, startByte, endByte, chunk) {
  398. chunk.readFinished(fileObj.file[Uploader.sliceName](startByte, endByte, fileType))
  399. }
  400. Uploader.version = version
  401. Uploader.defaults = {
  402. chunkSize: 1024 * 1024,
  403. forceChunkSize: false,
  404. simultaneousUploads: 3,
  405. singleFile: false,
  406. fileParameterName: 'file',
  407. progressCallbacksInterval: 500,
  408. speedSmoothingFactor: 0.1,
  409. query: {},
  410. headers: {},
  411. withCredentials: false,
  412. preprocess: null,
  413. method: 'multipart',
  414. testMethod: 'GET',
  415. uploadMethod: 'POST',
  416. prioritizeFirstAndLastChunk: false,
  417. allowDuplicateUploads: false,
  418. target: '/',
  419. testChunks: true,
  420. generateUniqueIdentifier: null,
  421. maxChunkRetries: 0,
  422. chunkRetryInterval: null,
  423. permanentErrors: [404, 415, 500, 501],
  424. successStatuses: [200, 201, 202],
  425. onDropStopPropagation: false,
  426. initFileFn: null,
  427. readFileFn: webAPIFileRead,
  428. checkChunkUploadedByResponse: null,
  429. initialPaused: false,
  430. processResponse: function (response, cb) {
  431. cb(null, response)
  432. },
  433. processParams: function (params) {
  434. return params
  435. }
  436. }
  437. Uploader.utils = utils
  438. Uploader.event = event
  439. Uploader.File = File
  440. Uploader.Chunk = Chunk
  441. // inherit file
  442. Uploader.prototype = utils.extend({}, File.prototype)
  443. // inherit event
  444. utils.extend(Uploader.prototype, event)
  445. utils.extend(Uploader.prototype, {
  446. constructor: Uploader,
  447. _trigger: function (name) {
  448. var args = utils.toArray(arguments)
  449. var preventDefault = !this.trigger.apply(this, arguments)
  450. if (name !== 'catchAll') {
  451. args.unshift('catchAll')
  452. preventDefault = !this.trigger.apply(this, args) || preventDefault
  453. }
  454. return !preventDefault
  455. },
  456. _triggerAsync: function () {
  457. var args = arguments
  458. utils.nextTick(function () {
  459. this._trigger.apply(this, args)
  460. }, this)
  461. },
  462. addFiles: function (files, evt) {
  463. var _files = []
  464. var oldFileListLen = this.fileList.length
  465. utils.each(files, function (file) {
  466. // Uploading empty file IE10/IE11 hangs indefinitely
  467. // Directories have size `0` and name `.`
  468. // Ignore already added files if opts.allowDuplicateUploads is set to false
  469. if ((!ie10plus || ie10plus && file.size > 0) && !(file.size % 4096 === 0 && (file.name === '.' || file.fileName === '.'))) {
  470. var uniqueIdentifier = this.generateUniqueIdentifier(file)
  471. if (this.opts.allowDuplicateUploads || !this.getFromUniqueIdentifier(uniqueIdentifier)) {
  472. var _file = new File(this, file, this)
  473. _file.uniqueIdentifier = uniqueIdentifier
  474. if (this._trigger('fileAdded', _file, evt)) {
  475. _files.push(_file)
  476. } else {
  477. File.prototype.removeFile.call(this, _file)
  478. }
  479. }
  480. }
  481. }, this)
  482. // get new fileList
  483. var newFileList = this.fileList.slice(oldFileListLen)
  484. if (this._trigger('filesAdded', _files, newFileList, evt)) {
  485. utils.each(_files, function (file) {
  486. if (this.opts.singleFile && this.files.length > 0) {
  487. this.removeFile(this.files[0])
  488. }
  489. this.files.push(file)
  490. }, this)
  491. this._trigger('filesSubmitted', _files, newFileList, evt)
  492. } else {
  493. utils.each(newFileList, function (file) {
  494. File.prototype.removeFile.call(this, file)
  495. }, this)
  496. }
  497. },
  498. addFile: function (file, evt) {
  499. this.addFiles([file], evt)
  500. },
  501. cancel: function () {
  502. for (var i = this.fileList.length - 1; i >= 0; i--) {
  503. this.fileList[i].cancel()
  504. }
  505. },
  506. removeFile: function (file) {
  507. File.prototype.removeFile.call(this, file)
  508. this._trigger('fileRemoved', file)
  509. },
  510. generateUniqueIdentifier: function (file) {
  511. var custom = this.opts.generateUniqueIdentifier
  512. if (utils.isFunction(custom)) {
  513. return custom(file)
  514. }
  515. /* istanbul ignore next */
  516. // Some confusion in different versions of Firefox
  517. var relativePath = file.relativePath || file.webkitRelativePath || file.fileName || file.name
  518. /* istanbul ignore next */
  519. return file.size + '-' + relativePath.replace(/[^0-9a-zA-Z_-]/img, '')
  520. },
  521. getFromUniqueIdentifier: function (uniqueIdentifier) {
  522. var ret = false
  523. utils.each(this.files, function (file) {
  524. if (file.uniqueIdentifier === uniqueIdentifier) {
  525. ret = file
  526. return false
  527. }
  528. })
  529. return ret
  530. },
  531. uploadNextChunk: function (preventEvents) {
  532. var found = false
  533. var pendingStatus = Chunk.STATUS.PENDING
  534. var checkChunkUploaded = this.uploader.opts.checkChunkUploadedByResponse
  535. if (this.opts.prioritizeFirstAndLastChunk) {
  536. utils.each(this.files, function (file) {
  537. if (file.paused) {
  538. return
  539. }
  540. if (checkChunkUploaded && !file._firstResponse && file.isUploading()) {
  541. // waiting for current file's first chunk response
  542. return
  543. }
  544. if (file.chunks.length && file.chunks[0].status() === pendingStatus) {
  545. file.chunks[0].send()
  546. found = true
  547. return false
  548. }
  549. if (file.chunks.length > 1 && file.chunks[file.chunks.length - 1].status() === pendingStatus) {
  550. file.chunks[file.chunks.length - 1].send()
  551. found = true
  552. return false
  553. }
  554. })
  555. if (found) {
  556. return found
  557. }
  558. }
  559. // Now, simply look for the next, best thing to upload
  560. utils.each(this.files, function (file) {
  561. if (!file.paused) {
  562. if (checkChunkUploaded && !file._firstResponse && file.isUploading()) {
  563. // waiting for current file's first chunk response
  564. return
  565. }
  566. utils.each(file.chunks, function (chunk) {
  567. if (chunk.status() === pendingStatus) {
  568. chunk.send()
  569. found = true
  570. return false
  571. }
  572. })
  573. }
  574. if (found) {
  575. return false
  576. }
  577. })
  578. if (found) {
  579. return true
  580. }
  581. // The are no more outstanding chunks to upload, check is everything is done
  582. var outstanding = false
  583. utils.each(this.files, function (file) {
  584. if (!file.isComplete()) {
  585. outstanding = true
  586. return false
  587. }
  588. })
  589. // should check files now
  590. // if now files in list
  591. // should not trigger complete event
  592. if (!outstanding && !preventEvents && this.files.length) {
  593. // All chunks have been uploaded, complete
  594. this._triggerAsync('complete')
  595. }
  596. return outstanding
  597. },
  598. upload: function (preventEvents) {
  599. // Make sure we don't start too many uploads at once
  600. var ret = this._shouldUploadNext()
  601. if (ret === false) {
  602. return
  603. }
  604. !preventEvents && this._trigger('uploadStart')
  605. var started = false
  606. for (var num = 1; num <= this.opts.simultaneousUploads - ret; num++) {
  607. started = this.uploadNextChunk(!preventEvents) || started
  608. if (!started && preventEvents) {
  609. // completed
  610. break
  611. }
  612. }
  613. if (!started && !preventEvents) {
  614. this._triggerAsync('complete')
  615. }
  616. },
  617. /**
  618. * should upload next chunk
  619. * @function
  620. * @returns {Boolean|Number}
  621. */
  622. _shouldUploadNext: function () {
  623. var num = 0
  624. var should = true
  625. var simultaneousUploads = this.opts.simultaneousUploads
  626. var uploadingStatus = Chunk.STATUS.UPLOADING
  627. utils.each(this.files, function (file) {
  628. utils.each(file.chunks, function (chunk) {
  629. if (chunk.status() === uploadingStatus) {
  630. num++
  631. if (num >= simultaneousUploads) {
  632. should = false
  633. return false
  634. }
  635. }
  636. })
  637. return should
  638. })
  639. // if should is true then return uploading chunks's length
  640. return should && num
  641. },
  642. /**
  643. * Assign a browse action to one or more DOM nodes.
  644. * @function
  645. * @param {Element|Array.<Element>} domNodes
  646. * @param {boolean} isDirectory Pass in true to allow directories to
  647. * @param {boolean} singleFile prevent multi file upload
  648. * @param {Object} attributes set custom attributes:
  649. * http://www.w3.org/TR/html-markup/input.file.html#input.file-attributes
  650. * eg: accept: 'image/*'
  651. * be selected (Chrome only).
  652. */
  653. assignBrowse: function (domNodes, isDirectory, singleFile, attributes) {
  654. if (typeof domNodes.length === 'undefined') {
  655. domNodes = [domNodes]
  656. }
  657. utils.each(domNodes, function (domNode) {
  658. var input
  659. if (domNode.tagName === 'INPUT' && domNode.type === 'file') {
  660. input = domNode
  661. } else {
  662. input = document.createElement('input')
  663. input.setAttribute('type', 'file')
  664. // display:none - not working in opera 12
  665. utils.extend(input.style, {
  666. visibility: 'hidden',
  667. position: 'absolute',
  668. width: '1px',
  669. height: '1px'
  670. })
  671. // for opera 12 browser, input must be assigned to a document
  672. domNode.appendChild(input)
  673. // https://developer.mozilla.org/en/using_files_from_web_applications)
  674. // event listener is executed two times
  675. // first one - original mouse click event
  676. // second - input.click(), input is inside domNode
  677. domNode.addEventListener('click', function (e) {
  678. if (domNode.tagName.toLowerCase() === 'label') {
  679. return
  680. }
  681. input.click()
  682. }, false)
  683. }
  684. if (!this.opts.singleFile && !singleFile) {
  685. input.setAttribute('multiple', 'multiple')
  686. }
  687. if (isDirectory) {
  688. input.setAttribute('webkitdirectory', 'webkitdirectory')
  689. }
  690. attributes && utils.each(attributes, function (value, key) {
  691. input.setAttribute(key, value)
  692. })
  693. // When new files are added, simply append them to the overall list
  694. var that = this
  695. input.addEventListener('change', function (e) {
  696. that._trigger(e.type, e)
  697. if (e.target.value) {
  698. that.addFiles(e.target.files, e)
  699. e.target.value = ''
  700. }
  701. }, false)
  702. }, this)
  703. },
  704. onDrop: function (evt) {
  705. this._trigger(evt.type, evt)
  706. if (this.opts.onDropStopPropagation) {
  707. evt.stopPropagation()
  708. }
  709. evt.preventDefault()
  710. this._parseDataTransfer(evt.dataTransfer, evt)
  711. },
  712. _parseDataTransfer: function (dataTransfer, evt) {
  713. if (dataTransfer.items && dataTransfer.items[0] &&
  714. dataTransfer.items[0].webkitGetAsEntry) {
  715. this.webkitReadDataTransfer(dataTransfer, evt)
  716. } else {
  717. this.addFiles(dataTransfer.files, evt)
  718. }
  719. },
  720. webkitReadDataTransfer: function (dataTransfer, evt) {
  721. var self = this
  722. var queue = dataTransfer.items.length
  723. var files = []
  724. utils.each(dataTransfer.items, function (item) {
  725. var entry = item.webkitGetAsEntry()
  726. if (!entry) {
  727. decrement()
  728. return
  729. }
  730. if (entry.isFile) {
  731. // due to a bug in Chrome's File System API impl - #149735
  732. fileReadSuccess(item.getAsFile(), entry.fullPath)
  733. } else {
  734. readDirectory(entry.createReader())
  735. }
  736. })
  737. function readDirectory (reader) {
  738. reader.readEntries(function (entries) {
  739. if (entries.length) {
  740. queue += entries.length
  741. utils.each(entries, function (entry) {
  742. if (entry.isFile) {
  743. var fullPath = entry.fullPath
  744. entry.file(function (file) {
  745. fileReadSuccess(file, fullPath)
  746. }, readError)
  747. } else if (entry.isDirectory) {
  748. readDirectory(entry.createReader())
  749. }
  750. })
  751. readDirectory(reader)
  752. } else {
  753. decrement()
  754. }
  755. }, readError)
  756. }
  757. function fileReadSuccess (file, fullPath) {
  758. // relative path should not start with "/"
  759. file.relativePath = fullPath.substring(1)
  760. files.push(file)
  761. decrement()
  762. }
  763. function readError (fileError) {
  764. throw fileError
  765. }
  766. function decrement () {
  767. if (--queue === 0) {
  768. self.addFiles(files, evt)
  769. }
  770. }
  771. },
  772. _assignHelper: function (domNodes, handles, remove) {
  773. if (typeof domNodes.length === 'undefined') {
  774. domNodes = [domNodes]
  775. }
  776. var evtMethod = remove ? 'removeEventListener' : 'addEventListener'
  777. utils.each(domNodes, function (domNode) {
  778. utils.each(handles, function (handler, name) {
  779. domNode[evtMethod](name, handler, false)
  780. }, this)
  781. }, this)
  782. },
  783. _preventEvent: function (e) {
  784. utils.preventEvent(e)
  785. this._trigger(e.type, e)
  786. },
  787. /**
  788. * Assign one or more DOM nodes as a drop target.
  789. * @function
  790. * @param {Element|Array.<Element>} domNodes
  791. */
  792. assignDrop: function (domNodes) {
  793. this._onDrop = utils.bind(this.onDrop, this)
  794. this._assignHelper(domNodes, {
  795. dragover: this.preventEvent,
  796. dragenter: this.preventEvent,
  797. dragleave: this.preventEvent,
  798. drop: this._onDrop
  799. })
  800. },
  801. /**
  802. * Un-assign drop event from DOM nodes
  803. * @function
  804. * @param domNodes
  805. */
  806. unAssignDrop: function (domNodes) {
  807. this._assignHelper(domNodes, {
  808. dragover: this.preventEvent,
  809. dragenter: this.preventEvent,
  810. dragleave: this.preventEvent,
  811. drop: this._onDrop
  812. }, true)
  813. this._onDrop = null
  814. }
  815. })
  816. module.exports = Uploader
  817. },{"./chunk":1,"./event":2,"./file":4,"./utils":5}],4:[function(_dereq_,module,exports){
  818. var utils = _dereq_('./utils')
  819. var Chunk = _dereq_('./chunk')
  820. function File (uploader, file, parent) {
  821. utils.defineNonEnumerable(this, 'uploader', uploader)
  822. this.isRoot = this.isFolder = uploader === this
  823. utils.defineNonEnumerable(this, 'parent', parent || null)
  824. utils.defineNonEnumerable(this, 'files', [])
  825. utils.defineNonEnumerable(this, 'fileList', [])
  826. utils.defineNonEnumerable(this, 'chunks', [])
  827. utils.defineNonEnumerable(this, '_errorFiles', [])
  828. utils.defineNonEnumerable(this, 'file', null)
  829. this.id = utils.uid()
  830. if (this.isRoot || !file) {
  831. this.file = null
  832. } else {
  833. if (utils.isString(file)) {
  834. // folder
  835. this.isFolder = true
  836. this.file = null
  837. this.path = file
  838. if (this.parent.path) {
  839. file = file.substr(this.parent.path.length)
  840. }
  841. this.name = file.charAt(file.length - 1) === '/' ? file.substr(0, file.length - 1) : file
  842. } else {
  843. this.file = file
  844. this.fileType = this.file.type
  845. this.name = file.fileName || file.name
  846. this.size = file.size
  847. this.relativePath = file.relativePath || file.webkitRelativePath || this.name
  848. this._parseFile()
  849. }
  850. }
  851. this.paused = uploader.opts.initialPaused
  852. this.error = false
  853. this.allError = false
  854. this.aborted = false
  855. this.completed = false
  856. this.averageSpeed = 0
  857. this.currentSpeed = 0
  858. this._lastProgressCallback = Date.now()
  859. this._prevUploadedSize = 0
  860. this._prevProgress = 0
  861. this.bootstrap()
  862. }
  863. utils.extend(File.prototype, {
  864. _parseFile: function () {
  865. var ppaths = parsePaths(this.relativePath)
  866. if (ppaths.length) {
  867. var filePaths = this.uploader.filePaths
  868. utils.each(ppaths, function (path, i) {
  869. var folderFile = filePaths[path]
  870. if (!folderFile) {
  871. folderFile = new File(this.uploader, path, this.parent)
  872. filePaths[path] = folderFile
  873. this._updateParentFileList(folderFile)
  874. }
  875. this.parent = folderFile
  876. folderFile.files.push(this)
  877. if (!ppaths[i + 1]) {
  878. folderFile.fileList.push(this)
  879. }
  880. }, this)
  881. } else {
  882. this._updateParentFileList()
  883. }
  884. },
  885. _updateParentFileList: function (file) {
  886. if (!file) {
  887. file = this
  888. }
  889. var p = this.parent
  890. if (p) {
  891. p.fileList.push(file)
  892. }
  893. },
  894. _eachAccess: function (eachFn, fileFn) {
  895. if (this.isFolder) {
  896. utils.each(this.files, function (f, i) {
  897. return eachFn.call(this, f, i)
  898. }, this)
  899. return
  900. }
  901. fileFn.call(this, this)
  902. },
  903. bootstrap: function () {
  904. if (this.isFolder) return
  905. var opts = this.uploader.opts
  906. if (utils.isFunction(opts.initFileFn)) {
  907. opts.initFileFn.call(this, this)
  908. }
  909. this.abort(true)
  910. this._resetError()
  911. // Rebuild stack of chunks from file
  912. this._prevProgress = 0
  913. var round = opts.forceChunkSize ? Math.ceil : Math.floor
  914. var chunks = Math.max(round(this.size / opts.chunkSize), 1)
  915. for (var offset = 0; offset < chunks; offset++) {
  916. this.chunks.push(new Chunk(this.uploader, this, offset))
  917. }
  918. },
  919. _measureSpeed: function () {
  920. var smoothingFactor = this.uploader.opts.speedSmoothingFactor
  921. var timeSpan = Date.now() - this._lastProgressCallback
  922. if (!timeSpan) {
  923. return
  924. }
  925. var uploaded = this.sizeUploaded()
  926. // Prevent negative upload speed after file upload resume
  927. this.currentSpeed = Math.max((uploaded - this._prevUploadedSize) / timeSpan * 1000, 0)
  928. this.averageSpeed = smoothingFactor * this.currentSpeed + (1 - smoothingFactor) * this.averageSpeed
  929. this._prevUploadedSize = uploaded
  930. if (this.parent && this.parent._checkProgress()) {
  931. this.parent._measureSpeed()
  932. }
  933. },
  934. _checkProgress: function (file) {
  935. return Date.now() - this._lastProgressCallback >= this.uploader.opts.progressCallbacksInterval
  936. },
  937. _chunkEvent: function (chunk, evt, message) {
  938. var uploader = this.uploader
  939. var STATUS = Chunk.STATUS
  940. var that = this
  941. var rootFile = this.getRoot()
  942. var triggerProgress = function () {
  943. that._measureSpeed()
  944. uploader._trigger('fileProgress', rootFile, that, chunk)
  945. that._lastProgressCallback = Date.now()
  946. }
  947. switch (evt) {
  948. case STATUS.PROGRESS:
  949. if (this._checkProgress()) {
  950. triggerProgress()
  951. }
  952. break
  953. case STATUS.ERROR:
  954. this._error()
  955. this.abort(true)
  956. uploader._trigger('fileError', rootFile, this, message, chunk)
  957. break
  958. case STATUS.SUCCESS:
  959. this._updateUploadedChunks(message, chunk)
  960. if (this.error) {
  961. return
  962. }
  963. clearTimeout(this._progeressId)
  964. this._progeressId = 0
  965. var timeDiff = Date.now() - this._lastProgressCallback
  966. if (timeDiff < uploader.opts.progressCallbacksInterval) {
  967. this._progeressId = setTimeout(triggerProgress, uploader.opts.progressCallbacksInterval - timeDiff)
  968. }
  969. if (this.isComplete()) {
  970. clearTimeout(this._progeressId)
  971. triggerProgress()
  972. this.currentSpeed = 0
  973. this.averageSpeed = 0
  974. uploader._trigger('fileSuccess', rootFile, this, message, chunk)
  975. if (rootFile.isComplete()) {
  976. uploader._trigger('fileComplete', rootFile, this)
  977. }
  978. } else if (!this._progeressId) {
  979. triggerProgress()
  980. }
  981. break
  982. case STATUS.RETRY:
  983. uploader._trigger('fileRetry', rootFile, this, chunk)
  984. break
  985. }
  986. },
  987. _updateUploadedChunks: function (message, chunk) {
  988. var checkChunkUploaded = this.uploader.opts.checkChunkUploadedByResponse
  989. if (checkChunkUploaded) {
  990. var xhr = chunk.xhr
  991. utils.each(this.chunks, function (_chunk) {
  992. if (!_chunk.tested) {
  993. var uploaded = checkChunkUploaded.call(this, _chunk, message)
  994. if (_chunk === chunk && !uploaded) {
  995. // fix the first chunk xhr status
  996. // treated as success but checkChunkUploaded is false
  997. // so the current chunk should be uploaded again
  998. _chunk.xhr = null
  999. }
  1000. if (uploaded) {
  1001. // first success and other chunks are uploaded
  1002. // then set xhr, so the uploaded chunks
  1003. // will be treated as success too
  1004. _chunk.xhr = xhr
  1005. }
  1006. _chunk.tested = true
  1007. }
  1008. }, this)
  1009. if (!this._firstResponse) {
  1010. this._firstResponse = true
  1011. this.uploader.upload(true)
  1012. } else {
  1013. this.uploader.uploadNextChunk()
  1014. }
  1015. } else {
  1016. this.uploader.uploadNextChunk()
  1017. }
  1018. },
  1019. _error: function () {
  1020. this.error = this.allError = true
  1021. var parent = this.parent
  1022. while (parent && parent !== this.uploader) {
  1023. parent._errorFiles.push(this)
  1024. parent.error = true
  1025. if (parent._errorFiles.length === parent.files.length) {
  1026. parent.allError = true
  1027. }
  1028. parent = parent.parent
  1029. }
  1030. },
  1031. _resetError: function () {
  1032. this.error = this.allError = false
  1033. var parent = this.parent
  1034. var index = -1
  1035. while (parent && parent !== this.uploader) {
  1036. index = parent._errorFiles.indexOf(this)
  1037. parent._errorFiles.splice(index, 1)
  1038. parent.allError = false
  1039. if (!parent._errorFiles.length) {
  1040. parent.error = false
  1041. }
  1042. parent = parent.parent
  1043. }
  1044. },
  1045. isComplete: function () {
  1046. if (!this.completed) {
  1047. var outstanding = false
  1048. this._eachAccess(function (file) {
  1049. if (!file.isComplete()) {
  1050. outstanding = true
  1051. return false
  1052. }
  1053. }, function () {
  1054. if (this.error) {
  1055. outstanding = true
  1056. } else {
  1057. var STATUS = Chunk.STATUS
  1058. utils.each(this.chunks, function (chunk) {
  1059. var status = chunk.status()
  1060. if (status === STATUS.ERROR || status === STATUS.PENDING || status === STATUS.UPLOADING || status === STATUS.READING || chunk.preprocessState === 1 || chunk.readState === 1) {
  1061. outstanding = true
  1062. return false
  1063. }
  1064. })
  1065. }
  1066. })
  1067. this.completed = !outstanding
  1068. }
  1069. return this.completed
  1070. },
  1071. isUploading: function () {
  1072. var uploading = false
  1073. this._eachAccess(function (file) {
  1074. if (file.isUploading()) {
  1075. uploading = true
  1076. return false
  1077. }
  1078. }, function () {
  1079. var uploadingStatus = Chunk.STATUS.UPLOADING
  1080. utils.each(this.chunks, function (chunk) {
  1081. if (chunk.status() === uploadingStatus) {
  1082. uploading = true
  1083. return false
  1084. }
  1085. })
  1086. })
  1087. return uploading
  1088. },
  1089. resume: function () {
  1090. this._eachAccess(function (f) {
  1091. f.resume()
  1092. }, function () {
  1093. this.paused = false
  1094. this.aborted = false
  1095. this.uploader.upload()
  1096. })
  1097. this.paused = false
  1098. this.aborted = false
  1099. },
  1100. pause: function () {
  1101. this._eachAccess(function (f) {
  1102. f.pause()
  1103. }, function () {
  1104. this.paused = true
  1105. this.abort()
  1106. })
  1107. this.paused = true
  1108. },
  1109. cancel: function () {
  1110. this.uploader.removeFile(this)
  1111. },
  1112. retry: function (file) {
  1113. var fileRetry = function (file) {
  1114. if (file.error) {
  1115. file.bootstrap()
  1116. }
  1117. }
  1118. if (file) {
  1119. file.bootstrap()
  1120. } else {
  1121. this._eachAccess(fileRetry, function () {
  1122. this.bootstrap()
  1123. })
  1124. }
  1125. this.uploader.upload()
  1126. },
  1127. abort: function (reset) {
  1128. if (this.aborted) {
  1129. return
  1130. }
  1131. this.currentSpeed = 0
  1132. this.averageSpeed = 0
  1133. this.aborted = !reset
  1134. var chunks = this.chunks
  1135. if (reset) {
  1136. this.chunks = []
  1137. }
  1138. var uploadingStatus = Chunk.STATUS.UPLOADING
  1139. utils.each(chunks, function (c) {
  1140. if (c.status() === uploadingStatus) {
  1141. c.abort()
  1142. this.uploader.uploadNextChunk()
  1143. }
  1144. }, this)
  1145. },
  1146. progress: function () {
  1147. var totalDone = 0
  1148. var totalSize = 0
  1149. var ret = 0
  1150. this._eachAccess(function (file, index) {
  1151. totalDone += file.progress() * file.size
  1152. totalSize += file.size
  1153. if (index === this.files.length - 1) {
  1154. ret = totalSize > 0 ? totalDone / totalSize : this.isComplete() ? 1 : 0
  1155. }
  1156. }, function () {
  1157. if (this.error) {
  1158. ret = 1
  1159. return
  1160. }
  1161. if (this.chunks.length === 1) {
  1162. this._prevProgress = Math.max(this._prevProgress, this.chunks[0].progress())
  1163. ret = this._prevProgress
  1164. return
  1165. }
  1166. // Sum up progress across everything
  1167. var bytesLoaded = 0
  1168. utils.each(this.chunks, function (c) {
  1169. // get chunk progress relative to entire file
  1170. bytesLoaded += c.progress() * (c.endByte - c.startByte)
  1171. })
  1172. var percent = bytesLoaded / this.size
  1173. // We don't want to lose percentages when an upload is paused
  1174. this._prevProgress = Math.max(this._prevProgress, percent > 0.9999 ? 1 : percent)
  1175. ret = this._prevProgress
  1176. })
  1177. return ret
  1178. },
  1179. getSize: function () {
  1180. var size = 0
  1181. this._eachAccess(function (file) {
  1182. size += file.size
  1183. }, function () {
  1184. size += this.size
  1185. })
  1186. return size
  1187. },
  1188. getFormatSize: function () {
  1189. var size = this.getSize()
  1190. return utils.formatSize(size)
  1191. },
  1192. getRoot: function () {
  1193. if (this.isRoot) {
  1194. return this
  1195. }
  1196. var parent = this.parent
  1197. while (parent) {
  1198. if (parent.parent === this.uploader) {
  1199. // find it
  1200. return parent
  1201. }
  1202. parent = parent.parent
  1203. }
  1204. return this
  1205. },
  1206. sizeUploaded: function () {
  1207. var size = 0
  1208. this._eachAccess(function (file) {
  1209. size += file.sizeUploaded()
  1210. }, function () {
  1211. utils.each(this.chunks, function (chunk) {
  1212. size += chunk.sizeUploaded()
  1213. })
  1214. })
  1215. return size
  1216. },
  1217. timeRemaining: function () {
  1218. var ret = 0
  1219. var sizeDelta = 0
  1220. var averageSpeed = 0
  1221. this._eachAccess(function (file, i) {
  1222. if (!file.paused && !file.error) {
  1223. sizeDelta += file.size - file.sizeUploaded()
  1224. averageSpeed += file.averageSpeed
  1225. }
  1226. if (i === this.files.length - 1) {
  1227. ret = calRet(sizeDelta, averageSpeed)
  1228. }
  1229. }, function () {
  1230. if (this.paused || this.error) {
  1231. ret = 0
  1232. return
  1233. }
  1234. var delta = this.size - this.sizeUploaded()
  1235. ret = calRet(delta, this.averageSpeed)
  1236. })
  1237. return ret
  1238. function calRet (delta, averageSpeed) {
  1239. if (delta && !averageSpeed) {
  1240. return Number.POSITIVE_INFINITY
  1241. }
  1242. if (!delta && !averageSpeed) {
  1243. return 0
  1244. }
  1245. return Math.floor(delta / averageSpeed)
  1246. }
  1247. },
  1248. removeFile: function (file) {
  1249. if (file.isFolder) {
  1250. while (file.files.length) {
  1251. var f = file.files[file.files.length - 1]
  1252. this._removeFile(f)
  1253. }
  1254. }
  1255. this._removeFile(file)
  1256. },
  1257. _delFilePath: function (file) {
  1258. if (file.path && this.filePaths) {
  1259. delete this.filePaths[file.path]
  1260. }
  1261. utils.each(file.fileList, function (file) {
  1262. this._delFilePath(file)
  1263. }, this)
  1264. },
  1265. _removeFile: function (file) {
  1266. if (!file.isFolder) {
  1267. utils.each(this.files, function (f, i) {
  1268. if (f === file) {
  1269. this.files.splice(i, 1)
  1270. return false
  1271. }
  1272. }, this)
  1273. file.abort()
  1274. var parent = file.parent
  1275. var newParent
  1276. while (parent && parent !== this) {
  1277. newParent = parent.parent
  1278. parent._removeFile(file)
  1279. parent = newParent
  1280. }
  1281. }
  1282. file.parent === this && utils.each(this.fileList, function (f, i) {
  1283. if (f === file) {
  1284. this.fileList.splice(i, 1)
  1285. return false
  1286. }
  1287. }, this)
  1288. if (!this.isRoot && this.isFolder && !this.files.length) {
  1289. this.parent._removeFile(this)
  1290. this.uploader._delFilePath(this)
  1291. }
  1292. file.parent = null
  1293. },
  1294. getType: function () {
  1295. if (this.isFolder) {
  1296. return 'folder'
  1297. }
  1298. return this.file.type && this.file.type.split('/')[1]
  1299. },
  1300. getExtension: function () {
  1301. if (this.isFolder) {
  1302. return ''
  1303. }
  1304. return this.name.substr((~-this.name.lastIndexOf('.') >>> 0) + 2).toLowerCase()
  1305. }
  1306. })
  1307. module.exports = File
  1308. function parsePaths (path) {
  1309. var ret = []
  1310. var paths = path.split('/')
  1311. var len = paths.length
  1312. var i = 1
  1313. paths.splice(len - 1, 1)
  1314. len--
  1315. if (paths.length) {
  1316. while (i <= len) {
  1317. ret.push(paths.slice(0, i++).join('/') + '/')
  1318. }
  1319. }
  1320. return ret
  1321. }
  1322. },{"./chunk":1,"./utils":5}],5:[function(_dereq_,module,exports){
  1323. var oproto = Object.prototype
  1324. var aproto = Array.prototype
  1325. var serialize = oproto.toString
  1326. var isFunction = function (fn) {
  1327. return serialize.call(fn) === '[object Function]'
  1328. }
  1329. var isArray = Array.isArray || /* istanbul ignore next */ function (ary) {
  1330. return serialize.call(ary) === '[object Array]'
  1331. }
  1332. var isPlainObject = function (obj) {
  1333. return serialize.call(obj) === '[object Object]' && Object.getPrototypeOf(obj) === oproto
  1334. }
  1335. var i = 0
  1336. var utils = {
  1337. uid: function () {
  1338. return ++i
  1339. },
  1340. noop: function () {},
  1341. bind: function (fn, context) {
  1342. return function () {
  1343. return fn.apply(context, arguments)
  1344. }
  1345. },
  1346. preventEvent: function (evt) {
  1347. evt.preventDefault()
  1348. },
  1349. stop: function (evt) {
  1350. evt.preventDefault()
  1351. evt.stopPropagation()
  1352. },
  1353. nextTick: function (fn, context) {
  1354. setTimeout(utils.bind(fn, context), 0)
  1355. },
  1356. toArray: function (ary, start, end) {
  1357. if (start === undefined) start = 0
  1358. if (end === undefined) end = ary.length
  1359. return aproto.slice.call(ary, start, end)
  1360. },
  1361. isPlainObject: isPlainObject,
  1362. isFunction: isFunction,
  1363. isArray: isArray,
  1364. isObject: function (obj) {
  1365. return Object(obj) === obj
  1366. },
  1367. isString: function (s) {
  1368. return typeof s === 'string'
  1369. },
  1370. isUndefined: function (a) {
  1371. return typeof a === 'undefined'
  1372. },
  1373. isDefined: function (a) {
  1374. return typeof a !== 'undefined'
  1375. },
  1376. each: function (ary, func, context) {
  1377. if (utils.isDefined(ary.length)) {
  1378. for (var i = 0, len = ary.length; i < len; i++) {
  1379. if (func.call(context, ary[i], i, ary) === false) {
  1380. break
  1381. }
  1382. }
  1383. } else {
  1384. for (var k in ary) {
  1385. if (func.call(context, ary[k], k, ary) === false) {
  1386. break
  1387. }
  1388. }
  1389. }
  1390. },
  1391. /**
  1392. * If option is a function, evaluate it with given params
  1393. * @param {*} data
  1394. * @param {...} args arguments of a callback
  1395. * @returns {*}
  1396. */
  1397. evalOpts: function (data, args) {
  1398. if (utils.isFunction(data)) {
  1399. // `arguments` is an object, not array, in FF, so:
  1400. args = utils.toArray(arguments)
  1401. data = data.apply(null, args.slice(1))
  1402. }
  1403. return data
  1404. },
  1405. extend: function () {
  1406. var options
  1407. var name
  1408. var src
  1409. var copy
  1410. var copyIsArray
  1411. var clone
  1412. var target = arguments[0] || {}
  1413. var i = 1
  1414. var length = arguments.length
  1415. var force = false
  1416. // 如果第一个参数为布尔,判定是否深拷贝
  1417. if (typeof target === 'boolean') {
  1418. force = target
  1419. target = arguments[1] || {}
  1420. i++
  1421. }
  1422. // 确保接受方为一个复杂的数据类型
  1423. if (typeof target !== 'object' && !isFunction(target)) {
  1424. target = {}
  1425. }
  1426. // 如果只有一个参数,那么新成员添加于 extend 所在的对象上
  1427. if (i === length) {
  1428. target = this
  1429. i--
  1430. }
  1431. for (; i < length; i++) {
  1432. // 只处理非空参数
  1433. if ((options = arguments[i]) != null) {
  1434. for (name in options) {
  1435. src = target[name]
  1436. copy = options[name]
  1437. // 防止环引用
  1438. if (target === copy) {
  1439. continue
  1440. }
  1441. if (force && copy && (isPlainObject(copy) || (copyIsArray = isArray(copy)))) {
  1442. if (copyIsArray) {
  1443. copyIsArray = false
  1444. clone = src && isArray(src) ? src : []
  1445. } else {
  1446. clone = src && isPlainObject(src) ? src : {}
  1447. }
  1448. target[name] = utils.extend(force, clone, copy)
  1449. } else if (copy !== undefined) {
  1450. target[name] = copy
  1451. }
  1452. }
  1453. }
  1454. }
  1455. return target
  1456. },
  1457. formatSize: function (size) {
  1458. if (size < 1024) {
  1459. return size.toFixed(0) + ' bytes'
  1460. } else if (size < 1024 * 1024) {
  1461. return (size / 1024.0).toFixed(0) + ' KB'
  1462. } else if (size < 1024 * 1024 * 1024) {
  1463. return (size / 1024.0 / 1024.0).toFixed(1) + ' MB'
  1464. } else {
  1465. return (size / 1024.0 / 1024.0 / 1024.0).toFixed(1) + ' GB'
  1466. }
  1467. },
  1468. defineNonEnumerable: function (target, key, value) {
  1469. Object.defineProperty(target, key, {
  1470. enumerable: false,
  1471. configurable: true,
  1472. writable: true,
  1473. value: value
  1474. })
  1475. }
  1476. }
  1477. module.exports = utils
  1478. },{}]},{},[3])
  1479. (3)
  1480. });