From e299054025e999c76f5f0a0dd8e284453366a653 Mon Sep 17 00:00:00 2001 From: phachon Date: Mon, 19 Nov 2018 18:59:57 +0800 Subject: [PATCH 01/14] upload attachment --- app/controllers/attachment.go | 15 + docs/databases/table.sql | 18 +- router.go | 1 + static/js/modules/page.js | 19 +- static/plugins/webuploader/README.md | 25 + static/plugins/webuploader/Uploader.swf | Bin 0 -> 143099 bytes static/plugins/webuploader/bg.png | Bin 0 -> 2851 bytes static/plugins/webuploader/icons.png | Bin 0 -> 2678 bytes static/plugins/webuploader/image.png | Bin 0 -> 1672 bytes static/plugins/webuploader/progress.png | Bin 0 -> 1269 bytes static/plugins/webuploader/success.png | Bin 0 -> 1621 bytes static/plugins/webuploader/upload.js | 569 ++ static/plugins/webuploader/upload.style.css | 14 + static/plugins/webuploader/webuploader.css | 28 + .../plugins/webuploader/webuploader.custom.js | 6502 +++++++++++++ .../webuploader/webuploader.custom.min.js | 2 + static/plugins/webuploader/webuploader.fis.js | 8083 ++++++++++++++++ .../webuploader/webuploader.flashonly.js | 4622 ++++++++++ .../webuploader/webuploader.flashonly.min.js | 2 + .../webuploader/webuploader.html5only.js | 6030 ++++++++++++ .../webuploader/webuploader.html5only.min.js | 2 + static/plugins/webuploader/webuploader.js | 8106 +++++++++++++++++ static/plugins/webuploader/webuploader.min.js | 3 + .../webuploader/webuploader.noimage.js | 5026 ++++++++++ .../webuploader/webuploader.noimage.min.js | 2 + .../plugins/webuploader/webuploader.nolog.js | 8012 ++++++++++++++++ .../webuploader/webuploader.nolog.min.js | 3 + .../webuploader/webuploader.withoutimage.js | 4993 ++++++++++ .../webuploader.withoutimage.min.js | 2 + views/attachment/page.html | 42 + views/layouts/attachment.html | 40 + views/page/edit.html | 9 +- 32 files changed, 52165 insertions(+), 5 deletions(-) create mode 100644 app/controllers/attachment.go create mode 100644 static/plugins/webuploader/README.md create mode 100644 static/plugins/webuploader/Uploader.swf create mode 100644 static/plugins/webuploader/bg.png create mode 100644 static/plugins/webuploader/icons.png create mode 100644 static/plugins/webuploader/image.png create mode 100644 static/plugins/webuploader/progress.png create mode 100644 static/plugins/webuploader/success.png create mode 100644 static/plugins/webuploader/upload.js create mode 100644 static/plugins/webuploader/upload.style.css create mode 100644 static/plugins/webuploader/webuploader.css create mode 100644 static/plugins/webuploader/webuploader.custom.js create mode 100644 static/plugins/webuploader/webuploader.custom.min.js create mode 100644 static/plugins/webuploader/webuploader.fis.js create mode 100644 static/plugins/webuploader/webuploader.flashonly.js create mode 100644 static/plugins/webuploader/webuploader.flashonly.min.js create mode 100644 static/plugins/webuploader/webuploader.html5only.js create mode 100644 static/plugins/webuploader/webuploader.html5only.min.js create mode 100644 static/plugins/webuploader/webuploader.js create mode 100644 static/plugins/webuploader/webuploader.min.js create mode 100644 static/plugins/webuploader/webuploader.noimage.js create mode 100644 static/plugins/webuploader/webuploader.noimage.min.js create mode 100644 static/plugins/webuploader/webuploader.nolog.js create mode 100644 static/plugins/webuploader/webuploader.nolog.min.js create mode 100644 static/plugins/webuploader/webuploader.withoutimage.js create mode 100644 static/plugins/webuploader/webuploader.withoutimage.min.js create mode 100644 views/attachment/page.html create mode 100644 views/layouts/attachment.html diff --git a/app/controllers/attachment.go b/app/controllers/attachment.go new file mode 100644 index 00000000..d2e44d5b --- /dev/null +++ b/app/controllers/attachment.go @@ -0,0 +1,15 @@ +package controllers + + +type AttachmentController struct { + BaseController +} + + +func (this *AttachmentController) Page() { + this.viewLayout("attachment/page", "attachment") +} + +func (this *AttachmentController) Upload() { + +} \ No newline at end of file diff --git a/docs/databases/table.sql b/docs/databases/table.sql index 04ffbe74..5b867310 100644 --- a/docs/databases/table.sql +++ b/docs/databases/table.sql @@ -280,4 +280,20 @@ CREATE TABLE `mw_contact` ( `create_time` int(11) NOT NULL DEFAULT '0' COMMENT '创建时间', `update_time` int(11) NOT NULL DEFAULT '0' COMMENT '更新时间', PRIMARY KEY (`contact_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='联系人表'; \ No newline at end of file +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='联系人表'; + +-- -------------------------------- +-- 附件表 +-- -------------------------------- +DROP TABLE IF EXISTS `mw_attachment`; +CREATE TABLE `mw_attachment` ( + `attachment_id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '附件 id', + `user_id` int(10) NOT NULL DEFAULT '0' COMMENT '创建用户id', + `document_id` int(10) NOT NULL DEFAULT '0' COMMENT '所属文档id', + `name` varchar(50) NOT NULL DEFAULT '' COMMENT '附件名称', + `url` varchar(100) NOT NULL DEFAULT '' COMMENT '附件地址或路径', + `create_time` int(11) NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`attachment_id`), + KEY (`document_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='附件表'; \ No newline at end of file diff --git a/router.go b/router.go index 1e46e22c..d3eb5711 100644 --- a/router.go +++ b/router.go @@ -29,6 +29,7 @@ func initRouter() { beego.AutoRouter(&controllers.DocumentController{}) beego.AutoRouter(&controllers.PageController{}) beego.AutoRouter(&controllers.ImageController{}) + beego.AutoRouter(&controllers.AttachmentController{}) systemNamespace := beego.NewNamespace("/system", beego.NSAutoRouter(&systemControllers.MainController{}), diff --git a/static/js/modules/page.js b/static/js/modules/page.js index 238aca62..90022c74 100644 --- a/static/js/modules/page.js +++ b/static/js/modules/page.js @@ -150,6 +150,23 @@ var Page = { }, function() { }); - } + }, + /** + * upload attachment + */ + attachment: function () { + layer.open({ + type: 2, + skin: Layers.skin, + title: '上传附件', + shadeClose: true, + shade : 0.1, + resize: false, + maxmin: false, + area: ["600px", "300px"], + content: "/attachment/page", + padding:"10px" + }); + } }; \ No newline at end of file diff --git a/static/plugins/webuploader/README.md b/static/plugins/webuploader/README.md new file mode 100644 index 00000000..93bceadc --- /dev/null +++ b/static/plugins/webuploader/README.md @@ -0,0 +1,25 @@ +目录说明 +======================== + +```bash +├── Uploader.swf # SWF文件,当使用Flash运行时需要引入。 +├ +├── webuploader.js # 完全版本。 +├── webuploader.min.js # min版本 +├ +├── webuploader.flashonly.js # 只有Flash实现的版本。 +├── webuploader.flashonly.min.js # min版本 +├ +├── webuploader.html5only.js # 只有Html5实现的版本。 +├── webuploader.html5only.min.js # min版本 +├ +├── webuploader.noimage.js # 去除图片处理的版本,包括HTML5和FLASH. +├── webuploader.noimage.min.js # min版本 +├ +├── webuploader.custom.js # 自定义打包方案,请查看 Gruntfile.js,满足移动端使用。 +└── webuploader.custom.min.js # min版本 +``` + +## 示例 + +请把整个 Git 包下载下来放在 php 服务器下,因为默认提供的文件接受是用 php 编写的,打开 examples 页面便能查看示例效果。 \ No newline at end of file diff --git a/static/plugins/webuploader/Uploader.swf b/static/plugins/webuploader/Uploader.swf new file mode 100644 index 0000000000000000000000000000000000000000..bd75d6082fc0c3e977390a71dcca78932727147c GIT binary patch literal 143099 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,+E) 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 literal 0 HcmV?d00001 diff --git a/static/plugins/webuploader/bg.png b/static/plugins/webuploader/bg.png new file mode 100644 index 0000000000000000000000000000000000000000..73d102db00caaa7c39241ad6bdf44f2f62deb4e4 GIT binary patch literal 2851 zcmaJ@dpMM78`svx*rZ4$VvKAeb1)1uF^VC!21S}^Y0SLC7;`W)j%ggCq?|T~l-1?i zT5>*qu|kR_rp(GB6hb7YaXyUU8*TM{-*tU`@Aba#^E~%;|9CT-hfG!1LBLe9IpnM{ z9*$>O0p3*G5H{cxV(&}}p^?yJhy@mG7KD)!&;c$H97OkJa43+iugMdG| zaB1d{zeN%7j$kV$8vq+akp?6<0s%&$pa>&l6bf+|YzRjf!r;;uWq>fk7^5(TCg9Hx zL>i4vreIFvY(B@5p3EWMTrLX(g9Qc#LIaUdCff^!K%>zc9EOGlQV#=8FoR1BGGK7D zzcAnc4v9@=aj8rOc!QDX$@J%%L!?UoPJzz)O3UDUE|auiuplA}hJeC1Qu^YE$Nzs< zI{m9RhkF|Mx8DCr%yAB80kG2mhw0BINgGGe-U!9QSg`>jm&tZ!GJU^P(b1d9Wpcck zEU=Xm3apDKlBkRg!=XmLdk5+#%N#|!{`*m=ohp(d#Fr@R&mJT*{FG?aK zBb#D}JLVkJJ^phmV-UXWgu?pch4s413FUysi9dmOouuT9$?!K%wQQaEc`CQ>kw+HR z)@tcJ0)arXo{*5hpO&mn1RTk%(+uPBYHMpXgid^q#rd9NEB)u4*uE8E<6%7k>ywh6 z=V9WaD33l5mRJmXLIS#hbOB!k^wT^)9f0wxUcr!09Vid|Gl@AfA;AaO*Tu-?uv5wB_d? ze_wc83@v7*Hy*D`0=ZCFQ_XScs>y`?2_m@JIcbUyR%l)uGF7P4)rzKmi zgE0HRc-@7l*)#m75MJoEG<$VK_J-Afj(V6@Z}uX)80PD>3wbI{F28)h`Dw$fab?HI ziOU}QR$uj}ZLKp}x?c}d;88{YZOSuR@_S=x4sY*|q{#N9jrj5P6Yqkq_5>W~36OY)|CtJu``tStB#IoQyj&KLzKr;{4(Z9Xz4)Z;X5f?!>1w>gUPz8&w|V{Sia4 z7Y2rA0Ov`A(5|bFPOw}PpXa7$-z6c1uG6)V4>3ghw;}y{n_Nnv2 zLG}8_#&zF?#H@^i=Bo|$wEmBBrnrdwil7l@@ReZStqq?4Mfjz(-g{4T{Z^qiB1 zz^-dlX?hlk7L4{qlcwtk_>b>rOkc6D?pi3Rb7EF*=Umh54ev>$% z_94$c2uP9eUmfjoCmVy55_~n*WsCCPOMEji9v58*4jFUluI#th8#TMCc)60U%Hqj0 z@6*Np!jXcWL;0v!JkDeD9+dHm+S89gR_ez@CP>^T({K@2?>vq^;&{#m!zWum84mGO!HVd83!PiwY zpAYzY(UdMZl8czj9i|7$THLMxw0kn(7B5d`I;6) zhmY{yi>B?RRhZO)rigLxWxf3>0fW+7yK3s^3$Vnvz10g>8do$gmMZXG$giW-9fqn8 zKYT0BcFBC(N^REOJ6tay-E3^o!xAcMbssmY+_y;1R9VF}Y(8FDoL|pdO0gSX@`#UK zRmod)!)9ku6=ky%X#`mF%*UPX5vdMMgeuQ9i2}V@=%Ig&|H9BG*dBIIRmY7tXFYiM z{M6V7ckToYzvZ~?~ z9dQN$txWTXP)ckZ@q@Sjmel5crc7bD;_0Jh`M2M7m^Cfu1#6hiy|}67Rz8-w=5*pJ4pG6HD8e*$A#CviD5Rs7^$Q_zfBU3TR8vs)&9)yhAY?2oDT2%*GNIAgZr z{e0~_ta?{xcJ6Ck^l>MUb%%w))1PQ@+NBFgjn%oBr0%M$AF!x@baizr=MHjT=hY@EY2&}t@Q z{ZnVnOz$hdeoP)y?GhIf>SC72hZNkGdQ0NYDf*SxrY5MZ^&FYM;dEp3;;!$u%;sO9*H6;am6P|>F0)n;;U$$_nB zN9y(a2IW=|yC}d+eB|P6a-ht@je|quCbehk#7CCZea9A2AAI}4Qy`UvYZNNw1F}>Y hD}v^2s$Q3a%g~P`+Sj%~6*jJ|cGmW|Ldy%0{{qKIBM1Nh literal 0 HcmV?d00001 diff --git a/static/plugins/webuploader/icons.png b/static/plugins/webuploader/icons.png new file mode 100644 index 0000000000000000000000000000000000000000..12e4700163ac87fa38ae3d92a2c39d0fb4690fed GIT binary patch literal 2678 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 literal 0 HcmV?d00001 diff --git a/static/plugins/webuploader/progress.png b/static/plugins/webuploader/progress.png new file mode 100644 index 0000000000000000000000000000000000000000..717c4865c90a959c6a0e9ad1af9c777d900a2e9c GIT binary patch literal 1269 zcmeAS@N?(olHy`uVBq!ia0vp^f8U}fi7AzZCsS=07??9MLn2Bde0{8v^Kf6`()~Xj@TAnpKdC8`Lf!&sHg;q@=(~U%$M( zT(8_%FTW^V-_X+15@d#vkuFe$ZgFK^Nn(X=Ua>OF1ees}t-3#_`hBq$Z(46Le)Ln;eW^@CE2 z^Gl18f$@>14ATq@JNy=b6armi>cVAJd5X6R;MWawh(V&G(G=xXffXz1o@ zX=ZL{;B08&Z0-!x>zP+vl9-pA3bQv8XfIT+GhV$`&PAz-CHX}m`T04p6cCV+Uy@&( zkzb(T9Bihb;hUJ8nFkWk1Vs?Uzb>gonPsUdZbkXI3g8g7%EaOV0~10%hv-cqC)D(T zj?o7t52WM*69T3|5EGvGfgE`DNzDW1nId53*cQ_-&cMK^?CIhdQgN$ga=rIq1A(^5 zYKwg&`wo`WvF`u>{iFU|`!+wz>b#B?t8F4hxRm$~lz-tTH#6E8xZnKlv%`YRwvA{oKseqmK8(Gfx|> z#)dT+Zy!CGH{+89Q&m{rC!PyyIjq?Y9m+ziHPqr6qxfF`+2Qt=-KQ=fE8_j%1#Y2} z>NffN)P;AQIhrE)QQzeqbFS^A8(M1XGuQqTO<=fcH+M~2lzlL$Ao6teb6Mw<&;$U? C61Fb@ literal 0 HcmV?d00001 diff --git a/static/plugins/webuploader/success.png b/static/plugins/webuploader/success.png new file mode 100644 index 0000000000000000000000000000000000000000..94f968dc8fd3c7ca8f6cb599d006ef3f23b62c7d GIT binary patch literal 1621 zcmeAS@N?(olHy`uVBq!ia0vp^8X(NU1|)m_?Z^dEk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+m{l@EB1$5BeXNr6bM+EIYV;~{3xK*A7;Nk-3KEmEQ%e+* zQqwc@Y?a>c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxSU1_g&``n5OwZ87 z)XdCKN5ROz&`93^h|F{iO{`4Ktc=VRpg;*|TTx1yRgjAt)Gi>;Rw<*Tq`*pFzr4I$ zuiRKKzbIYb(9+TpWQLKEE>MMTab;dfVufyAu`f(~1RD^r68eAMwS&*t9 zlvO-#?>2=9ZF3nBND}m`vLFhHXsTY(OatnYqyQCInmZhe+73JqDfIV%MiQ6rPIL(9V zO~LIJBb<8mfsWA!MJ-ZP!-Rn82gHOYTp$OY^i%VI>AeV;u(vXbonl~MI_~M>7*cWT z%njcRVMh`65C6~Q2yI{K`NBQQh9heAinXE1(JfB=Ul%M;Ke#x6g%L8^f5l4Te zj&BcK1znDIyZS2~eC7F3K+}tVTa$=u*HTU0W4>0#`+Qf%|GkyBERBs#$Y}P@k7@U2 zd#*XsYr~WGeCA1uSK>2HTD)g9mP}Xrpg$+`K&8dx2JS~bjCx(hj5}1;-8qn2_>|?> zp{d+2q{I0hbQS!U-jOut`kSEi+IhQJGmBl4O%se*W?OGKZ0Mp?XmGnRgezL^8vCJ4 z#-ek9(R&%4!h+;oXA~Mtbyie=u#0PDiI#!iX`%2?^@kRL8ma*r6?+*}W4e|_8~J&& z+-+*Ub3{dDcAIF|!MTDz0#78qKYeaeVU$wDOOs3cR=e5FZ*O2e-6xc{Uu=$;!s>*M z>nZ|V>zUp8-oE6^wXjV+_UOvuE&SORr`;3hnBKu~e3rzlY@NRjD|5Ui2t>^@Ve7jy z)2vT>u1vesjJgG`f;l_n`FmbR~b^~{VzXB}7CdU_pt>lO9l=E;X#4<2bZu32r~J^e^%TSISQ_Qm$= z7d{rOo2%^RUGseJkpn9(MWszUQdl(i&RN5PBgZ0d#S7ZipShU3W`>Nsy6C#pCD*KW zyxHW%b?m|pr;dF;L$saa#oaeO`u=>jkXTjl9*cLEvOD!Jdr4ooKJW9gY7?D{e?uM^ z`_}E4`0yR0boA=9>3>WXzUH{&pDfpZ;_2xb=j*pKsk#WyD%J#5t{1#azeB z4SJq8H2*dJVX$xugZHVh{7U`u4@oM|quz5j-hRJi{qwh1k5uNqu`W>3HJG&Y^R}6R z$Gq-HPue$sd${oT*XKO8zN>Oe_%K^bZNm3^-vf7@5X#' ) + .appendTo( $wrap.find( '.queueList' ) ), + + // 状态栏,包括进度和控制按钮 + $statusBar = $wrap.find( '.statusBar' ), + + // 文件总体选择信息。 + $info = $statusBar.find( '.info' ), + + // 上传按钮 + $upload = $wrap.find( '.uploadBtn' ), + + // 没选择文件之前的内容。 + $placeHolder = $wrap.find( '.placeholder' ), + + $progress = $statusBar.find( '.progress' ).hide(), + + // 添加的文件数量 + fileCount = 0, + + // 添加的文件总大小 + fileSize = 0, + + // 优化retina, 在retina下这个值是2 + ratio = window.devicePixelRatio || 1, + + // 缩略图大小 + thumbnailWidth = 110 * ratio, + thumbnailHeight = 110 * ratio, + + // 可能有pedding, ready, uploading, confirm, done. + state = 'pedding', + + // 所有文件的进度信息,key为file id + percentages = {}, + // 判断浏览器是否支持图片的base64 + isSupportBase64 = ( function() { + var data = new Image(); + var support = true; + data.onload = data.onerror = function() { + if( this.width != 1 || this.height != 1 ) { + support = false; + } + } + data.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw=="; + return support; + } )(), + + // 检测是否已经安装flash,检测flash的版本 + flashVersion = ( function() { + var version; + + try { + version = navigator.plugins[ 'Shockwave Flash' ]; + version = version.description; + } catch ( ex ) { + try { + version = new ActiveXObject('ShockwaveFlash.ShockwaveFlash') + .GetVariable('$version'); + } catch ( ex2 ) { + version = '0.0'; + } + } + version = version.match( /\d+/g ); + return parseFloat( version[ 0 ] + '.' + version[ 1 ], 10 ); + } )(), + + supportTransition = (function(){ + var s = document.createElement('p').style, + r = 'transition' in s || + 'WebkitTransition' in s || + 'MozTransition' in s || + 'msTransition' in s || + 'OTransition' in s; + s = null; + return r; + })(), + + // WebUploader实例 + uploader; + + if ( !WebUploader.Uploader.support('flash') && WebUploader.browser.ie ) { + + // flash 安装了但是版本过低。 + if (flashVersion) { + (function(container) { + window['expressinstallcallback'] = function( state ) { + switch(state) { + case 'Download.Cancelled': + alert('您取消了更新!') + break; + + case 'Download.Failed': + alert('安装失败') + break; + + default: + alert('安装已成功,请刷新!'); + break; + } + delete window['expressinstallcallback']; + }; + + var swf = './expressInstall.swf'; + // insert flash object + var html = '' + + '' + + '' + + ''; + + container.html(html); + + })($wrap); + + // 压根就没有安转。 + } else { + $wrap.html('get flash player'); + } + + return; + } else if (!WebUploader.Uploader.support()) { + alert( 'Web Uploader 不支持您的浏览器!'); + return; + } + + // 实例化 + uploader = WebUploader.create({ + pick: { + id: '#filePicker', + label: '点击选择图片' + }, + formData: { + uid: 123 + }, + dnd: '#dndArea', + paste: '#uploader', + swf: '../../dist/Uploader.swf', + chunked: false, + chunkSize: 512 * 1024, + server: '../../server/fileupload.php', + // runtimeOrder: 'flash', + + // accept: { + // title: 'Images', + // extensions: 'gif,jpg,jpeg,bmp,png', + // mimeTypes: 'image/*' + // }, + + // 禁掉全局的拖拽功能。这样不会出现图片拖进页面的时候,把图片打开。 + disableGlobalDnd: true, + fileNumLimit: 300, + fileSizeLimit: 200 * 1024 * 1024, // 200 M + fileSingleSizeLimit: 50 * 1024 * 1024 // 50 M + }); + + // 拖拽时不接受 js, txt 文件。 + uploader.on( 'dndAccept', function( items ) { + var denied = false, + len = items.length, + i = 0, + // 修改js类型 + unAllowed = 'text/plain;application/javascript '; + + for ( ; i < len; i++ ) { + // 如果在列表里面 + if ( ~unAllowed.indexOf( items[ i ].type ) ) { + denied = true; + break; + } + } + + return !denied; + }); + + // uploader.on('filesQueued', function() { + // uploader.sort(function( a, b ) { + // if ( a.name < b.name ) + // return -1; + // if ( a.name > b.name ) + // return 1; + // return 0; + // }); + // }); + + // 添加“添加文件”的按钮, + uploader.addButton({ + id: '#filePicker2', + label: '继续添加' + }); + + uploader.on('ready', function() { + window.uploader = uploader; + }); + + // 当有文件添加进来时执行,负责view的创建 + function addFile( file ) { + var $li = $( '
  • ' + + '

    ' + file.name + '

    ' + + '

    '+ + '

    ' + + '
  • ' ), + + $btns = $('
    ' + + '删除' + + '向右旋转' + + '向左旋转
    ').appendTo( $li ), + $prgress = $li.find('p.progress span'), + $wrap = $li.find( 'p.imgWrap' ), + $info = $('

    '), + + showError = function( code ) { + switch( code ) { + case 'exceed_size': + text = '文件大小超出'; + break; + + case 'interrupt': + text = '上传暂停'; + break; + + default: + text = '上传失败,请重试'; + break; + } + + $info.text( text ).appendTo( $li ); + }; + + if ( file.getStatus() === 'invalid' ) { + showError( file.statusText ); + } else { + // @todo lazyload + $wrap.text( '预览中' ); + uploader.makeThumb( file, function( error, src ) { + var img; + + if ( error ) { + $wrap.text( '不能预览' ); + return; + } + + if( isSupportBase64 ) { + img = $(''); + $wrap.empty().append( img ); + } else { + $.ajax('../../server/preview.php', { + method: 'POST', + data: src, + dataType:'json' + }).done(function( response ) { + if (response.result) { + img = $(''); + $wrap.empty().append( img ); + } else { + $wrap.text("预览出错"); + } + }); + } + }, thumbnailWidth, thumbnailHeight ); + + percentages[ file.id ] = [ file.size, 0 ]; + file.rotation = 0; + } + + file.on('statuschange', function( cur, prev ) { + if ( prev === 'progress' ) { + $prgress.hide().width(0); + } else if ( prev === 'queued' ) { + $li.off( 'mouseenter mouseleave' ); + $btns.remove(); + } + + // 成功 + if ( cur === 'error' || cur === 'invalid' ) { + console.log( file.statusText ); + showError( file.statusText ); + percentages[ file.id ][ 1 ] = 1; + } else if ( cur === 'interrupt' ) { + showError( 'interrupt' ); + } else if ( cur === 'queued' ) { + percentages[ file.id ][ 1 ] = 0; + } else if ( cur === 'progress' ) { + $info.remove(); + $prgress.css('display', 'block'); + } else if ( cur === 'complete' ) { + $li.append( '' ); + } + + $li.removeClass( 'state-' + prev ).addClass( 'state-' + cur ); + }); + + $li.on( 'mouseenter', function() { + $btns.stop().animate({height: 30}); + }); + + $li.on( 'mouseleave', function() { + $btns.stop().animate({height: 0}); + }); + + $btns.on( 'click', 'span', function() { + var index = $(this).index(), + deg; + + switch ( index ) { + case 0: + uploader.removeFile( file ); + return; + + case 1: + file.rotation += 90; + break; + + case 2: + file.rotation -= 90; + break; + } + + if ( supportTransition ) { + deg = 'rotate(' + file.rotation + 'deg)'; + $wrap.css({ + '-webkit-transform': deg, + '-mos-transform': deg, + '-o-transform': deg, + 'transform': deg + }); + } else { + $wrap.css( 'filter', 'progid:DXImageTransform.Microsoft.BasicImage(rotation='+ (~~((file.rotation/90)%4 + 4)%4) +')'); + // use jquery animate to rotation + // $({ + // rotation: rotation + // }).animate({ + // rotation: file.rotation + // }, { + // easing: 'linear', + // step: function( now ) { + // now = now * Math.PI / 180; + + // var cos = Math.cos( now ), + // sin = Math.sin( now ); + + // $wrap.css( 'filter', "progid:DXImageTransform.Microsoft.Matrix(M11=" + cos + ",M12=" + (-sin) + ",M21=" + sin + ",M22=" + cos + ",SizingMethod='auto expand')"); + // } + // }); + } + + + }); + + $li.appendTo( $queue ); + } + + // 负责view的销毁 + function removeFile( file ) { + var $li = $('#'+file.id); + + delete percentages[ file.id ]; + updateTotalProgress(); + $li.off().find('.file-panel').off().end().remove(); + } + + function updateTotalProgress() { + var loaded = 0, + total = 0, + spans = $progress.children(), + percent; + + $.each( percentages, function( k, v ) { + total += v[ 0 ]; + loaded += v[ 0 ] * v[ 1 ]; + } ); + + percent = total ? loaded / total : 0; + + + spans.eq( 0 ).text( Math.round( percent * 100 ) + '%' ); + spans.eq( 1 ).css( 'width', Math.round( percent * 100 ) + '%' ); + updateStatus(); + } + + function updateStatus() { + var text = '', stats; + + if ( state === 'ready' ) { + text = '选中' + fileCount + '张图片,共' + + WebUploader.formatSize( fileSize ) + '。'; + } else if ( state === 'confirm' ) { + stats = uploader.getStats(); + if ( stats.uploadFailNum ) { + text = '已成功上传' + stats.successNum+ '张照片至XX相册,'+ + stats.uploadFailNum + '张照片上传失败,重新上传失败图片或忽略' + } + + } else { + stats = uploader.getStats(); + text = '共' + fileCount + '张(' + + WebUploader.formatSize( fileSize ) + + '),已上传' + stats.successNum + '张'; + + if ( stats.uploadFailNum ) { + text += ',失败' + stats.uploadFailNum + '张'; + } + } + + $info.html( text ); + } + + function setState( val ) { + var file, stats; + + if ( val === state ) { + return; + } + + $upload.removeClass( 'state-' + state ); + $upload.addClass( 'state-' + val ); + state = val; + + switch ( state ) { + case 'pedding': + $placeHolder.removeClass( 'element-invisible' ); + $queue.hide(); + $statusBar.addClass( 'element-invisible' ); + uploader.refresh(); + break; + + case 'ready': + $placeHolder.addClass( 'element-invisible' ); + $( '#filePicker2' ).removeClass( 'element-invisible'); + $queue.show(); + $statusBar.removeClass('element-invisible'); + uploader.refresh(); + break; + + case 'uploading': + $( '#filePicker2' ).addClass( 'element-invisible' ); + $progress.show(); + $upload.text( '暂停上传' ); + break; + + case 'paused': + $progress.show(); + $upload.text( '继续上传' ); + break; + + case 'confirm': + $progress.hide(); + $( '#filePicker2' ).removeClass( 'element-invisible' ); + $upload.text( '开始上传' ); + + stats = uploader.getStats(); + if ( stats.successNum && !stats.uploadFailNum ) { + setState( 'finish' ); + return; + } + break; + case 'finish': + stats = uploader.getStats(); + if ( stats.successNum ) { + alert( '上传成功' ); + } else { + // 没有成功的图片,重设 + state = 'done'; + location.reload(); + } + break; + } + + updateStatus(); + } + + uploader.onUploadProgress = function( file, percentage ) { + var $li = $('#'+file.id), + $percent = $li.find('.progress span'); + + $percent.css( 'width', percentage * 100 + '%' ); + percentages[ file.id ][ 1 ] = percentage; + updateTotalProgress(); + }; + + uploader.onFileQueued = function( file ) { + fileCount++; + fileSize += file.size; + + if ( fileCount === 1 ) { + $placeHolder.addClass( 'element-invisible' ); + $statusBar.show(); + } + + addFile( file ); + setState( 'ready' ); + updateTotalProgress(); + }; + + uploader.onFileDequeued = function( file ) { + fileCount--; + fileSize -= file.size; + + if ( !fileCount ) { + setState( 'pedding' ); + } + + removeFile( file ); + updateTotalProgress(); + + }; + + uploader.on( 'all', function( type ) { + var stats; + switch( type ) { + case 'uploadFinished': + setState( 'confirm' ); + break; + + case 'startUpload': + setState( 'uploading' ); + break; + + case 'stopUpload': + setState( 'paused' ); + break; + + } + }); + + uploader.onError = function( code ) { + alert( 'Eroor: ' + code ); + }; + + $upload.on('click', function() { + if ( $(this).hasClass( 'disabled' ) ) { + return false; + } + + if ( state === 'ready' ) { + uploader.upload(); + } else if ( state === 'paused' ) { + uploader.upload(); + } else if ( state === 'uploading' ) { + uploader.stop(); + } + }); + + $info.on( 'click', '.retry', function() { + uploader.retry(); + } ); + + $info.on( 'click', '.ignore', function() { + alert( 'todo' ); + } ); + + $upload.addClass( 'state-' + state ); + updateTotalProgress(); + }); + +})( jQuery ); \ No newline at end of file diff --git a/static/plugins/webuploader/upload.style.css b/static/plugins/webuploader/upload.style.css new file mode 100644 index 00000000..1c56dbc5 --- /dev/null +++ b/static/plugins/webuploader/upload.style.css @@ -0,0 +1,14 @@ +#uploader .queueList { + margin: 6px; +} + +#uploader .placeholder { + border: 3px dashed #e6e6e6; + min-height: 38px; + padding-top: 15px; + padding-bottom: 15px; + text-align: center; + color: #cccccc; + font-size: 18px; + position: relative; +} \ No newline at end of file diff --git a/static/plugins/webuploader/webuploader.css b/static/plugins/webuploader/webuploader.css new file mode 100644 index 00000000..12f451f8 --- /dev/null +++ b/static/plugins/webuploader/webuploader.css @@ -0,0 +1,28 @@ +.webuploader-container { + position: relative; +} +.webuploader-element-invisible { + position: absolute !important; + clip: rect(1px 1px 1px 1px); /* IE6, IE7 */ + clip: rect(1px,1px,1px,1px); +} +.webuploader-pick { + position: relative; + display: inline-block; + cursor: pointer; + background: #00b7ee; + padding: 10px 15px; + color: #fff; + text-align: center; + border-radius: 3px; + overflow: hidden; +} +.webuploader-pick-hover { + background: #00a2d4; +} + +.webuploader-pick-disable { + opacity: 0.6; + pointer-events:none; +} + diff --git a/static/plugins/webuploader/webuploader.custom.js b/static/plugins/webuploader/webuploader.custom.js new file mode 100644 index 00000000..2e242c6e --- /dev/null +++ b/static/plugins/webuploader/webuploader.custom.js @@ -0,0 +1,6502 @@ +/*! WebUploader 0.1.5 */ + + +/** + * @fileOverview 让内部各个部件的代码可以用[amd](https://github.com/amdjs/amdjs-api/wiki/AMD)模块定义方式组织起来。 + * + * AMD API 内部的简单不完全实现,请忽略。只有当WebUploader被合并成一个文件的时候才会引入。 + */ +(function( root, factory ) { + var modules = {}, + + // 内部require, 简单不完全实现。 + // https://github.com/amdjs/amdjs-api/wiki/require + _require = function( deps, callback ) { + var args, len, i; + + // 如果deps不是数组,则直接返回指定module + if ( typeof deps === 'string' ) { + return getModule( deps ); + } else { + args = []; + for( len = deps.length, i = 0; i < len; i++ ) { + args.push( getModule( deps[ i ] ) ); + } + + return callback.apply( null, args ); + } + }, + + // 内部define,暂时不支持不指定id. + _define = function( id, deps, factory ) { + if ( arguments.length === 2 ) { + factory = deps; + deps = null; + } + + _require( deps || [], function() { + setModule( id, factory, arguments ); + }); + }, + + // 设置module, 兼容CommonJs写法。 + setModule = function( id, factory, args ) { + var module = { + exports: factory + }, + returned; + + if ( typeof factory === 'function' ) { + args.length || (args = [ _require, module.exports, module ]); + returned = factory.apply( null, args ); + returned !== undefined && (module.exports = returned); + } + + modules[ id ] = module.exports; + }, + + // 根据id获取module + getModule = function( id ) { + var module = modules[ id ] || root[ id ]; + + if ( !module ) { + throw new Error( '`' + id + '` is undefined' ); + } + + return module; + }, + + // 将所有modules,将路径ids装换成对象。 + exportsTo = function( obj ) { + var key, host, parts, part, last, ucFirst; + + // make the first character upper case. + ucFirst = function( str ) { + return str && (str.charAt( 0 ).toUpperCase() + str.substr( 1 )); + }; + + for ( key in modules ) { + host = obj; + + if ( !modules.hasOwnProperty( key ) ) { + continue; + } + + parts = key.split('/'); + last = ucFirst( parts.pop() ); + + while( (part = ucFirst( parts.shift() )) ) { + host[ part ] = host[ part ] || {}; + host = host[ part ]; + } + + host[ last ] = modules[ key ]; + } + + return obj; + }, + + makeExport = function( dollar ) { + root.__dollar = dollar; + + // exports every module. + return exportsTo( factory( root, _define, _require ) ); + }, + + origin; + + if ( typeof module === 'object' && typeof module.exports === 'object' ) { + + // For CommonJS and CommonJS-like environments where a proper window is present, + module.exports = makeExport(); + } else if ( typeof define === 'function' && define.amd ) { + + // Allow using this built library as an AMD module + // in another project. That other project will only + // see this AMD call, not the internal modules in + // the closure below. + define([ 'jquery' ], makeExport ); + } else { + + // Browser globals case. Just assign the + // result to a property on the global. + origin = root.WebUploader; + root.WebUploader = makeExport(); + root.WebUploader.noConflict = function() { + root.WebUploader = origin; + }; + } +})( window, function( window, define, require ) { + + + /** + * @fileOverview jQuery or Zepto + */ + define('dollar-third',[],function() { + var $ = window.__dollar || window.jQuery || window.Zepto; + + if ( !$ ) { + throw new Error('jQuery or Zepto not found!'); + } + + return $; + }); + /** + * @fileOverview Dom 操作相关 + */ + define('dollar',[ + 'dollar-third' + ], function( _ ) { + return _; + }); + /** + * 直接来源于jquery的代码。 + * @fileOverview Promise/A+ + * @beta + */ + define('promise-builtin',[ + 'dollar' + ], function( $ ) { + + var api; + + // 简单版Callbacks, 默认memory,可选once. + function Callbacks( once ) { + var list = [], + stack = !once && [], + fire = function( data ) { + memory = data; + fired = true; + firingIndex = firingStart || 0; + firingStart = 0; + firingLength = list.length; + firing = true; + + for ( ; list && firingIndex < firingLength; firingIndex++ ) { + list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ); + } + firing = false; + + if ( list ) { + if ( stack ) { + stack.length && fire( stack.shift() ); + } else { + list = []; + } + } + }, + self = { + add: function() { + if ( list ) { + var start = list.length; + (function add ( args ) { + $.each( args, function( _, arg ) { + var type = $.type( arg ); + if ( type === 'function' ) { + list.push( arg ); + } else if ( arg && arg.length && + type !== 'string' ) { + + add( arg ); + } + }); + })( arguments ); + + if ( firing ) { + firingLength = list.length; + } else if ( memory ) { + firingStart = start; + fire( memory ); + } + } + return this; + }, + + disable: function() { + list = stack = memory = undefined; + return this; + }, + + // Lock the list in its current state + lock: function() { + stack = undefined; + if ( !memory ) { + self.disable(); + } + return this; + }, + + fireWith: function( context, args ) { + if ( list && (!fired || stack) ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + if ( firing ) { + stack.push( args ); + } else { + fire( args ); + } + } + return this; + }, + + fire: function() { + self.fireWith( this, arguments ); + return this; + } + }, + + fired, firing, firingStart, firingLength, firingIndex, memory; + + return self; + } + + function Deferred( func ) { + var tuples = [ + // action, add listener, listener list, final state + [ 'resolve', 'done', Callbacks( true ), 'resolved' ], + [ 'reject', 'fail', Callbacks( true ), 'rejected' ], + [ 'notify', 'progress', Callbacks() ] + ], + state = 'pending', + promise = { + state: function() { + return state; + }, + always: function() { + deferred.done( arguments ).fail( arguments ); + return this; + }, + then: function( /* fnDone, fnFail, fnProgress */ ) { + var fns = arguments; + return Deferred(function( newDefer ) { + $.each( tuples, function( i, tuple ) { + var action = tuple[ 0 ], + fn = $.isFunction( fns[ i ] ) && fns[ i ]; + + // deferred[ done | fail | progress ] for + // forwarding actions to newDefer + deferred[ tuple[ 1 ] ](function() { + var returned; + + returned = fn && fn.apply( this, arguments ); + + if ( returned && + $.isFunction( returned.promise ) ) { + + returned.promise() + .done( newDefer.resolve ) + .fail( newDefer.reject ) + .progress( newDefer.notify ); + } else { + newDefer[ action + 'With' ]( + this === promise ? + newDefer.promise() : + this, + fn ? [ returned ] : arguments ); + } + }); + }); + fns = null; + }).promise(); + }, + + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + + return obj != null ? $.extend( obj, promise ) : promise; + } + }, + deferred = {}; + + // Keep pipe for back-compat + promise.pipe = promise.then; + + // Add list-specific methods + $.each( tuples, function( i, tuple ) { + var list = tuple[ 2 ], + stateString = tuple[ 3 ]; + + // promise[ done | fail | progress ] = list.add + promise[ tuple[ 1 ] ] = list.add; + + // Handle state + if ( stateString ) { + list.add(function() { + // state = [ resolved | rejected ] + state = stateString; + + // [ reject_list | resolve_list ].disable; progress_list.lock + }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock ); + } + + // deferred[ resolve | reject | notify ] + deferred[ tuple[ 0 ] ] = function() { + deferred[ tuple[ 0 ] + 'With' ]( this === deferred ? promise : + this, arguments ); + return this; + }; + deferred[ tuple[ 0 ] + 'With' ] = list.fireWith; + }); + + // Make the deferred a promise + promise.promise( deferred ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + } + + api = { + /** + * 创建一个[Deferred](http://api.jquery.com/category/deferred-object/)对象。 + * 详细的Deferred用法说明,请参照jQuery的API文档。 + * + * Deferred对象在钩子回掉函数中经常要用到,用来处理需要等待的异步操作。 + * + * @for Base + * @method Deferred + * @grammar Base.Deferred() => Deferred + * @example + * // 在文件开始发送前做些异步操作。 + * // WebUploader会等待此异步操作完成后,开始发送文件。 + * Uploader.register({ + * 'before-send-file': 'doSomthingAsync' + * }, { + * + * doSomthingAsync: function() { + * var deferred = Base.Deferred(); + * + * // 模拟一次异步操作。 + * setTimeout(deferred.resolve, 2000); + * + * return deferred.promise(); + * } + * }); + */ + Deferred: Deferred, + + /** + * 判断传入的参数是否为一个promise对象。 + * @method isPromise + * @grammar Base.isPromise( anything ) => Boolean + * @param {*} anything 检测对象。 + * @return {Boolean} + * @for Base + * @example + * console.log( Base.isPromise() ); // => false + * console.log( Base.isPromise({ key: '123' }) ); // => false + * console.log( Base.isPromise( Base.Deferred().promise() ) ); // => true + * + * // Deferred也是一个Promise + * console.log( Base.isPromise( Base.Deferred() ) ); // => true + */ + isPromise: function( anything ) { + return anything && typeof anything.then === 'function'; + }, + + /** + * 返回一个promise,此promise在所有传入的promise都完成了后完成。 + * 详细请查看[这里](http://api.jquery.com/jQuery.when/)。 + * + * @method when + * @for Base + * @grammar Base.when( promise1[, promise2[, promise3...]] ) => Promise + */ + when: function( subordinate /* , ..., subordinateN */ ) { + var i = 0, + slice = [].slice, + resolveValues = slice.call( arguments ), + length = resolveValues.length, + + // the count of uncompleted subordinates + remaining = length !== 1 || (subordinate && + $.isFunction( subordinate.promise )) ? length : 0, + + // the master Deferred. If resolveValues consist of + // only a single Deferred, just use that. + deferred = remaining === 1 ? subordinate : Deferred(), + + // Update function for both resolve and progress values + updateFunc = function( i, contexts, values ) { + return function( value ) { + contexts[ i ] = this; + values[ i ] = arguments.length > 1 ? + slice.call( arguments ) : value; + + if ( values === progressValues ) { + deferred.notifyWith( contexts, values ); + } else if ( !(--remaining) ) { + deferred.resolveWith( contexts, values ); + } + }; + }, + + progressValues, progressContexts, resolveContexts; + + // add listeners to Deferred subordinates; treat others as resolved + if ( length > 1 ) { + progressValues = new Array( length ); + progressContexts = new Array( length ); + resolveContexts = new Array( length ); + for ( ; i < length; i++ ) { + if ( resolveValues[ i ] && + $.isFunction( resolveValues[ i ].promise ) ) { + + resolveValues[ i ].promise() + .done( updateFunc( i, resolveContexts, + resolveValues ) ) + .fail( deferred.reject ) + .progress( updateFunc( i, progressContexts, + progressValues ) ); + } else { + --remaining; + } + } + } + + // if we're not waiting on anything, resolve the master + if ( !remaining ) { + deferred.resolveWith( resolveContexts, resolveValues ); + } + + return deferred.promise(); + } + }; + + return api; + }); + define('promise',[ + 'promise-builtin' + ], function( $ ) { + return $; + }); + /** + * @fileOverview 基础类方法。 + */ + + /** + * Web Uploader内部类的详细说明,以下提及的功能类,都可以在`WebUploader`这个变量中访问到。 + * + * As you know, Web Uploader的每个文件都是用过[AMD](https://github.com/amdjs/amdjs-api/wiki/AMD)规范中的`define`组织起来的, 每个Module都会有个module id. + * 默认module id为该文件的路径,而此路径将会转化成名字空间存放在WebUploader中。如: + * + * * module `base`:WebUploader.Base + * * module `file`: WebUploader.File + * * module `lib/dnd`: WebUploader.Lib.Dnd + * * module `runtime/html5/dnd`: WebUploader.Runtime.Html5.Dnd + * + * + * 以下文档中对类的使用可能省略掉了`WebUploader`前缀。 + * @module WebUploader + * @title WebUploader API文档 + */ + define('base',[ + 'dollar', + 'promise' + ], function( $, promise ) { + + var noop = function() {}, + call = Function.call; + + // http://jsperf.com/uncurrythis + // 反科里化 + function uncurryThis( fn ) { + return function() { + return call.apply( fn, arguments ); + }; + } + + function bindFn( fn, context ) { + return function() { + return fn.apply( context, arguments ); + }; + } + + function createObject( proto ) { + var f; + + if ( Object.create ) { + return Object.create( proto ); + } else { + f = function() {}; + f.prototype = proto; + return new f(); + } + } + + + /** + * 基础类,提供一些简单常用的方法。 + * @class Base + */ + return { + + /** + * @property {String} version 当前版本号。 + */ + version: '0.1.5', + + /** + * @property {jQuery|Zepto} $ 引用依赖的jQuery或者Zepto对象。 + */ + $: $, + + Deferred: promise.Deferred, + + isPromise: promise.isPromise, + + when: promise.when, + + /** + * @description 简单的浏览器检查结果。 + * + * * `webkit` webkit版本号,如果浏览器为非webkit内核,此属性为`undefined`。 + * * `chrome` chrome浏览器版本号,如果浏览器为chrome,此属性为`undefined`。 + * * `ie` ie浏览器版本号,如果浏览器为非ie,此属性为`undefined`。**暂不支持ie10+** + * * `firefox` firefox浏览器版本号,如果浏览器为非firefox,此属性为`undefined`。 + * * `safari` safari浏览器版本号,如果浏览器为非safari,此属性为`undefined`。 + * * `opera` opera浏览器版本号,如果浏览器为非opera,此属性为`undefined`。 + * + * @property {Object} [browser] + */ + browser: (function( ua ) { + var ret = {}, + webkit = ua.match( /WebKit\/([\d.]+)/ ), + chrome = ua.match( /Chrome\/([\d.]+)/ ) || + ua.match( /CriOS\/([\d.]+)/ ), + + ie = ua.match( /MSIE\s([\d\.]+)/ ) || + ua.match( /(?:trident)(?:.*rv:([\w.]+))?/i ), + firefox = ua.match( /Firefox\/([\d.]+)/ ), + safari = ua.match( /Safari\/([\d.]+)/ ), + opera = ua.match( /OPR\/([\d.]+)/ ); + + webkit && (ret.webkit = parseFloat( webkit[ 1 ] )); + chrome && (ret.chrome = parseFloat( chrome[ 1 ] )); + ie && (ret.ie = parseFloat( ie[ 1 ] )); + firefox && (ret.firefox = parseFloat( firefox[ 1 ] )); + safari && (ret.safari = parseFloat( safari[ 1 ] )); + opera && (ret.opera = parseFloat( opera[ 1 ] )); + + return ret; + })( navigator.userAgent ), + + /** + * @description 操作系统检查结果。 + * + * * `android` 如果在android浏览器环境下,此值为对应的android版本号,否则为`undefined`。 + * * `ios` 如果在ios浏览器环境下,此值为对应的ios版本号,否则为`undefined`。 + * @property {Object} [os] + */ + os: (function( ua ) { + var ret = {}, + + // osx = !!ua.match( /\(Macintosh\; Intel / ), + android = ua.match( /(?:Android);?[\s\/]+([\d.]+)?/ ), + ios = ua.match( /(?:iPad|iPod|iPhone).*OS\s([\d_]+)/ ); + + // osx && (ret.osx = true); + android && (ret.android = parseFloat( android[ 1 ] )); + ios && (ret.ios = parseFloat( ios[ 1 ].replace( /_/g, '.' ) )); + + return ret; + })( navigator.userAgent ), + + /** + * 实现类与类之间的继承。 + * @method inherits + * @grammar Base.inherits( super ) => child + * @grammar Base.inherits( super, protos ) => child + * @grammar Base.inherits( super, protos, statics ) => child + * @param {Class} super 父类 + * @param {Object | Function} [protos] 子类或者对象。如果对象中包含constructor,子类将是用此属性值。 + * @param {Function} [protos.constructor] 子类构造器,不指定的话将创建个临时的直接执行父类构造器的方法。 + * @param {Object} [statics] 静态属性或方法。 + * @return {Class} 返回子类。 + * @example + * function Person() { + * console.log( 'Super' ); + * } + * Person.prototype.hello = function() { + * console.log( 'hello' ); + * }; + * + * var Manager = Base.inherits( Person, { + * world: function() { + * console.log( 'World' ); + * } + * }); + * + * // 因为没有指定构造器,父类的构造器将会执行。 + * var instance = new Manager(); // => Super + * + * // 继承子父类的方法 + * instance.hello(); // => hello + * instance.world(); // => World + * + * // 子类的__super__属性指向父类 + * console.log( Manager.__super__ === Person ); // => true + */ + inherits: function( Super, protos, staticProtos ) { + var child; + + if ( typeof protos === 'function' ) { + child = protos; + protos = null; + } else if ( protos && protos.hasOwnProperty('constructor') ) { + child = protos.constructor; + } else { + child = function() { + return Super.apply( this, arguments ); + }; + } + + // 复制静态方法 + $.extend( true, child, Super, staticProtos || {} ); + + /* jshint camelcase: false */ + + // 让子类的__super__属性指向父类。 + child.__super__ = Super.prototype; + + // 构建原型,添加原型方法或属性。 + // 暂时用Object.create实现。 + child.prototype = createObject( Super.prototype ); + protos && $.extend( true, child.prototype, protos ); + + return child; + }, + + /** + * 一个不做任何事情的方法。可以用来赋值给默认的callback. + * @method noop + */ + noop: noop, + + /** + * 返回一个新的方法,此方法将已指定的`context`来执行。 + * @grammar Base.bindFn( fn, context ) => Function + * @method bindFn + * @example + * var doSomething = function() { + * console.log( this.name ); + * }, + * obj = { + * name: 'Object Name' + * }, + * aliasFn = Base.bind( doSomething, obj ); + * + * aliasFn(); // => Object Name + * + */ + bindFn: bindFn, + + /** + * 引用Console.log如果存在的话,否则引用一个[空函数noop](#WebUploader:Base.noop)。 + * @grammar Base.log( args... ) => undefined + * @method log + */ + log: (function() { + if ( window.console ) { + return bindFn( console.log, console ); + } + return noop; + })(), + + nextTick: (function() { + + return function( cb ) { + setTimeout( cb, 1 ); + }; + + // @bug 当浏览器不在当前窗口时就停了。 + // var next = window.requestAnimationFrame || + // window.webkitRequestAnimationFrame || + // window.mozRequestAnimationFrame || + // function( cb ) { + // window.setTimeout( cb, 1000 / 60 ); + // }; + + // // fix: Uncaught TypeError: Illegal invocation + // return bindFn( next, window ); + })(), + + /** + * 被[uncurrythis](http://www.2ality.com/2011/11/uncurrying-this.html)的数组slice方法。 + * 将用来将非数组对象转化成数组对象。 + * @grammar Base.slice( target, start[, end] ) => Array + * @method slice + * @example + * function doSomthing() { + * var args = Base.slice( arguments, 1 ); + * console.log( args ); + * } + * + * doSomthing( 'ignored', 'arg2', 'arg3' ); // => Array ["arg2", "arg3"] + */ + slice: uncurryThis( [].slice ), + + /** + * 生成唯一的ID + * @method guid + * @grammar Base.guid() => String + * @grammar Base.guid( prefx ) => String + */ + guid: (function() { + var counter = 0; + + return function( prefix ) { + var guid = (+new Date()).toString( 32 ), + i = 0; + + for ( ; i < 5; i++ ) { + guid += Math.floor( Math.random() * 65535 ).toString( 32 ); + } + + return (prefix || 'wu_') + guid + (counter++).toString( 32 ); + }; + })(), + + /** + * 格式化文件大小, 输出成带单位的字符串 + * @method formatSize + * @grammar Base.formatSize( size ) => String + * @grammar Base.formatSize( size, pointLength ) => String + * @grammar Base.formatSize( size, pointLength, units ) => String + * @param {Number} size 文件大小 + * @param {Number} [pointLength=2] 精确到的小数点数。 + * @param {Array} [units=[ 'B', 'K', 'M', 'G', 'TB' ]] 单位数组。从字节,到千字节,一直往上指定。如果单位数组里面只指定了到了K(千字节),同时文件大小大于M, 此方法的输出将还是显示成多少K. + * @example + * console.log( Base.formatSize( 100 ) ); // => 100B + * console.log( Base.formatSize( 1024 ) ); // => 1.00K + * console.log( Base.formatSize( 1024, 0 ) ); // => 1K + * console.log( Base.formatSize( 1024 * 1024 ) ); // => 1.00M + * console.log( Base.formatSize( 1024 * 1024 * 1024 ) ); // => 1.00G + * console.log( Base.formatSize( 1024 * 1024 * 1024, 0, ['B', 'KB', 'MB'] ) ); // => 1024MB + */ + formatSize: function( size, pointLength, units ) { + var unit; + + units = units || [ 'B', 'K', 'M', 'G', 'TB' ]; + + while ( (unit = units.shift()) && size > 1024 ) { + size = size / 1024; + } + + return (unit === 'B' ? size : size.toFixed( pointLength || 2 )) + + unit; + } + }; + }); + /** + * 事件处理类,可以独立使用,也可以扩展给对象使用。 + * @fileOverview Mediator + */ + define('mediator',[ + 'base' + ], function( Base ) { + var $ = Base.$, + slice = [].slice, + separator = /\s+/, + protos; + + // 根据条件过滤出事件handlers. + function findHandlers( arr, name, callback, context ) { + return $.grep( arr, function( handler ) { + return handler && + (!name || handler.e === name) && + (!callback || handler.cb === callback || + handler.cb._cb === callback) && + (!context || handler.ctx === context); + }); + } + + function eachEvent( events, callback, iterator ) { + // 不支持对象,只支持多个event用空格隔开 + $.each( (events || '').split( separator ), function( _, key ) { + iterator( key, callback ); + }); + } + + function triggerHanders( events, args ) { + var stoped = false, + i = -1, + len = events.length, + handler; + + while ( ++i < len ) { + handler = events[ i ]; + + if ( handler.cb.apply( handler.ctx2, args ) === false ) { + stoped = true; + break; + } + } + + return !stoped; + } + + protos = { + + /** + * 绑定事件。 + * + * `callback`方法在执行时,arguments将会来源于trigger的时候携带的参数。如 + * ```javascript + * var obj = {}; + * + * // 使得obj有事件行为 + * Mediator.installTo( obj ); + * + * obj.on( 'testa', function( arg1, arg2 ) { + * console.log( arg1, arg2 ); // => 'arg1', 'arg2' + * }); + * + * obj.trigger( 'testa', 'arg1', 'arg2' ); + * ``` + * + * 如果`callback`中,某一个方法`return false`了,则后续的其他`callback`都不会被执行到。 + * 切会影响到`trigger`方法的返回值,为`false`。 + * + * `on`还可以用来添加一个特殊事件`all`, 这样所有的事件触发都会响应到。同时此类`callback`中的arguments有一个不同处, + * 就是第一个参数为`type`,记录当前是什么事件在触发。此类`callback`的优先级比脚低,会再正常`callback`执行完后触发。 + * ```javascript + * obj.on( 'all', function( type, arg1, arg2 ) { + * console.log( type, arg1, arg2 ); // => 'testa', 'arg1', 'arg2' + * }); + * ``` + * + * @method on + * @grammar on( name, callback[, context] ) => self + * @param {String} name 事件名,支持多个事件用空格隔开 + * @param {Function} callback 事件处理器 + * @param {Object} [context] 事件处理器的上下文。 + * @return {self} 返回自身,方便链式 + * @chainable + * @class Mediator + */ + on: function( name, callback, context ) { + var me = this, + set; + + if ( !callback ) { + return this; + } + + set = this._events || (this._events = []); + + eachEvent( name, callback, function( name, callback ) { + var handler = { e: name }; + + handler.cb = callback; + handler.ctx = context; + handler.ctx2 = context || me; + handler.id = set.length; + + set.push( handler ); + }); + + return this; + }, + + /** + * 绑定事件,且当handler执行完后,自动解除绑定。 + * @method once + * @grammar once( name, callback[, context] ) => self + * @param {String} name 事件名 + * @param {Function} callback 事件处理器 + * @param {Object} [context] 事件处理器的上下文。 + * @return {self} 返回自身,方便链式 + * @chainable + */ + once: function( name, callback, context ) { + var me = this; + + if ( !callback ) { + return me; + } + + eachEvent( name, callback, function( name, callback ) { + var once = function() { + me.off( name, once ); + return callback.apply( context || me, arguments ); + }; + + once._cb = callback; + me.on( name, once, context ); + }); + + return me; + }, + + /** + * 解除事件绑定 + * @method off + * @grammar off( [name[, callback[, context] ] ] ) => self + * @param {String} [name] 事件名 + * @param {Function} [callback] 事件处理器 + * @param {Object} [context] 事件处理器的上下文。 + * @return {self} 返回自身,方便链式 + * @chainable + */ + off: function( name, cb, ctx ) { + var events = this._events; + + if ( !events ) { + return this; + } + + if ( !name && !cb && !ctx ) { + this._events = []; + return this; + } + + eachEvent( name, cb, function( name, cb ) { + $.each( findHandlers( events, name, cb, ctx ), function() { + delete events[ this.id ]; + }); + }); + + return this; + }, + + /** + * 触发事件 + * @method trigger + * @grammar trigger( name[, args...] ) => self + * @param {String} type 事件名 + * @param {*} [...] 任意参数 + * @return {Boolean} 如果handler中return false了,则返回false, 否则返回true + */ + trigger: function( type ) { + var args, events, allEvents; + + if ( !this._events || !type ) { + return this; + } + + args = slice.call( arguments, 1 ); + events = findHandlers( this._events, type ); + allEvents = findHandlers( this._events, 'all' ); + + return triggerHanders( events, args ) && + triggerHanders( allEvents, arguments ); + } + }; + + /** + * 中介者,它本身是个单例,但可以通过[installTo](#WebUploader:Mediator:installTo)方法,使任何对象具备事件行为。 + * 主要目的是负责模块与模块之间的合作,降低耦合度。 + * + * @class Mediator + */ + return $.extend({ + + /** + * 可以通过这个接口,使任何对象具备事件功能。 + * @method installTo + * @param {Object} obj 需要具备事件行为的对象。 + * @return {Object} 返回obj. + */ + installTo: function( obj ) { + return $.extend( obj, protos ); + } + + }, protos ); + }); + /** + * @fileOverview Uploader上传类 + */ + define('uploader',[ + 'base', + 'mediator' + ], function( Base, Mediator ) { + + var $ = Base.$; + + /** + * 上传入口类。 + * @class Uploader + * @constructor + * @grammar new Uploader( opts ) => Uploader + * @example + * var uploader = WebUploader.Uploader({ + * swf: 'path_of_swf/Uploader.swf', + * + * // 开起分片上传。 + * chunked: true + * }); + */ + function Uploader( opts ) { + this.options = $.extend( true, {}, Uploader.options, opts ); + this._init( this.options ); + } + + // default Options + // widgets中有相应扩展 + Uploader.options = {}; + Mediator.installTo( Uploader.prototype ); + + // 批量添加纯命令式方法。 + $.each({ + upload: 'start-upload', + stop: 'stop-upload', + getFile: 'get-file', + getFiles: 'get-files', + addFile: 'add-file', + addFiles: 'add-file', + sort: 'sort-files', + removeFile: 'remove-file', + cancelFile: 'cancel-file', + skipFile: 'skip-file', + retry: 'retry', + isInProgress: 'is-in-progress', + makeThumb: 'make-thumb', + md5File: 'md5-file', + getDimension: 'get-dimension', + addButton: 'add-btn', + predictRuntimeType: 'predict-runtime-type', + refresh: 'refresh', + disable: 'disable', + enable: 'enable', + reset: 'reset' + }, function( fn, command ) { + Uploader.prototype[ fn ] = function() { + return this.request( command, arguments ); + }; + }); + + $.extend( Uploader.prototype, { + state: 'pending', + + _init: function( opts ) { + var me = this; + + me.request( 'init', opts, function() { + me.state = 'ready'; + me.trigger('ready'); + }); + }, + + /** + * 获取或者设置Uploader配置项。 + * @method option + * @grammar option( key ) => * + * @grammar option( key, val ) => self + * @example + * + * // 初始状态图片上传前不会压缩 + * var uploader = new WebUploader.Uploader({ + * compress: null; + * }); + * + * // 修改后图片上传前,尝试将图片压缩到1600 * 1600 + * uploader.option( 'compress', { + * width: 1600, + * height: 1600 + * }); + */ + option: function( key, val ) { + var opts = this.options; + + // setter + if ( arguments.length > 1 ) { + + if ( $.isPlainObject( val ) && + $.isPlainObject( opts[ key ] ) ) { + $.extend( opts[ key ], val ); + } else { + opts[ key ] = val; + } + + } else { // getter + return key ? opts[ key ] : opts; + } + }, + + /** + * 获取文件统计信息。返回一个包含一下信息的对象。 + * * `successNum` 上传成功的文件数 + * * `progressNum` 上传中的文件数 + * * `cancelNum` 被删除的文件数 + * * `invalidNum` 无效的文件数 + * * `uploadFailNum` 上传失败的文件数 + * * `queueNum` 还在队列中的文件数 + * * `interruptNum` 被暂停的文件数 + * @method getStats + * @grammar getStats() => Object + */ + getStats: function() { + // return this._mgr.getStats.apply( this._mgr, arguments ); + var stats = this.request('get-stats'); + + return stats ? { + successNum: stats.numOfSuccess, + progressNum: stats.numOfProgress, + + // who care? + // queueFailNum: 0, + cancelNum: stats.numOfCancel, + invalidNum: stats.numOfInvalid, + uploadFailNum: stats.numOfUploadFailed, + queueNum: stats.numOfQueue, + interruptNum: stats.numofInterrupt + } : {}; + }, + + // 需要重写此方法来来支持opts.onEvent和instance.onEvent的处理器 + trigger: function( type/*, args...*/ ) { + var args = [].slice.call( arguments, 1 ), + opts = this.options, + name = 'on' + type.substring( 0, 1 ).toUpperCase() + + type.substring( 1 ); + + if ( + // 调用通过on方法注册的handler. + Mediator.trigger.apply( this, arguments ) === false || + + // 调用opts.onEvent + $.isFunction( opts[ name ] ) && + opts[ name ].apply( this, args ) === false || + + // 调用this.onEvent + $.isFunction( this[ name ] ) && + this[ name ].apply( this, args ) === false || + + // 广播所有uploader的事件。 + Mediator.trigger.apply( Mediator, + [ this, type ].concat( args ) ) === false ) { + + return false; + } + + return true; + }, + + /** + * 销毁 webuploader 实例 + * @method destroy + * @grammar destroy() => undefined + */ + destroy: function() { + this.request( 'destroy', arguments ); + this.off(); + }, + + // widgets/widget.js将补充此方法的详细文档。 + request: Base.noop + }); + + /** + * 创建Uploader实例,等同于new Uploader( opts ); + * @method create + * @class Base + * @static + * @grammar Base.create( opts ) => Uploader + */ + Base.create = Uploader.create = function( opts ) { + return new Uploader( opts ); + }; + + // 暴露Uploader,可以通过它来扩展业务逻辑。 + Base.Uploader = Uploader; + + return Uploader; + }); + /** + * @fileOverview Runtime管理器,负责Runtime的选择, 连接 + */ + define('runtime/runtime',[ + 'base', + 'mediator' + ], function( Base, Mediator ) { + + var $ = Base.$, + factories = {}, + + // 获取对象的第一个key + getFirstKey = function( obj ) { + for ( var key in obj ) { + if ( obj.hasOwnProperty( key ) ) { + return key; + } + } + return null; + }; + + // 接口类。 + function Runtime( options ) { + this.options = $.extend({ + container: document.body + }, options ); + this.uid = Base.guid('rt_'); + } + + $.extend( Runtime.prototype, { + + getContainer: function() { + var opts = this.options, + parent, container; + + if ( this._container ) { + return this._container; + } + + parent = $( opts.container || document.body ); + container = $( document.createElement('div') ); + + container.attr( 'id', 'rt_' + this.uid ); + container.css({ + position: 'absolute', + top: '0px', + left: '0px', + width: '1px', + height: '1px', + overflow: 'hidden' + }); + + parent.append( container ); + parent.addClass('webuploader-container'); + this._container = container; + this._parent = parent; + return container; + }, + + init: Base.noop, + exec: Base.noop, + + destroy: function() { + this._container && this._container.remove(); + this._parent && this._parent.removeClass('webuploader-container'); + this.off(); + } + }); + + Runtime.orders = 'html5,flash'; + + + /** + * 添加Runtime实现。 + * @param {String} type 类型 + * @param {Runtime} factory 具体Runtime实现。 + */ + Runtime.addRuntime = function( type, factory ) { + factories[ type ] = factory; + }; + + Runtime.hasRuntime = function( type ) { + return !!(type ? factories[ type ] : getFirstKey( factories )); + }; + + Runtime.create = function( opts, orders ) { + var type, runtime; + + orders = orders || Runtime.orders; + $.each( orders.split( /\s*,\s*/g ), function() { + if ( factories[ this ] ) { + type = this; + return false; + } + }); + + type = type || getFirstKey( factories ); + + if ( !type ) { + throw new Error('Runtime Error'); + } + + runtime = new factories[ type ]( opts ); + return runtime; + }; + + Mediator.installTo( Runtime.prototype ); + return Runtime; + }); + + /** + * @fileOverview Runtime管理器,负责Runtime的选择, 连接 + */ + define('runtime/client',[ + 'base', + 'mediator', + 'runtime/runtime' + ], function( Base, Mediator, Runtime ) { + + var cache; + + cache = (function() { + var obj = {}; + + return { + add: function( runtime ) { + obj[ runtime.uid ] = runtime; + }, + + get: function( ruid, standalone ) { + var i; + + if ( ruid ) { + return obj[ ruid ]; + } + + for ( i in obj ) { + // 有些类型不能重用,比如filepicker. + if ( standalone && obj[ i ].__standalone ) { + continue; + } + + return obj[ i ]; + } + + return null; + }, + + remove: function( runtime ) { + delete obj[ runtime.uid ]; + } + }; + })(); + + function RuntimeClient( component, standalone ) { + var deferred = Base.Deferred(), + runtime; + + this.uid = Base.guid('client_'); + + // 允许runtime没有初始化之前,注册一些方法在初始化后执行。 + this.runtimeReady = function( cb ) { + return deferred.done( cb ); + }; + + this.connectRuntime = function( opts, cb ) { + + // already connected. + if ( runtime ) { + throw new Error('already connected!'); + } + + deferred.done( cb ); + + if ( typeof opts === 'string' && cache.get( opts ) ) { + runtime = cache.get( opts ); + } + + // 像filePicker只能独立存在,不能公用。 + runtime = runtime || cache.get( null, standalone ); + + // 需要创建 + if ( !runtime ) { + runtime = Runtime.create( opts, opts.runtimeOrder ); + runtime.__promise = deferred.promise(); + runtime.once( 'ready', deferred.resolve ); + runtime.init(); + cache.add( runtime ); + runtime.__client = 1; + } else { + // 来自cache + Base.$.extend( runtime.options, opts ); + runtime.__promise.then( deferred.resolve ); + runtime.__client++; + } + + standalone && (runtime.__standalone = standalone); + return runtime; + }; + + this.getRuntime = function() { + return runtime; + }; + + this.disconnectRuntime = function() { + if ( !runtime ) { + return; + } + + runtime.__client--; + + if ( runtime.__client <= 0 ) { + cache.remove( runtime ); + delete runtime.__promise; + runtime.destroy(); + } + + runtime = null; + }; + + this.exec = function() { + if ( !runtime ) { + return; + } + + var args = Base.slice( arguments ); + component && args.unshift( component ); + + return runtime.exec.apply( this, args ); + }; + + this.getRuid = function() { + return runtime && runtime.uid; + }; + + this.destroy = (function( destroy ) { + return function() { + destroy && destroy.apply( this, arguments ); + this.trigger('destroy'); + this.off(); + this.exec('destroy'); + this.disconnectRuntime(); + }; + })( this.destroy ); + } + + Mediator.installTo( RuntimeClient.prototype ); + return RuntimeClient; + }); + /** + * @fileOverview Blob + */ + define('lib/blob',[ + 'base', + 'runtime/client' + ], function( Base, RuntimeClient ) { + + function Blob( ruid, source ) { + var me = this; + + me.source = source; + me.ruid = ruid; + this.size = source.size || 0; + + // 如果没有指定 mimetype, 但是知道文件后缀。 + if ( !source.type && this.ext && + ~'jpg,jpeg,png,gif,bmp'.indexOf( this.ext ) ) { + this.type = 'image/' + (this.ext === 'jpg' ? 'jpeg' : this.ext); + } else { + this.type = source.type || 'application/octet-stream'; + } + + RuntimeClient.call( me, 'Blob' ); + this.uid = source.uid || this.uid; + + if ( ruid ) { + me.connectRuntime( ruid ); + } + } + + Base.inherits( RuntimeClient, { + constructor: Blob, + + slice: function( start, end ) { + return this.exec( 'slice', start, end ); + }, + + getSource: function() { + return this.source; + } + }); + + return Blob; + }); + /** + * 为了统一化Flash的File和HTML5的File而存在。 + * 以至于要调用Flash里面的File,也可以像调用HTML5版本的File一下。 + * @fileOverview File + */ + define('lib/file',[ + 'base', + 'lib/blob' + ], function( Base, Blob ) { + + var uid = 1, + rExt = /\.([^.]+)$/; + + function File( ruid, file ) { + var ext; + + this.name = file.name || ('untitled' + uid++); + ext = rExt.exec( file.name ) ? RegExp.$1.toLowerCase() : ''; + + // todo 支持其他类型文件的转换。 + // 如果有 mimetype, 但是文件名里面没有找出后缀规律 + if ( !ext && file.type ) { + ext = /\/(jpg|jpeg|png|gif|bmp)$/i.exec( file.type ) ? + RegExp.$1.toLowerCase() : ''; + this.name += '.' + ext; + } + + this.ext = ext; + this.lastModifiedDate = file.lastModifiedDate || + (new Date()).toLocaleString(); + + Blob.apply( this, arguments ); + } + + return Base.inherits( Blob, File ); + }); + + /** + * @fileOverview 错误信息 + */ + define('lib/filepicker',[ + 'base', + 'runtime/client', + 'lib/file' + ], function( Base, RuntimeClent, File ) { + + var $ = Base.$; + + function FilePicker( opts ) { + opts = this.options = $.extend({}, FilePicker.options, opts ); + + opts.container = $( opts.id ); + + if ( !opts.container.length ) { + throw new Error('按钮指定错误'); + } + + opts.innerHTML = opts.innerHTML || opts.label || + opts.container.html() || ''; + + opts.button = $( opts.button || document.createElement('div') ); + opts.button.html( opts.innerHTML ); + opts.container.html( opts.button ); + + RuntimeClent.call( this, 'FilePicker', true ); + } + + FilePicker.options = { + button: null, + container: null, + label: null, + innerHTML: null, + multiple: true, + accept: null, + name: 'file' + }; + + Base.inherits( RuntimeClent, { + constructor: FilePicker, + + init: function() { + var me = this, + opts = me.options, + button = opts.button; + + button.addClass('webuploader-pick'); + + me.on( 'all', function( type ) { + var files; + + switch ( type ) { + case 'mouseenter': + button.addClass('webuploader-pick-hover'); + break; + + case 'mouseleave': + button.removeClass('webuploader-pick-hover'); + break; + + case 'change': + files = me.exec('getFiles'); + me.trigger( 'select', $.map( files, function( file ) { + file = new File( me.getRuid(), file ); + + // 记录来源。 + file._refer = opts.container; + return file; + }), opts.container ); + break; + } + }); + + me.connectRuntime( opts, function() { + me.refresh(); + me.exec( 'init', opts ); + me.trigger('ready'); + }); + + this._resizeHandler = Base.bindFn( this.refresh, this ); + $( window ).on( 'resize', this._resizeHandler ); + }, + + refresh: function() { + var shimContainer = this.getRuntime().getContainer(), + button = this.options.button, + width = button.outerWidth ? + button.outerWidth() : button.width(), + + height = button.outerHeight ? + button.outerHeight() : button.height(), + + pos = button.offset(); + + width && height && shimContainer.css({ + bottom: 'auto', + right: 'auto', + width: width + 'px', + height: height + 'px' + }).offset( pos ); + }, + + enable: function() { + var btn = this.options.button; + + btn.removeClass('webuploader-pick-disable'); + this.refresh(); + }, + + disable: function() { + var btn = this.options.button; + + this.getRuntime().getContainer().css({ + top: '-99999px' + }); + + btn.addClass('webuploader-pick-disable'); + }, + + destroy: function() { + var btn = this.options.button; + $( window ).off( 'resize', this._resizeHandler ); + btn.removeClass('webuploader-pick-disable webuploader-pick-hover ' + + 'webuploader-pick'); + } + }); + + return FilePicker; + }); + + /** + * @fileOverview 组件基类。 + */ + define('widgets/widget',[ + 'base', + 'uploader' + ], function( Base, Uploader ) { + + var $ = Base.$, + _init = Uploader.prototype._init, + _destroy = Uploader.prototype.destroy, + IGNORE = {}, + widgetClass = []; + + function isArrayLike( obj ) { + if ( !obj ) { + return false; + } + + var length = obj.length, + type = $.type( obj ); + + if ( obj.nodeType === 1 && length ) { + return true; + } + + return type === 'array' || type !== 'function' && type !== 'string' && + (length === 0 || typeof length === 'number' && length > 0 && + (length - 1) in obj); + } + + function Widget( uploader ) { + this.owner = uploader; + this.options = uploader.options; + } + + $.extend( Widget.prototype, { + + init: Base.noop, + + // 类Backbone的事件监听声明,监听uploader实例上的事件 + // widget直接无法监听事件,事件只能通过uploader来传递 + invoke: function( apiName, args ) { + + /* + { + 'make-thumb': 'makeThumb' + } + */ + var map = this.responseMap; + + // 如果无API响应声明则忽略 + if ( !map || !(apiName in map) || !(map[ apiName ] in this) || + !$.isFunction( this[ map[ apiName ] ] ) ) { + + return IGNORE; + } + + return this[ map[ apiName ] ].apply( this, args ); + + }, + + /** + * 发送命令。当传入`callback`或者`handler`中返回`promise`时。返回一个当所有`handler`中的promise都完成后完成的新`promise`。 + * @method request + * @grammar request( command, args ) => * | Promise + * @grammar request( command, args, callback ) => Promise + * @for Uploader + */ + request: function() { + return this.owner.request.apply( this.owner, arguments ); + } + }); + + // 扩展Uploader. + $.extend( Uploader.prototype, { + + /** + * @property {String | Array} [disableWidgets=undefined] + * @namespace options + * @for Uploader + * @description 默认所有 Uploader.register 了的 widget 都会被加载,如果禁用某一部分,请通过此 option 指定黑名单。 + */ + + // 覆写_init用来初始化widgets + _init: function() { + var me = this, + widgets = me._widgets = [], + deactives = me.options.disableWidgets || ''; + + $.each( widgetClass, function( _, klass ) { + (!deactives || !~deactives.indexOf( klass._name )) && + widgets.push( new klass( me ) ); + }); + + return _init.apply( me, arguments ); + }, + + request: function( apiName, args, callback ) { + var i = 0, + widgets = this._widgets, + len = widgets && widgets.length, + rlts = [], + dfds = [], + widget, rlt, promise, key; + + args = isArrayLike( args ) ? args : [ args ]; + + for ( ; i < len; i++ ) { + widget = widgets[ i ]; + rlt = widget.invoke( apiName, args ); + + if ( rlt !== IGNORE ) { + + // Deferred对象 + if ( Base.isPromise( rlt ) ) { + dfds.push( rlt ); + } else { + rlts.push( rlt ); + } + } + } + + // 如果有callback,则用异步方式。 + if ( callback || dfds.length ) { + promise = Base.when.apply( Base, dfds ); + key = promise.pipe ? 'pipe' : 'then'; + + // 很重要不能删除。删除了会死循环。 + // 保证执行顺序。让callback总是在下一个 tick 中执行。 + return promise[ key ](function() { + var deferred = Base.Deferred(), + args = arguments; + + if ( args.length === 1 ) { + args = args[ 0 ]; + } + + setTimeout(function() { + deferred.resolve( args ); + }, 1 ); + + return deferred.promise(); + })[ callback ? key : 'done' ]( callback || Base.noop ); + } else { + return rlts[ 0 ]; + } + }, + + destroy: function() { + _destroy.apply( this, arguments ); + this._widgets = null; + } + }); + + /** + * 添加组件 + * @grammar Uploader.register(proto); + * @grammar Uploader.register(map, proto); + * @param {object} responseMap API 名称与函数实现的映射 + * @param {object} proto 组件原型,构造函数通过 constructor 属性定义 + * @method Uploader.register + * @for Uploader + * @example + * Uploader.register({ + * 'make-thumb': 'makeThumb' + * }, { + * init: function( options ) {}, + * makeThumb: function() {} + * }); + * + * Uploader.register({ + * 'make-thumb': function() { + * + * } + * }); + */ + Uploader.register = Widget.register = function( responseMap, widgetProto ) { + var map = { init: 'init', destroy: 'destroy', name: 'anonymous' }, + klass; + + if ( arguments.length === 1 ) { + widgetProto = responseMap; + + // 自动生成 map 表。 + $.each(widgetProto, function(key) { + if ( key[0] === '_' || key === 'name' ) { + key === 'name' && (map.name = widgetProto.name); + return; + } + + map[key.replace(/[A-Z]/g, '-$&').toLowerCase()] = key; + }); + + } else { + map = $.extend( map, responseMap ); + } + + widgetProto.responseMap = map; + klass = Base.inherits( Widget, widgetProto ); + klass._name = map.name; + widgetClass.push( klass ); + + return klass; + }; + + /** + * 删除插件,只有在注册时指定了名字的才能被删除。 + * @grammar Uploader.unRegister(name); + * @param {string} name 组件名字 + * @method Uploader.unRegister + * @for Uploader + * @example + * + * Uploader.register({ + * name: 'custom', + * + * 'make-thumb': function() { + * + * } + * }); + * + * Uploader.unRegister('custom'); + */ + Uploader.unRegister = Widget.unRegister = function( name ) { + if ( !name || name === 'anonymous' ) { + return; + } + + // 删除指定的插件。 + for ( var i = widgetClass.length; i--; ) { + if ( widgetClass[i]._name === name ) { + widgetClass.splice(i, 1) + } + } + }; + + return Widget; + }); + /** + * @fileOverview 文件选择相关 + */ + define('widgets/filepicker',[ + 'base', + 'uploader', + 'lib/filepicker', + 'widgets/widget' + ], function( Base, Uploader, FilePicker ) { + var $ = Base.$; + + $.extend( Uploader.options, { + + /** + * @property {Selector | Object} [pick=undefined] + * @namespace options + * @for Uploader + * @description 指定选择文件的按钮容器,不指定则不创建按钮。 + * + * * `id` {Seletor|dom} 指定选择文件的按钮容器,不指定则不创建按钮。**注意** 这里虽然写的是 id, 但是不是只支持 id, 还支持 class, 或者 dom 节点。 + * * `label` {String} 请采用 `innerHTML` 代替 + * * `innerHTML` {String} 指定按钮文字。不指定时优先从指定的容器中看是否自带文字。 + * * `multiple` {Boolean} 是否开起同时选择多个文件能力。 + */ + pick: null, + + /** + * @property {Arroy} [accept=null] + * @namespace options + * @for Uploader + * @description 指定接受哪些类型的文件。 由于目前还有ext转mimeType表,所以这里需要分开指定。 + * + * * `title` {String} 文字描述 + * * `extensions` {String} 允许的文件后缀,不带点,多个用逗号分割。 + * * `mimeTypes` {String} 多个用逗号分割。 + * + * 如: + * + * ``` + * { + * title: 'Images', + * extensions: 'gif,jpg,jpeg,bmp,png', + * mimeTypes: 'image/*' + * } + * ``` + */ + accept: null/*{ + title: 'Images', + extensions: 'gif,jpg,jpeg,bmp,png', + mimeTypes: 'image/*' + }*/ + }); + + return Uploader.register({ + name: 'picker', + + init: function( opts ) { + this.pickers = []; + return opts.pick && this.addBtn( opts.pick ); + }, + + refresh: function() { + $.each( this.pickers, function() { + this.refresh(); + }); + }, + + /** + * @method addButton + * @for Uploader + * @grammar addButton( pick ) => Promise + * @description + * 添加文件选择按钮,如果一个按钮不够,需要调用此方法来添加。参数跟[options.pick](#WebUploader:Uploader:options)一致。 + * @example + * uploader.addButton({ + * id: '#btnContainer', + * innerHTML: '选择文件' + * }); + */ + addBtn: function( pick ) { + var me = this, + opts = me.options, + accept = opts.accept, + promises = []; + + if ( !pick ) { + return; + } + + $.isPlainObject( pick ) || (pick = { + id: pick + }); + + $( pick.id ).each(function() { + var options, picker, deferred; + + deferred = Base.Deferred(); + + options = $.extend({}, pick, { + accept: $.isPlainObject( accept ) ? [ accept ] : accept, + swf: opts.swf, + runtimeOrder: opts.runtimeOrder, + id: this + }); + + picker = new FilePicker( options ); + + picker.once( 'ready', deferred.resolve ); + picker.on( 'select', function( files ) { + me.owner.request( 'add-file', [ files ]); + }); + picker.init(); + + me.pickers.push( picker ); + + promises.push( deferred.promise() ); + }); + + return Base.when.apply( Base, promises ); + }, + + disable: function() { + $.each( this.pickers, function() { + this.disable(); + }); + }, + + enable: function() { + $.each( this.pickers, function() { + this.enable(); + }); + }, + + destroy: function() { + $.each( this.pickers, function() { + this.destroy(); + }); + this.pickers = null; + } + }); + }); + /** + * @fileOverview Image + */ + define('lib/image',[ + 'base', + 'runtime/client', + 'lib/blob' + ], function( Base, RuntimeClient, Blob ) { + var $ = Base.$; + + // 构造器。 + function Image( opts ) { + this.options = $.extend({}, Image.options, opts ); + RuntimeClient.call( this, 'Image' ); + + this.on( 'load', function() { + this._info = this.exec('info'); + this._meta = this.exec('meta'); + }); + } + + // 默认选项。 + Image.options = { + + // 默认的图片处理质量 + quality: 90, + + // 是否裁剪 + crop: false, + + // 是否保留头部信息 + preserveHeaders: false, + + // 是否允许放大。 + allowMagnify: false + }; + + // 继承RuntimeClient. + Base.inherits( RuntimeClient, { + constructor: Image, + + info: function( val ) { + + // setter + if ( val ) { + this._info = val; + return this; + } + + // getter + return this._info; + }, + + meta: function( val ) { + + // setter + if ( val ) { + this._meta = val; + return this; + } + + // getter + return this._meta; + }, + + loadFromBlob: function( blob ) { + var me = this, + ruid = blob.getRuid(); + + this.connectRuntime( ruid, function() { + me.exec( 'init', me.options ); + me.exec( 'loadFromBlob', blob ); + }); + }, + + resize: function() { + var args = Base.slice( arguments ); + return this.exec.apply( this, [ 'resize' ].concat( args ) ); + }, + + crop: function() { + var args = Base.slice( arguments ); + return this.exec.apply( this, [ 'crop' ].concat( args ) ); + }, + + getAsDataUrl: function( type ) { + return this.exec( 'getAsDataUrl', type ); + }, + + getAsBlob: function( type ) { + var blob = this.exec( 'getAsBlob', type ); + + return new Blob( this.getRuid(), blob ); + } + }); + + return Image; + }); + /** + * @fileOverview 图片操作, 负责预览图片和上传前压缩图片 + */ + define('widgets/image',[ + 'base', + 'uploader', + 'lib/image', + 'widgets/widget' + ], function( Base, Uploader, Image ) { + + var $ = Base.$, + throttle; + + // 根据要处理的文件大小来节流,一次不能处理太多,会卡。 + throttle = (function( max ) { + var occupied = 0, + waiting = [], + tick = function() { + var item; + + while ( waiting.length && occupied < max ) { + item = waiting.shift(); + occupied += item[ 0 ]; + item[ 1 ](); + } + }; + + return function( emiter, size, cb ) { + waiting.push([ size, cb ]); + emiter.once( 'destroy', function() { + occupied -= size; + setTimeout( tick, 1 ); + }); + setTimeout( tick, 1 ); + }; + })( 5 * 1024 * 1024 ); + + $.extend( Uploader.options, { + + /** + * @property {Object} [thumb] + * @namespace options + * @for Uploader + * @description 配置生成缩略图的选项。 + * + * 默认为: + * + * ```javascript + * { + * width: 110, + * height: 110, + * + * // 图片质量,只有type为`image/jpeg`的时候才有效。 + * quality: 70, + * + * // 是否允许放大,如果想要生成小图的时候不失真,此选项应该设置为false. + * allowMagnify: true, + * + * // 是否允许裁剪。 + * crop: true, + * + * // 为空的话则保留原有图片格式。 + * // 否则强制转换成指定的类型。 + * type: 'image/jpeg' + * } + * ``` + */ + thumb: { + width: 110, + height: 110, + quality: 70, + allowMagnify: true, + crop: true, + preserveHeaders: false, + + // 为空的话则保留原有图片格式。 + // 否则强制转换成指定的类型。 + // IE 8下面 base64 大小不能超过 32K 否则预览失败,而非 jpeg 编码的图片很可 + // 能会超过 32k, 所以这里设置成预览的时候都是 image/jpeg + type: 'image/jpeg' + }, + + /** + * @property {Object} [compress] + * @namespace options + * @for Uploader + * @description 配置压缩的图片的选项。如果此选项为`false`, 则图片在上传前不进行压缩。 + * + * 默认为: + * + * ```javascript + * { + * width: 1600, + * height: 1600, + * + * // 图片质量,只有type为`image/jpeg`的时候才有效。 + * quality: 90, + * + * // 是否允许放大,如果想要生成小图的时候不失真,此选项应该设置为false. + * allowMagnify: false, + * + * // 是否允许裁剪。 + * crop: false, + * + * // 是否保留头部meta信息。 + * preserveHeaders: true, + * + * // 如果发现压缩后文件大小比原来还大,则使用原来图片 + * // 此属性可能会影响图片自动纠正功能 + * noCompressIfLarger: false, + * + * // 单位字节,如果图片大小小于此值,不会采用压缩。 + * compressSize: 0 + * } + * ``` + */ + compress: { + width: 1600, + height: 1600, + quality: 90, + allowMagnify: false, + crop: false, + preserveHeaders: true + } + }); + + return Uploader.register({ + + name: 'image', + + + /** + * 生成缩略图,此过程为异步,所以需要传入`callback`。 + * 通常情况在图片加入队里后调用此方法来生成预览图以增强交互效果。 + * + * 当 width 或者 height 的值介于 0 - 1 时,被当成百分比使用。 + * + * `callback`中可以接收到两个参数。 + * * 第一个为error,如果生成缩略图有错误,此error将为真。 + * * 第二个为ret, 缩略图的Data URL值。 + * + * **注意** + * Date URL在IE6/7中不支持,所以不用调用此方法了,直接显示一张暂不支持预览图片好了。 + * 也可以借助服务端,将 base64 数据传给服务端,生成一个临时文件供预览。 + * + * @method makeThumb + * @grammar makeThumb( file, callback ) => undefined + * @grammar makeThumb( file, callback, width, height ) => undefined + * @for Uploader + * @example + * + * uploader.on( 'fileQueued', function( file ) { + * var $li = ...; + * + * uploader.makeThumb( file, function( error, ret ) { + * if ( error ) { + * $li.text('预览错误'); + * } else { + * $li.append(''); + * } + * }); + * + * }); + */ + makeThumb: function( file, cb, width, height ) { + var opts, image; + + file = this.request( 'get-file', file ); + + // 只预览图片格式。 + if ( !file.type.match( /^image/ ) ) { + cb( true ); + return; + } + + opts = $.extend({}, this.options.thumb ); + + // 如果传入的是object. + if ( $.isPlainObject( width ) ) { + opts = $.extend( opts, width ); + width = null; + } + + width = width || opts.width; + height = height || opts.height; + + image = new Image( opts ); + + image.once( 'load', function() { + file._info = file._info || image.info(); + file._meta = file._meta || image.meta(); + + // 如果 width 的值介于 0 - 1 + // 说明设置的是百分比。 + if ( width <= 1 && width > 0 ) { + width = file._info.width * width; + } + + // 同样的规则应用于 height + if ( height <= 1 && height > 0 ) { + height = file._info.height * height; + } + + image.resize( width, height ); + }); + + // 当 resize 完后 + image.once( 'complete', function() { + cb( false, image.getAsDataUrl( opts.type ) ); + image.destroy(); + }); + + image.once( 'error', function( reason ) { + cb( reason || true ); + image.destroy(); + }); + + throttle( image, file.source.size, function() { + file._info && image.info( file._info ); + file._meta && image.meta( file._meta ); + image.loadFromBlob( file.source ); + }); + }, + + beforeSendFile: function( file ) { + var opts = this.options.compress || this.options.resize, + compressSize = opts && opts.compressSize || 0, + noCompressIfLarger = opts && opts.noCompressIfLarger || false, + image, deferred; + + file = this.request( 'get-file', file ); + + // 只压缩 jpeg 图片格式。 + // gif 可能会丢失针 + // bmp png 基本上尺寸都不大,且压缩比比较小。 + if ( !opts || !~'image/jpeg,image/jpg'.indexOf( file.type ) || + file.size < compressSize || + file._compressed ) { + return; + } + + opts = $.extend({}, opts ); + deferred = Base.Deferred(); + + image = new Image( opts ); + + deferred.always(function() { + image.destroy(); + image = null; + }); + image.once( 'error', deferred.reject ); + image.once( 'load', function() { + var width = opts.width, + height = opts.height; + + file._info = file._info || image.info(); + file._meta = file._meta || image.meta(); + + // 如果 width 的值介于 0 - 1 + // 说明设置的是百分比。 + if ( width <= 1 && width > 0 ) { + width = file._info.width * width; + } + + // 同样的规则应用于 height + if ( height <= 1 && height > 0 ) { + height = file._info.height * height; + } + + image.resize( width, height ); + }); + + image.once( 'complete', function() { + var blob, size; + + // 移动端 UC / qq 浏览器的无图模式下 + // ctx.getImageData 处理大图的时候会报 Exception + // INDEX_SIZE_ERR: DOM Exception 1 + try { + blob = image.getAsBlob( opts.type ); + + size = file.size; + + // 如果压缩后,比原来还大则不用压缩后的。 + if ( !noCompressIfLarger || blob.size < size ) { + // file.source.destroy && file.source.destroy(); + file.source = blob; + file.size = blob.size; + + file.trigger( 'resize', blob.size, size ); + } + + // 标记,避免重复压缩。 + file._compressed = true; + deferred.resolve(); + } catch ( e ) { + // 出错了直接继续,让其上传原始图片 + deferred.resolve(); + } + }); + + file._info && image.info( file._info ); + file._meta && image.meta( file._meta ); + + image.loadFromBlob( file.source ); + return deferred.promise(); + } + }); + }); + /** + * @fileOverview 文件属性封装 + */ + define('file',[ + 'base', + 'mediator' + ], function( Base, Mediator ) { + + var $ = Base.$, + idPrefix = 'WU_FILE_', + idSuffix = 0, + rExt = /\.([^.]+)$/, + statusMap = {}; + + function gid() { + return idPrefix + idSuffix++; + } + + /** + * 文件类 + * @class File + * @constructor 构造函数 + * @grammar new File( source ) => File + * @param {Lib.File} source [lib.File](#Lib.File)实例, 此source对象是带有Runtime信息的。 + */ + function WUFile( source ) { + + /** + * 文件名,包括扩展名(后缀) + * @property name + * @type {string} + */ + this.name = source.name || 'Untitled'; + + /** + * 文件体积(字节) + * @property size + * @type {uint} + * @default 0 + */ + this.size = source.size || 0; + + /** + * 文件MIMETYPE类型,与文件类型的对应关系请参考[http://t.cn/z8ZnFny](http://t.cn/z8ZnFny) + * @property type + * @type {string} + * @default 'application/octet-stream' + */ + this.type = source.type || 'application/octet-stream'; + + /** + * 文件最后修改日期 + * @property lastModifiedDate + * @type {int} + * @default 当前时间戳 + */ + this.lastModifiedDate = source.lastModifiedDate || (new Date() * 1); + + /** + * 文件ID,每个对象具有唯一ID,与文件名无关 + * @property id + * @type {string} + */ + this.id = gid(); + + /** + * 文件扩展名,通过文件名获取,例如test.png的扩展名为png + * @property ext + * @type {string} + */ + this.ext = rExt.exec( this.name ) ? RegExp.$1 : ''; + + + /** + * 状态文字说明。在不同的status语境下有不同的用途。 + * @property statusText + * @type {string} + */ + this.statusText = ''; + + // 存储文件状态,防止通过属性直接修改 + statusMap[ this.id ] = WUFile.Status.INITED; + + this.source = source; + this.loaded = 0; + + this.on( 'error', function( msg ) { + this.setStatus( WUFile.Status.ERROR, msg ); + }); + } + + $.extend( WUFile.prototype, { + + /** + * 设置状态,状态变化时会触发`change`事件。 + * @method setStatus + * @grammar setStatus( status[, statusText] ); + * @param {File.Status|String} status [文件状态值](#WebUploader:File:File.Status) + * @param {String} [statusText=''] 状态说明,常在error时使用,用http, abort,server等来标记是由于什么原因导致文件错误。 + */ + setStatus: function( status, text ) { + + var prevStatus = statusMap[ this.id ]; + + typeof text !== 'undefined' && (this.statusText = text); + + if ( status !== prevStatus ) { + statusMap[ this.id ] = status; + /** + * 文件状态变化 + * @event statuschange + */ + this.trigger( 'statuschange', status, prevStatus ); + } + + }, + + /** + * 获取文件状态 + * @return {File.Status} + * @example + 文件状态具体包括以下几种类型: + { + // 初始化 + INITED: 0, + // 已入队列 + QUEUED: 1, + // 正在上传 + PROGRESS: 2, + // 上传出错 + ERROR: 3, + // 上传成功 + COMPLETE: 4, + // 上传取消 + CANCELLED: 5 + } + */ + getStatus: function() { + return statusMap[ this.id ]; + }, + + /** + * 获取文件原始信息。 + * @return {*} + */ + getSource: function() { + return this.source; + }, + + destroy: function() { + this.off(); + delete statusMap[ this.id ]; + } + }); + + Mediator.installTo( WUFile.prototype ); + + /** + * 文件状态值,具体包括以下几种类型: + * * `inited` 初始状态 + * * `queued` 已经进入队列, 等待上传 + * * `progress` 上传中 + * * `complete` 上传完成。 + * * `error` 上传出错,可重试 + * * `interrupt` 上传中断,可续传。 + * * `invalid` 文件不合格,不能重试上传。会自动从队列中移除。 + * * `cancelled` 文件被移除。 + * @property {Object} Status + * @namespace File + * @class File + * @static + */ + WUFile.Status = { + INITED: 'inited', // 初始状态 + QUEUED: 'queued', // 已经进入队列, 等待上传 + PROGRESS: 'progress', // 上传中 + ERROR: 'error', // 上传出错,可重试 + COMPLETE: 'complete', // 上传完成。 + CANCELLED: 'cancelled', // 上传取消。 + INTERRUPT: 'interrupt', // 上传中断,可续传。 + INVALID: 'invalid' // 文件不合格,不能重试上传。 + }; + + return WUFile; + }); + + /** + * @fileOverview 文件队列 + */ + define('queue',[ + 'base', + 'mediator', + 'file' + ], function( Base, Mediator, WUFile ) { + + var $ = Base.$, + STATUS = WUFile.Status; + + /** + * 文件队列, 用来存储各个状态中的文件。 + * @class Queue + * @extends Mediator + */ + function Queue() { + + /** + * 统计文件数。 + * * `numOfQueue` 队列中的文件数。 + * * `numOfSuccess` 上传成功的文件数 + * * `numOfCancel` 被取消的文件数 + * * `numOfProgress` 正在上传中的文件数 + * * `numOfUploadFailed` 上传错误的文件数。 + * * `numOfInvalid` 无效的文件数。 + * * `numofDeleted` 被移除的文件数。 + * @property {Object} stats + */ + this.stats = { + numOfQueue: 0, + numOfSuccess: 0, + numOfCancel: 0, + numOfProgress: 0, + numOfUploadFailed: 0, + numOfInvalid: 0, + numofDeleted: 0, + numofInterrupt: 0 + }; + + // 上传队列,仅包括等待上传的文件 + this._queue = []; + + // 存储所有文件 + this._map = {}; + } + + $.extend( Queue.prototype, { + + /** + * 将新文件加入对队列尾部 + * + * @method append + * @param {File} file 文件对象 + */ + append: function( file ) { + this._queue.push( file ); + this._fileAdded( file ); + return this; + }, + + /** + * 将新文件加入对队列头部 + * + * @method prepend + * @param {File} file 文件对象 + */ + prepend: function( file ) { + this._queue.unshift( file ); + this._fileAdded( file ); + return this; + }, + + /** + * 获取文件对象 + * + * @method getFile + * @param {String} fileId 文件ID + * @return {File} + */ + getFile: function( fileId ) { + if ( typeof fileId !== 'string' ) { + return fileId; + } + return this._map[ fileId ]; + }, + + /** + * 从队列中取出一个指定状态的文件。 + * @grammar fetch( status ) => File + * @method fetch + * @param {String} status [文件状态值](#WebUploader:File:File.Status) + * @return {File} [File](#WebUploader:File) + */ + fetch: function( status ) { + var len = this._queue.length, + i, file; + + status = status || STATUS.QUEUED; + + for ( i = 0; i < len; i++ ) { + file = this._queue[ i ]; + + if ( status === file.getStatus() ) { + return file; + } + } + + return null; + }, + + /** + * 对队列进行排序,能够控制文件上传顺序。 + * @grammar sort( fn ) => undefined + * @method sort + * @param {Function} fn 排序方法 + */ + sort: function( fn ) { + if ( typeof fn === 'function' ) { + this._queue.sort( fn ); + } + }, + + /** + * 获取指定类型的文件列表, 列表中每一个成员为[File](#WebUploader:File)对象。 + * @grammar getFiles( [status1[, status2 ...]] ) => Array + * @method getFiles + * @param {String} [status] [文件状态值](#WebUploader:File:File.Status) + */ + getFiles: function() { + var sts = [].slice.call( arguments, 0 ), + ret = [], + i = 0, + len = this._queue.length, + file; + + for ( ; i < len; i++ ) { + file = this._queue[ i ]; + + if ( sts.length && !~$.inArray( file.getStatus(), sts ) ) { + continue; + } + + ret.push( file ); + } + + return ret; + }, + + /** + * 在队列中删除文件。 + * @grammar removeFile( file ) => Array + * @method removeFile + * @param {File} 文件对象。 + */ + removeFile: function( file ) { + var me = this, + existing = this._map[ file.id ]; + + if ( existing ) { + delete this._map[ file.id ]; + file.destroy(); + this.stats.numofDeleted++; + } + }, + + _fileAdded: function( file ) { + var me = this, + existing = this._map[ file.id ]; + + if ( !existing ) { + this._map[ file.id ] = file; + + file.on( 'statuschange', function( cur, pre ) { + me._onFileStatusChange( cur, pre ); + }); + } + }, + + _onFileStatusChange: function( curStatus, preStatus ) { + var stats = this.stats; + + switch ( preStatus ) { + case STATUS.PROGRESS: + stats.numOfProgress--; + break; + + case STATUS.QUEUED: + stats.numOfQueue --; + break; + + case STATUS.ERROR: + stats.numOfUploadFailed--; + break; + + case STATUS.INVALID: + stats.numOfInvalid--; + break; + + case STATUS.INTERRUPT: + stats.numofInterrupt--; + break; + } + + switch ( curStatus ) { + case STATUS.QUEUED: + stats.numOfQueue++; + break; + + case STATUS.PROGRESS: + stats.numOfProgress++; + break; + + case STATUS.ERROR: + stats.numOfUploadFailed++; + break; + + case STATUS.COMPLETE: + stats.numOfSuccess++; + break; + + case STATUS.CANCELLED: + stats.numOfCancel++; + break; + + + case STATUS.INVALID: + stats.numOfInvalid++; + break; + + case STATUS.INTERRUPT: + stats.numofInterrupt++; + break; + } + } + + }); + + Mediator.installTo( Queue.prototype ); + + return Queue; + }); + /** + * @fileOverview 队列 + */ + define('widgets/queue',[ + 'base', + 'uploader', + 'queue', + 'file', + 'lib/file', + 'runtime/client', + 'widgets/widget' + ], function( Base, Uploader, Queue, WUFile, File, RuntimeClient ) { + + var $ = Base.$, + rExt = /\.\w+$/, + Status = WUFile.Status; + + return Uploader.register({ + name: 'queue', + + init: function( opts ) { + var me = this, + deferred, len, i, item, arr, accept, runtime; + + if ( $.isPlainObject( opts.accept ) ) { + opts.accept = [ opts.accept ]; + } + + // accept中的中生成匹配正则。 + if ( opts.accept ) { + arr = []; + + for ( i = 0, len = opts.accept.length; i < len; i++ ) { + item = opts.accept[ i ].extensions; + item && arr.push( item ); + } + + if ( arr.length ) { + accept = '\\.' + arr.join(',') + .replace( /,/g, '$|\\.' ) + .replace( /\*/g, '.*' ) + '$'; + } + + me.accept = new RegExp( accept, 'i' ); + } + + me.queue = new Queue(); + me.stats = me.queue.stats; + + // 如果当前不是html5运行时,那就算了。 + // 不执行后续操作 + if ( this.request('predict-runtime-type') !== 'html5' ) { + return; + } + + // 创建一个 html5 运行时的 placeholder + // 以至于外部添加原生 File 对象的时候能正确包裹一下供 webuploader 使用。 + deferred = Base.Deferred(); + this.placeholder = runtime = new RuntimeClient('Placeholder'); + runtime.connectRuntime({ + runtimeOrder: 'html5' + }, function() { + me._ruid = runtime.getRuid(); + deferred.resolve(); + }); + return deferred.promise(); + }, + + + // 为了支持外部直接添加一个原生File对象。 + _wrapFile: function( file ) { + if ( !(file instanceof WUFile) ) { + + if ( !(file instanceof File) ) { + if ( !this._ruid ) { + throw new Error('Can\'t add external files.'); + } + file = new File( this._ruid, file ); + } + + file = new WUFile( file ); + } + + return file; + }, + + // 判断文件是否可以被加入队列 + acceptFile: function( file ) { + var invalid = !file || !file.size || this.accept && + + // 如果名字中有后缀,才做后缀白名单处理。 + rExt.exec( file.name ) && !this.accept.test( file.name ); + + return !invalid; + }, + + + /** + * @event beforeFileQueued + * @param {File} file File对象 + * @description 当文件被加入队列之前触发,此事件的handler返回值为`false`,则此文件不会被添加进入队列。 + * @for Uploader + */ + + /** + * @event fileQueued + * @param {File} file File对象 + * @description 当文件被加入队列以后触发。 + * @for Uploader + */ + + _addFile: function( file ) { + var me = this; + + file = me._wrapFile( file ); + + // 不过类型判断允许不允许,先派送 `beforeFileQueued` + if ( !me.owner.trigger( 'beforeFileQueued', file ) ) { + return; + } + + // 类型不匹配,则派送错误事件,并返回。 + if ( !me.acceptFile( file ) ) { + me.owner.trigger( 'error', 'Q_TYPE_DENIED', file ); + return; + } + + me.queue.append( file ); + me.owner.trigger( 'fileQueued', file ); + return file; + }, + + getFile: function( fileId ) { + return this.queue.getFile( fileId ); + }, + + /** + * @event filesQueued + * @param {File} files 数组,内容为原始File(lib/File)对象。 + * @description 当一批文件添加进队列以后触发。 + * @for Uploader + */ + + /** + * @property {Boolean} [auto=false] + * @namespace options + * @for Uploader + * @description 设置为 true 后,不需要手动调用上传,有文件选择即开始上传。 + * + */ + + /** + * @method addFiles + * @grammar addFiles( file ) => undefined + * @grammar addFiles( [file1, file2 ...] ) => undefined + * @param {Array of File or File} [files] Files 对象 数组 + * @description 添加文件到队列 + * @for Uploader + */ + addFile: function( files ) { + var me = this; + + if ( !files.length ) { + files = [ files ]; + } + + files = $.map( files, function( file ) { + return me._addFile( file ); + }); + + me.owner.trigger( 'filesQueued', files ); + + if ( me.options.auto ) { + setTimeout(function() { + me.request('start-upload'); + }, 20 ); + } + }, + + getStats: function() { + return this.stats; + }, + + /** + * @event fileDequeued + * @param {File} file File对象 + * @description 当文件被移除队列后触发。 + * @for Uploader + */ + + /** + * @method removeFile + * @grammar removeFile( file ) => undefined + * @grammar removeFile( id ) => undefined + * @grammar removeFile( file, true ) => undefined + * @grammar removeFile( id, true ) => undefined + * @param {File|id} file File对象或这File对象的id + * @description 移除某一文件, 默认只会标记文件状态为已取消,如果第二个参数为 `true` 则会从 queue 中移除。 + * @for Uploader + * @example + * + * $li.on('click', '.remove-this', function() { + * uploader.removeFile( file ); + * }) + */ + removeFile: function( file, remove ) { + var me = this; + + file = file.id ? file : me.queue.getFile( file ); + + this.request( 'cancel-file', file ); + + if ( remove ) { + this.queue.removeFile( file ); + } + }, + + /** + * @method getFiles + * @grammar getFiles() => Array + * @grammar getFiles( status1, status2, status... ) => Array + * @description 返回指定状态的文件集合,不传参数将返回所有状态的文件。 + * @for Uploader + * @example + * console.log( uploader.getFiles() ); // => all files + * console.log( uploader.getFiles('error') ) // => all error files. + */ + getFiles: function() { + return this.queue.getFiles.apply( this.queue, arguments ); + }, + + fetchFile: function() { + return this.queue.fetch.apply( this.queue, arguments ); + }, + + /** + * @method retry + * @grammar retry() => undefined + * @grammar retry( file ) => undefined + * @description 重试上传,重试指定文件,或者从出错的文件开始重新上传。 + * @for Uploader + * @example + * function retry() { + * uploader.retry(); + * } + */ + retry: function( file, noForceStart ) { + var me = this, + files, i, len; + + if ( file ) { + file = file.id ? file : me.queue.getFile( file ); + file.setStatus( Status.QUEUED ); + noForceStart || me.request('start-upload'); + return; + } + + files = me.queue.getFiles( Status.ERROR ); + i = 0; + len = files.length; + + for ( ; i < len; i++ ) { + file = files[ i ]; + file.setStatus( Status.QUEUED ); + } + + me.request('start-upload'); + }, + + /** + * @method sort + * @grammar sort( fn ) => undefined + * @description 排序队列中的文件,在上传之前调整可以控制上传顺序。 + * @for Uploader + */ + sortFiles: function() { + return this.queue.sort.apply( this.queue, arguments ); + }, + + /** + * @event reset + * @description 当 uploader 被重置的时候触发。 + * @for Uploader + */ + + /** + * @method reset + * @grammar reset() => undefined + * @description 重置uploader。目前只重置了队列。 + * @for Uploader + * @example + * uploader.reset(); + */ + reset: function() { + this.owner.trigger('reset'); + this.queue = new Queue(); + this.stats = this.queue.stats; + }, + + destroy: function() { + this.reset(); + this.placeholder && this.placeholder.destroy(); + } + }); + + }); + /** + * @fileOverview 添加获取Runtime相关信息的方法。 + */ + define('widgets/runtime',[ + 'uploader', + 'runtime/runtime', + 'widgets/widget' + ], function( Uploader, Runtime ) { + + Uploader.support = function() { + return Runtime.hasRuntime.apply( Runtime, arguments ); + }; + + /** + * @property {Object} [runtimeOrder=html5,flash] + * @namespace options + * @for Uploader + * @description 指定运行时启动顺序。默认会想尝试 html5 是否支持,如果支持则使用 html5, 否则则使用 flash. + * + * 可以将此值设置成 `flash`,来强制使用 flash 运行时。 + */ + + return Uploader.register({ + name: 'runtime', + + init: function() { + if ( !this.predictRuntimeType() ) { + throw Error('Runtime Error'); + } + }, + + /** + * 预测Uploader将采用哪个`Runtime` + * @grammar predictRuntimeType() => String + * @method predictRuntimeType + * @for Uploader + */ + predictRuntimeType: function() { + var orders = this.options.runtimeOrder || Runtime.orders, + type = this.type, + i, len; + + if ( !type ) { + orders = orders.split( /\s*,\s*/g ); + + for ( i = 0, len = orders.length; i < len; i++ ) { + if ( Runtime.hasRuntime( orders[ i ] ) ) { + this.type = type = orders[ i ]; + break; + } + } + } + + return type; + } + }); + }); + /** + * @fileOverview Transport + */ + define('lib/transport',[ + 'base', + 'runtime/client', + 'mediator' + ], function( Base, RuntimeClient, Mediator ) { + + var $ = Base.$; + + function Transport( opts ) { + var me = this; + + opts = me.options = $.extend( true, {}, Transport.options, opts || {} ); + RuntimeClient.call( this, 'Transport' ); + + this._blob = null; + this._formData = opts.formData || {}; + this._headers = opts.headers || {}; + + this.on( 'progress', this._timeout ); + this.on( 'load error', function() { + me.trigger( 'progress', 1 ); + clearTimeout( me._timer ); + }); + } + + Transport.options = { + server: '', + method: 'POST', + + // 跨域时,是否允许携带cookie, 只有html5 runtime才有效 + withCredentials: false, + fileVal: 'file', + timeout: 2 * 60 * 1000, // 2分钟 + formData: {}, + headers: {}, + sendAsBinary: false + }; + + $.extend( Transport.prototype, { + + // 添加Blob, 只能添加一次,最后一次有效。 + appendBlob: function( key, blob, filename ) { + var me = this, + opts = me.options; + + if ( me.getRuid() ) { + me.disconnectRuntime(); + } + + // 连接到blob归属的同一个runtime. + me.connectRuntime( blob.ruid, function() { + me.exec('init'); + }); + + me._blob = blob; + opts.fileVal = key || opts.fileVal; + opts.filename = filename || opts.filename; + }, + + // 添加其他字段 + append: function( key, value ) { + if ( typeof key === 'object' ) { + $.extend( this._formData, key ); + } else { + this._formData[ key ] = value; + } + }, + + setRequestHeader: function( key, value ) { + if ( typeof key === 'object' ) { + $.extend( this._headers, key ); + } else { + this._headers[ key ] = value; + } + }, + + send: function( method ) { + this.exec( 'send', method ); + this._timeout(); + }, + + abort: function() { + clearTimeout( this._timer ); + return this.exec('abort'); + }, + + destroy: function() { + this.trigger('destroy'); + this.off(); + this.exec('destroy'); + this.disconnectRuntime(); + }, + + getResponse: function() { + return this.exec('getResponse'); + }, + + getResponseAsJson: function() { + return this.exec('getResponseAsJson'); + }, + + getStatus: function() { + return this.exec('getStatus'); + }, + + _timeout: function() { + var me = this, + duration = me.options.timeout; + + if ( !duration ) { + return; + } + + clearTimeout( me._timer ); + me._timer = setTimeout(function() { + me.abort(); + me.trigger( 'error', 'timeout' ); + }, duration ); + } + + }); + + // 让Transport具备事件功能。 + Mediator.installTo( Transport.prototype ); + + return Transport; + }); + /** + * @fileOverview 负责文件上传相关。 + */ + define('widgets/upload',[ + 'base', + 'uploader', + 'file', + 'lib/transport', + 'widgets/widget' + ], function( Base, Uploader, WUFile, Transport ) { + + var $ = Base.$, + isPromise = Base.isPromise, + Status = WUFile.Status; + + // 添加默认配置项 + $.extend( Uploader.options, { + + + /** + * @property {Boolean} [prepareNextFile=false] + * @namespace options + * @for Uploader + * @description 是否允许在文件传输时提前把下一个文件准备好。 + * 对于一个文件的准备工作比较耗时,比如图片压缩,md5序列化。 + * 如果能提前在当前文件传输期处理,可以节省总体耗时。 + */ + prepareNextFile: false, + + /** + * @property {Boolean} [chunked=false] + * @namespace options + * @for Uploader + * @description 是否要分片处理大文件上传。 + */ + chunked: false, + + /** + * @property {Boolean} [chunkSize=5242880] + * @namespace options + * @for Uploader + * @description 如果要分片,分多大一片? 默认大小为5M. + */ + chunkSize: 5 * 1024 * 1024, + + /** + * @property {Boolean} [chunkRetry=2] + * @namespace options + * @for Uploader + * @description 如果某个分片由于网络问题出错,允许自动重传多少次? + */ + chunkRetry: 2, + + /** + * @property {Boolean} [threads=3] + * @namespace options + * @for Uploader + * @description 上传并发数。允许同时最大上传进程数。 + */ + threads: 3, + + + /** + * @property {Object} [formData={}] + * @namespace options + * @for Uploader + * @description 文件上传请求的参数表,每次发送都会发送此对象中的参数。 + */ + formData: {} + + /** + * @property {Object} [fileVal='file'] + * @namespace options + * @for Uploader + * @description 设置文件上传域的name。 + */ + + /** + * @property {Object} [method='POST'] + * @namespace options + * @for Uploader + * @description 文件上传方式,`POST`或者`GET`。 + */ + + /** + * @property {Object} [sendAsBinary=false] + * @namespace options + * @for Uploader + * @description 是否已二进制的流的方式发送文件,这样整个上传内容`php://input`都为文件内容, + * 其他参数在$_GET数组中。 + */ + }); + + // 负责将文件切片。 + function CuteFile( file, chunkSize ) { + var pending = [], + blob = file.source, + total = blob.size, + chunks = chunkSize ? Math.ceil( total / chunkSize ) : 1, + start = 0, + index = 0, + len, api; + + api = { + file: file, + + has: function() { + return !!pending.length; + }, + + shift: function() { + return pending.shift(); + }, + + unshift: function( block ) { + pending.unshift( block ); + } + }; + + while ( index < chunks ) { + len = Math.min( chunkSize, total - start ); + + pending.push({ + file: file, + start: start, + end: chunkSize ? (start + len) : total, + total: total, + chunks: chunks, + chunk: index++, + cuted: api + }); + start += len; + } + + file.blocks = pending.concat(); + file.remaning = pending.length; + + return api; + } + + Uploader.register({ + name: 'upload', + + init: function() { + var owner = this.owner, + me = this; + + this.runing = false; + this.progress = false; + + owner + .on( 'startUpload', function() { + me.progress = true; + }) + .on( 'uploadFinished', function() { + me.progress = false; + }); + + // 记录当前正在传的数据,跟threads相关 + this.pool = []; + + // 缓存分好片的文件。 + this.stack = []; + + // 缓存即将上传的文件。 + this.pending = []; + + // 跟踪还有多少分片在上传中但是没有完成上传。 + this.remaning = 0; + this.__tick = Base.bindFn( this._tick, this ); + + owner.on( 'uploadComplete', function( file ) { + + // 把其他块取消了。 + file.blocks && $.each( file.blocks, function( _, v ) { + v.transport && (v.transport.abort(), v.transport.destroy()); + delete v.transport; + }); + + delete file.blocks; + delete file.remaning; + }); + }, + + reset: function() { + this.request( 'stop-upload', true ); + this.runing = false; + this.pool = []; + this.stack = []; + this.pending = []; + this.remaning = 0; + this._trigged = false; + this._promise = null; + }, + + /** + * @event startUpload + * @description 当开始上传流程时触发。 + * @for Uploader + */ + + /** + * 开始上传。此方法可以从初始状态调用开始上传流程,也可以从暂停状态调用,继续上传流程。 + * + * 可以指定开始某一个文件。 + * @grammar upload() => undefined + * @grammar upload( file | fileId) => undefined + * @method upload + * @for Uploader + */ + startUpload: function(file) { + var me = this; + + // 移出invalid的文件 + $.each( me.request( 'get-files', Status.INVALID ), function() { + me.request( 'remove-file', this ); + }); + + // 如果指定了开始某个文件,则只开始指定文件。 + if ( file ) { + file = file.id ? file : me.request( 'get-file', file ); + + if (file.getStatus() === Status.INTERRUPT) { + $.each( me.pool, function( _, v ) { + + // 之前暂停过。 + if (v.file !== file) { + return; + } + + v.transport && v.transport.send(); + }); + + file.setStatus( Status.QUEUED ); + } else if (file.getStatus() === Status.PROGRESS) { + return; + } else { + file.setStatus( Status.QUEUED ); + } + } else { + $.each( me.request( 'get-files', [ Status.INITED ] ), function() { + this.setStatus( Status.QUEUED ); + }); + } + + if ( me.runing ) { + return; + } + + me.runing = true; + + var files = []; + + // 如果有暂停的,则续传 + $.each( me.pool, function( _, v ) { + var file = v.file; + + if ( file.getStatus() === Status.INTERRUPT ) { + files.push(file); + me._trigged = false; + v.transport && v.transport.send(); + } + }); + + var file; + while ( (file = files.shift()) ) { + file.setStatus( Status.PROGRESS ); + } + + file || $.each( me.request( 'get-files', + Status.INTERRUPT ), function() { + this.setStatus( Status.PROGRESS ); + }); + + me._trigged = false; + Base.nextTick( me.__tick ); + me.owner.trigger('startUpload'); + }, + + /** + * @event stopUpload + * @description 当开始上传流程暂停时触发。 + * @for Uploader + */ + + /** + * 暂停上传。第一个参数为是否中断上传当前正在上传的文件。 + * + * 如果第一个参数是文件,则只暂停指定文件。 + * @grammar stop() => undefined + * @grammar stop( true ) => undefined + * @grammar stop( file ) => undefined + * @method stop + * @for Uploader + */ + stopUpload: function( file, interrupt ) { + var me = this; + + if (file === true) { + interrupt = file; + file = null; + } + + if ( me.runing === false ) { + return; + } + + // 如果只是暂停某个文件。 + if ( file ) { + file = file.id ? file : me.request( 'get-file', file ); + + if ( file.getStatus() !== Status.PROGRESS && + file.getStatus() !== Status.QUEUED ) { + return; + } + + file.setStatus( Status.INTERRUPT ); + $.each( me.pool, function( _, v ) { + + // 只 abort 指定的文件。 + if (v.file !== file) { + return; + } + + v.transport && v.transport.abort(); + me._putback(v); + me._popBlock(v); + }); + + return Base.nextTick( me.__tick ); + } + + me.runing = false; + + if (this._promise && this._promise.file) { + this._promise.file.setStatus( Status.INTERRUPT ); + } + + interrupt && $.each( me.pool, function( _, v ) { + v.transport && v.transport.abort(); + v.file.setStatus( Status.INTERRUPT ); + }); + + me.owner.trigger('stopUpload'); + }, + + /** + * @method cancelFile + * @grammar cancelFile( file ) => undefined + * @grammar cancelFile( id ) => undefined + * @param {File|id} file File对象或这File对象的id + * @description 标记文件状态为已取消, 同时将中断文件传输。 + * @for Uploader + * @example + * + * $li.on('click', '.remove-this', function() { + * uploader.cancelFile( file ); + * }) + */ + cancelFile: function( file ) { + file = file.id ? file : this.request( 'get-file', file ); + + // 如果正在上传。 + file.blocks && $.each( file.blocks, function( _, v ) { + var _tr = v.transport; + + if ( _tr ) { + _tr.abort(); + _tr.destroy(); + delete v.transport; + } + }); + + file.setStatus( Status.CANCELLED ); + this.owner.trigger( 'fileDequeued', file ); + }, + + /** + * 判断`Uplaode`r是否正在上传中。 + * @grammar isInProgress() => Boolean + * @method isInProgress + * @for Uploader + */ + isInProgress: function() { + return !!this.progress; + }, + + _getStats: function() { + return this.request('get-stats'); + }, + + /** + * 掉过一个文件上传,直接标记指定文件为已上传状态。 + * @grammar skipFile( file ) => undefined + * @method skipFile + * @for Uploader + */ + skipFile: function( file, status ) { + file = file.id ? file : this.request( 'get-file', file ); + + file.setStatus( status || Status.COMPLETE ); + file.skipped = true; + + // 如果正在上传。 + file.blocks && $.each( file.blocks, function( _, v ) { + var _tr = v.transport; + + if ( _tr ) { + _tr.abort(); + _tr.destroy(); + delete v.transport; + } + }); + + this.owner.trigger( 'uploadSkip', file ); + }, + + /** + * @event uploadFinished + * @description 当所有文件上传结束时触发。 + * @for Uploader + */ + _tick: function() { + var me = this, + opts = me.options, + fn, val; + + // 上一个promise还没有结束,则等待完成后再执行。 + if ( me._promise ) { + return me._promise.always( me.__tick ); + } + + // 还有位置,且还有文件要处理的话。 + if ( me.pool.length < opts.threads && (val = me._nextBlock()) ) { + me._trigged = false; + + fn = function( val ) { + me._promise = null; + + // 有可能是reject过来的,所以要检测val的类型。 + val && val.file && me._startSend( val ); + Base.nextTick( me.__tick ); + }; + + me._promise = isPromise( val ) ? val.always( fn ) : fn( val ); + + // 没有要上传的了,且没有正在传输的了。 + } else if ( !me.remaning && !me._getStats().numOfQueue && + !me._getStats().numofInterrupt ) { + me.runing = false; + + me._trigged || Base.nextTick(function() { + me.owner.trigger('uploadFinished'); + }); + me._trigged = true; + } + }, + + _putback: function(block) { + var idx; + + block.cuted.unshift(block); + idx = this.stack.indexOf(block.cuted); + + if (!~idx) { + this.stack.unshift(block.cuted); + } + }, + + _getStack: function() { + var i = 0, + act; + + while ( (act = this.stack[ i++ ]) ) { + if ( act.has() && act.file.getStatus() === Status.PROGRESS ) { + return act; + } else if (!act.has() || + act.file.getStatus() !== Status.PROGRESS && + act.file.getStatus() !== Status.INTERRUPT ) { + + // 把已经处理完了的,或者,状态为非 progress(上传中)、 + // interupt(暂停中) 的移除。 + this.stack.splice( --i, 1 ); + } + } + + return null; + }, + + _nextBlock: function() { + var me = this, + opts = me.options, + act, next, done, preparing; + + // 如果当前文件还有没有需要传输的,则直接返回剩下的。 + if ( (act = this._getStack()) ) { + + // 是否提前准备下一个文件 + if ( opts.prepareNextFile && !me.pending.length ) { + me._prepareNextFile(); + } + + return act.shift(); + + // 否则,如果正在运行,则准备下一个文件,并等待完成后返回下个分片。 + } else if ( me.runing ) { + + // 如果缓存中有,则直接在缓存中取,没有则去queue中取。 + if ( !me.pending.length && me._getStats().numOfQueue ) { + me._prepareNextFile(); + } + + next = me.pending.shift(); + done = function( file ) { + if ( !file ) { + return null; + } + + act = CuteFile( file, opts.chunked ? opts.chunkSize : 0 ); + me.stack.push(act); + return act.shift(); + }; + + // 文件可能还在prepare中,也有可能已经完全准备好了。 + if ( isPromise( next) ) { + preparing = next.file; + next = next[ next.pipe ? 'pipe' : 'then' ]( done ); + next.file = preparing; + return next; + } + + return done( next ); + } + }, + + + /** + * @event uploadStart + * @param {File} file File对象 + * @description 某个文件开始上传前触发,一个文件只会触发一次。 + * @for Uploader + */ + _prepareNextFile: function() { + var me = this, + file = me.request('fetch-file'), + pending = me.pending, + promise; + + if ( file ) { + promise = me.request( 'before-send-file', file, function() { + + // 有可能文件被skip掉了。文件被skip掉后,状态坑定不是Queued. + if ( file.getStatus() === Status.PROGRESS || + file.getStatus() === Status.INTERRUPT ) { + return file; + } + + return me._finishFile( file ); + }); + + me.owner.trigger( 'uploadStart', file ); + file.setStatus( Status.PROGRESS ); + + promise.file = file; + + // 如果还在pending中,则替换成文件本身。 + promise.done(function() { + var idx = $.inArray( promise, pending ); + + ~idx && pending.splice( idx, 1, file ); + }); + + // befeore-send-file的钩子就有错误发生。 + promise.fail(function( reason ) { + file.setStatus( Status.ERROR, reason ); + me.owner.trigger( 'uploadError', file, reason ); + me.owner.trigger( 'uploadComplete', file ); + }); + + pending.push( promise ); + } + }, + + // 让出位置了,可以让其他分片开始上传 + _popBlock: function( block ) { + var idx = $.inArray( block, this.pool ); + + this.pool.splice( idx, 1 ); + block.file.remaning--; + this.remaning--; + }, + + // 开始上传,可以被掉过。如果promise被reject了,则表示跳过此分片。 + _startSend: function( block ) { + var me = this, + file = block.file, + promise; + + // 有可能在 before-send-file 的 promise 期间改变了文件状态。 + // 如:暂停,取消 + // 我们不能中断 promise, 但是可以在 promise 完后,不做上传操作。 + if ( file.getStatus() !== Status.PROGRESS ) { + + // 如果是中断,则还需要放回去。 + if (file.getStatus() === Status.INTERRUPT) { + me._putback(block); + } + + return; + } + + me.pool.push( block ); + me.remaning++; + + // 如果没有分片,则直接使用原始的。 + // 不会丢失content-type信息。 + block.blob = block.chunks === 1 ? file.source : + file.source.slice( block.start, block.end ); + + // hook, 每个分片发送之前可能要做些异步的事情。 + promise = me.request( 'before-send', block, function() { + + // 有可能文件已经上传出错了,所以不需要再传输了。 + if ( file.getStatus() === Status.PROGRESS ) { + me._doSend( block ); + } else { + me._popBlock( block ); + Base.nextTick( me.__tick ); + } + }); + + // 如果为fail了,则跳过此分片。 + promise.fail(function() { + if ( file.remaning === 1 ) { + me._finishFile( file ).always(function() { + block.percentage = 1; + me._popBlock( block ); + me.owner.trigger( 'uploadComplete', file ); + Base.nextTick( me.__tick ); + }); + } else { + block.percentage = 1; + me.updateFileProgress( file ); + me._popBlock( block ); + Base.nextTick( me.__tick ); + } + }); + }, + + + /** + * @event uploadBeforeSend + * @param {Object} object + * @param {Object} data 默认的上传参数,可以扩展此对象来控制上传参数。 + * @param {Object} headers 可以扩展此对象来控制上传头部。 + * @description 当某个文件的分块在发送前触发,主要用来询问是否要添加附带参数,大文件在开起分片上传的前提下此事件可能会触发多次。 + * @for Uploader + */ + + /** + * @event uploadAccept + * @param {Object} object + * @param {Object} ret 服务端的返回数据,json格式,如果服务端不是json格式,从ret._raw中取数据,自行解析。 + * @description 当某个文件上传到服务端响应后,会派送此事件来询问服务端响应是否有效。如果此事件handler返回值为`false`, 则此文件将派送`server`类型的`uploadError`事件。 + * @for Uploader + */ + + /** + * @event uploadProgress + * @param {File} file File对象 + * @param {Number} percentage 上传进度 + * @description 上传过程中触发,携带上传进度。 + * @for Uploader + */ + + + /** + * @event uploadError + * @param {File} file File对象 + * @param {String} reason 出错的code + * @description 当文件上传出错时触发。 + * @for Uploader + */ + + /** + * @event uploadSuccess + * @param {File} file File对象 + * @param {Object} response 服务端返回的数据 + * @description 当文件上传成功时触发。 + * @for Uploader + */ + + /** + * @event uploadComplete + * @param {File} [file] File对象 + * @description 不管成功或者失败,文件上传完成时触发。 + * @for Uploader + */ + + // 做上传操作。 + _doSend: function( block ) { + var me = this, + owner = me.owner, + opts = me.options, + file = block.file, + tr = new Transport( opts ), + data = $.extend({}, opts.formData ), + headers = $.extend({}, opts.headers ), + requestAccept, ret; + + block.transport = tr; + + tr.on( 'destroy', function() { + delete block.transport; + me._popBlock( block ); + Base.nextTick( me.__tick ); + }); + + // 广播上传进度。以文件为单位。 + tr.on( 'progress', function( percentage ) { + block.percentage = percentage; + me.updateFileProgress( file ); + }); + + // 用来询问,是否返回的结果是有错误的。 + requestAccept = function( reject ) { + var fn; + + ret = tr.getResponseAsJson() || {}; + ret._raw = tr.getResponse(); + fn = function( value ) { + reject = value; + }; + + // 服务端响应了,不代表成功了,询问是否响应正确。 + if ( !owner.trigger( 'uploadAccept', block, ret, fn ) ) { + reject = reject || 'server'; + } + + return reject; + }; + + // 尝试重试,然后广播文件上传出错。 + tr.on( 'error', function( type, flag ) { + block.retried = block.retried || 0; + + // 自动重试 + if ( block.chunks > 1 && ~'http,abort'.indexOf( type ) && + block.retried < opts.chunkRetry ) { + + block.retried++; + tr.send(); + + } else { + + // http status 500 ~ 600 + if ( !flag && type === 'server' ) { + type = requestAccept( type ); + } + + file.setStatus( Status.ERROR, type ); + owner.trigger( 'uploadError', file, type ); + owner.trigger( 'uploadComplete', file ); + } + }); + + // 上传成功 + tr.on( 'load', function() { + var reason; + + // 如果非预期,转向上传出错。 + if ( (reason = requestAccept()) ) { + tr.trigger( 'error', reason, true ); + return; + } + + // 全部上传完成。 + if ( file.remaning === 1 ) { + me._finishFile( file, ret ); + } else { + tr.destroy(); + } + }); + + // 配置默认的上传字段。 + data = $.extend( data, { + id: file.id, + name: file.name, + type: file.type, + lastModifiedDate: file.lastModifiedDate, + size: file.size + }); + + block.chunks > 1 && $.extend( data, { + chunks: block.chunks, + chunk: block.chunk + }); + + // 在发送之间可以添加字段什么的。。。 + // 如果默认的字段不够使用,可以通过监听此事件来扩展 + owner.trigger( 'uploadBeforeSend', block, data, headers ); + + // 开始发送。 + tr.appendBlob( opts.fileVal, block.blob, file.name ); + tr.append( data ); + tr.setRequestHeader( headers ); + tr.send(); + }, + + // 完成上传。 + _finishFile: function( file, ret, hds ) { + var owner = this.owner; + + return owner + .request( 'after-send-file', arguments, function() { + file.setStatus( Status.COMPLETE ); + owner.trigger( 'uploadSuccess', file, ret, hds ); + }) + .fail(function( reason ) { + + // 如果外部已经标记为invalid什么的,不再改状态。 + if ( file.getStatus() === Status.PROGRESS ) { + file.setStatus( Status.ERROR, reason ); + } + + owner.trigger( 'uploadError', file, reason ); + }) + .always(function() { + owner.trigger( 'uploadComplete', file ); + }); + }, + + updateFileProgress: function(file) { + var totalPercent = 0, + uploaded = 0; + + if (!file.blocks) { + return; + } + + $.each( file.blocks, function( _, v ) { + uploaded += (v.percentage || 0) * (v.end - v.start); + }); + + totalPercent = uploaded / file.size; + this.owner.trigger( 'uploadProgress', file, totalPercent || 0 ); + } + + }); + }); + /** + * @fileOverview 日志组件,主要用来收集错误信息,可以帮助 webuploader 更好的定位问题和发展。 + * + * 如果您不想要启用此功能,请在打包的时候去掉 log 模块。 + * + * 或者可以在初始化的时候通过 options.disableWidgets 属性禁用。 + * + * 如: + * WebUploader.create({ + * ... + * + * disableWidgets: 'log', + * + * ... + * }) + */ + define('widgets/log',[ + 'base', + 'uploader', + 'widgets/widget' + ], function( Base, Uploader ) { + var $ = Base.$, + logUrl = ' http://static.tieba.baidu.com/tb/pms/img/st.gif??', + product = (location.hostname || location.host || 'protected').toLowerCase(), + + // 只针对 baidu 内部产品用户做统计功能。 + enable = product && /baidu/i.exec(product), + base; + + if (!enable) { + return; + } + + base = { + dv: 3, + master: 'webuploader', + online: /test/.exec(product) ? 0 : 1, + module: '', + product: product, + type: 0 + }; + + function send(data) { + var obj = $.extend({}, base, data), + url = logUrl.replace(/^(.*)\?/, '$1' + $.param( obj )), + image = new Image(); + + image.src = url; + } + + return Uploader.register({ + name: 'log', + + init: function() { + var owner = this.owner, + count = 0, + size = 0; + + owner + .on('error', function(code) { + send({ + type: 2, + c_error_code: code + }); + }) + .on('uploadError', function(file, reason) { + send({ + type: 2, + c_error_code: 'UPLOAD_ERROR', + c_reason: '' + reason + }); + }) + .on('uploadComplete', function(file) { + count++; + size += file.size; + }). + on('uploadFinished', function() { + send({ + c_count: count, + c_size: size + }); + count = size = 0; + }); + + send({ + c_usage: 1 + }); + } + }); + }); + /** + * @fileOverview Runtime管理器,负责Runtime的选择, 连接 + */ + define('runtime/compbase',[],function() { + + function CompBase( owner, runtime ) { + + this.owner = owner; + this.options = owner.options; + + this.getRuntime = function() { + return runtime; + }; + + this.getRuid = function() { + return runtime.uid; + }; + + this.trigger = function() { + return owner.trigger.apply( owner, arguments ); + }; + } + + return CompBase; + }); + /** + * @fileOverview Html5Runtime + */ + define('runtime/html5/runtime',[ + 'base', + 'runtime/runtime', + 'runtime/compbase' + ], function( Base, Runtime, CompBase ) { + + var type = 'html5', + components = {}; + + function Html5Runtime() { + var pool = {}, + me = this, + destroy = this.destroy; + + Runtime.apply( me, arguments ); + me.type = type; + + + // 这个方法的调用者,实际上是RuntimeClient + me.exec = function( comp, fn/*, args...*/) { + var client = this, + uid = client.uid, + args = Base.slice( arguments, 2 ), + instance; + + if ( components[ comp ] ) { + instance = pool[ uid ] = pool[ uid ] || + new components[ comp ]( client, me ); + + if ( instance[ fn ] ) { + return instance[ fn ].apply( instance, args ); + } + } + }; + + me.destroy = function() { + // @todo 删除池子中的所有实例 + return destroy && destroy.apply( this, arguments ); + }; + } + + Base.inherits( Runtime, { + constructor: Html5Runtime, + + // 不需要连接其他程序,直接执行callback + init: function() { + var me = this; + setTimeout(function() { + me.trigger('ready'); + }, 1 ); + } + + }); + + // 注册Components + Html5Runtime.register = function( name, component ) { + var klass = components[ name ] = Base.inherits( CompBase, component ); + return klass; + }; + + // 注册html5运行时。 + // 只有在支持的前提下注册。 + if ( window.Blob && window.FileReader && window.DataView ) { + Runtime.addRuntime( type, Html5Runtime ); + } + + return Html5Runtime; + }); + /** + * @fileOverview Blob Html实现 + */ + define('runtime/html5/blob',[ + 'runtime/html5/runtime', + 'lib/blob' + ], function( Html5Runtime, Blob ) { + + return Html5Runtime.register( 'Blob', { + slice: function( start, end ) { + var blob = this.owner.source, + slice = blob.slice || blob.webkitSlice || blob.mozSlice; + + blob = slice.call( blob, start, end ); + + return new Blob( this.getRuid(), blob ); + } + }); + }); + /** + * @fileOverview FilePicker + */ + define('runtime/html5/filepicker',[ + 'base', + 'runtime/html5/runtime' + ], function( Base, Html5Runtime ) { + + var $ = Base.$; + + return Html5Runtime.register( 'FilePicker', { + init: function() { + var container = this.getRuntime().getContainer(), + me = this, + owner = me.owner, + opts = me.options, + label = this.label = $( document.createElement('label') ), + input = this.input = $( document.createElement('input') ), + arr, i, len, mouseHandler; + + input.attr( 'type', 'file' ); + input.attr( 'name', opts.name ); + input.addClass('webuploader-element-invisible'); + + label.on( 'click', function() { + input.trigger('click'); + }); + + label.css({ + opacity: 0, + width: '100%', + height: '100%', + display: 'block', + cursor: 'pointer', + background: '#ffffff' + }); + + if ( opts.multiple ) { + input.attr( 'multiple', 'multiple' ); + } + + // @todo Firefox不支持单独指定后缀 + if ( opts.accept && opts.accept.length > 0 ) { + arr = []; + + for ( i = 0, len = opts.accept.length; i < len; i++ ) { + arr.push( opts.accept[ i ].mimeTypes ); + } + + input.attr( 'accept', arr.join(',') ); + } + + container.append( input ); + container.append( label ); + + mouseHandler = function( e ) { + owner.trigger( e.type ); + }; + + input.on( 'change', function( e ) { + var fn = arguments.callee, + clone; + + me.files = e.target.files; + + // reset input + clone = this.cloneNode( true ); + clone.value = null; + this.parentNode.replaceChild( clone, this ); + + input.off(); + input = $( clone ).on( 'change', fn ) + .on( 'mouseenter mouseleave', mouseHandler ); + + owner.trigger('change'); + }); + + label.on( 'mouseenter mouseleave', mouseHandler ); + + }, + + + getFiles: function() { + return this.files; + }, + + destroy: function() { + this.input.off(); + this.label.off(); + } + }); + }); + /** + * Terms: + * + * Uint8Array, FileReader, BlobBuilder, atob, ArrayBuffer + * @fileOverview Image控件 + */ + define('runtime/html5/util',[ + 'base' + ], function( Base ) { + + var urlAPI = window.createObjectURL && window || + window.URL && URL.revokeObjectURL && URL || + window.webkitURL, + createObjectURL = Base.noop, + revokeObjectURL = createObjectURL; + + if ( urlAPI ) { + + // 更安全的方式调用,比如android里面就能把context改成其他的对象。 + createObjectURL = function() { + return urlAPI.createObjectURL.apply( urlAPI, arguments ); + }; + + revokeObjectURL = function() { + return urlAPI.revokeObjectURL.apply( urlAPI, arguments ); + }; + } + + return { + createObjectURL: createObjectURL, + revokeObjectURL: revokeObjectURL, + + dataURL2Blob: function( dataURI ) { + var byteStr, intArray, ab, i, mimetype, parts; + + parts = dataURI.split(','); + + if ( ~parts[ 0 ].indexOf('base64') ) { + byteStr = atob( parts[ 1 ] ); + } else { + byteStr = decodeURIComponent( parts[ 1 ] ); + } + + ab = new ArrayBuffer( byteStr.length ); + intArray = new Uint8Array( ab ); + + for ( i = 0; i < byteStr.length; i++ ) { + intArray[ i ] = byteStr.charCodeAt( i ); + } + + mimetype = parts[ 0 ].split(':')[ 1 ].split(';')[ 0 ]; + + return this.arrayBufferToBlob( ab, mimetype ); + }, + + dataURL2ArrayBuffer: function( dataURI ) { + var byteStr, intArray, i, parts; + + parts = dataURI.split(','); + + if ( ~parts[ 0 ].indexOf('base64') ) { + byteStr = atob( parts[ 1 ] ); + } else { + byteStr = decodeURIComponent( parts[ 1 ] ); + } + + intArray = new Uint8Array( byteStr.length ); + + for ( i = 0; i < byteStr.length; i++ ) { + intArray[ i ] = byteStr.charCodeAt( i ); + } + + return intArray.buffer; + }, + + arrayBufferToBlob: function( buffer, type ) { + var builder = window.BlobBuilder || window.WebKitBlobBuilder, + bb; + + // android不支持直接new Blob, 只能借助blobbuilder. + if ( builder ) { + bb = new builder(); + bb.append( buffer ); + return bb.getBlob( type ); + } + + return new Blob([ buffer ], type ? { type: type } : {} ); + }, + + // 抽出来主要是为了解决android下面canvas.toDataUrl不支持jpeg. + // 你得到的结果是png. + canvasToDataUrl: function( canvas, type, quality ) { + return canvas.toDataURL( type, quality / 100 ); + }, + + // imagemeat会复写这个方法,如果用户选择加载那个文件了的话。 + parseMeta: function( blob, callback ) { + callback( false, {}); + }, + + // imagemeat会复写这个方法,如果用户选择加载那个文件了的话。 + updateImageHead: function( data ) { + return data; + } + }; + }); + /** + * Terms: + * + * Uint8Array, FileReader, BlobBuilder, atob, ArrayBuffer + * @fileOverview Image控件 + */ + define('runtime/html5/imagemeta',[ + 'runtime/html5/util' + ], function( Util ) { + + var api; + + api = { + parsers: { + 0xffe1: [] + }, + + maxMetaDataSize: 262144, + + parse: function( blob, cb ) { + var me = this, + fr = new FileReader(); + + fr.onload = function() { + cb( false, me._parse( this.result ) ); + fr = fr.onload = fr.onerror = null; + }; + + fr.onerror = function( e ) { + cb( e.message ); + fr = fr.onload = fr.onerror = null; + }; + + blob = blob.slice( 0, me.maxMetaDataSize ); + fr.readAsArrayBuffer( blob.getSource() ); + }, + + _parse: function( buffer, noParse ) { + if ( buffer.byteLength < 6 ) { + return; + } + + var dataview = new DataView( buffer ), + offset = 2, + maxOffset = dataview.byteLength - 4, + headLength = offset, + ret = {}, + markerBytes, markerLength, parsers, i; + + if ( dataview.getUint16( 0 ) === 0xffd8 ) { + + while ( offset < maxOffset ) { + markerBytes = dataview.getUint16( offset ); + + if ( markerBytes >= 0xffe0 && markerBytes <= 0xffef || + markerBytes === 0xfffe ) { + + markerLength = dataview.getUint16( offset + 2 ) + 2; + + if ( offset + markerLength > dataview.byteLength ) { + break; + } + + parsers = api.parsers[ markerBytes ]; + + if ( !noParse && parsers ) { + for ( i = 0; i < parsers.length; i += 1 ) { + parsers[ i ].call( api, dataview, offset, + markerLength, ret ); + } + } + + offset += markerLength; + headLength = offset; + } else { + break; + } + } + + if ( headLength > 6 ) { + if ( buffer.slice ) { + ret.imageHead = buffer.slice( 2, headLength ); + } else { + // Workaround for IE10, which does not yet + // support ArrayBuffer.slice: + ret.imageHead = new Uint8Array( buffer ) + .subarray( 2, headLength ); + } + } + } + + return ret; + }, + + updateImageHead: function( buffer, head ) { + var data = this._parse( buffer, true ), + buf1, buf2, bodyoffset; + + + bodyoffset = 2; + if ( data.imageHead ) { + bodyoffset = 2 + data.imageHead.byteLength; + } + + if ( buffer.slice ) { + buf2 = buffer.slice( bodyoffset ); + } else { + buf2 = new Uint8Array( buffer ).subarray( bodyoffset ); + } + + buf1 = new Uint8Array( head.byteLength + 2 + buf2.byteLength ); + + buf1[ 0 ] = 0xFF; + buf1[ 1 ] = 0xD8; + buf1.set( new Uint8Array( head ), 2 ); + buf1.set( new Uint8Array( buf2 ), head.byteLength + 2 ); + + return buf1.buffer; + } + }; + + Util.parseMeta = function() { + return api.parse.apply( api, arguments ); + }; + + Util.updateImageHead = function() { + return api.updateImageHead.apply( api, arguments ); + }; + + return api; + }); + /** + * 代码来自于:https://github.com/blueimp/JavaScript-Load-Image + * 暂时项目中只用了orientation. + * + * 去除了 Exif Sub IFD Pointer, GPS Info IFD Pointer, Exif Thumbnail. + * @fileOverview EXIF解析 + */ + + // Sample + // ==================================== + // Make : Apple + // Model : iPhone 4S + // Orientation : 1 + // XResolution : 72 [72/1] + // YResolution : 72 [72/1] + // ResolutionUnit : 2 + // Software : QuickTime 7.7.1 + // DateTime : 2013:09:01 22:53:55 + // ExifIFDPointer : 190 + // ExposureTime : 0.058823529411764705 [1/17] + // FNumber : 2.4 [12/5] + // ExposureProgram : Normal program + // ISOSpeedRatings : 800 + // ExifVersion : 0220 + // DateTimeOriginal : 2013:09:01 22:52:51 + // DateTimeDigitized : 2013:09:01 22:52:51 + // ComponentsConfiguration : YCbCr + // ShutterSpeedValue : 4.058893515764426 + // ApertureValue : 2.5260688216892597 [4845/1918] + // BrightnessValue : -0.3126686601998395 + // MeteringMode : Pattern + // Flash : Flash did not fire, compulsory flash mode + // FocalLength : 4.28 [107/25] + // SubjectArea : [4 values] + // FlashpixVersion : 0100 + // ColorSpace : 1 + // PixelXDimension : 2448 + // PixelYDimension : 3264 + // SensingMethod : One-chip color area sensor + // ExposureMode : 0 + // WhiteBalance : Auto white balance + // FocalLengthIn35mmFilm : 35 + // SceneCaptureType : Standard + define('runtime/html5/imagemeta/exif',[ + 'base', + 'runtime/html5/imagemeta' + ], function( Base, ImageMeta ) { + + var EXIF = {}; + + EXIF.ExifMap = function() { + return this; + }; + + EXIF.ExifMap.prototype.map = { + 'Orientation': 0x0112 + }; + + EXIF.ExifMap.prototype.get = function( id ) { + return this[ id ] || this[ this.map[ id ] ]; + }; + + EXIF.exifTagTypes = { + // byte, 8-bit unsigned int: + 1: { + getValue: function( dataView, dataOffset ) { + return dataView.getUint8( dataOffset ); + }, + size: 1 + }, + + // ascii, 8-bit byte: + 2: { + getValue: function( dataView, dataOffset ) { + return String.fromCharCode( dataView.getUint8( dataOffset ) ); + }, + size: 1, + ascii: true + }, + + // short, 16 bit int: + 3: { + getValue: function( dataView, dataOffset, littleEndian ) { + return dataView.getUint16( dataOffset, littleEndian ); + }, + size: 2 + }, + + // long, 32 bit int: + 4: { + getValue: function( dataView, dataOffset, littleEndian ) { + return dataView.getUint32( dataOffset, littleEndian ); + }, + size: 4 + }, + + // rational = two long values, + // first is numerator, second is denominator: + 5: { + getValue: function( dataView, dataOffset, littleEndian ) { + return dataView.getUint32( dataOffset, littleEndian ) / + dataView.getUint32( dataOffset + 4, littleEndian ); + }, + size: 8 + }, + + // slong, 32 bit signed int: + 9: { + getValue: function( dataView, dataOffset, littleEndian ) { + return dataView.getInt32( dataOffset, littleEndian ); + }, + size: 4 + }, + + // srational, two slongs, first is numerator, second is denominator: + 10: { + getValue: function( dataView, dataOffset, littleEndian ) { + return dataView.getInt32( dataOffset, littleEndian ) / + dataView.getInt32( dataOffset + 4, littleEndian ); + }, + size: 8 + } + }; + + // undefined, 8-bit byte, value depending on field: + EXIF.exifTagTypes[ 7 ] = EXIF.exifTagTypes[ 1 ]; + + EXIF.getExifValue = function( dataView, tiffOffset, offset, type, length, + littleEndian ) { + + var tagType = EXIF.exifTagTypes[ type ], + tagSize, dataOffset, values, i, str, c; + + if ( !tagType ) { + Base.log('Invalid Exif data: Invalid tag type.'); + return; + } + + tagSize = tagType.size * length; + + // Determine if the value is contained in the dataOffset bytes, + // or if the value at the dataOffset is a pointer to the actual data: + dataOffset = tagSize > 4 ? tiffOffset + dataView.getUint32( offset + 8, + littleEndian ) : (offset + 8); + + if ( dataOffset + tagSize > dataView.byteLength ) { + Base.log('Invalid Exif data: Invalid data offset.'); + return; + } + + if ( length === 1 ) { + return tagType.getValue( dataView, dataOffset, littleEndian ); + } + + values = []; + + for ( i = 0; i < length; i += 1 ) { + values[ i ] = tagType.getValue( dataView, + dataOffset + i * tagType.size, littleEndian ); + } + + if ( tagType.ascii ) { + str = ''; + + // Concatenate the chars: + for ( i = 0; i < values.length; i += 1 ) { + c = values[ i ]; + + // Ignore the terminating NULL byte(s): + if ( c === '\u0000' ) { + break; + } + str += c; + } + + return str; + } + return values; + }; + + EXIF.parseExifTag = function( dataView, tiffOffset, offset, littleEndian, + data ) { + + var tag = dataView.getUint16( offset, littleEndian ); + data.exif[ tag ] = EXIF.getExifValue( dataView, tiffOffset, offset, + dataView.getUint16( offset + 2, littleEndian ), // tag type + dataView.getUint32( offset + 4, littleEndian ), // tag length + littleEndian ); + }; + + EXIF.parseExifTags = function( dataView, tiffOffset, dirOffset, + littleEndian, data ) { + + var tagsNumber, dirEndOffset, i; + + if ( dirOffset + 6 > dataView.byteLength ) { + Base.log('Invalid Exif data: Invalid directory offset.'); + return; + } + + tagsNumber = dataView.getUint16( dirOffset, littleEndian ); + dirEndOffset = dirOffset + 2 + 12 * tagsNumber; + + if ( dirEndOffset + 4 > dataView.byteLength ) { + Base.log('Invalid Exif data: Invalid directory size.'); + return; + } + + for ( i = 0; i < tagsNumber; i += 1 ) { + this.parseExifTag( dataView, tiffOffset, + dirOffset + 2 + 12 * i, // tag offset + littleEndian, data ); + } + + // Return the offset to the next directory: + return dataView.getUint32( dirEndOffset, littleEndian ); + }; + + // EXIF.getExifThumbnail = function(dataView, offset, length) { + // var hexData, + // i, + // b; + // if (!length || offset + length > dataView.byteLength) { + // Base.log('Invalid Exif data: Invalid thumbnail data.'); + // return; + // } + // hexData = []; + // for (i = 0; i < length; i += 1) { + // b = dataView.getUint8(offset + i); + // hexData.push((b < 16 ? '0' : '') + b.toString(16)); + // } + // return 'data:image/jpeg,%' + hexData.join('%'); + // }; + + EXIF.parseExifData = function( dataView, offset, length, data ) { + + var tiffOffset = offset + 10, + littleEndian, dirOffset; + + // Check for the ASCII code for "Exif" (0x45786966): + if ( dataView.getUint32( offset + 4 ) !== 0x45786966 ) { + // No Exif data, might be XMP data instead + return; + } + if ( tiffOffset + 8 > dataView.byteLength ) { + Base.log('Invalid Exif data: Invalid segment size.'); + return; + } + + // Check for the two null bytes: + if ( dataView.getUint16( offset + 8 ) !== 0x0000 ) { + Base.log('Invalid Exif data: Missing byte alignment offset.'); + return; + } + + // Check the byte alignment: + switch ( dataView.getUint16( tiffOffset ) ) { + case 0x4949: + littleEndian = true; + break; + + case 0x4D4D: + littleEndian = false; + break; + + default: + Base.log('Invalid Exif data: Invalid byte alignment marker.'); + return; + } + + // Check for the TIFF tag marker (0x002A): + if ( dataView.getUint16( tiffOffset + 2, littleEndian ) !== 0x002A ) { + Base.log('Invalid Exif data: Missing TIFF marker.'); + return; + } + + // Retrieve the directory offset bytes, usually 0x00000008 or 8 decimal: + dirOffset = dataView.getUint32( tiffOffset + 4, littleEndian ); + // Create the exif object to store the tags: + data.exif = new EXIF.ExifMap(); + // Parse the tags of the main image directory and retrieve the + // offset to the next directory, usually the thumbnail directory: + dirOffset = EXIF.parseExifTags( dataView, tiffOffset, + tiffOffset + dirOffset, littleEndian, data ); + + // 尝试读取缩略图 + // if ( dirOffset ) { + // thumbnailData = {exif: {}}; + // dirOffset = EXIF.parseExifTags( + // dataView, + // tiffOffset, + // tiffOffset + dirOffset, + // littleEndian, + // thumbnailData + // ); + + // // Check for JPEG Thumbnail offset: + // if (thumbnailData.exif[0x0201]) { + // data.exif.Thumbnail = EXIF.getExifThumbnail( + // dataView, + // tiffOffset + thumbnailData.exif[0x0201], + // thumbnailData.exif[0x0202] // Thumbnail data length + // ); + // } + // } + }; + + ImageMeta.parsers[ 0xffe1 ].push( EXIF.parseExifData ); + return EXIF; + }); + /** + * @fileOverview Image + */ + define('runtime/html5/image',[ + 'base', + 'runtime/html5/runtime', + 'runtime/html5/util' + ], function( Base, Html5Runtime, Util ) { + + var BLANK = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs%3D'; + + return Html5Runtime.register( 'Image', { + + // flag: 标记是否被修改过。 + modified: false, + + init: function() { + var me = this, + img = new Image(); + + img.onload = function() { + + me._info = { + type: me.type, + width: this.width, + height: this.height + }; + + // 读取meta信息。 + if ( !me._metas && 'image/jpeg' === me.type ) { + Util.parseMeta( me._blob, function( error, ret ) { + me._metas = ret; + me.owner.trigger('load'); + }); + } else { + me.owner.trigger('load'); + } + }; + + img.onerror = function() { + me.owner.trigger('error'); + }; + + me._img = img; + }, + + loadFromBlob: function( blob ) { + var me = this, + img = me._img; + + me._blob = blob; + me.type = blob.type; + img.src = Util.createObjectURL( blob.getSource() ); + me.owner.once( 'load', function() { + Util.revokeObjectURL( img.src ); + }); + }, + + resize: function( width, height ) { + var canvas = this._canvas || + (this._canvas = document.createElement('canvas')); + + this._resize( this._img, canvas, width, height ); + this._blob = null; // 没用了,可以删掉了。 + this.modified = true; + this.owner.trigger( 'complete', 'resize' ); + }, + + crop: function( x, y, w, h, s ) { + var cvs = this._canvas || + (this._canvas = document.createElement('canvas')), + opts = this.options, + img = this._img, + iw = img.naturalWidth, + ih = img.naturalHeight, + orientation = this.getOrientation(); + + s = s || 1; + + // todo 解决 orientation 的问题。 + // values that require 90 degree rotation + // if ( ~[ 5, 6, 7, 8 ].indexOf( orientation ) ) { + + // switch ( orientation ) { + // case 6: + // tmp = x; + // x = y; + // y = iw * s - tmp - w; + // console.log(ih * s, tmp, w) + // break; + // } + + // (w ^= h, h ^= w, w ^= h); + // } + + cvs.width = w; + cvs.height = h; + + opts.preserveHeaders || this._rotate2Orientaion( cvs, orientation ); + this._renderImageToCanvas( cvs, img, -x, -y, iw * s, ih * s ); + + this._blob = null; // 没用了,可以删掉了。 + this.modified = true; + this.owner.trigger( 'complete', 'crop' ); + }, + + getAsBlob: function( type ) { + var blob = this._blob, + opts = this.options, + canvas; + + type = type || this.type; + + // blob需要重新生成。 + if ( this.modified || this.type !== type ) { + canvas = this._canvas; + + if ( type === 'image/jpeg' ) { + + blob = Util.canvasToDataUrl( canvas, type, opts.quality ); + + if ( opts.preserveHeaders && this._metas && + this._metas.imageHead ) { + + blob = Util.dataURL2ArrayBuffer( blob ); + blob = Util.updateImageHead( blob, + this._metas.imageHead ); + blob = Util.arrayBufferToBlob( blob, type ); + return blob; + } + } else { + blob = Util.canvasToDataUrl( canvas, type ); + } + + blob = Util.dataURL2Blob( blob ); + } + + return blob; + }, + + getAsDataUrl: function( type ) { + var opts = this.options; + + type = type || this.type; + + if ( type === 'image/jpeg' ) { + return Util.canvasToDataUrl( this._canvas, type, opts.quality ); + } else { + return this._canvas.toDataURL( type ); + } + }, + + getOrientation: function() { + return this._metas && this._metas.exif && + this._metas.exif.get('Orientation') || 1; + }, + + info: function( val ) { + + // setter + if ( val ) { + this._info = val; + return this; + } + + // getter + return this._info; + }, + + meta: function( val ) { + + // setter + if ( val ) { + this._meta = val; + return this; + } + + // getter + return this._meta; + }, + + destroy: function() { + var canvas = this._canvas; + this._img.onload = null; + + if ( canvas ) { + canvas.getContext('2d') + .clearRect( 0, 0, canvas.width, canvas.height ); + canvas.width = canvas.height = 0; + this._canvas = null; + } + + // 释放内存。非常重要,否则释放不了image的内存。 + this._img.src = BLANK; + this._img = this._blob = null; + }, + + _resize: function( img, cvs, width, height ) { + var opts = this.options, + naturalWidth = img.width, + naturalHeight = img.height, + orientation = this.getOrientation(), + scale, w, h, x, y; + + // values that require 90 degree rotation + if ( ~[ 5, 6, 7, 8 ].indexOf( orientation ) ) { + + // 交换width, height的值。 + width ^= height; + height ^= width; + width ^= height; + } + + scale = Math[ opts.crop ? 'max' : 'min' ]( width / naturalWidth, + height / naturalHeight ); + + // 不允许放大。 + opts.allowMagnify || (scale = Math.min( 1, scale )); + + w = naturalWidth * scale; + h = naturalHeight * scale; + + if ( opts.crop ) { + cvs.width = width; + cvs.height = height; + } else { + cvs.width = w; + cvs.height = h; + } + + x = (cvs.width - w) / 2; + y = (cvs.height - h) / 2; + + opts.preserveHeaders || this._rotate2Orientaion( cvs, orientation ); + + this._renderImageToCanvas( cvs, img, x, y, w, h ); + }, + + _rotate2Orientaion: function( canvas, orientation ) { + var width = canvas.width, + height = canvas.height, + ctx = canvas.getContext('2d'); + + switch ( orientation ) { + case 5: + case 6: + case 7: + case 8: + canvas.width = height; + canvas.height = width; + break; + } + + switch ( orientation ) { + case 2: // horizontal flip + ctx.translate( width, 0 ); + ctx.scale( -1, 1 ); + break; + + case 3: // 180 rotate left + ctx.translate( width, height ); + ctx.rotate( Math.PI ); + break; + + case 4: // vertical flip + ctx.translate( 0, height ); + ctx.scale( 1, -1 ); + break; + + case 5: // vertical flip + 90 rotate right + ctx.rotate( 0.5 * Math.PI ); + ctx.scale( 1, -1 ); + break; + + case 6: // 90 rotate right + ctx.rotate( 0.5 * Math.PI ); + ctx.translate( 0, -height ); + break; + + case 7: // horizontal flip + 90 rotate right + ctx.rotate( 0.5 * Math.PI ); + ctx.translate( width, -height ); + ctx.scale( -1, 1 ); + break; + + case 8: // 90 rotate left + ctx.rotate( -0.5 * Math.PI ); + ctx.translate( -width, 0 ); + break; + } + }, + + // https://github.com/stomita/ios-imagefile-megapixel/ + // blob/master/src/megapix-image.js + _renderImageToCanvas: (function() { + + // 如果不是ios, 不需要这么复杂! + if ( !Base.os.ios ) { + return function( canvas ) { + var args = Base.slice( arguments, 1 ), + ctx = canvas.getContext('2d'); + + ctx.drawImage.apply( ctx, args ); + }; + } + + /** + * Detecting vertical squash in loaded image. + * Fixes a bug which squash image vertically while drawing into + * canvas for some images. + */ + function detectVerticalSquash( img, iw, ih ) { + var canvas = document.createElement('canvas'), + ctx = canvas.getContext('2d'), + sy = 0, + ey = ih, + py = ih, + data, alpha, ratio; + + + canvas.width = 1; + canvas.height = ih; + ctx.drawImage( img, 0, 0 ); + data = ctx.getImageData( 0, 0, 1, ih ).data; + + // search image edge pixel position in case + // it is squashed vertically. + while ( py > sy ) { + alpha = data[ (py - 1) * 4 + 3 ]; + + if ( alpha === 0 ) { + ey = py; + } else { + sy = py; + } + + py = (ey + sy) >> 1; + } + + ratio = (py / ih); + return (ratio === 0) ? 1 : ratio; + } + + // fix ie7 bug + // http://stackoverflow.com/questions/11929099/ + // html5-canvas-drawimage-ratio-bug-ios + if ( Base.os.ios >= 7 ) { + return function( canvas, img, x, y, w, h ) { + var iw = img.naturalWidth, + ih = img.naturalHeight, + vertSquashRatio = detectVerticalSquash( img, iw, ih ); + + return canvas.getContext('2d').drawImage( img, 0, 0, + iw * vertSquashRatio, ih * vertSquashRatio, + x, y, w, h ); + }; + } + + /** + * Detect subsampling in loaded image. + * In iOS, larger images than 2M pixels may be + * subsampled in rendering. + */ + function detectSubsampling( img ) { + var iw = img.naturalWidth, + ih = img.naturalHeight, + canvas, ctx; + + // subsampling may happen overmegapixel image + if ( iw * ih > 1024 * 1024 ) { + canvas = document.createElement('canvas'); + canvas.width = canvas.height = 1; + ctx = canvas.getContext('2d'); + ctx.drawImage( img, -iw + 1, 0 ); + + // subsampled image becomes half smaller in rendering size. + // check alpha channel value to confirm image is covering + // edge pixel or not. if alpha value is 0 + // image is not covering, hence subsampled. + return ctx.getImageData( 0, 0, 1, 1 ).data[ 3 ] === 0; + } else { + return false; + } + } + + + return function( canvas, img, x, y, width, height ) { + var iw = img.naturalWidth, + ih = img.naturalHeight, + ctx = canvas.getContext('2d'), + subsampled = detectSubsampling( img ), + doSquash = this.type === 'image/jpeg', + d = 1024, + sy = 0, + dy = 0, + tmpCanvas, tmpCtx, vertSquashRatio, dw, dh, sx, dx; + + if ( subsampled ) { + iw /= 2; + ih /= 2; + } + + ctx.save(); + tmpCanvas = document.createElement('canvas'); + tmpCanvas.width = tmpCanvas.height = d; + + tmpCtx = tmpCanvas.getContext('2d'); + vertSquashRatio = doSquash ? + detectVerticalSquash( img, iw, ih ) : 1; + + dw = Math.ceil( d * width / iw ); + dh = Math.ceil( d * height / ih / vertSquashRatio ); + + while ( sy < ih ) { + sx = 0; + dx = 0; + while ( sx < iw ) { + tmpCtx.clearRect( 0, 0, d, d ); + tmpCtx.drawImage( img, -sx, -sy ); + ctx.drawImage( tmpCanvas, 0, 0, d, d, + x + dx, y + dy, dw, dh ); + sx += d; + dx += dw; + } + sy += d; + dy += dh; + } + ctx.restore(); + tmpCanvas = tmpCtx = null; + }; + })() + }); + }); + /** + * 这个方式性能不行,但是可以解决android里面的toDataUrl的bug + * android里面toDataUrl('image/jpege')得到的结果却是png. + * + * 所以这里没辙,只能借助这个工具 + * @fileOverview jpeg encoder + */ + define('runtime/html5/jpegencoder',[], function( require, exports, module ) { + + /* + Copyright (c) 2008, Adobe Systems Incorporated + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of Adobe Systems Incorporated nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + /* + JPEG encoder ported to JavaScript and optimized by Andreas Ritter, www.bytestrom.eu, 11/2009 + + Basic GUI blocking jpeg encoder + */ + + function JPEGEncoder(quality) { + var self = this; + var fround = Math.round; + var ffloor = Math.floor; + var YTable = new Array(64); + var UVTable = new Array(64); + var fdtbl_Y = new Array(64); + var fdtbl_UV = new Array(64); + var YDC_HT; + var UVDC_HT; + var YAC_HT; + var UVAC_HT; + + var bitcode = new Array(65535); + var category = new Array(65535); + var outputfDCTQuant = new Array(64); + var DU = new Array(64); + var byteout = []; + var bytenew = 0; + var bytepos = 7; + + var YDU = new Array(64); + var UDU = new Array(64); + var VDU = new Array(64); + var clt = new Array(256); + var RGB_YUV_TABLE = new Array(2048); + var currentQuality; + + var ZigZag = [ + 0, 1, 5, 6,14,15,27,28, + 2, 4, 7,13,16,26,29,42, + 3, 8,12,17,25,30,41,43, + 9,11,18,24,31,40,44,53, + 10,19,23,32,39,45,52,54, + 20,22,33,38,46,51,55,60, + 21,34,37,47,50,56,59,61, + 35,36,48,49,57,58,62,63 + ]; + + var std_dc_luminance_nrcodes = [0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0]; + var std_dc_luminance_values = [0,1,2,3,4,5,6,7,8,9,10,11]; + var std_ac_luminance_nrcodes = [0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d]; + var std_ac_luminance_values = [ + 0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12, + 0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07, + 0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08, + 0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0, + 0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16, + 0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28, + 0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39, + 0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49, + 0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59, + 0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69, + 0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79, + 0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89, + 0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98, + 0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7, + 0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6, + 0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5, + 0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4, + 0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2, + 0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea, + 0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8, + 0xf9,0xfa + ]; + + var std_dc_chrominance_nrcodes = [0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0]; + var std_dc_chrominance_values = [0,1,2,3,4,5,6,7,8,9,10,11]; + var std_ac_chrominance_nrcodes = [0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77]; + var std_ac_chrominance_values = [ + 0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21, + 0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71, + 0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91, + 0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0, + 0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34, + 0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26, + 0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38, + 0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48, + 0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58, + 0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68, + 0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78, + 0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87, + 0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96, + 0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5, + 0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4, + 0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3, + 0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2, + 0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda, + 0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9, + 0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8, + 0xf9,0xfa + ]; + + function initQuantTables(sf){ + var YQT = [ + 16, 11, 10, 16, 24, 40, 51, 61, + 12, 12, 14, 19, 26, 58, 60, 55, + 14, 13, 16, 24, 40, 57, 69, 56, + 14, 17, 22, 29, 51, 87, 80, 62, + 18, 22, 37, 56, 68,109,103, 77, + 24, 35, 55, 64, 81,104,113, 92, + 49, 64, 78, 87,103,121,120,101, + 72, 92, 95, 98,112,100,103, 99 + ]; + + for (var i = 0; i < 64; i++) { + var t = ffloor((YQT[i]*sf+50)/100); + if (t < 1) { + t = 1; + } else if (t > 255) { + t = 255; + } + YTable[ZigZag[i]] = t; + } + var UVQT = [ + 17, 18, 24, 47, 99, 99, 99, 99, + 18, 21, 26, 66, 99, 99, 99, 99, + 24, 26, 56, 99, 99, 99, 99, 99, + 47, 66, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99 + ]; + for (var j = 0; j < 64; j++) { + var u = ffloor((UVQT[j]*sf+50)/100); + if (u < 1) { + u = 1; + } else if (u > 255) { + u = 255; + } + UVTable[ZigZag[j]] = u; + } + var aasf = [ + 1.0, 1.387039845, 1.306562965, 1.175875602, + 1.0, 0.785694958, 0.541196100, 0.275899379 + ]; + var k = 0; + for (var row = 0; row < 8; row++) + { + for (var col = 0; col < 8; col++) + { + fdtbl_Y[k] = (1.0 / (YTable [ZigZag[k]] * aasf[row] * aasf[col] * 8.0)); + fdtbl_UV[k] = (1.0 / (UVTable[ZigZag[k]] * aasf[row] * aasf[col] * 8.0)); + k++; + } + } + } + + function computeHuffmanTbl(nrcodes, std_table){ + var codevalue = 0; + var pos_in_table = 0; + var HT = new Array(); + for (var k = 1; k <= 16; k++) { + for (var j = 1; j <= nrcodes[k]; j++) { + HT[std_table[pos_in_table]] = []; + HT[std_table[pos_in_table]][0] = codevalue; + HT[std_table[pos_in_table]][1] = k; + pos_in_table++; + codevalue++; + } + codevalue*=2; + } + return HT; + } + + function initHuffmanTbl() + { + YDC_HT = computeHuffmanTbl(std_dc_luminance_nrcodes,std_dc_luminance_values); + UVDC_HT = computeHuffmanTbl(std_dc_chrominance_nrcodes,std_dc_chrominance_values); + YAC_HT = computeHuffmanTbl(std_ac_luminance_nrcodes,std_ac_luminance_values); + UVAC_HT = computeHuffmanTbl(std_ac_chrominance_nrcodes,std_ac_chrominance_values); + } + + function initCategoryNumber() + { + var nrlower = 1; + var nrupper = 2; + for (var cat = 1; cat <= 15; cat++) { + //Positive numbers + for (var nr = nrlower; nr>0] = 38470 * i; + RGB_YUV_TABLE[(i+ 512)>>0] = 7471 * i + 0x8000; + RGB_YUV_TABLE[(i+ 768)>>0] = -11059 * i; + RGB_YUV_TABLE[(i+1024)>>0] = -21709 * i; + RGB_YUV_TABLE[(i+1280)>>0] = 32768 * i + 0x807FFF; + RGB_YUV_TABLE[(i+1536)>>0] = -27439 * i; + RGB_YUV_TABLE[(i+1792)>>0] = - 5329 * i; + } + } + + // IO functions + function writeBits(bs) + { + var value = bs[0]; + var posval = bs[1]-1; + while ( posval >= 0 ) { + if (value & (1 << posval) ) { + bytenew |= (1 << bytepos); + } + posval--; + bytepos--; + if (bytepos < 0) { + if (bytenew == 0xFF) { + writeByte(0xFF); + writeByte(0); + } + else { + writeByte(bytenew); + } + bytepos=7; + bytenew=0; + } + } + } + + function writeByte(value) + { + byteout.push(clt[value]); // write char directly instead of converting later + } + + function writeWord(value) + { + writeByte((value>>8)&0xFF); + writeByte((value )&0xFF); + } + + // DCT & quantization core + function fDCTQuant(data, fdtbl) + { + var d0, d1, d2, d3, d4, d5, d6, d7; + /* Pass 1: process rows. */ + var dataOff=0; + var i; + var I8 = 8; + var I64 = 64; + for (i=0; i 0.0) ? ((fDCTQuant + 0.5)|0) : ((fDCTQuant - 0.5)|0); + //outputfDCTQuant[i] = fround(fDCTQuant); + + } + return outputfDCTQuant; + } + + function writeAPP0() + { + writeWord(0xFFE0); // marker + writeWord(16); // length + writeByte(0x4A); // J + writeByte(0x46); // F + writeByte(0x49); // I + writeByte(0x46); // F + writeByte(0); // = "JFIF",'\0' + writeByte(1); // versionhi + writeByte(1); // versionlo + writeByte(0); // xyunits + writeWord(1); // xdensity + writeWord(1); // ydensity + writeByte(0); // thumbnwidth + writeByte(0); // thumbnheight + } + + function writeSOF0(width, height) + { + writeWord(0xFFC0); // marker + writeWord(17); // length, truecolor YUV JPG + writeByte(8); // precision + writeWord(height); + writeWord(width); + writeByte(3); // nrofcomponents + writeByte(1); // IdY + writeByte(0x11); // HVY + writeByte(0); // QTY + writeByte(2); // IdU + writeByte(0x11); // HVU + writeByte(1); // QTU + writeByte(3); // IdV + writeByte(0x11); // HVV + writeByte(1); // QTV + } + + function writeDQT() + { + writeWord(0xFFDB); // marker + writeWord(132); // length + writeByte(0); + for (var i=0; i<64; i++) { + writeByte(YTable[i]); + } + writeByte(1); + for (var j=0; j<64; j++) { + writeByte(UVTable[j]); + } + } + + function writeDHT() + { + writeWord(0xFFC4); // marker + writeWord(0x01A2); // length + + writeByte(0); // HTYDCinfo + for (var i=0; i<16; i++) { + writeByte(std_dc_luminance_nrcodes[i+1]); + } + for (var j=0; j<=11; j++) { + writeByte(std_dc_luminance_values[j]); + } + + writeByte(0x10); // HTYACinfo + for (var k=0; k<16; k++) { + writeByte(std_ac_luminance_nrcodes[k+1]); + } + for (var l=0; l<=161; l++) { + writeByte(std_ac_luminance_values[l]); + } + + writeByte(1); // HTUDCinfo + for (var m=0; m<16; m++) { + writeByte(std_dc_chrominance_nrcodes[m+1]); + } + for (var n=0; n<=11; n++) { + writeByte(std_dc_chrominance_values[n]); + } + + writeByte(0x11); // HTUACinfo + for (var o=0; o<16; o++) { + writeByte(std_ac_chrominance_nrcodes[o+1]); + } + for (var p=0; p<=161; p++) { + writeByte(std_ac_chrominance_values[p]); + } + } + + function writeSOS() + { + writeWord(0xFFDA); // marker + writeWord(12); // length + writeByte(3); // nrofcomponents + writeByte(1); // IdY + writeByte(0); // HTY + writeByte(2); // IdU + writeByte(0x11); // HTU + writeByte(3); // IdV + writeByte(0x11); // HTV + writeByte(0); // Ss + writeByte(0x3f); // Se + writeByte(0); // Bf + } + + function processDU(CDU, fdtbl, DC, HTDC, HTAC){ + var EOB = HTAC[0x00]; + var M16zeroes = HTAC[0xF0]; + var pos; + var I16 = 16; + var I63 = 63; + var I64 = 64; + var DU_DCT = fDCTQuant(CDU, fdtbl); + //ZigZag reorder + for (var j=0;j0)&&(DU[end0pos]==0); end0pos--) {}; + //end0pos = first element in reverse order !=0 + if ( end0pos == 0) { + writeBits(EOB); + return DC; + } + var i = 1; + var lng; + while ( i <= end0pos ) { + var startpos = i; + for (; (DU[i]==0) && (i<=end0pos); ++i) {} + var nrzeroes = i-startpos; + if ( nrzeroes >= I16 ) { + lng = nrzeroes>>4; + for (var nrmarker=1; nrmarker <= lng; ++nrmarker) + writeBits(M16zeroes); + nrzeroes = nrzeroes&0xF; + } + pos = 32767+DU[i]; + writeBits(HTAC[(nrzeroes<<4)+category[pos]]); + writeBits(bitcode[pos]); + i++; + } + if ( end0pos != I63 ) { + writeBits(EOB); + } + return DC; + } + + function initCharLookupTable(){ + var sfcc = String.fromCharCode; + for(var i=0; i < 256; i++){ ///// ACHTUNG // 255 + clt[i] = sfcc(i); + } + } + + this.encode = function(image,quality) // image data object + { + // var time_start = new Date().getTime(); + + if(quality) setQuality(quality); + + // Initialize bit writer + byteout = new Array(); + bytenew=0; + bytepos=7; + + // Add JPEG headers + writeWord(0xFFD8); // SOI + writeAPP0(); + writeDQT(); + writeSOF0(image.width,image.height); + writeDHT(); + writeSOS(); + + + // Encode 8x8 macroblocks + var DCY=0; + var DCU=0; + var DCV=0; + + bytenew=0; + bytepos=7; + + + this.encode.displayName = "_encode_"; + + var imageData = image.data; + var width = image.width; + var height = image.height; + + var quadWidth = width*4; + var tripleWidth = width*3; + + var x, y = 0; + var r, g, b; + var start,p, col,row,pos; + while(y < height){ + x = 0; + while(x < quadWidth){ + start = quadWidth * y + x; + p = start; + col = -1; + row = 0; + + for(pos=0; pos < 64; pos++){ + row = pos >> 3;// /8 + col = ( pos & 7 ) * 4; // %8 + p = start + ( row * quadWidth ) + col; + + if(y+row >= height){ // padding bottom + p-= (quadWidth*(y+1+row-height)); + } + + if(x+col >= quadWidth){ // padding right + p-= ((x+col) - quadWidth +4) + } + + r = imageData[ p++ ]; + g = imageData[ p++ ]; + b = imageData[ p++ ]; + + + /* // calculate YUV values dynamically + YDU[pos]=((( 0.29900)*r+( 0.58700)*g+( 0.11400)*b))-128; //-0x80 + UDU[pos]=(((-0.16874)*r+(-0.33126)*g+( 0.50000)*b)); + VDU[pos]=((( 0.50000)*r+(-0.41869)*g+(-0.08131)*b)); + */ + + // use lookup table (slightly faster) + YDU[pos] = ((RGB_YUV_TABLE[r] + RGB_YUV_TABLE[(g + 256)>>0] + RGB_YUV_TABLE[(b + 512)>>0]) >> 16)-128; + UDU[pos] = ((RGB_YUV_TABLE[(r + 768)>>0] + RGB_YUV_TABLE[(g + 1024)>>0] + RGB_YUV_TABLE[(b + 1280)>>0]) >> 16)-128; + VDU[pos] = ((RGB_YUV_TABLE[(r + 1280)>>0] + RGB_YUV_TABLE[(g + 1536)>>0] + RGB_YUV_TABLE[(b + 1792)>>0]) >> 16)-128; + + } + + DCY = processDU(YDU, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCU = processDU(UDU, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); + DCV = processDU(VDU, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); + x+=32; + } + y+=8; + } + + + //////////////////////////////////////////////////////////////// + + // Do the bit alignment of the EOI marker + if ( bytepos >= 0 ) { + var fillbits = []; + fillbits[1] = bytepos+1; + fillbits[0] = (1<<(bytepos+1))-1; + writeBits(fillbits); + } + + writeWord(0xFFD9); //EOI + + var jpegDataUri = 'data:image/jpeg;base64,' + btoa(byteout.join('')); + + byteout = []; + + // benchmarking + // var duration = new Date().getTime() - time_start; + // console.log('Encoding time: '+ currentQuality + 'ms'); + // + + return jpegDataUri + } + + function setQuality(quality){ + if (quality <= 0) { + quality = 1; + } + if (quality > 100) { + quality = 100; + } + + if(currentQuality == quality) return // don't recalc if unchanged + + var sf = 0; + if (quality < 50) { + sf = Math.floor(5000 / quality); + } else { + sf = Math.floor(200 - quality*2); + } + + initQuantTables(sf); + currentQuality = quality; + // console.log('Quality set to: '+quality +'%'); + } + + function init(){ + // var time_start = new Date().getTime(); + if(!quality) quality = 50; + // Create tables + initCharLookupTable() + initHuffmanTbl(); + initCategoryNumber(); + initRGBYUVTable(); + + setQuality(quality); + // var duration = new Date().getTime() - time_start; + // console.log('Initialization '+ duration + 'ms'); + } + + init(); + + }; + + JPEGEncoder.encode = function( data, quality ) { + var encoder = new JPEGEncoder( quality ); + + return encoder.encode( data ); + } + + return JPEGEncoder; + }); + /** + * @fileOverview Fix android canvas.toDataUrl bug. + */ + define('runtime/html5/androidpatch',[ + 'runtime/html5/util', + 'runtime/html5/jpegencoder', + 'base' + ], function( Util, encoder, Base ) { + var origin = Util.canvasToDataUrl, + supportJpeg; + + Util.canvasToDataUrl = function( canvas, type, quality ) { + var ctx, w, h, fragement, parts; + + // 非android手机直接跳过。 + if ( !Base.os.android ) { + return origin.apply( null, arguments ); + } + + // 检测是否canvas支持jpeg导出,根据数据格式来判断。 + // JPEG 前两位分别是:255, 216 + if ( type === 'image/jpeg' && typeof supportJpeg === 'undefined' ) { + fragement = origin.apply( null, arguments ); + + parts = fragement.split(','); + + if ( ~parts[ 0 ].indexOf('base64') ) { + fragement = atob( parts[ 1 ] ); + } else { + fragement = decodeURIComponent( parts[ 1 ] ); + } + + fragement = fragement.substring( 0, 2 ); + + supportJpeg = fragement.charCodeAt( 0 ) === 255 && + fragement.charCodeAt( 1 ) === 216; + } + + // 只有在android环境下才修复 + if ( type === 'image/jpeg' && !supportJpeg ) { + w = canvas.width; + h = canvas.height; + ctx = canvas.getContext('2d'); + + return encoder.encode( ctx.getImageData( 0, 0, w, h ), quality ); + } + + return origin.apply( null, arguments ); + }; + }); + /** + * @fileOverview Transport + * @todo 支持chunked传输,优势: + * 可以将大文件分成小块,挨个传输,可以提高大文件成功率,当失败的时候,也只需要重传那小部分, + * 而不需要重头再传一次。另外断点续传也需要用chunked方式。 + */ + define('runtime/html5/transport',[ + 'base', + 'runtime/html5/runtime' + ], function( Base, Html5Runtime ) { + + var noop = Base.noop, + $ = Base.$; + + return Html5Runtime.register( 'Transport', { + init: function() { + this._status = 0; + this._response = null; + }, + + send: function() { + var owner = this.owner, + opts = this.options, + xhr = this._initAjax(), + blob = owner._blob, + server = opts.server, + formData, binary, fr; + + if ( opts.sendAsBinary ) { + server += (/\?/.test( server ) ? '&' : '?') + + $.param( owner._formData ); + + binary = blob.getSource(); + } else { + formData = new FormData(); + $.each( owner._formData, function( k, v ) { + formData.append( k, v ); + }); + + formData.append( opts.fileVal, blob.getSource(), + opts.filename || owner._formData.name || '' ); + } + + if ( opts.withCredentials && 'withCredentials' in xhr ) { + xhr.open( opts.method, server, true ); + xhr.withCredentials = true; + } else { + xhr.open( opts.method, server ); + } + + this._setRequestHeader( xhr, opts.headers ); + + if ( binary ) { + // 强制设置成 content-type 为文件流。 + xhr.overrideMimeType && + xhr.overrideMimeType('application/octet-stream'); + + // android直接发送blob会导致服务端接收到的是空文件。 + // bug详情。 + // https://code.google.com/p/android/issues/detail?id=39882 + // 所以先用fileReader读取出来再通过arraybuffer的方式发送。 + if ( Base.os.android ) { + fr = new FileReader(); + + fr.onload = function() { + xhr.send( this.result ); + fr = fr.onload = null; + }; + + fr.readAsArrayBuffer( binary ); + } else { + xhr.send( binary ); + } + } else { + xhr.send( formData ); + } + }, + + getResponse: function() { + return this._response; + }, + + getResponseAsJson: function() { + return this._parseJson( this._response ); + }, + + getStatus: function() { + return this._status; + }, + + abort: function() { + var xhr = this._xhr; + + if ( xhr ) { + xhr.upload.onprogress = noop; + xhr.onreadystatechange = noop; + xhr.abort(); + + this._xhr = xhr = null; + } + }, + + destroy: function() { + this.abort(); + }, + + _initAjax: function() { + var me = this, + xhr = new XMLHttpRequest(), + opts = this.options; + + if ( opts.withCredentials && !('withCredentials' in xhr) && + typeof XDomainRequest !== 'undefined' ) { + xhr = new XDomainRequest(); + } + + xhr.upload.onprogress = function( e ) { + var percentage = 0; + + if ( e.lengthComputable ) { + percentage = e.loaded / e.total; + } + + return me.trigger( 'progress', percentage ); + }; + + xhr.onreadystatechange = function() { + + if ( xhr.readyState !== 4 ) { + return; + } + + xhr.upload.onprogress = noop; + xhr.onreadystatechange = noop; + me._xhr = null; + me._status = xhr.status; + + if ( xhr.status >= 200 && xhr.status < 300 ) { + me._response = xhr.responseText; + return me.trigger('load'); + } else if ( xhr.status >= 500 && xhr.status < 600 ) { + me._response = xhr.responseText; + return me.trigger( 'error', 'server' ); + } + + + return me.trigger( 'error', me._status ? 'http' : 'abort' ); + }; + + me._xhr = xhr; + return xhr; + }, + + _setRequestHeader: function( xhr, headers ) { + $.each( headers, function( key, val ) { + xhr.setRequestHeader( key, val ); + }); + }, + + _parseJson: function( str ) { + var json; + + try { + json = JSON.parse( str ); + } catch ( ex ) { + json = {}; + } + + return json; + } + }); + }); + define('webuploader',[ + 'base', + 'widgets/filepicker', + 'widgets/image', + 'widgets/queue', + 'widgets/runtime', + 'widgets/upload', + 'widgets/log', + 'runtime/html5/blob', + 'runtime/html5/filepicker', + 'runtime/html5/imagemeta/exif', + 'runtime/html5/image', + 'runtime/html5/androidpatch', + 'runtime/html5/transport' + ], function( Base ) { + return Base; + }); + return require('webuploader'); +}); diff --git a/static/plugins/webuploader/webuploader.custom.min.js b/static/plugins/webuploader/webuploader.custom.min.js new file mode 100644 index 00000000..09728105 --- /dev/null +++ b/static/plugins/webuploader/webuploader.custom.min.js @@ -0,0 +1,2 @@ +/* WebUploader 0.1.5 */!function(a,b){var c,d={},e=function(a,b){var c,d,e;if("string"==typeof a)return h(a);for(c=[],d=a.length,e=0;d>e;e++)c.push(h(a[e]));return b.apply(null,c)},f=function(a,b,c){2===arguments.length&&(c=b,b=null),e(b||[],function(){g(a,c,arguments)})},g=function(a,b,c){var f,g={exports:b};"function"==typeof b&&(c.length||(c=[e,g.exports,g]),f=b.apply(null,c),void 0!==f&&(g.exports=f)),d[a]=g.exports},h=function(b){var c=d[b]||a[b];if(!c)throw new Error("`"+b+"` is undefined");return c},i=function(a){var b,c,e,f,g,h;h=function(a){return a&&a.charAt(0).toUpperCase()+a.substr(1)};for(b in d)if(c=a,d.hasOwnProperty(b)){for(e=b.split("/"),g=h(e.pop());f=h(e.shift());)c[f]=c[f]||{},c=c[f];c[g]=d[b]}return a},j=function(c){return a.__dollar=c,i(b(a,f,e))};"object"==typeof module&&"object"==typeof module.exports?module.exports=j():"function"==typeof define&&define.amd?define(["jquery"],j):(c=a.WebUploader,a.WebUploader=j(),a.WebUploader.noConflict=function(){a.WebUploader=c})}(window,function(a,b,c){return b("dollar-third",[],function(){var b=a.__dollar||a.jQuery||a.Zepto;if(!b)throw new Error("jQuery or Zepto not found!");return b}),b("dollar",["dollar-third"],function(a){return a}),b("promise-builtin",["dollar"],function(a){function b(b){var c,d,e,f,g,h,i=[],j=!b&&[],k=function(a){for(h=a,c=!0,g=e||0,e=0,f=i.length,d=!0;i&&f>g;g++)i[g].apply(a[0],a[1]);d=!1,i&&(j?j.length&&k(j.shift()):i=[])},l={add:function(){if(i){var b=i.length;!function c(b){a.each(b,function(b,d){var e=a.type(d);"function"===e?i.push(d):d&&d.length&&"string"!==e&&c(d)})}(arguments),d?f=i.length:h&&(e=b,k(h))}return this},disable:function(){return i=j=h=void 0,this},lock:function(){return j=void 0,h||l.disable(),this},fireWith:function(a,b){return!i||c&&!j||(b=b||[],b=[a,b.slice?b.slice():b],d?j.push(b):k(b)),this},fire:function(){return l.fireWith(this,arguments),this}};return l}function c(d){var e=[["resolve","done",b(!0),"resolved"],["reject","fail",b(!0),"rejected"],["notify","progress",b()]],f="pending",g={state:function(){return f},always:function(){return h.done(arguments).fail(arguments),this},then:function(){var b=arguments;return c(function(c){a.each(e,function(d,e){var f=e[0],i=a.isFunction(b[d])&&b[d];h[e[1]](function(){var b;b=i&&i.apply(this,arguments),b&&a.isFunction(b.promise)?b.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f+"With"](this===g?c.promise():this,i?[b]:arguments)})}),b=null}).promise()},promise:function(b){return null!=b?a.extend(b,g):g}},h={};return g.pipe=g.then,a.each(e,function(a,b){var c=b[2],d=b[3];g[b[1]]=c.add,d&&c.add(function(){f=d},e[1^a][2].disable,e[2][2].lock),h[b[0]]=function(){return h[b[0]+"With"](this===h?g:this,arguments),this},h[b[0]+"With"]=c.fireWith}),g.promise(h),d&&d.call(h,h),h}var d;return d={Deferred:c,isPromise:function(a){return a&&"function"==typeof a.then},when:function(b){var d,e,f,g=0,h=[].slice,i=h.call(arguments),j=i.length,k=1!==j||b&&a.isFunction(b.promise)?j:0,l=1===k?b:c(),m=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?h.call(arguments):e,c===d?l.notifyWith(b,c):--k||l.resolveWith(b,c)}};if(j>1)for(d=new Array(j),e=new Array(j),f=new Array(j);j>g;g++)i[g]&&a.isFunction(i[g].promise)?i[g].promise().done(m(g,f,i)).fail(l.reject).progress(m(g,e,d)):--k;return k||l.resolveWith(f,i),l.promise()}}}),b("promise",["promise-builtin"],function(a){return a}),b("base",["dollar","promise"],function(b,c){function d(a){return function(){return h.apply(a,arguments)}}function e(a,b){return function(){return a.apply(b,arguments)}}function f(a){var b;return Object.create?Object.create(a):(b=function(){},b.prototype=a,new b)}var g=function(){},h=Function.call;return{version:"0.1.5",$:b,Deferred:c.Deferred,isPromise:c.isPromise,when:c.when,browser:function(a){var b={},c=a.match(/WebKit\/([\d.]+)/),d=a.match(/Chrome\/([\d.]+)/)||a.match(/CriOS\/([\d.]+)/),e=a.match(/MSIE\s([\d\.]+)/)||a.match(/(?:trident)(?:.*rv:([\w.]+))?/i),f=a.match(/Firefox\/([\d.]+)/),g=a.match(/Safari\/([\d.]+)/),h=a.match(/OPR\/([\d.]+)/);return c&&(b.webkit=parseFloat(c[1])),d&&(b.chrome=parseFloat(d[1])),e&&(b.ie=parseFloat(e[1])),f&&(b.firefox=parseFloat(f[1])),g&&(b.safari=parseFloat(g[1])),h&&(b.opera=parseFloat(h[1])),b}(navigator.userAgent),os:function(a){var b={},c=a.match(/(?:Android);?[\s\/]+([\d.]+)?/),d=a.match(/(?:iPad|iPod|iPhone).*OS\s([\d_]+)/);return c&&(b.android=parseFloat(c[1])),d&&(b.ios=parseFloat(d[1].replace(/_/g,"."))),b}(navigator.userAgent),inherits:function(a,c,d){var e;return"function"==typeof c?(e=c,c=null):e=c&&c.hasOwnProperty("constructor")?c.constructor:function(){return a.apply(this,arguments)},b.extend(!0,e,a,d||{}),e.__super__=a.prototype,e.prototype=f(a.prototype),c&&b.extend(!0,e.prototype,c),e},noop:g,bindFn:e,log:function(){return a.console?e(console.log,console):g}(),nextTick:function(){return function(a){setTimeout(a,1)}}(),slice:d([].slice),guid:function(){var a=0;return function(b){for(var c=(+new Date).toString(32),d=0;5>d;d++)c+=Math.floor(65535*Math.random()).toString(32);return(b||"wu_")+c+(a++).toString(32)}}(),formatSize:function(a,b,c){var d;for(c=c||["B","K","M","G","TB"];(d=c.shift())&&a>1024;)a/=1024;return("B"===d?a:a.toFixed(b||2))+d}}}),b("mediator",["base"],function(a){function b(a,b,c,d){return f.grep(a,function(a){return!(!a||b&&a.e!==b||c&&a.cb!==c&&a.cb._cb!==c||d&&a.ctx!==d)})}function c(a,b,c){f.each((a||"").split(h),function(a,d){c(d,b)})}function d(a,b){for(var c,d=!1,e=-1,f=a.length;++e1?void(d.isPlainObject(b)&&d.isPlainObject(c[a])?d.extend(c[a],b):c[a]=b):a?c[a]:c},getStats:function(){var a=this.request("get-stats");return a?{successNum:a.numOfSuccess,progressNum:a.numOfProgress,cancelNum:a.numOfCancel,invalidNum:a.numOfInvalid,uploadFailNum:a.numOfUploadFailed,queueNum:a.numOfQueue,interruptNum:a.numofInterrupt}:{}},trigger:function(a){var c=[].slice.call(arguments,1),e=this.options,f="on"+a.substring(0,1).toUpperCase()+a.substring(1);return b.trigger.apply(this,arguments)===!1||d.isFunction(e[f])&&e[f].apply(this,c)===!1||d.isFunction(this[f])&&this[f].apply(this,c)===!1||b.trigger.apply(b,[this,a].concat(c))===!1?!1:!0},destroy:function(){this.request("destroy",arguments),this.off()},request:a.noop}),a.create=c.create=function(a){return new c(a)},a.Uploader=c,c}),b("runtime/runtime",["base","mediator"],function(a,b){function c(b){this.options=d.extend({container:document.body},b),this.uid=a.guid("rt_")}var d=a.$,e={},f=function(a){for(var b in a)if(a.hasOwnProperty(b))return b;return null};return d.extend(c.prototype,{getContainer:function(){var a,b,c=this.options;return this._container?this._container:(a=d(c.container||document.body),b=d(document.createElement("div")),b.attr("id","rt_"+this.uid),b.css({position:"absolute",top:"0px",left:"0px",width:"1px",height:"1px",overflow:"hidden"}),a.append(b),a.addClass("webuploader-container"),this._container=b,this._parent=a,b)},init:a.noop,exec:a.noop,destroy:function(){this._container&&this._container.remove(),this._parent&&this._parent.removeClass("webuploader-container"),this.off()}}),c.orders="html5,flash",c.addRuntime=function(a,b){e[a]=b},c.hasRuntime=function(a){return!!(a?e[a]:f(e))},c.create=function(a,b){var g,h;if(b=b||c.orders,d.each(b.split(/\s*,\s*/g),function(){return e[this]?(g=this,!1):void 0}),g=g||f(e),!g)throw new Error("Runtime Error");return h=new e[g](a)},b.installTo(c.prototype),c}),b("runtime/client",["base","mediator","runtime/runtime"],function(a,b,c){function d(b,d){var f,g=a.Deferred();this.uid=a.guid("client_"),this.runtimeReady=function(a){return g.done(a)},this.connectRuntime=function(b,h){if(f)throw new Error("already connected!");return g.done(h),"string"==typeof b&&e.get(b)&&(f=e.get(b)),f=f||e.get(null,d),f?(a.$.extend(f.options,b),f.__promise.then(g.resolve),f.__client++):(f=c.create(b,b.runtimeOrder),f.__promise=g.promise(),f.once("ready",g.resolve),f.init(),e.add(f),f.__client=1),d&&(f.__standalone=d),f},this.getRuntime=function(){return f},this.disconnectRuntime=function(){f&&(f.__client--,f.__client<=0&&(e.remove(f),delete f.__promise,f.destroy()),f=null)},this.exec=function(){if(f){var c=a.slice(arguments);return b&&c.unshift(b),f.exec.apply(this,c)}},this.getRuid=function(){return f&&f.uid},this.destroy=function(a){return function(){a&&a.apply(this,arguments),this.trigger("destroy"),this.off(),this.exec("destroy"),this.disconnectRuntime()}}(this.destroy)}var e;return e=function(){var a={};return{add:function(b){a[b.uid]=b},get:function(b,c){var d;if(b)return a[b];for(d in a)if(!c||!a[d].__standalone)return a[d];return null},remove:function(b){delete a[b.uid]}}}(),b.installTo(d.prototype),d}),b("lib/blob",["base","runtime/client"],function(a,b){function c(a,c){var d=this;d.source=c,d.ruid=a,this.size=c.size||0,this.type=!c.type&&this.ext&&~"jpg,jpeg,png,gif,bmp".indexOf(this.ext)?"image/"+("jpg"===this.ext?"jpeg":this.ext):c.type||"application/octet-stream",b.call(d,"Blob"),this.uid=c.uid||this.uid,a&&d.connectRuntime(a)}return a.inherits(b,{constructor:c,slice:function(a,b){return this.exec("slice",a,b)},getSource:function(){return this.source}}),c}),b("lib/file",["base","lib/blob"],function(a,b){function c(a,c){var f;this.name=c.name||"untitled"+d++,f=e.exec(c.name)?RegExp.$1.toLowerCase():"",!f&&c.type&&(f=/\/(jpg|jpeg|png|gif|bmp)$/i.exec(c.type)?RegExp.$1.toLowerCase():"",this.name+="."+f),this.ext=f,this.lastModifiedDate=c.lastModifiedDate||(new Date).toLocaleString(),b.apply(this,arguments)}var d=1,e=/\.([^.]+)$/;return a.inherits(b,c)}),b("lib/filepicker",["base","runtime/client","lib/file"],function(b,c,d){function e(a){if(a=this.options=f.extend({},e.options,a),a.container=f(a.id),!a.container.length)throw new Error("按钮指定错误");a.innerHTML=a.innerHTML||a.label||a.container.html()||"",a.button=f(a.button||document.createElement("div")),a.button.html(a.innerHTML),a.container.html(a.button),c.call(this,"FilePicker",!0)}var f=b.$;return e.options={button:null,container:null,label:null,innerHTML:null,multiple:!0,accept:null,name:"file"},b.inherits(c,{constructor:e,init:function(){var c=this,e=c.options,g=e.button;g.addClass("webuploader-pick"),c.on("all",function(a){var b;switch(a){case"mouseenter":g.addClass("webuploader-pick-hover");break;case"mouseleave":g.removeClass("webuploader-pick-hover");break;case"change":b=c.exec("getFiles"),c.trigger("select",f.map(b,function(a){return a=new d(c.getRuid(),a),a._refer=e.container,a}),e.container)}}),c.connectRuntime(e,function(){c.refresh(),c.exec("init",e),c.trigger("ready")}),this._resizeHandler=b.bindFn(this.refresh,this),f(a).on("resize",this._resizeHandler)},refresh:function(){var a=this.getRuntime().getContainer(),b=this.options.button,c=b.outerWidth?b.outerWidth():b.width(),d=b.outerHeight?b.outerHeight():b.height(),e=b.offset();c&&d&&a.css({bottom:"auto",right:"auto",width:c+"px",height:d+"px"}).offset(e)},enable:function(){var a=this.options.button;a.removeClass("webuploader-pick-disable"),this.refresh()},disable:function(){var a=this.options.button;this.getRuntime().getContainer().css({top:"-99999px"}),a.addClass("webuploader-pick-disable")},destroy:function(){var b=this.options.button;f(a).off("resize",this._resizeHandler),b.removeClass("webuploader-pick-disable webuploader-pick-hover webuploader-pick")}}),e}),b("widgets/widget",["base","uploader"],function(a,b){function c(a){if(!a)return!1;var b=a.length,c=e.type(a);return 1===a.nodeType&&b?!0:"array"===c||"function"!==c&&"string"!==c&&(0===b||"number"==typeof b&&b>0&&b-1 in a)}function d(a){this.owner=a,this.options=a.options}var e=a.$,f=b.prototype._init,g=b.prototype.destroy,h={},i=[];return e.extend(d.prototype,{init:a.noop,invoke:function(a,b){var c=this.responseMap;return c&&a in c&&c[a]in this&&e.isFunction(this[c[a]])?this[c[a]].apply(this,b):h},request:function(){return this.owner.request.apply(this.owner,arguments)}}),e.extend(b.prototype,{_init:function(){var a=this,b=a._widgets=[],c=a.options.disableWidgets||"";return e.each(i,function(d,e){(!c||!~c.indexOf(e._name))&&b.push(new e(a))}),f.apply(a,arguments)},request:function(b,d,e){var f,g,i,j,k=0,l=this._widgets,m=l&&l.length,n=[],o=[];for(d=c(d)?d:[d];m>k;k++)f=l[k],g=f.invoke(b,d),g!==h&&(a.isPromise(g)?o.push(g):n.push(g));return e||o.length?(i=a.when.apply(a,o),j=i.pipe?"pipe":"then",i[j](function(){var b=a.Deferred(),c=arguments;return 1===c.length&&(c=c[0]),setTimeout(function(){b.resolve(c)},1),b.promise()})[e?j:"done"](e||a.noop)):n[0]},destroy:function(){g.apply(this,arguments),this._widgets=null}}),b.register=d.register=function(b,c){var f,g={init:"init",destroy:"destroy",name:"anonymous"};return 1===arguments.length?(c=b,e.each(c,function(a){return"_"===a[0]||"name"===a?void("name"===a&&(g.name=c.name)):void(g[a.replace(/[A-Z]/g,"-$&").toLowerCase()]=a)})):g=e.extend(g,b),c.responseMap=g,f=a.inherits(d,c),f._name=g.name,i.push(f),f},b.unRegister=d.unRegister=function(a){if(a&&"anonymous"!==a)for(var b=i.length;b--;)i[b]._name===a&&i.splice(b,1)},d}),b("widgets/filepicker",["base","uploader","lib/filepicker","widgets/widget"],function(a,b,c){var d=a.$;return d.extend(b.options,{pick:null,accept:null}),b.register({name:"picker",init:function(a){return this.pickers=[],a.pick&&this.addBtn(a.pick)},refresh:function(){d.each(this.pickers,function(){this.refresh()})},addBtn:function(b){var e=this,f=e.options,g=f.accept,h=[];if(b)return d.isPlainObject(b)||(b={id:b}),d(b.id).each(function(){var i,j,k;k=a.Deferred(),i=d.extend({},b,{accept:d.isPlainObject(g)?[g]:g,swf:f.swf,runtimeOrder:f.runtimeOrder,id:this}),j=new c(i),j.once("ready",k.resolve),j.on("select",function(a){e.owner.request("add-file",[a])}),j.init(),e.pickers.push(j),h.push(k.promise())}),a.when.apply(a,h)},disable:function(){d.each(this.pickers,function(){this.disable()})},enable:function(){d.each(this.pickers,function(){this.enable()})},destroy:function(){d.each(this.pickers,function(){this.destroy()}),this.pickers=null}})}),b("lib/image",["base","runtime/client","lib/blob"],function(a,b,c){function d(a){this.options=e.extend({},d.options,a),b.call(this,"Image"),this.on("load",function(){this._info=this.exec("info"),this._meta=this.exec("meta")})}var e=a.$;return d.options={quality:90,crop:!1,preserveHeaders:!1,allowMagnify:!1},a.inherits(b,{constructor:d,info:function(a){return a?(this._info=a,this):this._info},meta:function(a){return a?(this._meta=a,this):this._meta},loadFromBlob:function(a){var b=this,c=a.getRuid();this.connectRuntime(c,function(){b.exec("init",b.options),b.exec("loadFromBlob",a)})},resize:function(){var b=a.slice(arguments);return this.exec.apply(this,["resize"].concat(b))},crop:function(){var b=a.slice(arguments);return this.exec.apply(this,["crop"].concat(b))},getAsDataUrl:function(a){return this.exec("getAsDataUrl",a)},getAsBlob:function(a){var b=this.exec("getAsBlob",a);return new c(this.getRuid(),b)}}),d}),b("widgets/image",["base","uploader","lib/image","widgets/widget"],function(a,b,c){var d,e=a.$;return d=function(a){var b=0,c=[],d=function(){for(var d;c.length&&a>b;)d=c.shift(),b+=d[0],d[1]()};return function(a,e,f){c.push([e,f]),a.once("destroy",function(){b-=e,setTimeout(d,1)}),setTimeout(d,1)}}(5242880),e.extend(b.options,{thumb:{width:110,height:110,quality:70,allowMagnify:!0,crop:!0,preserveHeaders:!1,type:"image/jpeg"},compress:{width:1600,height:1600,quality:90,allowMagnify:!1,crop:!1,preserveHeaders:!0}}),b.register({name:"image",makeThumb:function(a,b,f,g){var h,i;return a=this.request("get-file",a),a.type.match(/^image/)?(h=e.extend({},this.options.thumb),e.isPlainObject(f)&&(h=e.extend(h,f),f=null),f=f||h.width,g=g||h.height,i=new c(h),i.once("load",function(){a._info=a._info||i.info(),a._meta=a._meta||i.meta(),1>=f&&f>0&&(f=a._info.width*f),1>=g&&g>0&&(g=a._info.height*g),i.resize(f,g)}),i.once("complete",function(){b(!1,i.getAsDataUrl(h.type)),i.destroy()}),i.once("error",function(a){b(a||!0),i.destroy()}),void d(i,a.source.size,function(){a._info&&i.info(a._info),a._meta&&i.meta(a._meta),i.loadFromBlob(a.source)})):void b(!0)},beforeSendFile:function(b){var d,f,g=this.options.compress||this.options.resize,h=g&&g.compressSize||0,i=g&&g.noCompressIfLarger||!1;return b=this.request("get-file",b),!g||!~"image/jpeg,image/jpg".indexOf(b.type)||b.size=a&&a>0&&(a=b._info.width*a),1>=c&&c>0&&(c=b._info.height*c),d.resize(a,c)}),d.once("complete",function(){var a,c;try{a=d.getAsBlob(g.type),c=b.size,(!i||a.sizeb;b++)if(c=this._queue[b],a===c.getStatus())return c;return null},sort:function(a){"function"==typeof a&&this._queue.sort(a)},getFiles:function(){for(var a,b=[].slice.call(arguments,0),c=[],d=0,f=this._queue.length;f>d;d++)a=this._queue[d],(!b.length||~e.inArray(a.getStatus(),b))&&c.push(a);return c},removeFile:function(a){var b=this._map[a.id];b&&(delete this._map[a.id],a.destroy(),this.stats.numofDeleted++)},_fileAdded:function(a){var b=this,c=this._map[a.id];c||(this._map[a.id]=a,a.on("statuschange",function(a,c){b._onFileStatusChange(a,c)}))},_onFileStatusChange:function(a,b){var c=this.stats;switch(b){case f.PROGRESS:c.numOfProgress--;break;case f.QUEUED:c.numOfQueue--;break;case f.ERROR:c.numOfUploadFailed--;break;case f.INVALID:c.numOfInvalid--;break;case f.INTERRUPT:c.numofInterrupt--}switch(a){case f.QUEUED:c.numOfQueue++;break;case f.PROGRESS:c.numOfProgress++;break;case f.ERROR:c.numOfUploadFailed++;break;case f.COMPLETE:c.numOfSuccess++;break;case f.CANCELLED:c.numOfCancel++;break;case f.INVALID:c.numOfInvalid++;break;case f.INTERRUPT:c.numofInterrupt++}}}),b.installTo(d.prototype),d}),b("widgets/queue",["base","uploader","queue","file","lib/file","runtime/client","widgets/widget"],function(a,b,c,d,e,f){var g=a.$,h=/\.\w+$/,i=d.Status;return b.register({name:"queue",init:function(b){var d,e,h,i,j,k,l,m=this;if(g.isPlainObject(b.accept)&&(b.accept=[b.accept]),b.accept){for(j=[],h=0,e=b.accept.length;e>h;h++)i=b.accept[h].extensions,i&&j.push(i);j.length&&(k="\\."+j.join(",").replace(/,/g,"$|\\.").replace(/\*/g,".*")+"$"),m.accept=new RegExp(k,"i")}return m.queue=new c,m.stats=m.queue.stats,"html5"===this.request("predict-runtime-type")?(d=a.Deferred(),this.placeholder=l=new f("Placeholder"),l.connectRuntime({runtimeOrder:"html5"},function(){m._ruid=l.getRuid(),d.resolve()}),d.promise()):void 0},_wrapFile:function(a){if(!(a instanceof d)){if(!(a instanceof e)){if(!this._ruid)throw new Error("Can't add external files.");a=new e(this._ruid,a)}a=new d(a)}return a},acceptFile:function(a){var b=!a||!a.size||this.accept&&h.exec(a.name)&&!this.accept.test(a.name);return!b},_addFile:function(a){var b=this;return a=b._wrapFile(a),b.owner.trigger("beforeFileQueued",a)?b.acceptFile(a)?(b.queue.append(a),b.owner.trigger("fileQueued",a),a):void b.owner.trigger("error","Q_TYPE_DENIED",a):void 0},getFile:function(a){return this.queue.getFile(a)},addFile:function(a){var b=this;a.length||(a=[a]),a=g.map(a,function(a){return b._addFile(a)}),b.owner.trigger("filesQueued",a),b.options.auto&&setTimeout(function(){b.request("start-upload")},20)},getStats:function(){return this.stats},removeFile:function(a,b){var c=this;a=a.id?a:c.queue.getFile(a),this.request("cancel-file",a),b&&this.queue.removeFile(a)},getFiles:function(){return this.queue.getFiles.apply(this.queue,arguments)},fetchFile:function(){return this.queue.fetch.apply(this.queue,arguments)},retry:function(a,b){var c,d,e,f=this;if(a)return a=a.id?a:f.queue.getFile(a),a.setStatus(i.QUEUED),void(b||f.request("start-upload"));for(c=f.queue.getFiles(i.ERROR),d=0,e=c.length;e>d;d++)a=c[d],a.setStatus(i.QUEUED);f.request("start-upload")},sortFiles:function(){return this.queue.sort.apply(this.queue,arguments)},reset:function(){this.owner.trigger("reset"),this.queue=new c,this.stats=this.queue.stats},destroy:function(){this.reset(),this.placeholder&&this.placeholder.destroy()}})}),b("widgets/runtime",["uploader","runtime/runtime","widgets/widget"],function(a,b){return a.support=function(){return b.hasRuntime.apply(b,arguments)},a.register({name:"runtime",init:function(){if(!this.predictRuntimeType())throw Error("Runtime Error")},predictRuntimeType:function(){var a,c,d=this.options.runtimeOrder||b.orders,e=this.type;if(!e)for(d=d.split(/\s*,\s*/g),a=0,c=d.length;c>a;a++)if(b.hasRuntime(d[a])){this.type=e=d[a];break}return e}})}),b("lib/transport",["base","runtime/client","mediator"],function(a,b,c){function d(a){var c=this;a=c.options=e.extend(!0,{},d.options,a||{}),b.call(this,"Transport"),this._blob=null,this._formData=a.formData||{},this._headers=a.headers||{},this.on("progress",this._timeout),this.on("load error",function(){c.trigger("progress",1),clearTimeout(c._timer)})}var e=a.$;return d.options={server:"",method:"POST",withCredentials:!1,fileVal:"file",timeout:12e4,formData:{},headers:{},sendAsBinary:!1},e.extend(d.prototype,{appendBlob:function(a,b,c){var d=this,e=d.options;d.getRuid()&&d.disconnectRuntime(),d.connectRuntime(b.ruid,function(){d.exec("init")}),d._blob=b,e.fileVal=a||e.fileVal,e.filename=c||e.filename},append:function(a,b){"object"==typeof a?e.extend(this._formData,a):this._formData[a]=b},setRequestHeader:function(a,b){"object"==typeof a?e.extend(this._headers,a):this._headers[a]=b},send:function(a){this.exec("send",a),this._timeout()},abort:function(){return clearTimeout(this._timer),this.exec("abort")},destroy:function(){this.trigger("destroy"),this.off(),this.exec("destroy"),this.disconnectRuntime()},getResponse:function(){return this.exec("getResponse")},getResponseAsJson:function(){return this.exec("getResponseAsJson")},getStatus:function(){return this.exec("getStatus")},_timeout:function(){var a=this,b=a.options.timeout;b&&(clearTimeout(a._timer),a._timer=setTimeout(function(){a.abort(),a.trigger("error","timeout")},b))}}),c.installTo(d.prototype),d}),b("widgets/upload",["base","uploader","file","lib/transport","widgets/widget"],function(a,b,c,d){function e(a,b){var c,d,e=[],f=a.source,g=f.size,h=b?Math.ceil(g/b):1,i=0,j=0;for(d={file:a,has:function(){return!!e.length},shift:function(){return e.shift()},unshift:function(a){e.unshift(a)}};h>j;)c=Math.min(b,g-i),e.push({file:a,start:i,end:b?i+c:g,total:g,chunks:h,chunk:j++,cuted:d}),i+=c;return a.blocks=e.concat(),a.remaning=e.length,d}var f=a.$,g=a.isPromise,h=c.Status;f.extend(b.options,{prepareNextFile:!1,chunked:!1,chunkSize:5242880,chunkRetry:2,threads:3,formData:{}}),b.register({name:"upload",init:function(){var b=this.owner,c=this;this.runing=!1,this.progress=!1,b.on("startUpload",function(){c.progress=!0}).on("uploadFinished",function(){c.progress=!1}),this.pool=[],this.stack=[],this.pending=[],this.remaning=0,this.__tick=a.bindFn(this._tick,this),b.on("uploadComplete",function(a){a.blocks&&f.each(a.blocks,function(a,b){b.transport&&(b.transport.abort(),b.transport.destroy()),delete b.transport}),delete a.blocks,delete a.remaning})},reset:function(){this.request("stop-upload",!0),this.runing=!1,this.pool=[],this.stack=[],this.pending=[],this.remaning=0,this._trigged=!1,this._promise=null},startUpload:function(b){var c=this;if(f.each(c.request("get-files",h.INVALID),function(){c.request("remove-file",this)}),b)if(b=b.id?b:c.request("get-file",b),b.getStatus()===h.INTERRUPT)f.each(c.pool,function(a,c){c.file===b&&c.transport&&c.transport.send()}),b.setStatus(h.QUEUED);else{if(b.getStatus()===h.PROGRESS)return;b.setStatus(h.QUEUED)}else f.each(c.request("get-files",[h.INITED]),function(){this.setStatus(h.QUEUED)});if(!c.runing){c.runing=!0;var d=[];f.each(c.pool,function(a,b){var e=b.file;e.getStatus()===h.INTERRUPT&&(d.push(e),c._trigged=!1,b.transport&&b.transport.send())});for(var b;b=d.shift();)b.setStatus(h.PROGRESS);b||f.each(c.request("get-files",h.INTERRUPT),function(){this.setStatus(h.PROGRESS)}),c._trigged=!1,a.nextTick(c.__tick),c.owner.trigger("startUpload")}},stopUpload:function(b,c){var d=this;if(b===!0&&(c=b,b=null),d.runing!==!1){if(b){if(b=b.id?b:d.request("get-file",b),b.getStatus()!==h.PROGRESS&&b.getStatus()!==h.QUEUED)return;return b.setStatus(h.INTERRUPT),f.each(d.pool,function(a,c){c.file===b&&(c.transport&&c.transport.abort(),d._putback(c),d._popBlock(c))}),a.nextTick(d.__tick)}d.runing=!1,this._promise&&this._promise.file&&this._promise.file.setStatus(h.INTERRUPT),c&&f.each(d.pool,function(a,b){b.transport&&b.transport.abort(),b.file.setStatus(h.INTERRUPT)}),d.owner.trigger("stopUpload")}},cancelFile:function(a){a=a.id?a:this.request("get-file",a),a.blocks&&f.each(a.blocks,function(a,b){var c=b.transport;c&&(c.abort(),c.destroy(),delete b.transport)}),a.setStatus(h.CANCELLED),this.owner.trigger("fileDequeued",a)},isInProgress:function(){return!!this.progress},_getStats:function(){return this.request("get-stats")},skipFile:function(a,b){a=a.id?a:this.request("get-file",a),a.setStatus(b||h.COMPLETE),a.skipped=!0,a.blocks&&f.each(a.blocks,function(a,b){var c=b.transport;c&&(c.abort(),c.destroy(),delete b.transport)}),this.owner.trigger("uploadSkip",a)},_tick:function(){var b,c,d=this,e=d.options;return d._promise?d._promise.always(d.__tick):void(d.pool.length1&&~"http,abort".indexOf(a)&&b.retried1&&f.extend(m,{chunks:b.chunks,chunk:b.chunk}),i.trigger("uploadBeforeSend",b,m,n),l.appendBlob(j.fileVal,b.blob,k.name),l.append(m),l.setRequestHeader(n),l.send()},_finishFile:function(a,b,c){var d=this.owner;return d.request("after-send-file",arguments,function(){a.setStatus(h.COMPLETE),d.trigger("uploadSuccess",a,b,c) +}).fail(function(b){a.getStatus()===h.PROGRESS&&a.setStatus(h.ERROR,b),d.trigger("uploadError",a,b)}).always(function(){d.trigger("uploadComplete",a)})},updateFileProgress:function(a){var b=0,c=0;a.blocks&&(f.each(a.blocks,function(a,b){c+=(b.percentage||0)*(b.end-b.start)}),b=c/a.size,this.owner.trigger("uploadProgress",a,b||0))}})}),b("widgets/log",["base","uploader","widgets/widget"],function(a,b){function c(a){var b=e.extend({},d,a),c=f.replace(/^(.*)\?/,"$1"+e.param(b)),g=new Image;g.src=c}var d,e=a.$,f=" http://static.tieba.baidu.com/tb/pms/img/st.gif??",g=(location.hostname||location.host||"protected").toLowerCase(),h=g&&/baidu/i.exec(g);if(h)return d={dv:3,master:"webuploader",online:/test/.exec(g)?0:1,module:"",product:g,type:0},b.register({name:"log",init:function(){var a=this.owner,b=0,d=0;a.on("error",function(a){c({type:2,c_error_code:a})}).on("uploadError",function(a,b){c({type:2,c_error_code:"UPLOAD_ERROR",c_reason:""+b})}).on("uploadComplete",function(a){b++,d+=a.size}).on("uploadFinished",function(){c({c_count:b,c_size:d}),b=d=0}),c({c_usage:1})}})}),b("runtime/compbase",[],function(){function a(a,b){this.owner=a,this.options=a.options,this.getRuntime=function(){return b},this.getRuid=function(){return b.uid},this.trigger=function(){return a.trigger.apply(a,arguments)}}return a}),b("runtime/html5/runtime",["base","runtime/runtime","runtime/compbase"],function(b,c,d){function e(){var a={},d=this,e=this.destroy;c.apply(d,arguments),d.type=f,d.exec=function(c,e){var f,h=this,i=h.uid,j=b.slice(arguments,2);return g[c]&&(f=a[i]=a[i]||new g[c](h,d),f[e])?f[e].apply(f,j):void 0},d.destroy=function(){return e&&e.apply(this,arguments)}}var f="html5",g={};return b.inherits(c,{constructor:e,init:function(){var a=this;setTimeout(function(){a.trigger("ready")},1)}}),e.register=function(a,c){var e=g[a]=b.inherits(d,c);return e},a.Blob&&a.FileReader&&a.DataView&&c.addRuntime(f,e),e}),b("runtime/html5/blob",["runtime/html5/runtime","lib/blob"],function(a,b){return a.register("Blob",{slice:function(a,c){var d=this.owner.source,e=d.slice||d.webkitSlice||d.mozSlice;return d=e.call(d,a,c),new b(this.getRuid(),d)}})}),b("runtime/html5/filepicker",["base","runtime/html5/runtime"],function(a,b){var c=a.$;return b.register("FilePicker",{init:function(){var a,b,d,e,f=this.getRuntime().getContainer(),g=this,h=g.owner,i=g.options,j=this.label=c(document.createElement("label")),k=this.input=c(document.createElement("input"));if(k.attr("type","file"),k.attr("name",i.name),k.addClass("webuploader-element-invisible"),j.on("click",function(){k.trigger("click")}),j.css({opacity:0,width:"100%",height:"100%",display:"block",cursor:"pointer",background:"#ffffff"}),i.multiple&&k.attr("multiple","multiple"),i.accept&&i.accept.length>0){for(a=[],b=0,d=i.accept.length;d>b;b++)a.push(i.accept[b].mimeTypes);k.attr("accept",a.join(","))}f.append(k),f.append(j),e=function(a){h.trigger(a.type)},k.on("change",function(a){var b,d=arguments.callee;g.files=a.target.files,b=this.cloneNode(!0),b.value=null,this.parentNode.replaceChild(b,this),k.off(),k=c(b).on("change",d).on("mouseenter mouseleave",e),h.trigger("change")}),j.on("mouseenter mouseleave",e)},getFiles:function(){return this.files},destroy:function(){this.input.off(),this.label.off()}})}),b("runtime/html5/util",["base"],function(b){var c=a.createObjectURL&&a||a.URL&&URL.revokeObjectURL&&URL||a.webkitURL,d=b.noop,e=d;return c&&(d=function(){return c.createObjectURL.apply(c,arguments)},e=function(){return c.revokeObjectURL.apply(c,arguments)}),{createObjectURL:d,revokeObjectURL:e,dataURL2Blob:function(a){var b,c,d,e,f,g;for(g=a.split(","),b=~g[0].indexOf("base64")?atob(g[1]):decodeURIComponent(g[1]),d=new ArrayBuffer(b.length),c=new Uint8Array(d),e=0;ei&&(d=h.getUint16(i),d>=65504&&65519>=d||65534===d)&&(e=h.getUint16(i+2)+2,!(i+e>h.byteLength));){if(f=b.parsers[d],!c&&f)for(g=0;g6&&(l.imageHead=a.slice?a.slice(2,k):new Uint8Array(a).subarray(2,k))}return l}},updateImageHead:function(a,b){var c,d,e,f=this._parse(a,!0);return e=2,f.imageHead&&(e=2+f.imageHead.byteLength),d=a.slice?a.slice(e):new Uint8Array(a).subarray(e),c=new Uint8Array(b.byteLength+2+d.byteLength),c[0]=255,c[1]=216,c.set(new Uint8Array(b),2),c.set(new Uint8Array(d),b.byteLength+2),c.buffer}},a.parseMeta=function(){return b.parse.apply(b,arguments)},a.updateImageHead=function(){return b.updateImageHead.apply(b,arguments)},b}),b("runtime/html5/imagemeta/exif",["base","runtime/html5/imagemeta"],function(a,b){var c={};return c.ExifMap=function(){return this},c.ExifMap.prototype.map={Orientation:274},c.ExifMap.prototype.get=function(a){return this[a]||this[this.map[a]]},c.exifTagTypes={1:{getValue:function(a,b){return a.getUint8(b)},size:1},2:{getValue:function(a,b){return String.fromCharCode(a.getUint8(b))},size:1,ascii:!0},3:{getValue:function(a,b,c){return a.getUint16(b,c)},size:2},4:{getValue:function(a,b,c){return a.getUint32(b,c)},size:4},5:{getValue:function(a,b,c){return a.getUint32(b,c)/a.getUint32(b+4,c)},size:8},9:{getValue:function(a,b,c){return a.getInt32(b,c)},size:4},10:{getValue:function(a,b,c){return a.getInt32(b,c)/a.getInt32(b+4,c)},size:8}},c.exifTagTypes[7]=c.exifTagTypes[1],c.getExifValue=function(b,d,e,f,g,h){var i,j,k,l,m,n,o=c.exifTagTypes[f];if(!o)return void a.log("Invalid Exif data: Invalid tag type.");if(i=o.size*g,j=i>4?d+b.getUint32(e+8,h):e+8,j+i>b.byteLength)return void a.log("Invalid Exif data: Invalid data offset.");if(1===g)return o.getValue(b,j,h);for(k=[],l=0;g>l;l+=1)k[l]=o.getValue(b,j+l*o.size,h);if(o.ascii){for(m="",l=0;lb.byteLength)return void a.log("Invalid Exif data: Invalid directory offset.");if(g=b.getUint16(d,e),h=d+2+12*g,h+4>b.byteLength)return void a.log("Invalid Exif data: Invalid directory size.");for(i=0;g>i;i+=1)this.parseExifTag(b,c,d+2+12*i,e,f);return b.getUint32(h,e)},c.parseExifData=function(b,d,e,f){var g,h,i=d+10;if(1165519206===b.getUint32(d+4)){if(i+8>b.byteLength)return void a.log("Invalid Exif data: Invalid segment size.");if(0!==b.getUint16(d+8))return void a.log("Invalid Exif data: Missing byte alignment offset.");switch(b.getUint16(i)){case 18761:g=!0;break;case 19789:g=!1;break;default:return void a.log("Invalid Exif data: Invalid byte alignment marker.")}if(42!==b.getUint16(i+2,g))return void a.log("Invalid Exif data: Missing TIFF marker.");h=b.getUint32(i+4,g),f.exif=new c.ExifMap,h=c.parseExifTags(b,i,i+h,g,f)}},b.parsers[65505].push(c.parseExifData),c}),b("runtime/html5/image",["base","runtime/html5/runtime","runtime/html5/util"],function(a,b,c){var d="data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs%3D";return b.register("Image",{modified:!1,init:function(){var a=this,b=new Image;b.onload=function(){a._info={type:a.type,width:this.width,height:this.height},a._metas||"image/jpeg"!==a.type?a.owner.trigger("load"):c.parseMeta(a._blob,function(b,c){a._metas=c,a.owner.trigger("load")})},b.onerror=function(){a.owner.trigger("error")},a._img=b},loadFromBlob:function(a){var b=this,d=b._img;b._blob=a,b.type=a.type,d.src=c.createObjectURL(a.getSource()),b.owner.once("load",function(){c.revokeObjectURL(d.src)})},resize:function(a,b){var c=this._canvas||(this._canvas=document.createElement("canvas"));this._resize(this._img,c,a,b),this._blob=null,this.modified=!0,this.owner.trigger("complete","resize")},crop:function(a,b,c,d,e){var f=this._canvas||(this._canvas=document.createElement("canvas")),g=this.options,h=this._img,i=h.naturalWidth,j=h.naturalHeight,k=this.getOrientation();e=e||1,f.width=c,f.height=d,g.preserveHeaders||this._rotate2Orientaion(f,k),this._renderImageToCanvas(f,h,-a,-b,i*e,j*e),this._blob=null,this.modified=!0,this.owner.trigger("complete","crop")},getAsBlob:function(a){var b,d=this._blob,e=this.options;if(a=a||this.type,this.modified||this.type!==a){if(b=this._canvas,"image/jpeg"===a){if(d=c.canvasToDataUrl(b,a,e.quality),e.preserveHeaders&&this._metas&&this._metas.imageHead)return d=c.dataURL2ArrayBuffer(d),d=c.updateImageHead(d,this._metas.imageHead),d=c.arrayBufferToBlob(d,a)}else d=c.canvasToDataUrl(b,a);d=c.dataURL2Blob(d)}return d},getAsDataUrl:function(a){var b=this.options;return a=a||this.type,"image/jpeg"===a?c.canvasToDataUrl(this._canvas,a,b.quality):this._canvas.toDataURL(a)},getOrientation:function(){return this._metas&&this._metas.exif&&this._metas.exif.get("Orientation")||1},info:function(a){return a?(this._info=a,this):this._info},meta:function(a){return a?(this._meta=a,this):this._meta},destroy:function(){var a=this._canvas;this._img.onload=null,a&&(a.getContext("2d").clearRect(0,0,a.width,a.height),a.width=a.height=0,this._canvas=null),this._img.src=d,this._img=this._blob=null},_resize:function(a,b,c,d){var e,f,g,h,i,j=this.options,k=a.width,l=a.height,m=this.getOrientation();~[5,6,7,8].indexOf(m)&&(c^=d,d^=c,c^=d),e=Math[j.crop?"max":"min"](c/k,d/l),j.allowMagnify||(e=Math.min(1,e)),f=k*e,g=l*e,j.crop?(b.width=c,b.height=d):(b.width=f,b.height=g),h=(b.width-f)/2,i=(b.height-g)/2,j.preserveHeaders||this._rotate2Orientaion(b,m),this._renderImageToCanvas(b,a,h,i,f,g)},_rotate2Orientaion:function(a,b){var c=a.width,d=a.height,e=a.getContext("2d");switch(b){case 5:case 6:case 7:case 8:a.width=d,a.height=c}switch(b){case 2:e.translate(c,0),e.scale(-1,1);break;case 3:e.translate(c,d),e.rotate(Math.PI);break;case 4:e.translate(0,d),e.scale(1,-1);break;case 5:e.rotate(.5*Math.PI),e.scale(1,-1);break;case 6:e.rotate(.5*Math.PI),e.translate(0,-d);break;case 7:e.rotate(.5*Math.PI),e.translate(c,-d),e.scale(-1,1);break;case 8:e.rotate(-.5*Math.PI),e.translate(-c,0)}},_renderImageToCanvas:function(){function b(a,b,c){var d,e,f,g=document.createElement("canvas"),h=g.getContext("2d"),i=0,j=c,k=c;for(g.width=1,g.height=c,h.drawImage(a,0,0),d=h.getImageData(0,0,1,c).data;k>i;)e=d[4*(k-1)+3],0===e?j=k:i=k,k=j+i>>1;return f=k/c,0===f?1:f}function c(a){var b,c,d=a.naturalWidth,e=a.naturalHeight;return d*e>1048576?(b=document.createElement("canvas"),b.width=b.height=1,c=b.getContext("2d"),c.drawImage(a,-d+1,0),0===c.getImageData(0,0,1,1).data[3]):!1}return a.os.ios?a.os.ios>=7?function(a,c,d,e,f,g){var h=c.naturalWidth,i=c.naturalHeight,j=b(c,h,i);return a.getContext("2d").drawImage(c,0,0,h*j,i*j,d,e,f,g)}:function(a,d,e,f,g,h){var i,j,k,l,m,n,o,p=d.naturalWidth,q=d.naturalHeight,r=a.getContext("2d"),s=c(d),t="image/jpeg"===this.type,u=1024,v=0,w=0;for(s&&(p/=2,q/=2),r.save(),i=document.createElement("canvas"),i.width=i.height=u,j=i.getContext("2d"),k=t?b(d,p,q):1,l=Math.ceil(u*g/p),m=Math.ceil(u*h/q/k);q>v;){for(n=0,o=0;p>n;)j.clearRect(0,0,u,u),j.drawImage(d,-n,-v),r.drawImage(i,0,0,u,u,e+o,f+w,l,m),n+=u,o+=l;v+=u,w+=m}r.restore(),i=j=null}:function(b){var c=a.slice(arguments,1),d=b.getContext("2d");d.drawImage.apply(d,c)}}()})}),b("runtime/html5/jpegencoder",[],function(){function a(a){function b(a){for(var b=[16,11,10,16,24,40,51,61,12,12,14,19,26,58,60,55,14,13,16,24,40,57,69,56,14,17,22,29,51,87,80,62,18,22,37,56,68,109,103,77,24,35,55,64,81,104,113,92,49,64,78,87,103,121,120,101,72,92,95,98,112,100,103,99],c=0;64>c;c++){var d=y((b[c]*a+50)/100);1>d?d=1:d>255&&(d=255),z[P[c]]=d}for(var e=[17,18,24,47,99,99,99,99,18,21,26,66,99,99,99,99,24,26,56,99,99,99,99,99,47,66,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99],f=0;64>f;f++){var g=y((e[f]*a+50)/100);1>g?g=1:g>255&&(g=255),A[P[f]]=g}for(var h=[1,1.387039845,1.306562965,1.175875602,1,.785694958,.5411961,.275899379],i=0,j=0;8>j;j++)for(var k=0;8>k;k++)B[i]=1/(z[P[i]]*h[j]*h[k]*8),C[i]=1/(A[P[i]]*h[j]*h[k]*8),i++}function c(a,b){for(var c=0,d=0,e=new Array,f=1;16>=f;f++){for(var g=1;g<=a[f];g++)e[b[d]]=[],e[b[d]][0]=c,e[b[d]][1]=f,d++,c++;c*=2}return e}function d(){t=c(Q,R),u=c(U,V),v=c(S,T),w=c(W,X)}function e(){for(var a=1,b=2,c=1;15>=c;c++){for(var d=a;b>d;d++)E[32767+d]=c,D[32767+d]=[],D[32767+d][1]=c,D[32767+d][0]=d;for(var e=-(b-1);-a>=e;e++)E[32767+e]=c,D[32767+e]=[],D[32767+e][1]=c,D[32767+e][0]=b-1+e;a<<=1,b<<=1}}function f(){for(var a=0;256>a;a++)O[a]=19595*a,O[a+256>>0]=38470*a,O[a+512>>0]=7471*a+32768,O[a+768>>0]=-11059*a,O[a+1024>>0]=-21709*a,O[a+1280>>0]=32768*a+8421375,O[a+1536>>0]=-27439*a,O[a+1792>>0]=-5329*a}function g(a){for(var b=a[0],c=a[1]-1;c>=0;)b&1<J&&(255==I?(h(255),h(0)):h(I),J=7,I=0)}function h(a){H.push(N[a])}function i(a){h(a>>8&255),h(255&a)}function j(a,b){var c,d,e,f,g,h,i,j,k,l=0,m=8,n=64;for(k=0;m>k;++k){c=a[l],d=a[l+1],e=a[l+2],f=a[l+3],g=a[l+4],h=a[l+5],i=a[l+6],j=a[l+7];var o=c+j,p=c-j,q=d+i,r=d-i,s=e+h,t=e-h,u=f+g,v=f-g,w=o+u,x=o-u,y=q+s,z=q-s;a[l]=w+y,a[l+4]=w-y;var A=.707106781*(z+x);a[l+2]=x+A,a[l+6]=x-A,w=v+t,y=t+r,z=r+p;var B=.382683433*(w-z),C=.5411961*w+B,D=1.306562965*z+B,E=.707106781*y,G=p+E,H=p-E;a[l+5]=H+C,a[l+3]=H-C,a[l+1]=G+D,a[l+7]=G-D,l+=8}for(l=0,k=0;m>k;++k){c=a[l],d=a[l+8],e=a[l+16],f=a[l+24],g=a[l+32],h=a[l+40],i=a[l+48],j=a[l+56];var I=c+j,J=c-j,K=d+i,L=d-i,M=e+h,N=e-h,O=f+g,P=f-g,Q=I+O,R=I-O,S=K+M,T=K-M;a[l]=Q+S,a[l+32]=Q-S;var U=.707106781*(T+R);a[l+16]=R+U,a[l+48]=R-U,Q=P+N,S=N+L,T=L+J;var V=.382683433*(Q-T),W=.5411961*Q+V,X=1.306562965*T+V,Y=.707106781*S,Z=J+Y,$=J-Y;a[l+40]=$+W,a[l+24]=$-W,a[l+8]=Z+X,a[l+56]=Z-X,l++}var _;for(k=0;n>k;++k)_=a[k]*b[k],F[k]=_>0?_+.5|0:_-.5|0;return F}function k(){i(65504),i(16),h(74),h(70),h(73),h(70),h(0),h(1),h(1),h(0),i(1),i(1),h(0),h(0)}function l(a,b){i(65472),i(17),h(8),i(b),i(a),h(3),h(1),h(17),h(0),h(2),h(17),h(1),h(3),h(17),h(1)}function m(){i(65499),i(132),h(0);for(var a=0;64>a;a++)h(z[a]);h(1);for(var b=0;64>b;b++)h(A[b])}function n(){i(65476),i(418),h(0);for(var a=0;16>a;a++)h(Q[a+1]);for(var b=0;11>=b;b++)h(R[b]);h(16);for(var c=0;16>c;c++)h(S[c+1]);for(var d=0;161>=d;d++)h(T[d]);h(1);for(var e=0;16>e;e++)h(U[e+1]);for(var f=0;11>=f;f++)h(V[f]);h(17);for(var g=0;16>g;g++)h(W[g+1]);for(var j=0;161>=j;j++)h(X[j])}function o(){i(65498),i(12),h(3),h(1),h(0),h(2),h(17),h(3),h(17),h(0),h(63),h(0)}function p(a,b,c,d,e){for(var f,h=e[0],i=e[240],k=16,l=63,m=64,n=j(a,b),o=0;m>o;++o)G[P[o]]=n[o];var p=G[0]-c;c=G[0],0==p?g(d[0]):(f=32767+p,g(d[E[f]]),g(D[f]));for(var q=63;q>0&&0==G[q];q--);if(0==q)return g(h),c;for(var r,s=1;q>=s;){for(var t=s;0==G[s]&&q>=s;++s);var u=s-t;if(u>=k){r=u>>4;for(var v=1;r>=v;++v)g(i);u=15&u}f=32767+G[s],g(e[(u<<4)+E[f]]),g(D[f]),s++}return q!=l&&g(h),c}function q(){for(var a=String.fromCharCode,b=0;256>b;b++)N[b]=a(b)}function r(a){if(0>=a&&(a=1),a>100&&(a=100),x!=a){var c=0;c=Math.floor(50>a?5e3/a:200-2*a),b(c),x=a}}function s(){a||(a=50),q(),d(),e(),f(),r(a)}var t,u,v,w,x,y=(Math.round,Math.floor),z=new Array(64),A=new Array(64),B=new Array(64),C=new Array(64),D=new Array(65535),E=new Array(65535),F=new Array(64),G=new Array(64),H=[],I=0,J=7,K=new Array(64),L=new Array(64),M=new Array(64),N=new Array(256),O=new Array(2048),P=[0,1,5,6,14,15,27,28,2,4,7,13,16,26,29,42,3,8,12,17,25,30,41,43,9,11,18,24,31,40,44,53,10,19,23,32,39,45,52,54,20,22,33,38,46,51,55,60,21,34,37,47,50,56,59,61,35,36,48,49,57,58,62,63],Q=[0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0],R=[0,1,2,3,4,5,6,7,8,9,10,11],S=[0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,125],T=[1,2,3,0,4,17,5,18,33,49,65,6,19,81,97,7,34,113,20,50,129,145,161,8,35,66,177,193,21,82,209,240,36,51,98,114,130,9,10,22,23,24,25,26,37,38,39,40,41,42,52,53,54,55,56,57,58,67,68,69,70,71,72,73,74,83,84,85,86,87,88,89,90,99,100,101,102,103,104,105,106,115,116,117,118,119,120,121,122,131,132,133,134,135,136,137,138,146,147,148,149,150,151,152,153,154,162,163,164,165,166,167,168,169,170,178,179,180,181,182,183,184,185,186,194,195,196,197,198,199,200,201,202,210,211,212,213,214,215,216,217,218,225,226,227,228,229,230,231,232,233,234,241,242,243,244,245,246,247,248,249,250],U=[0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0],V=[0,1,2,3,4,5,6,7,8,9,10,11],W=[0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,119],X=[0,1,2,3,17,4,5,33,49,6,18,65,81,7,97,113,19,34,50,129,8,20,66,145,161,177,193,9,35,51,82,240,21,98,114,209,10,22,36,52,225,37,241,23,24,25,26,38,39,40,41,42,53,54,55,56,57,58,67,68,69,70,71,72,73,74,83,84,85,86,87,88,89,90,99,100,101,102,103,104,105,106,115,116,117,118,119,120,121,122,130,131,132,133,134,135,136,137,138,146,147,148,149,150,151,152,153,154,162,163,164,165,166,167,168,169,170,178,179,180,181,182,183,184,185,186,194,195,196,197,198,199,200,201,202,210,211,212,213,214,215,216,217,218,226,227,228,229,230,231,232,233,234,242,243,244,245,246,247,248,249,250];this.encode=function(a,b){b&&r(b),H=new Array,I=0,J=7,i(65496),k(),m(),l(a.width,a.height),n(),o();var c=0,d=0,e=0;I=0,J=7,this.encode.displayName="_encode_";for(var f,h,j,q,s,x,y,z,A,D=a.data,E=a.width,F=a.height,G=4*E,N=0;F>N;){for(f=0;G>f;){for(s=G*N+f,x=s,y=-1,z=0,A=0;64>A;A++)z=A>>3,y=4*(7&A),x=s+z*G+y,N+z>=F&&(x-=G*(N+1+z-F)),f+y>=G&&(x-=f+y-G+4),h=D[x++],j=D[x++],q=D[x++],K[A]=(O[h]+O[j+256>>0]+O[q+512>>0]>>16)-128,L[A]=(O[h+768>>0]+O[j+1024>>0]+O[q+1280>>0]>>16)-128,M[A]=(O[h+1280>>0]+O[j+1536>>0]+O[q+1792>>0]>>16)-128;c=p(K,B,c,t,v),d=p(L,C,d,u,w),e=p(M,C,e,u,w),f+=32}N+=8}if(J>=0){var P=[];P[1]=J+1,P[0]=(1<=200&&b.status<300?(a._response=b.responseText,a.trigger("load")):b.status>=500&&b.status<600?(a._response=b.responseText,a.trigger("error","server")):a.trigger("error",a._status?"http":"abort")):void 0},a._xhr=b,b},_setRequestHeader:function(a,b){d.each(b,function(b,c){a.setRequestHeader(b,c)})},_parseJson:function(a){var b;try{b=JSON.parse(a)}catch(c){b={}}return b}})}),b("webuploader",["base","widgets/filepicker","widgets/image","widgets/queue","widgets/runtime","widgets/upload","widgets/log","runtime/html5/blob","runtime/html5/filepicker","runtime/html5/imagemeta/exif","runtime/html5/image","runtime/html5/androidpatch","runtime/html5/transport"],function(a){return a}),c("webuploader")}); \ No newline at end of file diff --git a/static/plugins/webuploader/webuploader.fis.js b/static/plugins/webuploader/webuploader.fis.js new file mode 100644 index 00000000..c82ca434 --- /dev/null +++ b/static/plugins/webuploader/webuploader.fis.js @@ -0,0 +1,8083 @@ +/*! WebUploader 0.1.5 */ + + +var jQuery = require('example:widget/ui/jquery/jquery.js') + +return (function( root, factory ) { + var modules = {}, + + // 内部require, 简单不完全实现。 + // https://github.com/amdjs/amdjs-api/wiki/require + _require = function( deps, callback ) { + var args, len, i; + + // 如果deps不是数组,则直接返回指定module + if ( typeof deps === 'string' ) { + return getModule( deps ); + } else { + args = []; + for( len = deps.length, i = 0; i < len; i++ ) { + args.push( getModule( deps[ i ] ) ); + } + + return callback.apply( null, args ); + } + }, + + // 内部define,暂时不支持不指定id. + _define = function( id, deps, factory ) { + if ( arguments.length === 2 ) { + factory = deps; + deps = null; + } + + _require( deps || [], function() { + setModule( id, factory, arguments ); + }); + }, + + // 设置module, 兼容CommonJs写法。 + setModule = function( id, factory, args ) { + var module = { + exports: factory + }, + returned; + + if ( typeof factory === 'function' ) { + args.length || (args = [ _require, module.exports, module ]); + returned = factory.apply( null, args ); + returned !== undefined && (module.exports = returned); + } + + modules[ id ] = module.exports; + }, + + // 根据id获取module + getModule = function( id ) { + var module = modules[ id ] || root[ id ]; + + if ( !module ) { + throw new Error( '`' + id + '` is undefined' ); + } + + return module; + }, + + // 将所有modules,将路径ids装换成对象。 + exportsTo = function( obj ) { + var key, host, parts, part, last, ucFirst; + + // make the first character upper case. + ucFirst = function( str ) { + return str && (str.charAt( 0 ).toUpperCase() + str.substr( 1 )); + }; + + for ( key in modules ) { + host = obj; + + if ( !modules.hasOwnProperty( key ) ) { + continue; + } + + parts = key.split('/'); + last = ucFirst( parts.pop() ); + + while( (part = ucFirst( parts.shift() )) ) { + host[ part ] = host[ part ] || {}; + host = host[ part ]; + } + + host[ last ] = modules[ key ]; + } + + return obj; + }, + + makeExport = function( dollar ) { + root.__dollar = dollar; + + // exports every module. + return exportsTo( factory( root, _define, _require ) ); + }; + + return makeExport( jQuery ); +})( window, function( window, define, require ) { + + + /** + * @fileOverview jQuery or Zepto + */ + define('dollar-third',[],function() { + var $ = window.__dollar || window.jQuery || window.Zepto; + + if ( !$ ) { + throw new Error('jQuery or Zepto not found!'); + } + + return $; + }); + /** + * @fileOverview Dom 操作相关 + */ + define('dollar',[ + 'dollar-third' + ], function( _ ) { + return _; + }); + /** + * @fileOverview 使用jQuery的Promise + */ + define('promise-third',[ + 'dollar' + ], function( $ ) { + return { + Deferred: $.Deferred, + when: $.when, + + isPromise: function( anything ) { + return anything && typeof anything.then === 'function'; + } + }; + }); + /** + * @fileOverview Promise/A+ + */ + define('promise',[ + 'promise-third' + ], function( _ ) { + return _; + }); + /** + * @fileOverview 基础类方法。 + */ + + /** + * Web Uploader内部类的详细说明,以下提及的功能类,都可以在`WebUploader`这个变量中访问到。 + * + * As you know, Web Uploader的每个文件都是用过[AMD](https://github.com/amdjs/amdjs-api/wiki/AMD)规范中的`define`组织起来的, 每个Module都会有个module id. + * 默认module id为该文件的路径,而此路径将会转化成名字空间存放在WebUploader中。如: + * + * * module `base`:WebUploader.Base + * * module `file`: WebUploader.File + * * module `lib/dnd`: WebUploader.Lib.Dnd + * * module `runtime/html5/dnd`: WebUploader.Runtime.Html5.Dnd + * + * + * 以下文档中对类的使用可能省略掉了`WebUploader`前缀。 + * @module WebUploader + * @title WebUploader API文档 + */ + define('base',[ + 'dollar', + 'promise' + ], function( $, promise ) { + + var noop = function() {}, + call = Function.call; + + // http://jsperf.com/uncurrythis + // 反科里化 + function uncurryThis( fn ) { + return function() { + return call.apply( fn, arguments ); + }; + } + + function bindFn( fn, context ) { + return function() { + return fn.apply( context, arguments ); + }; + } + + function createObject( proto ) { + var f; + + if ( Object.create ) { + return Object.create( proto ); + } else { + f = function() {}; + f.prototype = proto; + return new f(); + } + } + + + /** + * 基础类,提供一些简单常用的方法。 + * @class Base + */ + return { + + /** + * @property {String} version 当前版本号。 + */ + version: '0.1.5', + + /** + * @property {jQuery|Zepto} $ 引用依赖的jQuery或者Zepto对象。 + */ + $: $, + + Deferred: promise.Deferred, + + isPromise: promise.isPromise, + + when: promise.when, + + /** + * @description 简单的浏览器检查结果。 + * + * * `webkit` webkit版本号,如果浏览器为非webkit内核,此属性为`undefined`。 + * * `chrome` chrome浏览器版本号,如果浏览器为chrome,此属性为`undefined`。 + * * `ie` ie浏览器版本号,如果浏览器为非ie,此属性为`undefined`。**暂不支持ie10+** + * * `firefox` firefox浏览器版本号,如果浏览器为非firefox,此属性为`undefined`。 + * * `safari` safari浏览器版本号,如果浏览器为非safari,此属性为`undefined`。 + * * `opera` opera浏览器版本号,如果浏览器为非opera,此属性为`undefined`。 + * + * @property {Object} [browser] + */ + browser: (function( ua ) { + var ret = {}, + webkit = ua.match( /WebKit\/([\d.]+)/ ), + chrome = ua.match( /Chrome\/([\d.]+)/ ) || + ua.match( /CriOS\/([\d.]+)/ ), + + ie = ua.match( /MSIE\s([\d\.]+)/ ) || + ua.match( /(?:trident)(?:.*rv:([\w.]+))?/i ), + firefox = ua.match( /Firefox\/([\d.]+)/ ), + safari = ua.match( /Safari\/([\d.]+)/ ), + opera = ua.match( /OPR\/([\d.]+)/ ); + + webkit && (ret.webkit = parseFloat( webkit[ 1 ] )); + chrome && (ret.chrome = parseFloat( chrome[ 1 ] )); + ie && (ret.ie = parseFloat( ie[ 1 ] )); + firefox && (ret.firefox = parseFloat( firefox[ 1 ] )); + safari && (ret.safari = parseFloat( safari[ 1 ] )); + opera && (ret.opera = parseFloat( opera[ 1 ] )); + + return ret; + })( navigator.userAgent ), + + /** + * @description 操作系统检查结果。 + * + * * `android` 如果在android浏览器环境下,此值为对应的android版本号,否则为`undefined`。 + * * `ios` 如果在ios浏览器环境下,此值为对应的ios版本号,否则为`undefined`。 + * @property {Object} [os] + */ + os: (function( ua ) { + var ret = {}, + + // osx = !!ua.match( /\(Macintosh\; Intel / ), + android = ua.match( /(?:Android);?[\s\/]+([\d.]+)?/ ), + ios = ua.match( /(?:iPad|iPod|iPhone).*OS\s([\d_]+)/ ); + + // osx && (ret.osx = true); + android && (ret.android = parseFloat( android[ 1 ] )); + ios && (ret.ios = parseFloat( ios[ 1 ].replace( /_/g, '.' ) )); + + return ret; + })( navigator.userAgent ), + + /** + * 实现类与类之间的继承。 + * @method inherits + * @grammar Base.inherits( super ) => child + * @grammar Base.inherits( super, protos ) => child + * @grammar Base.inherits( super, protos, statics ) => child + * @param {Class} super 父类 + * @param {Object | Function} [protos] 子类或者对象。如果对象中包含constructor,子类将是用此属性值。 + * @param {Function} [protos.constructor] 子类构造器,不指定的话将创建个临时的直接执行父类构造器的方法。 + * @param {Object} [statics] 静态属性或方法。 + * @return {Class} 返回子类。 + * @example + * function Person() { + * console.log( 'Super' ); + * } + * Person.prototype.hello = function() { + * console.log( 'hello' ); + * }; + * + * var Manager = Base.inherits( Person, { + * world: function() { + * console.log( 'World' ); + * } + * }); + * + * // 因为没有指定构造器,父类的构造器将会执行。 + * var instance = new Manager(); // => Super + * + * // 继承子父类的方法 + * instance.hello(); // => hello + * instance.world(); // => World + * + * // 子类的__super__属性指向父类 + * console.log( Manager.__super__ === Person ); // => true + */ + inherits: function( Super, protos, staticProtos ) { + var child; + + if ( typeof protos === 'function' ) { + child = protos; + protos = null; + } else if ( protos && protos.hasOwnProperty('constructor') ) { + child = protos.constructor; + } else { + child = function() { + return Super.apply( this, arguments ); + }; + } + + // 复制静态方法 + $.extend( true, child, Super, staticProtos || {} ); + + /* jshint camelcase: false */ + + // 让子类的__super__属性指向父类。 + child.__super__ = Super.prototype; + + // 构建原型,添加原型方法或属性。 + // 暂时用Object.create实现。 + child.prototype = createObject( Super.prototype ); + protos && $.extend( true, child.prototype, protos ); + + return child; + }, + + /** + * 一个不做任何事情的方法。可以用来赋值给默认的callback. + * @method noop + */ + noop: noop, + + /** + * 返回一个新的方法,此方法将已指定的`context`来执行。 + * @grammar Base.bindFn( fn, context ) => Function + * @method bindFn + * @example + * var doSomething = function() { + * console.log( this.name ); + * }, + * obj = { + * name: 'Object Name' + * }, + * aliasFn = Base.bind( doSomething, obj ); + * + * aliasFn(); // => Object Name + * + */ + bindFn: bindFn, + + /** + * 引用Console.log如果存在的话,否则引用一个[空函数noop](#WebUploader:Base.noop)。 + * @grammar Base.log( args... ) => undefined + * @method log + */ + log: (function() { + if ( window.console ) { + return bindFn( console.log, console ); + } + return noop; + })(), + + nextTick: (function() { + + return function( cb ) { + setTimeout( cb, 1 ); + }; + + // @bug 当浏览器不在当前窗口时就停了。 + // var next = window.requestAnimationFrame || + // window.webkitRequestAnimationFrame || + // window.mozRequestAnimationFrame || + // function( cb ) { + // window.setTimeout( cb, 1000 / 60 ); + // }; + + // // fix: Uncaught TypeError: Illegal invocation + // return bindFn( next, window ); + })(), + + /** + * 被[uncurrythis](http://www.2ality.com/2011/11/uncurrying-this.html)的数组slice方法。 + * 将用来将非数组对象转化成数组对象。 + * @grammar Base.slice( target, start[, end] ) => Array + * @method slice + * @example + * function doSomthing() { + * var args = Base.slice( arguments, 1 ); + * console.log( args ); + * } + * + * doSomthing( 'ignored', 'arg2', 'arg3' ); // => Array ["arg2", "arg3"] + */ + slice: uncurryThis( [].slice ), + + /** + * 生成唯一的ID + * @method guid + * @grammar Base.guid() => String + * @grammar Base.guid( prefx ) => String + */ + guid: (function() { + var counter = 0; + + return function( prefix ) { + var guid = (+new Date()).toString( 32 ), + i = 0; + + for ( ; i < 5; i++ ) { + guid += Math.floor( Math.random() * 65535 ).toString( 32 ); + } + + return (prefix || 'wu_') + guid + (counter++).toString( 32 ); + }; + })(), + + /** + * 格式化文件大小, 输出成带单位的字符串 + * @method formatSize + * @grammar Base.formatSize( size ) => String + * @grammar Base.formatSize( size, pointLength ) => String + * @grammar Base.formatSize( size, pointLength, units ) => String + * @param {Number} size 文件大小 + * @param {Number} [pointLength=2] 精确到的小数点数。 + * @param {Array} [units=[ 'B', 'K', 'M', 'G', 'TB' ]] 单位数组。从字节,到千字节,一直往上指定。如果单位数组里面只指定了到了K(千字节),同时文件大小大于M, 此方法的输出将还是显示成多少K. + * @example + * console.log( Base.formatSize( 100 ) ); // => 100B + * console.log( Base.formatSize( 1024 ) ); // => 1.00K + * console.log( Base.formatSize( 1024, 0 ) ); // => 1K + * console.log( Base.formatSize( 1024 * 1024 ) ); // => 1.00M + * console.log( Base.formatSize( 1024 * 1024 * 1024 ) ); // => 1.00G + * console.log( Base.formatSize( 1024 * 1024 * 1024, 0, ['B', 'KB', 'MB'] ) ); // => 1024MB + */ + formatSize: function( size, pointLength, units ) { + var unit; + + units = units || [ 'B', 'K', 'M', 'G', 'TB' ]; + + while ( (unit = units.shift()) && size > 1024 ) { + size = size / 1024; + } + + return (unit === 'B' ? size : size.toFixed( pointLength || 2 )) + + unit; + } + }; + }); + /** + * 事件处理类,可以独立使用,也可以扩展给对象使用。 + * @fileOverview Mediator + */ + define('mediator',[ + 'base' + ], function( Base ) { + var $ = Base.$, + slice = [].slice, + separator = /\s+/, + protos; + + // 根据条件过滤出事件handlers. + function findHandlers( arr, name, callback, context ) { + return $.grep( arr, function( handler ) { + return handler && + (!name || handler.e === name) && + (!callback || handler.cb === callback || + handler.cb._cb === callback) && + (!context || handler.ctx === context); + }); + } + + function eachEvent( events, callback, iterator ) { + // 不支持对象,只支持多个event用空格隔开 + $.each( (events || '').split( separator ), function( _, key ) { + iterator( key, callback ); + }); + } + + function triggerHanders( events, args ) { + var stoped = false, + i = -1, + len = events.length, + handler; + + while ( ++i < len ) { + handler = events[ i ]; + + if ( handler.cb.apply( handler.ctx2, args ) === false ) { + stoped = true; + break; + } + } + + return !stoped; + } + + protos = { + + /** + * 绑定事件。 + * + * `callback`方法在执行时,arguments将会来源于trigger的时候携带的参数。如 + * ```javascript + * var obj = {}; + * + * // 使得obj有事件行为 + * Mediator.installTo( obj ); + * + * obj.on( 'testa', function( arg1, arg2 ) { + * console.log( arg1, arg2 ); // => 'arg1', 'arg2' + * }); + * + * obj.trigger( 'testa', 'arg1', 'arg2' ); + * ``` + * + * 如果`callback`中,某一个方法`return false`了,则后续的其他`callback`都不会被执行到。 + * 切会影响到`trigger`方法的返回值,为`false`。 + * + * `on`还可以用来添加一个特殊事件`all`, 这样所有的事件触发都会响应到。同时此类`callback`中的arguments有一个不同处, + * 就是第一个参数为`type`,记录当前是什么事件在触发。此类`callback`的优先级比脚低,会再正常`callback`执行完后触发。 + * ```javascript + * obj.on( 'all', function( type, arg1, arg2 ) { + * console.log( type, arg1, arg2 ); // => 'testa', 'arg1', 'arg2' + * }); + * ``` + * + * @method on + * @grammar on( name, callback[, context] ) => self + * @param {String} name 事件名,支持多个事件用空格隔开 + * @param {Function} callback 事件处理器 + * @param {Object} [context] 事件处理器的上下文。 + * @return {self} 返回自身,方便链式 + * @chainable + * @class Mediator + */ + on: function( name, callback, context ) { + var me = this, + set; + + if ( !callback ) { + return this; + } + + set = this._events || (this._events = []); + + eachEvent( name, callback, function( name, callback ) { + var handler = { e: name }; + + handler.cb = callback; + handler.ctx = context; + handler.ctx2 = context || me; + handler.id = set.length; + + set.push( handler ); + }); + + return this; + }, + + /** + * 绑定事件,且当handler执行完后,自动解除绑定。 + * @method once + * @grammar once( name, callback[, context] ) => self + * @param {String} name 事件名 + * @param {Function} callback 事件处理器 + * @param {Object} [context] 事件处理器的上下文。 + * @return {self} 返回自身,方便链式 + * @chainable + */ + once: function( name, callback, context ) { + var me = this; + + if ( !callback ) { + return me; + } + + eachEvent( name, callback, function( name, callback ) { + var once = function() { + me.off( name, once ); + return callback.apply( context || me, arguments ); + }; + + once._cb = callback; + me.on( name, once, context ); + }); + + return me; + }, + + /** + * 解除事件绑定 + * @method off + * @grammar off( [name[, callback[, context] ] ] ) => self + * @param {String} [name] 事件名 + * @param {Function} [callback] 事件处理器 + * @param {Object} [context] 事件处理器的上下文。 + * @return {self} 返回自身,方便链式 + * @chainable + */ + off: function( name, cb, ctx ) { + var events = this._events; + + if ( !events ) { + return this; + } + + if ( !name && !cb && !ctx ) { + this._events = []; + return this; + } + + eachEvent( name, cb, function( name, cb ) { + $.each( findHandlers( events, name, cb, ctx ), function() { + delete events[ this.id ]; + }); + }); + + return this; + }, + + /** + * 触发事件 + * @method trigger + * @grammar trigger( name[, args...] ) => self + * @param {String} type 事件名 + * @param {*} [...] 任意参数 + * @return {Boolean} 如果handler中return false了,则返回false, 否则返回true + */ + trigger: function( type ) { + var args, events, allEvents; + + if ( !this._events || !type ) { + return this; + } + + args = slice.call( arguments, 1 ); + events = findHandlers( this._events, type ); + allEvents = findHandlers( this._events, 'all' ); + + return triggerHanders( events, args ) && + triggerHanders( allEvents, arguments ); + } + }; + + /** + * 中介者,它本身是个单例,但可以通过[installTo](#WebUploader:Mediator:installTo)方法,使任何对象具备事件行为。 + * 主要目的是负责模块与模块之间的合作,降低耦合度。 + * + * @class Mediator + */ + return $.extend({ + + /** + * 可以通过这个接口,使任何对象具备事件功能。 + * @method installTo + * @param {Object} obj 需要具备事件行为的对象。 + * @return {Object} 返回obj. + */ + installTo: function( obj ) { + return $.extend( obj, protos ); + } + + }, protos ); + }); + /** + * @fileOverview Uploader上传类 + */ + define('uploader',[ + 'base', + 'mediator' + ], function( Base, Mediator ) { + + var $ = Base.$; + + /** + * 上传入口类。 + * @class Uploader + * @constructor + * @grammar new Uploader( opts ) => Uploader + * @example + * var uploader = WebUploader.Uploader({ + * swf: 'path_of_swf/Uploader.swf', + * + * // 开起分片上传。 + * chunked: true + * }); + */ + function Uploader( opts ) { + this.options = $.extend( true, {}, Uploader.options, opts ); + this._init( this.options ); + } + + // default Options + // widgets中有相应扩展 + Uploader.options = {}; + Mediator.installTo( Uploader.prototype ); + + // 批量添加纯命令式方法。 + $.each({ + upload: 'start-upload', + stop: 'stop-upload', + getFile: 'get-file', + getFiles: 'get-files', + addFile: 'add-file', + addFiles: 'add-file', + sort: 'sort-files', + removeFile: 'remove-file', + cancelFile: 'cancel-file', + skipFile: 'skip-file', + retry: 'retry', + isInProgress: 'is-in-progress', + makeThumb: 'make-thumb', + md5File: 'md5-file', + getDimension: 'get-dimension', + addButton: 'add-btn', + predictRuntimeType: 'predict-runtime-type', + refresh: 'refresh', + disable: 'disable', + enable: 'enable', + reset: 'reset' + }, function( fn, command ) { + Uploader.prototype[ fn ] = function() { + return this.request( command, arguments ); + }; + }); + + $.extend( Uploader.prototype, { + state: 'pending', + + _init: function( opts ) { + var me = this; + + me.request( 'init', opts, function() { + me.state = 'ready'; + me.trigger('ready'); + }); + }, + + /** + * 获取或者设置Uploader配置项。 + * @method option + * @grammar option( key ) => * + * @grammar option( key, val ) => self + * @example + * + * // 初始状态图片上传前不会压缩 + * var uploader = new WebUploader.Uploader({ + * compress: null; + * }); + * + * // 修改后图片上传前,尝试将图片压缩到1600 * 1600 + * uploader.option( 'compress', { + * width: 1600, + * height: 1600 + * }); + */ + option: function( key, val ) { + var opts = this.options; + + // setter + if ( arguments.length > 1 ) { + + if ( $.isPlainObject( val ) && + $.isPlainObject( opts[ key ] ) ) { + $.extend( opts[ key ], val ); + } else { + opts[ key ] = val; + } + + } else { // getter + return key ? opts[ key ] : opts; + } + }, + + /** + * 获取文件统计信息。返回一个包含一下信息的对象。 + * * `successNum` 上传成功的文件数 + * * `progressNum` 上传中的文件数 + * * `cancelNum` 被删除的文件数 + * * `invalidNum` 无效的文件数 + * * `uploadFailNum` 上传失败的文件数 + * * `queueNum` 还在队列中的文件数 + * * `interruptNum` 被暂停的文件数 + * @method getStats + * @grammar getStats() => Object + */ + getStats: function() { + // return this._mgr.getStats.apply( this._mgr, arguments ); + var stats = this.request('get-stats'); + + return stats ? { + successNum: stats.numOfSuccess, + progressNum: stats.numOfProgress, + + // who care? + // queueFailNum: 0, + cancelNum: stats.numOfCancel, + invalidNum: stats.numOfInvalid, + uploadFailNum: stats.numOfUploadFailed, + queueNum: stats.numOfQueue, + interruptNum: stats.numofInterrupt + } : {}; + }, + + // 需要重写此方法来来支持opts.onEvent和instance.onEvent的处理器 + trigger: function( type/*, args...*/ ) { + var args = [].slice.call( arguments, 1 ), + opts = this.options, + name = 'on' + type.substring( 0, 1 ).toUpperCase() + + type.substring( 1 ); + + if ( + // 调用通过on方法注册的handler. + Mediator.trigger.apply( this, arguments ) === false || + + // 调用opts.onEvent + $.isFunction( opts[ name ] ) && + opts[ name ].apply( this, args ) === false || + + // 调用this.onEvent + $.isFunction( this[ name ] ) && + this[ name ].apply( this, args ) === false || + + // 广播所有uploader的事件。 + Mediator.trigger.apply( Mediator, + [ this, type ].concat( args ) ) === false ) { + + return false; + } + + return true; + }, + + /** + * 销毁 webuploader 实例 + * @method destroy + * @grammar destroy() => undefined + */ + destroy: function() { + this.request( 'destroy', arguments ); + this.off(); + }, + + // widgets/widget.js将补充此方法的详细文档。 + request: Base.noop + }); + + /** + * 创建Uploader实例,等同于new Uploader( opts ); + * @method create + * @class Base + * @static + * @grammar Base.create( opts ) => Uploader + */ + Base.create = Uploader.create = function( opts ) { + return new Uploader( opts ); + }; + + // 暴露Uploader,可以通过它来扩展业务逻辑。 + Base.Uploader = Uploader; + + return Uploader; + }); + /** + * @fileOverview Runtime管理器,负责Runtime的选择, 连接 + */ + define('runtime/runtime',[ + 'base', + 'mediator' + ], function( Base, Mediator ) { + + var $ = Base.$, + factories = {}, + + // 获取对象的第一个key + getFirstKey = function( obj ) { + for ( var key in obj ) { + if ( obj.hasOwnProperty( key ) ) { + return key; + } + } + return null; + }; + + // 接口类。 + function Runtime( options ) { + this.options = $.extend({ + container: document.body + }, options ); + this.uid = Base.guid('rt_'); + } + + $.extend( Runtime.prototype, { + + getContainer: function() { + var opts = this.options, + parent, container; + + if ( this._container ) { + return this._container; + } + + parent = $( opts.container || document.body ); + container = $( document.createElement('div') ); + + container.attr( 'id', 'rt_' + this.uid ); + container.css({ + position: 'absolute', + top: '0px', + left: '0px', + width: '1px', + height: '1px', + overflow: 'hidden' + }); + + parent.append( container ); + parent.addClass('webuploader-container'); + this._container = container; + this._parent = parent; + return container; + }, + + init: Base.noop, + exec: Base.noop, + + destroy: function() { + this._container && this._container.remove(); + this._parent && this._parent.removeClass('webuploader-container'); + this.off(); + } + }); + + Runtime.orders = 'html5,flash'; + + + /** + * 添加Runtime实现。 + * @param {String} type 类型 + * @param {Runtime} factory 具体Runtime实现。 + */ + Runtime.addRuntime = function( type, factory ) { + factories[ type ] = factory; + }; + + Runtime.hasRuntime = function( type ) { + return !!(type ? factories[ type ] : getFirstKey( factories )); + }; + + Runtime.create = function( opts, orders ) { + var type, runtime; + + orders = orders || Runtime.orders; + $.each( orders.split( /\s*,\s*/g ), function() { + if ( factories[ this ] ) { + type = this; + return false; + } + }); + + type = type || getFirstKey( factories ); + + if ( !type ) { + throw new Error('Runtime Error'); + } + + runtime = new factories[ type ]( opts ); + return runtime; + }; + + Mediator.installTo( Runtime.prototype ); + return Runtime; + }); + + /** + * @fileOverview Runtime管理器,负责Runtime的选择, 连接 + */ + define('runtime/client',[ + 'base', + 'mediator', + 'runtime/runtime' + ], function( Base, Mediator, Runtime ) { + + var cache; + + cache = (function() { + var obj = {}; + + return { + add: function( runtime ) { + obj[ runtime.uid ] = runtime; + }, + + get: function( ruid, standalone ) { + var i; + + if ( ruid ) { + return obj[ ruid ]; + } + + for ( i in obj ) { + // 有些类型不能重用,比如filepicker. + if ( standalone && obj[ i ].__standalone ) { + continue; + } + + return obj[ i ]; + } + + return null; + }, + + remove: function( runtime ) { + delete obj[ runtime.uid ]; + } + }; + })(); + + function RuntimeClient( component, standalone ) { + var deferred = Base.Deferred(), + runtime; + + this.uid = Base.guid('client_'); + + // 允许runtime没有初始化之前,注册一些方法在初始化后执行。 + this.runtimeReady = function( cb ) { + return deferred.done( cb ); + }; + + this.connectRuntime = function( opts, cb ) { + + // already connected. + if ( runtime ) { + throw new Error('already connected!'); + } + + deferred.done( cb ); + + if ( typeof opts === 'string' && cache.get( opts ) ) { + runtime = cache.get( opts ); + } + + // 像filePicker只能独立存在,不能公用。 + runtime = runtime || cache.get( null, standalone ); + + // 需要创建 + if ( !runtime ) { + runtime = Runtime.create( opts, opts.runtimeOrder ); + runtime.__promise = deferred.promise(); + runtime.once( 'ready', deferred.resolve ); + runtime.init(); + cache.add( runtime ); + runtime.__client = 1; + } else { + // 来自cache + Base.$.extend( runtime.options, opts ); + runtime.__promise.then( deferred.resolve ); + runtime.__client++; + } + + standalone && (runtime.__standalone = standalone); + return runtime; + }; + + this.getRuntime = function() { + return runtime; + }; + + this.disconnectRuntime = function() { + if ( !runtime ) { + return; + } + + runtime.__client--; + + if ( runtime.__client <= 0 ) { + cache.remove( runtime ); + delete runtime.__promise; + runtime.destroy(); + } + + runtime = null; + }; + + this.exec = function() { + if ( !runtime ) { + return; + } + + var args = Base.slice( arguments ); + component && args.unshift( component ); + + return runtime.exec.apply( this, args ); + }; + + this.getRuid = function() { + return runtime && runtime.uid; + }; + + this.destroy = (function( destroy ) { + return function() { + destroy && destroy.apply( this, arguments ); + this.trigger('destroy'); + this.off(); + this.exec('destroy'); + this.disconnectRuntime(); + }; + })( this.destroy ); + } + + Mediator.installTo( RuntimeClient.prototype ); + return RuntimeClient; + }); + /** + * @fileOverview 错误信息 + */ + define('lib/dnd',[ + 'base', + 'mediator', + 'runtime/client' + ], function( Base, Mediator, RuntimeClent ) { + + var $ = Base.$; + + function DragAndDrop( opts ) { + opts = this.options = $.extend({}, DragAndDrop.options, opts ); + + opts.container = $( opts.container ); + + if ( !opts.container.length ) { + return; + } + + RuntimeClent.call( this, 'DragAndDrop' ); + } + + DragAndDrop.options = { + accept: null, + disableGlobalDnd: false + }; + + Base.inherits( RuntimeClent, { + constructor: DragAndDrop, + + init: function() { + var me = this; + + me.connectRuntime( me.options, function() { + me.exec('init'); + me.trigger('ready'); + }); + } + }); + + Mediator.installTo( DragAndDrop.prototype ); + + return DragAndDrop; + }); + /** + * @fileOverview 组件基类。 + */ + define('widgets/widget',[ + 'base', + 'uploader' + ], function( Base, Uploader ) { + + var $ = Base.$, + _init = Uploader.prototype._init, + _destroy = Uploader.prototype.destroy, + IGNORE = {}, + widgetClass = []; + + function isArrayLike( obj ) { + if ( !obj ) { + return false; + } + + var length = obj.length, + type = $.type( obj ); + + if ( obj.nodeType === 1 && length ) { + return true; + } + + return type === 'array' || type !== 'function' && type !== 'string' && + (length === 0 || typeof length === 'number' && length > 0 && + (length - 1) in obj); + } + + function Widget( uploader ) { + this.owner = uploader; + this.options = uploader.options; + } + + $.extend( Widget.prototype, { + + init: Base.noop, + + // 类Backbone的事件监听声明,监听uploader实例上的事件 + // widget直接无法监听事件,事件只能通过uploader来传递 + invoke: function( apiName, args ) { + + /* + { + 'make-thumb': 'makeThumb' + } + */ + var map = this.responseMap; + + // 如果无API响应声明则忽略 + if ( !map || !(apiName in map) || !(map[ apiName ] in this) || + !$.isFunction( this[ map[ apiName ] ] ) ) { + + return IGNORE; + } + + return this[ map[ apiName ] ].apply( this, args ); + + }, + + /** + * 发送命令。当传入`callback`或者`handler`中返回`promise`时。返回一个当所有`handler`中的promise都完成后完成的新`promise`。 + * @method request + * @grammar request( command, args ) => * | Promise + * @grammar request( command, args, callback ) => Promise + * @for Uploader + */ + request: function() { + return this.owner.request.apply( this.owner, arguments ); + } + }); + + // 扩展Uploader. + $.extend( Uploader.prototype, { + + /** + * @property {String | Array} [disableWidgets=undefined] + * @namespace options + * @for Uploader + * @description 默认所有 Uploader.register 了的 widget 都会被加载,如果禁用某一部分,请通过此 option 指定黑名单。 + */ + + // 覆写_init用来初始化widgets + _init: function() { + var me = this, + widgets = me._widgets = [], + deactives = me.options.disableWidgets || ''; + + $.each( widgetClass, function( _, klass ) { + (!deactives || !~deactives.indexOf( klass._name )) && + widgets.push( new klass( me ) ); + }); + + return _init.apply( me, arguments ); + }, + + request: function( apiName, args, callback ) { + var i = 0, + widgets = this._widgets, + len = widgets && widgets.length, + rlts = [], + dfds = [], + widget, rlt, promise, key; + + args = isArrayLike( args ) ? args : [ args ]; + + for ( ; i < len; i++ ) { + widget = widgets[ i ]; + rlt = widget.invoke( apiName, args ); + + if ( rlt !== IGNORE ) { + + // Deferred对象 + if ( Base.isPromise( rlt ) ) { + dfds.push( rlt ); + } else { + rlts.push( rlt ); + } + } + } + + // 如果有callback,则用异步方式。 + if ( callback || dfds.length ) { + promise = Base.when.apply( Base, dfds ); + key = promise.pipe ? 'pipe' : 'then'; + + // 很重要不能删除。删除了会死循环。 + // 保证执行顺序。让callback总是在下一个 tick 中执行。 + return promise[ key ](function() { + var deferred = Base.Deferred(), + args = arguments; + + if ( args.length === 1 ) { + args = args[ 0 ]; + } + + setTimeout(function() { + deferred.resolve( args ); + }, 1 ); + + return deferred.promise(); + })[ callback ? key : 'done' ]( callback || Base.noop ); + } else { + return rlts[ 0 ]; + } + }, + + destroy: function() { + _destroy.apply( this, arguments ); + this._widgets = null; + } + }); + + /** + * 添加组件 + * @grammar Uploader.register(proto); + * @grammar Uploader.register(map, proto); + * @param {object} responseMap API 名称与函数实现的映射 + * @param {object} proto 组件原型,构造函数通过 constructor 属性定义 + * @method Uploader.register + * @for Uploader + * @example + * Uploader.register({ + * 'make-thumb': 'makeThumb' + * }, { + * init: function( options ) {}, + * makeThumb: function() {} + * }); + * + * Uploader.register({ + * 'make-thumb': function() { + * + * } + * }); + */ + Uploader.register = Widget.register = function( responseMap, widgetProto ) { + var map = { init: 'init', destroy: 'destroy', name: 'anonymous' }, + klass; + + if ( arguments.length === 1 ) { + widgetProto = responseMap; + + // 自动生成 map 表。 + $.each(widgetProto, function(key) { + if ( key[0] === '_' || key === 'name' ) { + key === 'name' && (map.name = widgetProto.name); + return; + } + + map[key.replace(/[A-Z]/g, '-$&').toLowerCase()] = key; + }); + + } else { + map = $.extend( map, responseMap ); + } + + widgetProto.responseMap = map; + klass = Base.inherits( Widget, widgetProto ); + klass._name = map.name; + widgetClass.push( klass ); + + return klass; + }; + + /** + * 删除插件,只有在注册时指定了名字的才能被删除。 + * @grammar Uploader.unRegister(name); + * @param {string} name 组件名字 + * @method Uploader.unRegister + * @for Uploader + * @example + * + * Uploader.register({ + * name: 'custom', + * + * 'make-thumb': function() { + * + * } + * }); + * + * Uploader.unRegister('custom'); + */ + Uploader.unRegister = Widget.unRegister = function( name ) { + if ( !name || name === 'anonymous' ) { + return; + } + + // 删除指定的插件。 + for ( var i = widgetClass.length; i--; ) { + if ( widgetClass[i]._name === name ) { + widgetClass.splice(i, 1) + } + } + }; + + return Widget; + }); + /** + * @fileOverview DragAndDrop Widget。 + */ + define('widgets/filednd',[ + 'base', + 'uploader', + 'lib/dnd', + 'widgets/widget' + ], function( Base, Uploader, Dnd ) { + var $ = Base.$; + + Uploader.options.dnd = ''; + + /** + * @property {Selector} [dnd=undefined] 指定Drag And Drop拖拽的容器,如果不指定,则不启动。 + * @namespace options + * @for Uploader + */ + + /** + * @property {Selector} [disableGlobalDnd=false] 是否禁掉整个页面的拖拽功能,如果不禁用,图片拖进来的时候会默认被浏览器打开。 + * @namespace options + * @for Uploader + */ + + /** + * @event dndAccept + * @param {DataTransferItemList} items DataTransferItem + * @description 阻止此事件可以拒绝某些类型的文件拖入进来。目前只有 chrome 提供这样的 API,且只能通过 mime-type 验证。 + * @for Uploader + */ + return Uploader.register({ + name: 'dnd', + + init: function( opts ) { + + if ( !opts.dnd || + this.request('predict-runtime-type') !== 'html5' ) { + return; + } + + var me = this, + deferred = Base.Deferred(), + options = $.extend({}, { + disableGlobalDnd: opts.disableGlobalDnd, + container: opts.dnd, + accept: opts.accept + }), + dnd; + + this.dnd = dnd = new Dnd( options ); + + dnd.once( 'ready', deferred.resolve ); + dnd.on( 'drop', function( files ) { + me.request( 'add-file', [ files ]); + }); + + // 检测文件是否全部允许添加。 + dnd.on( 'accept', function( items ) { + return me.owner.trigger( 'dndAccept', items ); + }); + + dnd.init(); + + return deferred.promise(); + }, + + destroy: function() { + this.dnd && this.dnd.destroy(); + } + }); + }); + + /** + * @fileOverview 错误信息 + */ + define('lib/filepaste',[ + 'base', + 'mediator', + 'runtime/client' + ], function( Base, Mediator, RuntimeClent ) { + + var $ = Base.$; + + function FilePaste( opts ) { + opts = this.options = $.extend({}, opts ); + opts.container = $( opts.container || document.body ); + RuntimeClent.call( this, 'FilePaste' ); + } + + Base.inherits( RuntimeClent, { + constructor: FilePaste, + + init: function() { + var me = this; + + me.connectRuntime( me.options, function() { + me.exec('init'); + me.trigger('ready'); + }); + } + }); + + Mediator.installTo( FilePaste.prototype ); + + return FilePaste; + }); + /** + * @fileOverview 组件基类。 + */ + define('widgets/filepaste',[ + 'base', + 'uploader', + 'lib/filepaste', + 'widgets/widget' + ], function( Base, Uploader, FilePaste ) { + var $ = Base.$; + + /** + * @property {Selector} [paste=undefined] 指定监听paste事件的容器,如果不指定,不启用此功能。此功能为通过粘贴来添加截屏的图片。建议设置为`document.body`. + * @namespace options + * @for Uploader + */ + return Uploader.register({ + name: 'paste', + + init: function( opts ) { + + if ( !opts.paste || + this.request('predict-runtime-type') !== 'html5' ) { + return; + } + + var me = this, + deferred = Base.Deferred(), + options = $.extend({}, { + container: opts.paste, + accept: opts.accept + }), + paste; + + this.paste = paste = new FilePaste( options ); + + paste.once( 'ready', deferred.resolve ); + paste.on( 'paste', function( files ) { + me.owner.request( 'add-file', [ files ]); + }); + paste.init(); + + return deferred.promise(); + }, + + destroy: function() { + this.paste && this.paste.destroy(); + } + }); + }); + /** + * @fileOverview Blob + */ + define('lib/blob',[ + 'base', + 'runtime/client' + ], function( Base, RuntimeClient ) { + + function Blob( ruid, source ) { + var me = this; + + me.source = source; + me.ruid = ruid; + this.size = source.size || 0; + + // 如果没有指定 mimetype, 但是知道文件后缀。 + if ( !source.type && this.ext && + ~'jpg,jpeg,png,gif,bmp'.indexOf( this.ext ) ) { + this.type = 'image/' + (this.ext === 'jpg' ? 'jpeg' : this.ext); + } else { + this.type = source.type || 'application/octet-stream'; + } + + RuntimeClient.call( me, 'Blob' ); + this.uid = source.uid || this.uid; + + if ( ruid ) { + me.connectRuntime( ruid ); + } + } + + Base.inherits( RuntimeClient, { + constructor: Blob, + + slice: function( start, end ) { + return this.exec( 'slice', start, end ); + }, + + getSource: function() { + return this.source; + } + }); + + return Blob; + }); + /** + * 为了统一化Flash的File和HTML5的File而存在。 + * 以至于要调用Flash里面的File,也可以像调用HTML5版本的File一下。 + * @fileOverview File + */ + define('lib/file',[ + 'base', + 'lib/blob' + ], function( Base, Blob ) { + + var uid = 1, + rExt = /\.([^.]+)$/; + + function File( ruid, file ) { + var ext; + + this.name = file.name || ('untitled' + uid++); + ext = rExt.exec( file.name ) ? RegExp.$1.toLowerCase() : ''; + + // todo 支持其他类型文件的转换。 + // 如果有 mimetype, 但是文件名里面没有找出后缀规律 + if ( !ext && file.type ) { + ext = /\/(jpg|jpeg|png|gif|bmp)$/i.exec( file.type ) ? + RegExp.$1.toLowerCase() : ''; + this.name += '.' + ext; + } + + this.ext = ext; + this.lastModifiedDate = file.lastModifiedDate || + (new Date()).toLocaleString(); + + Blob.apply( this, arguments ); + } + + return Base.inherits( Blob, File ); + }); + + /** + * @fileOverview 错误信息 + */ + define('lib/filepicker',[ + 'base', + 'runtime/client', + 'lib/file' + ], function( Base, RuntimeClent, File ) { + + var $ = Base.$; + + function FilePicker( opts ) { + opts = this.options = $.extend({}, FilePicker.options, opts ); + + opts.container = $( opts.id ); + + if ( !opts.container.length ) { + throw new Error('按钮指定错误'); + } + + opts.innerHTML = opts.innerHTML || opts.label || + opts.container.html() || ''; + + opts.button = $( opts.button || document.createElement('div') ); + opts.button.html( opts.innerHTML ); + opts.container.html( opts.button ); + + RuntimeClent.call( this, 'FilePicker', true ); + } + + FilePicker.options = { + button: null, + container: null, + label: null, + innerHTML: null, + multiple: true, + accept: null, + name: 'file' + }; + + Base.inherits( RuntimeClent, { + constructor: FilePicker, + + init: function() { + var me = this, + opts = me.options, + button = opts.button; + + button.addClass('webuploader-pick'); + + me.on( 'all', function( type ) { + var files; + + switch ( type ) { + case 'mouseenter': + button.addClass('webuploader-pick-hover'); + break; + + case 'mouseleave': + button.removeClass('webuploader-pick-hover'); + break; + + case 'change': + files = me.exec('getFiles'); + me.trigger( 'select', $.map( files, function( file ) { + file = new File( me.getRuid(), file ); + + // 记录来源。 + file._refer = opts.container; + return file; + }), opts.container ); + break; + } + }); + + me.connectRuntime( opts, function() { + me.refresh(); + me.exec( 'init', opts ); + me.trigger('ready'); + }); + + this._resizeHandler = Base.bindFn( this.refresh, this ); + $( window ).on( 'resize', this._resizeHandler ); + }, + + refresh: function() { + var shimContainer = this.getRuntime().getContainer(), + button = this.options.button, + width = button.outerWidth ? + button.outerWidth() : button.width(), + + height = button.outerHeight ? + button.outerHeight() : button.height(), + + pos = button.offset(); + + width && height && shimContainer.css({ + bottom: 'auto', + right: 'auto', + width: width + 'px', + height: height + 'px' + }).offset( pos ); + }, + + enable: function() { + var btn = this.options.button; + + btn.removeClass('webuploader-pick-disable'); + this.refresh(); + }, + + disable: function() { + var btn = this.options.button; + + this.getRuntime().getContainer().css({ + top: '-99999px' + }); + + btn.addClass('webuploader-pick-disable'); + }, + + destroy: function() { + var btn = this.options.button; + $( window ).off( 'resize', this._resizeHandler ); + btn.removeClass('webuploader-pick-disable webuploader-pick-hover ' + + 'webuploader-pick'); + } + }); + + return FilePicker; + }); + + /** + * @fileOverview 文件选择相关 + */ + define('widgets/filepicker',[ + 'base', + 'uploader', + 'lib/filepicker', + 'widgets/widget' + ], function( Base, Uploader, FilePicker ) { + var $ = Base.$; + + $.extend( Uploader.options, { + + /** + * @property {Selector | Object} [pick=undefined] + * @namespace options + * @for Uploader + * @description 指定选择文件的按钮容器,不指定则不创建按钮。 + * + * * `id` {Seletor|dom} 指定选择文件的按钮容器,不指定则不创建按钮。**注意** 这里虽然写的是 id, 但是不是只支持 id, 还支持 class, 或者 dom 节点。 + * * `label` {String} 请采用 `innerHTML` 代替 + * * `innerHTML` {String} 指定按钮文字。不指定时优先从指定的容器中看是否自带文字。 + * * `multiple` {Boolean} 是否开起同时选择多个文件能力。 + */ + pick: null, + + /** + * @property {Arroy} [accept=null] + * @namespace options + * @for Uploader + * @description 指定接受哪些类型的文件。 由于目前还有ext转mimeType表,所以这里需要分开指定。 + * + * * `title` {String} 文字描述 + * * `extensions` {String} 允许的文件后缀,不带点,多个用逗号分割。 + * * `mimeTypes` {String} 多个用逗号分割。 + * + * 如: + * + * ``` + * { + * title: 'Images', + * extensions: 'gif,jpg,jpeg,bmp,png', + * mimeTypes: 'image/*' + * } + * ``` + */ + accept: null/*{ + title: 'Images', + extensions: 'gif,jpg,jpeg,bmp,png', + mimeTypes: 'image/*' + }*/ + }); + + return Uploader.register({ + name: 'picker', + + init: function( opts ) { + this.pickers = []; + return opts.pick && this.addBtn( opts.pick ); + }, + + refresh: function() { + $.each( this.pickers, function() { + this.refresh(); + }); + }, + + /** + * @method addButton + * @for Uploader + * @grammar addButton( pick ) => Promise + * @description + * 添加文件选择按钮,如果一个按钮不够,需要调用此方法来添加。参数跟[options.pick](#WebUploader:Uploader:options)一致。 + * @example + * uploader.addButton({ + * id: '#btnContainer', + * innerHTML: '选择文件' + * }); + */ + addBtn: function( pick ) { + var me = this, + opts = me.options, + accept = opts.accept, + promises = []; + + if ( !pick ) { + return; + } + + $.isPlainObject( pick ) || (pick = { + id: pick + }); + + $( pick.id ).each(function() { + var options, picker, deferred; + + deferred = Base.Deferred(); + + options = $.extend({}, pick, { + accept: $.isPlainObject( accept ) ? [ accept ] : accept, + swf: opts.swf, + runtimeOrder: opts.runtimeOrder, + id: this + }); + + picker = new FilePicker( options ); + + picker.once( 'ready', deferred.resolve ); + picker.on( 'select', function( files ) { + me.owner.request( 'add-file', [ files ]); + }); + picker.init(); + + me.pickers.push( picker ); + + promises.push( deferred.promise() ); + }); + + return Base.when.apply( Base, promises ); + }, + + disable: function() { + $.each( this.pickers, function() { + this.disable(); + }); + }, + + enable: function() { + $.each( this.pickers, function() { + this.enable(); + }); + }, + + destroy: function() { + $.each( this.pickers, function() { + this.destroy(); + }); + this.pickers = null; + } + }); + }); + /** + * @fileOverview Image + */ + define('lib/image',[ + 'base', + 'runtime/client', + 'lib/blob' + ], function( Base, RuntimeClient, Blob ) { + var $ = Base.$; + + // 构造器。 + function Image( opts ) { + this.options = $.extend({}, Image.options, opts ); + RuntimeClient.call( this, 'Image' ); + + this.on( 'load', function() { + this._info = this.exec('info'); + this._meta = this.exec('meta'); + }); + } + + // 默认选项。 + Image.options = { + + // 默认的图片处理质量 + quality: 90, + + // 是否裁剪 + crop: false, + + // 是否保留头部信息 + preserveHeaders: false, + + // 是否允许放大。 + allowMagnify: false + }; + + // 继承RuntimeClient. + Base.inherits( RuntimeClient, { + constructor: Image, + + info: function( val ) { + + // setter + if ( val ) { + this._info = val; + return this; + } + + // getter + return this._info; + }, + + meta: function( val ) { + + // setter + if ( val ) { + this._meta = val; + return this; + } + + // getter + return this._meta; + }, + + loadFromBlob: function( blob ) { + var me = this, + ruid = blob.getRuid(); + + this.connectRuntime( ruid, function() { + me.exec( 'init', me.options ); + me.exec( 'loadFromBlob', blob ); + }); + }, + + resize: function() { + var args = Base.slice( arguments ); + return this.exec.apply( this, [ 'resize' ].concat( args ) ); + }, + + crop: function() { + var args = Base.slice( arguments ); + return this.exec.apply( this, [ 'crop' ].concat( args ) ); + }, + + getAsDataUrl: function( type ) { + return this.exec( 'getAsDataUrl', type ); + }, + + getAsBlob: function( type ) { + var blob = this.exec( 'getAsBlob', type ); + + return new Blob( this.getRuid(), blob ); + } + }); + + return Image; + }); + /** + * @fileOverview 图片操作, 负责预览图片和上传前压缩图片 + */ + define('widgets/image',[ + 'base', + 'uploader', + 'lib/image', + 'widgets/widget' + ], function( Base, Uploader, Image ) { + + var $ = Base.$, + throttle; + + // 根据要处理的文件大小来节流,一次不能处理太多,会卡。 + throttle = (function( max ) { + var occupied = 0, + waiting = [], + tick = function() { + var item; + + while ( waiting.length && occupied < max ) { + item = waiting.shift(); + occupied += item[ 0 ]; + item[ 1 ](); + } + }; + + return function( emiter, size, cb ) { + waiting.push([ size, cb ]); + emiter.once( 'destroy', function() { + occupied -= size; + setTimeout( tick, 1 ); + }); + setTimeout( tick, 1 ); + }; + })( 5 * 1024 * 1024 ); + + $.extend( Uploader.options, { + + /** + * @property {Object} [thumb] + * @namespace options + * @for Uploader + * @description 配置生成缩略图的选项。 + * + * 默认为: + * + * ```javascript + * { + * width: 110, + * height: 110, + * + * // 图片质量,只有type为`image/jpeg`的时候才有效。 + * quality: 70, + * + * // 是否允许放大,如果想要生成小图的时候不失真,此选项应该设置为false. + * allowMagnify: true, + * + * // 是否允许裁剪。 + * crop: true, + * + * // 为空的话则保留原有图片格式。 + * // 否则强制转换成指定的类型。 + * type: 'image/jpeg' + * } + * ``` + */ + thumb: { + width: 110, + height: 110, + quality: 70, + allowMagnify: true, + crop: true, + preserveHeaders: false, + + // 为空的话则保留原有图片格式。 + // 否则强制转换成指定的类型。 + // IE 8下面 base64 大小不能超过 32K 否则预览失败,而非 jpeg 编码的图片很可 + // 能会超过 32k, 所以这里设置成预览的时候都是 image/jpeg + type: 'image/jpeg' + }, + + /** + * @property {Object} [compress] + * @namespace options + * @for Uploader + * @description 配置压缩的图片的选项。如果此选项为`false`, 则图片在上传前不进行压缩。 + * + * 默认为: + * + * ```javascript + * { + * width: 1600, + * height: 1600, + * + * // 图片质量,只有type为`image/jpeg`的时候才有效。 + * quality: 90, + * + * // 是否允许放大,如果想要生成小图的时候不失真,此选项应该设置为false. + * allowMagnify: false, + * + * // 是否允许裁剪。 + * crop: false, + * + * // 是否保留头部meta信息。 + * preserveHeaders: true, + * + * // 如果发现压缩后文件大小比原来还大,则使用原来图片 + * // 此属性可能会影响图片自动纠正功能 + * noCompressIfLarger: false, + * + * // 单位字节,如果图片大小小于此值,不会采用压缩。 + * compressSize: 0 + * } + * ``` + */ + compress: { + width: 1600, + height: 1600, + quality: 90, + allowMagnify: false, + crop: false, + preserveHeaders: true + } + }); + + return Uploader.register({ + + name: 'image', + + + /** + * 生成缩略图,此过程为异步,所以需要传入`callback`。 + * 通常情况在图片加入队里后调用此方法来生成预览图以增强交互效果。 + * + * 当 width 或者 height 的值介于 0 - 1 时,被当成百分比使用。 + * + * `callback`中可以接收到两个参数。 + * * 第一个为error,如果生成缩略图有错误,此error将为真。 + * * 第二个为ret, 缩略图的Data URL值。 + * + * **注意** + * Date URL在IE6/7中不支持,所以不用调用此方法了,直接显示一张暂不支持预览图片好了。 + * 也可以借助服务端,将 base64 数据传给服务端,生成一个临时文件供预览。 + * + * @method makeThumb + * @grammar makeThumb( file, callback ) => undefined + * @grammar makeThumb( file, callback, width, height ) => undefined + * @for Uploader + * @example + * + * uploader.on( 'fileQueued', function( file ) { + * var $li = ...; + * + * uploader.makeThumb( file, function( error, ret ) { + * if ( error ) { + * $li.text('预览错误'); + * } else { + * $li.append(''); + * } + * }); + * + * }); + */ + makeThumb: function( file, cb, width, height ) { + var opts, image; + + file = this.request( 'get-file', file ); + + // 只预览图片格式。 + if ( !file.type.match( /^image/ ) ) { + cb( true ); + return; + } + + opts = $.extend({}, this.options.thumb ); + + // 如果传入的是object. + if ( $.isPlainObject( width ) ) { + opts = $.extend( opts, width ); + width = null; + } + + width = width || opts.width; + height = height || opts.height; + + image = new Image( opts ); + + image.once( 'load', function() { + file._info = file._info || image.info(); + file._meta = file._meta || image.meta(); + + // 如果 width 的值介于 0 - 1 + // 说明设置的是百分比。 + if ( width <= 1 && width > 0 ) { + width = file._info.width * width; + } + + // 同样的规则应用于 height + if ( height <= 1 && height > 0 ) { + height = file._info.height * height; + } + + image.resize( width, height ); + }); + + // 当 resize 完后 + image.once( 'complete', function() { + cb( false, image.getAsDataUrl( opts.type ) ); + image.destroy(); + }); + + image.once( 'error', function( reason ) { + cb( reason || true ); + image.destroy(); + }); + + throttle( image, file.source.size, function() { + file._info && image.info( file._info ); + file._meta && image.meta( file._meta ); + image.loadFromBlob( file.source ); + }); + }, + + beforeSendFile: function( file ) { + var opts = this.options.compress || this.options.resize, + compressSize = opts && opts.compressSize || 0, + noCompressIfLarger = opts && opts.noCompressIfLarger || false, + image, deferred; + + file = this.request( 'get-file', file ); + + // 只压缩 jpeg 图片格式。 + // gif 可能会丢失针 + // bmp png 基本上尺寸都不大,且压缩比比较小。 + if ( !opts || !~'image/jpeg,image/jpg'.indexOf( file.type ) || + file.size < compressSize || + file._compressed ) { + return; + } + + opts = $.extend({}, opts ); + deferred = Base.Deferred(); + + image = new Image( opts ); + + deferred.always(function() { + image.destroy(); + image = null; + }); + image.once( 'error', deferred.reject ); + image.once( 'load', function() { + var width = opts.width, + height = opts.height; + + file._info = file._info || image.info(); + file._meta = file._meta || image.meta(); + + // 如果 width 的值介于 0 - 1 + // 说明设置的是百分比。 + if ( width <= 1 && width > 0 ) { + width = file._info.width * width; + } + + // 同样的规则应用于 height + if ( height <= 1 && height > 0 ) { + height = file._info.height * height; + } + + image.resize( width, height ); + }); + + image.once( 'complete', function() { + var blob, size; + + // 移动端 UC / qq 浏览器的无图模式下 + // ctx.getImageData 处理大图的时候会报 Exception + // INDEX_SIZE_ERR: DOM Exception 1 + try { + blob = image.getAsBlob( opts.type ); + + size = file.size; + + // 如果压缩后,比原来还大则不用压缩后的。 + if ( !noCompressIfLarger || blob.size < size ) { + // file.source.destroy && file.source.destroy(); + file.source = blob; + file.size = blob.size; + + file.trigger( 'resize', blob.size, size ); + } + + // 标记,避免重复压缩。 + file._compressed = true; + deferred.resolve(); + } catch ( e ) { + // 出错了直接继续,让其上传原始图片 + deferred.resolve(); + } + }); + + file._info && image.info( file._info ); + file._meta && image.meta( file._meta ); + + image.loadFromBlob( file.source ); + return deferred.promise(); + } + }); + }); + /** + * @fileOverview 文件属性封装 + */ + define('file',[ + 'base', + 'mediator' + ], function( Base, Mediator ) { + + var $ = Base.$, + idPrefix = 'WU_FILE_', + idSuffix = 0, + rExt = /\.([^.]+)$/, + statusMap = {}; + + function gid() { + return idPrefix + idSuffix++; + } + + /** + * 文件类 + * @class File + * @constructor 构造函数 + * @grammar new File( source ) => File + * @param {Lib.File} source [lib.File](#Lib.File)实例, 此source对象是带有Runtime信息的。 + */ + function WUFile( source ) { + + /** + * 文件名,包括扩展名(后缀) + * @property name + * @type {string} + */ + this.name = source.name || 'Untitled'; + + /** + * 文件体积(字节) + * @property size + * @type {uint} + * @default 0 + */ + this.size = source.size || 0; + + /** + * 文件MIMETYPE类型,与文件类型的对应关系请参考[http://t.cn/z8ZnFny](http://t.cn/z8ZnFny) + * @property type + * @type {string} + * @default 'application/octet-stream' + */ + this.type = source.type || 'application/octet-stream'; + + /** + * 文件最后修改日期 + * @property lastModifiedDate + * @type {int} + * @default 当前时间戳 + */ + this.lastModifiedDate = source.lastModifiedDate || (new Date() * 1); + + /** + * 文件ID,每个对象具有唯一ID,与文件名无关 + * @property id + * @type {string} + */ + this.id = gid(); + + /** + * 文件扩展名,通过文件名获取,例如test.png的扩展名为png + * @property ext + * @type {string} + */ + this.ext = rExt.exec( this.name ) ? RegExp.$1 : ''; + + + /** + * 状态文字说明。在不同的status语境下有不同的用途。 + * @property statusText + * @type {string} + */ + this.statusText = ''; + + // 存储文件状态,防止通过属性直接修改 + statusMap[ this.id ] = WUFile.Status.INITED; + + this.source = source; + this.loaded = 0; + + this.on( 'error', function( msg ) { + this.setStatus( WUFile.Status.ERROR, msg ); + }); + } + + $.extend( WUFile.prototype, { + + /** + * 设置状态,状态变化时会触发`change`事件。 + * @method setStatus + * @grammar setStatus( status[, statusText] ); + * @param {File.Status|String} status [文件状态值](#WebUploader:File:File.Status) + * @param {String} [statusText=''] 状态说明,常在error时使用,用http, abort,server等来标记是由于什么原因导致文件错误。 + */ + setStatus: function( status, text ) { + + var prevStatus = statusMap[ this.id ]; + + typeof text !== 'undefined' && (this.statusText = text); + + if ( status !== prevStatus ) { + statusMap[ this.id ] = status; + /** + * 文件状态变化 + * @event statuschange + */ + this.trigger( 'statuschange', status, prevStatus ); + } + + }, + + /** + * 获取文件状态 + * @return {File.Status} + * @example + 文件状态具体包括以下几种类型: + { + // 初始化 + INITED: 0, + // 已入队列 + QUEUED: 1, + // 正在上传 + PROGRESS: 2, + // 上传出错 + ERROR: 3, + // 上传成功 + COMPLETE: 4, + // 上传取消 + CANCELLED: 5 + } + */ + getStatus: function() { + return statusMap[ this.id ]; + }, + + /** + * 获取文件原始信息。 + * @return {*} + */ + getSource: function() { + return this.source; + }, + + destroy: function() { + this.off(); + delete statusMap[ this.id ]; + } + }); + + Mediator.installTo( WUFile.prototype ); + + /** + * 文件状态值,具体包括以下几种类型: + * * `inited` 初始状态 + * * `queued` 已经进入队列, 等待上传 + * * `progress` 上传中 + * * `complete` 上传完成。 + * * `error` 上传出错,可重试 + * * `interrupt` 上传中断,可续传。 + * * `invalid` 文件不合格,不能重试上传。会自动从队列中移除。 + * * `cancelled` 文件被移除。 + * @property {Object} Status + * @namespace File + * @class File + * @static + */ + WUFile.Status = { + INITED: 'inited', // 初始状态 + QUEUED: 'queued', // 已经进入队列, 等待上传 + PROGRESS: 'progress', // 上传中 + ERROR: 'error', // 上传出错,可重试 + COMPLETE: 'complete', // 上传完成。 + CANCELLED: 'cancelled', // 上传取消。 + INTERRUPT: 'interrupt', // 上传中断,可续传。 + INVALID: 'invalid' // 文件不合格,不能重试上传。 + }; + + return WUFile; + }); + + /** + * @fileOverview 文件队列 + */ + define('queue',[ + 'base', + 'mediator', + 'file' + ], function( Base, Mediator, WUFile ) { + + var $ = Base.$, + STATUS = WUFile.Status; + + /** + * 文件队列, 用来存储各个状态中的文件。 + * @class Queue + * @extends Mediator + */ + function Queue() { + + /** + * 统计文件数。 + * * `numOfQueue` 队列中的文件数。 + * * `numOfSuccess` 上传成功的文件数 + * * `numOfCancel` 被取消的文件数 + * * `numOfProgress` 正在上传中的文件数 + * * `numOfUploadFailed` 上传错误的文件数。 + * * `numOfInvalid` 无效的文件数。 + * * `numofDeleted` 被移除的文件数。 + * @property {Object} stats + */ + this.stats = { + numOfQueue: 0, + numOfSuccess: 0, + numOfCancel: 0, + numOfProgress: 0, + numOfUploadFailed: 0, + numOfInvalid: 0, + numofDeleted: 0, + numofInterrupt: 0 + }; + + // 上传队列,仅包括等待上传的文件 + this._queue = []; + + // 存储所有文件 + this._map = {}; + } + + $.extend( Queue.prototype, { + + /** + * 将新文件加入对队列尾部 + * + * @method append + * @param {File} file 文件对象 + */ + append: function( file ) { + this._queue.push( file ); + this._fileAdded( file ); + return this; + }, + + /** + * 将新文件加入对队列头部 + * + * @method prepend + * @param {File} file 文件对象 + */ + prepend: function( file ) { + this._queue.unshift( file ); + this._fileAdded( file ); + return this; + }, + + /** + * 获取文件对象 + * + * @method getFile + * @param {String} fileId 文件ID + * @return {File} + */ + getFile: function( fileId ) { + if ( typeof fileId !== 'string' ) { + return fileId; + } + return this._map[ fileId ]; + }, + + /** + * 从队列中取出一个指定状态的文件。 + * @grammar fetch( status ) => File + * @method fetch + * @param {String} status [文件状态值](#WebUploader:File:File.Status) + * @return {File} [File](#WebUploader:File) + */ + fetch: function( status ) { + var len = this._queue.length, + i, file; + + status = status || STATUS.QUEUED; + + for ( i = 0; i < len; i++ ) { + file = this._queue[ i ]; + + if ( status === file.getStatus() ) { + return file; + } + } + + return null; + }, + + /** + * 对队列进行排序,能够控制文件上传顺序。 + * @grammar sort( fn ) => undefined + * @method sort + * @param {Function} fn 排序方法 + */ + sort: function( fn ) { + if ( typeof fn === 'function' ) { + this._queue.sort( fn ); + } + }, + + /** + * 获取指定类型的文件列表, 列表中每一个成员为[File](#WebUploader:File)对象。 + * @grammar getFiles( [status1[, status2 ...]] ) => Array + * @method getFiles + * @param {String} [status] [文件状态值](#WebUploader:File:File.Status) + */ + getFiles: function() { + var sts = [].slice.call( arguments, 0 ), + ret = [], + i = 0, + len = this._queue.length, + file; + + for ( ; i < len; i++ ) { + file = this._queue[ i ]; + + if ( sts.length && !~$.inArray( file.getStatus(), sts ) ) { + continue; + } + + ret.push( file ); + } + + return ret; + }, + + /** + * 在队列中删除文件。 + * @grammar removeFile( file ) => Array + * @method removeFile + * @param {File} 文件对象。 + */ + removeFile: function( file ) { + var me = this, + existing = this._map[ file.id ]; + + if ( existing ) { + delete this._map[ file.id ]; + file.destroy(); + this.stats.numofDeleted++; + } + }, + + _fileAdded: function( file ) { + var me = this, + existing = this._map[ file.id ]; + + if ( !existing ) { + this._map[ file.id ] = file; + + file.on( 'statuschange', function( cur, pre ) { + me._onFileStatusChange( cur, pre ); + }); + } + }, + + _onFileStatusChange: function( curStatus, preStatus ) { + var stats = this.stats; + + switch ( preStatus ) { + case STATUS.PROGRESS: + stats.numOfProgress--; + break; + + case STATUS.QUEUED: + stats.numOfQueue --; + break; + + case STATUS.ERROR: + stats.numOfUploadFailed--; + break; + + case STATUS.INVALID: + stats.numOfInvalid--; + break; + + case STATUS.INTERRUPT: + stats.numofInterrupt--; + break; + } + + switch ( curStatus ) { + case STATUS.QUEUED: + stats.numOfQueue++; + break; + + case STATUS.PROGRESS: + stats.numOfProgress++; + break; + + case STATUS.ERROR: + stats.numOfUploadFailed++; + break; + + case STATUS.COMPLETE: + stats.numOfSuccess++; + break; + + case STATUS.CANCELLED: + stats.numOfCancel++; + break; + + + case STATUS.INVALID: + stats.numOfInvalid++; + break; + + case STATUS.INTERRUPT: + stats.numofInterrupt++; + break; + } + } + + }); + + Mediator.installTo( Queue.prototype ); + + return Queue; + }); + /** + * @fileOverview 队列 + */ + define('widgets/queue',[ + 'base', + 'uploader', + 'queue', + 'file', + 'lib/file', + 'runtime/client', + 'widgets/widget' + ], function( Base, Uploader, Queue, WUFile, File, RuntimeClient ) { + + var $ = Base.$, + rExt = /\.\w+$/, + Status = WUFile.Status; + + return Uploader.register({ + name: 'queue', + + init: function( opts ) { + var me = this, + deferred, len, i, item, arr, accept, runtime; + + if ( $.isPlainObject( opts.accept ) ) { + opts.accept = [ opts.accept ]; + } + + // accept中的中生成匹配正则。 + if ( opts.accept ) { + arr = []; + + for ( i = 0, len = opts.accept.length; i < len; i++ ) { + item = opts.accept[ i ].extensions; + item && arr.push( item ); + } + + if ( arr.length ) { + accept = '\\.' + arr.join(',') + .replace( /,/g, '$|\\.' ) + .replace( /\*/g, '.*' ) + '$'; + } + + me.accept = new RegExp( accept, 'i' ); + } + + me.queue = new Queue(); + me.stats = me.queue.stats; + + // 如果当前不是html5运行时,那就算了。 + // 不执行后续操作 + if ( this.request('predict-runtime-type') !== 'html5' ) { + return; + } + + // 创建一个 html5 运行时的 placeholder + // 以至于外部添加原生 File 对象的时候能正确包裹一下供 webuploader 使用。 + deferred = Base.Deferred(); + this.placeholder = runtime = new RuntimeClient('Placeholder'); + runtime.connectRuntime({ + runtimeOrder: 'html5' + }, function() { + me._ruid = runtime.getRuid(); + deferred.resolve(); + }); + return deferred.promise(); + }, + + + // 为了支持外部直接添加一个原生File对象。 + _wrapFile: function( file ) { + if ( !(file instanceof WUFile) ) { + + if ( !(file instanceof File) ) { + if ( !this._ruid ) { + throw new Error('Can\'t add external files.'); + } + file = new File( this._ruid, file ); + } + + file = new WUFile( file ); + } + + return file; + }, + + // 判断文件是否可以被加入队列 + acceptFile: function( file ) { + var invalid = !file || !file.size || this.accept && + + // 如果名字中有后缀,才做后缀白名单处理。 + rExt.exec( file.name ) && !this.accept.test( file.name ); + + return !invalid; + }, + + + /** + * @event beforeFileQueued + * @param {File} file File对象 + * @description 当文件被加入队列之前触发,此事件的handler返回值为`false`,则此文件不会被添加进入队列。 + * @for Uploader + */ + + /** + * @event fileQueued + * @param {File} file File对象 + * @description 当文件被加入队列以后触发。 + * @for Uploader + */ + + _addFile: function( file ) { + var me = this; + + file = me._wrapFile( file ); + + // 不过类型判断允许不允许,先派送 `beforeFileQueued` + if ( !me.owner.trigger( 'beforeFileQueued', file ) ) { + return; + } + + // 类型不匹配,则派送错误事件,并返回。 + if ( !me.acceptFile( file ) ) { + me.owner.trigger( 'error', 'Q_TYPE_DENIED', file ); + return; + } + + me.queue.append( file ); + me.owner.trigger( 'fileQueued', file ); + return file; + }, + + getFile: function( fileId ) { + return this.queue.getFile( fileId ); + }, + + /** + * @event filesQueued + * @param {File} files 数组,内容为原始File(lib/File)对象。 + * @description 当一批文件添加进队列以后触发。 + * @for Uploader + */ + + /** + * @property {Boolean} [auto=false] + * @namespace options + * @for Uploader + * @description 设置为 true 后,不需要手动调用上传,有文件选择即开始上传。 + * + */ + + /** + * @method addFiles + * @grammar addFiles( file ) => undefined + * @grammar addFiles( [file1, file2 ...] ) => undefined + * @param {Array of File or File} [files] Files 对象 数组 + * @description 添加文件到队列 + * @for Uploader + */ + addFile: function( files ) { + var me = this; + + if ( !files.length ) { + files = [ files ]; + } + + files = $.map( files, function( file ) { + return me._addFile( file ); + }); + + me.owner.trigger( 'filesQueued', files ); + + if ( me.options.auto ) { + setTimeout(function() { + me.request('start-upload'); + }, 20 ); + } + }, + + getStats: function() { + return this.stats; + }, + + /** + * @event fileDequeued + * @param {File} file File对象 + * @description 当文件被移除队列后触发。 + * @for Uploader + */ + + /** + * @method removeFile + * @grammar removeFile( file ) => undefined + * @grammar removeFile( id ) => undefined + * @grammar removeFile( file, true ) => undefined + * @grammar removeFile( id, true ) => undefined + * @param {File|id} file File对象或这File对象的id + * @description 移除某一文件, 默认只会标记文件状态为已取消,如果第二个参数为 `true` 则会从 queue 中移除。 + * @for Uploader + * @example + * + * $li.on('click', '.remove-this', function() { + * uploader.removeFile( file ); + * }) + */ + removeFile: function( file, remove ) { + var me = this; + + file = file.id ? file : me.queue.getFile( file ); + + this.request( 'cancel-file', file ); + + if ( remove ) { + this.queue.removeFile( file ); + } + }, + + /** + * @method getFiles + * @grammar getFiles() => Array + * @grammar getFiles( status1, status2, status... ) => Array + * @description 返回指定状态的文件集合,不传参数将返回所有状态的文件。 + * @for Uploader + * @example + * console.log( uploader.getFiles() ); // => all files + * console.log( uploader.getFiles('error') ) // => all error files. + */ + getFiles: function() { + return this.queue.getFiles.apply( this.queue, arguments ); + }, + + fetchFile: function() { + return this.queue.fetch.apply( this.queue, arguments ); + }, + + /** + * @method retry + * @grammar retry() => undefined + * @grammar retry( file ) => undefined + * @description 重试上传,重试指定文件,或者从出错的文件开始重新上传。 + * @for Uploader + * @example + * function retry() { + * uploader.retry(); + * } + */ + retry: function( file, noForceStart ) { + var me = this, + files, i, len; + + if ( file ) { + file = file.id ? file : me.queue.getFile( file ); + file.setStatus( Status.QUEUED ); + noForceStart || me.request('start-upload'); + return; + } + + files = me.queue.getFiles( Status.ERROR ); + i = 0; + len = files.length; + + for ( ; i < len; i++ ) { + file = files[ i ]; + file.setStatus( Status.QUEUED ); + } + + me.request('start-upload'); + }, + + /** + * @method sort + * @grammar sort( fn ) => undefined + * @description 排序队列中的文件,在上传之前调整可以控制上传顺序。 + * @for Uploader + */ + sortFiles: function() { + return this.queue.sort.apply( this.queue, arguments ); + }, + + /** + * @event reset + * @description 当 uploader 被重置的时候触发。 + * @for Uploader + */ + + /** + * @method reset + * @grammar reset() => undefined + * @description 重置uploader。目前只重置了队列。 + * @for Uploader + * @example + * uploader.reset(); + */ + reset: function() { + this.owner.trigger('reset'); + this.queue = new Queue(); + this.stats = this.queue.stats; + }, + + destroy: function() { + this.reset(); + this.placeholder && this.placeholder.destroy(); + } + }); + + }); + /** + * @fileOverview 添加获取Runtime相关信息的方法。 + */ + define('widgets/runtime',[ + 'uploader', + 'runtime/runtime', + 'widgets/widget' + ], function( Uploader, Runtime ) { + + Uploader.support = function() { + return Runtime.hasRuntime.apply( Runtime, arguments ); + }; + + /** + * @property {Object} [runtimeOrder=html5,flash] + * @namespace options + * @for Uploader + * @description 指定运行时启动顺序。默认会想尝试 html5 是否支持,如果支持则使用 html5, 否则则使用 flash. + * + * 可以将此值设置成 `flash`,来强制使用 flash 运行时。 + */ + + return Uploader.register({ + name: 'runtime', + + init: function() { + if ( !this.predictRuntimeType() ) { + throw Error('Runtime Error'); + } + }, + + /** + * 预测Uploader将采用哪个`Runtime` + * @grammar predictRuntimeType() => String + * @method predictRuntimeType + * @for Uploader + */ + predictRuntimeType: function() { + var orders = this.options.runtimeOrder || Runtime.orders, + type = this.type, + i, len; + + if ( !type ) { + orders = orders.split( /\s*,\s*/g ); + + for ( i = 0, len = orders.length; i < len; i++ ) { + if ( Runtime.hasRuntime( orders[ i ] ) ) { + this.type = type = orders[ i ]; + break; + } + } + } + + return type; + } + }); + }); + /** + * @fileOverview Transport + */ + define('lib/transport',[ + 'base', + 'runtime/client', + 'mediator' + ], function( Base, RuntimeClient, Mediator ) { + + var $ = Base.$; + + function Transport( opts ) { + var me = this; + + opts = me.options = $.extend( true, {}, Transport.options, opts || {} ); + RuntimeClient.call( this, 'Transport' ); + + this._blob = null; + this._formData = opts.formData || {}; + this._headers = opts.headers || {}; + + this.on( 'progress', this._timeout ); + this.on( 'load error', function() { + me.trigger( 'progress', 1 ); + clearTimeout( me._timer ); + }); + } + + Transport.options = { + server: '', + method: 'POST', + + // 跨域时,是否允许携带cookie, 只有html5 runtime才有效 + withCredentials: false, + fileVal: 'file', + timeout: 2 * 60 * 1000, // 2分钟 + formData: {}, + headers: {}, + sendAsBinary: false + }; + + $.extend( Transport.prototype, { + + // 添加Blob, 只能添加一次,最后一次有效。 + appendBlob: function( key, blob, filename ) { + var me = this, + opts = me.options; + + if ( me.getRuid() ) { + me.disconnectRuntime(); + } + + // 连接到blob归属的同一个runtime. + me.connectRuntime( blob.ruid, function() { + me.exec('init'); + }); + + me._blob = blob; + opts.fileVal = key || opts.fileVal; + opts.filename = filename || opts.filename; + }, + + // 添加其他字段 + append: function( key, value ) { + if ( typeof key === 'object' ) { + $.extend( this._formData, key ); + } else { + this._formData[ key ] = value; + } + }, + + setRequestHeader: function( key, value ) { + if ( typeof key === 'object' ) { + $.extend( this._headers, key ); + } else { + this._headers[ key ] = value; + } + }, + + send: function( method ) { + this.exec( 'send', method ); + this._timeout(); + }, + + abort: function() { + clearTimeout( this._timer ); + return this.exec('abort'); + }, + + destroy: function() { + this.trigger('destroy'); + this.off(); + this.exec('destroy'); + this.disconnectRuntime(); + }, + + getResponse: function() { + return this.exec('getResponse'); + }, + + getResponseAsJson: function() { + return this.exec('getResponseAsJson'); + }, + + getStatus: function() { + return this.exec('getStatus'); + }, + + _timeout: function() { + var me = this, + duration = me.options.timeout; + + if ( !duration ) { + return; + } + + clearTimeout( me._timer ); + me._timer = setTimeout(function() { + me.abort(); + me.trigger( 'error', 'timeout' ); + }, duration ); + } + + }); + + // 让Transport具备事件功能。 + Mediator.installTo( Transport.prototype ); + + return Transport; + }); + /** + * @fileOverview 负责文件上传相关。 + */ + define('widgets/upload',[ + 'base', + 'uploader', + 'file', + 'lib/transport', + 'widgets/widget' + ], function( Base, Uploader, WUFile, Transport ) { + + var $ = Base.$, + isPromise = Base.isPromise, + Status = WUFile.Status; + + // 添加默认配置项 + $.extend( Uploader.options, { + + + /** + * @property {Boolean} [prepareNextFile=false] + * @namespace options + * @for Uploader + * @description 是否允许在文件传输时提前把下一个文件准备好。 + * 对于一个文件的准备工作比较耗时,比如图片压缩,md5序列化。 + * 如果能提前在当前文件传输期处理,可以节省总体耗时。 + */ + prepareNextFile: false, + + /** + * @property {Boolean} [chunked=false] + * @namespace options + * @for Uploader + * @description 是否要分片处理大文件上传。 + */ + chunked: false, + + /** + * @property {Boolean} [chunkSize=5242880] + * @namespace options + * @for Uploader + * @description 如果要分片,分多大一片? 默认大小为5M. + */ + chunkSize: 5 * 1024 * 1024, + + /** + * @property {Boolean} [chunkRetry=2] + * @namespace options + * @for Uploader + * @description 如果某个分片由于网络问题出错,允许自动重传多少次? + */ + chunkRetry: 2, + + /** + * @property {Boolean} [threads=3] + * @namespace options + * @for Uploader + * @description 上传并发数。允许同时最大上传进程数。 + */ + threads: 3, + + + /** + * @property {Object} [formData={}] + * @namespace options + * @for Uploader + * @description 文件上传请求的参数表,每次发送都会发送此对象中的参数。 + */ + formData: {} + + /** + * @property {Object} [fileVal='file'] + * @namespace options + * @for Uploader + * @description 设置文件上传域的name。 + */ + + /** + * @property {Object} [method='POST'] + * @namespace options + * @for Uploader + * @description 文件上传方式,`POST`或者`GET`。 + */ + + /** + * @property {Object} [sendAsBinary=false] + * @namespace options + * @for Uploader + * @description 是否已二进制的流的方式发送文件,这样整个上传内容`php://input`都为文件内容, + * 其他参数在$_GET数组中。 + */ + }); + + // 负责将文件切片。 + function CuteFile( file, chunkSize ) { + var pending = [], + blob = file.source, + total = blob.size, + chunks = chunkSize ? Math.ceil( total / chunkSize ) : 1, + start = 0, + index = 0, + len, api; + + api = { + file: file, + + has: function() { + return !!pending.length; + }, + + shift: function() { + return pending.shift(); + }, + + unshift: function( block ) { + pending.unshift( block ); + } + }; + + while ( index < chunks ) { + len = Math.min( chunkSize, total - start ); + + pending.push({ + file: file, + start: start, + end: chunkSize ? (start + len) : total, + total: total, + chunks: chunks, + chunk: index++, + cuted: api + }); + start += len; + } + + file.blocks = pending.concat(); + file.remaning = pending.length; + + return api; + } + + Uploader.register({ + name: 'upload', + + init: function() { + var owner = this.owner, + me = this; + + this.runing = false; + this.progress = false; + + owner + .on( 'startUpload', function() { + me.progress = true; + }) + .on( 'uploadFinished', function() { + me.progress = false; + }); + + // 记录当前正在传的数据,跟threads相关 + this.pool = []; + + // 缓存分好片的文件。 + this.stack = []; + + // 缓存即将上传的文件。 + this.pending = []; + + // 跟踪还有多少分片在上传中但是没有完成上传。 + this.remaning = 0; + this.__tick = Base.bindFn( this._tick, this ); + + owner.on( 'uploadComplete', function( file ) { + + // 把其他块取消了。 + file.blocks && $.each( file.blocks, function( _, v ) { + v.transport && (v.transport.abort(), v.transport.destroy()); + delete v.transport; + }); + + delete file.blocks; + delete file.remaning; + }); + }, + + reset: function() { + this.request( 'stop-upload', true ); + this.runing = false; + this.pool = []; + this.stack = []; + this.pending = []; + this.remaning = 0; + this._trigged = false; + this._promise = null; + }, + + /** + * @event startUpload + * @description 当开始上传流程时触发。 + * @for Uploader + */ + + /** + * 开始上传。此方法可以从初始状态调用开始上传流程,也可以从暂停状态调用,继续上传流程。 + * + * 可以指定开始某一个文件。 + * @grammar upload() => undefined + * @grammar upload( file | fileId) => undefined + * @method upload + * @for Uploader + */ + startUpload: function(file) { + var me = this; + + // 移出invalid的文件 + $.each( me.request( 'get-files', Status.INVALID ), function() { + me.request( 'remove-file', this ); + }); + + // 如果指定了开始某个文件,则只开始指定文件。 + if ( file ) { + file = file.id ? file : me.request( 'get-file', file ); + + if (file.getStatus() === Status.INTERRUPT) { + $.each( me.pool, function( _, v ) { + + // 之前暂停过。 + if (v.file !== file) { + return; + } + + v.transport && v.transport.send(); + }); + + file.setStatus( Status.QUEUED ); + } else if (file.getStatus() === Status.PROGRESS) { + return; + } else { + file.setStatus( Status.QUEUED ); + } + } else { + $.each( me.request( 'get-files', [ Status.INITED ] ), function() { + this.setStatus( Status.QUEUED ); + }); + } + + if ( me.runing ) { + return; + } + + me.runing = true; + + var files = []; + + // 如果有暂停的,则续传 + $.each( me.pool, function( _, v ) { + var file = v.file; + + if ( file.getStatus() === Status.INTERRUPT ) { + files.push(file); + me._trigged = false; + v.transport && v.transport.send(); + } + }); + + var file; + while ( (file = files.shift()) ) { + file.setStatus( Status.PROGRESS ); + } + + file || $.each( me.request( 'get-files', + Status.INTERRUPT ), function() { + this.setStatus( Status.PROGRESS ); + }); + + me._trigged = false; + Base.nextTick( me.__tick ); + me.owner.trigger('startUpload'); + }, + + /** + * @event stopUpload + * @description 当开始上传流程暂停时触发。 + * @for Uploader + */ + + /** + * 暂停上传。第一个参数为是否中断上传当前正在上传的文件。 + * + * 如果第一个参数是文件,则只暂停指定文件。 + * @grammar stop() => undefined + * @grammar stop( true ) => undefined + * @grammar stop( file ) => undefined + * @method stop + * @for Uploader + */ + stopUpload: function( file, interrupt ) { + var me = this; + + if (file === true) { + interrupt = file; + file = null; + } + + if ( me.runing === false ) { + return; + } + + // 如果只是暂停某个文件。 + if ( file ) { + file = file.id ? file : me.request( 'get-file', file ); + + if ( file.getStatus() !== Status.PROGRESS && + file.getStatus() !== Status.QUEUED ) { + return; + } + + file.setStatus( Status.INTERRUPT ); + $.each( me.pool, function( _, v ) { + + // 只 abort 指定的文件。 + if (v.file !== file) { + return; + } + + v.transport && v.transport.abort(); + me._putback(v); + me._popBlock(v); + }); + + return Base.nextTick( me.__tick ); + } + + me.runing = false; + + if (this._promise && this._promise.file) { + this._promise.file.setStatus( Status.INTERRUPT ); + } + + interrupt && $.each( me.pool, function( _, v ) { + v.transport && v.transport.abort(); + v.file.setStatus( Status.INTERRUPT ); + }); + + me.owner.trigger('stopUpload'); + }, + + /** + * @method cancelFile + * @grammar cancelFile( file ) => undefined + * @grammar cancelFile( id ) => undefined + * @param {File|id} file File对象或这File对象的id + * @description 标记文件状态为已取消, 同时将中断文件传输。 + * @for Uploader + * @example + * + * $li.on('click', '.remove-this', function() { + * uploader.cancelFile( file ); + * }) + */ + cancelFile: function( file ) { + file = file.id ? file : this.request( 'get-file', file ); + + // 如果正在上传。 + file.blocks && $.each( file.blocks, function( _, v ) { + var _tr = v.transport; + + if ( _tr ) { + _tr.abort(); + _tr.destroy(); + delete v.transport; + } + }); + + file.setStatus( Status.CANCELLED ); + this.owner.trigger( 'fileDequeued', file ); + }, + + /** + * 判断`Uplaode`r是否正在上传中。 + * @grammar isInProgress() => Boolean + * @method isInProgress + * @for Uploader + */ + isInProgress: function() { + return !!this.progress; + }, + + _getStats: function() { + return this.request('get-stats'); + }, + + /** + * 掉过一个文件上传,直接标记指定文件为已上传状态。 + * @grammar skipFile( file ) => undefined + * @method skipFile + * @for Uploader + */ + skipFile: function( file, status ) { + file = file.id ? file : this.request( 'get-file', file ); + + file.setStatus( status || Status.COMPLETE ); + file.skipped = true; + + // 如果正在上传。 + file.blocks && $.each( file.blocks, function( _, v ) { + var _tr = v.transport; + + if ( _tr ) { + _tr.abort(); + _tr.destroy(); + delete v.transport; + } + }); + + this.owner.trigger( 'uploadSkip', file ); + }, + + /** + * @event uploadFinished + * @description 当所有文件上传结束时触发。 + * @for Uploader + */ + _tick: function() { + var me = this, + opts = me.options, + fn, val; + + // 上一个promise还没有结束,则等待完成后再执行。 + if ( me._promise ) { + return me._promise.always( me.__tick ); + } + + // 还有位置,且还有文件要处理的话。 + if ( me.pool.length < opts.threads && (val = me._nextBlock()) ) { + me._trigged = false; + + fn = function( val ) { + me._promise = null; + + // 有可能是reject过来的,所以要检测val的类型。 + val && val.file && me._startSend( val ); + Base.nextTick( me.__tick ); + }; + + me._promise = isPromise( val ) ? val.always( fn ) : fn( val ); + + // 没有要上传的了,且没有正在传输的了。 + } else if ( !me.remaning && !me._getStats().numOfQueue && + !me._getStats().numofInterrupt ) { + me.runing = false; + + me._trigged || Base.nextTick(function() { + me.owner.trigger('uploadFinished'); + }); + me._trigged = true; + } + }, + + _putback: function(block) { + var idx; + + block.cuted.unshift(block); + idx = this.stack.indexOf(block.cuted); + + if (!~idx) { + this.stack.unshift(block.cuted); + } + }, + + _getStack: function() { + var i = 0, + act; + + while ( (act = this.stack[ i++ ]) ) { + if ( act.has() && act.file.getStatus() === Status.PROGRESS ) { + return act; + } else if (!act.has() || + act.file.getStatus() !== Status.PROGRESS && + act.file.getStatus() !== Status.INTERRUPT ) { + + // 把已经处理完了的,或者,状态为非 progress(上传中)、 + // interupt(暂停中) 的移除。 + this.stack.splice( --i, 1 ); + } + } + + return null; + }, + + _nextBlock: function() { + var me = this, + opts = me.options, + act, next, done, preparing; + + // 如果当前文件还有没有需要传输的,则直接返回剩下的。 + if ( (act = this._getStack()) ) { + + // 是否提前准备下一个文件 + if ( opts.prepareNextFile && !me.pending.length ) { + me._prepareNextFile(); + } + + return act.shift(); + + // 否则,如果正在运行,则准备下一个文件,并等待完成后返回下个分片。 + } else if ( me.runing ) { + + // 如果缓存中有,则直接在缓存中取,没有则去queue中取。 + if ( !me.pending.length && me._getStats().numOfQueue ) { + me._prepareNextFile(); + } + + next = me.pending.shift(); + done = function( file ) { + if ( !file ) { + return null; + } + + act = CuteFile( file, opts.chunked ? opts.chunkSize : 0 ); + me.stack.push(act); + return act.shift(); + }; + + // 文件可能还在prepare中,也有可能已经完全准备好了。 + if ( isPromise( next) ) { + preparing = next.file; + next = next[ next.pipe ? 'pipe' : 'then' ]( done ); + next.file = preparing; + return next; + } + + return done( next ); + } + }, + + + /** + * @event uploadStart + * @param {File} file File对象 + * @description 某个文件开始上传前触发,一个文件只会触发一次。 + * @for Uploader + */ + _prepareNextFile: function() { + var me = this, + file = me.request('fetch-file'), + pending = me.pending, + promise; + + if ( file ) { + promise = me.request( 'before-send-file', file, function() { + + // 有可能文件被skip掉了。文件被skip掉后,状态坑定不是Queued. + if ( file.getStatus() === Status.PROGRESS || + file.getStatus() === Status.INTERRUPT ) { + return file; + } + + return me._finishFile( file ); + }); + + me.owner.trigger( 'uploadStart', file ); + file.setStatus( Status.PROGRESS ); + + promise.file = file; + + // 如果还在pending中,则替换成文件本身。 + promise.done(function() { + var idx = $.inArray( promise, pending ); + + ~idx && pending.splice( idx, 1, file ); + }); + + // befeore-send-file的钩子就有错误发生。 + promise.fail(function( reason ) { + file.setStatus( Status.ERROR, reason ); + me.owner.trigger( 'uploadError', file, reason ); + me.owner.trigger( 'uploadComplete', file ); + }); + + pending.push( promise ); + } + }, + + // 让出位置了,可以让其他分片开始上传 + _popBlock: function( block ) { + var idx = $.inArray( block, this.pool ); + + this.pool.splice( idx, 1 ); + block.file.remaning--; + this.remaning--; + }, + + // 开始上传,可以被掉过。如果promise被reject了,则表示跳过此分片。 + _startSend: function( block ) { + var me = this, + file = block.file, + promise; + + // 有可能在 before-send-file 的 promise 期间改变了文件状态。 + // 如:暂停,取消 + // 我们不能中断 promise, 但是可以在 promise 完后,不做上传操作。 + if ( file.getStatus() !== Status.PROGRESS ) { + + // 如果是中断,则还需要放回去。 + if (file.getStatus() === Status.INTERRUPT) { + me._putback(block); + } + + return; + } + + me.pool.push( block ); + me.remaning++; + + // 如果没有分片,则直接使用原始的。 + // 不会丢失content-type信息。 + block.blob = block.chunks === 1 ? file.source : + file.source.slice( block.start, block.end ); + + // hook, 每个分片发送之前可能要做些异步的事情。 + promise = me.request( 'before-send', block, function() { + + // 有可能文件已经上传出错了,所以不需要再传输了。 + if ( file.getStatus() === Status.PROGRESS ) { + me._doSend( block ); + } else { + me._popBlock( block ); + Base.nextTick( me.__tick ); + } + }); + + // 如果为fail了,则跳过此分片。 + promise.fail(function() { + if ( file.remaning === 1 ) { + me._finishFile( file ).always(function() { + block.percentage = 1; + me._popBlock( block ); + me.owner.trigger( 'uploadComplete', file ); + Base.nextTick( me.__tick ); + }); + } else { + block.percentage = 1; + me.updateFileProgress( file ); + me._popBlock( block ); + Base.nextTick( me.__tick ); + } + }); + }, + + + /** + * @event uploadBeforeSend + * @param {Object} object + * @param {Object} data 默认的上传参数,可以扩展此对象来控制上传参数。 + * @param {Object} headers 可以扩展此对象来控制上传头部。 + * @description 当某个文件的分块在发送前触发,主要用来询问是否要添加附带参数,大文件在开起分片上传的前提下此事件可能会触发多次。 + * @for Uploader + */ + + /** + * @event uploadAccept + * @param {Object} object + * @param {Object} ret 服务端的返回数据,json格式,如果服务端不是json格式,从ret._raw中取数据,自行解析。 + * @description 当某个文件上传到服务端响应后,会派送此事件来询问服务端响应是否有效。如果此事件handler返回值为`false`, 则此文件将派送`server`类型的`uploadError`事件。 + * @for Uploader + */ + + /** + * @event uploadProgress + * @param {File} file File对象 + * @param {Number} percentage 上传进度 + * @description 上传过程中触发,携带上传进度。 + * @for Uploader + */ + + + /** + * @event uploadError + * @param {File} file File对象 + * @param {String} reason 出错的code + * @description 当文件上传出错时触发。 + * @for Uploader + */ + + /** + * @event uploadSuccess + * @param {File} file File对象 + * @param {Object} response 服务端返回的数据 + * @description 当文件上传成功时触发。 + * @for Uploader + */ + + /** + * @event uploadComplete + * @param {File} [file] File对象 + * @description 不管成功或者失败,文件上传完成时触发。 + * @for Uploader + */ + + // 做上传操作。 + _doSend: function( block ) { + var me = this, + owner = me.owner, + opts = me.options, + file = block.file, + tr = new Transport( opts ), + data = $.extend({}, opts.formData ), + headers = $.extend({}, opts.headers ), + requestAccept, ret; + + block.transport = tr; + + tr.on( 'destroy', function() { + delete block.transport; + me._popBlock( block ); + Base.nextTick( me.__tick ); + }); + + // 广播上传进度。以文件为单位。 + tr.on( 'progress', function( percentage ) { + block.percentage = percentage; + me.updateFileProgress( file ); + }); + + // 用来询问,是否返回的结果是有错误的。 + requestAccept = function( reject ) { + var fn; + + ret = tr.getResponseAsJson() || {}; + ret._raw = tr.getResponse(); + fn = function( value ) { + reject = value; + }; + + // 服务端响应了,不代表成功了,询问是否响应正确。 + if ( !owner.trigger( 'uploadAccept', block, ret, fn ) ) { + reject = reject || 'server'; + } + + return reject; + }; + + // 尝试重试,然后广播文件上传出错。 + tr.on( 'error', function( type, flag ) { + block.retried = block.retried || 0; + + // 自动重试 + if ( block.chunks > 1 && ~'http,abort'.indexOf( type ) && + block.retried < opts.chunkRetry ) { + + block.retried++; + tr.send(); + + } else { + + // http status 500 ~ 600 + if ( !flag && type === 'server' ) { + type = requestAccept( type ); + } + + file.setStatus( Status.ERROR, type ); + owner.trigger( 'uploadError', file, type ); + owner.trigger( 'uploadComplete', file ); + } + }); + + // 上传成功 + tr.on( 'load', function() { + var reason; + + // 如果非预期,转向上传出错。 + if ( (reason = requestAccept()) ) { + tr.trigger( 'error', reason, true ); + return; + } + + // 全部上传完成。 + if ( file.remaning === 1 ) { + me._finishFile( file, ret ); + } else { + tr.destroy(); + } + }); + + // 配置默认的上传字段。 + data = $.extend( data, { + id: file.id, + name: file.name, + type: file.type, + lastModifiedDate: file.lastModifiedDate, + size: file.size + }); + + block.chunks > 1 && $.extend( data, { + chunks: block.chunks, + chunk: block.chunk + }); + + // 在发送之间可以添加字段什么的。。。 + // 如果默认的字段不够使用,可以通过监听此事件来扩展 + owner.trigger( 'uploadBeforeSend', block, data, headers ); + + // 开始发送。 + tr.appendBlob( opts.fileVal, block.blob, file.name ); + tr.append( data ); + tr.setRequestHeader( headers ); + tr.send(); + }, + + // 完成上传。 + _finishFile: function( file, ret, hds ) { + var owner = this.owner; + + return owner + .request( 'after-send-file', arguments, function() { + file.setStatus( Status.COMPLETE ); + owner.trigger( 'uploadSuccess', file, ret, hds ); + }) + .fail(function( reason ) { + + // 如果外部已经标记为invalid什么的,不再改状态。 + if ( file.getStatus() === Status.PROGRESS ) { + file.setStatus( Status.ERROR, reason ); + } + + owner.trigger( 'uploadError', file, reason ); + }) + .always(function() { + owner.trigger( 'uploadComplete', file ); + }); + }, + + updateFileProgress: function(file) { + var totalPercent = 0, + uploaded = 0; + + if (!file.blocks) { + return; + } + + $.each( file.blocks, function( _, v ) { + uploaded += (v.percentage || 0) * (v.end - v.start); + }); + + totalPercent = uploaded / file.size; + this.owner.trigger( 'uploadProgress', file, totalPercent || 0 ); + } + + }); + }); + /** + * @fileOverview 各种验证,包括文件总大小是否超出、单文件是否超出和文件是否重复。 + */ + + define('widgets/validator',[ + 'base', + 'uploader', + 'file', + 'widgets/widget' + ], function( Base, Uploader, WUFile ) { + + var $ = Base.$, + validators = {}, + api; + + /** + * @event error + * @param {String} type 错误类型。 + * @description 当validate不通过时,会以派送错误事件的形式通知调用者。通过`upload.on('error', handler)`可以捕获到此类错误,目前有以下错误会在特定的情况下派送错来。 + * + * * `Q_EXCEED_NUM_LIMIT` 在设置了`fileNumLimit`且尝试给`uploader`添加的文件数量超出这个值时派送。 + * * `Q_EXCEED_SIZE_LIMIT` 在设置了`Q_EXCEED_SIZE_LIMIT`且尝试给`uploader`添加的文件总大小超出这个值时派送。 + * * `Q_TYPE_DENIED` 当文件类型不满足时触发。。 + * @for Uploader + */ + + // 暴露给外面的api + api = { + + // 添加验证器 + addValidator: function( type, cb ) { + validators[ type ] = cb; + }, + + // 移除验证器 + removeValidator: function( type ) { + delete validators[ type ]; + } + }; + + // 在Uploader初始化的时候启动Validators的初始化 + Uploader.register({ + name: 'validator', + + init: function() { + var me = this; + Base.nextTick(function() { + $.each( validators, function() { + this.call( me.owner ); + }); + }); + } + }); + + /** + * @property {int} [fileNumLimit=undefined] + * @namespace options + * @for Uploader + * @description 验证文件总数量, 超出则不允许加入队列。 + */ + api.addValidator( 'fileNumLimit', function() { + var uploader = this, + opts = uploader.options, + count = 0, + max = parseInt( opts.fileNumLimit, 10 ), + flag = true; + + if ( !max ) { + return; + } + + uploader.on( 'beforeFileQueued', function( file ) { + + if ( count >= max && flag ) { + flag = false; + this.trigger( 'error', 'Q_EXCEED_NUM_LIMIT', max, file ); + setTimeout(function() { + flag = true; + }, 1 ); + } + + return count >= max ? false : true; + }); + + uploader.on( 'fileQueued', function() { + count++; + }); + + uploader.on( 'fileDequeued', function() { + count--; + }); + + uploader.on( 'reset', function() { + count = 0; + }); + }); + + + /** + * @property {int} [fileSizeLimit=undefined] + * @namespace options + * @for Uploader + * @description 验证文件总大小是否超出限制, 超出则不允许加入队列。 + */ + api.addValidator( 'fileSizeLimit', function() { + var uploader = this, + opts = uploader.options, + count = 0, + max = parseInt( opts.fileSizeLimit, 10 ), + flag = true; + + if ( !max ) { + return; + } + + uploader.on( 'beforeFileQueued', function( file ) { + var invalid = count + file.size > max; + + if ( invalid && flag ) { + flag = false; + this.trigger( 'error', 'Q_EXCEED_SIZE_LIMIT', max, file ); + setTimeout(function() { + flag = true; + }, 1 ); + } + + return invalid ? false : true; + }); + + uploader.on( 'fileQueued', function( file ) { + count += file.size; + }); + + uploader.on( 'fileDequeued', function( file ) { + count -= file.size; + }); + + uploader.on( 'reset', function() { + count = 0; + }); + }); + + /** + * @property {int} [fileSingleSizeLimit=undefined] + * @namespace options + * @for Uploader + * @description 验证单个文件大小是否超出限制, 超出则不允许加入队列。 + */ + api.addValidator( 'fileSingleSizeLimit', function() { + var uploader = this, + opts = uploader.options, + max = opts.fileSingleSizeLimit; + + if ( !max ) { + return; + } + + uploader.on( 'beforeFileQueued', function( file ) { + + if ( file.size > max ) { + file.setStatus( WUFile.Status.INVALID, 'exceed_size' ); + this.trigger( 'error', 'F_EXCEED_SIZE', max, file ); + return false; + } + + }); + + }); + + /** + * @property {Boolean} [duplicate=undefined] + * @namespace options + * @for Uploader + * @description 去重, 根据文件名字、文件大小和最后修改时间来生成hash Key. + */ + api.addValidator( 'duplicate', function() { + var uploader = this, + opts = uploader.options, + mapping = {}; + + if ( opts.duplicate ) { + return; + } + + function hashString( str ) { + var hash = 0, + i = 0, + len = str.length, + _char; + + for ( ; i < len; i++ ) { + _char = str.charCodeAt( i ); + hash = _char + (hash << 6) + (hash << 16) - hash; + } + + return hash; + } + + uploader.on( 'beforeFileQueued', function( file ) { + var hash = file.__hash || (file.__hash = hashString( file.name + + file.size + file.lastModifiedDate )); + + // 已经重复了 + if ( mapping[ hash ] ) { + this.trigger( 'error', 'F_DUPLICATE', file ); + return false; + } + }); + + uploader.on( 'fileQueued', function( file ) { + var hash = file.__hash; + + hash && (mapping[ hash ] = true); + }); + + uploader.on( 'fileDequeued', function( file ) { + var hash = file.__hash; + + hash && (delete mapping[ hash ]); + }); + + uploader.on( 'reset', function() { + mapping = {}; + }); + }); + + return api; + }); + + /** + * @fileOverview Md5 + */ + define('lib/md5',[ + 'runtime/client', + 'mediator' + ], function( RuntimeClient, Mediator ) { + + function Md5() { + RuntimeClient.call( this, 'Md5' ); + } + + // 让 Md5 具备事件功能。 + Mediator.installTo( Md5.prototype ); + + Md5.prototype.loadFromBlob = function( blob ) { + var me = this; + + if ( me.getRuid() ) { + me.disconnectRuntime(); + } + + // 连接到blob归属的同一个runtime. + me.connectRuntime( blob.ruid, function() { + me.exec('init'); + me.exec( 'loadFromBlob', blob ); + }); + }; + + Md5.prototype.getResult = function() { + return this.exec('getResult'); + }; + + return Md5; + }); + /** + * @fileOverview 图片操作, 负责预览图片和上传前压缩图片 + */ + define('widgets/md5',[ + 'base', + 'uploader', + 'lib/md5', + 'lib/blob', + 'widgets/widget' + ], function( Base, Uploader, Md5, Blob ) { + + return Uploader.register({ + name: 'md5', + + + /** + * 计算文件 md5 值,返回一个 promise 对象,可以监听 progress 进度。 + * + * + * @method md5File + * @grammar md5File( file[, start[, end]] ) => promise + * @for Uploader + * @example + * + * uploader.on( 'fileQueued', function( file ) { + * var $li = ...; + * + * uploader.md5File( file ) + * + * // 及时显示进度 + * .progress(function(percentage) { + * console.log('Percentage:', percentage); + * }) + * + * // 完成 + * .then(function(val) { + * console.log('md5 result:', val); + * }); + * + * }); + */ + md5File: function( file, start, end ) { + var md5 = new Md5(), + deferred = Base.Deferred(), + blob = (file instanceof Blob) ? file : + this.request( 'get-file', file ).source; + + md5.on( 'progress load', function( e ) { + e = e || {}; + deferred.notify( e.total ? e.loaded / e.total : 1 ); + }); + + md5.on( 'complete', function() { + deferred.resolve( md5.getResult() ); + }); + + md5.on( 'error', function( reason ) { + deferred.reject( reason ); + }); + + if ( arguments.length > 1 ) { + start = start || 0; + end = end || 0; + start < 0 && (start = blob.size + start); + end < 0 && (end = blob.size + end); + end = Math.min( end, blob.size ); + blob = blob.slice( start, end ); + } + + md5.loadFromBlob( blob ); + + return deferred.promise(); + } + }); + }); + /** + * @fileOverview Runtime管理器,负责Runtime的选择, 连接 + */ + define('runtime/compbase',[],function() { + + function CompBase( owner, runtime ) { + + this.owner = owner; + this.options = owner.options; + + this.getRuntime = function() { + return runtime; + }; + + this.getRuid = function() { + return runtime.uid; + }; + + this.trigger = function() { + return owner.trigger.apply( owner, arguments ); + }; + } + + return CompBase; + }); + /** + * @fileOverview Html5Runtime + */ + define('runtime/html5/runtime',[ + 'base', + 'runtime/runtime', + 'runtime/compbase' + ], function( Base, Runtime, CompBase ) { + + var type = 'html5', + components = {}; + + function Html5Runtime() { + var pool = {}, + me = this, + destroy = this.destroy; + + Runtime.apply( me, arguments ); + me.type = type; + + + // 这个方法的调用者,实际上是RuntimeClient + me.exec = function( comp, fn/*, args...*/) { + var client = this, + uid = client.uid, + args = Base.slice( arguments, 2 ), + instance; + + if ( components[ comp ] ) { + instance = pool[ uid ] = pool[ uid ] || + new components[ comp ]( client, me ); + + if ( instance[ fn ] ) { + return instance[ fn ].apply( instance, args ); + } + } + }; + + me.destroy = function() { + // @todo 删除池子中的所有实例 + return destroy && destroy.apply( this, arguments ); + }; + } + + Base.inherits( Runtime, { + constructor: Html5Runtime, + + // 不需要连接其他程序,直接执行callback + init: function() { + var me = this; + setTimeout(function() { + me.trigger('ready'); + }, 1 ); + } + + }); + + // 注册Components + Html5Runtime.register = function( name, component ) { + var klass = components[ name ] = Base.inherits( CompBase, component ); + return klass; + }; + + // 注册html5运行时。 + // 只有在支持的前提下注册。 + if ( window.Blob && window.FileReader && window.DataView ) { + Runtime.addRuntime( type, Html5Runtime ); + } + + return Html5Runtime; + }); + /** + * @fileOverview Blob Html实现 + */ + define('runtime/html5/blob',[ + 'runtime/html5/runtime', + 'lib/blob' + ], function( Html5Runtime, Blob ) { + + return Html5Runtime.register( 'Blob', { + slice: function( start, end ) { + var blob = this.owner.source, + slice = blob.slice || blob.webkitSlice || blob.mozSlice; + + blob = slice.call( blob, start, end ); + + return new Blob( this.getRuid(), blob ); + } + }); + }); + /** + * @fileOverview FilePaste + */ + define('runtime/html5/dnd',[ + 'base', + 'runtime/html5/runtime', + 'lib/file' + ], function( Base, Html5Runtime, File ) { + + var $ = Base.$, + prefix = 'webuploader-dnd-'; + + return Html5Runtime.register( 'DragAndDrop', { + init: function() { + var elem = this.elem = this.options.container; + + this.dragEnterHandler = Base.bindFn( this._dragEnterHandler, this ); + this.dragOverHandler = Base.bindFn( this._dragOverHandler, this ); + this.dragLeaveHandler = Base.bindFn( this._dragLeaveHandler, this ); + this.dropHandler = Base.bindFn( this._dropHandler, this ); + this.dndOver = false; + + elem.on( 'dragenter', this.dragEnterHandler ); + elem.on( 'dragover', this.dragOverHandler ); + elem.on( 'dragleave', this.dragLeaveHandler ); + elem.on( 'drop', this.dropHandler ); + + if ( this.options.disableGlobalDnd ) { + $( document ).on( 'dragover', this.dragOverHandler ); + $( document ).on( 'drop', this.dropHandler ); + } + }, + + _dragEnterHandler: function( e ) { + var me = this, + denied = me._denied || false, + items; + + e = e.originalEvent || e; + + if ( !me.dndOver ) { + me.dndOver = true; + + // 注意只有 chrome 支持。 + items = e.dataTransfer.items; + + if ( items && items.length ) { + me._denied = denied = !me.trigger( 'accept', items ); + } + + me.elem.addClass( prefix + 'over' ); + me.elem[ denied ? 'addClass' : + 'removeClass' ]( prefix + 'denied' ); + } + + e.dataTransfer.dropEffect = denied ? 'none' : 'copy'; + + return false; + }, + + _dragOverHandler: function( e ) { + // 只处理框内的。 + var parentElem = this.elem.parent().get( 0 ); + if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) { + return false; + } + + clearTimeout( this._leaveTimer ); + this._dragEnterHandler.call( this, e ); + + return false; + }, + + _dragLeaveHandler: function() { + var me = this, + handler; + + handler = function() { + me.dndOver = false; + me.elem.removeClass( prefix + 'over ' + prefix + 'denied' ); + }; + + clearTimeout( me._leaveTimer ); + me._leaveTimer = setTimeout( handler, 100 ); + return false; + }, + + _dropHandler: function( e ) { + var me = this, + ruid = me.getRuid(), + parentElem = me.elem.parent().get( 0 ), + dataTransfer, data; + + // 只处理框内的。 + if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) { + return false; + } + + e = e.originalEvent || e; + dataTransfer = e.dataTransfer; + + // 如果是页面内拖拽,还不能处理,不阻止事件。 + // 此处 ie11 下会报参数错误, + try { + data = dataTransfer.getData('text/html'); + } catch( err ) { + } + + if ( data ) { + return; + } + + me._getTansferFiles( dataTransfer, function( results ) { + me.trigger( 'drop', $.map( results, function( file ) { + return new File( ruid, file ); + }) ); + }); + + me.dndOver = false; + me.elem.removeClass( prefix + 'over' ); + return false; + }, + + // 如果传入 callback 则去查看文件夹,否则只管当前文件夹。 + _getTansferFiles: function( dataTransfer, callback ) { + var results = [], + promises = [], + items, files, file, item, i, len, canAccessFolder; + + items = dataTransfer.items; + files = dataTransfer.files; + + canAccessFolder = !!(items && items[ 0 ].webkitGetAsEntry); + + for ( i = 0, len = files.length; i < len; i++ ) { + file = files[ i ]; + item = items && items[ i ]; + + if ( canAccessFolder && item.webkitGetAsEntry().isDirectory ) { + + promises.push( this._traverseDirectoryTree( + item.webkitGetAsEntry(), results ) ); + } else { + results.push( file ); + } + } + + Base.when.apply( Base, promises ).done(function() { + + if ( !results.length ) { + return; + } + + callback( results ); + }); + }, + + _traverseDirectoryTree: function( entry, results ) { + var deferred = Base.Deferred(), + me = this; + + if ( entry.isFile ) { + entry.file(function( file ) { + results.push( file ); + deferred.resolve(); + }); + } else if ( entry.isDirectory ) { + entry.createReader().readEntries(function( entries ) { + var len = entries.length, + promises = [], + arr = [], // 为了保证顺序。 + i; + + for ( i = 0; i < len; i++ ) { + promises.push( me._traverseDirectoryTree( + entries[ i ], arr ) ); + } + + Base.when.apply( Base, promises ).then(function() { + results.push.apply( results, arr ); + deferred.resolve(); + }, deferred.reject ); + }); + } + + return deferred.promise(); + }, + + destroy: function() { + var elem = this.elem; + + // 还没 init 就调用 destroy + if (!elem) { + return; + } + + elem.off( 'dragenter', this.dragEnterHandler ); + elem.off( 'dragover', this.dragOverHandler ); + elem.off( 'dragleave', this.dragLeaveHandler ); + elem.off( 'drop', this.dropHandler ); + + if ( this.options.disableGlobalDnd ) { + $( document ).off( 'dragover', this.dragOverHandler ); + $( document ).off( 'drop', this.dropHandler ); + } + } + }); + }); + + /** + * @fileOverview FilePaste + */ + define('runtime/html5/filepaste',[ + 'base', + 'runtime/html5/runtime', + 'lib/file' + ], function( Base, Html5Runtime, File ) { + + return Html5Runtime.register( 'FilePaste', { + init: function() { + var opts = this.options, + elem = this.elem = opts.container, + accept = '.*', + arr, i, len, item; + + // accetp的mimeTypes中生成匹配正则。 + if ( opts.accept ) { + arr = []; + + for ( i = 0, len = opts.accept.length; i < len; i++ ) { + item = opts.accept[ i ].mimeTypes; + item && arr.push( item ); + } + + if ( arr.length ) { + accept = arr.join(','); + accept = accept.replace( /,/g, '|' ).replace( /\*/g, '.*' ); + } + } + this.accept = accept = new RegExp( accept, 'i' ); + this.hander = Base.bindFn( this._pasteHander, this ); + elem.on( 'paste', this.hander ); + }, + + _pasteHander: function( e ) { + var allowed = [], + ruid = this.getRuid(), + items, item, blob, i, len; + + e = e.originalEvent || e; + items = e.clipboardData.items; + + for ( i = 0, len = items.length; i < len; i++ ) { + item = items[ i ]; + + if ( item.kind !== 'file' || !(blob = item.getAsFile()) ) { + continue; + } + + allowed.push( new File( ruid, blob ) ); + } + + if ( allowed.length ) { + // 不阻止非文件粘贴(文字粘贴)的事件冒泡 + e.preventDefault(); + e.stopPropagation(); + this.trigger( 'paste', allowed ); + } + }, + + destroy: function() { + this.elem.off( 'paste', this.hander ); + } + }); + }); + + /** + * @fileOverview FilePicker + */ + define('runtime/html5/filepicker',[ + 'base', + 'runtime/html5/runtime' + ], function( Base, Html5Runtime ) { + + var $ = Base.$; + + return Html5Runtime.register( 'FilePicker', { + init: function() { + var container = this.getRuntime().getContainer(), + me = this, + owner = me.owner, + opts = me.options, + label = this.label = $( document.createElement('label') ), + input = this.input = $( document.createElement('input') ), + arr, i, len, mouseHandler; + + input.attr( 'type', 'file' ); + input.attr( 'name', opts.name ); + input.addClass('webuploader-element-invisible'); + + label.on( 'click', function() { + input.trigger('click'); + }); + + label.css({ + opacity: 0, + width: '100%', + height: '100%', + display: 'block', + cursor: 'pointer', + background: '#ffffff' + }); + + if ( opts.multiple ) { + input.attr( 'multiple', 'multiple' ); + } + + // @todo Firefox不支持单独指定后缀 + if ( opts.accept && opts.accept.length > 0 ) { + arr = []; + + for ( i = 0, len = opts.accept.length; i < len; i++ ) { + arr.push( opts.accept[ i ].mimeTypes ); + } + + input.attr( 'accept', arr.join(',') ); + } + + container.append( input ); + container.append( label ); + + mouseHandler = function( e ) { + owner.trigger( e.type ); + }; + + input.on( 'change', function( e ) { + var fn = arguments.callee, + clone; + + me.files = e.target.files; + + // reset input + clone = this.cloneNode( true ); + clone.value = null; + this.parentNode.replaceChild( clone, this ); + + input.off(); + input = $( clone ).on( 'change', fn ) + .on( 'mouseenter mouseleave', mouseHandler ); + + owner.trigger('change'); + }); + + label.on( 'mouseenter mouseleave', mouseHandler ); + + }, + + + getFiles: function() { + return this.files; + }, + + destroy: function() { + this.input.off(); + this.label.off(); + } + }); + }); + /** + * Terms: + * + * Uint8Array, FileReader, BlobBuilder, atob, ArrayBuffer + * @fileOverview Image控件 + */ + define('runtime/html5/util',[ + 'base' + ], function( Base ) { + + var urlAPI = window.createObjectURL && window || + window.URL && URL.revokeObjectURL && URL || + window.webkitURL, + createObjectURL = Base.noop, + revokeObjectURL = createObjectURL; + + if ( urlAPI ) { + + // 更安全的方式调用,比如android里面就能把context改成其他的对象。 + createObjectURL = function() { + return urlAPI.createObjectURL.apply( urlAPI, arguments ); + }; + + revokeObjectURL = function() { + return urlAPI.revokeObjectURL.apply( urlAPI, arguments ); + }; + } + + return { + createObjectURL: createObjectURL, + revokeObjectURL: revokeObjectURL, + + dataURL2Blob: function( dataURI ) { + var byteStr, intArray, ab, i, mimetype, parts; + + parts = dataURI.split(','); + + if ( ~parts[ 0 ].indexOf('base64') ) { + byteStr = atob( parts[ 1 ] ); + } else { + byteStr = decodeURIComponent( parts[ 1 ] ); + } + + ab = new ArrayBuffer( byteStr.length ); + intArray = new Uint8Array( ab ); + + for ( i = 0; i < byteStr.length; i++ ) { + intArray[ i ] = byteStr.charCodeAt( i ); + } + + mimetype = parts[ 0 ].split(':')[ 1 ].split(';')[ 0 ]; + + return this.arrayBufferToBlob( ab, mimetype ); + }, + + dataURL2ArrayBuffer: function( dataURI ) { + var byteStr, intArray, i, parts; + + parts = dataURI.split(','); + + if ( ~parts[ 0 ].indexOf('base64') ) { + byteStr = atob( parts[ 1 ] ); + } else { + byteStr = decodeURIComponent( parts[ 1 ] ); + } + + intArray = new Uint8Array( byteStr.length ); + + for ( i = 0; i < byteStr.length; i++ ) { + intArray[ i ] = byteStr.charCodeAt( i ); + } + + return intArray.buffer; + }, + + arrayBufferToBlob: function( buffer, type ) { + var builder = window.BlobBuilder || window.WebKitBlobBuilder, + bb; + + // android不支持直接new Blob, 只能借助blobbuilder. + if ( builder ) { + bb = new builder(); + bb.append( buffer ); + return bb.getBlob( type ); + } + + return new Blob([ buffer ], type ? { type: type } : {} ); + }, + + // 抽出来主要是为了解决android下面canvas.toDataUrl不支持jpeg. + // 你得到的结果是png. + canvasToDataUrl: function( canvas, type, quality ) { + return canvas.toDataURL( type, quality / 100 ); + }, + + // imagemeat会复写这个方法,如果用户选择加载那个文件了的话。 + parseMeta: function( blob, callback ) { + callback( false, {}); + }, + + // imagemeat会复写这个方法,如果用户选择加载那个文件了的话。 + updateImageHead: function( data ) { + return data; + } + }; + }); + /** + * Terms: + * + * Uint8Array, FileReader, BlobBuilder, atob, ArrayBuffer + * @fileOverview Image控件 + */ + define('runtime/html5/imagemeta',[ + 'runtime/html5/util' + ], function( Util ) { + + var api; + + api = { + parsers: { + 0xffe1: [] + }, + + maxMetaDataSize: 262144, + + parse: function( blob, cb ) { + var me = this, + fr = new FileReader(); + + fr.onload = function() { + cb( false, me._parse( this.result ) ); + fr = fr.onload = fr.onerror = null; + }; + + fr.onerror = function( e ) { + cb( e.message ); + fr = fr.onload = fr.onerror = null; + }; + + blob = blob.slice( 0, me.maxMetaDataSize ); + fr.readAsArrayBuffer( blob.getSource() ); + }, + + _parse: function( buffer, noParse ) { + if ( buffer.byteLength < 6 ) { + return; + } + + var dataview = new DataView( buffer ), + offset = 2, + maxOffset = dataview.byteLength - 4, + headLength = offset, + ret = {}, + markerBytes, markerLength, parsers, i; + + if ( dataview.getUint16( 0 ) === 0xffd8 ) { + + while ( offset < maxOffset ) { + markerBytes = dataview.getUint16( offset ); + + if ( markerBytes >= 0xffe0 && markerBytes <= 0xffef || + markerBytes === 0xfffe ) { + + markerLength = dataview.getUint16( offset + 2 ) + 2; + + if ( offset + markerLength > dataview.byteLength ) { + break; + } + + parsers = api.parsers[ markerBytes ]; + + if ( !noParse && parsers ) { + for ( i = 0; i < parsers.length; i += 1 ) { + parsers[ i ].call( api, dataview, offset, + markerLength, ret ); + } + } + + offset += markerLength; + headLength = offset; + } else { + break; + } + } + + if ( headLength > 6 ) { + if ( buffer.slice ) { + ret.imageHead = buffer.slice( 2, headLength ); + } else { + // Workaround for IE10, which does not yet + // support ArrayBuffer.slice: + ret.imageHead = new Uint8Array( buffer ) + .subarray( 2, headLength ); + } + } + } + + return ret; + }, + + updateImageHead: function( buffer, head ) { + var data = this._parse( buffer, true ), + buf1, buf2, bodyoffset; + + + bodyoffset = 2; + if ( data.imageHead ) { + bodyoffset = 2 + data.imageHead.byteLength; + } + + if ( buffer.slice ) { + buf2 = buffer.slice( bodyoffset ); + } else { + buf2 = new Uint8Array( buffer ).subarray( bodyoffset ); + } + + buf1 = new Uint8Array( head.byteLength + 2 + buf2.byteLength ); + + buf1[ 0 ] = 0xFF; + buf1[ 1 ] = 0xD8; + buf1.set( new Uint8Array( head ), 2 ); + buf1.set( new Uint8Array( buf2 ), head.byteLength + 2 ); + + return buf1.buffer; + } + }; + + Util.parseMeta = function() { + return api.parse.apply( api, arguments ); + }; + + Util.updateImageHead = function() { + return api.updateImageHead.apply( api, arguments ); + }; + + return api; + }); + /** + * 代码来自于:https://github.com/blueimp/JavaScript-Load-Image + * 暂时项目中只用了orientation. + * + * 去除了 Exif Sub IFD Pointer, GPS Info IFD Pointer, Exif Thumbnail. + * @fileOverview EXIF解析 + */ + + // Sample + // ==================================== + // Make : Apple + // Model : iPhone 4S + // Orientation : 1 + // XResolution : 72 [72/1] + // YResolution : 72 [72/1] + // ResolutionUnit : 2 + // Software : QuickTime 7.7.1 + // DateTime : 2013:09:01 22:53:55 + // ExifIFDPointer : 190 + // ExposureTime : 0.058823529411764705 [1/17] + // FNumber : 2.4 [12/5] + // ExposureProgram : Normal program + // ISOSpeedRatings : 800 + // ExifVersion : 0220 + // DateTimeOriginal : 2013:09:01 22:52:51 + // DateTimeDigitized : 2013:09:01 22:52:51 + // ComponentsConfiguration : YCbCr + // ShutterSpeedValue : 4.058893515764426 + // ApertureValue : 2.5260688216892597 [4845/1918] + // BrightnessValue : -0.3126686601998395 + // MeteringMode : Pattern + // Flash : Flash did not fire, compulsory flash mode + // FocalLength : 4.28 [107/25] + // SubjectArea : [4 values] + // FlashpixVersion : 0100 + // ColorSpace : 1 + // PixelXDimension : 2448 + // PixelYDimension : 3264 + // SensingMethod : One-chip color area sensor + // ExposureMode : 0 + // WhiteBalance : Auto white balance + // FocalLengthIn35mmFilm : 35 + // SceneCaptureType : Standard + define('runtime/html5/imagemeta/exif',[ + 'base', + 'runtime/html5/imagemeta' + ], function( Base, ImageMeta ) { + + var EXIF = {}; + + EXIF.ExifMap = function() { + return this; + }; + + EXIF.ExifMap.prototype.map = { + 'Orientation': 0x0112 + }; + + EXIF.ExifMap.prototype.get = function( id ) { + return this[ id ] || this[ this.map[ id ] ]; + }; + + EXIF.exifTagTypes = { + // byte, 8-bit unsigned int: + 1: { + getValue: function( dataView, dataOffset ) { + return dataView.getUint8( dataOffset ); + }, + size: 1 + }, + + // ascii, 8-bit byte: + 2: { + getValue: function( dataView, dataOffset ) { + return String.fromCharCode( dataView.getUint8( dataOffset ) ); + }, + size: 1, + ascii: true + }, + + // short, 16 bit int: + 3: { + getValue: function( dataView, dataOffset, littleEndian ) { + return dataView.getUint16( dataOffset, littleEndian ); + }, + size: 2 + }, + + // long, 32 bit int: + 4: { + getValue: function( dataView, dataOffset, littleEndian ) { + return dataView.getUint32( dataOffset, littleEndian ); + }, + size: 4 + }, + + // rational = two long values, + // first is numerator, second is denominator: + 5: { + getValue: function( dataView, dataOffset, littleEndian ) { + return dataView.getUint32( dataOffset, littleEndian ) / + dataView.getUint32( dataOffset + 4, littleEndian ); + }, + size: 8 + }, + + // slong, 32 bit signed int: + 9: { + getValue: function( dataView, dataOffset, littleEndian ) { + return dataView.getInt32( dataOffset, littleEndian ); + }, + size: 4 + }, + + // srational, two slongs, first is numerator, second is denominator: + 10: { + getValue: function( dataView, dataOffset, littleEndian ) { + return dataView.getInt32( dataOffset, littleEndian ) / + dataView.getInt32( dataOffset + 4, littleEndian ); + }, + size: 8 + } + }; + + // undefined, 8-bit byte, value depending on field: + EXIF.exifTagTypes[ 7 ] = EXIF.exifTagTypes[ 1 ]; + + EXIF.getExifValue = function( dataView, tiffOffset, offset, type, length, + littleEndian ) { + + var tagType = EXIF.exifTagTypes[ type ], + tagSize, dataOffset, values, i, str, c; + + if ( !tagType ) { + Base.log('Invalid Exif data: Invalid tag type.'); + return; + } + + tagSize = tagType.size * length; + + // Determine if the value is contained in the dataOffset bytes, + // or if the value at the dataOffset is a pointer to the actual data: + dataOffset = tagSize > 4 ? tiffOffset + dataView.getUint32( offset + 8, + littleEndian ) : (offset + 8); + + if ( dataOffset + tagSize > dataView.byteLength ) { + Base.log('Invalid Exif data: Invalid data offset.'); + return; + } + + if ( length === 1 ) { + return tagType.getValue( dataView, dataOffset, littleEndian ); + } + + values = []; + + for ( i = 0; i < length; i += 1 ) { + values[ i ] = tagType.getValue( dataView, + dataOffset + i * tagType.size, littleEndian ); + } + + if ( tagType.ascii ) { + str = ''; + + // Concatenate the chars: + for ( i = 0; i < values.length; i += 1 ) { + c = values[ i ]; + + // Ignore the terminating NULL byte(s): + if ( c === '\u0000' ) { + break; + } + str += c; + } + + return str; + } + return values; + }; + + EXIF.parseExifTag = function( dataView, tiffOffset, offset, littleEndian, + data ) { + + var tag = dataView.getUint16( offset, littleEndian ); + data.exif[ tag ] = EXIF.getExifValue( dataView, tiffOffset, offset, + dataView.getUint16( offset + 2, littleEndian ), // tag type + dataView.getUint32( offset + 4, littleEndian ), // tag length + littleEndian ); + }; + + EXIF.parseExifTags = function( dataView, tiffOffset, dirOffset, + littleEndian, data ) { + + var tagsNumber, dirEndOffset, i; + + if ( dirOffset + 6 > dataView.byteLength ) { + Base.log('Invalid Exif data: Invalid directory offset.'); + return; + } + + tagsNumber = dataView.getUint16( dirOffset, littleEndian ); + dirEndOffset = dirOffset + 2 + 12 * tagsNumber; + + if ( dirEndOffset + 4 > dataView.byteLength ) { + Base.log('Invalid Exif data: Invalid directory size.'); + return; + } + + for ( i = 0; i < tagsNumber; i += 1 ) { + this.parseExifTag( dataView, tiffOffset, + dirOffset + 2 + 12 * i, // tag offset + littleEndian, data ); + } + + // Return the offset to the next directory: + return dataView.getUint32( dirEndOffset, littleEndian ); + }; + + // EXIF.getExifThumbnail = function(dataView, offset, length) { + // var hexData, + // i, + // b; + // if (!length || offset + length > dataView.byteLength) { + // Base.log('Invalid Exif data: Invalid thumbnail data.'); + // return; + // } + // hexData = []; + // for (i = 0; i < length; i += 1) { + // b = dataView.getUint8(offset + i); + // hexData.push((b < 16 ? '0' : '') + b.toString(16)); + // } + // return 'data:image/jpeg,%' + hexData.join('%'); + // }; + + EXIF.parseExifData = function( dataView, offset, length, data ) { + + var tiffOffset = offset + 10, + littleEndian, dirOffset; + + // Check for the ASCII code for "Exif" (0x45786966): + if ( dataView.getUint32( offset + 4 ) !== 0x45786966 ) { + // No Exif data, might be XMP data instead + return; + } + if ( tiffOffset + 8 > dataView.byteLength ) { + Base.log('Invalid Exif data: Invalid segment size.'); + return; + } + + // Check for the two null bytes: + if ( dataView.getUint16( offset + 8 ) !== 0x0000 ) { + Base.log('Invalid Exif data: Missing byte alignment offset.'); + return; + } + + // Check the byte alignment: + switch ( dataView.getUint16( tiffOffset ) ) { + case 0x4949: + littleEndian = true; + break; + + case 0x4D4D: + littleEndian = false; + break; + + default: + Base.log('Invalid Exif data: Invalid byte alignment marker.'); + return; + } + + // Check for the TIFF tag marker (0x002A): + if ( dataView.getUint16( tiffOffset + 2, littleEndian ) !== 0x002A ) { + Base.log('Invalid Exif data: Missing TIFF marker.'); + return; + } + + // Retrieve the directory offset bytes, usually 0x00000008 or 8 decimal: + dirOffset = dataView.getUint32( tiffOffset + 4, littleEndian ); + // Create the exif object to store the tags: + data.exif = new EXIF.ExifMap(); + // Parse the tags of the main image directory and retrieve the + // offset to the next directory, usually the thumbnail directory: + dirOffset = EXIF.parseExifTags( dataView, tiffOffset, + tiffOffset + dirOffset, littleEndian, data ); + + // 尝试读取缩略图 + // if ( dirOffset ) { + // thumbnailData = {exif: {}}; + // dirOffset = EXIF.parseExifTags( + // dataView, + // tiffOffset, + // tiffOffset + dirOffset, + // littleEndian, + // thumbnailData + // ); + + // // Check for JPEG Thumbnail offset: + // if (thumbnailData.exif[0x0201]) { + // data.exif.Thumbnail = EXIF.getExifThumbnail( + // dataView, + // tiffOffset + thumbnailData.exif[0x0201], + // thumbnailData.exif[0x0202] // Thumbnail data length + // ); + // } + // } + }; + + ImageMeta.parsers[ 0xffe1 ].push( EXIF.parseExifData ); + return EXIF; + }); + /** + * 这个方式性能不行,但是可以解决android里面的toDataUrl的bug + * android里面toDataUrl('image/jpege')得到的结果却是png. + * + * 所以这里没辙,只能借助这个工具 + * @fileOverview jpeg encoder + */ + define('runtime/html5/jpegencoder',[], function( require, exports, module ) { + + /* + Copyright (c) 2008, Adobe Systems Incorporated + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of Adobe Systems Incorporated nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + /* + JPEG encoder ported to JavaScript and optimized by Andreas Ritter, www.bytestrom.eu, 11/2009 + + Basic GUI blocking jpeg encoder + */ + + function JPEGEncoder(quality) { + var self = this; + var fround = Math.round; + var ffloor = Math.floor; + var YTable = new Array(64); + var UVTable = new Array(64); + var fdtbl_Y = new Array(64); + var fdtbl_UV = new Array(64); + var YDC_HT; + var UVDC_HT; + var YAC_HT; + var UVAC_HT; + + var bitcode = new Array(65535); + var category = new Array(65535); + var outputfDCTQuant = new Array(64); + var DU = new Array(64); + var byteout = []; + var bytenew = 0; + var bytepos = 7; + + var YDU = new Array(64); + var UDU = new Array(64); + var VDU = new Array(64); + var clt = new Array(256); + var RGB_YUV_TABLE = new Array(2048); + var currentQuality; + + var ZigZag = [ + 0, 1, 5, 6,14,15,27,28, + 2, 4, 7,13,16,26,29,42, + 3, 8,12,17,25,30,41,43, + 9,11,18,24,31,40,44,53, + 10,19,23,32,39,45,52,54, + 20,22,33,38,46,51,55,60, + 21,34,37,47,50,56,59,61, + 35,36,48,49,57,58,62,63 + ]; + + var std_dc_luminance_nrcodes = [0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0]; + var std_dc_luminance_values = [0,1,2,3,4,5,6,7,8,9,10,11]; + var std_ac_luminance_nrcodes = [0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d]; + var std_ac_luminance_values = [ + 0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12, + 0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07, + 0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08, + 0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0, + 0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16, + 0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28, + 0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39, + 0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49, + 0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59, + 0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69, + 0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79, + 0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89, + 0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98, + 0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7, + 0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6, + 0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5, + 0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4, + 0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2, + 0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea, + 0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8, + 0xf9,0xfa + ]; + + var std_dc_chrominance_nrcodes = [0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0]; + var std_dc_chrominance_values = [0,1,2,3,4,5,6,7,8,9,10,11]; + var std_ac_chrominance_nrcodes = [0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77]; + var std_ac_chrominance_values = [ + 0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21, + 0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71, + 0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91, + 0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0, + 0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34, + 0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26, + 0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38, + 0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48, + 0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58, + 0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68, + 0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78, + 0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87, + 0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96, + 0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5, + 0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4, + 0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3, + 0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2, + 0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda, + 0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9, + 0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8, + 0xf9,0xfa + ]; + + function initQuantTables(sf){ + var YQT = [ + 16, 11, 10, 16, 24, 40, 51, 61, + 12, 12, 14, 19, 26, 58, 60, 55, + 14, 13, 16, 24, 40, 57, 69, 56, + 14, 17, 22, 29, 51, 87, 80, 62, + 18, 22, 37, 56, 68,109,103, 77, + 24, 35, 55, 64, 81,104,113, 92, + 49, 64, 78, 87,103,121,120,101, + 72, 92, 95, 98,112,100,103, 99 + ]; + + for (var i = 0; i < 64; i++) { + var t = ffloor((YQT[i]*sf+50)/100); + if (t < 1) { + t = 1; + } else if (t > 255) { + t = 255; + } + YTable[ZigZag[i]] = t; + } + var UVQT = [ + 17, 18, 24, 47, 99, 99, 99, 99, + 18, 21, 26, 66, 99, 99, 99, 99, + 24, 26, 56, 99, 99, 99, 99, 99, + 47, 66, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99 + ]; + for (var j = 0; j < 64; j++) { + var u = ffloor((UVQT[j]*sf+50)/100); + if (u < 1) { + u = 1; + } else if (u > 255) { + u = 255; + } + UVTable[ZigZag[j]] = u; + } + var aasf = [ + 1.0, 1.387039845, 1.306562965, 1.175875602, + 1.0, 0.785694958, 0.541196100, 0.275899379 + ]; + var k = 0; + for (var row = 0; row < 8; row++) + { + for (var col = 0; col < 8; col++) + { + fdtbl_Y[k] = (1.0 / (YTable [ZigZag[k]] * aasf[row] * aasf[col] * 8.0)); + fdtbl_UV[k] = (1.0 / (UVTable[ZigZag[k]] * aasf[row] * aasf[col] * 8.0)); + k++; + } + } + } + + function computeHuffmanTbl(nrcodes, std_table){ + var codevalue = 0; + var pos_in_table = 0; + var HT = new Array(); + for (var k = 1; k <= 16; k++) { + for (var j = 1; j <= nrcodes[k]; j++) { + HT[std_table[pos_in_table]] = []; + HT[std_table[pos_in_table]][0] = codevalue; + HT[std_table[pos_in_table]][1] = k; + pos_in_table++; + codevalue++; + } + codevalue*=2; + } + return HT; + } + + function initHuffmanTbl() + { + YDC_HT = computeHuffmanTbl(std_dc_luminance_nrcodes,std_dc_luminance_values); + UVDC_HT = computeHuffmanTbl(std_dc_chrominance_nrcodes,std_dc_chrominance_values); + YAC_HT = computeHuffmanTbl(std_ac_luminance_nrcodes,std_ac_luminance_values); + UVAC_HT = computeHuffmanTbl(std_ac_chrominance_nrcodes,std_ac_chrominance_values); + } + + function initCategoryNumber() + { + var nrlower = 1; + var nrupper = 2; + for (var cat = 1; cat <= 15; cat++) { + //Positive numbers + for (var nr = nrlower; nr>0] = 38470 * i; + RGB_YUV_TABLE[(i+ 512)>>0] = 7471 * i + 0x8000; + RGB_YUV_TABLE[(i+ 768)>>0] = -11059 * i; + RGB_YUV_TABLE[(i+1024)>>0] = -21709 * i; + RGB_YUV_TABLE[(i+1280)>>0] = 32768 * i + 0x807FFF; + RGB_YUV_TABLE[(i+1536)>>0] = -27439 * i; + RGB_YUV_TABLE[(i+1792)>>0] = - 5329 * i; + } + } + + // IO functions + function writeBits(bs) + { + var value = bs[0]; + var posval = bs[1]-1; + while ( posval >= 0 ) { + if (value & (1 << posval) ) { + bytenew |= (1 << bytepos); + } + posval--; + bytepos--; + if (bytepos < 0) { + if (bytenew == 0xFF) { + writeByte(0xFF); + writeByte(0); + } + else { + writeByte(bytenew); + } + bytepos=7; + bytenew=0; + } + } + } + + function writeByte(value) + { + byteout.push(clt[value]); // write char directly instead of converting later + } + + function writeWord(value) + { + writeByte((value>>8)&0xFF); + writeByte((value )&0xFF); + } + + // DCT & quantization core + function fDCTQuant(data, fdtbl) + { + var d0, d1, d2, d3, d4, d5, d6, d7; + /* Pass 1: process rows. */ + var dataOff=0; + var i; + var I8 = 8; + var I64 = 64; + for (i=0; i 0.0) ? ((fDCTQuant + 0.5)|0) : ((fDCTQuant - 0.5)|0); + //outputfDCTQuant[i] = fround(fDCTQuant); + + } + return outputfDCTQuant; + } + + function writeAPP0() + { + writeWord(0xFFE0); // marker + writeWord(16); // length + writeByte(0x4A); // J + writeByte(0x46); // F + writeByte(0x49); // I + writeByte(0x46); // F + writeByte(0); // = "JFIF",'\0' + writeByte(1); // versionhi + writeByte(1); // versionlo + writeByte(0); // xyunits + writeWord(1); // xdensity + writeWord(1); // ydensity + writeByte(0); // thumbnwidth + writeByte(0); // thumbnheight + } + + function writeSOF0(width, height) + { + writeWord(0xFFC0); // marker + writeWord(17); // length, truecolor YUV JPG + writeByte(8); // precision + writeWord(height); + writeWord(width); + writeByte(3); // nrofcomponents + writeByte(1); // IdY + writeByte(0x11); // HVY + writeByte(0); // QTY + writeByte(2); // IdU + writeByte(0x11); // HVU + writeByte(1); // QTU + writeByte(3); // IdV + writeByte(0x11); // HVV + writeByte(1); // QTV + } + + function writeDQT() + { + writeWord(0xFFDB); // marker + writeWord(132); // length + writeByte(0); + for (var i=0; i<64; i++) { + writeByte(YTable[i]); + } + writeByte(1); + for (var j=0; j<64; j++) { + writeByte(UVTable[j]); + } + } + + function writeDHT() + { + writeWord(0xFFC4); // marker + writeWord(0x01A2); // length + + writeByte(0); // HTYDCinfo + for (var i=0; i<16; i++) { + writeByte(std_dc_luminance_nrcodes[i+1]); + } + for (var j=0; j<=11; j++) { + writeByte(std_dc_luminance_values[j]); + } + + writeByte(0x10); // HTYACinfo + for (var k=0; k<16; k++) { + writeByte(std_ac_luminance_nrcodes[k+1]); + } + for (var l=0; l<=161; l++) { + writeByte(std_ac_luminance_values[l]); + } + + writeByte(1); // HTUDCinfo + for (var m=0; m<16; m++) { + writeByte(std_dc_chrominance_nrcodes[m+1]); + } + for (var n=0; n<=11; n++) { + writeByte(std_dc_chrominance_values[n]); + } + + writeByte(0x11); // HTUACinfo + for (var o=0; o<16; o++) { + writeByte(std_ac_chrominance_nrcodes[o+1]); + } + for (var p=0; p<=161; p++) { + writeByte(std_ac_chrominance_values[p]); + } + } + + function writeSOS() + { + writeWord(0xFFDA); // marker + writeWord(12); // length + writeByte(3); // nrofcomponents + writeByte(1); // IdY + writeByte(0); // HTY + writeByte(2); // IdU + writeByte(0x11); // HTU + writeByte(3); // IdV + writeByte(0x11); // HTV + writeByte(0); // Ss + writeByte(0x3f); // Se + writeByte(0); // Bf + } + + function processDU(CDU, fdtbl, DC, HTDC, HTAC){ + var EOB = HTAC[0x00]; + var M16zeroes = HTAC[0xF0]; + var pos; + var I16 = 16; + var I63 = 63; + var I64 = 64; + var DU_DCT = fDCTQuant(CDU, fdtbl); + //ZigZag reorder + for (var j=0;j0)&&(DU[end0pos]==0); end0pos--) {}; + //end0pos = first element in reverse order !=0 + if ( end0pos == 0) { + writeBits(EOB); + return DC; + } + var i = 1; + var lng; + while ( i <= end0pos ) { + var startpos = i; + for (; (DU[i]==0) && (i<=end0pos); ++i) {} + var nrzeroes = i-startpos; + if ( nrzeroes >= I16 ) { + lng = nrzeroes>>4; + for (var nrmarker=1; nrmarker <= lng; ++nrmarker) + writeBits(M16zeroes); + nrzeroes = nrzeroes&0xF; + } + pos = 32767+DU[i]; + writeBits(HTAC[(nrzeroes<<4)+category[pos]]); + writeBits(bitcode[pos]); + i++; + } + if ( end0pos != I63 ) { + writeBits(EOB); + } + return DC; + } + + function initCharLookupTable(){ + var sfcc = String.fromCharCode; + for(var i=0; i < 256; i++){ ///// ACHTUNG // 255 + clt[i] = sfcc(i); + } + } + + this.encode = function(image,quality) // image data object + { + // var time_start = new Date().getTime(); + + if(quality) setQuality(quality); + + // Initialize bit writer + byteout = new Array(); + bytenew=0; + bytepos=7; + + // Add JPEG headers + writeWord(0xFFD8); // SOI + writeAPP0(); + writeDQT(); + writeSOF0(image.width,image.height); + writeDHT(); + writeSOS(); + + + // Encode 8x8 macroblocks + var DCY=0; + var DCU=0; + var DCV=0; + + bytenew=0; + bytepos=7; + + + this.encode.displayName = "_encode_"; + + var imageData = image.data; + var width = image.width; + var height = image.height; + + var quadWidth = width*4; + var tripleWidth = width*3; + + var x, y = 0; + var r, g, b; + var start,p, col,row,pos; + while(y < height){ + x = 0; + while(x < quadWidth){ + start = quadWidth * y + x; + p = start; + col = -1; + row = 0; + + for(pos=0; pos < 64; pos++){ + row = pos >> 3;// /8 + col = ( pos & 7 ) * 4; // %8 + p = start + ( row * quadWidth ) + col; + + if(y+row >= height){ // padding bottom + p-= (quadWidth*(y+1+row-height)); + } + + if(x+col >= quadWidth){ // padding right + p-= ((x+col) - quadWidth +4) + } + + r = imageData[ p++ ]; + g = imageData[ p++ ]; + b = imageData[ p++ ]; + + + /* // calculate YUV values dynamically + YDU[pos]=((( 0.29900)*r+( 0.58700)*g+( 0.11400)*b))-128; //-0x80 + UDU[pos]=(((-0.16874)*r+(-0.33126)*g+( 0.50000)*b)); + VDU[pos]=((( 0.50000)*r+(-0.41869)*g+(-0.08131)*b)); + */ + + // use lookup table (slightly faster) + YDU[pos] = ((RGB_YUV_TABLE[r] + RGB_YUV_TABLE[(g + 256)>>0] + RGB_YUV_TABLE[(b + 512)>>0]) >> 16)-128; + UDU[pos] = ((RGB_YUV_TABLE[(r + 768)>>0] + RGB_YUV_TABLE[(g + 1024)>>0] + RGB_YUV_TABLE[(b + 1280)>>0]) >> 16)-128; + VDU[pos] = ((RGB_YUV_TABLE[(r + 1280)>>0] + RGB_YUV_TABLE[(g + 1536)>>0] + RGB_YUV_TABLE[(b + 1792)>>0]) >> 16)-128; + + } + + DCY = processDU(YDU, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCU = processDU(UDU, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); + DCV = processDU(VDU, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); + x+=32; + } + y+=8; + } + + + //////////////////////////////////////////////////////////////// + + // Do the bit alignment of the EOI marker + if ( bytepos >= 0 ) { + var fillbits = []; + fillbits[1] = bytepos+1; + fillbits[0] = (1<<(bytepos+1))-1; + writeBits(fillbits); + } + + writeWord(0xFFD9); //EOI + + var jpegDataUri = 'data:image/jpeg;base64,' + btoa(byteout.join('')); + + byteout = []; + + // benchmarking + // var duration = new Date().getTime() - time_start; + // console.log('Encoding time: '+ currentQuality + 'ms'); + // + + return jpegDataUri + } + + function setQuality(quality){ + if (quality <= 0) { + quality = 1; + } + if (quality > 100) { + quality = 100; + } + + if(currentQuality == quality) return // don't recalc if unchanged + + var sf = 0; + if (quality < 50) { + sf = Math.floor(5000 / quality); + } else { + sf = Math.floor(200 - quality*2); + } + + initQuantTables(sf); + currentQuality = quality; + // console.log('Quality set to: '+quality +'%'); + } + + function init(){ + // var time_start = new Date().getTime(); + if(!quality) quality = 50; + // Create tables + initCharLookupTable() + initHuffmanTbl(); + initCategoryNumber(); + initRGBYUVTable(); + + setQuality(quality); + // var duration = new Date().getTime() - time_start; + // console.log('Initialization '+ duration + 'ms'); + } + + init(); + + }; + + JPEGEncoder.encode = function( data, quality ) { + var encoder = new JPEGEncoder( quality ); + + return encoder.encode( data ); + } + + return JPEGEncoder; + }); + /** + * @fileOverview Fix android canvas.toDataUrl bug. + */ + define('runtime/html5/androidpatch',[ + 'runtime/html5/util', + 'runtime/html5/jpegencoder', + 'base' + ], function( Util, encoder, Base ) { + var origin = Util.canvasToDataUrl, + supportJpeg; + + Util.canvasToDataUrl = function( canvas, type, quality ) { + var ctx, w, h, fragement, parts; + + // 非android手机直接跳过。 + if ( !Base.os.android ) { + return origin.apply( null, arguments ); + } + + // 检测是否canvas支持jpeg导出,根据数据格式来判断。 + // JPEG 前两位分别是:255, 216 + if ( type === 'image/jpeg' && typeof supportJpeg === 'undefined' ) { + fragement = origin.apply( null, arguments ); + + parts = fragement.split(','); + + if ( ~parts[ 0 ].indexOf('base64') ) { + fragement = atob( parts[ 1 ] ); + } else { + fragement = decodeURIComponent( parts[ 1 ] ); + } + + fragement = fragement.substring( 0, 2 ); + + supportJpeg = fragement.charCodeAt( 0 ) === 255 && + fragement.charCodeAt( 1 ) === 216; + } + + // 只有在android环境下才修复 + if ( type === 'image/jpeg' && !supportJpeg ) { + w = canvas.width; + h = canvas.height; + ctx = canvas.getContext('2d'); + + return encoder.encode( ctx.getImageData( 0, 0, w, h ), quality ); + } + + return origin.apply( null, arguments ); + }; + }); + /** + * @fileOverview Image + */ + define('runtime/html5/image',[ + 'base', + 'runtime/html5/runtime', + 'runtime/html5/util' + ], function( Base, Html5Runtime, Util ) { + + var BLANK = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs%3D'; + + return Html5Runtime.register( 'Image', { + + // flag: 标记是否被修改过。 + modified: false, + + init: function() { + var me = this, + img = new Image(); + + img.onload = function() { + + me._info = { + type: me.type, + width: this.width, + height: this.height + }; + + // 读取meta信息。 + if ( !me._metas && 'image/jpeg' === me.type ) { + Util.parseMeta( me._blob, function( error, ret ) { + me._metas = ret; + me.owner.trigger('load'); + }); + } else { + me.owner.trigger('load'); + } + }; + + img.onerror = function() { + me.owner.trigger('error'); + }; + + me._img = img; + }, + + loadFromBlob: function( blob ) { + var me = this, + img = me._img; + + me._blob = blob; + me.type = blob.type; + img.src = Util.createObjectURL( blob.getSource() ); + me.owner.once( 'load', function() { + Util.revokeObjectURL( img.src ); + }); + }, + + resize: function( width, height ) { + var canvas = this._canvas || + (this._canvas = document.createElement('canvas')); + + this._resize( this._img, canvas, width, height ); + this._blob = null; // 没用了,可以删掉了。 + this.modified = true; + this.owner.trigger( 'complete', 'resize' ); + }, + + crop: function( x, y, w, h, s ) { + var cvs = this._canvas || + (this._canvas = document.createElement('canvas')), + opts = this.options, + img = this._img, + iw = img.naturalWidth, + ih = img.naturalHeight, + orientation = this.getOrientation(); + + s = s || 1; + + // todo 解决 orientation 的问题。 + // values that require 90 degree rotation + // if ( ~[ 5, 6, 7, 8 ].indexOf( orientation ) ) { + + // switch ( orientation ) { + // case 6: + // tmp = x; + // x = y; + // y = iw * s - tmp - w; + // console.log(ih * s, tmp, w) + // break; + // } + + // (w ^= h, h ^= w, w ^= h); + // } + + cvs.width = w; + cvs.height = h; + + opts.preserveHeaders || this._rotate2Orientaion( cvs, orientation ); + this._renderImageToCanvas( cvs, img, -x, -y, iw * s, ih * s ); + + this._blob = null; // 没用了,可以删掉了。 + this.modified = true; + this.owner.trigger( 'complete', 'crop' ); + }, + + getAsBlob: function( type ) { + var blob = this._blob, + opts = this.options, + canvas; + + type = type || this.type; + + // blob需要重新生成。 + if ( this.modified || this.type !== type ) { + canvas = this._canvas; + + if ( type === 'image/jpeg' ) { + + blob = Util.canvasToDataUrl( canvas, type, opts.quality ); + + if ( opts.preserveHeaders && this._metas && + this._metas.imageHead ) { + + blob = Util.dataURL2ArrayBuffer( blob ); + blob = Util.updateImageHead( blob, + this._metas.imageHead ); + blob = Util.arrayBufferToBlob( blob, type ); + return blob; + } + } else { + blob = Util.canvasToDataUrl( canvas, type ); + } + + blob = Util.dataURL2Blob( blob ); + } + + return blob; + }, + + getAsDataUrl: function( type ) { + var opts = this.options; + + type = type || this.type; + + if ( type === 'image/jpeg' ) { + return Util.canvasToDataUrl( this._canvas, type, opts.quality ); + } else { + return this._canvas.toDataURL( type ); + } + }, + + getOrientation: function() { + return this._metas && this._metas.exif && + this._metas.exif.get('Orientation') || 1; + }, + + info: function( val ) { + + // setter + if ( val ) { + this._info = val; + return this; + } + + // getter + return this._info; + }, + + meta: function( val ) { + + // setter + if ( val ) { + this._meta = val; + return this; + } + + // getter + return this._meta; + }, + + destroy: function() { + var canvas = this._canvas; + this._img.onload = null; + + if ( canvas ) { + canvas.getContext('2d') + .clearRect( 0, 0, canvas.width, canvas.height ); + canvas.width = canvas.height = 0; + this._canvas = null; + } + + // 释放内存。非常重要,否则释放不了image的内存。 + this._img.src = BLANK; + this._img = this._blob = null; + }, + + _resize: function( img, cvs, width, height ) { + var opts = this.options, + naturalWidth = img.width, + naturalHeight = img.height, + orientation = this.getOrientation(), + scale, w, h, x, y; + + // values that require 90 degree rotation + if ( ~[ 5, 6, 7, 8 ].indexOf( orientation ) ) { + + // 交换width, height的值。 + width ^= height; + height ^= width; + width ^= height; + } + + scale = Math[ opts.crop ? 'max' : 'min' ]( width / naturalWidth, + height / naturalHeight ); + + // 不允许放大。 + opts.allowMagnify || (scale = Math.min( 1, scale )); + + w = naturalWidth * scale; + h = naturalHeight * scale; + + if ( opts.crop ) { + cvs.width = width; + cvs.height = height; + } else { + cvs.width = w; + cvs.height = h; + } + + x = (cvs.width - w) / 2; + y = (cvs.height - h) / 2; + + opts.preserveHeaders || this._rotate2Orientaion( cvs, orientation ); + + this._renderImageToCanvas( cvs, img, x, y, w, h ); + }, + + _rotate2Orientaion: function( canvas, orientation ) { + var width = canvas.width, + height = canvas.height, + ctx = canvas.getContext('2d'); + + switch ( orientation ) { + case 5: + case 6: + case 7: + case 8: + canvas.width = height; + canvas.height = width; + break; + } + + switch ( orientation ) { + case 2: // horizontal flip + ctx.translate( width, 0 ); + ctx.scale( -1, 1 ); + break; + + case 3: // 180 rotate left + ctx.translate( width, height ); + ctx.rotate( Math.PI ); + break; + + case 4: // vertical flip + ctx.translate( 0, height ); + ctx.scale( 1, -1 ); + break; + + case 5: // vertical flip + 90 rotate right + ctx.rotate( 0.5 * Math.PI ); + ctx.scale( 1, -1 ); + break; + + case 6: // 90 rotate right + ctx.rotate( 0.5 * Math.PI ); + ctx.translate( 0, -height ); + break; + + case 7: // horizontal flip + 90 rotate right + ctx.rotate( 0.5 * Math.PI ); + ctx.translate( width, -height ); + ctx.scale( -1, 1 ); + break; + + case 8: // 90 rotate left + ctx.rotate( -0.5 * Math.PI ); + ctx.translate( -width, 0 ); + break; + } + }, + + // https://github.com/stomita/ios-imagefile-megapixel/ + // blob/master/src/megapix-image.js + _renderImageToCanvas: (function() { + + // 如果不是ios, 不需要这么复杂! + if ( !Base.os.ios ) { + return function( canvas ) { + var args = Base.slice( arguments, 1 ), + ctx = canvas.getContext('2d'); + + ctx.drawImage.apply( ctx, args ); + }; + } + + /** + * Detecting vertical squash in loaded image. + * Fixes a bug which squash image vertically while drawing into + * canvas for some images. + */ + function detectVerticalSquash( img, iw, ih ) { + var canvas = document.createElement('canvas'), + ctx = canvas.getContext('2d'), + sy = 0, + ey = ih, + py = ih, + data, alpha, ratio; + + + canvas.width = 1; + canvas.height = ih; + ctx.drawImage( img, 0, 0 ); + data = ctx.getImageData( 0, 0, 1, ih ).data; + + // search image edge pixel position in case + // it is squashed vertically. + while ( py > sy ) { + alpha = data[ (py - 1) * 4 + 3 ]; + + if ( alpha === 0 ) { + ey = py; + } else { + sy = py; + } + + py = (ey + sy) >> 1; + } + + ratio = (py / ih); + return (ratio === 0) ? 1 : ratio; + } + + // fix ie7 bug + // http://stackoverflow.com/questions/11929099/ + // html5-canvas-drawimage-ratio-bug-ios + if ( Base.os.ios >= 7 ) { + return function( canvas, img, x, y, w, h ) { + var iw = img.naturalWidth, + ih = img.naturalHeight, + vertSquashRatio = detectVerticalSquash( img, iw, ih ); + + return canvas.getContext('2d').drawImage( img, 0, 0, + iw * vertSquashRatio, ih * vertSquashRatio, + x, y, w, h ); + }; + } + + /** + * Detect subsampling in loaded image. + * In iOS, larger images than 2M pixels may be + * subsampled in rendering. + */ + function detectSubsampling( img ) { + var iw = img.naturalWidth, + ih = img.naturalHeight, + canvas, ctx; + + // subsampling may happen overmegapixel image + if ( iw * ih > 1024 * 1024 ) { + canvas = document.createElement('canvas'); + canvas.width = canvas.height = 1; + ctx = canvas.getContext('2d'); + ctx.drawImage( img, -iw + 1, 0 ); + + // subsampled image becomes half smaller in rendering size. + // check alpha channel value to confirm image is covering + // edge pixel or not. if alpha value is 0 + // image is not covering, hence subsampled. + return ctx.getImageData( 0, 0, 1, 1 ).data[ 3 ] === 0; + } else { + return false; + } + } + + + return function( canvas, img, x, y, width, height ) { + var iw = img.naturalWidth, + ih = img.naturalHeight, + ctx = canvas.getContext('2d'), + subsampled = detectSubsampling( img ), + doSquash = this.type === 'image/jpeg', + d = 1024, + sy = 0, + dy = 0, + tmpCanvas, tmpCtx, vertSquashRatio, dw, dh, sx, dx; + + if ( subsampled ) { + iw /= 2; + ih /= 2; + } + + ctx.save(); + tmpCanvas = document.createElement('canvas'); + tmpCanvas.width = tmpCanvas.height = d; + + tmpCtx = tmpCanvas.getContext('2d'); + vertSquashRatio = doSquash ? + detectVerticalSquash( img, iw, ih ) : 1; + + dw = Math.ceil( d * width / iw ); + dh = Math.ceil( d * height / ih / vertSquashRatio ); + + while ( sy < ih ) { + sx = 0; + dx = 0; + while ( sx < iw ) { + tmpCtx.clearRect( 0, 0, d, d ); + tmpCtx.drawImage( img, -sx, -sy ); + ctx.drawImage( tmpCanvas, 0, 0, d, d, + x + dx, y + dy, dw, dh ); + sx += d; + dx += dw; + } + sy += d; + dy += dh; + } + ctx.restore(); + tmpCanvas = tmpCtx = null; + }; + })() + }); + }); + /** + * @fileOverview Transport + * @todo 支持chunked传输,优势: + * 可以将大文件分成小块,挨个传输,可以提高大文件成功率,当失败的时候,也只需要重传那小部分, + * 而不需要重头再传一次。另外断点续传也需要用chunked方式。 + */ + define('runtime/html5/transport',[ + 'base', + 'runtime/html5/runtime' + ], function( Base, Html5Runtime ) { + + var noop = Base.noop, + $ = Base.$; + + return Html5Runtime.register( 'Transport', { + init: function() { + this._status = 0; + this._response = null; + }, + + send: function() { + var owner = this.owner, + opts = this.options, + xhr = this._initAjax(), + blob = owner._blob, + server = opts.server, + formData, binary, fr; + + if ( opts.sendAsBinary ) { + server += (/\?/.test( server ) ? '&' : '?') + + $.param( owner._formData ); + + binary = blob.getSource(); + } else { + formData = new FormData(); + $.each( owner._formData, function( k, v ) { + formData.append( k, v ); + }); + + formData.append( opts.fileVal, blob.getSource(), + opts.filename || owner._formData.name || '' ); + } + + if ( opts.withCredentials && 'withCredentials' in xhr ) { + xhr.open( opts.method, server, true ); + xhr.withCredentials = true; + } else { + xhr.open( opts.method, server ); + } + + this._setRequestHeader( xhr, opts.headers ); + + if ( binary ) { + // 强制设置成 content-type 为文件流。 + xhr.overrideMimeType && + xhr.overrideMimeType('application/octet-stream'); + + // android直接发送blob会导致服务端接收到的是空文件。 + // bug详情。 + // https://code.google.com/p/android/issues/detail?id=39882 + // 所以先用fileReader读取出来再通过arraybuffer的方式发送。 + if ( Base.os.android ) { + fr = new FileReader(); + + fr.onload = function() { + xhr.send( this.result ); + fr = fr.onload = null; + }; + + fr.readAsArrayBuffer( binary ); + } else { + xhr.send( binary ); + } + } else { + xhr.send( formData ); + } + }, + + getResponse: function() { + return this._response; + }, + + getResponseAsJson: function() { + return this._parseJson( this._response ); + }, + + getStatus: function() { + return this._status; + }, + + abort: function() { + var xhr = this._xhr; + + if ( xhr ) { + xhr.upload.onprogress = noop; + xhr.onreadystatechange = noop; + xhr.abort(); + + this._xhr = xhr = null; + } + }, + + destroy: function() { + this.abort(); + }, + + _initAjax: function() { + var me = this, + xhr = new XMLHttpRequest(), + opts = this.options; + + if ( opts.withCredentials && !('withCredentials' in xhr) && + typeof XDomainRequest !== 'undefined' ) { + xhr = new XDomainRequest(); + } + + xhr.upload.onprogress = function( e ) { + var percentage = 0; + + if ( e.lengthComputable ) { + percentage = e.loaded / e.total; + } + + return me.trigger( 'progress', percentage ); + }; + + xhr.onreadystatechange = function() { + + if ( xhr.readyState !== 4 ) { + return; + } + + xhr.upload.onprogress = noop; + xhr.onreadystatechange = noop; + me._xhr = null; + me._status = xhr.status; + + if ( xhr.status >= 200 && xhr.status < 300 ) { + me._response = xhr.responseText; + return me.trigger('load'); + } else if ( xhr.status >= 500 && xhr.status < 600 ) { + me._response = xhr.responseText; + return me.trigger( 'error', 'server' ); + } + + + return me.trigger( 'error', me._status ? 'http' : 'abort' ); + }; + + me._xhr = xhr; + return xhr; + }, + + _setRequestHeader: function( xhr, headers ) { + $.each( headers, function( key, val ) { + xhr.setRequestHeader( key, val ); + }); + }, + + _parseJson: function( str ) { + var json; + + try { + json = JSON.parse( str ); + } catch ( ex ) { + json = {}; + } + + return json; + } + }); + }); + /** + * @fileOverview Transport flash实现 + */ + define('runtime/html5/md5',[ + 'runtime/html5/runtime' + ], function( FlashRuntime ) { + + /* + * Fastest md5 implementation around (JKM md5) + * Credits: Joseph Myers + * + * @see http://www.myersdaily.org/joseph/javascript/md5-text.html + * @see http://jsperf.com/md5-shootout/7 + */ + + /* this function is much faster, + so if possible we use it. Some IEs + are the only ones I know of that + need the idiotic second function, + generated by an if clause. */ + var add32 = function (a, b) { + return (a + b) & 0xFFFFFFFF; + }, + + cmn = function (q, a, b, x, s, t) { + a = add32(add32(a, q), add32(x, t)); + return add32((a << s) | (a >>> (32 - s)), b); + }, + + ff = function (a, b, c, d, x, s, t) { + return cmn((b & c) | ((~b) & d), a, b, x, s, t); + }, + + gg = function (a, b, c, d, x, s, t) { + return cmn((b & d) | (c & (~d)), a, b, x, s, t); + }, + + hh = function (a, b, c, d, x, s, t) { + return cmn(b ^ c ^ d, a, b, x, s, t); + }, + + ii = function (a, b, c, d, x, s, t) { + return cmn(c ^ (b | (~d)), a, b, x, s, t); + }, + + md5cycle = function (x, k) { + var a = x[0], + b = x[1], + c = x[2], + d = x[3]; + + a = ff(a, b, c, d, k[0], 7, -680876936); + d = ff(d, a, b, c, k[1], 12, -389564586); + c = ff(c, d, a, b, k[2], 17, 606105819); + b = ff(b, c, d, a, k[3], 22, -1044525330); + a = ff(a, b, c, d, k[4], 7, -176418897); + d = ff(d, a, b, c, k[5], 12, 1200080426); + c = ff(c, d, a, b, k[6], 17, -1473231341); + b = ff(b, c, d, a, k[7], 22, -45705983); + a = ff(a, b, c, d, k[8], 7, 1770035416); + d = ff(d, a, b, c, k[9], 12, -1958414417); + c = ff(c, d, a, b, k[10], 17, -42063); + b = ff(b, c, d, a, k[11], 22, -1990404162); + a = ff(a, b, c, d, k[12], 7, 1804603682); + d = ff(d, a, b, c, k[13], 12, -40341101); + c = ff(c, d, a, b, k[14], 17, -1502002290); + b = ff(b, c, d, a, k[15], 22, 1236535329); + + a = gg(a, b, c, d, k[1], 5, -165796510); + d = gg(d, a, b, c, k[6], 9, -1069501632); + c = gg(c, d, a, b, k[11], 14, 643717713); + b = gg(b, c, d, a, k[0], 20, -373897302); + a = gg(a, b, c, d, k[5], 5, -701558691); + d = gg(d, a, b, c, k[10], 9, 38016083); + c = gg(c, d, a, b, k[15], 14, -660478335); + b = gg(b, c, d, a, k[4], 20, -405537848); + a = gg(a, b, c, d, k[9], 5, 568446438); + d = gg(d, a, b, c, k[14], 9, -1019803690); + c = gg(c, d, a, b, k[3], 14, -187363961); + b = gg(b, c, d, a, k[8], 20, 1163531501); + a = gg(a, b, c, d, k[13], 5, -1444681467); + d = gg(d, a, b, c, k[2], 9, -51403784); + c = gg(c, d, a, b, k[7], 14, 1735328473); + b = gg(b, c, d, a, k[12], 20, -1926607734); + + a = hh(a, b, c, d, k[5], 4, -378558); + d = hh(d, a, b, c, k[8], 11, -2022574463); + c = hh(c, d, a, b, k[11], 16, 1839030562); + b = hh(b, c, d, a, k[14], 23, -35309556); + a = hh(a, b, c, d, k[1], 4, -1530992060); + d = hh(d, a, b, c, k[4], 11, 1272893353); + c = hh(c, d, a, b, k[7], 16, -155497632); + b = hh(b, c, d, a, k[10], 23, -1094730640); + a = hh(a, b, c, d, k[13], 4, 681279174); + d = hh(d, a, b, c, k[0], 11, -358537222); + c = hh(c, d, a, b, k[3], 16, -722521979); + b = hh(b, c, d, a, k[6], 23, 76029189); + a = hh(a, b, c, d, k[9], 4, -640364487); + d = hh(d, a, b, c, k[12], 11, -421815835); + c = hh(c, d, a, b, k[15], 16, 530742520); + b = hh(b, c, d, a, k[2], 23, -995338651); + + a = ii(a, b, c, d, k[0], 6, -198630844); + d = ii(d, a, b, c, k[7], 10, 1126891415); + c = ii(c, d, a, b, k[14], 15, -1416354905); + b = ii(b, c, d, a, k[5], 21, -57434055); + a = ii(a, b, c, d, k[12], 6, 1700485571); + d = ii(d, a, b, c, k[3], 10, -1894986606); + c = ii(c, d, a, b, k[10], 15, -1051523); + b = ii(b, c, d, a, k[1], 21, -2054922799); + a = ii(a, b, c, d, k[8], 6, 1873313359); + d = ii(d, a, b, c, k[15], 10, -30611744); + c = ii(c, d, a, b, k[6], 15, -1560198380); + b = ii(b, c, d, a, k[13], 21, 1309151649); + a = ii(a, b, c, d, k[4], 6, -145523070); + d = ii(d, a, b, c, k[11], 10, -1120210379); + c = ii(c, d, a, b, k[2], 15, 718787259); + b = ii(b, c, d, a, k[9], 21, -343485551); + + x[0] = add32(a, x[0]); + x[1] = add32(b, x[1]); + x[2] = add32(c, x[2]); + x[3] = add32(d, x[3]); + }, + + /* there needs to be support for Unicode here, + * unless we pretend that we can redefine the MD-5 + * algorithm for multi-byte characters (perhaps + * by adding every four 16-bit characters and + * shortening the sum to 32 bits). Otherwise + * I suggest performing MD-5 as if every character + * was two bytes--e.g., 0040 0025 = @%--but then + * how will an ordinary MD-5 sum be matched? + * There is no way to standardize text to something + * like UTF-8 before transformation; speed cost is + * utterly prohibitive. The JavaScript standard + * itself needs to look at this: it should start + * providing access to strings as preformed UTF-8 + * 8-bit unsigned value arrays. + */ + md5blk = function (s) { + var md5blks = [], + i; /* Andy King said do it this way. */ + + for (i = 0; i < 64; i += 4) { + md5blks[i >> 2] = s.charCodeAt(i) + (s.charCodeAt(i + 1) << 8) + (s.charCodeAt(i + 2) << 16) + (s.charCodeAt(i + 3) << 24); + } + return md5blks; + }, + + md5blk_array = function (a) { + var md5blks = [], + i; /* Andy King said do it this way. */ + + for (i = 0; i < 64; i += 4) { + md5blks[i >> 2] = a[i] + (a[i + 1] << 8) + (a[i + 2] << 16) + (a[i + 3] << 24); + } + return md5blks; + }, + + md51 = function (s) { + var n = s.length, + state = [1732584193, -271733879, -1732584194, 271733878], + i, + length, + tail, + tmp, + lo, + hi; + + for (i = 64; i <= n; i += 64) { + md5cycle(state, md5blk(s.substring(i - 64, i))); + } + s = s.substring(i - 64); + length = s.length; + tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + for (i = 0; i < length; i += 1) { + tail[i >> 2] |= s.charCodeAt(i) << ((i % 4) << 3); + } + tail[i >> 2] |= 0x80 << ((i % 4) << 3); + if (i > 55) { + md5cycle(state, tail); + for (i = 0; i < 16; i += 1) { + tail[i] = 0; + } + } + + // Beware that the final length might not fit in 32 bits so we take care of that + tmp = n * 8; + tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/); + lo = parseInt(tmp[2], 16); + hi = parseInt(tmp[1], 16) || 0; + + tail[14] = lo; + tail[15] = hi; + + md5cycle(state, tail); + return state; + }, + + md51_array = function (a) { + var n = a.length, + state = [1732584193, -271733879, -1732584194, 271733878], + i, + length, + tail, + tmp, + lo, + hi; + + for (i = 64; i <= n; i += 64) { + md5cycle(state, md5blk_array(a.subarray(i - 64, i))); + } + + // Not sure if it is a bug, however IE10 will always produce a sub array of length 1 + // containing the last element of the parent array if the sub array specified starts + // beyond the length of the parent array - weird. + // https://connect.microsoft.com/IE/feedback/details/771452/typed-array-subarray-issue + a = (i - 64) < n ? a.subarray(i - 64) : new Uint8Array(0); + + length = a.length; + tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + for (i = 0; i < length; i += 1) { + tail[i >> 2] |= a[i] << ((i % 4) << 3); + } + + tail[i >> 2] |= 0x80 << ((i % 4) << 3); + if (i > 55) { + md5cycle(state, tail); + for (i = 0; i < 16; i += 1) { + tail[i] = 0; + } + } + + // Beware that the final length might not fit in 32 bits so we take care of that + tmp = n * 8; + tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/); + lo = parseInt(tmp[2], 16); + hi = parseInt(tmp[1], 16) || 0; + + tail[14] = lo; + tail[15] = hi; + + md5cycle(state, tail); + + return state; + }, + + hex_chr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'], + + rhex = function (n) { + var s = '', + j; + for (j = 0; j < 4; j += 1) { + s += hex_chr[(n >> (j * 8 + 4)) & 0x0F] + hex_chr[(n >> (j * 8)) & 0x0F]; + } + return s; + }, + + hex = function (x) { + var i; + for (i = 0; i < x.length; i += 1) { + x[i] = rhex(x[i]); + } + return x.join(''); + }, + + md5 = function (s) { + return hex(md51(s)); + }, + + + + //////////////////////////////////////////////////////////////////////////// + + /** + * SparkMD5 OOP implementation. + * + * Use this class to perform an incremental md5, otherwise use the + * static methods instead. + */ + SparkMD5 = function () { + // call reset to init the instance + this.reset(); + }; + + + // In some cases the fast add32 function cannot be used.. + if (md5('hello') !== '5d41402abc4b2a76b9719d911017c592') { + add32 = function (x, y) { + var lsw = (x & 0xFFFF) + (y & 0xFFFF), + msw = (x >> 16) + (y >> 16) + (lsw >> 16); + return (msw << 16) | (lsw & 0xFFFF); + }; + } + + + /** + * Appends a string. + * A conversion will be applied if an utf8 string is detected. + * + * @param {String} str The string to be appended + * + * @return {SparkMD5} The instance itself + */ + SparkMD5.prototype.append = function (str) { + // converts the string to utf8 bytes if necessary + if (/[\u0080-\uFFFF]/.test(str)) { + str = unescape(encodeURIComponent(str)); + } + + // then append as binary + this.appendBinary(str); + + return this; + }; + + /** + * Appends a binary string. + * + * @param {String} contents The binary string to be appended + * + * @return {SparkMD5} The instance itself + */ + SparkMD5.prototype.appendBinary = function (contents) { + this._buff += contents; + this._length += contents.length; + + var length = this._buff.length, + i; + + for (i = 64; i <= length; i += 64) { + md5cycle(this._state, md5blk(this._buff.substring(i - 64, i))); + } + + this._buff = this._buff.substr(i - 64); + + return this; + }; + + /** + * Finishes the incremental computation, reseting the internal state and + * returning the result. + * Use the raw parameter to obtain the raw result instead of the hex one. + * + * @param {Boolean} raw True to get the raw result, false to get the hex result + * + * @return {String|Array} The result + */ + SparkMD5.prototype.end = function (raw) { + var buff = this._buff, + length = buff.length, + i, + tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + ret; + + for (i = 0; i < length; i += 1) { + tail[i >> 2] |= buff.charCodeAt(i) << ((i % 4) << 3); + } + + this._finish(tail, length); + ret = !!raw ? this._state : hex(this._state); + + this.reset(); + + return ret; + }; + + /** + * Finish the final calculation based on the tail. + * + * @param {Array} tail The tail (will be modified) + * @param {Number} length The length of the remaining buffer + */ + SparkMD5.prototype._finish = function (tail, length) { + var i = length, + tmp, + lo, + hi; + + tail[i >> 2] |= 0x80 << ((i % 4) << 3); + if (i > 55) { + md5cycle(this._state, tail); + for (i = 0; i < 16; i += 1) { + tail[i] = 0; + } + } + + // Do the final computation based on the tail and length + // Beware that the final length may not fit in 32 bits so we take care of that + tmp = this._length * 8; + tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/); + lo = parseInt(tmp[2], 16); + hi = parseInt(tmp[1], 16) || 0; + + tail[14] = lo; + tail[15] = hi; + md5cycle(this._state, tail); + }; + + /** + * Resets the internal state of the computation. + * + * @return {SparkMD5} The instance itself + */ + SparkMD5.prototype.reset = function () { + this._buff = ""; + this._length = 0; + this._state = [1732584193, -271733879, -1732584194, 271733878]; + + return this; + }; + + /** + * Releases memory used by the incremental buffer and other aditional + * resources. If you plan to use the instance again, use reset instead. + */ + SparkMD5.prototype.destroy = function () { + delete this._state; + delete this._buff; + delete this._length; + }; + + + /** + * Performs the md5 hash on a string. + * A conversion will be applied if utf8 string is detected. + * + * @param {String} str The string + * @param {Boolean} raw True to get the raw result, false to get the hex result + * + * @return {String|Array} The result + */ + SparkMD5.hash = function (str, raw) { + // converts the string to utf8 bytes if necessary + if (/[\u0080-\uFFFF]/.test(str)) { + str = unescape(encodeURIComponent(str)); + } + + var hash = md51(str); + + return !!raw ? hash : hex(hash); + }; + + /** + * Performs the md5 hash on a binary string. + * + * @param {String} content The binary string + * @param {Boolean} raw True to get the raw result, false to get the hex result + * + * @return {String|Array} The result + */ + SparkMD5.hashBinary = function (content, raw) { + var hash = md51(content); + + return !!raw ? hash : hex(hash); + }; + + /** + * SparkMD5 OOP implementation for array buffers. + * + * Use this class to perform an incremental md5 ONLY for array buffers. + */ + SparkMD5.ArrayBuffer = function () { + // call reset to init the instance + this.reset(); + }; + + //////////////////////////////////////////////////////////////////////////// + + /** + * Appends an array buffer. + * + * @param {ArrayBuffer} arr The array to be appended + * + * @return {SparkMD5.ArrayBuffer} The instance itself + */ + SparkMD5.ArrayBuffer.prototype.append = function (arr) { + // TODO: we could avoid the concatenation here but the algorithm would be more complex + // if you find yourself needing extra performance, please make a PR. + var buff = this._concatArrayBuffer(this._buff, arr), + length = buff.length, + i; + + this._length += arr.byteLength; + + for (i = 64; i <= length; i += 64) { + md5cycle(this._state, md5blk_array(buff.subarray(i - 64, i))); + } + + // Avoids IE10 weirdness (documented above) + this._buff = (i - 64) < length ? buff.subarray(i - 64) : new Uint8Array(0); + + return this; + }; + + /** + * Finishes the incremental computation, reseting the internal state and + * returning the result. + * Use the raw parameter to obtain the raw result instead of the hex one. + * + * @param {Boolean} raw True to get the raw result, false to get the hex result + * + * @return {String|Array} The result + */ + SparkMD5.ArrayBuffer.prototype.end = function (raw) { + var buff = this._buff, + length = buff.length, + tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + i, + ret; + + for (i = 0; i < length; i += 1) { + tail[i >> 2] |= buff[i] << ((i % 4) << 3); + } + + this._finish(tail, length); + ret = !!raw ? this._state : hex(this._state); + + this.reset(); + + return ret; + }; + + SparkMD5.ArrayBuffer.prototype._finish = SparkMD5.prototype._finish; + + /** + * Resets the internal state of the computation. + * + * @return {SparkMD5.ArrayBuffer} The instance itself + */ + SparkMD5.ArrayBuffer.prototype.reset = function () { + this._buff = new Uint8Array(0); + this._length = 0; + this._state = [1732584193, -271733879, -1732584194, 271733878]; + + return this; + }; + + /** + * Releases memory used by the incremental buffer and other aditional + * resources. If you plan to use the instance again, use reset instead. + */ + SparkMD5.ArrayBuffer.prototype.destroy = SparkMD5.prototype.destroy; + + /** + * Concats two array buffers, returning a new one. + * + * @param {ArrayBuffer} first The first array buffer + * @param {ArrayBuffer} second The second array buffer + * + * @return {ArrayBuffer} The new array buffer + */ + SparkMD5.ArrayBuffer.prototype._concatArrayBuffer = function (first, second) { + var firstLength = first.length, + result = new Uint8Array(firstLength + second.byteLength); + + result.set(first); + result.set(new Uint8Array(second), firstLength); + + return result; + }; + + /** + * Performs the md5 hash on an array buffer. + * + * @param {ArrayBuffer} arr The array buffer + * @param {Boolean} raw True to get the raw result, false to get the hex result + * + * @return {String|Array} The result + */ + SparkMD5.ArrayBuffer.hash = function (arr, raw) { + var hash = md51_array(new Uint8Array(arr)); + + return !!raw ? hash : hex(hash); + }; + + return FlashRuntime.register( 'Md5', { + init: function() { + // do nothing. + }, + + loadFromBlob: function( file ) { + var blob = file.getSource(), + chunkSize = 2 * 1024 * 1024, + chunks = Math.ceil( blob.size / chunkSize ), + chunk = 0, + owner = this.owner, + spark = new SparkMD5.ArrayBuffer(), + me = this, + blobSlice = blob.mozSlice || blob.webkitSlice || blob.slice, + loadNext, fr; + + fr = new FileReader(); + + loadNext = function() { + var start, end; + + start = chunk * chunkSize; + end = Math.min( start + chunkSize, blob.size ); + + fr.onload = function( e ) { + spark.append( e.target.result ); + owner.trigger( 'progress', { + total: file.size, + loaded: end + }); + }; + + fr.onloadend = function() { + fr.onloadend = fr.onload = null; + + if ( ++chunk < chunks ) { + setTimeout( loadNext, 1 ); + } else { + setTimeout(function(){ + owner.trigger('load'); + me.result = spark.end(); + loadNext = file = blob = spark = null; + owner.trigger('complete'); + }, 50 ); + } + }; + + fr.readAsArrayBuffer( blobSlice.call( blob, start, end ) ); + }; + + loadNext(); + }, + + getResult: function() { + return this.result; + } + }); + }); + /** + * @fileOverview FlashRuntime + */ + define('runtime/flash/runtime',[ + 'base', + 'runtime/runtime', + 'runtime/compbase' + ], function( Base, Runtime, CompBase ) { + + var $ = Base.$, + type = 'flash', + components = {}; + + + function getFlashVersion() { + var version; + + try { + version = navigator.plugins[ 'Shockwave Flash' ]; + version = version.description; + } catch ( ex ) { + try { + version = new ActiveXObject('ShockwaveFlash.ShockwaveFlash') + .GetVariable('$version'); + } catch ( ex2 ) { + version = '0.0'; + } + } + version = version.match( /\d+/g ); + return parseFloat( version[ 0 ] + '.' + version[ 1 ], 10 ); + } + + function FlashRuntime() { + var pool = {}, + clients = {}, + destroy = this.destroy, + me = this, + jsreciver = Base.guid('webuploader_'); + + Runtime.apply( me, arguments ); + me.type = type; + + + // 这个方法的调用者,实际上是RuntimeClient + me.exec = function( comp, fn/*, args...*/ ) { + var client = this, + uid = client.uid, + args = Base.slice( arguments, 2 ), + instance; + + clients[ uid ] = client; + + if ( components[ comp ] ) { + if ( !pool[ uid ] ) { + pool[ uid ] = new components[ comp ]( client, me ); + } + + instance = pool[ uid ]; + + if ( instance[ fn ] ) { + return instance[ fn ].apply( instance, args ); + } + } + + return me.flashExec.apply( client, arguments ); + }; + + function handler( evt, obj ) { + var type = evt.type || evt, + parts, uid; + + parts = type.split('::'); + uid = parts[ 0 ]; + type = parts[ 1 ]; + + // console.log.apply( console, arguments ); + + if ( type === 'Ready' && uid === me.uid ) { + me.trigger('ready'); + } else if ( clients[ uid ] ) { + clients[ uid ].trigger( type.toLowerCase(), evt, obj ); + } + + // Base.log( evt, obj ); + } + + // flash的接受器。 + window[ jsreciver ] = function() { + var args = arguments; + + // 为了能捕获得到。 + setTimeout(function() { + handler.apply( null, args ); + }, 1 ); + }; + + this.jsreciver = jsreciver; + + this.destroy = function() { + // @todo 删除池子中的所有实例 + return destroy && destroy.apply( this, arguments ); + }; + + this.flashExec = function( comp, fn ) { + var flash = me.getFlash(), + args = Base.slice( arguments, 2 ); + + return flash.exec( this.uid, comp, fn, args ); + }; + + // @todo + } + + Base.inherits( Runtime, { + constructor: FlashRuntime, + + init: function() { + var container = this.getContainer(), + opts = this.options, + html; + + // if not the minimal height, shims are not initialized + // in older browsers (e.g FF3.6, IE6,7,8, Safari 4.0,5.0, etc) + container.css({ + position: 'absolute', + top: '-8px', + left: '-8px', + width: '9px', + height: '9px', + overflow: 'hidden' + }); + + // insert flash object + html = '' + + '' + + '' + + '' + + ''; + + container.html( html ); + }, + + getFlash: function() { + if ( this._flash ) { + return this._flash; + } + + this._flash = $( '#' + this.uid ).get( 0 ); + return this._flash; + } + + }); + + FlashRuntime.register = function( name, component ) { + component = components[ name ] = Base.inherits( CompBase, $.extend({ + + // @todo fix this later + flashExec: function() { + var owner = this.owner, + runtime = this.getRuntime(); + + return runtime.flashExec.apply( owner, arguments ); + } + }, component ) ); + + return component; + }; + + if ( getFlashVersion() >= 11.4 ) { + Runtime.addRuntime( type, FlashRuntime ); + } + + return FlashRuntime; + }); + /** + * @fileOverview FilePicker + */ + define('runtime/flash/filepicker',[ + 'base', + 'runtime/flash/runtime' + ], function( Base, FlashRuntime ) { + var $ = Base.$; + + return FlashRuntime.register( 'FilePicker', { + init: function( opts ) { + var copy = $.extend({}, opts ), + len, i; + + // 修复Flash再没有设置title的情况下无法弹出flash文件选择框的bug. + len = copy.accept && copy.accept.length; + for ( i = 0; i < len; i++ ) { + if ( !copy.accept[ i ].title ) { + copy.accept[ i ].title = 'Files'; + } + } + + delete copy.button; + delete copy.id; + delete copy.container; + + this.flashExec( 'FilePicker', 'init', copy ); + }, + + destroy: function() { + this.flashExec( 'FilePicker', 'destroy' ); + } + }); + }); + /** + * @fileOverview 图片压缩 + */ + define('runtime/flash/image',[ + 'runtime/flash/runtime' + ], function( FlashRuntime ) { + + return FlashRuntime.register( 'Image', { + // init: function( options ) { + // var owner = this.owner; + + // this.flashExec( 'Image', 'init', options ); + // owner.on( 'load', function() { + // debugger; + // }); + // }, + + loadFromBlob: function( blob ) { + var owner = this.owner; + + owner.info() && this.flashExec( 'Image', 'info', owner.info() ); + owner.meta() && this.flashExec( 'Image', 'meta', owner.meta() ); + + this.flashExec( 'Image', 'loadFromBlob', blob.uid ); + } + }); + }); + /** + * @fileOverview Transport flash实现 + */ + define('runtime/flash/transport',[ + 'base', + 'runtime/flash/runtime', + 'runtime/client' + ], function( Base, FlashRuntime, RuntimeClient ) { + var $ = Base.$; + + return FlashRuntime.register( 'Transport', { + init: function() { + this._status = 0; + this._response = null; + this._responseJson = null; + }, + + send: function() { + var owner = this.owner, + opts = this.options, + xhr = this._initAjax(), + blob = owner._blob, + server = opts.server, + binary; + + xhr.connectRuntime( blob.ruid ); + + if ( opts.sendAsBinary ) { + server += (/\?/.test( server ) ? '&' : '?') + + $.param( owner._formData ); + + binary = blob.uid; + } else { + $.each( owner._formData, function( k, v ) { + xhr.exec( 'append', k, v ); + }); + + xhr.exec( 'appendBlob', opts.fileVal, blob.uid, + opts.filename || owner._formData.name || '' ); + } + + this._setRequestHeader( xhr, opts.headers ); + xhr.exec( 'send', { + method: opts.method, + url: server, + forceURLStream: opts.forceURLStream, + mimeType: 'application/octet-stream' + }, binary ); + }, + + getStatus: function() { + return this._status; + }, + + getResponse: function() { + return this._response || ''; + }, + + getResponseAsJson: function() { + return this._responseJson; + }, + + abort: function() { + var xhr = this._xhr; + + if ( xhr ) { + xhr.exec('abort'); + xhr.destroy(); + this._xhr = xhr = null; + } + }, + + destroy: function() { + this.abort(); + }, + + _initAjax: function() { + var me = this, + xhr = new RuntimeClient('XMLHttpRequest'); + + xhr.on( 'uploadprogress progress', function( e ) { + var percent = e.loaded / e.total; + percent = Math.min( 1, Math.max( 0, percent ) ); + return me.trigger( 'progress', percent ); + }); + + xhr.on( 'load', function() { + var status = xhr.exec('getStatus'), + readBody = false, + err = '', + p; + + xhr.off(); + me._xhr = null; + + if ( status >= 200 && status < 300 ) { + readBody = true; + } else if ( status >= 500 && status < 600 ) { + readBody = true; + err = 'server'; + } else { + err = 'http'; + } + + if ( readBody ) { + me._response = xhr.exec('getResponse'); + me._response = decodeURIComponent( me._response ); + + // flash 处理可能存在 bug, 没辙只能靠 js 了 + // try { + // me._responseJson = xhr.exec('getResponseAsJson'); + // } catch ( error ) { + + p = window.JSON && window.JSON.parse || function( s ) { + try { + return new Function('return ' + s).call(); + } catch ( err ) { + return {}; + } + }; + me._responseJson = me._response ? p(me._response) : {}; + + // } + } + + xhr.destroy(); + xhr = null; + + return err ? me.trigger( 'error', err ) : me.trigger('load'); + }); + + xhr.on( 'error', function() { + xhr.off(); + me._xhr = null; + me.trigger( 'error', 'http' ); + }); + + me._xhr = xhr; + return xhr; + }, + + _setRequestHeader: function( xhr, headers ) { + $.each( headers, function( key, val ) { + xhr.exec( 'setRequestHeader', key, val ); + }); + } + }); + }); + /** + * @fileOverview Blob Html实现 + */ + define('runtime/flash/blob',[ + 'runtime/flash/runtime', + 'lib/blob' + ], function( FlashRuntime, Blob ) { + + return FlashRuntime.register( 'Blob', { + slice: function( start, end ) { + var blob = this.flashExec( 'Blob', 'slice', start, end ); + + return new Blob( blob.uid, blob ); + } + }); + }); + /** + * @fileOverview Md5 flash实现 + */ + define('runtime/flash/md5',[ + 'runtime/flash/runtime' + ], function( FlashRuntime ) { + + return FlashRuntime.register( 'Md5', { + init: function() { + // do nothing. + }, + + loadFromBlob: function( blob ) { + return this.flashExec( 'Md5', 'loadFromBlob', blob.uid ); + } + }); + }); + /** + * @fileOverview 完全版本。 + */ + define('preset/all',[ + 'base', + + // widgets + 'widgets/filednd', + 'widgets/filepaste', + 'widgets/filepicker', + 'widgets/image', + 'widgets/queue', + 'widgets/runtime', + 'widgets/upload', + 'widgets/validator', + 'widgets/md5', + + // runtimes + // html5 + 'runtime/html5/blob', + 'runtime/html5/dnd', + 'runtime/html5/filepaste', + 'runtime/html5/filepicker', + 'runtime/html5/imagemeta/exif', + 'runtime/html5/androidpatch', + 'runtime/html5/image', + 'runtime/html5/transport', + 'runtime/html5/md5', + + // flash + 'runtime/flash/filepicker', + 'runtime/flash/image', + 'runtime/flash/transport', + 'runtime/flash/blob', + 'runtime/flash/md5' + ], function( Base ) { + return Base; + }); + /** + * @fileOverview 日志组件,主要用来收集错误信息,可以帮助 webuploader 更好的定位问题和发展。 + * + * 如果您不想要启用此功能,请在打包的时候去掉 log 模块。 + * + * 或者可以在初始化的时候通过 options.disableWidgets 属性禁用。 + * + * 如: + * WebUploader.create({ + * ... + * + * disableWidgets: 'log', + * + * ... + * }) + */ + define('widgets/log',[ + 'base', + 'uploader', + 'widgets/widget' + ], function( Base, Uploader ) { + var $ = Base.$, + logUrl = ' http://static.tieba.baidu.com/tb/pms/img/st.gif??', + product = (location.hostname || location.host || 'protected').toLowerCase(), + + // 只针对 baidu 内部产品用户做统计功能。 + enable = product && /baidu/i.exec(product), + base; + + if (!enable) { + return; + } + + base = { + dv: 3, + master: 'webuploader', + online: /test/.exec(product) ? 0 : 1, + module: '', + product: product, + type: 0 + }; + + function send(data) { + var obj = $.extend({}, base, data), + url = logUrl.replace(/^(.*)\?/, '$1' + $.param( obj )), + image = new Image(); + + image.src = url; + } + + return Uploader.register({ + name: 'log', + + init: function() { + var owner = this.owner, + count = 0, + size = 0; + + owner + .on('error', function(code) { + send({ + type: 2, + c_error_code: code + }); + }) + .on('uploadError', function(file, reason) { + send({ + type: 2, + c_error_code: 'UPLOAD_ERROR', + c_reason: '' + reason + }); + }) + .on('uploadComplete', function(file) { + count++; + size += file.size; + }). + on('uploadFinished', function() { + send({ + c_count: count, + c_size: size + }); + count = size = 0; + }); + + send({ + c_usage: 1 + }); + } + }); + }); + /** + * @fileOverview Uploader上传类 + */ + define('webuploader',[ + 'preset/all', + 'widgets/log' + ], function( preset ) { + return preset; + }); + + var _require = require; + return _require('webuploader'); +}); diff --git a/static/plugins/webuploader/webuploader.flashonly.js b/static/plugins/webuploader/webuploader.flashonly.js new file mode 100644 index 00000000..956e256b --- /dev/null +++ b/static/plugins/webuploader/webuploader.flashonly.js @@ -0,0 +1,4622 @@ +/*! WebUploader 0.1.5 */ + + +/** + * @fileOverview 让内部各个部件的代码可以用[amd](https://github.com/amdjs/amdjs-api/wiki/AMD)模块定义方式组织起来。 + * + * AMD API 内部的简单不完全实现,请忽略。只有当WebUploader被合并成一个文件的时候才会引入。 + */ +(function( root, factory ) { + var modules = {}, + + // 内部require, 简单不完全实现。 + // https://github.com/amdjs/amdjs-api/wiki/require + _require = function( deps, callback ) { + var args, len, i; + + // 如果deps不是数组,则直接返回指定module + if ( typeof deps === 'string' ) { + return getModule( deps ); + } else { + args = []; + for( len = deps.length, i = 0; i < len; i++ ) { + args.push( getModule( deps[ i ] ) ); + } + + return callback.apply( null, args ); + } + }, + + // 内部define,暂时不支持不指定id. + _define = function( id, deps, factory ) { + if ( arguments.length === 2 ) { + factory = deps; + deps = null; + } + + _require( deps || [], function() { + setModule( id, factory, arguments ); + }); + }, + + // 设置module, 兼容CommonJs写法。 + setModule = function( id, factory, args ) { + var module = { + exports: factory + }, + returned; + + if ( typeof factory === 'function' ) { + args.length || (args = [ _require, module.exports, module ]); + returned = factory.apply( null, args ); + returned !== undefined && (module.exports = returned); + } + + modules[ id ] = module.exports; + }, + + // 根据id获取module + getModule = function( id ) { + var module = modules[ id ] || root[ id ]; + + if ( !module ) { + throw new Error( '`' + id + '` is undefined' ); + } + + return module; + }, + + // 将所有modules,将路径ids装换成对象。 + exportsTo = function( obj ) { + var key, host, parts, part, last, ucFirst; + + // make the first character upper case. + ucFirst = function( str ) { + return str && (str.charAt( 0 ).toUpperCase() + str.substr( 1 )); + }; + + for ( key in modules ) { + host = obj; + + if ( !modules.hasOwnProperty( key ) ) { + continue; + } + + parts = key.split('/'); + last = ucFirst( parts.pop() ); + + while( (part = ucFirst( parts.shift() )) ) { + host[ part ] = host[ part ] || {}; + host = host[ part ]; + } + + host[ last ] = modules[ key ]; + } + + return obj; + }, + + makeExport = function( dollar ) { + root.__dollar = dollar; + + // exports every module. + return exportsTo( factory( root, _define, _require ) ); + }, + + origin; + + if ( typeof module === 'object' && typeof module.exports === 'object' ) { + + // For CommonJS and CommonJS-like environments where a proper window is present, + module.exports = makeExport(); + } else if ( typeof define === 'function' && define.amd ) { + + // Allow using this built library as an AMD module + // in another project. That other project will only + // see this AMD call, not the internal modules in + // the closure below. + define([ 'jquery' ], makeExport ); + } else { + + // Browser globals case. Just assign the + // result to a property on the global. + origin = root.WebUploader; + root.WebUploader = makeExport(); + root.WebUploader.noConflict = function() { + root.WebUploader = origin; + }; + } +})( window, function( window, define, require ) { + + + /** + * @fileOverview jQuery or Zepto + */ + define('dollar-third',[],function() { + var $ = window.__dollar || window.jQuery || window.Zepto; + + if ( !$ ) { + throw new Error('jQuery or Zepto not found!'); + } + + return $; + }); + /** + * @fileOverview Dom 操作相关 + */ + define('dollar',[ + 'dollar-third' + ], function( _ ) { + return _; + }); + /** + * @fileOverview 使用jQuery的Promise + */ + define('promise-third',[ + 'dollar' + ], function( $ ) { + return { + Deferred: $.Deferred, + when: $.when, + + isPromise: function( anything ) { + return anything && typeof anything.then === 'function'; + } + }; + }); + /** + * @fileOverview Promise/A+ + */ + define('promise',[ + 'promise-third' + ], function( _ ) { + return _; + }); + /** + * @fileOverview 基础类方法。 + */ + + /** + * Web Uploader内部类的详细说明,以下提及的功能类,都可以在`WebUploader`这个变量中访问到。 + * + * As you know, Web Uploader的每个文件都是用过[AMD](https://github.com/amdjs/amdjs-api/wiki/AMD)规范中的`define`组织起来的, 每个Module都会有个module id. + * 默认module id为该文件的路径,而此路径将会转化成名字空间存放在WebUploader中。如: + * + * * module `base`:WebUploader.Base + * * module `file`: WebUploader.File + * * module `lib/dnd`: WebUploader.Lib.Dnd + * * module `runtime/html5/dnd`: WebUploader.Runtime.Html5.Dnd + * + * + * 以下文档中对类的使用可能省略掉了`WebUploader`前缀。 + * @module WebUploader + * @title WebUploader API文档 + */ + define('base',[ + 'dollar', + 'promise' + ], function( $, promise ) { + + var noop = function() {}, + call = Function.call; + + // http://jsperf.com/uncurrythis + // 反科里化 + function uncurryThis( fn ) { + return function() { + return call.apply( fn, arguments ); + }; + } + + function bindFn( fn, context ) { + return function() { + return fn.apply( context, arguments ); + }; + } + + function createObject( proto ) { + var f; + + if ( Object.create ) { + return Object.create( proto ); + } else { + f = function() {}; + f.prototype = proto; + return new f(); + } + } + + + /** + * 基础类,提供一些简单常用的方法。 + * @class Base + */ + return { + + /** + * @property {String} version 当前版本号。 + */ + version: '0.1.5', + + /** + * @property {jQuery|Zepto} $ 引用依赖的jQuery或者Zepto对象。 + */ + $: $, + + Deferred: promise.Deferred, + + isPromise: promise.isPromise, + + when: promise.when, + + /** + * @description 简单的浏览器检查结果。 + * + * * `webkit` webkit版本号,如果浏览器为非webkit内核,此属性为`undefined`。 + * * `chrome` chrome浏览器版本号,如果浏览器为chrome,此属性为`undefined`。 + * * `ie` ie浏览器版本号,如果浏览器为非ie,此属性为`undefined`。**暂不支持ie10+** + * * `firefox` firefox浏览器版本号,如果浏览器为非firefox,此属性为`undefined`。 + * * `safari` safari浏览器版本号,如果浏览器为非safari,此属性为`undefined`。 + * * `opera` opera浏览器版本号,如果浏览器为非opera,此属性为`undefined`。 + * + * @property {Object} [browser] + */ + browser: (function( ua ) { + var ret = {}, + webkit = ua.match( /WebKit\/([\d.]+)/ ), + chrome = ua.match( /Chrome\/([\d.]+)/ ) || + ua.match( /CriOS\/([\d.]+)/ ), + + ie = ua.match( /MSIE\s([\d\.]+)/ ) || + ua.match( /(?:trident)(?:.*rv:([\w.]+))?/i ), + firefox = ua.match( /Firefox\/([\d.]+)/ ), + safari = ua.match( /Safari\/([\d.]+)/ ), + opera = ua.match( /OPR\/([\d.]+)/ ); + + webkit && (ret.webkit = parseFloat( webkit[ 1 ] )); + chrome && (ret.chrome = parseFloat( chrome[ 1 ] )); + ie && (ret.ie = parseFloat( ie[ 1 ] )); + firefox && (ret.firefox = parseFloat( firefox[ 1 ] )); + safari && (ret.safari = parseFloat( safari[ 1 ] )); + opera && (ret.opera = parseFloat( opera[ 1 ] )); + + return ret; + })( navigator.userAgent ), + + /** + * @description 操作系统检查结果。 + * + * * `android` 如果在android浏览器环境下,此值为对应的android版本号,否则为`undefined`。 + * * `ios` 如果在ios浏览器环境下,此值为对应的ios版本号,否则为`undefined`。 + * @property {Object} [os] + */ + os: (function( ua ) { + var ret = {}, + + // osx = !!ua.match( /\(Macintosh\; Intel / ), + android = ua.match( /(?:Android);?[\s\/]+([\d.]+)?/ ), + ios = ua.match( /(?:iPad|iPod|iPhone).*OS\s([\d_]+)/ ); + + // osx && (ret.osx = true); + android && (ret.android = parseFloat( android[ 1 ] )); + ios && (ret.ios = parseFloat( ios[ 1 ].replace( /_/g, '.' ) )); + + return ret; + })( navigator.userAgent ), + + /** + * 实现类与类之间的继承。 + * @method inherits + * @grammar Base.inherits( super ) => child + * @grammar Base.inherits( super, protos ) => child + * @grammar Base.inherits( super, protos, statics ) => child + * @param {Class} super 父类 + * @param {Object | Function} [protos] 子类或者对象。如果对象中包含constructor,子类将是用此属性值。 + * @param {Function} [protos.constructor] 子类构造器,不指定的话将创建个临时的直接执行父类构造器的方法。 + * @param {Object} [statics] 静态属性或方法。 + * @return {Class} 返回子类。 + * @example + * function Person() { + * console.log( 'Super' ); + * } + * Person.prototype.hello = function() { + * console.log( 'hello' ); + * }; + * + * var Manager = Base.inherits( Person, { + * world: function() { + * console.log( 'World' ); + * } + * }); + * + * // 因为没有指定构造器,父类的构造器将会执行。 + * var instance = new Manager(); // => Super + * + * // 继承子父类的方法 + * instance.hello(); // => hello + * instance.world(); // => World + * + * // 子类的__super__属性指向父类 + * console.log( Manager.__super__ === Person ); // => true + */ + inherits: function( Super, protos, staticProtos ) { + var child; + + if ( typeof protos === 'function' ) { + child = protos; + protos = null; + } else if ( protos && protos.hasOwnProperty('constructor') ) { + child = protos.constructor; + } else { + child = function() { + return Super.apply( this, arguments ); + }; + } + + // 复制静态方法 + $.extend( true, child, Super, staticProtos || {} ); + + /* jshint camelcase: false */ + + // 让子类的__super__属性指向父类。 + child.__super__ = Super.prototype; + + // 构建原型,添加原型方法或属性。 + // 暂时用Object.create实现。 + child.prototype = createObject( Super.prototype ); + protos && $.extend( true, child.prototype, protos ); + + return child; + }, + + /** + * 一个不做任何事情的方法。可以用来赋值给默认的callback. + * @method noop + */ + noop: noop, + + /** + * 返回一个新的方法,此方法将已指定的`context`来执行。 + * @grammar Base.bindFn( fn, context ) => Function + * @method bindFn + * @example + * var doSomething = function() { + * console.log( this.name ); + * }, + * obj = { + * name: 'Object Name' + * }, + * aliasFn = Base.bind( doSomething, obj ); + * + * aliasFn(); // => Object Name + * + */ + bindFn: bindFn, + + /** + * 引用Console.log如果存在的话,否则引用一个[空函数noop](#WebUploader:Base.noop)。 + * @grammar Base.log( args... ) => undefined + * @method log + */ + log: (function() { + if ( window.console ) { + return bindFn( console.log, console ); + } + return noop; + })(), + + nextTick: (function() { + + return function( cb ) { + setTimeout( cb, 1 ); + }; + + // @bug 当浏览器不在当前窗口时就停了。 + // var next = window.requestAnimationFrame || + // window.webkitRequestAnimationFrame || + // window.mozRequestAnimationFrame || + // function( cb ) { + // window.setTimeout( cb, 1000 / 60 ); + // }; + + // // fix: Uncaught TypeError: Illegal invocation + // return bindFn( next, window ); + })(), + + /** + * 被[uncurrythis](http://www.2ality.com/2011/11/uncurrying-this.html)的数组slice方法。 + * 将用来将非数组对象转化成数组对象。 + * @grammar Base.slice( target, start[, end] ) => Array + * @method slice + * @example + * function doSomthing() { + * var args = Base.slice( arguments, 1 ); + * console.log( args ); + * } + * + * doSomthing( 'ignored', 'arg2', 'arg3' ); // => Array ["arg2", "arg3"] + */ + slice: uncurryThis( [].slice ), + + /** + * 生成唯一的ID + * @method guid + * @grammar Base.guid() => String + * @grammar Base.guid( prefx ) => String + */ + guid: (function() { + var counter = 0; + + return function( prefix ) { + var guid = (+new Date()).toString( 32 ), + i = 0; + + for ( ; i < 5; i++ ) { + guid += Math.floor( Math.random() * 65535 ).toString( 32 ); + } + + return (prefix || 'wu_') + guid + (counter++).toString( 32 ); + }; + })(), + + /** + * 格式化文件大小, 输出成带单位的字符串 + * @method formatSize + * @grammar Base.formatSize( size ) => String + * @grammar Base.formatSize( size, pointLength ) => String + * @grammar Base.formatSize( size, pointLength, units ) => String + * @param {Number} size 文件大小 + * @param {Number} [pointLength=2] 精确到的小数点数。 + * @param {Array} [units=[ 'B', 'K', 'M', 'G', 'TB' ]] 单位数组。从字节,到千字节,一直往上指定。如果单位数组里面只指定了到了K(千字节),同时文件大小大于M, 此方法的输出将还是显示成多少K. + * @example + * console.log( Base.formatSize( 100 ) ); // => 100B + * console.log( Base.formatSize( 1024 ) ); // => 1.00K + * console.log( Base.formatSize( 1024, 0 ) ); // => 1K + * console.log( Base.formatSize( 1024 * 1024 ) ); // => 1.00M + * console.log( Base.formatSize( 1024 * 1024 * 1024 ) ); // => 1.00G + * console.log( Base.formatSize( 1024 * 1024 * 1024, 0, ['B', 'KB', 'MB'] ) ); // => 1024MB + */ + formatSize: function( size, pointLength, units ) { + var unit; + + units = units || [ 'B', 'K', 'M', 'G', 'TB' ]; + + while ( (unit = units.shift()) && size > 1024 ) { + size = size / 1024; + } + + return (unit === 'B' ? size : size.toFixed( pointLength || 2 )) + + unit; + } + }; + }); + /** + * 事件处理类,可以独立使用,也可以扩展给对象使用。 + * @fileOverview Mediator + */ + define('mediator',[ + 'base' + ], function( Base ) { + var $ = Base.$, + slice = [].slice, + separator = /\s+/, + protos; + + // 根据条件过滤出事件handlers. + function findHandlers( arr, name, callback, context ) { + return $.grep( arr, function( handler ) { + return handler && + (!name || handler.e === name) && + (!callback || handler.cb === callback || + handler.cb._cb === callback) && + (!context || handler.ctx === context); + }); + } + + function eachEvent( events, callback, iterator ) { + // 不支持对象,只支持多个event用空格隔开 + $.each( (events || '').split( separator ), function( _, key ) { + iterator( key, callback ); + }); + } + + function triggerHanders( events, args ) { + var stoped = false, + i = -1, + len = events.length, + handler; + + while ( ++i < len ) { + handler = events[ i ]; + + if ( handler.cb.apply( handler.ctx2, args ) === false ) { + stoped = true; + break; + } + } + + return !stoped; + } + + protos = { + + /** + * 绑定事件。 + * + * `callback`方法在执行时,arguments将会来源于trigger的时候携带的参数。如 + * ```javascript + * var obj = {}; + * + * // 使得obj有事件行为 + * Mediator.installTo( obj ); + * + * obj.on( 'testa', function( arg1, arg2 ) { + * console.log( arg1, arg2 ); // => 'arg1', 'arg2' + * }); + * + * obj.trigger( 'testa', 'arg1', 'arg2' ); + * ``` + * + * 如果`callback`中,某一个方法`return false`了,则后续的其他`callback`都不会被执行到。 + * 切会影响到`trigger`方法的返回值,为`false`。 + * + * `on`还可以用来添加一个特殊事件`all`, 这样所有的事件触发都会响应到。同时此类`callback`中的arguments有一个不同处, + * 就是第一个参数为`type`,记录当前是什么事件在触发。此类`callback`的优先级比脚低,会再正常`callback`执行完后触发。 + * ```javascript + * obj.on( 'all', function( type, arg1, arg2 ) { + * console.log( type, arg1, arg2 ); // => 'testa', 'arg1', 'arg2' + * }); + * ``` + * + * @method on + * @grammar on( name, callback[, context] ) => self + * @param {String} name 事件名,支持多个事件用空格隔开 + * @param {Function} callback 事件处理器 + * @param {Object} [context] 事件处理器的上下文。 + * @return {self} 返回自身,方便链式 + * @chainable + * @class Mediator + */ + on: function( name, callback, context ) { + var me = this, + set; + + if ( !callback ) { + return this; + } + + set = this._events || (this._events = []); + + eachEvent( name, callback, function( name, callback ) { + var handler = { e: name }; + + handler.cb = callback; + handler.ctx = context; + handler.ctx2 = context || me; + handler.id = set.length; + + set.push( handler ); + }); + + return this; + }, + + /** + * 绑定事件,且当handler执行完后,自动解除绑定。 + * @method once + * @grammar once( name, callback[, context] ) => self + * @param {String} name 事件名 + * @param {Function} callback 事件处理器 + * @param {Object} [context] 事件处理器的上下文。 + * @return {self} 返回自身,方便链式 + * @chainable + */ + once: function( name, callback, context ) { + var me = this; + + if ( !callback ) { + return me; + } + + eachEvent( name, callback, function( name, callback ) { + var once = function() { + me.off( name, once ); + return callback.apply( context || me, arguments ); + }; + + once._cb = callback; + me.on( name, once, context ); + }); + + return me; + }, + + /** + * 解除事件绑定 + * @method off + * @grammar off( [name[, callback[, context] ] ] ) => self + * @param {String} [name] 事件名 + * @param {Function} [callback] 事件处理器 + * @param {Object} [context] 事件处理器的上下文。 + * @return {self} 返回自身,方便链式 + * @chainable + */ + off: function( name, cb, ctx ) { + var events = this._events; + + if ( !events ) { + return this; + } + + if ( !name && !cb && !ctx ) { + this._events = []; + return this; + } + + eachEvent( name, cb, function( name, cb ) { + $.each( findHandlers( events, name, cb, ctx ), function() { + delete events[ this.id ]; + }); + }); + + return this; + }, + + /** + * 触发事件 + * @method trigger + * @grammar trigger( name[, args...] ) => self + * @param {String} type 事件名 + * @param {*} [...] 任意参数 + * @return {Boolean} 如果handler中return false了,则返回false, 否则返回true + */ + trigger: function( type ) { + var args, events, allEvents; + + if ( !this._events || !type ) { + return this; + } + + args = slice.call( arguments, 1 ); + events = findHandlers( this._events, type ); + allEvents = findHandlers( this._events, 'all' ); + + return triggerHanders( events, args ) && + triggerHanders( allEvents, arguments ); + } + }; + + /** + * 中介者,它本身是个单例,但可以通过[installTo](#WebUploader:Mediator:installTo)方法,使任何对象具备事件行为。 + * 主要目的是负责模块与模块之间的合作,降低耦合度。 + * + * @class Mediator + */ + return $.extend({ + + /** + * 可以通过这个接口,使任何对象具备事件功能。 + * @method installTo + * @param {Object} obj 需要具备事件行为的对象。 + * @return {Object} 返回obj. + */ + installTo: function( obj ) { + return $.extend( obj, protos ); + } + + }, protos ); + }); + /** + * @fileOverview Uploader上传类 + */ + define('uploader',[ + 'base', + 'mediator' + ], function( Base, Mediator ) { + + var $ = Base.$; + + /** + * 上传入口类。 + * @class Uploader + * @constructor + * @grammar new Uploader( opts ) => Uploader + * @example + * var uploader = WebUploader.Uploader({ + * swf: 'path_of_swf/Uploader.swf', + * + * // 开起分片上传。 + * chunked: true + * }); + */ + function Uploader( opts ) { + this.options = $.extend( true, {}, Uploader.options, opts ); + this._init( this.options ); + } + + // default Options + // widgets中有相应扩展 + Uploader.options = {}; + Mediator.installTo( Uploader.prototype ); + + // 批量添加纯命令式方法。 + $.each({ + upload: 'start-upload', + stop: 'stop-upload', + getFile: 'get-file', + getFiles: 'get-files', + addFile: 'add-file', + addFiles: 'add-file', + sort: 'sort-files', + removeFile: 'remove-file', + cancelFile: 'cancel-file', + skipFile: 'skip-file', + retry: 'retry', + isInProgress: 'is-in-progress', + makeThumb: 'make-thumb', + md5File: 'md5-file', + getDimension: 'get-dimension', + addButton: 'add-btn', + predictRuntimeType: 'predict-runtime-type', + refresh: 'refresh', + disable: 'disable', + enable: 'enable', + reset: 'reset' + }, function( fn, command ) { + Uploader.prototype[ fn ] = function() { + return this.request( command, arguments ); + }; + }); + + $.extend( Uploader.prototype, { + state: 'pending', + + _init: function( opts ) { + var me = this; + + me.request( 'init', opts, function() { + me.state = 'ready'; + me.trigger('ready'); + }); + }, + + /** + * 获取或者设置Uploader配置项。 + * @method option + * @grammar option( key ) => * + * @grammar option( key, val ) => self + * @example + * + * // 初始状态图片上传前不会压缩 + * var uploader = new WebUploader.Uploader({ + * compress: null; + * }); + * + * // 修改后图片上传前,尝试将图片压缩到1600 * 1600 + * uploader.option( 'compress', { + * width: 1600, + * height: 1600 + * }); + */ + option: function( key, val ) { + var opts = this.options; + + // setter + if ( arguments.length > 1 ) { + + if ( $.isPlainObject( val ) && + $.isPlainObject( opts[ key ] ) ) { + $.extend( opts[ key ], val ); + } else { + opts[ key ] = val; + } + + } else { // getter + return key ? opts[ key ] : opts; + } + }, + + /** + * 获取文件统计信息。返回一个包含一下信息的对象。 + * * `successNum` 上传成功的文件数 + * * `progressNum` 上传中的文件数 + * * `cancelNum` 被删除的文件数 + * * `invalidNum` 无效的文件数 + * * `uploadFailNum` 上传失败的文件数 + * * `queueNum` 还在队列中的文件数 + * * `interruptNum` 被暂停的文件数 + * @method getStats + * @grammar getStats() => Object + */ + getStats: function() { + // return this._mgr.getStats.apply( this._mgr, arguments ); + var stats = this.request('get-stats'); + + return stats ? { + successNum: stats.numOfSuccess, + progressNum: stats.numOfProgress, + + // who care? + // queueFailNum: 0, + cancelNum: stats.numOfCancel, + invalidNum: stats.numOfInvalid, + uploadFailNum: stats.numOfUploadFailed, + queueNum: stats.numOfQueue, + interruptNum: stats.numofInterrupt + } : {}; + }, + + // 需要重写此方法来来支持opts.onEvent和instance.onEvent的处理器 + trigger: function( type/*, args...*/ ) { + var args = [].slice.call( arguments, 1 ), + opts = this.options, + name = 'on' + type.substring( 0, 1 ).toUpperCase() + + type.substring( 1 ); + + if ( + // 调用通过on方法注册的handler. + Mediator.trigger.apply( this, arguments ) === false || + + // 调用opts.onEvent + $.isFunction( opts[ name ] ) && + opts[ name ].apply( this, args ) === false || + + // 调用this.onEvent + $.isFunction( this[ name ] ) && + this[ name ].apply( this, args ) === false || + + // 广播所有uploader的事件。 + Mediator.trigger.apply( Mediator, + [ this, type ].concat( args ) ) === false ) { + + return false; + } + + return true; + }, + + /** + * 销毁 webuploader 实例 + * @method destroy + * @grammar destroy() => undefined + */ + destroy: function() { + this.request( 'destroy', arguments ); + this.off(); + }, + + // widgets/widget.js将补充此方法的详细文档。 + request: Base.noop + }); + + /** + * 创建Uploader实例,等同于new Uploader( opts ); + * @method create + * @class Base + * @static + * @grammar Base.create( opts ) => Uploader + */ + Base.create = Uploader.create = function( opts ) { + return new Uploader( opts ); + }; + + // 暴露Uploader,可以通过它来扩展业务逻辑。 + Base.Uploader = Uploader; + + return Uploader; + }); + /** + * @fileOverview Runtime管理器,负责Runtime的选择, 连接 + */ + define('runtime/runtime',[ + 'base', + 'mediator' + ], function( Base, Mediator ) { + + var $ = Base.$, + factories = {}, + + // 获取对象的第一个key + getFirstKey = function( obj ) { + for ( var key in obj ) { + if ( obj.hasOwnProperty( key ) ) { + return key; + } + } + return null; + }; + + // 接口类。 + function Runtime( options ) { + this.options = $.extend({ + container: document.body + }, options ); + this.uid = Base.guid('rt_'); + } + + $.extend( Runtime.prototype, { + + getContainer: function() { + var opts = this.options, + parent, container; + + if ( this._container ) { + return this._container; + } + + parent = $( opts.container || document.body ); + container = $( document.createElement('div') ); + + container.attr( 'id', 'rt_' + this.uid ); + container.css({ + position: 'absolute', + top: '0px', + left: '0px', + width: '1px', + height: '1px', + overflow: 'hidden' + }); + + parent.append( container ); + parent.addClass('webuploader-container'); + this._container = container; + this._parent = parent; + return container; + }, + + init: Base.noop, + exec: Base.noop, + + destroy: function() { + this._container && this._container.remove(); + this._parent && this._parent.removeClass('webuploader-container'); + this.off(); + } + }); + + Runtime.orders = 'html5,flash'; + + + /** + * 添加Runtime实现。 + * @param {String} type 类型 + * @param {Runtime} factory 具体Runtime实现。 + */ + Runtime.addRuntime = function( type, factory ) { + factories[ type ] = factory; + }; + + Runtime.hasRuntime = function( type ) { + return !!(type ? factories[ type ] : getFirstKey( factories )); + }; + + Runtime.create = function( opts, orders ) { + var type, runtime; + + orders = orders || Runtime.orders; + $.each( orders.split( /\s*,\s*/g ), function() { + if ( factories[ this ] ) { + type = this; + return false; + } + }); + + type = type || getFirstKey( factories ); + + if ( !type ) { + throw new Error('Runtime Error'); + } + + runtime = new factories[ type ]( opts ); + return runtime; + }; + + Mediator.installTo( Runtime.prototype ); + return Runtime; + }); + + /** + * @fileOverview Runtime管理器,负责Runtime的选择, 连接 + */ + define('runtime/client',[ + 'base', + 'mediator', + 'runtime/runtime' + ], function( Base, Mediator, Runtime ) { + + var cache; + + cache = (function() { + var obj = {}; + + return { + add: function( runtime ) { + obj[ runtime.uid ] = runtime; + }, + + get: function( ruid, standalone ) { + var i; + + if ( ruid ) { + return obj[ ruid ]; + } + + for ( i in obj ) { + // 有些类型不能重用,比如filepicker. + if ( standalone && obj[ i ].__standalone ) { + continue; + } + + return obj[ i ]; + } + + return null; + }, + + remove: function( runtime ) { + delete obj[ runtime.uid ]; + } + }; + })(); + + function RuntimeClient( component, standalone ) { + var deferred = Base.Deferred(), + runtime; + + this.uid = Base.guid('client_'); + + // 允许runtime没有初始化之前,注册一些方法在初始化后执行。 + this.runtimeReady = function( cb ) { + return deferred.done( cb ); + }; + + this.connectRuntime = function( opts, cb ) { + + // already connected. + if ( runtime ) { + throw new Error('already connected!'); + } + + deferred.done( cb ); + + if ( typeof opts === 'string' && cache.get( opts ) ) { + runtime = cache.get( opts ); + } + + // 像filePicker只能独立存在,不能公用。 + runtime = runtime || cache.get( null, standalone ); + + // 需要创建 + if ( !runtime ) { + runtime = Runtime.create( opts, opts.runtimeOrder ); + runtime.__promise = deferred.promise(); + runtime.once( 'ready', deferred.resolve ); + runtime.init(); + cache.add( runtime ); + runtime.__client = 1; + } else { + // 来自cache + Base.$.extend( runtime.options, opts ); + runtime.__promise.then( deferred.resolve ); + runtime.__client++; + } + + standalone && (runtime.__standalone = standalone); + return runtime; + }; + + this.getRuntime = function() { + return runtime; + }; + + this.disconnectRuntime = function() { + if ( !runtime ) { + return; + } + + runtime.__client--; + + if ( runtime.__client <= 0 ) { + cache.remove( runtime ); + delete runtime.__promise; + runtime.destroy(); + } + + runtime = null; + }; + + this.exec = function() { + if ( !runtime ) { + return; + } + + var args = Base.slice( arguments ); + component && args.unshift( component ); + + return runtime.exec.apply( this, args ); + }; + + this.getRuid = function() { + return runtime && runtime.uid; + }; + + this.destroy = (function( destroy ) { + return function() { + destroy && destroy.apply( this, arguments ); + this.trigger('destroy'); + this.off(); + this.exec('destroy'); + this.disconnectRuntime(); + }; + })( this.destroy ); + } + + Mediator.installTo( RuntimeClient.prototype ); + return RuntimeClient; + }); + /** + * @fileOverview Blob + */ + define('lib/blob',[ + 'base', + 'runtime/client' + ], function( Base, RuntimeClient ) { + + function Blob( ruid, source ) { + var me = this; + + me.source = source; + me.ruid = ruid; + this.size = source.size || 0; + + // 如果没有指定 mimetype, 但是知道文件后缀。 + if ( !source.type && this.ext && + ~'jpg,jpeg,png,gif,bmp'.indexOf( this.ext ) ) { + this.type = 'image/' + (this.ext === 'jpg' ? 'jpeg' : this.ext); + } else { + this.type = source.type || 'application/octet-stream'; + } + + RuntimeClient.call( me, 'Blob' ); + this.uid = source.uid || this.uid; + + if ( ruid ) { + me.connectRuntime( ruid ); + } + } + + Base.inherits( RuntimeClient, { + constructor: Blob, + + slice: function( start, end ) { + return this.exec( 'slice', start, end ); + }, + + getSource: function() { + return this.source; + } + }); + + return Blob; + }); + /** + * 为了统一化Flash的File和HTML5的File而存在。 + * 以至于要调用Flash里面的File,也可以像调用HTML5版本的File一下。 + * @fileOverview File + */ + define('lib/file',[ + 'base', + 'lib/blob' + ], function( Base, Blob ) { + + var uid = 1, + rExt = /\.([^.]+)$/; + + function File( ruid, file ) { + var ext; + + this.name = file.name || ('untitled' + uid++); + ext = rExt.exec( file.name ) ? RegExp.$1.toLowerCase() : ''; + + // todo 支持其他类型文件的转换。 + // 如果有 mimetype, 但是文件名里面没有找出后缀规律 + if ( !ext && file.type ) { + ext = /\/(jpg|jpeg|png|gif|bmp)$/i.exec( file.type ) ? + RegExp.$1.toLowerCase() : ''; + this.name += '.' + ext; + } + + this.ext = ext; + this.lastModifiedDate = file.lastModifiedDate || + (new Date()).toLocaleString(); + + Blob.apply( this, arguments ); + } + + return Base.inherits( Blob, File ); + }); + + /** + * @fileOverview 错误信息 + */ + define('lib/filepicker',[ + 'base', + 'runtime/client', + 'lib/file' + ], function( Base, RuntimeClent, File ) { + + var $ = Base.$; + + function FilePicker( opts ) { + opts = this.options = $.extend({}, FilePicker.options, opts ); + + opts.container = $( opts.id ); + + if ( !opts.container.length ) { + throw new Error('按钮指定错误'); + } + + opts.innerHTML = opts.innerHTML || opts.label || + opts.container.html() || ''; + + opts.button = $( opts.button || document.createElement('div') ); + opts.button.html( opts.innerHTML ); + opts.container.html( opts.button ); + + RuntimeClent.call( this, 'FilePicker', true ); + } + + FilePicker.options = { + button: null, + container: null, + label: null, + innerHTML: null, + multiple: true, + accept: null, + name: 'file' + }; + + Base.inherits( RuntimeClent, { + constructor: FilePicker, + + init: function() { + var me = this, + opts = me.options, + button = opts.button; + + button.addClass('webuploader-pick'); + + me.on( 'all', function( type ) { + var files; + + switch ( type ) { + case 'mouseenter': + button.addClass('webuploader-pick-hover'); + break; + + case 'mouseleave': + button.removeClass('webuploader-pick-hover'); + break; + + case 'change': + files = me.exec('getFiles'); + me.trigger( 'select', $.map( files, function( file ) { + file = new File( me.getRuid(), file ); + + // 记录来源。 + file._refer = opts.container; + return file; + }), opts.container ); + break; + } + }); + + me.connectRuntime( opts, function() { + me.refresh(); + me.exec( 'init', opts ); + me.trigger('ready'); + }); + + this._resizeHandler = Base.bindFn( this.refresh, this ); + $( window ).on( 'resize', this._resizeHandler ); + }, + + refresh: function() { + var shimContainer = this.getRuntime().getContainer(), + button = this.options.button, + width = button.outerWidth ? + button.outerWidth() : button.width(), + + height = button.outerHeight ? + button.outerHeight() : button.height(), + + pos = button.offset(); + + width && height && shimContainer.css({ + bottom: 'auto', + right: 'auto', + width: width + 'px', + height: height + 'px' + }).offset( pos ); + }, + + enable: function() { + var btn = this.options.button; + + btn.removeClass('webuploader-pick-disable'); + this.refresh(); + }, + + disable: function() { + var btn = this.options.button; + + this.getRuntime().getContainer().css({ + top: '-99999px' + }); + + btn.addClass('webuploader-pick-disable'); + }, + + destroy: function() { + var btn = this.options.button; + $( window ).off( 'resize', this._resizeHandler ); + btn.removeClass('webuploader-pick-disable webuploader-pick-hover ' + + 'webuploader-pick'); + } + }); + + return FilePicker; + }); + + /** + * @fileOverview 组件基类。 + */ + define('widgets/widget',[ + 'base', + 'uploader' + ], function( Base, Uploader ) { + + var $ = Base.$, + _init = Uploader.prototype._init, + _destroy = Uploader.prototype.destroy, + IGNORE = {}, + widgetClass = []; + + function isArrayLike( obj ) { + if ( !obj ) { + return false; + } + + var length = obj.length, + type = $.type( obj ); + + if ( obj.nodeType === 1 && length ) { + return true; + } + + return type === 'array' || type !== 'function' && type !== 'string' && + (length === 0 || typeof length === 'number' && length > 0 && + (length - 1) in obj); + } + + function Widget( uploader ) { + this.owner = uploader; + this.options = uploader.options; + } + + $.extend( Widget.prototype, { + + init: Base.noop, + + // 类Backbone的事件监听声明,监听uploader实例上的事件 + // widget直接无法监听事件,事件只能通过uploader来传递 + invoke: function( apiName, args ) { + + /* + { + 'make-thumb': 'makeThumb' + } + */ + var map = this.responseMap; + + // 如果无API响应声明则忽略 + if ( !map || !(apiName in map) || !(map[ apiName ] in this) || + !$.isFunction( this[ map[ apiName ] ] ) ) { + + return IGNORE; + } + + return this[ map[ apiName ] ].apply( this, args ); + + }, + + /** + * 发送命令。当传入`callback`或者`handler`中返回`promise`时。返回一个当所有`handler`中的promise都完成后完成的新`promise`。 + * @method request + * @grammar request( command, args ) => * | Promise + * @grammar request( command, args, callback ) => Promise + * @for Uploader + */ + request: function() { + return this.owner.request.apply( this.owner, arguments ); + } + }); + + // 扩展Uploader. + $.extend( Uploader.prototype, { + + /** + * @property {String | Array} [disableWidgets=undefined] + * @namespace options + * @for Uploader + * @description 默认所有 Uploader.register 了的 widget 都会被加载,如果禁用某一部分,请通过此 option 指定黑名单。 + */ + + // 覆写_init用来初始化widgets + _init: function() { + var me = this, + widgets = me._widgets = [], + deactives = me.options.disableWidgets || ''; + + $.each( widgetClass, function( _, klass ) { + (!deactives || !~deactives.indexOf( klass._name )) && + widgets.push( new klass( me ) ); + }); + + return _init.apply( me, arguments ); + }, + + request: function( apiName, args, callback ) { + var i = 0, + widgets = this._widgets, + len = widgets && widgets.length, + rlts = [], + dfds = [], + widget, rlt, promise, key; + + args = isArrayLike( args ) ? args : [ args ]; + + for ( ; i < len; i++ ) { + widget = widgets[ i ]; + rlt = widget.invoke( apiName, args ); + + if ( rlt !== IGNORE ) { + + // Deferred对象 + if ( Base.isPromise( rlt ) ) { + dfds.push( rlt ); + } else { + rlts.push( rlt ); + } + } + } + + // 如果有callback,则用异步方式。 + if ( callback || dfds.length ) { + promise = Base.when.apply( Base, dfds ); + key = promise.pipe ? 'pipe' : 'then'; + + // 很重要不能删除。删除了会死循环。 + // 保证执行顺序。让callback总是在下一个 tick 中执行。 + return promise[ key ](function() { + var deferred = Base.Deferred(), + args = arguments; + + if ( args.length === 1 ) { + args = args[ 0 ]; + } + + setTimeout(function() { + deferred.resolve( args ); + }, 1 ); + + return deferred.promise(); + })[ callback ? key : 'done' ]( callback || Base.noop ); + } else { + return rlts[ 0 ]; + } + }, + + destroy: function() { + _destroy.apply( this, arguments ); + this._widgets = null; + } + }); + + /** + * 添加组件 + * @grammar Uploader.register(proto); + * @grammar Uploader.register(map, proto); + * @param {object} responseMap API 名称与函数实现的映射 + * @param {object} proto 组件原型,构造函数通过 constructor 属性定义 + * @method Uploader.register + * @for Uploader + * @example + * Uploader.register({ + * 'make-thumb': 'makeThumb' + * }, { + * init: function( options ) {}, + * makeThumb: function() {} + * }); + * + * Uploader.register({ + * 'make-thumb': function() { + * + * } + * }); + */ + Uploader.register = Widget.register = function( responseMap, widgetProto ) { + var map = { init: 'init', destroy: 'destroy', name: 'anonymous' }, + klass; + + if ( arguments.length === 1 ) { + widgetProto = responseMap; + + // 自动生成 map 表。 + $.each(widgetProto, function(key) { + if ( key[0] === '_' || key === 'name' ) { + key === 'name' && (map.name = widgetProto.name); + return; + } + + map[key.replace(/[A-Z]/g, '-$&').toLowerCase()] = key; + }); + + } else { + map = $.extend( map, responseMap ); + } + + widgetProto.responseMap = map; + klass = Base.inherits( Widget, widgetProto ); + klass._name = map.name; + widgetClass.push( klass ); + + return klass; + }; + + /** + * 删除插件,只有在注册时指定了名字的才能被删除。 + * @grammar Uploader.unRegister(name); + * @param {string} name 组件名字 + * @method Uploader.unRegister + * @for Uploader + * @example + * + * Uploader.register({ + * name: 'custom', + * + * 'make-thumb': function() { + * + * } + * }); + * + * Uploader.unRegister('custom'); + */ + Uploader.unRegister = Widget.unRegister = function( name ) { + if ( !name || name === 'anonymous' ) { + return; + } + + // 删除指定的插件。 + for ( var i = widgetClass.length; i--; ) { + if ( widgetClass[i]._name === name ) { + widgetClass.splice(i, 1) + } + } + }; + + return Widget; + }); + /** + * @fileOverview 文件选择相关 + */ + define('widgets/filepicker',[ + 'base', + 'uploader', + 'lib/filepicker', + 'widgets/widget' + ], function( Base, Uploader, FilePicker ) { + var $ = Base.$; + + $.extend( Uploader.options, { + + /** + * @property {Selector | Object} [pick=undefined] + * @namespace options + * @for Uploader + * @description 指定选择文件的按钮容器,不指定则不创建按钮。 + * + * * `id` {Seletor|dom} 指定选择文件的按钮容器,不指定则不创建按钮。**注意** 这里虽然写的是 id, 但是不是只支持 id, 还支持 class, 或者 dom 节点。 + * * `label` {String} 请采用 `innerHTML` 代替 + * * `innerHTML` {String} 指定按钮文字。不指定时优先从指定的容器中看是否自带文字。 + * * `multiple` {Boolean} 是否开起同时选择多个文件能力。 + */ + pick: null, + + /** + * @property {Arroy} [accept=null] + * @namespace options + * @for Uploader + * @description 指定接受哪些类型的文件。 由于目前还有ext转mimeType表,所以这里需要分开指定。 + * + * * `title` {String} 文字描述 + * * `extensions` {String} 允许的文件后缀,不带点,多个用逗号分割。 + * * `mimeTypes` {String} 多个用逗号分割。 + * + * 如: + * + * ``` + * { + * title: 'Images', + * extensions: 'gif,jpg,jpeg,bmp,png', + * mimeTypes: 'image/*' + * } + * ``` + */ + accept: null/*{ + title: 'Images', + extensions: 'gif,jpg,jpeg,bmp,png', + mimeTypes: 'image/*' + }*/ + }); + + return Uploader.register({ + name: 'picker', + + init: function( opts ) { + this.pickers = []; + return opts.pick && this.addBtn( opts.pick ); + }, + + refresh: function() { + $.each( this.pickers, function() { + this.refresh(); + }); + }, + + /** + * @method addButton + * @for Uploader + * @grammar addButton( pick ) => Promise + * @description + * 添加文件选择按钮,如果一个按钮不够,需要调用此方法来添加。参数跟[options.pick](#WebUploader:Uploader:options)一致。 + * @example + * uploader.addButton({ + * id: '#btnContainer', + * innerHTML: '选择文件' + * }); + */ + addBtn: function( pick ) { + var me = this, + opts = me.options, + accept = opts.accept, + promises = []; + + if ( !pick ) { + return; + } + + $.isPlainObject( pick ) || (pick = { + id: pick + }); + + $( pick.id ).each(function() { + var options, picker, deferred; + + deferred = Base.Deferred(); + + options = $.extend({}, pick, { + accept: $.isPlainObject( accept ) ? [ accept ] : accept, + swf: opts.swf, + runtimeOrder: opts.runtimeOrder, + id: this + }); + + picker = new FilePicker( options ); + + picker.once( 'ready', deferred.resolve ); + picker.on( 'select', function( files ) { + me.owner.request( 'add-file', [ files ]); + }); + picker.init(); + + me.pickers.push( picker ); + + promises.push( deferred.promise() ); + }); + + return Base.when.apply( Base, promises ); + }, + + disable: function() { + $.each( this.pickers, function() { + this.disable(); + }); + }, + + enable: function() { + $.each( this.pickers, function() { + this.enable(); + }); + }, + + destroy: function() { + $.each( this.pickers, function() { + this.destroy(); + }); + this.pickers = null; + } + }); + }); + /** + * @fileOverview Image + */ + define('lib/image',[ + 'base', + 'runtime/client', + 'lib/blob' + ], function( Base, RuntimeClient, Blob ) { + var $ = Base.$; + + // 构造器。 + function Image( opts ) { + this.options = $.extend({}, Image.options, opts ); + RuntimeClient.call( this, 'Image' ); + + this.on( 'load', function() { + this._info = this.exec('info'); + this._meta = this.exec('meta'); + }); + } + + // 默认选项。 + Image.options = { + + // 默认的图片处理质量 + quality: 90, + + // 是否裁剪 + crop: false, + + // 是否保留头部信息 + preserveHeaders: false, + + // 是否允许放大。 + allowMagnify: false + }; + + // 继承RuntimeClient. + Base.inherits( RuntimeClient, { + constructor: Image, + + info: function( val ) { + + // setter + if ( val ) { + this._info = val; + return this; + } + + // getter + return this._info; + }, + + meta: function( val ) { + + // setter + if ( val ) { + this._meta = val; + return this; + } + + // getter + return this._meta; + }, + + loadFromBlob: function( blob ) { + var me = this, + ruid = blob.getRuid(); + + this.connectRuntime( ruid, function() { + me.exec( 'init', me.options ); + me.exec( 'loadFromBlob', blob ); + }); + }, + + resize: function() { + var args = Base.slice( arguments ); + return this.exec.apply( this, [ 'resize' ].concat( args ) ); + }, + + crop: function() { + var args = Base.slice( arguments ); + return this.exec.apply( this, [ 'crop' ].concat( args ) ); + }, + + getAsDataUrl: function( type ) { + return this.exec( 'getAsDataUrl', type ); + }, + + getAsBlob: function( type ) { + var blob = this.exec( 'getAsBlob', type ); + + return new Blob( this.getRuid(), blob ); + } + }); + + return Image; + }); + /** + * @fileOverview 图片操作, 负责预览图片和上传前压缩图片 + */ + define('widgets/image',[ + 'base', + 'uploader', + 'lib/image', + 'widgets/widget' + ], function( Base, Uploader, Image ) { + + var $ = Base.$, + throttle; + + // 根据要处理的文件大小来节流,一次不能处理太多,会卡。 + throttle = (function( max ) { + var occupied = 0, + waiting = [], + tick = function() { + var item; + + while ( waiting.length && occupied < max ) { + item = waiting.shift(); + occupied += item[ 0 ]; + item[ 1 ](); + } + }; + + return function( emiter, size, cb ) { + waiting.push([ size, cb ]); + emiter.once( 'destroy', function() { + occupied -= size; + setTimeout( tick, 1 ); + }); + setTimeout( tick, 1 ); + }; + })( 5 * 1024 * 1024 ); + + $.extend( Uploader.options, { + + /** + * @property {Object} [thumb] + * @namespace options + * @for Uploader + * @description 配置生成缩略图的选项。 + * + * 默认为: + * + * ```javascript + * { + * width: 110, + * height: 110, + * + * // 图片质量,只有type为`image/jpeg`的时候才有效。 + * quality: 70, + * + * // 是否允许放大,如果想要生成小图的时候不失真,此选项应该设置为false. + * allowMagnify: true, + * + * // 是否允许裁剪。 + * crop: true, + * + * // 为空的话则保留原有图片格式。 + * // 否则强制转换成指定的类型。 + * type: 'image/jpeg' + * } + * ``` + */ + thumb: { + width: 110, + height: 110, + quality: 70, + allowMagnify: true, + crop: true, + preserveHeaders: false, + + // 为空的话则保留原有图片格式。 + // 否则强制转换成指定的类型。 + // IE 8下面 base64 大小不能超过 32K 否则预览失败,而非 jpeg 编码的图片很可 + // 能会超过 32k, 所以这里设置成预览的时候都是 image/jpeg + type: 'image/jpeg' + }, + + /** + * @property {Object} [compress] + * @namespace options + * @for Uploader + * @description 配置压缩的图片的选项。如果此选项为`false`, 则图片在上传前不进行压缩。 + * + * 默认为: + * + * ```javascript + * { + * width: 1600, + * height: 1600, + * + * // 图片质量,只有type为`image/jpeg`的时候才有效。 + * quality: 90, + * + * // 是否允许放大,如果想要生成小图的时候不失真,此选项应该设置为false. + * allowMagnify: false, + * + * // 是否允许裁剪。 + * crop: false, + * + * // 是否保留头部meta信息。 + * preserveHeaders: true, + * + * // 如果发现压缩后文件大小比原来还大,则使用原来图片 + * // 此属性可能会影响图片自动纠正功能 + * noCompressIfLarger: false, + * + * // 单位字节,如果图片大小小于此值,不会采用压缩。 + * compressSize: 0 + * } + * ``` + */ + compress: { + width: 1600, + height: 1600, + quality: 90, + allowMagnify: false, + crop: false, + preserveHeaders: true + } + }); + + return Uploader.register({ + + name: 'image', + + + /** + * 生成缩略图,此过程为异步,所以需要传入`callback`。 + * 通常情况在图片加入队里后调用此方法来生成预览图以增强交互效果。 + * + * 当 width 或者 height 的值介于 0 - 1 时,被当成百分比使用。 + * + * `callback`中可以接收到两个参数。 + * * 第一个为error,如果生成缩略图有错误,此error将为真。 + * * 第二个为ret, 缩略图的Data URL值。 + * + * **注意** + * Date URL在IE6/7中不支持,所以不用调用此方法了,直接显示一张暂不支持预览图片好了。 + * 也可以借助服务端,将 base64 数据传给服务端,生成一个临时文件供预览。 + * + * @method makeThumb + * @grammar makeThumb( file, callback ) => undefined + * @grammar makeThumb( file, callback, width, height ) => undefined + * @for Uploader + * @example + * + * uploader.on( 'fileQueued', function( file ) { + * var $li = ...; + * + * uploader.makeThumb( file, function( error, ret ) { + * if ( error ) { + * $li.text('预览错误'); + * } else { + * $li.append(''); + * } + * }); + * + * }); + */ + makeThumb: function( file, cb, width, height ) { + var opts, image; + + file = this.request( 'get-file', file ); + + // 只预览图片格式。 + if ( !file.type.match( /^image/ ) ) { + cb( true ); + return; + } + + opts = $.extend({}, this.options.thumb ); + + // 如果传入的是object. + if ( $.isPlainObject( width ) ) { + opts = $.extend( opts, width ); + width = null; + } + + width = width || opts.width; + height = height || opts.height; + + image = new Image( opts ); + + image.once( 'load', function() { + file._info = file._info || image.info(); + file._meta = file._meta || image.meta(); + + // 如果 width 的值介于 0 - 1 + // 说明设置的是百分比。 + if ( width <= 1 && width > 0 ) { + width = file._info.width * width; + } + + // 同样的规则应用于 height + if ( height <= 1 && height > 0 ) { + height = file._info.height * height; + } + + image.resize( width, height ); + }); + + // 当 resize 完后 + image.once( 'complete', function() { + cb( false, image.getAsDataUrl( opts.type ) ); + image.destroy(); + }); + + image.once( 'error', function( reason ) { + cb( reason || true ); + image.destroy(); + }); + + throttle( image, file.source.size, function() { + file._info && image.info( file._info ); + file._meta && image.meta( file._meta ); + image.loadFromBlob( file.source ); + }); + }, + + beforeSendFile: function( file ) { + var opts = this.options.compress || this.options.resize, + compressSize = opts && opts.compressSize || 0, + noCompressIfLarger = opts && opts.noCompressIfLarger || false, + image, deferred; + + file = this.request( 'get-file', file ); + + // 只压缩 jpeg 图片格式。 + // gif 可能会丢失针 + // bmp png 基本上尺寸都不大,且压缩比比较小。 + if ( !opts || !~'image/jpeg,image/jpg'.indexOf( file.type ) || + file.size < compressSize || + file._compressed ) { + return; + } + + opts = $.extend({}, opts ); + deferred = Base.Deferred(); + + image = new Image( opts ); + + deferred.always(function() { + image.destroy(); + image = null; + }); + image.once( 'error', deferred.reject ); + image.once( 'load', function() { + var width = opts.width, + height = opts.height; + + file._info = file._info || image.info(); + file._meta = file._meta || image.meta(); + + // 如果 width 的值介于 0 - 1 + // 说明设置的是百分比。 + if ( width <= 1 && width > 0 ) { + width = file._info.width * width; + } + + // 同样的规则应用于 height + if ( height <= 1 && height > 0 ) { + height = file._info.height * height; + } + + image.resize( width, height ); + }); + + image.once( 'complete', function() { + var blob, size; + + // 移动端 UC / qq 浏览器的无图模式下 + // ctx.getImageData 处理大图的时候会报 Exception + // INDEX_SIZE_ERR: DOM Exception 1 + try { + blob = image.getAsBlob( opts.type ); + + size = file.size; + + // 如果压缩后,比原来还大则不用压缩后的。 + if ( !noCompressIfLarger || blob.size < size ) { + // file.source.destroy && file.source.destroy(); + file.source = blob; + file.size = blob.size; + + file.trigger( 'resize', blob.size, size ); + } + + // 标记,避免重复压缩。 + file._compressed = true; + deferred.resolve(); + } catch ( e ) { + // 出错了直接继续,让其上传原始图片 + deferred.resolve(); + } + }); + + file._info && image.info( file._info ); + file._meta && image.meta( file._meta ); + + image.loadFromBlob( file.source ); + return deferred.promise(); + } + }); + }); + /** + * @fileOverview 文件属性封装 + */ + define('file',[ + 'base', + 'mediator' + ], function( Base, Mediator ) { + + var $ = Base.$, + idPrefix = 'WU_FILE_', + idSuffix = 0, + rExt = /\.([^.]+)$/, + statusMap = {}; + + function gid() { + return idPrefix + idSuffix++; + } + + /** + * 文件类 + * @class File + * @constructor 构造函数 + * @grammar new File( source ) => File + * @param {Lib.File} source [lib.File](#Lib.File)实例, 此source对象是带有Runtime信息的。 + */ + function WUFile( source ) { + + /** + * 文件名,包括扩展名(后缀) + * @property name + * @type {string} + */ + this.name = source.name || 'Untitled'; + + /** + * 文件体积(字节) + * @property size + * @type {uint} + * @default 0 + */ + this.size = source.size || 0; + + /** + * 文件MIMETYPE类型,与文件类型的对应关系请参考[http://t.cn/z8ZnFny](http://t.cn/z8ZnFny) + * @property type + * @type {string} + * @default 'application/octet-stream' + */ + this.type = source.type || 'application/octet-stream'; + + /** + * 文件最后修改日期 + * @property lastModifiedDate + * @type {int} + * @default 当前时间戳 + */ + this.lastModifiedDate = source.lastModifiedDate || (new Date() * 1); + + /** + * 文件ID,每个对象具有唯一ID,与文件名无关 + * @property id + * @type {string} + */ + this.id = gid(); + + /** + * 文件扩展名,通过文件名获取,例如test.png的扩展名为png + * @property ext + * @type {string} + */ + this.ext = rExt.exec( this.name ) ? RegExp.$1 : ''; + + + /** + * 状态文字说明。在不同的status语境下有不同的用途。 + * @property statusText + * @type {string} + */ + this.statusText = ''; + + // 存储文件状态,防止通过属性直接修改 + statusMap[ this.id ] = WUFile.Status.INITED; + + this.source = source; + this.loaded = 0; + + this.on( 'error', function( msg ) { + this.setStatus( WUFile.Status.ERROR, msg ); + }); + } + + $.extend( WUFile.prototype, { + + /** + * 设置状态,状态变化时会触发`change`事件。 + * @method setStatus + * @grammar setStatus( status[, statusText] ); + * @param {File.Status|String} status [文件状态值](#WebUploader:File:File.Status) + * @param {String} [statusText=''] 状态说明,常在error时使用,用http, abort,server等来标记是由于什么原因导致文件错误。 + */ + setStatus: function( status, text ) { + + var prevStatus = statusMap[ this.id ]; + + typeof text !== 'undefined' && (this.statusText = text); + + if ( status !== prevStatus ) { + statusMap[ this.id ] = status; + /** + * 文件状态变化 + * @event statuschange + */ + this.trigger( 'statuschange', status, prevStatus ); + } + + }, + + /** + * 获取文件状态 + * @return {File.Status} + * @example + 文件状态具体包括以下几种类型: + { + // 初始化 + INITED: 0, + // 已入队列 + QUEUED: 1, + // 正在上传 + PROGRESS: 2, + // 上传出错 + ERROR: 3, + // 上传成功 + COMPLETE: 4, + // 上传取消 + CANCELLED: 5 + } + */ + getStatus: function() { + return statusMap[ this.id ]; + }, + + /** + * 获取文件原始信息。 + * @return {*} + */ + getSource: function() { + return this.source; + }, + + destroy: function() { + this.off(); + delete statusMap[ this.id ]; + } + }); + + Mediator.installTo( WUFile.prototype ); + + /** + * 文件状态值,具体包括以下几种类型: + * * `inited` 初始状态 + * * `queued` 已经进入队列, 等待上传 + * * `progress` 上传中 + * * `complete` 上传完成。 + * * `error` 上传出错,可重试 + * * `interrupt` 上传中断,可续传。 + * * `invalid` 文件不合格,不能重试上传。会自动从队列中移除。 + * * `cancelled` 文件被移除。 + * @property {Object} Status + * @namespace File + * @class File + * @static + */ + WUFile.Status = { + INITED: 'inited', // 初始状态 + QUEUED: 'queued', // 已经进入队列, 等待上传 + PROGRESS: 'progress', // 上传中 + ERROR: 'error', // 上传出错,可重试 + COMPLETE: 'complete', // 上传完成。 + CANCELLED: 'cancelled', // 上传取消。 + INTERRUPT: 'interrupt', // 上传中断,可续传。 + INVALID: 'invalid' // 文件不合格,不能重试上传。 + }; + + return WUFile; + }); + + /** + * @fileOverview 文件队列 + */ + define('queue',[ + 'base', + 'mediator', + 'file' + ], function( Base, Mediator, WUFile ) { + + var $ = Base.$, + STATUS = WUFile.Status; + + /** + * 文件队列, 用来存储各个状态中的文件。 + * @class Queue + * @extends Mediator + */ + function Queue() { + + /** + * 统计文件数。 + * * `numOfQueue` 队列中的文件数。 + * * `numOfSuccess` 上传成功的文件数 + * * `numOfCancel` 被取消的文件数 + * * `numOfProgress` 正在上传中的文件数 + * * `numOfUploadFailed` 上传错误的文件数。 + * * `numOfInvalid` 无效的文件数。 + * * `numofDeleted` 被移除的文件数。 + * @property {Object} stats + */ + this.stats = { + numOfQueue: 0, + numOfSuccess: 0, + numOfCancel: 0, + numOfProgress: 0, + numOfUploadFailed: 0, + numOfInvalid: 0, + numofDeleted: 0, + numofInterrupt: 0 + }; + + // 上传队列,仅包括等待上传的文件 + this._queue = []; + + // 存储所有文件 + this._map = {}; + } + + $.extend( Queue.prototype, { + + /** + * 将新文件加入对队列尾部 + * + * @method append + * @param {File} file 文件对象 + */ + append: function( file ) { + this._queue.push( file ); + this._fileAdded( file ); + return this; + }, + + /** + * 将新文件加入对队列头部 + * + * @method prepend + * @param {File} file 文件对象 + */ + prepend: function( file ) { + this._queue.unshift( file ); + this._fileAdded( file ); + return this; + }, + + /** + * 获取文件对象 + * + * @method getFile + * @param {String} fileId 文件ID + * @return {File} + */ + getFile: function( fileId ) { + if ( typeof fileId !== 'string' ) { + return fileId; + } + return this._map[ fileId ]; + }, + + /** + * 从队列中取出一个指定状态的文件。 + * @grammar fetch( status ) => File + * @method fetch + * @param {String} status [文件状态值](#WebUploader:File:File.Status) + * @return {File} [File](#WebUploader:File) + */ + fetch: function( status ) { + var len = this._queue.length, + i, file; + + status = status || STATUS.QUEUED; + + for ( i = 0; i < len; i++ ) { + file = this._queue[ i ]; + + if ( status === file.getStatus() ) { + return file; + } + } + + return null; + }, + + /** + * 对队列进行排序,能够控制文件上传顺序。 + * @grammar sort( fn ) => undefined + * @method sort + * @param {Function} fn 排序方法 + */ + sort: function( fn ) { + if ( typeof fn === 'function' ) { + this._queue.sort( fn ); + } + }, + + /** + * 获取指定类型的文件列表, 列表中每一个成员为[File](#WebUploader:File)对象。 + * @grammar getFiles( [status1[, status2 ...]] ) => Array + * @method getFiles + * @param {String} [status] [文件状态值](#WebUploader:File:File.Status) + */ + getFiles: function() { + var sts = [].slice.call( arguments, 0 ), + ret = [], + i = 0, + len = this._queue.length, + file; + + for ( ; i < len; i++ ) { + file = this._queue[ i ]; + + if ( sts.length && !~$.inArray( file.getStatus(), sts ) ) { + continue; + } + + ret.push( file ); + } + + return ret; + }, + + /** + * 在队列中删除文件。 + * @grammar removeFile( file ) => Array + * @method removeFile + * @param {File} 文件对象。 + */ + removeFile: function( file ) { + var me = this, + existing = this._map[ file.id ]; + + if ( existing ) { + delete this._map[ file.id ]; + file.destroy(); + this.stats.numofDeleted++; + } + }, + + _fileAdded: function( file ) { + var me = this, + existing = this._map[ file.id ]; + + if ( !existing ) { + this._map[ file.id ] = file; + + file.on( 'statuschange', function( cur, pre ) { + me._onFileStatusChange( cur, pre ); + }); + } + }, + + _onFileStatusChange: function( curStatus, preStatus ) { + var stats = this.stats; + + switch ( preStatus ) { + case STATUS.PROGRESS: + stats.numOfProgress--; + break; + + case STATUS.QUEUED: + stats.numOfQueue --; + break; + + case STATUS.ERROR: + stats.numOfUploadFailed--; + break; + + case STATUS.INVALID: + stats.numOfInvalid--; + break; + + case STATUS.INTERRUPT: + stats.numofInterrupt--; + break; + } + + switch ( curStatus ) { + case STATUS.QUEUED: + stats.numOfQueue++; + break; + + case STATUS.PROGRESS: + stats.numOfProgress++; + break; + + case STATUS.ERROR: + stats.numOfUploadFailed++; + break; + + case STATUS.COMPLETE: + stats.numOfSuccess++; + break; + + case STATUS.CANCELLED: + stats.numOfCancel++; + break; + + + case STATUS.INVALID: + stats.numOfInvalid++; + break; + + case STATUS.INTERRUPT: + stats.numofInterrupt++; + break; + } + } + + }); + + Mediator.installTo( Queue.prototype ); + + return Queue; + }); + /** + * @fileOverview 队列 + */ + define('widgets/queue',[ + 'base', + 'uploader', + 'queue', + 'file', + 'lib/file', + 'runtime/client', + 'widgets/widget' + ], function( Base, Uploader, Queue, WUFile, File, RuntimeClient ) { + + var $ = Base.$, + rExt = /\.\w+$/, + Status = WUFile.Status; + + return Uploader.register({ + name: 'queue', + + init: function( opts ) { + var me = this, + deferred, len, i, item, arr, accept, runtime; + + if ( $.isPlainObject( opts.accept ) ) { + opts.accept = [ opts.accept ]; + } + + // accept中的中生成匹配正则。 + if ( opts.accept ) { + arr = []; + + for ( i = 0, len = opts.accept.length; i < len; i++ ) { + item = opts.accept[ i ].extensions; + item && arr.push( item ); + } + + if ( arr.length ) { + accept = '\\.' + arr.join(',') + .replace( /,/g, '$|\\.' ) + .replace( /\*/g, '.*' ) + '$'; + } + + me.accept = new RegExp( accept, 'i' ); + } + + me.queue = new Queue(); + me.stats = me.queue.stats; + + // 如果当前不是html5运行时,那就算了。 + // 不执行后续操作 + if ( this.request('predict-runtime-type') !== 'html5' ) { + return; + } + + // 创建一个 html5 运行时的 placeholder + // 以至于外部添加原生 File 对象的时候能正确包裹一下供 webuploader 使用。 + deferred = Base.Deferred(); + this.placeholder = runtime = new RuntimeClient('Placeholder'); + runtime.connectRuntime({ + runtimeOrder: 'html5' + }, function() { + me._ruid = runtime.getRuid(); + deferred.resolve(); + }); + return deferred.promise(); + }, + + + // 为了支持外部直接添加一个原生File对象。 + _wrapFile: function( file ) { + if ( !(file instanceof WUFile) ) { + + if ( !(file instanceof File) ) { + if ( !this._ruid ) { + throw new Error('Can\'t add external files.'); + } + file = new File( this._ruid, file ); + } + + file = new WUFile( file ); + } + + return file; + }, + + // 判断文件是否可以被加入队列 + acceptFile: function( file ) { + var invalid = !file || !file.size || this.accept && + + // 如果名字中有后缀,才做后缀白名单处理。 + rExt.exec( file.name ) && !this.accept.test( file.name ); + + return !invalid; + }, + + + /** + * @event beforeFileQueued + * @param {File} file File对象 + * @description 当文件被加入队列之前触发,此事件的handler返回值为`false`,则此文件不会被添加进入队列。 + * @for Uploader + */ + + /** + * @event fileQueued + * @param {File} file File对象 + * @description 当文件被加入队列以后触发。 + * @for Uploader + */ + + _addFile: function( file ) { + var me = this; + + file = me._wrapFile( file ); + + // 不过类型判断允许不允许,先派送 `beforeFileQueued` + if ( !me.owner.trigger( 'beforeFileQueued', file ) ) { + return; + } + + // 类型不匹配,则派送错误事件,并返回。 + if ( !me.acceptFile( file ) ) { + me.owner.trigger( 'error', 'Q_TYPE_DENIED', file ); + return; + } + + me.queue.append( file ); + me.owner.trigger( 'fileQueued', file ); + return file; + }, + + getFile: function( fileId ) { + return this.queue.getFile( fileId ); + }, + + /** + * @event filesQueued + * @param {File} files 数组,内容为原始File(lib/File)对象。 + * @description 当一批文件添加进队列以后触发。 + * @for Uploader + */ + + /** + * @property {Boolean} [auto=false] + * @namespace options + * @for Uploader + * @description 设置为 true 后,不需要手动调用上传,有文件选择即开始上传。 + * + */ + + /** + * @method addFiles + * @grammar addFiles( file ) => undefined + * @grammar addFiles( [file1, file2 ...] ) => undefined + * @param {Array of File or File} [files] Files 对象 数组 + * @description 添加文件到队列 + * @for Uploader + */ + addFile: function( files ) { + var me = this; + + if ( !files.length ) { + files = [ files ]; + } + + files = $.map( files, function( file ) { + return me._addFile( file ); + }); + + me.owner.trigger( 'filesQueued', files ); + + if ( me.options.auto ) { + setTimeout(function() { + me.request('start-upload'); + }, 20 ); + } + }, + + getStats: function() { + return this.stats; + }, + + /** + * @event fileDequeued + * @param {File} file File对象 + * @description 当文件被移除队列后触发。 + * @for Uploader + */ + + /** + * @method removeFile + * @grammar removeFile( file ) => undefined + * @grammar removeFile( id ) => undefined + * @grammar removeFile( file, true ) => undefined + * @grammar removeFile( id, true ) => undefined + * @param {File|id} file File对象或这File对象的id + * @description 移除某一文件, 默认只会标记文件状态为已取消,如果第二个参数为 `true` 则会从 queue 中移除。 + * @for Uploader + * @example + * + * $li.on('click', '.remove-this', function() { + * uploader.removeFile( file ); + * }) + */ + removeFile: function( file, remove ) { + var me = this; + + file = file.id ? file : me.queue.getFile( file ); + + this.request( 'cancel-file', file ); + + if ( remove ) { + this.queue.removeFile( file ); + } + }, + + /** + * @method getFiles + * @grammar getFiles() => Array + * @grammar getFiles( status1, status2, status... ) => Array + * @description 返回指定状态的文件集合,不传参数将返回所有状态的文件。 + * @for Uploader + * @example + * console.log( uploader.getFiles() ); // => all files + * console.log( uploader.getFiles('error') ) // => all error files. + */ + getFiles: function() { + return this.queue.getFiles.apply( this.queue, arguments ); + }, + + fetchFile: function() { + return this.queue.fetch.apply( this.queue, arguments ); + }, + + /** + * @method retry + * @grammar retry() => undefined + * @grammar retry( file ) => undefined + * @description 重试上传,重试指定文件,或者从出错的文件开始重新上传。 + * @for Uploader + * @example + * function retry() { + * uploader.retry(); + * } + */ + retry: function( file, noForceStart ) { + var me = this, + files, i, len; + + if ( file ) { + file = file.id ? file : me.queue.getFile( file ); + file.setStatus( Status.QUEUED ); + noForceStart || me.request('start-upload'); + return; + } + + files = me.queue.getFiles( Status.ERROR ); + i = 0; + len = files.length; + + for ( ; i < len; i++ ) { + file = files[ i ]; + file.setStatus( Status.QUEUED ); + } + + me.request('start-upload'); + }, + + /** + * @method sort + * @grammar sort( fn ) => undefined + * @description 排序队列中的文件,在上传之前调整可以控制上传顺序。 + * @for Uploader + */ + sortFiles: function() { + return this.queue.sort.apply( this.queue, arguments ); + }, + + /** + * @event reset + * @description 当 uploader 被重置的时候触发。 + * @for Uploader + */ + + /** + * @method reset + * @grammar reset() => undefined + * @description 重置uploader。目前只重置了队列。 + * @for Uploader + * @example + * uploader.reset(); + */ + reset: function() { + this.owner.trigger('reset'); + this.queue = new Queue(); + this.stats = this.queue.stats; + }, + + destroy: function() { + this.reset(); + this.placeholder && this.placeholder.destroy(); + } + }); + + }); + /** + * @fileOverview 添加获取Runtime相关信息的方法。 + */ + define('widgets/runtime',[ + 'uploader', + 'runtime/runtime', + 'widgets/widget' + ], function( Uploader, Runtime ) { + + Uploader.support = function() { + return Runtime.hasRuntime.apply( Runtime, arguments ); + }; + + /** + * @property {Object} [runtimeOrder=html5,flash] + * @namespace options + * @for Uploader + * @description 指定运行时启动顺序。默认会想尝试 html5 是否支持,如果支持则使用 html5, 否则则使用 flash. + * + * 可以将此值设置成 `flash`,来强制使用 flash 运行时。 + */ + + return Uploader.register({ + name: 'runtime', + + init: function() { + if ( !this.predictRuntimeType() ) { + throw Error('Runtime Error'); + } + }, + + /** + * 预测Uploader将采用哪个`Runtime` + * @grammar predictRuntimeType() => String + * @method predictRuntimeType + * @for Uploader + */ + predictRuntimeType: function() { + var orders = this.options.runtimeOrder || Runtime.orders, + type = this.type, + i, len; + + if ( !type ) { + orders = orders.split( /\s*,\s*/g ); + + for ( i = 0, len = orders.length; i < len; i++ ) { + if ( Runtime.hasRuntime( orders[ i ] ) ) { + this.type = type = orders[ i ]; + break; + } + } + } + + return type; + } + }); + }); + /** + * @fileOverview Transport + */ + define('lib/transport',[ + 'base', + 'runtime/client', + 'mediator' + ], function( Base, RuntimeClient, Mediator ) { + + var $ = Base.$; + + function Transport( opts ) { + var me = this; + + opts = me.options = $.extend( true, {}, Transport.options, opts || {} ); + RuntimeClient.call( this, 'Transport' ); + + this._blob = null; + this._formData = opts.formData || {}; + this._headers = opts.headers || {}; + + this.on( 'progress', this._timeout ); + this.on( 'load error', function() { + me.trigger( 'progress', 1 ); + clearTimeout( me._timer ); + }); + } + + Transport.options = { + server: '', + method: 'POST', + + // 跨域时,是否允许携带cookie, 只有html5 runtime才有效 + withCredentials: false, + fileVal: 'file', + timeout: 2 * 60 * 1000, // 2分钟 + formData: {}, + headers: {}, + sendAsBinary: false + }; + + $.extend( Transport.prototype, { + + // 添加Blob, 只能添加一次,最后一次有效。 + appendBlob: function( key, blob, filename ) { + var me = this, + opts = me.options; + + if ( me.getRuid() ) { + me.disconnectRuntime(); + } + + // 连接到blob归属的同一个runtime. + me.connectRuntime( blob.ruid, function() { + me.exec('init'); + }); + + me._blob = blob; + opts.fileVal = key || opts.fileVal; + opts.filename = filename || opts.filename; + }, + + // 添加其他字段 + append: function( key, value ) { + if ( typeof key === 'object' ) { + $.extend( this._formData, key ); + } else { + this._formData[ key ] = value; + } + }, + + setRequestHeader: function( key, value ) { + if ( typeof key === 'object' ) { + $.extend( this._headers, key ); + } else { + this._headers[ key ] = value; + } + }, + + send: function( method ) { + this.exec( 'send', method ); + this._timeout(); + }, + + abort: function() { + clearTimeout( this._timer ); + return this.exec('abort'); + }, + + destroy: function() { + this.trigger('destroy'); + this.off(); + this.exec('destroy'); + this.disconnectRuntime(); + }, + + getResponse: function() { + return this.exec('getResponse'); + }, + + getResponseAsJson: function() { + return this.exec('getResponseAsJson'); + }, + + getStatus: function() { + return this.exec('getStatus'); + }, + + _timeout: function() { + var me = this, + duration = me.options.timeout; + + if ( !duration ) { + return; + } + + clearTimeout( me._timer ); + me._timer = setTimeout(function() { + me.abort(); + me.trigger( 'error', 'timeout' ); + }, duration ); + } + + }); + + // 让Transport具备事件功能。 + Mediator.installTo( Transport.prototype ); + + return Transport; + }); + /** + * @fileOverview 负责文件上传相关。 + */ + define('widgets/upload',[ + 'base', + 'uploader', + 'file', + 'lib/transport', + 'widgets/widget' + ], function( Base, Uploader, WUFile, Transport ) { + + var $ = Base.$, + isPromise = Base.isPromise, + Status = WUFile.Status; + + // 添加默认配置项 + $.extend( Uploader.options, { + + + /** + * @property {Boolean} [prepareNextFile=false] + * @namespace options + * @for Uploader + * @description 是否允许在文件传输时提前把下一个文件准备好。 + * 对于一个文件的准备工作比较耗时,比如图片压缩,md5序列化。 + * 如果能提前在当前文件传输期处理,可以节省总体耗时。 + */ + prepareNextFile: false, + + /** + * @property {Boolean} [chunked=false] + * @namespace options + * @for Uploader + * @description 是否要分片处理大文件上传。 + */ + chunked: false, + + /** + * @property {Boolean} [chunkSize=5242880] + * @namespace options + * @for Uploader + * @description 如果要分片,分多大一片? 默认大小为5M. + */ + chunkSize: 5 * 1024 * 1024, + + /** + * @property {Boolean} [chunkRetry=2] + * @namespace options + * @for Uploader + * @description 如果某个分片由于网络问题出错,允许自动重传多少次? + */ + chunkRetry: 2, + + /** + * @property {Boolean} [threads=3] + * @namespace options + * @for Uploader + * @description 上传并发数。允许同时最大上传进程数。 + */ + threads: 3, + + + /** + * @property {Object} [formData={}] + * @namespace options + * @for Uploader + * @description 文件上传请求的参数表,每次发送都会发送此对象中的参数。 + */ + formData: {} + + /** + * @property {Object} [fileVal='file'] + * @namespace options + * @for Uploader + * @description 设置文件上传域的name。 + */ + + /** + * @property {Object} [method='POST'] + * @namespace options + * @for Uploader + * @description 文件上传方式,`POST`或者`GET`。 + */ + + /** + * @property {Object} [sendAsBinary=false] + * @namespace options + * @for Uploader + * @description 是否已二进制的流的方式发送文件,这样整个上传内容`php://input`都为文件内容, + * 其他参数在$_GET数组中。 + */ + }); + + // 负责将文件切片。 + function CuteFile( file, chunkSize ) { + var pending = [], + blob = file.source, + total = blob.size, + chunks = chunkSize ? Math.ceil( total / chunkSize ) : 1, + start = 0, + index = 0, + len, api; + + api = { + file: file, + + has: function() { + return !!pending.length; + }, + + shift: function() { + return pending.shift(); + }, + + unshift: function( block ) { + pending.unshift( block ); + } + }; + + while ( index < chunks ) { + len = Math.min( chunkSize, total - start ); + + pending.push({ + file: file, + start: start, + end: chunkSize ? (start + len) : total, + total: total, + chunks: chunks, + chunk: index++, + cuted: api + }); + start += len; + } + + file.blocks = pending.concat(); + file.remaning = pending.length; + + return api; + } + + Uploader.register({ + name: 'upload', + + init: function() { + var owner = this.owner, + me = this; + + this.runing = false; + this.progress = false; + + owner + .on( 'startUpload', function() { + me.progress = true; + }) + .on( 'uploadFinished', function() { + me.progress = false; + }); + + // 记录当前正在传的数据,跟threads相关 + this.pool = []; + + // 缓存分好片的文件。 + this.stack = []; + + // 缓存即将上传的文件。 + this.pending = []; + + // 跟踪还有多少分片在上传中但是没有完成上传。 + this.remaning = 0; + this.__tick = Base.bindFn( this._tick, this ); + + owner.on( 'uploadComplete', function( file ) { + + // 把其他块取消了。 + file.blocks && $.each( file.blocks, function( _, v ) { + v.transport && (v.transport.abort(), v.transport.destroy()); + delete v.transport; + }); + + delete file.blocks; + delete file.remaning; + }); + }, + + reset: function() { + this.request( 'stop-upload', true ); + this.runing = false; + this.pool = []; + this.stack = []; + this.pending = []; + this.remaning = 0; + this._trigged = false; + this._promise = null; + }, + + /** + * @event startUpload + * @description 当开始上传流程时触发。 + * @for Uploader + */ + + /** + * 开始上传。此方法可以从初始状态调用开始上传流程,也可以从暂停状态调用,继续上传流程。 + * + * 可以指定开始某一个文件。 + * @grammar upload() => undefined + * @grammar upload( file | fileId) => undefined + * @method upload + * @for Uploader + */ + startUpload: function(file) { + var me = this; + + // 移出invalid的文件 + $.each( me.request( 'get-files', Status.INVALID ), function() { + me.request( 'remove-file', this ); + }); + + // 如果指定了开始某个文件,则只开始指定文件。 + if ( file ) { + file = file.id ? file : me.request( 'get-file', file ); + + if (file.getStatus() === Status.INTERRUPT) { + $.each( me.pool, function( _, v ) { + + // 之前暂停过。 + if (v.file !== file) { + return; + } + + v.transport && v.transport.send(); + }); + + file.setStatus( Status.QUEUED ); + } else if (file.getStatus() === Status.PROGRESS) { + return; + } else { + file.setStatus( Status.QUEUED ); + } + } else { + $.each( me.request( 'get-files', [ Status.INITED ] ), function() { + this.setStatus( Status.QUEUED ); + }); + } + + if ( me.runing ) { + return; + } + + me.runing = true; + + var files = []; + + // 如果有暂停的,则续传 + $.each( me.pool, function( _, v ) { + var file = v.file; + + if ( file.getStatus() === Status.INTERRUPT ) { + files.push(file); + me._trigged = false; + v.transport && v.transport.send(); + } + }); + + var file; + while ( (file = files.shift()) ) { + file.setStatus( Status.PROGRESS ); + } + + file || $.each( me.request( 'get-files', + Status.INTERRUPT ), function() { + this.setStatus( Status.PROGRESS ); + }); + + me._trigged = false; + Base.nextTick( me.__tick ); + me.owner.trigger('startUpload'); + }, + + /** + * @event stopUpload + * @description 当开始上传流程暂停时触发。 + * @for Uploader + */ + + /** + * 暂停上传。第一个参数为是否中断上传当前正在上传的文件。 + * + * 如果第一个参数是文件,则只暂停指定文件。 + * @grammar stop() => undefined + * @grammar stop( true ) => undefined + * @grammar stop( file ) => undefined + * @method stop + * @for Uploader + */ + stopUpload: function( file, interrupt ) { + var me = this; + + if (file === true) { + interrupt = file; + file = null; + } + + if ( me.runing === false ) { + return; + } + + // 如果只是暂停某个文件。 + if ( file ) { + file = file.id ? file : me.request( 'get-file', file ); + + if ( file.getStatus() !== Status.PROGRESS && + file.getStatus() !== Status.QUEUED ) { + return; + } + + file.setStatus( Status.INTERRUPT ); + $.each( me.pool, function( _, v ) { + + // 只 abort 指定的文件。 + if (v.file !== file) { + return; + } + + v.transport && v.transport.abort(); + me._putback(v); + me._popBlock(v); + }); + + return Base.nextTick( me.__tick ); + } + + me.runing = false; + + if (this._promise && this._promise.file) { + this._promise.file.setStatus( Status.INTERRUPT ); + } + + interrupt && $.each( me.pool, function( _, v ) { + v.transport && v.transport.abort(); + v.file.setStatus( Status.INTERRUPT ); + }); + + me.owner.trigger('stopUpload'); + }, + + /** + * @method cancelFile + * @grammar cancelFile( file ) => undefined + * @grammar cancelFile( id ) => undefined + * @param {File|id} file File对象或这File对象的id + * @description 标记文件状态为已取消, 同时将中断文件传输。 + * @for Uploader + * @example + * + * $li.on('click', '.remove-this', function() { + * uploader.cancelFile( file ); + * }) + */ + cancelFile: function( file ) { + file = file.id ? file : this.request( 'get-file', file ); + + // 如果正在上传。 + file.blocks && $.each( file.blocks, function( _, v ) { + var _tr = v.transport; + + if ( _tr ) { + _tr.abort(); + _tr.destroy(); + delete v.transport; + } + }); + + file.setStatus( Status.CANCELLED ); + this.owner.trigger( 'fileDequeued', file ); + }, + + /** + * 判断`Uplaode`r是否正在上传中。 + * @grammar isInProgress() => Boolean + * @method isInProgress + * @for Uploader + */ + isInProgress: function() { + return !!this.progress; + }, + + _getStats: function() { + return this.request('get-stats'); + }, + + /** + * 掉过一个文件上传,直接标记指定文件为已上传状态。 + * @grammar skipFile( file ) => undefined + * @method skipFile + * @for Uploader + */ + skipFile: function( file, status ) { + file = file.id ? file : this.request( 'get-file', file ); + + file.setStatus( status || Status.COMPLETE ); + file.skipped = true; + + // 如果正在上传。 + file.blocks && $.each( file.blocks, function( _, v ) { + var _tr = v.transport; + + if ( _tr ) { + _tr.abort(); + _tr.destroy(); + delete v.transport; + } + }); + + this.owner.trigger( 'uploadSkip', file ); + }, + + /** + * @event uploadFinished + * @description 当所有文件上传结束时触发。 + * @for Uploader + */ + _tick: function() { + var me = this, + opts = me.options, + fn, val; + + // 上一个promise还没有结束,则等待完成后再执行。 + if ( me._promise ) { + return me._promise.always( me.__tick ); + } + + // 还有位置,且还有文件要处理的话。 + if ( me.pool.length < opts.threads && (val = me._nextBlock()) ) { + me._trigged = false; + + fn = function( val ) { + me._promise = null; + + // 有可能是reject过来的,所以要检测val的类型。 + val && val.file && me._startSend( val ); + Base.nextTick( me.__tick ); + }; + + me._promise = isPromise( val ) ? val.always( fn ) : fn( val ); + + // 没有要上传的了,且没有正在传输的了。 + } else if ( !me.remaning && !me._getStats().numOfQueue && + !me._getStats().numofInterrupt ) { + me.runing = false; + + me._trigged || Base.nextTick(function() { + me.owner.trigger('uploadFinished'); + }); + me._trigged = true; + } + }, + + _putback: function(block) { + var idx; + + block.cuted.unshift(block); + idx = this.stack.indexOf(block.cuted); + + if (!~idx) { + this.stack.unshift(block.cuted); + } + }, + + _getStack: function() { + var i = 0, + act; + + while ( (act = this.stack[ i++ ]) ) { + if ( act.has() && act.file.getStatus() === Status.PROGRESS ) { + return act; + } else if (!act.has() || + act.file.getStatus() !== Status.PROGRESS && + act.file.getStatus() !== Status.INTERRUPT ) { + + // 把已经处理完了的,或者,状态为非 progress(上传中)、 + // interupt(暂停中) 的移除。 + this.stack.splice( --i, 1 ); + } + } + + return null; + }, + + _nextBlock: function() { + var me = this, + opts = me.options, + act, next, done, preparing; + + // 如果当前文件还有没有需要传输的,则直接返回剩下的。 + if ( (act = this._getStack()) ) { + + // 是否提前准备下一个文件 + if ( opts.prepareNextFile && !me.pending.length ) { + me._prepareNextFile(); + } + + return act.shift(); + + // 否则,如果正在运行,则准备下一个文件,并等待完成后返回下个分片。 + } else if ( me.runing ) { + + // 如果缓存中有,则直接在缓存中取,没有则去queue中取。 + if ( !me.pending.length && me._getStats().numOfQueue ) { + me._prepareNextFile(); + } + + next = me.pending.shift(); + done = function( file ) { + if ( !file ) { + return null; + } + + act = CuteFile( file, opts.chunked ? opts.chunkSize : 0 ); + me.stack.push(act); + return act.shift(); + }; + + // 文件可能还在prepare中,也有可能已经完全准备好了。 + if ( isPromise( next) ) { + preparing = next.file; + next = next[ next.pipe ? 'pipe' : 'then' ]( done ); + next.file = preparing; + return next; + } + + return done( next ); + } + }, + + + /** + * @event uploadStart + * @param {File} file File对象 + * @description 某个文件开始上传前触发,一个文件只会触发一次。 + * @for Uploader + */ + _prepareNextFile: function() { + var me = this, + file = me.request('fetch-file'), + pending = me.pending, + promise; + + if ( file ) { + promise = me.request( 'before-send-file', file, function() { + + // 有可能文件被skip掉了。文件被skip掉后,状态坑定不是Queued. + if ( file.getStatus() === Status.PROGRESS || + file.getStatus() === Status.INTERRUPT ) { + return file; + } + + return me._finishFile( file ); + }); + + me.owner.trigger( 'uploadStart', file ); + file.setStatus( Status.PROGRESS ); + + promise.file = file; + + // 如果还在pending中,则替换成文件本身。 + promise.done(function() { + var idx = $.inArray( promise, pending ); + + ~idx && pending.splice( idx, 1, file ); + }); + + // befeore-send-file的钩子就有错误发生。 + promise.fail(function( reason ) { + file.setStatus( Status.ERROR, reason ); + me.owner.trigger( 'uploadError', file, reason ); + me.owner.trigger( 'uploadComplete', file ); + }); + + pending.push( promise ); + } + }, + + // 让出位置了,可以让其他分片开始上传 + _popBlock: function( block ) { + var idx = $.inArray( block, this.pool ); + + this.pool.splice( idx, 1 ); + block.file.remaning--; + this.remaning--; + }, + + // 开始上传,可以被掉过。如果promise被reject了,则表示跳过此分片。 + _startSend: function( block ) { + var me = this, + file = block.file, + promise; + + // 有可能在 before-send-file 的 promise 期间改变了文件状态。 + // 如:暂停,取消 + // 我们不能中断 promise, 但是可以在 promise 完后,不做上传操作。 + if ( file.getStatus() !== Status.PROGRESS ) { + + // 如果是中断,则还需要放回去。 + if (file.getStatus() === Status.INTERRUPT) { + me._putback(block); + } + + return; + } + + me.pool.push( block ); + me.remaning++; + + // 如果没有分片,则直接使用原始的。 + // 不会丢失content-type信息。 + block.blob = block.chunks === 1 ? file.source : + file.source.slice( block.start, block.end ); + + // hook, 每个分片发送之前可能要做些异步的事情。 + promise = me.request( 'before-send', block, function() { + + // 有可能文件已经上传出错了,所以不需要再传输了。 + if ( file.getStatus() === Status.PROGRESS ) { + me._doSend( block ); + } else { + me._popBlock( block ); + Base.nextTick( me.__tick ); + } + }); + + // 如果为fail了,则跳过此分片。 + promise.fail(function() { + if ( file.remaning === 1 ) { + me._finishFile( file ).always(function() { + block.percentage = 1; + me._popBlock( block ); + me.owner.trigger( 'uploadComplete', file ); + Base.nextTick( me.__tick ); + }); + } else { + block.percentage = 1; + me.updateFileProgress( file ); + me._popBlock( block ); + Base.nextTick( me.__tick ); + } + }); + }, + + + /** + * @event uploadBeforeSend + * @param {Object} object + * @param {Object} data 默认的上传参数,可以扩展此对象来控制上传参数。 + * @param {Object} headers 可以扩展此对象来控制上传头部。 + * @description 当某个文件的分块在发送前触发,主要用来询问是否要添加附带参数,大文件在开起分片上传的前提下此事件可能会触发多次。 + * @for Uploader + */ + + /** + * @event uploadAccept + * @param {Object} object + * @param {Object} ret 服务端的返回数据,json格式,如果服务端不是json格式,从ret._raw中取数据,自行解析。 + * @description 当某个文件上传到服务端响应后,会派送此事件来询问服务端响应是否有效。如果此事件handler返回值为`false`, 则此文件将派送`server`类型的`uploadError`事件。 + * @for Uploader + */ + + /** + * @event uploadProgress + * @param {File} file File对象 + * @param {Number} percentage 上传进度 + * @description 上传过程中触发,携带上传进度。 + * @for Uploader + */ + + + /** + * @event uploadError + * @param {File} file File对象 + * @param {String} reason 出错的code + * @description 当文件上传出错时触发。 + * @for Uploader + */ + + /** + * @event uploadSuccess + * @param {File} file File对象 + * @param {Object} response 服务端返回的数据 + * @description 当文件上传成功时触发。 + * @for Uploader + */ + + /** + * @event uploadComplete + * @param {File} [file] File对象 + * @description 不管成功或者失败,文件上传完成时触发。 + * @for Uploader + */ + + // 做上传操作。 + _doSend: function( block ) { + var me = this, + owner = me.owner, + opts = me.options, + file = block.file, + tr = new Transport( opts ), + data = $.extend({}, opts.formData ), + headers = $.extend({}, opts.headers ), + requestAccept, ret; + + block.transport = tr; + + tr.on( 'destroy', function() { + delete block.transport; + me._popBlock( block ); + Base.nextTick( me.__tick ); + }); + + // 广播上传进度。以文件为单位。 + tr.on( 'progress', function( percentage ) { + block.percentage = percentage; + me.updateFileProgress( file ); + }); + + // 用来询问,是否返回的结果是有错误的。 + requestAccept = function( reject ) { + var fn; + + ret = tr.getResponseAsJson() || {}; + ret._raw = tr.getResponse(); + fn = function( value ) { + reject = value; + }; + + // 服务端响应了,不代表成功了,询问是否响应正确。 + if ( !owner.trigger( 'uploadAccept', block, ret, fn ) ) { + reject = reject || 'server'; + } + + return reject; + }; + + // 尝试重试,然后广播文件上传出错。 + tr.on( 'error', function( type, flag ) { + block.retried = block.retried || 0; + + // 自动重试 + if ( block.chunks > 1 && ~'http,abort'.indexOf( type ) && + block.retried < opts.chunkRetry ) { + + block.retried++; + tr.send(); + + } else { + + // http status 500 ~ 600 + if ( !flag && type === 'server' ) { + type = requestAccept( type ); + } + + file.setStatus( Status.ERROR, type ); + owner.trigger( 'uploadError', file, type ); + owner.trigger( 'uploadComplete', file ); + } + }); + + // 上传成功 + tr.on( 'load', function() { + var reason; + + // 如果非预期,转向上传出错。 + if ( (reason = requestAccept()) ) { + tr.trigger( 'error', reason, true ); + return; + } + + // 全部上传完成。 + if ( file.remaning === 1 ) { + me._finishFile( file, ret ); + } else { + tr.destroy(); + } + }); + + // 配置默认的上传字段。 + data = $.extend( data, { + id: file.id, + name: file.name, + type: file.type, + lastModifiedDate: file.lastModifiedDate, + size: file.size + }); + + block.chunks > 1 && $.extend( data, { + chunks: block.chunks, + chunk: block.chunk + }); + + // 在发送之间可以添加字段什么的。。。 + // 如果默认的字段不够使用,可以通过监听此事件来扩展 + owner.trigger( 'uploadBeforeSend', block, data, headers ); + + // 开始发送。 + tr.appendBlob( opts.fileVal, block.blob, file.name ); + tr.append( data ); + tr.setRequestHeader( headers ); + tr.send(); + }, + + // 完成上传。 + _finishFile: function( file, ret, hds ) { + var owner = this.owner; + + return owner + .request( 'after-send-file', arguments, function() { + file.setStatus( Status.COMPLETE ); + owner.trigger( 'uploadSuccess', file, ret, hds ); + }) + .fail(function( reason ) { + + // 如果外部已经标记为invalid什么的,不再改状态。 + if ( file.getStatus() === Status.PROGRESS ) { + file.setStatus( Status.ERROR, reason ); + } + + owner.trigger( 'uploadError', file, reason ); + }) + .always(function() { + owner.trigger( 'uploadComplete', file ); + }); + }, + + updateFileProgress: function(file) { + var totalPercent = 0, + uploaded = 0; + + if (!file.blocks) { + return; + } + + $.each( file.blocks, function( _, v ) { + uploaded += (v.percentage || 0) * (v.end - v.start); + }); + + totalPercent = uploaded / file.size; + this.owner.trigger( 'uploadProgress', file, totalPercent || 0 ); + } + + }); + }); + /** + * @fileOverview 各种验证,包括文件总大小是否超出、单文件是否超出和文件是否重复。 + */ + + define('widgets/validator',[ + 'base', + 'uploader', + 'file', + 'widgets/widget' + ], function( Base, Uploader, WUFile ) { + + var $ = Base.$, + validators = {}, + api; + + /** + * @event error + * @param {String} type 错误类型。 + * @description 当validate不通过时,会以派送错误事件的形式通知调用者。通过`upload.on('error', handler)`可以捕获到此类错误,目前有以下错误会在特定的情况下派送错来。 + * + * * `Q_EXCEED_NUM_LIMIT` 在设置了`fileNumLimit`且尝试给`uploader`添加的文件数量超出这个值时派送。 + * * `Q_EXCEED_SIZE_LIMIT` 在设置了`Q_EXCEED_SIZE_LIMIT`且尝试给`uploader`添加的文件总大小超出这个值时派送。 + * * `Q_TYPE_DENIED` 当文件类型不满足时触发。。 + * @for Uploader + */ + + // 暴露给外面的api + api = { + + // 添加验证器 + addValidator: function( type, cb ) { + validators[ type ] = cb; + }, + + // 移除验证器 + removeValidator: function( type ) { + delete validators[ type ]; + } + }; + + // 在Uploader初始化的时候启动Validators的初始化 + Uploader.register({ + name: 'validator', + + init: function() { + var me = this; + Base.nextTick(function() { + $.each( validators, function() { + this.call( me.owner ); + }); + }); + } + }); + + /** + * @property {int} [fileNumLimit=undefined] + * @namespace options + * @for Uploader + * @description 验证文件总数量, 超出则不允许加入队列。 + */ + api.addValidator( 'fileNumLimit', function() { + var uploader = this, + opts = uploader.options, + count = 0, + max = parseInt( opts.fileNumLimit, 10 ), + flag = true; + + if ( !max ) { + return; + } + + uploader.on( 'beforeFileQueued', function( file ) { + + if ( count >= max && flag ) { + flag = false; + this.trigger( 'error', 'Q_EXCEED_NUM_LIMIT', max, file ); + setTimeout(function() { + flag = true; + }, 1 ); + } + + return count >= max ? false : true; + }); + + uploader.on( 'fileQueued', function() { + count++; + }); + + uploader.on( 'fileDequeued', function() { + count--; + }); + + uploader.on( 'reset', function() { + count = 0; + }); + }); + + + /** + * @property {int} [fileSizeLimit=undefined] + * @namespace options + * @for Uploader + * @description 验证文件总大小是否超出限制, 超出则不允许加入队列。 + */ + api.addValidator( 'fileSizeLimit', function() { + var uploader = this, + opts = uploader.options, + count = 0, + max = parseInt( opts.fileSizeLimit, 10 ), + flag = true; + + if ( !max ) { + return; + } + + uploader.on( 'beforeFileQueued', function( file ) { + var invalid = count + file.size > max; + + if ( invalid && flag ) { + flag = false; + this.trigger( 'error', 'Q_EXCEED_SIZE_LIMIT', max, file ); + setTimeout(function() { + flag = true; + }, 1 ); + } + + return invalid ? false : true; + }); + + uploader.on( 'fileQueued', function( file ) { + count += file.size; + }); + + uploader.on( 'fileDequeued', function( file ) { + count -= file.size; + }); + + uploader.on( 'reset', function() { + count = 0; + }); + }); + + /** + * @property {int} [fileSingleSizeLimit=undefined] + * @namespace options + * @for Uploader + * @description 验证单个文件大小是否超出限制, 超出则不允许加入队列。 + */ + api.addValidator( 'fileSingleSizeLimit', function() { + var uploader = this, + opts = uploader.options, + max = opts.fileSingleSizeLimit; + + if ( !max ) { + return; + } + + uploader.on( 'beforeFileQueued', function( file ) { + + if ( file.size > max ) { + file.setStatus( WUFile.Status.INVALID, 'exceed_size' ); + this.trigger( 'error', 'F_EXCEED_SIZE', max, file ); + return false; + } + + }); + + }); + + /** + * @property {Boolean} [duplicate=undefined] + * @namespace options + * @for Uploader + * @description 去重, 根据文件名字、文件大小和最后修改时间来生成hash Key. + */ + api.addValidator( 'duplicate', function() { + var uploader = this, + opts = uploader.options, + mapping = {}; + + if ( opts.duplicate ) { + return; + } + + function hashString( str ) { + var hash = 0, + i = 0, + len = str.length, + _char; + + for ( ; i < len; i++ ) { + _char = str.charCodeAt( i ); + hash = _char + (hash << 6) + (hash << 16) - hash; + } + + return hash; + } + + uploader.on( 'beforeFileQueued', function( file ) { + var hash = file.__hash || (file.__hash = hashString( file.name + + file.size + file.lastModifiedDate )); + + // 已经重复了 + if ( mapping[ hash ] ) { + this.trigger( 'error', 'F_DUPLICATE', file ); + return false; + } + }); + + uploader.on( 'fileQueued', function( file ) { + var hash = file.__hash; + + hash && (mapping[ hash ] = true); + }); + + uploader.on( 'fileDequeued', function( file ) { + var hash = file.__hash; + + hash && (delete mapping[ hash ]); + }); + + uploader.on( 'reset', function() { + mapping = {}; + }); + }); + + return api; + }); + + /** + * @fileOverview Runtime管理器,负责Runtime的选择, 连接 + */ + define('runtime/compbase',[],function() { + + function CompBase( owner, runtime ) { + + this.owner = owner; + this.options = owner.options; + + this.getRuntime = function() { + return runtime; + }; + + this.getRuid = function() { + return runtime.uid; + }; + + this.trigger = function() { + return owner.trigger.apply( owner, arguments ); + }; + } + + return CompBase; + }); + /** + * @fileOverview FlashRuntime + */ + define('runtime/flash/runtime',[ + 'base', + 'runtime/runtime', + 'runtime/compbase' + ], function( Base, Runtime, CompBase ) { + + var $ = Base.$, + type = 'flash', + components = {}; + + + function getFlashVersion() { + var version; + + try { + version = navigator.plugins[ 'Shockwave Flash' ]; + version = version.description; + } catch ( ex ) { + try { + version = new ActiveXObject('ShockwaveFlash.ShockwaveFlash') + .GetVariable('$version'); + } catch ( ex2 ) { + version = '0.0'; + } + } + version = version.match( /\d+/g ); + return parseFloat( version[ 0 ] + '.' + version[ 1 ], 10 ); + } + + function FlashRuntime() { + var pool = {}, + clients = {}, + destroy = this.destroy, + me = this, + jsreciver = Base.guid('webuploader_'); + + Runtime.apply( me, arguments ); + me.type = type; + + + // 这个方法的调用者,实际上是RuntimeClient + me.exec = function( comp, fn/*, args...*/ ) { + var client = this, + uid = client.uid, + args = Base.slice( arguments, 2 ), + instance; + + clients[ uid ] = client; + + if ( components[ comp ] ) { + if ( !pool[ uid ] ) { + pool[ uid ] = new components[ comp ]( client, me ); + } + + instance = pool[ uid ]; + + if ( instance[ fn ] ) { + return instance[ fn ].apply( instance, args ); + } + } + + return me.flashExec.apply( client, arguments ); + }; + + function handler( evt, obj ) { + var type = evt.type || evt, + parts, uid; + + parts = type.split('::'); + uid = parts[ 0 ]; + type = parts[ 1 ]; + + // console.log.apply( console, arguments ); + + if ( type === 'Ready' && uid === me.uid ) { + me.trigger('ready'); + } else if ( clients[ uid ] ) { + clients[ uid ].trigger( type.toLowerCase(), evt, obj ); + } + + // Base.log( evt, obj ); + } + + // flash的接受器。 + window[ jsreciver ] = function() { + var args = arguments; + + // 为了能捕获得到。 + setTimeout(function() { + handler.apply( null, args ); + }, 1 ); + }; + + this.jsreciver = jsreciver; + + this.destroy = function() { + // @todo 删除池子中的所有实例 + return destroy && destroy.apply( this, arguments ); + }; + + this.flashExec = function( comp, fn ) { + var flash = me.getFlash(), + args = Base.slice( arguments, 2 ); + + return flash.exec( this.uid, comp, fn, args ); + }; + + // @todo + } + + Base.inherits( Runtime, { + constructor: FlashRuntime, + + init: function() { + var container = this.getContainer(), + opts = this.options, + html; + + // if not the minimal height, shims are not initialized + // in older browsers (e.g FF3.6, IE6,7,8, Safari 4.0,5.0, etc) + container.css({ + position: 'absolute', + top: '-8px', + left: '-8px', + width: '9px', + height: '9px', + overflow: 'hidden' + }); + + // insert flash object + html = '' + + '' + + '' + + '' + + ''; + + container.html( html ); + }, + + getFlash: function() { + if ( this._flash ) { + return this._flash; + } + + this._flash = $( '#' + this.uid ).get( 0 ); + return this._flash; + } + + }); + + FlashRuntime.register = function( name, component ) { + component = components[ name ] = Base.inherits( CompBase, $.extend({ + + // @todo fix this later + flashExec: function() { + var owner = this.owner, + runtime = this.getRuntime(); + + return runtime.flashExec.apply( owner, arguments ); + } + }, component ) ); + + return component; + }; + + if ( getFlashVersion() >= 11.4 ) { + Runtime.addRuntime( type, FlashRuntime ); + } + + return FlashRuntime; + }); + /** + * @fileOverview FilePicker + */ + define('runtime/flash/filepicker',[ + 'base', + 'runtime/flash/runtime' + ], function( Base, FlashRuntime ) { + var $ = Base.$; + + return FlashRuntime.register( 'FilePicker', { + init: function( opts ) { + var copy = $.extend({}, opts ), + len, i; + + // 修复Flash再没有设置title的情况下无法弹出flash文件选择框的bug. + len = copy.accept && copy.accept.length; + for ( i = 0; i < len; i++ ) { + if ( !copy.accept[ i ].title ) { + copy.accept[ i ].title = 'Files'; + } + } + + delete copy.button; + delete copy.id; + delete copy.container; + + this.flashExec( 'FilePicker', 'init', copy ); + }, + + destroy: function() { + this.flashExec( 'FilePicker', 'destroy' ); + } + }); + }); + /** + * @fileOverview 图片压缩 + */ + define('runtime/flash/image',[ + 'runtime/flash/runtime' + ], function( FlashRuntime ) { + + return FlashRuntime.register( 'Image', { + // init: function( options ) { + // var owner = this.owner; + + // this.flashExec( 'Image', 'init', options ); + // owner.on( 'load', function() { + // debugger; + // }); + // }, + + loadFromBlob: function( blob ) { + var owner = this.owner; + + owner.info() && this.flashExec( 'Image', 'info', owner.info() ); + owner.meta() && this.flashExec( 'Image', 'meta', owner.meta() ); + + this.flashExec( 'Image', 'loadFromBlob', blob.uid ); + } + }); + }); + /** + * @fileOverview Blob Html实现 + */ + define('runtime/flash/blob',[ + 'runtime/flash/runtime', + 'lib/blob' + ], function( FlashRuntime, Blob ) { + + return FlashRuntime.register( 'Blob', { + slice: function( start, end ) { + var blob = this.flashExec( 'Blob', 'slice', start, end ); + + return new Blob( blob.uid, blob ); + } + }); + }); + /** + * @fileOverview Transport flash实现 + */ + define('runtime/flash/transport',[ + 'base', + 'runtime/flash/runtime', + 'runtime/client' + ], function( Base, FlashRuntime, RuntimeClient ) { + var $ = Base.$; + + return FlashRuntime.register( 'Transport', { + init: function() { + this._status = 0; + this._response = null; + this._responseJson = null; + }, + + send: function() { + var owner = this.owner, + opts = this.options, + xhr = this._initAjax(), + blob = owner._blob, + server = opts.server, + binary; + + xhr.connectRuntime( blob.ruid ); + + if ( opts.sendAsBinary ) { + server += (/\?/.test( server ) ? '&' : '?') + + $.param( owner._formData ); + + binary = blob.uid; + } else { + $.each( owner._formData, function( k, v ) { + xhr.exec( 'append', k, v ); + }); + + xhr.exec( 'appendBlob', opts.fileVal, blob.uid, + opts.filename || owner._formData.name || '' ); + } + + this._setRequestHeader( xhr, opts.headers ); + xhr.exec( 'send', { + method: opts.method, + url: server, + forceURLStream: opts.forceURLStream, + mimeType: 'application/octet-stream' + }, binary ); + }, + + getStatus: function() { + return this._status; + }, + + getResponse: function() { + return this._response || ''; + }, + + getResponseAsJson: function() { + return this._responseJson; + }, + + abort: function() { + var xhr = this._xhr; + + if ( xhr ) { + xhr.exec('abort'); + xhr.destroy(); + this._xhr = xhr = null; + } + }, + + destroy: function() { + this.abort(); + }, + + _initAjax: function() { + var me = this, + xhr = new RuntimeClient('XMLHttpRequest'); + + xhr.on( 'uploadprogress progress', function( e ) { + var percent = e.loaded / e.total; + percent = Math.min( 1, Math.max( 0, percent ) ); + return me.trigger( 'progress', percent ); + }); + + xhr.on( 'load', function() { + var status = xhr.exec('getStatus'), + readBody = false, + err = '', + p; + + xhr.off(); + me._xhr = null; + + if ( status >= 200 && status < 300 ) { + readBody = true; + } else if ( status >= 500 && status < 600 ) { + readBody = true; + err = 'server'; + } else { + err = 'http'; + } + + if ( readBody ) { + me._response = xhr.exec('getResponse'); + me._response = decodeURIComponent( me._response ); + + // flash 处理可能存在 bug, 没辙只能靠 js 了 + // try { + // me._responseJson = xhr.exec('getResponseAsJson'); + // } catch ( error ) { + + p = window.JSON && window.JSON.parse || function( s ) { + try { + return new Function('return ' + s).call(); + } catch ( err ) { + return {}; + } + }; + me._responseJson = me._response ? p(me._response) : {}; + + // } + } + + xhr.destroy(); + xhr = null; + + return err ? me.trigger( 'error', err ) : me.trigger('load'); + }); + + xhr.on( 'error', function() { + xhr.off(); + me._xhr = null; + me.trigger( 'error', 'http' ); + }); + + me._xhr = xhr; + return xhr; + }, + + _setRequestHeader: function( xhr, headers ) { + $.each( headers, function( key, val ) { + xhr.exec( 'setRequestHeader', key, val ); + }); + } + }); + }); + /** + * @fileOverview 只有flash实现的文件版本。 + */ + define('preset/flashonly',[ + 'base', + + // widgets + 'widgets/filepicker', + 'widgets/image', + 'widgets/queue', + 'widgets/runtime', + 'widgets/upload', + 'widgets/validator', + + // runtimes + + // flash + 'runtime/flash/filepicker', + 'runtime/flash/image', + 'runtime/flash/blob', + 'runtime/flash/transport' + ], function( Base ) { + return Base; + }); + define('webuploader',[ + 'preset/flashonly' + ], function( preset ) { + return preset; + }); + return require('webuploader'); +}); diff --git a/static/plugins/webuploader/webuploader.flashonly.min.js b/static/plugins/webuploader/webuploader.flashonly.min.js new file mode 100644 index 00000000..7a2c8abb --- /dev/null +++ b/static/plugins/webuploader/webuploader.flashonly.min.js @@ -0,0 +1,2 @@ +/* WebUploader 0.1.5 */!function(a,b){var c,d={},e=function(a,b){var c,d,e;if("string"==typeof a)return h(a);for(c=[],d=a.length,e=0;d>e;e++)c.push(h(a[e]));return b.apply(null,c)},f=function(a,b,c){2===arguments.length&&(c=b,b=null),e(b||[],function(){g(a,c,arguments)})},g=function(a,b,c){var f,g={exports:b};"function"==typeof b&&(c.length||(c=[e,g.exports,g]),f=b.apply(null,c),void 0!==f&&(g.exports=f)),d[a]=g.exports},h=function(b){var c=d[b]||a[b];if(!c)throw new Error("`"+b+"` is undefined");return c},i=function(a){var b,c,e,f,g,h;h=function(a){return a&&a.charAt(0).toUpperCase()+a.substr(1)};for(b in d)if(c=a,d.hasOwnProperty(b)){for(e=b.split("/"),g=h(e.pop());f=h(e.shift());)c[f]=c[f]||{},c=c[f];c[g]=d[b]}return a},j=function(c){return a.__dollar=c,i(b(a,f,e))};"object"==typeof module&&"object"==typeof module.exports?module.exports=j():"function"==typeof define&&define.amd?define(["jquery"],j):(c=a.WebUploader,a.WebUploader=j(),a.WebUploader.noConflict=function(){a.WebUploader=c})}(window,function(a,b,c){return b("dollar-third",[],function(){var b=a.__dollar||a.jQuery||a.Zepto;if(!b)throw new Error("jQuery or Zepto not found!");return b}),b("dollar",["dollar-third"],function(a){return a}),b("promise-third",["dollar"],function(a){return{Deferred:a.Deferred,when:a.when,isPromise:function(a){return a&&"function"==typeof a.then}}}),b("promise",["promise-third"],function(a){return a}),b("base",["dollar","promise"],function(b,c){function d(a){return function(){return h.apply(a,arguments)}}function e(a,b){return function(){return a.apply(b,arguments)}}function f(a){var b;return Object.create?Object.create(a):(b=function(){},b.prototype=a,new b)}var g=function(){},h=Function.call;return{version:"0.1.5",$:b,Deferred:c.Deferred,isPromise:c.isPromise,when:c.when,browser:function(a){var b={},c=a.match(/WebKit\/([\d.]+)/),d=a.match(/Chrome\/([\d.]+)/)||a.match(/CriOS\/([\d.]+)/),e=a.match(/MSIE\s([\d\.]+)/)||a.match(/(?:trident)(?:.*rv:([\w.]+))?/i),f=a.match(/Firefox\/([\d.]+)/),g=a.match(/Safari\/([\d.]+)/),h=a.match(/OPR\/([\d.]+)/);return c&&(b.webkit=parseFloat(c[1])),d&&(b.chrome=parseFloat(d[1])),e&&(b.ie=parseFloat(e[1])),f&&(b.firefox=parseFloat(f[1])),g&&(b.safari=parseFloat(g[1])),h&&(b.opera=parseFloat(h[1])),b}(navigator.userAgent),os:function(a){var b={},c=a.match(/(?:Android);?[\s\/]+([\d.]+)?/),d=a.match(/(?:iPad|iPod|iPhone).*OS\s([\d_]+)/);return c&&(b.android=parseFloat(c[1])),d&&(b.ios=parseFloat(d[1].replace(/_/g,"."))),b}(navigator.userAgent),inherits:function(a,c,d){var e;return"function"==typeof c?(e=c,c=null):e=c&&c.hasOwnProperty("constructor")?c.constructor:function(){return a.apply(this,arguments)},b.extend(!0,e,a,d||{}),e.__super__=a.prototype,e.prototype=f(a.prototype),c&&b.extend(!0,e.prototype,c),e},noop:g,bindFn:e,log:function(){return a.console?e(console.log,console):g}(),nextTick:function(){return function(a){setTimeout(a,1)}}(),slice:d([].slice),guid:function(){var a=0;return function(b){for(var c=(+new Date).toString(32),d=0;5>d;d++)c+=Math.floor(65535*Math.random()).toString(32);return(b||"wu_")+c+(a++).toString(32)}}(),formatSize:function(a,b,c){var d;for(c=c||["B","K","M","G","TB"];(d=c.shift())&&a>1024;)a/=1024;return("B"===d?a:a.toFixed(b||2))+d}}}),b("mediator",["base"],function(a){function b(a,b,c,d){return f.grep(a,function(a){return!(!a||b&&a.e!==b||c&&a.cb!==c&&a.cb._cb!==c||d&&a.ctx!==d)})}function c(a,b,c){f.each((a||"").split(h),function(a,d){c(d,b)})}function d(a,b){for(var c,d=!1,e=-1,f=a.length;++e1?void(d.isPlainObject(b)&&d.isPlainObject(c[a])?d.extend(c[a],b):c[a]=b):a?c[a]:c},getStats:function(){var a=this.request("get-stats");return a?{successNum:a.numOfSuccess,progressNum:a.numOfProgress,cancelNum:a.numOfCancel,invalidNum:a.numOfInvalid,uploadFailNum:a.numOfUploadFailed,queueNum:a.numOfQueue,interruptNum:a.numofInterrupt}:{}},trigger:function(a){var c=[].slice.call(arguments,1),e=this.options,f="on"+a.substring(0,1).toUpperCase()+a.substring(1);return b.trigger.apply(this,arguments)===!1||d.isFunction(e[f])&&e[f].apply(this,c)===!1||d.isFunction(this[f])&&this[f].apply(this,c)===!1||b.trigger.apply(b,[this,a].concat(c))===!1?!1:!0},destroy:function(){this.request("destroy",arguments),this.off()},request:a.noop}),a.create=c.create=function(a){return new c(a)},a.Uploader=c,c}),b("runtime/runtime",["base","mediator"],function(a,b){function c(b){this.options=d.extend({container:document.body},b),this.uid=a.guid("rt_")}var d=a.$,e={},f=function(a){for(var b in a)if(a.hasOwnProperty(b))return b;return null};return d.extend(c.prototype,{getContainer:function(){var a,b,c=this.options;return this._container?this._container:(a=d(c.container||document.body),b=d(document.createElement("div")),b.attr("id","rt_"+this.uid),b.css({position:"absolute",top:"0px",left:"0px",width:"1px",height:"1px",overflow:"hidden"}),a.append(b),a.addClass("webuploader-container"),this._container=b,this._parent=a,b)},init:a.noop,exec:a.noop,destroy:function(){this._container&&this._container.remove(),this._parent&&this._parent.removeClass("webuploader-container"),this.off()}}),c.orders="html5,flash",c.addRuntime=function(a,b){e[a]=b},c.hasRuntime=function(a){return!!(a?e[a]:f(e))},c.create=function(a,b){var g,h;if(b=b||c.orders,d.each(b.split(/\s*,\s*/g),function(){return e[this]?(g=this,!1):void 0}),g=g||f(e),!g)throw new Error("Runtime Error");return h=new e[g](a)},b.installTo(c.prototype),c}),b("runtime/client",["base","mediator","runtime/runtime"],function(a,b,c){function d(b,d){var f,g=a.Deferred();this.uid=a.guid("client_"),this.runtimeReady=function(a){return g.done(a)},this.connectRuntime=function(b,h){if(f)throw new Error("already connected!");return g.done(h),"string"==typeof b&&e.get(b)&&(f=e.get(b)),f=f||e.get(null,d),f?(a.$.extend(f.options,b),f.__promise.then(g.resolve),f.__client++):(f=c.create(b,b.runtimeOrder),f.__promise=g.promise(),f.once("ready",g.resolve),f.init(),e.add(f),f.__client=1),d&&(f.__standalone=d),f},this.getRuntime=function(){return f},this.disconnectRuntime=function(){f&&(f.__client--,f.__client<=0&&(e.remove(f),delete f.__promise,f.destroy()),f=null)},this.exec=function(){if(f){var c=a.slice(arguments);return b&&c.unshift(b),f.exec.apply(this,c)}},this.getRuid=function(){return f&&f.uid},this.destroy=function(a){return function(){a&&a.apply(this,arguments),this.trigger("destroy"),this.off(),this.exec("destroy"),this.disconnectRuntime()}}(this.destroy)}var e;return e=function(){var a={};return{add:function(b){a[b.uid]=b},get:function(b,c){var d;if(b)return a[b];for(d in a)if(!c||!a[d].__standalone)return a[d];return null},remove:function(b){delete a[b.uid]}}}(),b.installTo(d.prototype),d}),b("lib/blob",["base","runtime/client"],function(a,b){function c(a,c){var d=this;d.source=c,d.ruid=a,this.size=c.size||0,this.type=!c.type&&this.ext&&~"jpg,jpeg,png,gif,bmp".indexOf(this.ext)?"image/"+("jpg"===this.ext?"jpeg":this.ext):c.type||"application/octet-stream",b.call(d,"Blob"),this.uid=c.uid||this.uid,a&&d.connectRuntime(a)}return a.inherits(b,{constructor:c,slice:function(a,b){return this.exec("slice",a,b)},getSource:function(){return this.source}}),c}),b("lib/file",["base","lib/blob"],function(a,b){function c(a,c){var f;this.name=c.name||"untitled"+d++,f=e.exec(c.name)?RegExp.$1.toLowerCase():"",!f&&c.type&&(f=/\/(jpg|jpeg|png|gif|bmp)$/i.exec(c.type)?RegExp.$1.toLowerCase():"",this.name+="."+f),this.ext=f,this.lastModifiedDate=c.lastModifiedDate||(new Date).toLocaleString(),b.apply(this,arguments)}var d=1,e=/\.([^.]+)$/;return a.inherits(b,c)}),b("lib/filepicker",["base","runtime/client","lib/file"],function(b,c,d){function e(a){if(a=this.options=f.extend({},e.options,a),a.container=f(a.id),!a.container.length)throw new Error("按钮指定错误");a.innerHTML=a.innerHTML||a.label||a.container.html()||"",a.button=f(a.button||document.createElement("div")),a.button.html(a.innerHTML),a.container.html(a.button),c.call(this,"FilePicker",!0)}var f=b.$;return e.options={button:null,container:null,label:null,innerHTML:null,multiple:!0,accept:null,name:"file"},b.inherits(c,{constructor:e,init:function(){var c=this,e=c.options,g=e.button;g.addClass("webuploader-pick"),c.on("all",function(a){var b;switch(a){case"mouseenter":g.addClass("webuploader-pick-hover");break;case"mouseleave":g.removeClass("webuploader-pick-hover");break;case"change":b=c.exec("getFiles"),c.trigger("select",f.map(b,function(a){return a=new d(c.getRuid(),a),a._refer=e.container,a}),e.container)}}),c.connectRuntime(e,function(){c.refresh(),c.exec("init",e),c.trigger("ready")}),this._resizeHandler=b.bindFn(this.refresh,this),f(a).on("resize",this._resizeHandler)},refresh:function(){var a=this.getRuntime().getContainer(),b=this.options.button,c=b.outerWidth?b.outerWidth():b.width(),d=b.outerHeight?b.outerHeight():b.height(),e=b.offset();c&&d&&a.css({bottom:"auto",right:"auto",width:c+"px",height:d+"px"}).offset(e)},enable:function(){var a=this.options.button;a.removeClass("webuploader-pick-disable"),this.refresh()},disable:function(){var a=this.options.button;this.getRuntime().getContainer().css({top:"-99999px"}),a.addClass("webuploader-pick-disable")},destroy:function(){var b=this.options.button;f(a).off("resize",this._resizeHandler),b.removeClass("webuploader-pick-disable webuploader-pick-hover webuploader-pick")}}),e}),b("widgets/widget",["base","uploader"],function(a,b){function c(a){if(!a)return!1;var b=a.length,c=e.type(a);return 1===a.nodeType&&b?!0:"array"===c||"function"!==c&&"string"!==c&&(0===b||"number"==typeof b&&b>0&&b-1 in a)}function d(a){this.owner=a,this.options=a.options}var e=a.$,f=b.prototype._init,g=b.prototype.destroy,h={},i=[];return e.extend(d.prototype,{init:a.noop,invoke:function(a,b){var c=this.responseMap;return c&&a in c&&c[a]in this&&e.isFunction(this[c[a]])?this[c[a]].apply(this,b):h},request:function(){return this.owner.request.apply(this.owner,arguments)}}),e.extend(b.prototype,{_init:function(){var a=this,b=a._widgets=[],c=a.options.disableWidgets||"";return e.each(i,function(d,e){(!c||!~c.indexOf(e._name))&&b.push(new e(a))}),f.apply(a,arguments)},request:function(b,d,e){var f,g,i,j,k=0,l=this._widgets,m=l&&l.length,n=[],o=[];for(d=c(d)?d:[d];m>k;k++)f=l[k],g=f.invoke(b,d),g!==h&&(a.isPromise(g)?o.push(g):n.push(g));return e||o.length?(i=a.when.apply(a,o),j=i.pipe?"pipe":"then",i[j](function(){var b=a.Deferred(),c=arguments;return 1===c.length&&(c=c[0]),setTimeout(function(){b.resolve(c)},1),b.promise()})[e?j:"done"](e||a.noop)):n[0]},destroy:function(){g.apply(this,arguments),this._widgets=null}}),b.register=d.register=function(b,c){var f,g={init:"init",destroy:"destroy",name:"anonymous"};return 1===arguments.length?(c=b,e.each(c,function(a){return"_"===a[0]||"name"===a?void("name"===a&&(g.name=c.name)):void(g[a.replace(/[A-Z]/g,"-$&").toLowerCase()]=a)})):g=e.extend(g,b),c.responseMap=g,f=a.inherits(d,c),f._name=g.name,i.push(f),f},b.unRegister=d.unRegister=function(a){if(a&&"anonymous"!==a)for(var b=i.length;b--;)i[b]._name===a&&i.splice(b,1)},d}),b("widgets/filepicker",["base","uploader","lib/filepicker","widgets/widget"],function(a,b,c){var d=a.$;return d.extend(b.options,{pick:null,accept:null}),b.register({name:"picker",init:function(a){return this.pickers=[],a.pick&&this.addBtn(a.pick)},refresh:function(){d.each(this.pickers,function(){this.refresh()})},addBtn:function(b){var e=this,f=e.options,g=f.accept,h=[];if(b)return d.isPlainObject(b)||(b={id:b}),d(b.id).each(function(){var i,j,k;k=a.Deferred(),i=d.extend({},b,{accept:d.isPlainObject(g)?[g]:g,swf:f.swf,runtimeOrder:f.runtimeOrder,id:this}),j=new c(i),j.once("ready",k.resolve),j.on("select",function(a){e.owner.request("add-file",[a])}),j.init(),e.pickers.push(j),h.push(k.promise())}),a.when.apply(a,h)},disable:function(){d.each(this.pickers,function(){this.disable()})},enable:function(){d.each(this.pickers,function(){this.enable()})},destroy:function(){d.each(this.pickers,function(){this.destroy()}),this.pickers=null}})}),b("lib/image",["base","runtime/client","lib/blob"],function(a,b,c){function d(a){this.options=e.extend({},d.options,a),b.call(this,"Image"),this.on("load",function(){this._info=this.exec("info"),this._meta=this.exec("meta")})}var e=a.$;return d.options={quality:90,crop:!1,preserveHeaders:!1,allowMagnify:!1},a.inherits(b,{constructor:d,info:function(a){return a?(this._info=a,this):this._info},meta:function(a){return a?(this._meta=a,this):this._meta},loadFromBlob:function(a){var b=this,c=a.getRuid();this.connectRuntime(c,function(){b.exec("init",b.options),b.exec("loadFromBlob",a)})},resize:function(){var b=a.slice(arguments);return this.exec.apply(this,["resize"].concat(b))},crop:function(){var b=a.slice(arguments);return this.exec.apply(this,["crop"].concat(b))},getAsDataUrl:function(a){return this.exec("getAsDataUrl",a)},getAsBlob:function(a){var b=this.exec("getAsBlob",a);return new c(this.getRuid(),b)}}),d}),b("widgets/image",["base","uploader","lib/image","widgets/widget"],function(a,b,c){var d,e=a.$;return d=function(a){var b=0,c=[],d=function(){for(var d;c.length&&a>b;)d=c.shift(),b+=d[0],d[1]()};return function(a,e,f){c.push([e,f]),a.once("destroy",function(){b-=e,setTimeout(d,1)}),setTimeout(d,1)}}(5242880),e.extend(b.options,{thumb:{width:110,height:110,quality:70,allowMagnify:!0,crop:!0,preserveHeaders:!1,type:"image/jpeg"},compress:{width:1600,height:1600,quality:90,allowMagnify:!1,crop:!1,preserveHeaders:!0}}),b.register({name:"image",makeThumb:function(a,b,f,g){var h,i;return a=this.request("get-file",a),a.type.match(/^image/)?(h=e.extend({},this.options.thumb),e.isPlainObject(f)&&(h=e.extend(h,f),f=null),f=f||h.width,g=g||h.height,i=new c(h),i.once("load",function(){a._info=a._info||i.info(),a._meta=a._meta||i.meta(),1>=f&&f>0&&(f=a._info.width*f),1>=g&&g>0&&(g=a._info.height*g),i.resize(f,g)}),i.once("complete",function(){b(!1,i.getAsDataUrl(h.type)),i.destroy()}),i.once("error",function(a){b(a||!0),i.destroy()}),void d(i,a.source.size,function(){a._info&&i.info(a._info),a._meta&&i.meta(a._meta),i.loadFromBlob(a.source)})):void b(!0)},beforeSendFile:function(b){var d,f,g=this.options.compress||this.options.resize,h=g&&g.compressSize||0,i=g&&g.noCompressIfLarger||!1;return b=this.request("get-file",b),!g||!~"image/jpeg,image/jpg".indexOf(b.type)||b.size=a&&a>0&&(a=b._info.width*a),1>=c&&c>0&&(c=b._info.height*c),d.resize(a,c)}),d.once("complete",function(){var a,c;try{a=d.getAsBlob(g.type),c=b.size,(!i||a.sizeb;b++)if(c=this._queue[b],a===c.getStatus())return c;return null},sort:function(a){"function"==typeof a&&this._queue.sort(a)},getFiles:function(){for(var a,b=[].slice.call(arguments,0),c=[],d=0,f=this._queue.length;f>d;d++)a=this._queue[d],(!b.length||~e.inArray(a.getStatus(),b))&&c.push(a);return c},removeFile:function(a){var b=this._map[a.id];b&&(delete this._map[a.id],a.destroy(),this.stats.numofDeleted++)},_fileAdded:function(a){var b=this,c=this._map[a.id];c||(this._map[a.id]=a,a.on("statuschange",function(a,c){b._onFileStatusChange(a,c)}))},_onFileStatusChange:function(a,b){var c=this.stats;switch(b){case f.PROGRESS:c.numOfProgress--;break;case f.QUEUED:c.numOfQueue--;break;case f.ERROR:c.numOfUploadFailed--;break;case f.INVALID:c.numOfInvalid--;break;case f.INTERRUPT:c.numofInterrupt--}switch(a){case f.QUEUED:c.numOfQueue++;break;case f.PROGRESS:c.numOfProgress++;break;case f.ERROR:c.numOfUploadFailed++;break;case f.COMPLETE:c.numOfSuccess++;break;case f.CANCELLED:c.numOfCancel++;break;case f.INVALID:c.numOfInvalid++;break;case f.INTERRUPT:c.numofInterrupt++}}}),b.installTo(d.prototype),d}),b("widgets/queue",["base","uploader","queue","file","lib/file","runtime/client","widgets/widget"],function(a,b,c,d,e,f){var g=a.$,h=/\.\w+$/,i=d.Status;return b.register({name:"queue",init:function(b){var d,e,h,i,j,k,l,m=this;if(g.isPlainObject(b.accept)&&(b.accept=[b.accept]),b.accept){for(j=[],h=0,e=b.accept.length;e>h;h++)i=b.accept[h].extensions,i&&j.push(i);j.length&&(k="\\."+j.join(",").replace(/,/g,"$|\\.").replace(/\*/g,".*")+"$"),m.accept=new RegExp(k,"i")}return m.queue=new c,m.stats=m.queue.stats,"html5"===this.request("predict-runtime-type")?(d=a.Deferred(),this.placeholder=l=new f("Placeholder"),l.connectRuntime({runtimeOrder:"html5"},function(){m._ruid=l.getRuid(),d.resolve()}),d.promise()):void 0},_wrapFile:function(a){if(!(a instanceof d)){if(!(a instanceof e)){if(!this._ruid)throw new Error("Can't add external files.");a=new e(this._ruid,a)}a=new d(a)}return a},acceptFile:function(a){var b=!a||!a.size||this.accept&&h.exec(a.name)&&!this.accept.test(a.name);return!b},_addFile:function(a){var b=this;return a=b._wrapFile(a),b.owner.trigger("beforeFileQueued",a)?b.acceptFile(a)?(b.queue.append(a),b.owner.trigger("fileQueued",a),a):void b.owner.trigger("error","Q_TYPE_DENIED",a):void 0},getFile:function(a){return this.queue.getFile(a)},addFile:function(a){var b=this;a.length||(a=[a]),a=g.map(a,function(a){return b._addFile(a)}),b.owner.trigger("filesQueued",a),b.options.auto&&setTimeout(function(){b.request("start-upload")},20)},getStats:function(){return this.stats},removeFile:function(a,b){var c=this;a=a.id?a:c.queue.getFile(a),this.request("cancel-file",a),b&&this.queue.removeFile(a)},getFiles:function(){return this.queue.getFiles.apply(this.queue,arguments)},fetchFile:function(){return this.queue.fetch.apply(this.queue,arguments)},retry:function(a,b){var c,d,e,f=this;if(a)return a=a.id?a:f.queue.getFile(a),a.setStatus(i.QUEUED),void(b||f.request("start-upload"));for(c=f.queue.getFiles(i.ERROR),d=0,e=c.length;e>d;d++)a=c[d],a.setStatus(i.QUEUED);f.request("start-upload")},sortFiles:function(){return this.queue.sort.apply(this.queue,arguments)},reset:function(){this.owner.trigger("reset"),this.queue=new c,this.stats=this.queue.stats},destroy:function(){this.reset(),this.placeholder&&this.placeholder.destroy()}})}),b("widgets/runtime",["uploader","runtime/runtime","widgets/widget"],function(a,b){return a.support=function(){return b.hasRuntime.apply(b,arguments)},a.register({name:"runtime",init:function(){if(!this.predictRuntimeType())throw Error("Runtime Error")},predictRuntimeType:function(){var a,c,d=this.options.runtimeOrder||b.orders,e=this.type;if(!e)for(d=d.split(/\s*,\s*/g),a=0,c=d.length;c>a;a++)if(b.hasRuntime(d[a])){this.type=e=d[a];break}return e}})}),b("lib/transport",["base","runtime/client","mediator"],function(a,b,c){function d(a){var c=this;a=c.options=e.extend(!0,{},d.options,a||{}),b.call(this,"Transport"),this._blob=null,this._formData=a.formData||{},this._headers=a.headers||{},this.on("progress",this._timeout),this.on("load error",function(){c.trigger("progress",1),clearTimeout(c._timer)})}var e=a.$;return d.options={server:"",method:"POST",withCredentials:!1,fileVal:"file",timeout:12e4,formData:{},headers:{},sendAsBinary:!1},e.extend(d.prototype,{appendBlob:function(a,b,c){var d=this,e=d.options;d.getRuid()&&d.disconnectRuntime(),d.connectRuntime(b.ruid,function(){d.exec("init")}),d._blob=b,e.fileVal=a||e.fileVal,e.filename=c||e.filename},append:function(a,b){"object"==typeof a?e.extend(this._formData,a):this._formData[a]=b},setRequestHeader:function(a,b){"object"==typeof a?e.extend(this._headers,a):this._headers[a]=b},send:function(a){this.exec("send",a),this._timeout()},abort:function(){return clearTimeout(this._timer),this.exec("abort")},destroy:function(){this.trigger("destroy"),this.off(),this.exec("destroy"),this.disconnectRuntime()},getResponse:function(){return this.exec("getResponse")},getResponseAsJson:function(){return this.exec("getResponseAsJson")},getStatus:function(){return this.exec("getStatus")},_timeout:function(){var a=this,b=a.options.timeout;b&&(clearTimeout(a._timer),a._timer=setTimeout(function(){a.abort(),a.trigger("error","timeout")},b))}}),c.installTo(d.prototype),d}),b("widgets/upload",["base","uploader","file","lib/transport","widgets/widget"],function(a,b,c,d){function e(a,b){var c,d,e=[],f=a.source,g=f.size,h=b?Math.ceil(g/b):1,i=0,j=0;for(d={file:a,has:function(){return!!e.length},shift:function(){return e.shift()},unshift:function(a){e.unshift(a)}};h>j;)c=Math.min(b,g-i),e.push({file:a,start:i,end:b?i+c:g,total:g,chunks:h,chunk:j++,cuted:d}),i+=c;return a.blocks=e.concat(),a.remaning=e.length,d}var f=a.$,g=a.isPromise,h=c.Status;f.extend(b.options,{prepareNextFile:!1,chunked:!1,chunkSize:5242880,chunkRetry:2,threads:3,formData:{}}),b.register({name:"upload",init:function(){var b=this.owner,c=this;this.runing=!1,this.progress=!1,b.on("startUpload",function(){c.progress=!0}).on("uploadFinished",function(){c.progress=!1}),this.pool=[],this.stack=[],this.pending=[],this.remaning=0,this.__tick=a.bindFn(this._tick,this),b.on("uploadComplete",function(a){a.blocks&&f.each(a.blocks,function(a,b){b.transport&&(b.transport.abort(),b.transport.destroy()),delete b.transport}),delete a.blocks,delete a.remaning})},reset:function(){this.request("stop-upload",!0),this.runing=!1,this.pool=[],this.stack=[],this.pending=[],this.remaning=0,this._trigged=!1,this._promise=null},startUpload:function(b){var c=this;if(f.each(c.request("get-files",h.INVALID),function(){c.request("remove-file",this)}),b)if(b=b.id?b:c.request("get-file",b),b.getStatus()===h.INTERRUPT)f.each(c.pool,function(a,c){c.file===b&&c.transport&&c.transport.send()}),b.setStatus(h.QUEUED);else{if(b.getStatus()===h.PROGRESS)return;b.setStatus(h.QUEUED)}else f.each(c.request("get-files",[h.INITED]),function(){this.setStatus(h.QUEUED)});if(!c.runing){c.runing=!0;var d=[];f.each(c.pool,function(a,b){var e=b.file;e.getStatus()===h.INTERRUPT&&(d.push(e),c._trigged=!1,b.transport&&b.transport.send())});for(var b;b=d.shift();)b.setStatus(h.PROGRESS);b||f.each(c.request("get-files",h.INTERRUPT),function(){this.setStatus(h.PROGRESS)}),c._trigged=!1,a.nextTick(c.__tick),c.owner.trigger("startUpload")}},stopUpload:function(b,c){var d=this;if(b===!0&&(c=b,b=null),d.runing!==!1){if(b){if(b=b.id?b:d.request("get-file",b),b.getStatus()!==h.PROGRESS&&b.getStatus()!==h.QUEUED)return;return b.setStatus(h.INTERRUPT),f.each(d.pool,function(a,c){c.file===b&&(c.transport&&c.transport.abort(),d._putback(c),d._popBlock(c))}),a.nextTick(d.__tick)}d.runing=!1,this._promise&&this._promise.file&&this._promise.file.setStatus(h.INTERRUPT),c&&f.each(d.pool,function(a,b){b.transport&&b.transport.abort(),b.file.setStatus(h.INTERRUPT)}),d.owner.trigger("stopUpload")}},cancelFile:function(a){a=a.id?a:this.request("get-file",a),a.blocks&&f.each(a.blocks,function(a,b){var c=b.transport;c&&(c.abort(),c.destroy(),delete b.transport)}),a.setStatus(h.CANCELLED),this.owner.trigger("fileDequeued",a)},isInProgress:function(){return!!this.progress},_getStats:function(){return this.request("get-stats")},skipFile:function(a,b){a=a.id?a:this.request("get-file",a),a.setStatus(b||h.COMPLETE),a.skipped=!0,a.blocks&&f.each(a.blocks,function(a,b){var c=b.transport;c&&(c.abort(),c.destroy(),delete b.transport)}),this.owner.trigger("uploadSkip",a)},_tick:function(){var b,c,d=this,e=d.options;return d._promise?d._promise.always(d.__tick):void(d.pool.length1&&~"http,abort".indexOf(a)&&b.retried1&&f.extend(m,{chunks:b.chunks,chunk:b.chunk}),i.trigger("uploadBeforeSend",b,m,n),l.appendBlob(j.fileVal,b.blob,k.name),l.append(m),l.setRequestHeader(n),l.send()},_finishFile:function(a,b,c){var d=this.owner;return d.request("after-send-file",arguments,function(){a.setStatus(h.COMPLETE),d.trigger("uploadSuccess",a,b,c)}).fail(function(b){a.getStatus()===h.PROGRESS&&a.setStatus(h.ERROR,b),d.trigger("uploadError",a,b)}).always(function(){d.trigger("uploadComplete",a)})},updateFileProgress:function(a){var b=0,c=0;a.blocks&&(f.each(a.blocks,function(a,b){c+=(b.percentage||0)*(b.end-b.start)}),b=c/a.size,this.owner.trigger("uploadProgress",a,b||0))}})}),b("widgets/validator",["base","uploader","file","widgets/widget"],function(a,b,c){var d,e=a.$,f={};return d={addValidator:function(a,b){f[a]=b},removeValidator:function(a){delete f[a]}},b.register({name:"validator",init:function(){var b=this;a.nextTick(function(){e.each(f,function(){this.call(b.owner)})})}}),d.addValidator("fileNumLimit",function(){var a=this,b=a.options,c=0,d=parseInt(b.fileNumLimit,10),e=!0;d&&(a.on("beforeFileQueued",function(a){return c>=d&&e&&(e=!1,this.trigger("error","Q_EXCEED_NUM_LIMIT",d,a),setTimeout(function(){e=!0},1)),c>=d?!1:!0}),a.on("fileQueued",function(){c++}),a.on("fileDequeued",function(){c--}),a.on("reset",function(){c=0}))}),d.addValidator("fileSizeLimit",function(){var a=this,b=a.options,c=0,d=parseInt(b.fileSizeLimit,10),e=!0;d&&(a.on("beforeFileQueued",function(a){var b=c+a.size>d;return b&&e&&(e=!1,this.trigger("error","Q_EXCEED_SIZE_LIMIT",d,a),setTimeout(function(){e=!0},1)),b?!1:!0}),a.on("fileQueued",function(a){c+=a.size}),a.on("fileDequeued",function(a){c-=a.size}),a.on("reset",function(){c=0}))}),d.addValidator("fileSingleSizeLimit",function(){var a=this,b=a.options,d=b.fileSingleSizeLimit;d&&a.on("beforeFileQueued",function(a){return a.size>d?(a.setStatus(c.Status.INVALID,"exceed_size"),this.trigger("error","F_EXCEED_SIZE",d,a),!1):void 0})}),d.addValidator("duplicate",function(){function a(a){for(var b,c=0,d=0,e=a.length;e>d;d++)b=a.charCodeAt(d),c=b+(c<<6)+(c<<16)-c;return c}var b=this,c=b.options,d={};c.duplicate||(b.on("beforeFileQueued",function(b){var c=b.__hash||(b.__hash=a(b.name+b.size+b.lastModifiedDate)); +return d[c]?(this.trigger("error","F_DUPLICATE",b),!1):void 0}),b.on("fileQueued",function(a){var b=a.__hash;b&&(d[b]=!0)}),b.on("fileDequeued",function(a){var b=a.__hash;b&&delete d[b]}),b.on("reset",function(){d={}}))}),d}),b("runtime/compbase",[],function(){function a(a,b){this.owner=a,this.options=a.options,this.getRuntime=function(){return b},this.getRuid=function(){return b.uid},this.trigger=function(){return a.trigger.apply(a,arguments)}}return a}),b("runtime/flash/runtime",["base","runtime/runtime","runtime/compbase"],function(b,c,d){function e(){var a;try{a=navigator.plugins["Shockwave Flash"],a=a.description}catch(b){try{a=new ActiveXObject("ShockwaveFlash.ShockwaveFlash").GetVariable("$version")}catch(c){a="0.0"}}return a=a.match(/\d+/g),parseFloat(a[0]+"."+a[1],10)}function f(){function d(a,b){var c,d,e=a.type||a;c=e.split("::"),d=c[0],e=c[1],"Ready"===e&&d===j.uid?j.trigger("ready"):f[d]&&f[d].trigger(e.toLowerCase(),a,b)}var e={},f={},g=this.destroy,j=this,k=b.guid("webuploader_");c.apply(j,arguments),j.type=h,j.exec=function(a,c){var d,g=this,h=g.uid,k=b.slice(arguments,2);return f[h]=g,i[a]&&(e[h]||(e[h]=new i[a](g,j)),d=e[h],d[c])?d[c].apply(d,k):j.flashExec.apply(g,arguments)},a[k]=function(){var a=arguments;setTimeout(function(){d.apply(null,a)},1)},this.jsreciver=k,this.destroy=function(){return g&&g.apply(this,arguments)},this.flashExec=function(a,c){var d=j.getFlash(),e=b.slice(arguments,2);return d.exec(this.uid,a,c,e)}}var g=b.$,h="flash",i={};return b.inherits(c,{constructor:f,init:function(){var a,c=this.getContainer(),d=this.options;c.css({position:"absolute",top:"-8px",left:"-8px",width:"9px",height:"9px",overflow:"hidden"}),a='',c.html(a)},getFlash:function(){return this._flash?this._flash:(this._flash=g("#"+this.uid).get(0),this._flash)}}),f.register=function(a,c){return c=i[a]=b.inherits(d,g.extend({flashExec:function(){var a=this.owner,b=this.getRuntime();return b.flashExec.apply(a,arguments)}},c))},e()>=11.4&&c.addRuntime(h,f),f}),b("runtime/flash/filepicker",["base","runtime/flash/runtime"],function(a,b){var c=a.$;return b.register("FilePicker",{init:function(a){var b,d,e=c.extend({},a);for(b=e.accept&&e.accept.length,d=0;b>d;d++)e.accept[d].title||(e.accept[d].title="Files");delete e.button,delete e.id,delete e.container,this.flashExec("FilePicker","init",e)},destroy:function(){this.flashExec("FilePicker","destroy")}})}),b("runtime/flash/image",["runtime/flash/runtime"],function(a){return a.register("Image",{loadFromBlob:function(a){var b=this.owner;b.info()&&this.flashExec("Image","info",b.info()),b.meta()&&this.flashExec("Image","meta",b.meta()),this.flashExec("Image","loadFromBlob",a.uid)}})}),b("runtime/flash/blob",["runtime/flash/runtime","lib/blob"],function(a,b){return a.register("Blob",{slice:function(a,c){var d=this.flashExec("Blob","slice",a,c);return new b(d.uid,d)}})}),b("runtime/flash/transport",["base","runtime/flash/runtime","runtime/client"],function(b,c,d){var e=b.$;return c.register("Transport",{init:function(){this._status=0,this._response=null,this._responseJson=null},send:function(){var a,b=this.owner,c=this.options,d=this._initAjax(),f=b._blob,g=c.server;d.connectRuntime(f.ruid),c.sendAsBinary?(g+=(/\?/.test(g)?"&":"?")+e.param(b._formData),a=f.uid):(e.each(b._formData,function(a,b){d.exec("append",a,b)}),d.exec("appendBlob",c.fileVal,f.uid,c.filename||b._formData.name||"")),this._setRequestHeader(d,c.headers),d.exec("send",{method:c.method,url:g,forceURLStream:c.forceURLStream,mimeType:"application/octet-stream"},a)},getStatus:function(){return this._status},getResponse:function(){return this._response||""},getResponseAsJson:function(){return this._responseJson},abort:function(){var a=this._xhr;a&&(a.exec("abort"),a.destroy(),this._xhr=a=null)},destroy:function(){this.abort()},_initAjax:function(){var b=this,c=new d("XMLHttpRequest");return c.on("uploadprogress progress",function(a){var c=a.loaded/a.total;return c=Math.min(1,Math.max(0,c)),b.trigger("progress",c)}),c.on("load",function(){var d,e=c.exec("getStatus"),f=!1,g="";return c.off(),b._xhr=null,e>=200&&300>e?f=!0:e>=500&&600>e?(f=!0,g="server"):g="http",f&&(b._response=c.exec("getResponse"),b._response=decodeURIComponent(b._response),d=a.JSON&&a.JSON.parse||function(a){try{return new Function("return "+a).call()}catch(b){return{}}},b._responseJson=b._response?d(b._response):{}),c.destroy(),c=null,g?b.trigger("error",g):b.trigger("load")}),c.on("error",function(){c.off(),b._xhr=null,b.trigger("error","http")}),b._xhr=c,c},_setRequestHeader:function(a,b){e.each(b,function(b,c){a.exec("setRequestHeader",b,c)})}})}),b("preset/flashonly",["base","widgets/filepicker","widgets/image","widgets/queue","widgets/runtime","widgets/upload","widgets/validator","runtime/flash/filepicker","runtime/flash/image","runtime/flash/blob","runtime/flash/transport"],function(a){return a}),b("webuploader",["preset/flashonly"],function(a){return a}),c("webuploader")}); \ No newline at end of file diff --git a/static/plugins/webuploader/webuploader.html5only.js b/static/plugins/webuploader/webuploader.html5only.js new file mode 100644 index 00000000..6c21cefa --- /dev/null +++ b/static/plugins/webuploader/webuploader.html5only.js @@ -0,0 +1,6030 @@ +/*! WebUploader 0.1.5 */ + + +/** + * @fileOverview 让内部各个部件的代码可以用[amd](https://github.com/amdjs/amdjs-api/wiki/AMD)模块定义方式组织起来。 + * + * AMD API 内部的简单不完全实现,请忽略。只有当WebUploader被合并成一个文件的时候才会引入。 + */ +(function( root, factory ) { + var modules = {}, + + // 内部require, 简单不完全实现。 + // https://github.com/amdjs/amdjs-api/wiki/require + _require = function( deps, callback ) { + var args, len, i; + + // 如果deps不是数组,则直接返回指定module + if ( typeof deps === 'string' ) { + return getModule( deps ); + } else { + args = []; + for( len = deps.length, i = 0; i < len; i++ ) { + args.push( getModule( deps[ i ] ) ); + } + + return callback.apply( null, args ); + } + }, + + // 内部define,暂时不支持不指定id. + _define = function( id, deps, factory ) { + if ( arguments.length === 2 ) { + factory = deps; + deps = null; + } + + _require( deps || [], function() { + setModule( id, factory, arguments ); + }); + }, + + // 设置module, 兼容CommonJs写法。 + setModule = function( id, factory, args ) { + var module = { + exports: factory + }, + returned; + + if ( typeof factory === 'function' ) { + args.length || (args = [ _require, module.exports, module ]); + returned = factory.apply( null, args ); + returned !== undefined && (module.exports = returned); + } + + modules[ id ] = module.exports; + }, + + // 根据id获取module + getModule = function( id ) { + var module = modules[ id ] || root[ id ]; + + if ( !module ) { + throw new Error( '`' + id + '` is undefined' ); + } + + return module; + }, + + // 将所有modules,将路径ids装换成对象。 + exportsTo = function( obj ) { + var key, host, parts, part, last, ucFirst; + + // make the first character upper case. + ucFirst = function( str ) { + return str && (str.charAt( 0 ).toUpperCase() + str.substr( 1 )); + }; + + for ( key in modules ) { + host = obj; + + if ( !modules.hasOwnProperty( key ) ) { + continue; + } + + parts = key.split('/'); + last = ucFirst( parts.pop() ); + + while( (part = ucFirst( parts.shift() )) ) { + host[ part ] = host[ part ] || {}; + host = host[ part ]; + } + + host[ last ] = modules[ key ]; + } + + return obj; + }, + + makeExport = function( dollar ) { + root.__dollar = dollar; + + // exports every module. + return exportsTo( factory( root, _define, _require ) ); + }, + + origin; + + if ( typeof module === 'object' && typeof module.exports === 'object' ) { + + // For CommonJS and CommonJS-like environments where a proper window is present, + module.exports = makeExport(); + } else if ( typeof define === 'function' && define.amd ) { + + // Allow using this built library as an AMD module + // in another project. That other project will only + // see this AMD call, not the internal modules in + // the closure below. + define([ 'jquery' ], makeExport ); + } else { + + // Browser globals case. Just assign the + // result to a property on the global. + origin = root.WebUploader; + root.WebUploader = makeExport(); + root.WebUploader.noConflict = function() { + root.WebUploader = origin; + }; + } +})( window, function( window, define, require ) { + + + /** + * @fileOverview jQuery or Zepto + */ + define('dollar-third',[],function() { + var $ = window.__dollar || window.jQuery || window.Zepto; + + if ( !$ ) { + throw new Error('jQuery or Zepto not found!'); + } + + return $; + }); + /** + * @fileOverview Dom 操作相关 + */ + define('dollar',[ + 'dollar-third' + ], function( _ ) { + return _; + }); + /** + * @fileOverview 使用jQuery的Promise + */ + define('promise-third',[ + 'dollar' + ], function( $ ) { + return { + Deferred: $.Deferred, + when: $.when, + + isPromise: function( anything ) { + return anything && typeof anything.then === 'function'; + } + }; + }); + /** + * @fileOverview Promise/A+ + */ + define('promise',[ + 'promise-third' + ], function( _ ) { + return _; + }); + /** + * @fileOverview 基础类方法。 + */ + + /** + * Web Uploader内部类的详细说明,以下提及的功能类,都可以在`WebUploader`这个变量中访问到。 + * + * As you know, Web Uploader的每个文件都是用过[AMD](https://github.com/amdjs/amdjs-api/wiki/AMD)规范中的`define`组织起来的, 每个Module都会有个module id. + * 默认module id为该文件的路径,而此路径将会转化成名字空间存放在WebUploader中。如: + * + * * module `base`:WebUploader.Base + * * module `file`: WebUploader.File + * * module `lib/dnd`: WebUploader.Lib.Dnd + * * module `runtime/html5/dnd`: WebUploader.Runtime.Html5.Dnd + * + * + * 以下文档中对类的使用可能省略掉了`WebUploader`前缀。 + * @module WebUploader + * @title WebUploader API文档 + */ + define('base',[ + 'dollar', + 'promise' + ], function( $, promise ) { + + var noop = function() {}, + call = Function.call; + + // http://jsperf.com/uncurrythis + // 反科里化 + function uncurryThis( fn ) { + return function() { + return call.apply( fn, arguments ); + }; + } + + function bindFn( fn, context ) { + return function() { + return fn.apply( context, arguments ); + }; + } + + function createObject( proto ) { + var f; + + if ( Object.create ) { + return Object.create( proto ); + } else { + f = function() {}; + f.prototype = proto; + return new f(); + } + } + + + /** + * 基础类,提供一些简单常用的方法。 + * @class Base + */ + return { + + /** + * @property {String} version 当前版本号。 + */ + version: '0.1.5', + + /** + * @property {jQuery|Zepto} $ 引用依赖的jQuery或者Zepto对象。 + */ + $: $, + + Deferred: promise.Deferred, + + isPromise: promise.isPromise, + + when: promise.when, + + /** + * @description 简单的浏览器检查结果。 + * + * * `webkit` webkit版本号,如果浏览器为非webkit内核,此属性为`undefined`。 + * * `chrome` chrome浏览器版本号,如果浏览器为chrome,此属性为`undefined`。 + * * `ie` ie浏览器版本号,如果浏览器为非ie,此属性为`undefined`。**暂不支持ie10+** + * * `firefox` firefox浏览器版本号,如果浏览器为非firefox,此属性为`undefined`。 + * * `safari` safari浏览器版本号,如果浏览器为非safari,此属性为`undefined`。 + * * `opera` opera浏览器版本号,如果浏览器为非opera,此属性为`undefined`。 + * + * @property {Object} [browser] + */ + browser: (function( ua ) { + var ret = {}, + webkit = ua.match( /WebKit\/([\d.]+)/ ), + chrome = ua.match( /Chrome\/([\d.]+)/ ) || + ua.match( /CriOS\/([\d.]+)/ ), + + ie = ua.match( /MSIE\s([\d\.]+)/ ) || + ua.match( /(?:trident)(?:.*rv:([\w.]+))?/i ), + firefox = ua.match( /Firefox\/([\d.]+)/ ), + safari = ua.match( /Safari\/([\d.]+)/ ), + opera = ua.match( /OPR\/([\d.]+)/ ); + + webkit && (ret.webkit = parseFloat( webkit[ 1 ] )); + chrome && (ret.chrome = parseFloat( chrome[ 1 ] )); + ie && (ret.ie = parseFloat( ie[ 1 ] )); + firefox && (ret.firefox = parseFloat( firefox[ 1 ] )); + safari && (ret.safari = parseFloat( safari[ 1 ] )); + opera && (ret.opera = parseFloat( opera[ 1 ] )); + + return ret; + })( navigator.userAgent ), + + /** + * @description 操作系统检查结果。 + * + * * `android` 如果在android浏览器环境下,此值为对应的android版本号,否则为`undefined`。 + * * `ios` 如果在ios浏览器环境下,此值为对应的ios版本号,否则为`undefined`。 + * @property {Object} [os] + */ + os: (function( ua ) { + var ret = {}, + + // osx = !!ua.match( /\(Macintosh\; Intel / ), + android = ua.match( /(?:Android);?[\s\/]+([\d.]+)?/ ), + ios = ua.match( /(?:iPad|iPod|iPhone).*OS\s([\d_]+)/ ); + + // osx && (ret.osx = true); + android && (ret.android = parseFloat( android[ 1 ] )); + ios && (ret.ios = parseFloat( ios[ 1 ].replace( /_/g, '.' ) )); + + return ret; + })( navigator.userAgent ), + + /** + * 实现类与类之间的继承。 + * @method inherits + * @grammar Base.inherits( super ) => child + * @grammar Base.inherits( super, protos ) => child + * @grammar Base.inherits( super, protos, statics ) => child + * @param {Class} super 父类 + * @param {Object | Function} [protos] 子类或者对象。如果对象中包含constructor,子类将是用此属性值。 + * @param {Function} [protos.constructor] 子类构造器,不指定的话将创建个临时的直接执行父类构造器的方法。 + * @param {Object} [statics] 静态属性或方法。 + * @return {Class} 返回子类。 + * @example + * function Person() { + * console.log( 'Super' ); + * } + * Person.prototype.hello = function() { + * console.log( 'hello' ); + * }; + * + * var Manager = Base.inherits( Person, { + * world: function() { + * console.log( 'World' ); + * } + * }); + * + * // 因为没有指定构造器,父类的构造器将会执行。 + * var instance = new Manager(); // => Super + * + * // 继承子父类的方法 + * instance.hello(); // => hello + * instance.world(); // => World + * + * // 子类的__super__属性指向父类 + * console.log( Manager.__super__ === Person ); // => true + */ + inherits: function( Super, protos, staticProtos ) { + var child; + + if ( typeof protos === 'function' ) { + child = protos; + protos = null; + } else if ( protos && protos.hasOwnProperty('constructor') ) { + child = protos.constructor; + } else { + child = function() { + return Super.apply( this, arguments ); + }; + } + + // 复制静态方法 + $.extend( true, child, Super, staticProtos || {} ); + + /* jshint camelcase: false */ + + // 让子类的__super__属性指向父类。 + child.__super__ = Super.prototype; + + // 构建原型,添加原型方法或属性。 + // 暂时用Object.create实现。 + child.prototype = createObject( Super.prototype ); + protos && $.extend( true, child.prototype, protos ); + + return child; + }, + + /** + * 一个不做任何事情的方法。可以用来赋值给默认的callback. + * @method noop + */ + noop: noop, + + /** + * 返回一个新的方法,此方法将已指定的`context`来执行。 + * @grammar Base.bindFn( fn, context ) => Function + * @method bindFn + * @example + * var doSomething = function() { + * console.log( this.name ); + * }, + * obj = { + * name: 'Object Name' + * }, + * aliasFn = Base.bind( doSomething, obj ); + * + * aliasFn(); // => Object Name + * + */ + bindFn: bindFn, + + /** + * 引用Console.log如果存在的话,否则引用一个[空函数noop](#WebUploader:Base.noop)。 + * @grammar Base.log( args... ) => undefined + * @method log + */ + log: (function() { + if ( window.console ) { + return bindFn( console.log, console ); + } + return noop; + })(), + + nextTick: (function() { + + return function( cb ) { + setTimeout( cb, 1 ); + }; + + // @bug 当浏览器不在当前窗口时就停了。 + // var next = window.requestAnimationFrame || + // window.webkitRequestAnimationFrame || + // window.mozRequestAnimationFrame || + // function( cb ) { + // window.setTimeout( cb, 1000 / 60 ); + // }; + + // // fix: Uncaught TypeError: Illegal invocation + // return bindFn( next, window ); + })(), + + /** + * 被[uncurrythis](http://www.2ality.com/2011/11/uncurrying-this.html)的数组slice方法。 + * 将用来将非数组对象转化成数组对象。 + * @grammar Base.slice( target, start[, end] ) => Array + * @method slice + * @example + * function doSomthing() { + * var args = Base.slice( arguments, 1 ); + * console.log( args ); + * } + * + * doSomthing( 'ignored', 'arg2', 'arg3' ); // => Array ["arg2", "arg3"] + */ + slice: uncurryThis( [].slice ), + + /** + * 生成唯一的ID + * @method guid + * @grammar Base.guid() => String + * @grammar Base.guid( prefx ) => String + */ + guid: (function() { + var counter = 0; + + return function( prefix ) { + var guid = (+new Date()).toString( 32 ), + i = 0; + + for ( ; i < 5; i++ ) { + guid += Math.floor( Math.random() * 65535 ).toString( 32 ); + } + + return (prefix || 'wu_') + guid + (counter++).toString( 32 ); + }; + })(), + + /** + * 格式化文件大小, 输出成带单位的字符串 + * @method formatSize + * @grammar Base.formatSize( size ) => String + * @grammar Base.formatSize( size, pointLength ) => String + * @grammar Base.formatSize( size, pointLength, units ) => String + * @param {Number} size 文件大小 + * @param {Number} [pointLength=2] 精确到的小数点数。 + * @param {Array} [units=[ 'B', 'K', 'M', 'G', 'TB' ]] 单位数组。从字节,到千字节,一直往上指定。如果单位数组里面只指定了到了K(千字节),同时文件大小大于M, 此方法的输出将还是显示成多少K. + * @example + * console.log( Base.formatSize( 100 ) ); // => 100B + * console.log( Base.formatSize( 1024 ) ); // => 1.00K + * console.log( Base.formatSize( 1024, 0 ) ); // => 1K + * console.log( Base.formatSize( 1024 * 1024 ) ); // => 1.00M + * console.log( Base.formatSize( 1024 * 1024 * 1024 ) ); // => 1.00G + * console.log( Base.formatSize( 1024 * 1024 * 1024, 0, ['B', 'KB', 'MB'] ) ); // => 1024MB + */ + formatSize: function( size, pointLength, units ) { + var unit; + + units = units || [ 'B', 'K', 'M', 'G', 'TB' ]; + + while ( (unit = units.shift()) && size > 1024 ) { + size = size / 1024; + } + + return (unit === 'B' ? size : size.toFixed( pointLength || 2 )) + + unit; + } + }; + }); + /** + * 事件处理类,可以独立使用,也可以扩展给对象使用。 + * @fileOverview Mediator + */ + define('mediator',[ + 'base' + ], function( Base ) { + var $ = Base.$, + slice = [].slice, + separator = /\s+/, + protos; + + // 根据条件过滤出事件handlers. + function findHandlers( arr, name, callback, context ) { + return $.grep( arr, function( handler ) { + return handler && + (!name || handler.e === name) && + (!callback || handler.cb === callback || + handler.cb._cb === callback) && + (!context || handler.ctx === context); + }); + } + + function eachEvent( events, callback, iterator ) { + // 不支持对象,只支持多个event用空格隔开 + $.each( (events || '').split( separator ), function( _, key ) { + iterator( key, callback ); + }); + } + + function triggerHanders( events, args ) { + var stoped = false, + i = -1, + len = events.length, + handler; + + while ( ++i < len ) { + handler = events[ i ]; + + if ( handler.cb.apply( handler.ctx2, args ) === false ) { + stoped = true; + break; + } + } + + return !stoped; + } + + protos = { + + /** + * 绑定事件。 + * + * `callback`方法在执行时,arguments将会来源于trigger的时候携带的参数。如 + * ```javascript + * var obj = {}; + * + * // 使得obj有事件行为 + * Mediator.installTo( obj ); + * + * obj.on( 'testa', function( arg1, arg2 ) { + * console.log( arg1, arg2 ); // => 'arg1', 'arg2' + * }); + * + * obj.trigger( 'testa', 'arg1', 'arg2' ); + * ``` + * + * 如果`callback`中,某一个方法`return false`了,则后续的其他`callback`都不会被执行到。 + * 切会影响到`trigger`方法的返回值,为`false`。 + * + * `on`还可以用来添加一个特殊事件`all`, 这样所有的事件触发都会响应到。同时此类`callback`中的arguments有一个不同处, + * 就是第一个参数为`type`,记录当前是什么事件在触发。此类`callback`的优先级比脚低,会再正常`callback`执行完后触发。 + * ```javascript + * obj.on( 'all', function( type, arg1, arg2 ) { + * console.log( type, arg1, arg2 ); // => 'testa', 'arg1', 'arg2' + * }); + * ``` + * + * @method on + * @grammar on( name, callback[, context] ) => self + * @param {String} name 事件名,支持多个事件用空格隔开 + * @param {Function} callback 事件处理器 + * @param {Object} [context] 事件处理器的上下文。 + * @return {self} 返回自身,方便链式 + * @chainable + * @class Mediator + */ + on: function( name, callback, context ) { + var me = this, + set; + + if ( !callback ) { + return this; + } + + set = this._events || (this._events = []); + + eachEvent( name, callback, function( name, callback ) { + var handler = { e: name }; + + handler.cb = callback; + handler.ctx = context; + handler.ctx2 = context || me; + handler.id = set.length; + + set.push( handler ); + }); + + return this; + }, + + /** + * 绑定事件,且当handler执行完后,自动解除绑定。 + * @method once + * @grammar once( name, callback[, context] ) => self + * @param {String} name 事件名 + * @param {Function} callback 事件处理器 + * @param {Object} [context] 事件处理器的上下文。 + * @return {self} 返回自身,方便链式 + * @chainable + */ + once: function( name, callback, context ) { + var me = this; + + if ( !callback ) { + return me; + } + + eachEvent( name, callback, function( name, callback ) { + var once = function() { + me.off( name, once ); + return callback.apply( context || me, arguments ); + }; + + once._cb = callback; + me.on( name, once, context ); + }); + + return me; + }, + + /** + * 解除事件绑定 + * @method off + * @grammar off( [name[, callback[, context] ] ] ) => self + * @param {String} [name] 事件名 + * @param {Function} [callback] 事件处理器 + * @param {Object} [context] 事件处理器的上下文。 + * @return {self} 返回自身,方便链式 + * @chainable + */ + off: function( name, cb, ctx ) { + var events = this._events; + + if ( !events ) { + return this; + } + + if ( !name && !cb && !ctx ) { + this._events = []; + return this; + } + + eachEvent( name, cb, function( name, cb ) { + $.each( findHandlers( events, name, cb, ctx ), function() { + delete events[ this.id ]; + }); + }); + + return this; + }, + + /** + * 触发事件 + * @method trigger + * @grammar trigger( name[, args...] ) => self + * @param {String} type 事件名 + * @param {*} [...] 任意参数 + * @return {Boolean} 如果handler中return false了,则返回false, 否则返回true + */ + trigger: function( type ) { + var args, events, allEvents; + + if ( !this._events || !type ) { + return this; + } + + args = slice.call( arguments, 1 ); + events = findHandlers( this._events, type ); + allEvents = findHandlers( this._events, 'all' ); + + return triggerHanders( events, args ) && + triggerHanders( allEvents, arguments ); + } + }; + + /** + * 中介者,它本身是个单例,但可以通过[installTo](#WebUploader:Mediator:installTo)方法,使任何对象具备事件行为。 + * 主要目的是负责模块与模块之间的合作,降低耦合度。 + * + * @class Mediator + */ + return $.extend({ + + /** + * 可以通过这个接口,使任何对象具备事件功能。 + * @method installTo + * @param {Object} obj 需要具备事件行为的对象。 + * @return {Object} 返回obj. + */ + installTo: function( obj ) { + return $.extend( obj, protos ); + } + + }, protos ); + }); + /** + * @fileOverview Uploader上传类 + */ + define('uploader',[ + 'base', + 'mediator' + ], function( Base, Mediator ) { + + var $ = Base.$; + + /** + * 上传入口类。 + * @class Uploader + * @constructor + * @grammar new Uploader( opts ) => Uploader + * @example + * var uploader = WebUploader.Uploader({ + * swf: 'path_of_swf/Uploader.swf', + * + * // 开起分片上传。 + * chunked: true + * }); + */ + function Uploader( opts ) { + this.options = $.extend( true, {}, Uploader.options, opts ); + this._init( this.options ); + } + + // default Options + // widgets中有相应扩展 + Uploader.options = {}; + Mediator.installTo( Uploader.prototype ); + + // 批量添加纯命令式方法。 + $.each({ + upload: 'start-upload', + stop: 'stop-upload', + getFile: 'get-file', + getFiles: 'get-files', + addFile: 'add-file', + addFiles: 'add-file', + sort: 'sort-files', + removeFile: 'remove-file', + cancelFile: 'cancel-file', + skipFile: 'skip-file', + retry: 'retry', + isInProgress: 'is-in-progress', + makeThumb: 'make-thumb', + md5File: 'md5-file', + getDimension: 'get-dimension', + addButton: 'add-btn', + predictRuntimeType: 'predict-runtime-type', + refresh: 'refresh', + disable: 'disable', + enable: 'enable', + reset: 'reset' + }, function( fn, command ) { + Uploader.prototype[ fn ] = function() { + return this.request( command, arguments ); + }; + }); + + $.extend( Uploader.prototype, { + state: 'pending', + + _init: function( opts ) { + var me = this; + + me.request( 'init', opts, function() { + me.state = 'ready'; + me.trigger('ready'); + }); + }, + + /** + * 获取或者设置Uploader配置项。 + * @method option + * @grammar option( key ) => * + * @grammar option( key, val ) => self + * @example + * + * // 初始状态图片上传前不会压缩 + * var uploader = new WebUploader.Uploader({ + * compress: null; + * }); + * + * // 修改后图片上传前,尝试将图片压缩到1600 * 1600 + * uploader.option( 'compress', { + * width: 1600, + * height: 1600 + * }); + */ + option: function( key, val ) { + var opts = this.options; + + // setter + if ( arguments.length > 1 ) { + + if ( $.isPlainObject( val ) && + $.isPlainObject( opts[ key ] ) ) { + $.extend( opts[ key ], val ); + } else { + opts[ key ] = val; + } + + } else { // getter + return key ? opts[ key ] : opts; + } + }, + + /** + * 获取文件统计信息。返回一个包含一下信息的对象。 + * * `successNum` 上传成功的文件数 + * * `progressNum` 上传中的文件数 + * * `cancelNum` 被删除的文件数 + * * `invalidNum` 无效的文件数 + * * `uploadFailNum` 上传失败的文件数 + * * `queueNum` 还在队列中的文件数 + * * `interruptNum` 被暂停的文件数 + * @method getStats + * @grammar getStats() => Object + */ + getStats: function() { + // return this._mgr.getStats.apply( this._mgr, arguments ); + var stats = this.request('get-stats'); + + return stats ? { + successNum: stats.numOfSuccess, + progressNum: stats.numOfProgress, + + // who care? + // queueFailNum: 0, + cancelNum: stats.numOfCancel, + invalidNum: stats.numOfInvalid, + uploadFailNum: stats.numOfUploadFailed, + queueNum: stats.numOfQueue, + interruptNum: stats.numofInterrupt + } : {}; + }, + + // 需要重写此方法来来支持opts.onEvent和instance.onEvent的处理器 + trigger: function( type/*, args...*/ ) { + var args = [].slice.call( arguments, 1 ), + opts = this.options, + name = 'on' + type.substring( 0, 1 ).toUpperCase() + + type.substring( 1 ); + + if ( + // 调用通过on方法注册的handler. + Mediator.trigger.apply( this, arguments ) === false || + + // 调用opts.onEvent + $.isFunction( opts[ name ] ) && + opts[ name ].apply( this, args ) === false || + + // 调用this.onEvent + $.isFunction( this[ name ] ) && + this[ name ].apply( this, args ) === false || + + // 广播所有uploader的事件。 + Mediator.trigger.apply( Mediator, + [ this, type ].concat( args ) ) === false ) { + + return false; + } + + return true; + }, + + /** + * 销毁 webuploader 实例 + * @method destroy + * @grammar destroy() => undefined + */ + destroy: function() { + this.request( 'destroy', arguments ); + this.off(); + }, + + // widgets/widget.js将补充此方法的详细文档。 + request: Base.noop + }); + + /** + * 创建Uploader实例,等同于new Uploader( opts ); + * @method create + * @class Base + * @static + * @grammar Base.create( opts ) => Uploader + */ + Base.create = Uploader.create = function( opts ) { + return new Uploader( opts ); + }; + + // 暴露Uploader,可以通过它来扩展业务逻辑。 + Base.Uploader = Uploader; + + return Uploader; + }); + /** + * @fileOverview Runtime管理器,负责Runtime的选择, 连接 + */ + define('runtime/runtime',[ + 'base', + 'mediator' + ], function( Base, Mediator ) { + + var $ = Base.$, + factories = {}, + + // 获取对象的第一个key + getFirstKey = function( obj ) { + for ( var key in obj ) { + if ( obj.hasOwnProperty( key ) ) { + return key; + } + } + return null; + }; + + // 接口类。 + function Runtime( options ) { + this.options = $.extend({ + container: document.body + }, options ); + this.uid = Base.guid('rt_'); + } + + $.extend( Runtime.prototype, { + + getContainer: function() { + var opts = this.options, + parent, container; + + if ( this._container ) { + return this._container; + } + + parent = $( opts.container || document.body ); + container = $( document.createElement('div') ); + + container.attr( 'id', 'rt_' + this.uid ); + container.css({ + position: 'absolute', + top: '0px', + left: '0px', + width: '1px', + height: '1px', + overflow: 'hidden' + }); + + parent.append( container ); + parent.addClass('webuploader-container'); + this._container = container; + this._parent = parent; + return container; + }, + + init: Base.noop, + exec: Base.noop, + + destroy: function() { + this._container && this._container.remove(); + this._parent && this._parent.removeClass('webuploader-container'); + this.off(); + } + }); + + Runtime.orders = 'html5,flash'; + + + /** + * 添加Runtime实现。 + * @param {String} type 类型 + * @param {Runtime} factory 具体Runtime实现。 + */ + Runtime.addRuntime = function( type, factory ) { + factories[ type ] = factory; + }; + + Runtime.hasRuntime = function( type ) { + return !!(type ? factories[ type ] : getFirstKey( factories )); + }; + + Runtime.create = function( opts, orders ) { + var type, runtime; + + orders = orders || Runtime.orders; + $.each( orders.split( /\s*,\s*/g ), function() { + if ( factories[ this ] ) { + type = this; + return false; + } + }); + + type = type || getFirstKey( factories ); + + if ( !type ) { + throw new Error('Runtime Error'); + } + + runtime = new factories[ type ]( opts ); + return runtime; + }; + + Mediator.installTo( Runtime.prototype ); + return Runtime; + }); + + /** + * @fileOverview Runtime管理器,负责Runtime的选择, 连接 + */ + define('runtime/client',[ + 'base', + 'mediator', + 'runtime/runtime' + ], function( Base, Mediator, Runtime ) { + + var cache; + + cache = (function() { + var obj = {}; + + return { + add: function( runtime ) { + obj[ runtime.uid ] = runtime; + }, + + get: function( ruid, standalone ) { + var i; + + if ( ruid ) { + return obj[ ruid ]; + } + + for ( i in obj ) { + // 有些类型不能重用,比如filepicker. + if ( standalone && obj[ i ].__standalone ) { + continue; + } + + return obj[ i ]; + } + + return null; + }, + + remove: function( runtime ) { + delete obj[ runtime.uid ]; + } + }; + })(); + + function RuntimeClient( component, standalone ) { + var deferred = Base.Deferred(), + runtime; + + this.uid = Base.guid('client_'); + + // 允许runtime没有初始化之前,注册一些方法在初始化后执行。 + this.runtimeReady = function( cb ) { + return deferred.done( cb ); + }; + + this.connectRuntime = function( opts, cb ) { + + // already connected. + if ( runtime ) { + throw new Error('already connected!'); + } + + deferred.done( cb ); + + if ( typeof opts === 'string' && cache.get( opts ) ) { + runtime = cache.get( opts ); + } + + // 像filePicker只能独立存在,不能公用。 + runtime = runtime || cache.get( null, standalone ); + + // 需要创建 + if ( !runtime ) { + runtime = Runtime.create( opts, opts.runtimeOrder ); + runtime.__promise = deferred.promise(); + runtime.once( 'ready', deferred.resolve ); + runtime.init(); + cache.add( runtime ); + runtime.__client = 1; + } else { + // 来自cache + Base.$.extend( runtime.options, opts ); + runtime.__promise.then( deferred.resolve ); + runtime.__client++; + } + + standalone && (runtime.__standalone = standalone); + return runtime; + }; + + this.getRuntime = function() { + return runtime; + }; + + this.disconnectRuntime = function() { + if ( !runtime ) { + return; + } + + runtime.__client--; + + if ( runtime.__client <= 0 ) { + cache.remove( runtime ); + delete runtime.__promise; + runtime.destroy(); + } + + runtime = null; + }; + + this.exec = function() { + if ( !runtime ) { + return; + } + + var args = Base.slice( arguments ); + component && args.unshift( component ); + + return runtime.exec.apply( this, args ); + }; + + this.getRuid = function() { + return runtime && runtime.uid; + }; + + this.destroy = (function( destroy ) { + return function() { + destroy && destroy.apply( this, arguments ); + this.trigger('destroy'); + this.off(); + this.exec('destroy'); + this.disconnectRuntime(); + }; + })( this.destroy ); + } + + Mediator.installTo( RuntimeClient.prototype ); + return RuntimeClient; + }); + /** + * @fileOverview 错误信息 + */ + define('lib/dnd',[ + 'base', + 'mediator', + 'runtime/client' + ], function( Base, Mediator, RuntimeClent ) { + + var $ = Base.$; + + function DragAndDrop( opts ) { + opts = this.options = $.extend({}, DragAndDrop.options, opts ); + + opts.container = $( opts.container ); + + if ( !opts.container.length ) { + return; + } + + RuntimeClent.call( this, 'DragAndDrop' ); + } + + DragAndDrop.options = { + accept: null, + disableGlobalDnd: false + }; + + Base.inherits( RuntimeClent, { + constructor: DragAndDrop, + + init: function() { + var me = this; + + me.connectRuntime( me.options, function() { + me.exec('init'); + me.trigger('ready'); + }); + } + }); + + Mediator.installTo( DragAndDrop.prototype ); + + return DragAndDrop; + }); + /** + * @fileOverview 组件基类。 + */ + define('widgets/widget',[ + 'base', + 'uploader' + ], function( Base, Uploader ) { + + var $ = Base.$, + _init = Uploader.prototype._init, + _destroy = Uploader.prototype.destroy, + IGNORE = {}, + widgetClass = []; + + function isArrayLike( obj ) { + if ( !obj ) { + return false; + } + + var length = obj.length, + type = $.type( obj ); + + if ( obj.nodeType === 1 && length ) { + return true; + } + + return type === 'array' || type !== 'function' && type !== 'string' && + (length === 0 || typeof length === 'number' && length > 0 && + (length - 1) in obj); + } + + function Widget( uploader ) { + this.owner = uploader; + this.options = uploader.options; + } + + $.extend( Widget.prototype, { + + init: Base.noop, + + // 类Backbone的事件监听声明,监听uploader实例上的事件 + // widget直接无法监听事件,事件只能通过uploader来传递 + invoke: function( apiName, args ) { + + /* + { + 'make-thumb': 'makeThumb' + } + */ + var map = this.responseMap; + + // 如果无API响应声明则忽略 + if ( !map || !(apiName in map) || !(map[ apiName ] in this) || + !$.isFunction( this[ map[ apiName ] ] ) ) { + + return IGNORE; + } + + return this[ map[ apiName ] ].apply( this, args ); + + }, + + /** + * 发送命令。当传入`callback`或者`handler`中返回`promise`时。返回一个当所有`handler`中的promise都完成后完成的新`promise`。 + * @method request + * @grammar request( command, args ) => * | Promise + * @grammar request( command, args, callback ) => Promise + * @for Uploader + */ + request: function() { + return this.owner.request.apply( this.owner, arguments ); + } + }); + + // 扩展Uploader. + $.extend( Uploader.prototype, { + + /** + * @property {String | Array} [disableWidgets=undefined] + * @namespace options + * @for Uploader + * @description 默认所有 Uploader.register 了的 widget 都会被加载,如果禁用某一部分,请通过此 option 指定黑名单。 + */ + + // 覆写_init用来初始化widgets + _init: function() { + var me = this, + widgets = me._widgets = [], + deactives = me.options.disableWidgets || ''; + + $.each( widgetClass, function( _, klass ) { + (!deactives || !~deactives.indexOf( klass._name )) && + widgets.push( new klass( me ) ); + }); + + return _init.apply( me, arguments ); + }, + + request: function( apiName, args, callback ) { + var i = 0, + widgets = this._widgets, + len = widgets && widgets.length, + rlts = [], + dfds = [], + widget, rlt, promise, key; + + args = isArrayLike( args ) ? args : [ args ]; + + for ( ; i < len; i++ ) { + widget = widgets[ i ]; + rlt = widget.invoke( apiName, args ); + + if ( rlt !== IGNORE ) { + + // Deferred对象 + if ( Base.isPromise( rlt ) ) { + dfds.push( rlt ); + } else { + rlts.push( rlt ); + } + } + } + + // 如果有callback,则用异步方式。 + if ( callback || dfds.length ) { + promise = Base.when.apply( Base, dfds ); + key = promise.pipe ? 'pipe' : 'then'; + + // 很重要不能删除。删除了会死循环。 + // 保证执行顺序。让callback总是在下一个 tick 中执行。 + return promise[ key ](function() { + var deferred = Base.Deferred(), + args = arguments; + + if ( args.length === 1 ) { + args = args[ 0 ]; + } + + setTimeout(function() { + deferred.resolve( args ); + }, 1 ); + + return deferred.promise(); + })[ callback ? key : 'done' ]( callback || Base.noop ); + } else { + return rlts[ 0 ]; + } + }, + + destroy: function() { + _destroy.apply( this, arguments ); + this._widgets = null; + } + }); + + /** + * 添加组件 + * @grammar Uploader.register(proto); + * @grammar Uploader.register(map, proto); + * @param {object} responseMap API 名称与函数实现的映射 + * @param {object} proto 组件原型,构造函数通过 constructor 属性定义 + * @method Uploader.register + * @for Uploader + * @example + * Uploader.register({ + * 'make-thumb': 'makeThumb' + * }, { + * init: function( options ) {}, + * makeThumb: function() {} + * }); + * + * Uploader.register({ + * 'make-thumb': function() { + * + * } + * }); + */ + Uploader.register = Widget.register = function( responseMap, widgetProto ) { + var map = { init: 'init', destroy: 'destroy', name: 'anonymous' }, + klass; + + if ( arguments.length === 1 ) { + widgetProto = responseMap; + + // 自动生成 map 表。 + $.each(widgetProto, function(key) { + if ( key[0] === '_' || key === 'name' ) { + key === 'name' && (map.name = widgetProto.name); + return; + } + + map[key.replace(/[A-Z]/g, '-$&').toLowerCase()] = key; + }); + + } else { + map = $.extend( map, responseMap ); + } + + widgetProto.responseMap = map; + klass = Base.inherits( Widget, widgetProto ); + klass._name = map.name; + widgetClass.push( klass ); + + return klass; + }; + + /** + * 删除插件,只有在注册时指定了名字的才能被删除。 + * @grammar Uploader.unRegister(name); + * @param {string} name 组件名字 + * @method Uploader.unRegister + * @for Uploader + * @example + * + * Uploader.register({ + * name: 'custom', + * + * 'make-thumb': function() { + * + * } + * }); + * + * Uploader.unRegister('custom'); + */ + Uploader.unRegister = Widget.unRegister = function( name ) { + if ( !name || name === 'anonymous' ) { + return; + } + + // 删除指定的插件。 + for ( var i = widgetClass.length; i--; ) { + if ( widgetClass[i]._name === name ) { + widgetClass.splice(i, 1) + } + } + }; + + return Widget; + }); + /** + * @fileOverview DragAndDrop Widget。 + */ + define('widgets/filednd',[ + 'base', + 'uploader', + 'lib/dnd', + 'widgets/widget' + ], function( Base, Uploader, Dnd ) { + var $ = Base.$; + + Uploader.options.dnd = ''; + + /** + * @property {Selector} [dnd=undefined] 指定Drag And Drop拖拽的容器,如果不指定,则不启动。 + * @namespace options + * @for Uploader + */ + + /** + * @property {Selector} [disableGlobalDnd=false] 是否禁掉整个页面的拖拽功能,如果不禁用,图片拖进来的时候会默认被浏览器打开。 + * @namespace options + * @for Uploader + */ + + /** + * @event dndAccept + * @param {DataTransferItemList} items DataTransferItem + * @description 阻止此事件可以拒绝某些类型的文件拖入进来。目前只有 chrome 提供这样的 API,且只能通过 mime-type 验证。 + * @for Uploader + */ + return Uploader.register({ + name: 'dnd', + + init: function( opts ) { + + if ( !opts.dnd || + this.request('predict-runtime-type') !== 'html5' ) { + return; + } + + var me = this, + deferred = Base.Deferred(), + options = $.extend({}, { + disableGlobalDnd: opts.disableGlobalDnd, + container: opts.dnd, + accept: opts.accept + }), + dnd; + + this.dnd = dnd = new Dnd( options ); + + dnd.once( 'ready', deferred.resolve ); + dnd.on( 'drop', function( files ) { + me.request( 'add-file', [ files ]); + }); + + // 检测文件是否全部允许添加。 + dnd.on( 'accept', function( items ) { + return me.owner.trigger( 'dndAccept', items ); + }); + + dnd.init(); + + return deferred.promise(); + }, + + destroy: function() { + this.dnd && this.dnd.destroy(); + } + }); + }); + + /** + * @fileOverview 错误信息 + */ + define('lib/filepaste',[ + 'base', + 'mediator', + 'runtime/client' + ], function( Base, Mediator, RuntimeClent ) { + + var $ = Base.$; + + function FilePaste( opts ) { + opts = this.options = $.extend({}, opts ); + opts.container = $( opts.container || document.body ); + RuntimeClent.call( this, 'FilePaste' ); + } + + Base.inherits( RuntimeClent, { + constructor: FilePaste, + + init: function() { + var me = this; + + me.connectRuntime( me.options, function() { + me.exec('init'); + me.trigger('ready'); + }); + } + }); + + Mediator.installTo( FilePaste.prototype ); + + return FilePaste; + }); + /** + * @fileOverview 组件基类。 + */ + define('widgets/filepaste',[ + 'base', + 'uploader', + 'lib/filepaste', + 'widgets/widget' + ], function( Base, Uploader, FilePaste ) { + var $ = Base.$; + + /** + * @property {Selector} [paste=undefined] 指定监听paste事件的容器,如果不指定,不启用此功能。此功能为通过粘贴来添加截屏的图片。建议设置为`document.body`. + * @namespace options + * @for Uploader + */ + return Uploader.register({ + name: 'paste', + + init: function( opts ) { + + if ( !opts.paste || + this.request('predict-runtime-type') !== 'html5' ) { + return; + } + + var me = this, + deferred = Base.Deferred(), + options = $.extend({}, { + container: opts.paste, + accept: opts.accept + }), + paste; + + this.paste = paste = new FilePaste( options ); + + paste.once( 'ready', deferred.resolve ); + paste.on( 'paste', function( files ) { + me.owner.request( 'add-file', [ files ]); + }); + paste.init(); + + return deferred.promise(); + }, + + destroy: function() { + this.paste && this.paste.destroy(); + } + }); + }); + /** + * @fileOverview Blob + */ + define('lib/blob',[ + 'base', + 'runtime/client' + ], function( Base, RuntimeClient ) { + + function Blob( ruid, source ) { + var me = this; + + me.source = source; + me.ruid = ruid; + this.size = source.size || 0; + + // 如果没有指定 mimetype, 但是知道文件后缀。 + if ( !source.type && this.ext && + ~'jpg,jpeg,png,gif,bmp'.indexOf( this.ext ) ) { + this.type = 'image/' + (this.ext === 'jpg' ? 'jpeg' : this.ext); + } else { + this.type = source.type || 'application/octet-stream'; + } + + RuntimeClient.call( me, 'Blob' ); + this.uid = source.uid || this.uid; + + if ( ruid ) { + me.connectRuntime( ruid ); + } + } + + Base.inherits( RuntimeClient, { + constructor: Blob, + + slice: function( start, end ) { + return this.exec( 'slice', start, end ); + }, + + getSource: function() { + return this.source; + } + }); + + return Blob; + }); + /** + * 为了统一化Flash的File和HTML5的File而存在。 + * 以至于要调用Flash里面的File,也可以像调用HTML5版本的File一下。 + * @fileOverview File + */ + define('lib/file',[ + 'base', + 'lib/blob' + ], function( Base, Blob ) { + + var uid = 1, + rExt = /\.([^.]+)$/; + + function File( ruid, file ) { + var ext; + + this.name = file.name || ('untitled' + uid++); + ext = rExt.exec( file.name ) ? RegExp.$1.toLowerCase() : ''; + + // todo 支持其他类型文件的转换。 + // 如果有 mimetype, 但是文件名里面没有找出后缀规律 + if ( !ext && file.type ) { + ext = /\/(jpg|jpeg|png|gif|bmp)$/i.exec( file.type ) ? + RegExp.$1.toLowerCase() : ''; + this.name += '.' + ext; + } + + this.ext = ext; + this.lastModifiedDate = file.lastModifiedDate || + (new Date()).toLocaleString(); + + Blob.apply( this, arguments ); + } + + return Base.inherits( Blob, File ); + }); + + /** + * @fileOverview 错误信息 + */ + define('lib/filepicker',[ + 'base', + 'runtime/client', + 'lib/file' + ], function( Base, RuntimeClent, File ) { + + var $ = Base.$; + + function FilePicker( opts ) { + opts = this.options = $.extend({}, FilePicker.options, opts ); + + opts.container = $( opts.id ); + + if ( !opts.container.length ) { + throw new Error('按钮指定错误'); + } + + opts.innerHTML = opts.innerHTML || opts.label || + opts.container.html() || ''; + + opts.button = $( opts.button || document.createElement('div') ); + opts.button.html( opts.innerHTML ); + opts.container.html( opts.button ); + + RuntimeClent.call( this, 'FilePicker', true ); + } + + FilePicker.options = { + button: null, + container: null, + label: null, + innerHTML: null, + multiple: true, + accept: null, + name: 'file' + }; + + Base.inherits( RuntimeClent, { + constructor: FilePicker, + + init: function() { + var me = this, + opts = me.options, + button = opts.button; + + button.addClass('webuploader-pick'); + + me.on( 'all', function( type ) { + var files; + + switch ( type ) { + case 'mouseenter': + button.addClass('webuploader-pick-hover'); + break; + + case 'mouseleave': + button.removeClass('webuploader-pick-hover'); + break; + + case 'change': + files = me.exec('getFiles'); + me.trigger( 'select', $.map( files, function( file ) { + file = new File( me.getRuid(), file ); + + // 记录来源。 + file._refer = opts.container; + return file; + }), opts.container ); + break; + } + }); + + me.connectRuntime( opts, function() { + me.refresh(); + me.exec( 'init', opts ); + me.trigger('ready'); + }); + + this._resizeHandler = Base.bindFn( this.refresh, this ); + $( window ).on( 'resize', this._resizeHandler ); + }, + + refresh: function() { + var shimContainer = this.getRuntime().getContainer(), + button = this.options.button, + width = button.outerWidth ? + button.outerWidth() : button.width(), + + height = button.outerHeight ? + button.outerHeight() : button.height(), + + pos = button.offset(); + + width && height && shimContainer.css({ + bottom: 'auto', + right: 'auto', + width: width + 'px', + height: height + 'px' + }).offset( pos ); + }, + + enable: function() { + var btn = this.options.button; + + btn.removeClass('webuploader-pick-disable'); + this.refresh(); + }, + + disable: function() { + var btn = this.options.button; + + this.getRuntime().getContainer().css({ + top: '-99999px' + }); + + btn.addClass('webuploader-pick-disable'); + }, + + destroy: function() { + var btn = this.options.button; + $( window ).off( 'resize', this._resizeHandler ); + btn.removeClass('webuploader-pick-disable webuploader-pick-hover ' + + 'webuploader-pick'); + } + }); + + return FilePicker; + }); + + /** + * @fileOverview 文件选择相关 + */ + define('widgets/filepicker',[ + 'base', + 'uploader', + 'lib/filepicker', + 'widgets/widget' + ], function( Base, Uploader, FilePicker ) { + var $ = Base.$; + + $.extend( Uploader.options, { + + /** + * @property {Selector | Object} [pick=undefined] + * @namespace options + * @for Uploader + * @description 指定选择文件的按钮容器,不指定则不创建按钮。 + * + * * `id` {Seletor|dom} 指定选择文件的按钮容器,不指定则不创建按钮。**注意** 这里虽然写的是 id, 但是不是只支持 id, 还支持 class, 或者 dom 节点。 + * * `label` {String} 请采用 `innerHTML` 代替 + * * `innerHTML` {String} 指定按钮文字。不指定时优先从指定的容器中看是否自带文字。 + * * `multiple` {Boolean} 是否开起同时选择多个文件能力。 + */ + pick: null, + + /** + * @property {Arroy} [accept=null] + * @namespace options + * @for Uploader + * @description 指定接受哪些类型的文件。 由于目前还有ext转mimeType表,所以这里需要分开指定。 + * + * * `title` {String} 文字描述 + * * `extensions` {String} 允许的文件后缀,不带点,多个用逗号分割。 + * * `mimeTypes` {String} 多个用逗号分割。 + * + * 如: + * + * ``` + * { + * title: 'Images', + * extensions: 'gif,jpg,jpeg,bmp,png', + * mimeTypes: 'image/*' + * } + * ``` + */ + accept: null/*{ + title: 'Images', + extensions: 'gif,jpg,jpeg,bmp,png', + mimeTypes: 'image/*' + }*/ + }); + + return Uploader.register({ + name: 'picker', + + init: function( opts ) { + this.pickers = []; + return opts.pick && this.addBtn( opts.pick ); + }, + + refresh: function() { + $.each( this.pickers, function() { + this.refresh(); + }); + }, + + /** + * @method addButton + * @for Uploader + * @grammar addButton( pick ) => Promise + * @description + * 添加文件选择按钮,如果一个按钮不够,需要调用此方法来添加。参数跟[options.pick](#WebUploader:Uploader:options)一致。 + * @example + * uploader.addButton({ + * id: '#btnContainer', + * innerHTML: '选择文件' + * }); + */ + addBtn: function( pick ) { + var me = this, + opts = me.options, + accept = opts.accept, + promises = []; + + if ( !pick ) { + return; + } + + $.isPlainObject( pick ) || (pick = { + id: pick + }); + + $( pick.id ).each(function() { + var options, picker, deferred; + + deferred = Base.Deferred(); + + options = $.extend({}, pick, { + accept: $.isPlainObject( accept ) ? [ accept ] : accept, + swf: opts.swf, + runtimeOrder: opts.runtimeOrder, + id: this + }); + + picker = new FilePicker( options ); + + picker.once( 'ready', deferred.resolve ); + picker.on( 'select', function( files ) { + me.owner.request( 'add-file', [ files ]); + }); + picker.init(); + + me.pickers.push( picker ); + + promises.push( deferred.promise() ); + }); + + return Base.when.apply( Base, promises ); + }, + + disable: function() { + $.each( this.pickers, function() { + this.disable(); + }); + }, + + enable: function() { + $.each( this.pickers, function() { + this.enable(); + }); + }, + + destroy: function() { + $.each( this.pickers, function() { + this.destroy(); + }); + this.pickers = null; + } + }); + }); + /** + * @fileOverview Image + */ + define('lib/image',[ + 'base', + 'runtime/client', + 'lib/blob' + ], function( Base, RuntimeClient, Blob ) { + var $ = Base.$; + + // 构造器。 + function Image( opts ) { + this.options = $.extend({}, Image.options, opts ); + RuntimeClient.call( this, 'Image' ); + + this.on( 'load', function() { + this._info = this.exec('info'); + this._meta = this.exec('meta'); + }); + } + + // 默认选项。 + Image.options = { + + // 默认的图片处理质量 + quality: 90, + + // 是否裁剪 + crop: false, + + // 是否保留头部信息 + preserveHeaders: false, + + // 是否允许放大。 + allowMagnify: false + }; + + // 继承RuntimeClient. + Base.inherits( RuntimeClient, { + constructor: Image, + + info: function( val ) { + + // setter + if ( val ) { + this._info = val; + return this; + } + + // getter + return this._info; + }, + + meta: function( val ) { + + // setter + if ( val ) { + this._meta = val; + return this; + } + + // getter + return this._meta; + }, + + loadFromBlob: function( blob ) { + var me = this, + ruid = blob.getRuid(); + + this.connectRuntime( ruid, function() { + me.exec( 'init', me.options ); + me.exec( 'loadFromBlob', blob ); + }); + }, + + resize: function() { + var args = Base.slice( arguments ); + return this.exec.apply( this, [ 'resize' ].concat( args ) ); + }, + + crop: function() { + var args = Base.slice( arguments ); + return this.exec.apply( this, [ 'crop' ].concat( args ) ); + }, + + getAsDataUrl: function( type ) { + return this.exec( 'getAsDataUrl', type ); + }, + + getAsBlob: function( type ) { + var blob = this.exec( 'getAsBlob', type ); + + return new Blob( this.getRuid(), blob ); + } + }); + + return Image; + }); + /** + * @fileOverview 图片操作, 负责预览图片和上传前压缩图片 + */ + define('widgets/image',[ + 'base', + 'uploader', + 'lib/image', + 'widgets/widget' + ], function( Base, Uploader, Image ) { + + var $ = Base.$, + throttle; + + // 根据要处理的文件大小来节流,一次不能处理太多,会卡。 + throttle = (function( max ) { + var occupied = 0, + waiting = [], + tick = function() { + var item; + + while ( waiting.length && occupied < max ) { + item = waiting.shift(); + occupied += item[ 0 ]; + item[ 1 ](); + } + }; + + return function( emiter, size, cb ) { + waiting.push([ size, cb ]); + emiter.once( 'destroy', function() { + occupied -= size; + setTimeout( tick, 1 ); + }); + setTimeout( tick, 1 ); + }; + })( 5 * 1024 * 1024 ); + + $.extend( Uploader.options, { + + /** + * @property {Object} [thumb] + * @namespace options + * @for Uploader + * @description 配置生成缩略图的选项。 + * + * 默认为: + * + * ```javascript + * { + * width: 110, + * height: 110, + * + * // 图片质量,只有type为`image/jpeg`的时候才有效。 + * quality: 70, + * + * // 是否允许放大,如果想要生成小图的时候不失真,此选项应该设置为false. + * allowMagnify: true, + * + * // 是否允许裁剪。 + * crop: true, + * + * // 为空的话则保留原有图片格式。 + * // 否则强制转换成指定的类型。 + * type: 'image/jpeg' + * } + * ``` + */ + thumb: { + width: 110, + height: 110, + quality: 70, + allowMagnify: true, + crop: true, + preserveHeaders: false, + + // 为空的话则保留原有图片格式。 + // 否则强制转换成指定的类型。 + // IE 8下面 base64 大小不能超过 32K 否则预览失败,而非 jpeg 编码的图片很可 + // 能会超过 32k, 所以这里设置成预览的时候都是 image/jpeg + type: 'image/jpeg' + }, + + /** + * @property {Object} [compress] + * @namespace options + * @for Uploader + * @description 配置压缩的图片的选项。如果此选项为`false`, 则图片在上传前不进行压缩。 + * + * 默认为: + * + * ```javascript + * { + * width: 1600, + * height: 1600, + * + * // 图片质量,只有type为`image/jpeg`的时候才有效。 + * quality: 90, + * + * // 是否允许放大,如果想要生成小图的时候不失真,此选项应该设置为false. + * allowMagnify: false, + * + * // 是否允许裁剪。 + * crop: false, + * + * // 是否保留头部meta信息。 + * preserveHeaders: true, + * + * // 如果发现压缩后文件大小比原来还大,则使用原来图片 + * // 此属性可能会影响图片自动纠正功能 + * noCompressIfLarger: false, + * + * // 单位字节,如果图片大小小于此值,不会采用压缩。 + * compressSize: 0 + * } + * ``` + */ + compress: { + width: 1600, + height: 1600, + quality: 90, + allowMagnify: false, + crop: false, + preserveHeaders: true + } + }); + + return Uploader.register({ + + name: 'image', + + + /** + * 生成缩略图,此过程为异步,所以需要传入`callback`。 + * 通常情况在图片加入队里后调用此方法来生成预览图以增强交互效果。 + * + * 当 width 或者 height 的值介于 0 - 1 时,被当成百分比使用。 + * + * `callback`中可以接收到两个参数。 + * * 第一个为error,如果生成缩略图有错误,此error将为真。 + * * 第二个为ret, 缩略图的Data URL值。 + * + * **注意** + * Date URL在IE6/7中不支持,所以不用调用此方法了,直接显示一张暂不支持预览图片好了。 + * 也可以借助服务端,将 base64 数据传给服务端,生成一个临时文件供预览。 + * + * @method makeThumb + * @grammar makeThumb( file, callback ) => undefined + * @grammar makeThumb( file, callback, width, height ) => undefined + * @for Uploader + * @example + * + * uploader.on( 'fileQueued', function( file ) { + * var $li = ...; + * + * uploader.makeThumb( file, function( error, ret ) { + * if ( error ) { + * $li.text('预览错误'); + * } else { + * $li.append(''); + * } + * }); + * + * }); + */ + makeThumb: function( file, cb, width, height ) { + var opts, image; + + file = this.request( 'get-file', file ); + + // 只预览图片格式。 + if ( !file.type.match( /^image/ ) ) { + cb( true ); + return; + } + + opts = $.extend({}, this.options.thumb ); + + // 如果传入的是object. + if ( $.isPlainObject( width ) ) { + opts = $.extend( opts, width ); + width = null; + } + + width = width || opts.width; + height = height || opts.height; + + image = new Image( opts ); + + image.once( 'load', function() { + file._info = file._info || image.info(); + file._meta = file._meta || image.meta(); + + // 如果 width 的值介于 0 - 1 + // 说明设置的是百分比。 + if ( width <= 1 && width > 0 ) { + width = file._info.width * width; + } + + // 同样的规则应用于 height + if ( height <= 1 && height > 0 ) { + height = file._info.height * height; + } + + image.resize( width, height ); + }); + + // 当 resize 完后 + image.once( 'complete', function() { + cb( false, image.getAsDataUrl( opts.type ) ); + image.destroy(); + }); + + image.once( 'error', function( reason ) { + cb( reason || true ); + image.destroy(); + }); + + throttle( image, file.source.size, function() { + file._info && image.info( file._info ); + file._meta && image.meta( file._meta ); + image.loadFromBlob( file.source ); + }); + }, + + beforeSendFile: function( file ) { + var opts = this.options.compress || this.options.resize, + compressSize = opts && opts.compressSize || 0, + noCompressIfLarger = opts && opts.noCompressIfLarger || false, + image, deferred; + + file = this.request( 'get-file', file ); + + // 只压缩 jpeg 图片格式。 + // gif 可能会丢失针 + // bmp png 基本上尺寸都不大,且压缩比比较小。 + if ( !opts || !~'image/jpeg,image/jpg'.indexOf( file.type ) || + file.size < compressSize || + file._compressed ) { + return; + } + + opts = $.extend({}, opts ); + deferred = Base.Deferred(); + + image = new Image( opts ); + + deferred.always(function() { + image.destroy(); + image = null; + }); + image.once( 'error', deferred.reject ); + image.once( 'load', function() { + var width = opts.width, + height = opts.height; + + file._info = file._info || image.info(); + file._meta = file._meta || image.meta(); + + // 如果 width 的值介于 0 - 1 + // 说明设置的是百分比。 + if ( width <= 1 && width > 0 ) { + width = file._info.width * width; + } + + // 同样的规则应用于 height + if ( height <= 1 && height > 0 ) { + height = file._info.height * height; + } + + image.resize( width, height ); + }); + + image.once( 'complete', function() { + var blob, size; + + // 移动端 UC / qq 浏览器的无图模式下 + // ctx.getImageData 处理大图的时候会报 Exception + // INDEX_SIZE_ERR: DOM Exception 1 + try { + blob = image.getAsBlob( opts.type ); + + size = file.size; + + // 如果压缩后,比原来还大则不用压缩后的。 + if ( !noCompressIfLarger || blob.size < size ) { + // file.source.destroy && file.source.destroy(); + file.source = blob; + file.size = blob.size; + + file.trigger( 'resize', blob.size, size ); + } + + // 标记,避免重复压缩。 + file._compressed = true; + deferred.resolve(); + } catch ( e ) { + // 出错了直接继续,让其上传原始图片 + deferred.resolve(); + } + }); + + file._info && image.info( file._info ); + file._meta && image.meta( file._meta ); + + image.loadFromBlob( file.source ); + return deferred.promise(); + } + }); + }); + /** + * @fileOverview 文件属性封装 + */ + define('file',[ + 'base', + 'mediator' + ], function( Base, Mediator ) { + + var $ = Base.$, + idPrefix = 'WU_FILE_', + idSuffix = 0, + rExt = /\.([^.]+)$/, + statusMap = {}; + + function gid() { + return idPrefix + idSuffix++; + } + + /** + * 文件类 + * @class File + * @constructor 构造函数 + * @grammar new File( source ) => File + * @param {Lib.File} source [lib.File](#Lib.File)实例, 此source对象是带有Runtime信息的。 + */ + function WUFile( source ) { + + /** + * 文件名,包括扩展名(后缀) + * @property name + * @type {string} + */ + this.name = source.name || 'Untitled'; + + /** + * 文件体积(字节) + * @property size + * @type {uint} + * @default 0 + */ + this.size = source.size || 0; + + /** + * 文件MIMETYPE类型,与文件类型的对应关系请参考[http://t.cn/z8ZnFny](http://t.cn/z8ZnFny) + * @property type + * @type {string} + * @default 'application/octet-stream' + */ + this.type = source.type || 'application/octet-stream'; + + /** + * 文件最后修改日期 + * @property lastModifiedDate + * @type {int} + * @default 当前时间戳 + */ + this.lastModifiedDate = source.lastModifiedDate || (new Date() * 1); + + /** + * 文件ID,每个对象具有唯一ID,与文件名无关 + * @property id + * @type {string} + */ + this.id = gid(); + + /** + * 文件扩展名,通过文件名获取,例如test.png的扩展名为png + * @property ext + * @type {string} + */ + this.ext = rExt.exec( this.name ) ? RegExp.$1 : ''; + + + /** + * 状态文字说明。在不同的status语境下有不同的用途。 + * @property statusText + * @type {string} + */ + this.statusText = ''; + + // 存储文件状态,防止通过属性直接修改 + statusMap[ this.id ] = WUFile.Status.INITED; + + this.source = source; + this.loaded = 0; + + this.on( 'error', function( msg ) { + this.setStatus( WUFile.Status.ERROR, msg ); + }); + } + + $.extend( WUFile.prototype, { + + /** + * 设置状态,状态变化时会触发`change`事件。 + * @method setStatus + * @grammar setStatus( status[, statusText] ); + * @param {File.Status|String} status [文件状态值](#WebUploader:File:File.Status) + * @param {String} [statusText=''] 状态说明,常在error时使用,用http, abort,server等来标记是由于什么原因导致文件错误。 + */ + setStatus: function( status, text ) { + + var prevStatus = statusMap[ this.id ]; + + typeof text !== 'undefined' && (this.statusText = text); + + if ( status !== prevStatus ) { + statusMap[ this.id ] = status; + /** + * 文件状态变化 + * @event statuschange + */ + this.trigger( 'statuschange', status, prevStatus ); + } + + }, + + /** + * 获取文件状态 + * @return {File.Status} + * @example + 文件状态具体包括以下几种类型: + { + // 初始化 + INITED: 0, + // 已入队列 + QUEUED: 1, + // 正在上传 + PROGRESS: 2, + // 上传出错 + ERROR: 3, + // 上传成功 + COMPLETE: 4, + // 上传取消 + CANCELLED: 5 + } + */ + getStatus: function() { + return statusMap[ this.id ]; + }, + + /** + * 获取文件原始信息。 + * @return {*} + */ + getSource: function() { + return this.source; + }, + + destroy: function() { + this.off(); + delete statusMap[ this.id ]; + } + }); + + Mediator.installTo( WUFile.prototype ); + + /** + * 文件状态值,具体包括以下几种类型: + * * `inited` 初始状态 + * * `queued` 已经进入队列, 等待上传 + * * `progress` 上传中 + * * `complete` 上传完成。 + * * `error` 上传出错,可重试 + * * `interrupt` 上传中断,可续传。 + * * `invalid` 文件不合格,不能重试上传。会自动从队列中移除。 + * * `cancelled` 文件被移除。 + * @property {Object} Status + * @namespace File + * @class File + * @static + */ + WUFile.Status = { + INITED: 'inited', // 初始状态 + QUEUED: 'queued', // 已经进入队列, 等待上传 + PROGRESS: 'progress', // 上传中 + ERROR: 'error', // 上传出错,可重试 + COMPLETE: 'complete', // 上传完成。 + CANCELLED: 'cancelled', // 上传取消。 + INTERRUPT: 'interrupt', // 上传中断,可续传。 + INVALID: 'invalid' // 文件不合格,不能重试上传。 + }; + + return WUFile; + }); + + /** + * @fileOverview 文件队列 + */ + define('queue',[ + 'base', + 'mediator', + 'file' + ], function( Base, Mediator, WUFile ) { + + var $ = Base.$, + STATUS = WUFile.Status; + + /** + * 文件队列, 用来存储各个状态中的文件。 + * @class Queue + * @extends Mediator + */ + function Queue() { + + /** + * 统计文件数。 + * * `numOfQueue` 队列中的文件数。 + * * `numOfSuccess` 上传成功的文件数 + * * `numOfCancel` 被取消的文件数 + * * `numOfProgress` 正在上传中的文件数 + * * `numOfUploadFailed` 上传错误的文件数。 + * * `numOfInvalid` 无效的文件数。 + * * `numofDeleted` 被移除的文件数。 + * @property {Object} stats + */ + this.stats = { + numOfQueue: 0, + numOfSuccess: 0, + numOfCancel: 0, + numOfProgress: 0, + numOfUploadFailed: 0, + numOfInvalid: 0, + numofDeleted: 0, + numofInterrupt: 0 + }; + + // 上传队列,仅包括等待上传的文件 + this._queue = []; + + // 存储所有文件 + this._map = {}; + } + + $.extend( Queue.prototype, { + + /** + * 将新文件加入对队列尾部 + * + * @method append + * @param {File} file 文件对象 + */ + append: function( file ) { + this._queue.push( file ); + this._fileAdded( file ); + return this; + }, + + /** + * 将新文件加入对队列头部 + * + * @method prepend + * @param {File} file 文件对象 + */ + prepend: function( file ) { + this._queue.unshift( file ); + this._fileAdded( file ); + return this; + }, + + /** + * 获取文件对象 + * + * @method getFile + * @param {String} fileId 文件ID + * @return {File} + */ + getFile: function( fileId ) { + if ( typeof fileId !== 'string' ) { + return fileId; + } + return this._map[ fileId ]; + }, + + /** + * 从队列中取出一个指定状态的文件。 + * @grammar fetch( status ) => File + * @method fetch + * @param {String} status [文件状态值](#WebUploader:File:File.Status) + * @return {File} [File](#WebUploader:File) + */ + fetch: function( status ) { + var len = this._queue.length, + i, file; + + status = status || STATUS.QUEUED; + + for ( i = 0; i < len; i++ ) { + file = this._queue[ i ]; + + if ( status === file.getStatus() ) { + return file; + } + } + + return null; + }, + + /** + * 对队列进行排序,能够控制文件上传顺序。 + * @grammar sort( fn ) => undefined + * @method sort + * @param {Function} fn 排序方法 + */ + sort: function( fn ) { + if ( typeof fn === 'function' ) { + this._queue.sort( fn ); + } + }, + + /** + * 获取指定类型的文件列表, 列表中每一个成员为[File](#WebUploader:File)对象。 + * @grammar getFiles( [status1[, status2 ...]] ) => Array + * @method getFiles + * @param {String} [status] [文件状态值](#WebUploader:File:File.Status) + */ + getFiles: function() { + var sts = [].slice.call( arguments, 0 ), + ret = [], + i = 0, + len = this._queue.length, + file; + + for ( ; i < len; i++ ) { + file = this._queue[ i ]; + + if ( sts.length && !~$.inArray( file.getStatus(), sts ) ) { + continue; + } + + ret.push( file ); + } + + return ret; + }, + + /** + * 在队列中删除文件。 + * @grammar removeFile( file ) => Array + * @method removeFile + * @param {File} 文件对象。 + */ + removeFile: function( file ) { + var me = this, + existing = this._map[ file.id ]; + + if ( existing ) { + delete this._map[ file.id ]; + file.destroy(); + this.stats.numofDeleted++; + } + }, + + _fileAdded: function( file ) { + var me = this, + existing = this._map[ file.id ]; + + if ( !existing ) { + this._map[ file.id ] = file; + + file.on( 'statuschange', function( cur, pre ) { + me._onFileStatusChange( cur, pre ); + }); + } + }, + + _onFileStatusChange: function( curStatus, preStatus ) { + var stats = this.stats; + + switch ( preStatus ) { + case STATUS.PROGRESS: + stats.numOfProgress--; + break; + + case STATUS.QUEUED: + stats.numOfQueue --; + break; + + case STATUS.ERROR: + stats.numOfUploadFailed--; + break; + + case STATUS.INVALID: + stats.numOfInvalid--; + break; + + case STATUS.INTERRUPT: + stats.numofInterrupt--; + break; + } + + switch ( curStatus ) { + case STATUS.QUEUED: + stats.numOfQueue++; + break; + + case STATUS.PROGRESS: + stats.numOfProgress++; + break; + + case STATUS.ERROR: + stats.numOfUploadFailed++; + break; + + case STATUS.COMPLETE: + stats.numOfSuccess++; + break; + + case STATUS.CANCELLED: + stats.numOfCancel++; + break; + + + case STATUS.INVALID: + stats.numOfInvalid++; + break; + + case STATUS.INTERRUPT: + stats.numofInterrupt++; + break; + } + } + + }); + + Mediator.installTo( Queue.prototype ); + + return Queue; + }); + /** + * @fileOverview 队列 + */ + define('widgets/queue',[ + 'base', + 'uploader', + 'queue', + 'file', + 'lib/file', + 'runtime/client', + 'widgets/widget' + ], function( Base, Uploader, Queue, WUFile, File, RuntimeClient ) { + + var $ = Base.$, + rExt = /\.\w+$/, + Status = WUFile.Status; + + return Uploader.register({ + name: 'queue', + + init: function( opts ) { + var me = this, + deferred, len, i, item, arr, accept, runtime; + + if ( $.isPlainObject( opts.accept ) ) { + opts.accept = [ opts.accept ]; + } + + // accept中的中生成匹配正则。 + if ( opts.accept ) { + arr = []; + + for ( i = 0, len = opts.accept.length; i < len; i++ ) { + item = opts.accept[ i ].extensions; + item && arr.push( item ); + } + + if ( arr.length ) { + accept = '\\.' + arr.join(',') + .replace( /,/g, '$|\\.' ) + .replace( /\*/g, '.*' ) + '$'; + } + + me.accept = new RegExp( accept, 'i' ); + } + + me.queue = new Queue(); + me.stats = me.queue.stats; + + // 如果当前不是html5运行时,那就算了。 + // 不执行后续操作 + if ( this.request('predict-runtime-type') !== 'html5' ) { + return; + } + + // 创建一个 html5 运行时的 placeholder + // 以至于外部添加原生 File 对象的时候能正确包裹一下供 webuploader 使用。 + deferred = Base.Deferred(); + this.placeholder = runtime = new RuntimeClient('Placeholder'); + runtime.connectRuntime({ + runtimeOrder: 'html5' + }, function() { + me._ruid = runtime.getRuid(); + deferred.resolve(); + }); + return deferred.promise(); + }, + + + // 为了支持外部直接添加一个原生File对象。 + _wrapFile: function( file ) { + if ( !(file instanceof WUFile) ) { + + if ( !(file instanceof File) ) { + if ( !this._ruid ) { + throw new Error('Can\'t add external files.'); + } + file = new File( this._ruid, file ); + } + + file = new WUFile( file ); + } + + return file; + }, + + // 判断文件是否可以被加入队列 + acceptFile: function( file ) { + var invalid = !file || !file.size || this.accept && + + // 如果名字中有后缀,才做后缀白名单处理。 + rExt.exec( file.name ) && !this.accept.test( file.name ); + + return !invalid; + }, + + + /** + * @event beforeFileQueued + * @param {File} file File对象 + * @description 当文件被加入队列之前触发,此事件的handler返回值为`false`,则此文件不会被添加进入队列。 + * @for Uploader + */ + + /** + * @event fileQueued + * @param {File} file File对象 + * @description 当文件被加入队列以后触发。 + * @for Uploader + */ + + _addFile: function( file ) { + var me = this; + + file = me._wrapFile( file ); + + // 不过类型判断允许不允许,先派送 `beforeFileQueued` + if ( !me.owner.trigger( 'beforeFileQueued', file ) ) { + return; + } + + // 类型不匹配,则派送错误事件,并返回。 + if ( !me.acceptFile( file ) ) { + me.owner.trigger( 'error', 'Q_TYPE_DENIED', file ); + return; + } + + me.queue.append( file ); + me.owner.trigger( 'fileQueued', file ); + return file; + }, + + getFile: function( fileId ) { + return this.queue.getFile( fileId ); + }, + + /** + * @event filesQueued + * @param {File} files 数组,内容为原始File(lib/File)对象。 + * @description 当一批文件添加进队列以后触发。 + * @for Uploader + */ + + /** + * @property {Boolean} [auto=false] + * @namespace options + * @for Uploader + * @description 设置为 true 后,不需要手动调用上传,有文件选择即开始上传。 + * + */ + + /** + * @method addFiles + * @grammar addFiles( file ) => undefined + * @grammar addFiles( [file1, file2 ...] ) => undefined + * @param {Array of File or File} [files] Files 对象 数组 + * @description 添加文件到队列 + * @for Uploader + */ + addFile: function( files ) { + var me = this; + + if ( !files.length ) { + files = [ files ]; + } + + files = $.map( files, function( file ) { + return me._addFile( file ); + }); + + me.owner.trigger( 'filesQueued', files ); + + if ( me.options.auto ) { + setTimeout(function() { + me.request('start-upload'); + }, 20 ); + } + }, + + getStats: function() { + return this.stats; + }, + + /** + * @event fileDequeued + * @param {File} file File对象 + * @description 当文件被移除队列后触发。 + * @for Uploader + */ + + /** + * @method removeFile + * @grammar removeFile( file ) => undefined + * @grammar removeFile( id ) => undefined + * @grammar removeFile( file, true ) => undefined + * @grammar removeFile( id, true ) => undefined + * @param {File|id} file File对象或这File对象的id + * @description 移除某一文件, 默认只会标记文件状态为已取消,如果第二个参数为 `true` 则会从 queue 中移除。 + * @for Uploader + * @example + * + * $li.on('click', '.remove-this', function() { + * uploader.removeFile( file ); + * }) + */ + removeFile: function( file, remove ) { + var me = this; + + file = file.id ? file : me.queue.getFile( file ); + + this.request( 'cancel-file', file ); + + if ( remove ) { + this.queue.removeFile( file ); + } + }, + + /** + * @method getFiles + * @grammar getFiles() => Array + * @grammar getFiles( status1, status2, status... ) => Array + * @description 返回指定状态的文件集合,不传参数将返回所有状态的文件。 + * @for Uploader + * @example + * console.log( uploader.getFiles() ); // => all files + * console.log( uploader.getFiles('error') ) // => all error files. + */ + getFiles: function() { + return this.queue.getFiles.apply( this.queue, arguments ); + }, + + fetchFile: function() { + return this.queue.fetch.apply( this.queue, arguments ); + }, + + /** + * @method retry + * @grammar retry() => undefined + * @grammar retry( file ) => undefined + * @description 重试上传,重试指定文件,或者从出错的文件开始重新上传。 + * @for Uploader + * @example + * function retry() { + * uploader.retry(); + * } + */ + retry: function( file, noForceStart ) { + var me = this, + files, i, len; + + if ( file ) { + file = file.id ? file : me.queue.getFile( file ); + file.setStatus( Status.QUEUED ); + noForceStart || me.request('start-upload'); + return; + } + + files = me.queue.getFiles( Status.ERROR ); + i = 0; + len = files.length; + + for ( ; i < len; i++ ) { + file = files[ i ]; + file.setStatus( Status.QUEUED ); + } + + me.request('start-upload'); + }, + + /** + * @method sort + * @grammar sort( fn ) => undefined + * @description 排序队列中的文件,在上传之前调整可以控制上传顺序。 + * @for Uploader + */ + sortFiles: function() { + return this.queue.sort.apply( this.queue, arguments ); + }, + + /** + * @event reset + * @description 当 uploader 被重置的时候触发。 + * @for Uploader + */ + + /** + * @method reset + * @grammar reset() => undefined + * @description 重置uploader。目前只重置了队列。 + * @for Uploader + * @example + * uploader.reset(); + */ + reset: function() { + this.owner.trigger('reset'); + this.queue = new Queue(); + this.stats = this.queue.stats; + }, + + destroy: function() { + this.reset(); + this.placeholder && this.placeholder.destroy(); + } + }); + + }); + /** + * @fileOverview 添加获取Runtime相关信息的方法。 + */ + define('widgets/runtime',[ + 'uploader', + 'runtime/runtime', + 'widgets/widget' + ], function( Uploader, Runtime ) { + + Uploader.support = function() { + return Runtime.hasRuntime.apply( Runtime, arguments ); + }; + + /** + * @property {Object} [runtimeOrder=html5,flash] + * @namespace options + * @for Uploader + * @description 指定运行时启动顺序。默认会想尝试 html5 是否支持,如果支持则使用 html5, 否则则使用 flash. + * + * 可以将此值设置成 `flash`,来强制使用 flash 运行时。 + */ + + return Uploader.register({ + name: 'runtime', + + init: function() { + if ( !this.predictRuntimeType() ) { + throw Error('Runtime Error'); + } + }, + + /** + * 预测Uploader将采用哪个`Runtime` + * @grammar predictRuntimeType() => String + * @method predictRuntimeType + * @for Uploader + */ + predictRuntimeType: function() { + var orders = this.options.runtimeOrder || Runtime.orders, + type = this.type, + i, len; + + if ( !type ) { + orders = orders.split( /\s*,\s*/g ); + + for ( i = 0, len = orders.length; i < len; i++ ) { + if ( Runtime.hasRuntime( orders[ i ] ) ) { + this.type = type = orders[ i ]; + break; + } + } + } + + return type; + } + }); + }); + /** + * @fileOverview Transport + */ + define('lib/transport',[ + 'base', + 'runtime/client', + 'mediator' + ], function( Base, RuntimeClient, Mediator ) { + + var $ = Base.$; + + function Transport( opts ) { + var me = this; + + opts = me.options = $.extend( true, {}, Transport.options, opts || {} ); + RuntimeClient.call( this, 'Transport' ); + + this._blob = null; + this._formData = opts.formData || {}; + this._headers = opts.headers || {}; + + this.on( 'progress', this._timeout ); + this.on( 'load error', function() { + me.trigger( 'progress', 1 ); + clearTimeout( me._timer ); + }); + } + + Transport.options = { + server: '', + method: 'POST', + + // 跨域时,是否允许携带cookie, 只有html5 runtime才有效 + withCredentials: false, + fileVal: 'file', + timeout: 2 * 60 * 1000, // 2分钟 + formData: {}, + headers: {}, + sendAsBinary: false + }; + + $.extend( Transport.prototype, { + + // 添加Blob, 只能添加一次,最后一次有效。 + appendBlob: function( key, blob, filename ) { + var me = this, + opts = me.options; + + if ( me.getRuid() ) { + me.disconnectRuntime(); + } + + // 连接到blob归属的同一个runtime. + me.connectRuntime( blob.ruid, function() { + me.exec('init'); + }); + + me._blob = blob; + opts.fileVal = key || opts.fileVal; + opts.filename = filename || opts.filename; + }, + + // 添加其他字段 + append: function( key, value ) { + if ( typeof key === 'object' ) { + $.extend( this._formData, key ); + } else { + this._formData[ key ] = value; + } + }, + + setRequestHeader: function( key, value ) { + if ( typeof key === 'object' ) { + $.extend( this._headers, key ); + } else { + this._headers[ key ] = value; + } + }, + + send: function( method ) { + this.exec( 'send', method ); + this._timeout(); + }, + + abort: function() { + clearTimeout( this._timer ); + return this.exec('abort'); + }, + + destroy: function() { + this.trigger('destroy'); + this.off(); + this.exec('destroy'); + this.disconnectRuntime(); + }, + + getResponse: function() { + return this.exec('getResponse'); + }, + + getResponseAsJson: function() { + return this.exec('getResponseAsJson'); + }, + + getStatus: function() { + return this.exec('getStatus'); + }, + + _timeout: function() { + var me = this, + duration = me.options.timeout; + + if ( !duration ) { + return; + } + + clearTimeout( me._timer ); + me._timer = setTimeout(function() { + me.abort(); + me.trigger( 'error', 'timeout' ); + }, duration ); + } + + }); + + // 让Transport具备事件功能。 + Mediator.installTo( Transport.prototype ); + + return Transport; + }); + /** + * @fileOverview 负责文件上传相关。 + */ + define('widgets/upload',[ + 'base', + 'uploader', + 'file', + 'lib/transport', + 'widgets/widget' + ], function( Base, Uploader, WUFile, Transport ) { + + var $ = Base.$, + isPromise = Base.isPromise, + Status = WUFile.Status; + + // 添加默认配置项 + $.extend( Uploader.options, { + + + /** + * @property {Boolean} [prepareNextFile=false] + * @namespace options + * @for Uploader + * @description 是否允许在文件传输时提前把下一个文件准备好。 + * 对于一个文件的准备工作比较耗时,比如图片压缩,md5序列化。 + * 如果能提前在当前文件传输期处理,可以节省总体耗时。 + */ + prepareNextFile: false, + + /** + * @property {Boolean} [chunked=false] + * @namespace options + * @for Uploader + * @description 是否要分片处理大文件上传。 + */ + chunked: false, + + /** + * @property {Boolean} [chunkSize=5242880] + * @namespace options + * @for Uploader + * @description 如果要分片,分多大一片? 默认大小为5M. + */ + chunkSize: 5 * 1024 * 1024, + + /** + * @property {Boolean} [chunkRetry=2] + * @namespace options + * @for Uploader + * @description 如果某个分片由于网络问题出错,允许自动重传多少次? + */ + chunkRetry: 2, + + /** + * @property {Boolean} [threads=3] + * @namespace options + * @for Uploader + * @description 上传并发数。允许同时最大上传进程数。 + */ + threads: 3, + + + /** + * @property {Object} [formData={}] + * @namespace options + * @for Uploader + * @description 文件上传请求的参数表,每次发送都会发送此对象中的参数。 + */ + formData: {} + + /** + * @property {Object} [fileVal='file'] + * @namespace options + * @for Uploader + * @description 设置文件上传域的name。 + */ + + /** + * @property {Object} [method='POST'] + * @namespace options + * @for Uploader + * @description 文件上传方式,`POST`或者`GET`。 + */ + + /** + * @property {Object} [sendAsBinary=false] + * @namespace options + * @for Uploader + * @description 是否已二进制的流的方式发送文件,这样整个上传内容`php://input`都为文件内容, + * 其他参数在$_GET数组中。 + */ + }); + + // 负责将文件切片。 + function CuteFile( file, chunkSize ) { + var pending = [], + blob = file.source, + total = blob.size, + chunks = chunkSize ? Math.ceil( total / chunkSize ) : 1, + start = 0, + index = 0, + len, api; + + api = { + file: file, + + has: function() { + return !!pending.length; + }, + + shift: function() { + return pending.shift(); + }, + + unshift: function( block ) { + pending.unshift( block ); + } + }; + + while ( index < chunks ) { + len = Math.min( chunkSize, total - start ); + + pending.push({ + file: file, + start: start, + end: chunkSize ? (start + len) : total, + total: total, + chunks: chunks, + chunk: index++, + cuted: api + }); + start += len; + } + + file.blocks = pending.concat(); + file.remaning = pending.length; + + return api; + } + + Uploader.register({ + name: 'upload', + + init: function() { + var owner = this.owner, + me = this; + + this.runing = false; + this.progress = false; + + owner + .on( 'startUpload', function() { + me.progress = true; + }) + .on( 'uploadFinished', function() { + me.progress = false; + }); + + // 记录当前正在传的数据,跟threads相关 + this.pool = []; + + // 缓存分好片的文件。 + this.stack = []; + + // 缓存即将上传的文件。 + this.pending = []; + + // 跟踪还有多少分片在上传中但是没有完成上传。 + this.remaning = 0; + this.__tick = Base.bindFn( this._tick, this ); + + owner.on( 'uploadComplete', function( file ) { + + // 把其他块取消了。 + file.blocks && $.each( file.blocks, function( _, v ) { + v.transport && (v.transport.abort(), v.transport.destroy()); + delete v.transport; + }); + + delete file.blocks; + delete file.remaning; + }); + }, + + reset: function() { + this.request( 'stop-upload', true ); + this.runing = false; + this.pool = []; + this.stack = []; + this.pending = []; + this.remaning = 0; + this._trigged = false; + this._promise = null; + }, + + /** + * @event startUpload + * @description 当开始上传流程时触发。 + * @for Uploader + */ + + /** + * 开始上传。此方法可以从初始状态调用开始上传流程,也可以从暂停状态调用,继续上传流程。 + * + * 可以指定开始某一个文件。 + * @grammar upload() => undefined + * @grammar upload( file | fileId) => undefined + * @method upload + * @for Uploader + */ + startUpload: function(file) { + var me = this; + + // 移出invalid的文件 + $.each( me.request( 'get-files', Status.INVALID ), function() { + me.request( 'remove-file', this ); + }); + + // 如果指定了开始某个文件,则只开始指定文件。 + if ( file ) { + file = file.id ? file : me.request( 'get-file', file ); + + if (file.getStatus() === Status.INTERRUPT) { + $.each( me.pool, function( _, v ) { + + // 之前暂停过。 + if (v.file !== file) { + return; + } + + v.transport && v.transport.send(); + }); + + file.setStatus( Status.QUEUED ); + } else if (file.getStatus() === Status.PROGRESS) { + return; + } else { + file.setStatus( Status.QUEUED ); + } + } else { + $.each( me.request( 'get-files', [ Status.INITED ] ), function() { + this.setStatus( Status.QUEUED ); + }); + } + + if ( me.runing ) { + return; + } + + me.runing = true; + + var files = []; + + // 如果有暂停的,则续传 + $.each( me.pool, function( _, v ) { + var file = v.file; + + if ( file.getStatus() === Status.INTERRUPT ) { + files.push(file); + me._trigged = false; + v.transport && v.transport.send(); + } + }); + + var file; + while ( (file = files.shift()) ) { + file.setStatus( Status.PROGRESS ); + } + + file || $.each( me.request( 'get-files', + Status.INTERRUPT ), function() { + this.setStatus( Status.PROGRESS ); + }); + + me._trigged = false; + Base.nextTick( me.__tick ); + me.owner.trigger('startUpload'); + }, + + /** + * @event stopUpload + * @description 当开始上传流程暂停时触发。 + * @for Uploader + */ + + /** + * 暂停上传。第一个参数为是否中断上传当前正在上传的文件。 + * + * 如果第一个参数是文件,则只暂停指定文件。 + * @grammar stop() => undefined + * @grammar stop( true ) => undefined + * @grammar stop( file ) => undefined + * @method stop + * @for Uploader + */ + stopUpload: function( file, interrupt ) { + var me = this; + + if (file === true) { + interrupt = file; + file = null; + } + + if ( me.runing === false ) { + return; + } + + // 如果只是暂停某个文件。 + if ( file ) { + file = file.id ? file : me.request( 'get-file', file ); + + if ( file.getStatus() !== Status.PROGRESS && + file.getStatus() !== Status.QUEUED ) { + return; + } + + file.setStatus( Status.INTERRUPT ); + $.each( me.pool, function( _, v ) { + + // 只 abort 指定的文件。 + if (v.file !== file) { + return; + } + + v.transport && v.transport.abort(); + me._putback(v); + me._popBlock(v); + }); + + return Base.nextTick( me.__tick ); + } + + me.runing = false; + + if (this._promise && this._promise.file) { + this._promise.file.setStatus( Status.INTERRUPT ); + } + + interrupt && $.each( me.pool, function( _, v ) { + v.transport && v.transport.abort(); + v.file.setStatus( Status.INTERRUPT ); + }); + + me.owner.trigger('stopUpload'); + }, + + /** + * @method cancelFile + * @grammar cancelFile( file ) => undefined + * @grammar cancelFile( id ) => undefined + * @param {File|id} file File对象或这File对象的id + * @description 标记文件状态为已取消, 同时将中断文件传输。 + * @for Uploader + * @example + * + * $li.on('click', '.remove-this', function() { + * uploader.cancelFile( file ); + * }) + */ + cancelFile: function( file ) { + file = file.id ? file : this.request( 'get-file', file ); + + // 如果正在上传。 + file.blocks && $.each( file.blocks, function( _, v ) { + var _tr = v.transport; + + if ( _tr ) { + _tr.abort(); + _tr.destroy(); + delete v.transport; + } + }); + + file.setStatus( Status.CANCELLED ); + this.owner.trigger( 'fileDequeued', file ); + }, + + /** + * 判断`Uplaode`r是否正在上传中。 + * @grammar isInProgress() => Boolean + * @method isInProgress + * @for Uploader + */ + isInProgress: function() { + return !!this.progress; + }, + + _getStats: function() { + return this.request('get-stats'); + }, + + /** + * 掉过一个文件上传,直接标记指定文件为已上传状态。 + * @grammar skipFile( file ) => undefined + * @method skipFile + * @for Uploader + */ + skipFile: function( file, status ) { + file = file.id ? file : this.request( 'get-file', file ); + + file.setStatus( status || Status.COMPLETE ); + file.skipped = true; + + // 如果正在上传。 + file.blocks && $.each( file.blocks, function( _, v ) { + var _tr = v.transport; + + if ( _tr ) { + _tr.abort(); + _tr.destroy(); + delete v.transport; + } + }); + + this.owner.trigger( 'uploadSkip', file ); + }, + + /** + * @event uploadFinished + * @description 当所有文件上传结束时触发。 + * @for Uploader + */ + _tick: function() { + var me = this, + opts = me.options, + fn, val; + + // 上一个promise还没有结束,则等待完成后再执行。 + if ( me._promise ) { + return me._promise.always( me.__tick ); + } + + // 还有位置,且还有文件要处理的话。 + if ( me.pool.length < opts.threads && (val = me._nextBlock()) ) { + me._trigged = false; + + fn = function( val ) { + me._promise = null; + + // 有可能是reject过来的,所以要检测val的类型。 + val && val.file && me._startSend( val ); + Base.nextTick( me.__tick ); + }; + + me._promise = isPromise( val ) ? val.always( fn ) : fn( val ); + + // 没有要上传的了,且没有正在传输的了。 + } else if ( !me.remaning && !me._getStats().numOfQueue && + !me._getStats().numofInterrupt ) { + me.runing = false; + + me._trigged || Base.nextTick(function() { + me.owner.trigger('uploadFinished'); + }); + me._trigged = true; + } + }, + + _putback: function(block) { + var idx; + + block.cuted.unshift(block); + idx = this.stack.indexOf(block.cuted); + + if (!~idx) { + this.stack.unshift(block.cuted); + } + }, + + _getStack: function() { + var i = 0, + act; + + while ( (act = this.stack[ i++ ]) ) { + if ( act.has() && act.file.getStatus() === Status.PROGRESS ) { + return act; + } else if (!act.has() || + act.file.getStatus() !== Status.PROGRESS && + act.file.getStatus() !== Status.INTERRUPT ) { + + // 把已经处理完了的,或者,状态为非 progress(上传中)、 + // interupt(暂停中) 的移除。 + this.stack.splice( --i, 1 ); + } + } + + return null; + }, + + _nextBlock: function() { + var me = this, + opts = me.options, + act, next, done, preparing; + + // 如果当前文件还有没有需要传输的,则直接返回剩下的。 + if ( (act = this._getStack()) ) { + + // 是否提前准备下一个文件 + if ( opts.prepareNextFile && !me.pending.length ) { + me._prepareNextFile(); + } + + return act.shift(); + + // 否则,如果正在运行,则准备下一个文件,并等待完成后返回下个分片。 + } else if ( me.runing ) { + + // 如果缓存中有,则直接在缓存中取,没有则去queue中取。 + if ( !me.pending.length && me._getStats().numOfQueue ) { + me._prepareNextFile(); + } + + next = me.pending.shift(); + done = function( file ) { + if ( !file ) { + return null; + } + + act = CuteFile( file, opts.chunked ? opts.chunkSize : 0 ); + me.stack.push(act); + return act.shift(); + }; + + // 文件可能还在prepare中,也有可能已经完全准备好了。 + if ( isPromise( next) ) { + preparing = next.file; + next = next[ next.pipe ? 'pipe' : 'then' ]( done ); + next.file = preparing; + return next; + } + + return done( next ); + } + }, + + + /** + * @event uploadStart + * @param {File} file File对象 + * @description 某个文件开始上传前触发,一个文件只会触发一次。 + * @for Uploader + */ + _prepareNextFile: function() { + var me = this, + file = me.request('fetch-file'), + pending = me.pending, + promise; + + if ( file ) { + promise = me.request( 'before-send-file', file, function() { + + // 有可能文件被skip掉了。文件被skip掉后,状态坑定不是Queued. + if ( file.getStatus() === Status.PROGRESS || + file.getStatus() === Status.INTERRUPT ) { + return file; + } + + return me._finishFile( file ); + }); + + me.owner.trigger( 'uploadStart', file ); + file.setStatus( Status.PROGRESS ); + + promise.file = file; + + // 如果还在pending中,则替换成文件本身。 + promise.done(function() { + var idx = $.inArray( promise, pending ); + + ~idx && pending.splice( idx, 1, file ); + }); + + // befeore-send-file的钩子就有错误发生。 + promise.fail(function( reason ) { + file.setStatus( Status.ERROR, reason ); + me.owner.trigger( 'uploadError', file, reason ); + me.owner.trigger( 'uploadComplete', file ); + }); + + pending.push( promise ); + } + }, + + // 让出位置了,可以让其他分片开始上传 + _popBlock: function( block ) { + var idx = $.inArray( block, this.pool ); + + this.pool.splice( idx, 1 ); + block.file.remaning--; + this.remaning--; + }, + + // 开始上传,可以被掉过。如果promise被reject了,则表示跳过此分片。 + _startSend: function( block ) { + var me = this, + file = block.file, + promise; + + // 有可能在 before-send-file 的 promise 期间改变了文件状态。 + // 如:暂停,取消 + // 我们不能中断 promise, 但是可以在 promise 完后,不做上传操作。 + if ( file.getStatus() !== Status.PROGRESS ) { + + // 如果是中断,则还需要放回去。 + if (file.getStatus() === Status.INTERRUPT) { + me._putback(block); + } + + return; + } + + me.pool.push( block ); + me.remaning++; + + // 如果没有分片,则直接使用原始的。 + // 不会丢失content-type信息。 + block.blob = block.chunks === 1 ? file.source : + file.source.slice( block.start, block.end ); + + // hook, 每个分片发送之前可能要做些异步的事情。 + promise = me.request( 'before-send', block, function() { + + // 有可能文件已经上传出错了,所以不需要再传输了。 + if ( file.getStatus() === Status.PROGRESS ) { + me._doSend( block ); + } else { + me._popBlock( block ); + Base.nextTick( me.__tick ); + } + }); + + // 如果为fail了,则跳过此分片。 + promise.fail(function() { + if ( file.remaning === 1 ) { + me._finishFile( file ).always(function() { + block.percentage = 1; + me._popBlock( block ); + me.owner.trigger( 'uploadComplete', file ); + Base.nextTick( me.__tick ); + }); + } else { + block.percentage = 1; + me.updateFileProgress( file ); + me._popBlock( block ); + Base.nextTick( me.__tick ); + } + }); + }, + + + /** + * @event uploadBeforeSend + * @param {Object} object + * @param {Object} data 默认的上传参数,可以扩展此对象来控制上传参数。 + * @param {Object} headers 可以扩展此对象来控制上传头部。 + * @description 当某个文件的分块在发送前触发,主要用来询问是否要添加附带参数,大文件在开起分片上传的前提下此事件可能会触发多次。 + * @for Uploader + */ + + /** + * @event uploadAccept + * @param {Object} object + * @param {Object} ret 服务端的返回数据,json格式,如果服务端不是json格式,从ret._raw中取数据,自行解析。 + * @description 当某个文件上传到服务端响应后,会派送此事件来询问服务端响应是否有效。如果此事件handler返回值为`false`, 则此文件将派送`server`类型的`uploadError`事件。 + * @for Uploader + */ + + /** + * @event uploadProgress + * @param {File} file File对象 + * @param {Number} percentage 上传进度 + * @description 上传过程中触发,携带上传进度。 + * @for Uploader + */ + + + /** + * @event uploadError + * @param {File} file File对象 + * @param {String} reason 出错的code + * @description 当文件上传出错时触发。 + * @for Uploader + */ + + /** + * @event uploadSuccess + * @param {File} file File对象 + * @param {Object} response 服务端返回的数据 + * @description 当文件上传成功时触发。 + * @for Uploader + */ + + /** + * @event uploadComplete + * @param {File} [file] File对象 + * @description 不管成功或者失败,文件上传完成时触发。 + * @for Uploader + */ + + // 做上传操作。 + _doSend: function( block ) { + var me = this, + owner = me.owner, + opts = me.options, + file = block.file, + tr = new Transport( opts ), + data = $.extend({}, opts.formData ), + headers = $.extend({}, opts.headers ), + requestAccept, ret; + + block.transport = tr; + + tr.on( 'destroy', function() { + delete block.transport; + me._popBlock( block ); + Base.nextTick( me.__tick ); + }); + + // 广播上传进度。以文件为单位。 + tr.on( 'progress', function( percentage ) { + block.percentage = percentage; + me.updateFileProgress( file ); + }); + + // 用来询问,是否返回的结果是有错误的。 + requestAccept = function( reject ) { + var fn; + + ret = tr.getResponseAsJson() || {}; + ret._raw = tr.getResponse(); + fn = function( value ) { + reject = value; + }; + + // 服务端响应了,不代表成功了,询问是否响应正确。 + if ( !owner.trigger( 'uploadAccept', block, ret, fn ) ) { + reject = reject || 'server'; + } + + return reject; + }; + + // 尝试重试,然后广播文件上传出错。 + tr.on( 'error', function( type, flag ) { + block.retried = block.retried || 0; + + // 自动重试 + if ( block.chunks > 1 && ~'http,abort'.indexOf( type ) && + block.retried < opts.chunkRetry ) { + + block.retried++; + tr.send(); + + } else { + + // http status 500 ~ 600 + if ( !flag && type === 'server' ) { + type = requestAccept( type ); + } + + file.setStatus( Status.ERROR, type ); + owner.trigger( 'uploadError', file, type ); + owner.trigger( 'uploadComplete', file ); + } + }); + + // 上传成功 + tr.on( 'load', function() { + var reason; + + // 如果非预期,转向上传出错。 + if ( (reason = requestAccept()) ) { + tr.trigger( 'error', reason, true ); + return; + } + + // 全部上传完成。 + if ( file.remaning === 1 ) { + me._finishFile( file, ret ); + } else { + tr.destroy(); + } + }); + + // 配置默认的上传字段。 + data = $.extend( data, { + id: file.id, + name: file.name, + type: file.type, + lastModifiedDate: file.lastModifiedDate, + size: file.size + }); + + block.chunks > 1 && $.extend( data, { + chunks: block.chunks, + chunk: block.chunk + }); + + // 在发送之间可以添加字段什么的。。。 + // 如果默认的字段不够使用,可以通过监听此事件来扩展 + owner.trigger( 'uploadBeforeSend', block, data, headers ); + + // 开始发送。 + tr.appendBlob( opts.fileVal, block.blob, file.name ); + tr.append( data ); + tr.setRequestHeader( headers ); + tr.send(); + }, + + // 完成上传。 + _finishFile: function( file, ret, hds ) { + var owner = this.owner; + + return owner + .request( 'after-send-file', arguments, function() { + file.setStatus( Status.COMPLETE ); + owner.trigger( 'uploadSuccess', file, ret, hds ); + }) + .fail(function( reason ) { + + // 如果外部已经标记为invalid什么的,不再改状态。 + if ( file.getStatus() === Status.PROGRESS ) { + file.setStatus( Status.ERROR, reason ); + } + + owner.trigger( 'uploadError', file, reason ); + }) + .always(function() { + owner.trigger( 'uploadComplete', file ); + }); + }, + + updateFileProgress: function(file) { + var totalPercent = 0, + uploaded = 0; + + if (!file.blocks) { + return; + } + + $.each( file.blocks, function( _, v ) { + uploaded += (v.percentage || 0) * (v.end - v.start); + }); + + totalPercent = uploaded / file.size; + this.owner.trigger( 'uploadProgress', file, totalPercent || 0 ); + } + + }); + }); + /** + * @fileOverview 各种验证,包括文件总大小是否超出、单文件是否超出和文件是否重复。 + */ + + define('widgets/validator',[ + 'base', + 'uploader', + 'file', + 'widgets/widget' + ], function( Base, Uploader, WUFile ) { + + var $ = Base.$, + validators = {}, + api; + + /** + * @event error + * @param {String} type 错误类型。 + * @description 当validate不通过时,会以派送错误事件的形式通知调用者。通过`upload.on('error', handler)`可以捕获到此类错误,目前有以下错误会在特定的情况下派送错来。 + * + * * `Q_EXCEED_NUM_LIMIT` 在设置了`fileNumLimit`且尝试给`uploader`添加的文件数量超出这个值时派送。 + * * `Q_EXCEED_SIZE_LIMIT` 在设置了`Q_EXCEED_SIZE_LIMIT`且尝试给`uploader`添加的文件总大小超出这个值时派送。 + * * `Q_TYPE_DENIED` 当文件类型不满足时触发。。 + * @for Uploader + */ + + // 暴露给外面的api + api = { + + // 添加验证器 + addValidator: function( type, cb ) { + validators[ type ] = cb; + }, + + // 移除验证器 + removeValidator: function( type ) { + delete validators[ type ]; + } + }; + + // 在Uploader初始化的时候启动Validators的初始化 + Uploader.register({ + name: 'validator', + + init: function() { + var me = this; + Base.nextTick(function() { + $.each( validators, function() { + this.call( me.owner ); + }); + }); + } + }); + + /** + * @property {int} [fileNumLimit=undefined] + * @namespace options + * @for Uploader + * @description 验证文件总数量, 超出则不允许加入队列。 + */ + api.addValidator( 'fileNumLimit', function() { + var uploader = this, + opts = uploader.options, + count = 0, + max = parseInt( opts.fileNumLimit, 10 ), + flag = true; + + if ( !max ) { + return; + } + + uploader.on( 'beforeFileQueued', function( file ) { + + if ( count >= max && flag ) { + flag = false; + this.trigger( 'error', 'Q_EXCEED_NUM_LIMIT', max, file ); + setTimeout(function() { + flag = true; + }, 1 ); + } + + return count >= max ? false : true; + }); + + uploader.on( 'fileQueued', function() { + count++; + }); + + uploader.on( 'fileDequeued', function() { + count--; + }); + + uploader.on( 'reset', function() { + count = 0; + }); + }); + + + /** + * @property {int} [fileSizeLimit=undefined] + * @namespace options + * @for Uploader + * @description 验证文件总大小是否超出限制, 超出则不允许加入队列。 + */ + api.addValidator( 'fileSizeLimit', function() { + var uploader = this, + opts = uploader.options, + count = 0, + max = parseInt( opts.fileSizeLimit, 10 ), + flag = true; + + if ( !max ) { + return; + } + + uploader.on( 'beforeFileQueued', function( file ) { + var invalid = count + file.size > max; + + if ( invalid && flag ) { + flag = false; + this.trigger( 'error', 'Q_EXCEED_SIZE_LIMIT', max, file ); + setTimeout(function() { + flag = true; + }, 1 ); + } + + return invalid ? false : true; + }); + + uploader.on( 'fileQueued', function( file ) { + count += file.size; + }); + + uploader.on( 'fileDequeued', function( file ) { + count -= file.size; + }); + + uploader.on( 'reset', function() { + count = 0; + }); + }); + + /** + * @property {int} [fileSingleSizeLimit=undefined] + * @namespace options + * @for Uploader + * @description 验证单个文件大小是否超出限制, 超出则不允许加入队列。 + */ + api.addValidator( 'fileSingleSizeLimit', function() { + var uploader = this, + opts = uploader.options, + max = opts.fileSingleSizeLimit; + + if ( !max ) { + return; + } + + uploader.on( 'beforeFileQueued', function( file ) { + + if ( file.size > max ) { + file.setStatus( WUFile.Status.INVALID, 'exceed_size' ); + this.trigger( 'error', 'F_EXCEED_SIZE', max, file ); + return false; + } + + }); + + }); + + /** + * @property {Boolean} [duplicate=undefined] + * @namespace options + * @for Uploader + * @description 去重, 根据文件名字、文件大小和最后修改时间来生成hash Key. + */ + api.addValidator( 'duplicate', function() { + var uploader = this, + opts = uploader.options, + mapping = {}; + + if ( opts.duplicate ) { + return; + } + + function hashString( str ) { + var hash = 0, + i = 0, + len = str.length, + _char; + + for ( ; i < len; i++ ) { + _char = str.charCodeAt( i ); + hash = _char + (hash << 6) + (hash << 16) - hash; + } + + return hash; + } + + uploader.on( 'beforeFileQueued', function( file ) { + var hash = file.__hash || (file.__hash = hashString( file.name + + file.size + file.lastModifiedDate )); + + // 已经重复了 + if ( mapping[ hash ] ) { + this.trigger( 'error', 'F_DUPLICATE', file ); + return false; + } + }); + + uploader.on( 'fileQueued', function( file ) { + var hash = file.__hash; + + hash && (mapping[ hash ] = true); + }); + + uploader.on( 'fileDequeued', function( file ) { + var hash = file.__hash; + + hash && (delete mapping[ hash ]); + }); + + uploader.on( 'reset', function() { + mapping = {}; + }); + }); + + return api; + }); + + /** + * @fileOverview Runtime管理器,负责Runtime的选择, 连接 + */ + define('runtime/compbase',[],function() { + + function CompBase( owner, runtime ) { + + this.owner = owner; + this.options = owner.options; + + this.getRuntime = function() { + return runtime; + }; + + this.getRuid = function() { + return runtime.uid; + }; + + this.trigger = function() { + return owner.trigger.apply( owner, arguments ); + }; + } + + return CompBase; + }); + /** + * @fileOverview Html5Runtime + */ + define('runtime/html5/runtime',[ + 'base', + 'runtime/runtime', + 'runtime/compbase' + ], function( Base, Runtime, CompBase ) { + + var type = 'html5', + components = {}; + + function Html5Runtime() { + var pool = {}, + me = this, + destroy = this.destroy; + + Runtime.apply( me, arguments ); + me.type = type; + + + // 这个方法的调用者,实际上是RuntimeClient + me.exec = function( comp, fn/*, args...*/) { + var client = this, + uid = client.uid, + args = Base.slice( arguments, 2 ), + instance; + + if ( components[ comp ] ) { + instance = pool[ uid ] = pool[ uid ] || + new components[ comp ]( client, me ); + + if ( instance[ fn ] ) { + return instance[ fn ].apply( instance, args ); + } + } + }; + + me.destroy = function() { + // @todo 删除池子中的所有实例 + return destroy && destroy.apply( this, arguments ); + }; + } + + Base.inherits( Runtime, { + constructor: Html5Runtime, + + // 不需要连接其他程序,直接执行callback + init: function() { + var me = this; + setTimeout(function() { + me.trigger('ready'); + }, 1 ); + } + + }); + + // 注册Components + Html5Runtime.register = function( name, component ) { + var klass = components[ name ] = Base.inherits( CompBase, component ); + return klass; + }; + + // 注册html5运行时。 + // 只有在支持的前提下注册。 + if ( window.Blob && window.FileReader && window.DataView ) { + Runtime.addRuntime( type, Html5Runtime ); + } + + return Html5Runtime; + }); + /** + * @fileOverview Blob Html实现 + */ + define('runtime/html5/blob',[ + 'runtime/html5/runtime', + 'lib/blob' + ], function( Html5Runtime, Blob ) { + + return Html5Runtime.register( 'Blob', { + slice: function( start, end ) { + var blob = this.owner.source, + slice = blob.slice || blob.webkitSlice || blob.mozSlice; + + blob = slice.call( blob, start, end ); + + return new Blob( this.getRuid(), blob ); + } + }); + }); + /** + * @fileOverview FilePaste + */ + define('runtime/html5/dnd',[ + 'base', + 'runtime/html5/runtime', + 'lib/file' + ], function( Base, Html5Runtime, File ) { + + var $ = Base.$, + prefix = 'webuploader-dnd-'; + + return Html5Runtime.register( 'DragAndDrop', { + init: function() { + var elem = this.elem = this.options.container; + + this.dragEnterHandler = Base.bindFn( this._dragEnterHandler, this ); + this.dragOverHandler = Base.bindFn( this._dragOverHandler, this ); + this.dragLeaveHandler = Base.bindFn( this._dragLeaveHandler, this ); + this.dropHandler = Base.bindFn( this._dropHandler, this ); + this.dndOver = false; + + elem.on( 'dragenter', this.dragEnterHandler ); + elem.on( 'dragover', this.dragOverHandler ); + elem.on( 'dragleave', this.dragLeaveHandler ); + elem.on( 'drop', this.dropHandler ); + + if ( this.options.disableGlobalDnd ) { + $( document ).on( 'dragover', this.dragOverHandler ); + $( document ).on( 'drop', this.dropHandler ); + } + }, + + _dragEnterHandler: function( e ) { + var me = this, + denied = me._denied || false, + items; + + e = e.originalEvent || e; + + if ( !me.dndOver ) { + me.dndOver = true; + + // 注意只有 chrome 支持。 + items = e.dataTransfer.items; + + if ( items && items.length ) { + me._denied = denied = !me.trigger( 'accept', items ); + } + + me.elem.addClass( prefix + 'over' ); + me.elem[ denied ? 'addClass' : + 'removeClass' ]( prefix + 'denied' ); + } + + e.dataTransfer.dropEffect = denied ? 'none' : 'copy'; + + return false; + }, + + _dragOverHandler: function( e ) { + // 只处理框内的。 + var parentElem = this.elem.parent().get( 0 ); + if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) { + return false; + } + + clearTimeout( this._leaveTimer ); + this._dragEnterHandler.call( this, e ); + + return false; + }, + + _dragLeaveHandler: function() { + var me = this, + handler; + + handler = function() { + me.dndOver = false; + me.elem.removeClass( prefix + 'over ' + prefix + 'denied' ); + }; + + clearTimeout( me._leaveTimer ); + me._leaveTimer = setTimeout( handler, 100 ); + return false; + }, + + _dropHandler: function( e ) { + var me = this, + ruid = me.getRuid(), + parentElem = me.elem.parent().get( 0 ), + dataTransfer, data; + + // 只处理框内的。 + if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) { + return false; + } + + e = e.originalEvent || e; + dataTransfer = e.dataTransfer; + + // 如果是页面内拖拽,还不能处理,不阻止事件。 + // 此处 ie11 下会报参数错误, + try { + data = dataTransfer.getData('text/html'); + } catch( err ) { + } + + if ( data ) { + return; + } + + me._getTansferFiles( dataTransfer, function( results ) { + me.trigger( 'drop', $.map( results, function( file ) { + return new File( ruid, file ); + }) ); + }); + + me.dndOver = false; + me.elem.removeClass( prefix + 'over' ); + return false; + }, + + // 如果传入 callback 则去查看文件夹,否则只管当前文件夹。 + _getTansferFiles: function( dataTransfer, callback ) { + var results = [], + promises = [], + items, files, file, item, i, len, canAccessFolder; + + items = dataTransfer.items; + files = dataTransfer.files; + + canAccessFolder = !!(items && items[ 0 ].webkitGetAsEntry); + + for ( i = 0, len = files.length; i < len; i++ ) { + file = files[ i ]; + item = items && items[ i ]; + + if ( canAccessFolder && item.webkitGetAsEntry().isDirectory ) { + + promises.push( this._traverseDirectoryTree( + item.webkitGetAsEntry(), results ) ); + } else { + results.push( file ); + } + } + + Base.when.apply( Base, promises ).done(function() { + + if ( !results.length ) { + return; + } + + callback( results ); + }); + }, + + _traverseDirectoryTree: function( entry, results ) { + var deferred = Base.Deferred(), + me = this; + + if ( entry.isFile ) { + entry.file(function( file ) { + results.push( file ); + deferred.resolve(); + }); + } else if ( entry.isDirectory ) { + entry.createReader().readEntries(function( entries ) { + var len = entries.length, + promises = [], + arr = [], // 为了保证顺序。 + i; + + for ( i = 0; i < len; i++ ) { + promises.push( me._traverseDirectoryTree( + entries[ i ], arr ) ); + } + + Base.when.apply( Base, promises ).then(function() { + results.push.apply( results, arr ); + deferred.resolve(); + }, deferred.reject ); + }); + } + + return deferred.promise(); + }, + + destroy: function() { + var elem = this.elem; + + // 还没 init 就调用 destroy + if (!elem) { + return; + } + + elem.off( 'dragenter', this.dragEnterHandler ); + elem.off( 'dragover', this.dragOverHandler ); + elem.off( 'dragleave', this.dragLeaveHandler ); + elem.off( 'drop', this.dropHandler ); + + if ( this.options.disableGlobalDnd ) { + $( document ).off( 'dragover', this.dragOverHandler ); + $( document ).off( 'drop', this.dropHandler ); + } + } + }); + }); + + /** + * @fileOverview FilePaste + */ + define('runtime/html5/filepaste',[ + 'base', + 'runtime/html5/runtime', + 'lib/file' + ], function( Base, Html5Runtime, File ) { + + return Html5Runtime.register( 'FilePaste', { + init: function() { + var opts = this.options, + elem = this.elem = opts.container, + accept = '.*', + arr, i, len, item; + + // accetp的mimeTypes中生成匹配正则。 + if ( opts.accept ) { + arr = []; + + for ( i = 0, len = opts.accept.length; i < len; i++ ) { + item = opts.accept[ i ].mimeTypes; + item && arr.push( item ); + } + + if ( arr.length ) { + accept = arr.join(','); + accept = accept.replace( /,/g, '|' ).replace( /\*/g, '.*' ); + } + } + this.accept = accept = new RegExp( accept, 'i' ); + this.hander = Base.bindFn( this._pasteHander, this ); + elem.on( 'paste', this.hander ); + }, + + _pasteHander: function( e ) { + var allowed = [], + ruid = this.getRuid(), + items, item, blob, i, len; + + e = e.originalEvent || e; + items = e.clipboardData.items; + + for ( i = 0, len = items.length; i < len; i++ ) { + item = items[ i ]; + + if ( item.kind !== 'file' || !(blob = item.getAsFile()) ) { + continue; + } + + allowed.push( new File( ruid, blob ) ); + } + + if ( allowed.length ) { + // 不阻止非文件粘贴(文字粘贴)的事件冒泡 + e.preventDefault(); + e.stopPropagation(); + this.trigger( 'paste', allowed ); + } + }, + + destroy: function() { + this.elem.off( 'paste', this.hander ); + } + }); + }); + + /** + * @fileOverview FilePicker + */ + define('runtime/html5/filepicker',[ + 'base', + 'runtime/html5/runtime' + ], function( Base, Html5Runtime ) { + + var $ = Base.$; + + return Html5Runtime.register( 'FilePicker', { + init: function() { + var container = this.getRuntime().getContainer(), + me = this, + owner = me.owner, + opts = me.options, + label = this.label = $( document.createElement('label') ), + input = this.input = $( document.createElement('input') ), + arr, i, len, mouseHandler; + + input.attr( 'type', 'file' ); + input.attr( 'name', opts.name ); + input.addClass('webuploader-element-invisible'); + + label.on( 'click', function() { + input.trigger('click'); + }); + + label.css({ + opacity: 0, + width: '100%', + height: '100%', + display: 'block', + cursor: 'pointer', + background: '#ffffff' + }); + + if ( opts.multiple ) { + input.attr( 'multiple', 'multiple' ); + } + + // @todo Firefox不支持单独指定后缀 + if ( opts.accept && opts.accept.length > 0 ) { + arr = []; + + for ( i = 0, len = opts.accept.length; i < len; i++ ) { + arr.push( opts.accept[ i ].mimeTypes ); + } + + input.attr( 'accept', arr.join(',') ); + } + + container.append( input ); + container.append( label ); + + mouseHandler = function( e ) { + owner.trigger( e.type ); + }; + + input.on( 'change', function( e ) { + var fn = arguments.callee, + clone; + + me.files = e.target.files; + + // reset input + clone = this.cloneNode( true ); + clone.value = null; + this.parentNode.replaceChild( clone, this ); + + input.off(); + input = $( clone ).on( 'change', fn ) + .on( 'mouseenter mouseleave', mouseHandler ); + + owner.trigger('change'); + }); + + label.on( 'mouseenter mouseleave', mouseHandler ); + + }, + + + getFiles: function() { + return this.files; + }, + + destroy: function() { + this.input.off(); + this.label.off(); + } + }); + }); + /** + * Terms: + * + * Uint8Array, FileReader, BlobBuilder, atob, ArrayBuffer + * @fileOverview Image控件 + */ + define('runtime/html5/util',[ + 'base' + ], function( Base ) { + + var urlAPI = window.createObjectURL && window || + window.URL && URL.revokeObjectURL && URL || + window.webkitURL, + createObjectURL = Base.noop, + revokeObjectURL = createObjectURL; + + if ( urlAPI ) { + + // 更安全的方式调用,比如android里面就能把context改成其他的对象。 + createObjectURL = function() { + return urlAPI.createObjectURL.apply( urlAPI, arguments ); + }; + + revokeObjectURL = function() { + return urlAPI.revokeObjectURL.apply( urlAPI, arguments ); + }; + } + + return { + createObjectURL: createObjectURL, + revokeObjectURL: revokeObjectURL, + + dataURL2Blob: function( dataURI ) { + var byteStr, intArray, ab, i, mimetype, parts; + + parts = dataURI.split(','); + + if ( ~parts[ 0 ].indexOf('base64') ) { + byteStr = atob( parts[ 1 ] ); + } else { + byteStr = decodeURIComponent( parts[ 1 ] ); + } + + ab = new ArrayBuffer( byteStr.length ); + intArray = new Uint8Array( ab ); + + for ( i = 0; i < byteStr.length; i++ ) { + intArray[ i ] = byteStr.charCodeAt( i ); + } + + mimetype = parts[ 0 ].split(':')[ 1 ].split(';')[ 0 ]; + + return this.arrayBufferToBlob( ab, mimetype ); + }, + + dataURL2ArrayBuffer: function( dataURI ) { + var byteStr, intArray, i, parts; + + parts = dataURI.split(','); + + if ( ~parts[ 0 ].indexOf('base64') ) { + byteStr = atob( parts[ 1 ] ); + } else { + byteStr = decodeURIComponent( parts[ 1 ] ); + } + + intArray = new Uint8Array( byteStr.length ); + + for ( i = 0; i < byteStr.length; i++ ) { + intArray[ i ] = byteStr.charCodeAt( i ); + } + + return intArray.buffer; + }, + + arrayBufferToBlob: function( buffer, type ) { + var builder = window.BlobBuilder || window.WebKitBlobBuilder, + bb; + + // android不支持直接new Blob, 只能借助blobbuilder. + if ( builder ) { + bb = new builder(); + bb.append( buffer ); + return bb.getBlob( type ); + } + + return new Blob([ buffer ], type ? { type: type } : {} ); + }, + + // 抽出来主要是为了解决android下面canvas.toDataUrl不支持jpeg. + // 你得到的结果是png. + canvasToDataUrl: function( canvas, type, quality ) { + return canvas.toDataURL( type, quality / 100 ); + }, + + // imagemeat会复写这个方法,如果用户选择加载那个文件了的话。 + parseMeta: function( blob, callback ) { + callback( false, {}); + }, + + // imagemeat会复写这个方法,如果用户选择加载那个文件了的话。 + updateImageHead: function( data ) { + return data; + } + }; + }); + /** + * Terms: + * + * Uint8Array, FileReader, BlobBuilder, atob, ArrayBuffer + * @fileOverview Image控件 + */ + define('runtime/html5/imagemeta',[ + 'runtime/html5/util' + ], function( Util ) { + + var api; + + api = { + parsers: { + 0xffe1: [] + }, + + maxMetaDataSize: 262144, + + parse: function( blob, cb ) { + var me = this, + fr = new FileReader(); + + fr.onload = function() { + cb( false, me._parse( this.result ) ); + fr = fr.onload = fr.onerror = null; + }; + + fr.onerror = function( e ) { + cb( e.message ); + fr = fr.onload = fr.onerror = null; + }; + + blob = blob.slice( 0, me.maxMetaDataSize ); + fr.readAsArrayBuffer( blob.getSource() ); + }, + + _parse: function( buffer, noParse ) { + if ( buffer.byteLength < 6 ) { + return; + } + + var dataview = new DataView( buffer ), + offset = 2, + maxOffset = dataview.byteLength - 4, + headLength = offset, + ret = {}, + markerBytes, markerLength, parsers, i; + + if ( dataview.getUint16( 0 ) === 0xffd8 ) { + + while ( offset < maxOffset ) { + markerBytes = dataview.getUint16( offset ); + + if ( markerBytes >= 0xffe0 && markerBytes <= 0xffef || + markerBytes === 0xfffe ) { + + markerLength = dataview.getUint16( offset + 2 ) + 2; + + if ( offset + markerLength > dataview.byteLength ) { + break; + } + + parsers = api.parsers[ markerBytes ]; + + if ( !noParse && parsers ) { + for ( i = 0; i < parsers.length; i += 1 ) { + parsers[ i ].call( api, dataview, offset, + markerLength, ret ); + } + } + + offset += markerLength; + headLength = offset; + } else { + break; + } + } + + if ( headLength > 6 ) { + if ( buffer.slice ) { + ret.imageHead = buffer.slice( 2, headLength ); + } else { + // Workaround for IE10, which does not yet + // support ArrayBuffer.slice: + ret.imageHead = new Uint8Array( buffer ) + .subarray( 2, headLength ); + } + } + } + + return ret; + }, + + updateImageHead: function( buffer, head ) { + var data = this._parse( buffer, true ), + buf1, buf2, bodyoffset; + + + bodyoffset = 2; + if ( data.imageHead ) { + bodyoffset = 2 + data.imageHead.byteLength; + } + + if ( buffer.slice ) { + buf2 = buffer.slice( bodyoffset ); + } else { + buf2 = new Uint8Array( buffer ).subarray( bodyoffset ); + } + + buf1 = new Uint8Array( head.byteLength + 2 + buf2.byteLength ); + + buf1[ 0 ] = 0xFF; + buf1[ 1 ] = 0xD8; + buf1.set( new Uint8Array( head ), 2 ); + buf1.set( new Uint8Array( buf2 ), head.byteLength + 2 ); + + return buf1.buffer; + } + }; + + Util.parseMeta = function() { + return api.parse.apply( api, arguments ); + }; + + Util.updateImageHead = function() { + return api.updateImageHead.apply( api, arguments ); + }; + + return api; + }); + /** + * 代码来自于:https://github.com/blueimp/JavaScript-Load-Image + * 暂时项目中只用了orientation. + * + * 去除了 Exif Sub IFD Pointer, GPS Info IFD Pointer, Exif Thumbnail. + * @fileOverview EXIF解析 + */ + + // Sample + // ==================================== + // Make : Apple + // Model : iPhone 4S + // Orientation : 1 + // XResolution : 72 [72/1] + // YResolution : 72 [72/1] + // ResolutionUnit : 2 + // Software : QuickTime 7.7.1 + // DateTime : 2013:09:01 22:53:55 + // ExifIFDPointer : 190 + // ExposureTime : 0.058823529411764705 [1/17] + // FNumber : 2.4 [12/5] + // ExposureProgram : Normal program + // ISOSpeedRatings : 800 + // ExifVersion : 0220 + // DateTimeOriginal : 2013:09:01 22:52:51 + // DateTimeDigitized : 2013:09:01 22:52:51 + // ComponentsConfiguration : YCbCr + // ShutterSpeedValue : 4.058893515764426 + // ApertureValue : 2.5260688216892597 [4845/1918] + // BrightnessValue : -0.3126686601998395 + // MeteringMode : Pattern + // Flash : Flash did not fire, compulsory flash mode + // FocalLength : 4.28 [107/25] + // SubjectArea : [4 values] + // FlashpixVersion : 0100 + // ColorSpace : 1 + // PixelXDimension : 2448 + // PixelYDimension : 3264 + // SensingMethod : One-chip color area sensor + // ExposureMode : 0 + // WhiteBalance : Auto white balance + // FocalLengthIn35mmFilm : 35 + // SceneCaptureType : Standard + define('runtime/html5/imagemeta/exif',[ + 'base', + 'runtime/html5/imagemeta' + ], function( Base, ImageMeta ) { + + var EXIF = {}; + + EXIF.ExifMap = function() { + return this; + }; + + EXIF.ExifMap.prototype.map = { + 'Orientation': 0x0112 + }; + + EXIF.ExifMap.prototype.get = function( id ) { + return this[ id ] || this[ this.map[ id ] ]; + }; + + EXIF.exifTagTypes = { + // byte, 8-bit unsigned int: + 1: { + getValue: function( dataView, dataOffset ) { + return dataView.getUint8( dataOffset ); + }, + size: 1 + }, + + // ascii, 8-bit byte: + 2: { + getValue: function( dataView, dataOffset ) { + return String.fromCharCode( dataView.getUint8( dataOffset ) ); + }, + size: 1, + ascii: true + }, + + // short, 16 bit int: + 3: { + getValue: function( dataView, dataOffset, littleEndian ) { + return dataView.getUint16( dataOffset, littleEndian ); + }, + size: 2 + }, + + // long, 32 bit int: + 4: { + getValue: function( dataView, dataOffset, littleEndian ) { + return dataView.getUint32( dataOffset, littleEndian ); + }, + size: 4 + }, + + // rational = two long values, + // first is numerator, second is denominator: + 5: { + getValue: function( dataView, dataOffset, littleEndian ) { + return dataView.getUint32( dataOffset, littleEndian ) / + dataView.getUint32( dataOffset + 4, littleEndian ); + }, + size: 8 + }, + + // slong, 32 bit signed int: + 9: { + getValue: function( dataView, dataOffset, littleEndian ) { + return dataView.getInt32( dataOffset, littleEndian ); + }, + size: 4 + }, + + // srational, two slongs, first is numerator, second is denominator: + 10: { + getValue: function( dataView, dataOffset, littleEndian ) { + return dataView.getInt32( dataOffset, littleEndian ) / + dataView.getInt32( dataOffset + 4, littleEndian ); + }, + size: 8 + } + }; + + // undefined, 8-bit byte, value depending on field: + EXIF.exifTagTypes[ 7 ] = EXIF.exifTagTypes[ 1 ]; + + EXIF.getExifValue = function( dataView, tiffOffset, offset, type, length, + littleEndian ) { + + var tagType = EXIF.exifTagTypes[ type ], + tagSize, dataOffset, values, i, str, c; + + if ( !tagType ) { + Base.log('Invalid Exif data: Invalid tag type.'); + return; + } + + tagSize = tagType.size * length; + + // Determine if the value is contained in the dataOffset bytes, + // or if the value at the dataOffset is a pointer to the actual data: + dataOffset = tagSize > 4 ? tiffOffset + dataView.getUint32( offset + 8, + littleEndian ) : (offset + 8); + + if ( dataOffset + tagSize > dataView.byteLength ) { + Base.log('Invalid Exif data: Invalid data offset.'); + return; + } + + if ( length === 1 ) { + return tagType.getValue( dataView, dataOffset, littleEndian ); + } + + values = []; + + for ( i = 0; i < length; i += 1 ) { + values[ i ] = tagType.getValue( dataView, + dataOffset + i * tagType.size, littleEndian ); + } + + if ( tagType.ascii ) { + str = ''; + + // Concatenate the chars: + for ( i = 0; i < values.length; i += 1 ) { + c = values[ i ]; + + // Ignore the terminating NULL byte(s): + if ( c === '\u0000' ) { + break; + } + str += c; + } + + return str; + } + return values; + }; + + EXIF.parseExifTag = function( dataView, tiffOffset, offset, littleEndian, + data ) { + + var tag = dataView.getUint16( offset, littleEndian ); + data.exif[ tag ] = EXIF.getExifValue( dataView, tiffOffset, offset, + dataView.getUint16( offset + 2, littleEndian ), // tag type + dataView.getUint32( offset + 4, littleEndian ), // tag length + littleEndian ); + }; + + EXIF.parseExifTags = function( dataView, tiffOffset, dirOffset, + littleEndian, data ) { + + var tagsNumber, dirEndOffset, i; + + if ( dirOffset + 6 > dataView.byteLength ) { + Base.log('Invalid Exif data: Invalid directory offset.'); + return; + } + + tagsNumber = dataView.getUint16( dirOffset, littleEndian ); + dirEndOffset = dirOffset + 2 + 12 * tagsNumber; + + if ( dirEndOffset + 4 > dataView.byteLength ) { + Base.log('Invalid Exif data: Invalid directory size.'); + return; + } + + for ( i = 0; i < tagsNumber; i += 1 ) { + this.parseExifTag( dataView, tiffOffset, + dirOffset + 2 + 12 * i, // tag offset + littleEndian, data ); + } + + // Return the offset to the next directory: + return dataView.getUint32( dirEndOffset, littleEndian ); + }; + + // EXIF.getExifThumbnail = function(dataView, offset, length) { + // var hexData, + // i, + // b; + // if (!length || offset + length > dataView.byteLength) { + // Base.log('Invalid Exif data: Invalid thumbnail data.'); + // return; + // } + // hexData = []; + // for (i = 0; i < length; i += 1) { + // b = dataView.getUint8(offset + i); + // hexData.push((b < 16 ? '0' : '') + b.toString(16)); + // } + // return 'data:image/jpeg,%' + hexData.join('%'); + // }; + + EXIF.parseExifData = function( dataView, offset, length, data ) { + + var tiffOffset = offset + 10, + littleEndian, dirOffset; + + // Check for the ASCII code for "Exif" (0x45786966): + if ( dataView.getUint32( offset + 4 ) !== 0x45786966 ) { + // No Exif data, might be XMP data instead + return; + } + if ( tiffOffset + 8 > dataView.byteLength ) { + Base.log('Invalid Exif data: Invalid segment size.'); + return; + } + + // Check for the two null bytes: + if ( dataView.getUint16( offset + 8 ) !== 0x0000 ) { + Base.log('Invalid Exif data: Missing byte alignment offset.'); + return; + } + + // Check the byte alignment: + switch ( dataView.getUint16( tiffOffset ) ) { + case 0x4949: + littleEndian = true; + break; + + case 0x4D4D: + littleEndian = false; + break; + + default: + Base.log('Invalid Exif data: Invalid byte alignment marker.'); + return; + } + + // Check for the TIFF tag marker (0x002A): + if ( dataView.getUint16( tiffOffset + 2, littleEndian ) !== 0x002A ) { + Base.log('Invalid Exif data: Missing TIFF marker.'); + return; + } + + // Retrieve the directory offset bytes, usually 0x00000008 or 8 decimal: + dirOffset = dataView.getUint32( tiffOffset + 4, littleEndian ); + // Create the exif object to store the tags: + data.exif = new EXIF.ExifMap(); + // Parse the tags of the main image directory and retrieve the + // offset to the next directory, usually the thumbnail directory: + dirOffset = EXIF.parseExifTags( dataView, tiffOffset, + tiffOffset + dirOffset, littleEndian, data ); + + // 尝试读取缩略图 + // if ( dirOffset ) { + // thumbnailData = {exif: {}}; + // dirOffset = EXIF.parseExifTags( + // dataView, + // tiffOffset, + // tiffOffset + dirOffset, + // littleEndian, + // thumbnailData + // ); + + // // Check for JPEG Thumbnail offset: + // if (thumbnailData.exif[0x0201]) { + // data.exif.Thumbnail = EXIF.getExifThumbnail( + // dataView, + // tiffOffset + thumbnailData.exif[0x0201], + // thumbnailData.exif[0x0202] // Thumbnail data length + // ); + // } + // } + }; + + ImageMeta.parsers[ 0xffe1 ].push( EXIF.parseExifData ); + return EXIF; + }); + /** + * @fileOverview Image + */ + define('runtime/html5/image',[ + 'base', + 'runtime/html5/runtime', + 'runtime/html5/util' + ], function( Base, Html5Runtime, Util ) { + + var BLANK = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs%3D'; + + return Html5Runtime.register( 'Image', { + + // flag: 标记是否被修改过。 + modified: false, + + init: function() { + var me = this, + img = new Image(); + + img.onload = function() { + + me._info = { + type: me.type, + width: this.width, + height: this.height + }; + + // 读取meta信息。 + if ( !me._metas && 'image/jpeg' === me.type ) { + Util.parseMeta( me._blob, function( error, ret ) { + me._metas = ret; + me.owner.trigger('load'); + }); + } else { + me.owner.trigger('load'); + } + }; + + img.onerror = function() { + me.owner.trigger('error'); + }; + + me._img = img; + }, + + loadFromBlob: function( blob ) { + var me = this, + img = me._img; + + me._blob = blob; + me.type = blob.type; + img.src = Util.createObjectURL( blob.getSource() ); + me.owner.once( 'load', function() { + Util.revokeObjectURL( img.src ); + }); + }, + + resize: function( width, height ) { + var canvas = this._canvas || + (this._canvas = document.createElement('canvas')); + + this._resize( this._img, canvas, width, height ); + this._blob = null; // 没用了,可以删掉了。 + this.modified = true; + this.owner.trigger( 'complete', 'resize' ); + }, + + crop: function( x, y, w, h, s ) { + var cvs = this._canvas || + (this._canvas = document.createElement('canvas')), + opts = this.options, + img = this._img, + iw = img.naturalWidth, + ih = img.naturalHeight, + orientation = this.getOrientation(); + + s = s || 1; + + // todo 解决 orientation 的问题。 + // values that require 90 degree rotation + // if ( ~[ 5, 6, 7, 8 ].indexOf( orientation ) ) { + + // switch ( orientation ) { + // case 6: + // tmp = x; + // x = y; + // y = iw * s - tmp - w; + // console.log(ih * s, tmp, w) + // break; + // } + + // (w ^= h, h ^= w, w ^= h); + // } + + cvs.width = w; + cvs.height = h; + + opts.preserveHeaders || this._rotate2Orientaion( cvs, orientation ); + this._renderImageToCanvas( cvs, img, -x, -y, iw * s, ih * s ); + + this._blob = null; // 没用了,可以删掉了。 + this.modified = true; + this.owner.trigger( 'complete', 'crop' ); + }, + + getAsBlob: function( type ) { + var blob = this._blob, + opts = this.options, + canvas; + + type = type || this.type; + + // blob需要重新生成。 + if ( this.modified || this.type !== type ) { + canvas = this._canvas; + + if ( type === 'image/jpeg' ) { + + blob = Util.canvasToDataUrl( canvas, type, opts.quality ); + + if ( opts.preserveHeaders && this._metas && + this._metas.imageHead ) { + + blob = Util.dataURL2ArrayBuffer( blob ); + blob = Util.updateImageHead( blob, + this._metas.imageHead ); + blob = Util.arrayBufferToBlob( blob, type ); + return blob; + } + } else { + blob = Util.canvasToDataUrl( canvas, type ); + } + + blob = Util.dataURL2Blob( blob ); + } + + return blob; + }, + + getAsDataUrl: function( type ) { + var opts = this.options; + + type = type || this.type; + + if ( type === 'image/jpeg' ) { + return Util.canvasToDataUrl( this._canvas, type, opts.quality ); + } else { + return this._canvas.toDataURL( type ); + } + }, + + getOrientation: function() { + return this._metas && this._metas.exif && + this._metas.exif.get('Orientation') || 1; + }, + + info: function( val ) { + + // setter + if ( val ) { + this._info = val; + return this; + } + + // getter + return this._info; + }, + + meta: function( val ) { + + // setter + if ( val ) { + this._meta = val; + return this; + } + + // getter + return this._meta; + }, + + destroy: function() { + var canvas = this._canvas; + this._img.onload = null; + + if ( canvas ) { + canvas.getContext('2d') + .clearRect( 0, 0, canvas.width, canvas.height ); + canvas.width = canvas.height = 0; + this._canvas = null; + } + + // 释放内存。非常重要,否则释放不了image的内存。 + this._img.src = BLANK; + this._img = this._blob = null; + }, + + _resize: function( img, cvs, width, height ) { + var opts = this.options, + naturalWidth = img.width, + naturalHeight = img.height, + orientation = this.getOrientation(), + scale, w, h, x, y; + + // values that require 90 degree rotation + if ( ~[ 5, 6, 7, 8 ].indexOf( orientation ) ) { + + // 交换width, height的值。 + width ^= height; + height ^= width; + width ^= height; + } + + scale = Math[ opts.crop ? 'max' : 'min' ]( width / naturalWidth, + height / naturalHeight ); + + // 不允许放大。 + opts.allowMagnify || (scale = Math.min( 1, scale )); + + w = naturalWidth * scale; + h = naturalHeight * scale; + + if ( opts.crop ) { + cvs.width = width; + cvs.height = height; + } else { + cvs.width = w; + cvs.height = h; + } + + x = (cvs.width - w) / 2; + y = (cvs.height - h) / 2; + + opts.preserveHeaders || this._rotate2Orientaion( cvs, orientation ); + + this._renderImageToCanvas( cvs, img, x, y, w, h ); + }, + + _rotate2Orientaion: function( canvas, orientation ) { + var width = canvas.width, + height = canvas.height, + ctx = canvas.getContext('2d'); + + switch ( orientation ) { + case 5: + case 6: + case 7: + case 8: + canvas.width = height; + canvas.height = width; + break; + } + + switch ( orientation ) { + case 2: // horizontal flip + ctx.translate( width, 0 ); + ctx.scale( -1, 1 ); + break; + + case 3: // 180 rotate left + ctx.translate( width, height ); + ctx.rotate( Math.PI ); + break; + + case 4: // vertical flip + ctx.translate( 0, height ); + ctx.scale( 1, -1 ); + break; + + case 5: // vertical flip + 90 rotate right + ctx.rotate( 0.5 * Math.PI ); + ctx.scale( 1, -1 ); + break; + + case 6: // 90 rotate right + ctx.rotate( 0.5 * Math.PI ); + ctx.translate( 0, -height ); + break; + + case 7: // horizontal flip + 90 rotate right + ctx.rotate( 0.5 * Math.PI ); + ctx.translate( width, -height ); + ctx.scale( -1, 1 ); + break; + + case 8: // 90 rotate left + ctx.rotate( -0.5 * Math.PI ); + ctx.translate( -width, 0 ); + break; + } + }, + + // https://github.com/stomita/ios-imagefile-megapixel/ + // blob/master/src/megapix-image.js + _renderImageToCanvas: (function() { + + // 如果不是ios, 不需要这么复杂! + if ( !Base.os.ios ) { + return function( canvas ) { + var args = Base.slice( arguments, 1 ), + ctx = canvas.getContext('2d'); + + ctx.drawImage.apply( ctx, args ); + }; + } + + /** + * Detecting vertical squash in loaded image. + * Fixes a bug which squash image vertically while drawing into + * canvas for some images. + */ + function detectVerticalSquash( img, iw, ih ) { + var canvas = document.createElement('canvas'), + ctx = canvas.getContext('2d'), + sy = 0, + ey = ih, + py = ih, + data, alpha, ratio; + + + canvas.width = 1; + canvas.height = ih; + ctx.drawImage( img, 0, 0 ); + data = ctx.getImageData( 0, 0, 1, ih ).data; + + // search image edge pixel position in case + // it is squashed vertically. + while ( py > sy ) { + alpha = data[ (py - 1) * 4 + 3 ]; + + if ( alpha === 0 ) { + ey = py; + } else { + sy = py; + } + + py = (ey + sy) >> 1; + } + + ratio = (py / ih); + return (ratio === 0) ? 1 : ratio; + } + + // fix ie7 bug + // http://stackoverflow.com/questions/11929099/ + // html5-canvas-drawimage-ratio-bug-ios + if ( Base.os.ios >= 7 ) { + return function( canvas, img, x, y, w, h ) { + var iw = img.naturalWidth, + ih = img.naturalHeight, + vertSquashRatio = detectVerticalSquash( img, iw, ih ); + + return canvas.getContext('2d').drawImage( img, 0, 0, + iw * vertSquashRatio, ih * vertSquashRatio, + x, y, w, h ); + }; + } + + /** + * Detect subsampling in loaded image. + * In iOS, larger images than 2M pixels may be + * subsampled in rendering. + */ + function detectSubsampling( img ) { + var iw = img.naturalWidth, + ih = img.naturalHeight, + canvas, ctx; + + // subsampling may happen overmegapixel image + if ( iw * ih > 1024 * 1024 ) { + canvas = document.createElement('canvas'); + canvas.width = canvas.height = 1; + ctx = canvas.getContext('2d'); + ctx.drawImage( img, -iw + 1, 0 ); + + // subsampled image becomes half smaller in rendering size. + // check alpha channel value to confirm image is covering + // edge pixel or not. if alpha value is 0 + // image is not covering, hence subsampled. + return ctx.getImageData( 0, 0, 1, 1 ).data[ 3 ] === 0; + } else { + return false; + } + } + + + return function( canvas, img, x, y, width, height ) { + var iw = img.naturalWidth, + ih = img.naturalHeight, + ctx = canvas.getContext('2d'), + subsampled = detectSubsampling( img ), + doSquash = this.type === 'image/jpeg', + d = 1024, + sy = 0, + dy = 0, + tmpCanvas, tmpCtx, vertSquashRatio, dw, dh, sx, dx; + + if ( subsampled ) { + iw /= 2; + ih /= 2; + } + + ctx.save(); + tmpCanvas = document.createElement('canvas'); + tmpCanvas.width = tmpCanvas.height = d; + + tmpCtx = tmpCanvas.getContext('2d'); + vertSquashRatio = doSquash ? + detectVerticalSquash( img, iw, ih ) : 1; + + dw = Math.ceil( d * width / iw ); + dh = Math.ceil( d * height / ih / vertSquashRatio ); + + while ( sy < ih ) { + sx = 0; + dx = 0; + while ( sx < iw ) { + tmpCtx.clearRect( 0, 0, d, d ); + tmpCtx.drawImage( img, -sx, -sy ); + ctx.drawImage( tmpCanvas, 0, 0, d, d, + x + dx, y + dy, dw, dh ); + sx += d; + dx += dw; + } + sy += d; + dy += dh; + } + ctx.restore(); + tmpCanvas = tmpCtx = null; + }; + })() + }); + }); + /** + * @fileOverview Transport + * @todo 支持chunked传输,优势: + * 可以将大文件分成小块,挨个传输,可以提高大文件成功率,当失败的时候,也只需要重传那小部分, + * 而不需要重头再传一次。另外断点续传也需要用chunked方式。 + */ + define('runtime/html5/transport',[ + 'base', + 'runtime/html5/runtime' + ], function( Base, Html5Runtime ) { + + var noop = Base.noop, + $ = Base.$; + + return Html5Runtime.register( 'Transport', { + init: function() { + this._status = 0; + this._response = null; + }, + + send: function() { + var owner = this.owner, + opts = this.options, + xhr = this._initAjax(), + blob = owner._blob, + server = opts.server, + formData, binary, fr; + + if ( opts.sendAsBinary ) { + server += (/\?/.test( server ) ? '&' : '?') + + $.param( owner._formData ); + + binary = blob.getSource(); + } else { + formData = new FormData(); + $.each( owner._formData, function( k, v ) { + formData.append( k, v ); + }); + + formData.append( opts.fileVal, blob.getSource(), + opts.filename || owner._formData.name || '' ); + } + + if ( opts.withCredentials && 'withCredentials' in xhr ) { + xhr.open( opts.method, server, true ); + xhr.withCredentials = true; + } else { + xhr.open( opts.method, server ); + } + + this._setRequestHeader( xhr, opts.headers ); + + if ( binary ) { + // 强制设置成 content-type 为文件流。 + xhr.overrideMimeType && + xhr.overrideMimeType('application/octet-stream'); + + // android直接发送blob会导致服务端接收到的是空文件。 + // bug详情。 + // https://code.google.com/p/android/issues/detail?id=39882 + // 所以先用fileReader读取出来再通过arraybuffer的方式发送。 + if ( Base.os.android ) { + fr = new FileReader(); + + fr.onload = function() { + xhr.send( this.result ); + fr = fr.onload = null; + }; + + fr.readAsArrayBuffer( binary ); + } else { + xhr.send( binary ); + } + } else { + xhr.send( formData ); + } + }, + + getResponse: function() { + return this._response; + }, + + getResponseAsJson: function() { + return this._parseJson( this._response ); + }, + + getStatus: function() { + return this._status; + }, + + abort: function() { + var xhr = this._xhr; + + if ( xhr ) { + xhr.upload.onprogress = noop; + xhr.onreadystatechange = noop; + xhr.abort(); + + this._xhr = xhr = null; + } + }, + + destroy: function() { + this.abort(); + }, + + _initAjax: function() { + var me = this, + xhr = new XMLHttpRequest(), + opts = this.options; + + if ( opts.withCredentials && !('withCredentials' in xhr) && + typeof XDomainRequest !== 'undefined' ) { + xhr = new XDomainRequest(); + } + + xhr.upload.onprogress = function( e ) { + var percentage = 0; + + if ( e.lengthComputable ) { + percentage = e.loaded / e.total; + } + + return me.trigger( 'progress', percentage ); + }; + + xhr.onreadystatechange = function() { + + if ( xhr.readyState !== 4 ) { + return; + } + + xhr.upload.onprogress = noop; + xhr.onreadystatechange = noop; + me._xhr = null; + me._status = xhr.status; + + if ( xhr.status >= 200 && xhr.status < 300 ) { + me._response = xhr.responseText; + return me.trigger('load'); + } else if ( xhr.status >= 500 && xhr.status < 600 ) { + me._response = xhr.responseText; + return me.trigger( 'error', 'server' ); + } + + + return me.trigger( 'error', me._status ? 'http' : 'abort' ); + }; + + me._xhr = xhr; + return xhr; + }, + + _setRequestHeader: function( xhr, headers ) { + $.each( headers, function( key, val ) { + xhr.setRequestHeader( key, val ); + }); + }, + + _parseJson: function( str ) { + var json; + + try { + json = JSON.parse( str ); + } catch ( ex ) { + json = {}; + } + + return json; + } + }); + }); + /** + * @fileOverview 只有html5实现的文件版本。 + */ + define('preset/html5only',[ + 'base', + + // widgets + 'widgets/filednd', + 'widgets/filepaste', + 'widgets/filepicker', + 'widgets/image', + 'widgets/queue', + 'widgets/runtime', + 'widgets/upload', + 'widgets/validator', + + // runtimes + // html5 + 'runtime/html5/blob', + 'runtime/html5/dnd', + 'runtime/html5/filepaste', + 'runtime/html5/filepicker', + 'runtime/html5/imagemeta/exif', + 'runtime/html5/image', + 'runtime/html5/transport' + ], function( Base ) { + return Base; + }); + define('webuploader',[ + 'preset/html5only' + ], function( preset ) { + return preset; + }); + return require('webuploader'); +}); diff --git a/static/plugins/webuploader/webuploader.html5only.min.js b/static/plugins/webuploader/webuploader.html5only.min.js new file mode 100644 index 00000000..8710e4ea --- /dev/null +++ b/static/plugins/webuploader/webuploader.html5only.min.js @@ -0,0 +1,2 @@ +/* WebUploader 0.1.5 */!function(a,b){var c,d={},e=function(a,b){var c,d,e;if("string"==typeof a)return h(a);for(c=[],d=a.length,e=0;d>e;e++)c.push(h(a[e]));return b.apply(null,c)},f=function(a,b,c){2===arguments.length&&(c=b,b=null),e(b||[],function(){g(a,c,arguments)})},g=function(a,b,c){var f,g={exports:b};"function"==typeof b&&(c.length||(c=[e,g.exports,g]),f=b.apply(null,c),void 0!==f&&(g.exports=f)),d[a]=g.exports},h=function(b){var c=d[b]||a[b];if(!c)throw new Error("`"+b+"` is undefined");return c},i=function(a){var b,c,e,f,g,h;h=function(a){return a&&a.charAt(0).toUpperCase()+a.substr(1)};for(b in d)if(c=a,d.hasOwnProperty(b)){for(e=b.split("/"),g=h(e.pop());f=h(e.shift());)c[f]=c[f]||{},c=c[f];c[g]=d[b]}return a},j=function(c){return a.__dollar=c,i(b(a,f,e))};"object"==typeof module&&"object"==typeof module.exports?module.exports=j():"function"==typeof define&&define.amd?define(["jquery"],j):(c=a.WebUploader,a.WebUploader=j(),a.WebUploader.noConflict=function(){a.WebUploader=c})}(window,function(a,b,c){return b("dollar-third",[],function(){var b=a.__dollar||a.jQuery||a.Zepto;if(!b)throw new Error("jQuery or Zepto not found!");return b}),b("dollar",["dollar-third"],function(a){return a}),b("promise-third",["dollar"],function(a){return{Deferred:a.Deferred,when:a.when,isPromise:function(a){return a&&"function"==typeof a.then}}}),b("promise",["promise-third"],function(a){return a}),b("base",["dollar","promise"],function(b,c){function d(a){return function(){return h.apply(a,arguments)}}function e(a,b){return function(){return a.apply(b,arguments)}}function f(a){var b;return Object.create?Object.create(a):(b=function(){},b.prototype=a,new b)}var g=function(){},h=Function.call;return{version:"0.1.5",$:b,Deferred:c.Deferred,isPromise:c.isPromise,when:c.when,browser:function(a){var b={},c=a.match(/WebKit\/([\d.]+)/),d=a.match(/Chrome\/([\d.]+)/)||a.match(/CriOS\/([\d.]+)/),e=a.match(/MSIE\s([\d\.]+)/)||a.match(/(?:trident)(?:.*rv:([\w.]+))?/i),f=a.match(/Firefox\/([\d.]+)/),g=a.match(/Safari\/([\d.]+)/),h=a.match(/OPR\/([\d.]+)/);return c&&(b.webkit=parseFloat(c[1])),d&&(b.chrome=parseFloat(d[1])),e&&(b.ie=parseFloat(e[1])),f&&(b.firefox=parseFloat(f[1])),g&&(b.safari=parseFloat(g[1])),h&&(b.opera=parseFloat(h[1])),b}(navigator.userAgent),os:function(a){var b={},c=a.match(/(?:Android);?[\s\/]+([\d.]+)?/),d=a.match(/(?:iPad|iPod|iPhone).*OS\s([\d_]+)/);return c&&(b.android=parseFloat(c[1])),d&&(b.ios=parseFloat(d[1].replace(/_/g,"."))),b}(navigator.userAgent),inherits:function(a,c,d){var e;return"function"==typeof c?(e=c,c=null):e=c&&c.hasOwnProperty("constructor")?c.constructor:function(){return a.apply(this,arguments)},b.extend(!0,e,a,d||{}),e.__super__=a.prototype,e.prototype=f(a.prototype),c&&b.extend(!0,e.prototype,c),e},noop:g,bindFn:e,log:function(){return a.console?e(console.log,console):g}(),nextTick:function(){return function(a){setTimeout(a,1)}}(),slice:d([].slice),guid:function(){var a=0;return function(b){for(var c=(+new Date).toString(32),d=0;5>d;d++)c+=Math.floor(65535*Math.random()).toString(32);return(b||"wu_")+c+(a++).toString(32)}}(),formatSize:function(a,b,c){var d;for(c=c||["B","K","M","G","TB"];(d=c.shift())&&a>1024;)a/=1024;return("B"===d?a:a.toFixed(b||2))+d}}}),b("mediator",["base"],function(a){function b(a,b,c,d){return f.grep(a,function(a){return!(!a||b&&a.e!==b||c&&a.cb!==c&&a.cb._cb!==c||d&&a.ctx!==d)})}function c(a,b,c){f.each((a||"").split(h),function(a,d){c(d,b)})}function d(a,b){for(var c,d=!1,e=-1,f=a.length;++e1?void(d.isPlainObject(b)&&d.isPlainObject(c[a])?d.extend(c[a],b):c[a]=b):a?c[a]:c},getStats:function(){var a=this.request("get-stats");return a?{successNum:a.numOfSuccess,progressNum:a.numOfProgress,cancelNum:a.numOfCancel,invalidNum:a.numOfInvalid,uploadFailNum:a.numOfUploadFailed,queueNum:a.numOfQueue,interruptNum:a.numofInterrupt}:{}},trigger:function(a){var c=[].slice.call(arguments,1),e=this.options,f="on"+a.substring(0,1).toUpperCase()+a.substring(1);return b.trigger.apply(this,arguments)===!1||d.isFunction(e[f])&&e[f].apply(this,c)===!1||d.isFunction(this[f])&&this[f].apply(this,c)===!1||b.trigger.apply(b,[this,a].concat(c))===!1?!1:!0},destroy:function(){this.request("destroy",arguments),this.off()},request:a.noop}),a.create=c.create=function(a){return new c(a)},a.Uploader=c,c}),b("runtime/runtime",["base","mediator"],function(a,b){function c(b){this.options=d.extend({container:document.body},b),this.uid=a.guid("rt_")}var d=a.$,e={},f=function(a){for(var b in a)if(a.hasOwnProperty(b))return b;return null};return d.extend(c.prototype,{getContainer:function(){var a,b,c=this.options;return this._container?this._container:(a=d(c.container||document.body),b=d(document.createElement("div")),b.attr("id","rt_"+this.uid),b.css({position:"absolute",top:"0px",left:"0px",width:"1px",height:"1px",overflow:"hidden"}),a.append(b),a.addClass("webuploader-container"),this._container=b,this._parent=a,b)},init:a.noop,exec:a.noop,destroy:function(){this._container&&this._container.remove(),this._parent&&this._parent.removeClass("webuploader-container"),this.off()}}),c.orders="html5,flash",c.addRuntime=function(a,b){e[a]=b},c.hasRuntime=function(a){return!!(a?e[a]:f(e))},c.create=function(a,b){var g,h;if(b=b||c.orders,d.each(b.split(/\s*,\s*/g),function(){return e[this]?(g=this,!1):void 0}),g=g||f(e),!g)throw new Error("Runtime Error");return h=new e[g](a)},b.installTo(c.prototype),c}),b("runtime/client",["base","mediator","runtime/runtime"],function(a,b,c){function d(b,d){var f,g=a.Deferred();this.uid=a.guid("client_"),this.runtimeReady=function(a){return g.done(a)},this.connectRuntime=function(b,h){if(f)throw new Error("already connected!");return g.done(h),"string"==typeof b&&e.get(b)&&(f=e.get(b)),f=f||e.get(null,d),f?(a.$.extend(f.options,b),f.__promise.then(g.resolve),f.__client++):(f=c.create(b,b.runtimeOrder),f.__promise=g.promise(),f.once("ready",g.resolve),f.init(),e.add(f),f.__client=1),d&&(f.__standalone=d),f},this.getRuntime=function(){return f},this.disconnectRuntime=function(){f&&(f.__client--,f.__client<=0&&(e.remove(f),delete f.__promise,f.destroy()),f=null)},this.exec=function(){if(f){var c=a.slice(arguments);return b&&c.unshift(b),f.exec.apply(this,c)}},this.getRuid=function(){return f&&f.uid},this.destroy=function(a){return function(){a&&a.apply(this,arguments),this.trigger("destroy"),this.off(),this.exec("destroy"),this.disconnectRuntime()}}(this.destroy)}var e;return e=function(){var a={};return{add:function(b){a[b.uid]=b},get:function(b,c){var d;if(b)return a[b];for(d in a)if(!c||!a[d].__standalone)return a[d];return null},remove:function(b){delete a[b.uid]}}}(),b.installTo(d.prototype),d}),b("lib/dnd",["base","mediator","runtime/client"],function(a,b,c){function d(a){a=this.options=e.extend({},d.options,a),a.container=e(a.container),a.container.length&&c.call(this,"DragAndDrop")}var e=a.$;return d.options={accept:null,disableGlobalDnd:!1},a.inherits(c,{constructor:d,init:function(){var a=this;a.connectRuntime(a.options,function(){a.exec("init"),a.trigger("ready")})}}),b.installTo(d.prototype),d}),b("widgets/widget",["base","uploader"],function(a,b){function c(a){if(!a)return!1;var b=a.length,c=e.type(a);return 1===a.nodeType&&b?!0:"array"===c||"function"!==c&&"string"!==c&&(0===b||"number"==typeof b&&b>0&&b-1 in a)}function d(a){this.owner=a,this.options=a.options}var e=a.$,f=b.prototype._init,g=b.prototype.destroy,h={},i=[];return e.extend(d.prototype,{init:a.noop,invoke:function(a,b){var c=this.responseMap;return c&&a in c&&c[a]in this&&e.isFunction(this[c[a]])?this[c[a]].apply(this,b):h},request:function(){return this.owner.request.apply(this.owner,arguments)}}),e.extend(b.prototype,{_init:function(){var a=this,b=a._widgets=[],c=a.options.disableWidgets||"";return e.each(i,function(d,e){(!c||!~c.indexOf(e._name))&&b.push(new e(a))}),f.apply(a,arguments)},request:function(b,d,e){var f,g,i,j,k=0,l=this._widgets,m=l&&l.length,n=[],o=[];for(d=c(d)?d:[d];m>k;k++)f=l[k],g=f.invoke(b,d),g!==h&&(a.isPromise(g)?o.push(g):n.push(g));return e||o.length?(i=a.when.apply(a,o),j=i.pipe?"pipe":"then",i[j](function(){var b=a.Deferred(),c=arguments;return 1===c.length&&(c=c[0]),setTimeout(function(){b.resolve(c)},1),b.promise()})[e?j:"done"](e||a.noop)):n[0]},destroy:function(){g.apply(this,arguments),this._widgets=null}}),b.register=d.register=function(b,c){var f,g={init:"init",destroy:"destroy",name:"anonymous"};return 1===arguments.length?(c=b,e.each(c,function(a){return"_"===a[0]||"name"===a?void("name"===a&&(g.name=c.name)):void(g[a.replace(/[A-Z]/g,"-$&").toLowerCase()]=a)})):g=e.extend(g,b),c.responseMap=g,f=a.inherits(d,c),f._name=g.name,i.push(f),f},b.unRegister=d.unRegister=function(a){if(a&&"anonymous"!==a)for(var b=i.length;b--;)i[b]._name===a&&i.splice(b,1)},d}),b("widgets/filednd",["base","uploader","lib/dnd","widgets/widget"],function(a,b,c){var d=a.$;return b.options.dnd="",b.register({name:"dnd",init:function(b){if(b.dnd&&"html5"===this.request("predict-runtime-type")){var e,f=this,g=a.Deferred(),h=d.extend({},{disableGlobalDnd:b.disableGlobalDnd,container:b.dnd,accept:b.accept});return this.dnd=e=new c(h),e.once("ready",g.resolve),e.on("drop",function(a){f.request("add-file",[a])}),e.on("accept",function(a){return f.owner.trigger("dndAccept",a)}),e.init(),g.promise()}},destroy:function(){this.dnd&&this.dnd.destroy()}})}),b("lib/filepaste",["base","mediator","runtime/client"],function(a,b,c){function d(a){a=this.options=e.extend({},a),a.container=e(a.container||document.body),c.call(this,"FilePaste")}var e=a.$;return a.inherits(c,{constructor:d,init:function(){var a=this;a.connectRuntime(a.options,function(){a.exec("init"),a.trigger("ready")})}}),b.installTo(d.prototype),d}),b("widgets/filepaste",["base","uploader","lib/filepaste","widgets/widget"],function(a,b,c){var d=a.$;return b.register({name:"paste",init:function(b){if(b.paste&&"html5"===this.request("predict-runtime-type")){var e,f=this,g=a.Deferred(),h=d.extend({},{container:b.paste,accept:b.accept});return this.paste=e=new c(h),e.once("ready",g.resolve),e.on("paste",function(a){f.owner.request("add-file",[a])}),e.init(),g.promise()}},destroy:function(){this.paste&&this.paste.destroy()}})}),b("lib/blob",["base","runtime/client"],function(a,b){function c(a,c){var d=this;d.source=c,d.ruid=a,this.size=c.size||0,this.type=!c.type&&this.ext&&~"jpg,jpeg,png,gif,bmp".indexOf(this.ext)?"image/"+("jpg"===this.ext?"jpeg":this.ext):c.type||"application/octet-stream",b.call(d,"Blob"),this.uid=c.uid||this.uid,a&&d.connectRuntime(a)}return a.inherits(b,{constructor:c,slice:function(a,b){return this.exec("slice",a,b)},getSource:function(){return this.source}}),c}),b("lib/file",["base","lib/blob"],function(a,b){function c(a,c){var f;this.name=c.name||"untitled"+d++,f=e.exec(c.name)?RegExp.$1.toLowerCase():"",!f&&c.type&&(f=/\/(jpg|jpeg|png|gif|bmp)$/i.exec(c.type)?RegExp.$1.toLowerCase():"",this.name+="."+f),this.ext=f,this.lastModifiedDate=c.lastModifiedDate||(new Date).toLocaleString(),b.apply(this,arguments)}var d=1,e=/\.([^.]+)$/;return a.inherits(b,c)}),b("lib/filepicker",["base","runtime/client","lib/file"],function(b,c,d){function e(a){if(a=this.options=f.extend({},e.options,a),a.container=f(a.id),!a.container.length)throw new Error("按钮指定错误");a.innerHTML=a.innerHTML||a.label||a.container.html()||"",a.button=f(a.button||document.createElement("div")),a.button.html(a.innerHTML),a.container.html(a.button),c.call(this,"FilePicker",!0)}var f=b.$;return e.options={button:null,container:null,label:null,innerHTML:null,multiple:!0,accept:null,name:"file"},b.inherits(c,{constructor:e,init:function(){var c=this,e=c.options,g=e.button;g.addClass("webuploader-pick"),c.on("all",function(a){var b;switch(a){case"mouseenter":g.addClass("webuploader-pick-hover");break;case"mouseleave":g.removeClass("webuploader-pick-hover");break;case"change":b=c.exec("getFiles"),c.trigger("select",f.map(b,function(a){return a=new d(c.getRuid(),a),a._refer=e.container,a}),e.container)}}),c.connectRuntime(e,function(){c.refresh(),c.exec("init",e),c.trigger("ready")}),this._resizeHandler=b.bindFn(this.refresh,this),f(a).on("resize",this._resizeHandler)},refresh:function(){var a=this.getRuntime().getContainer(),b=this.options.button,c=b.outerWidth?b.outerWidth():b.width(),d=b.outerHeight?b.outerHeight():b.height(),e=b.offset();c&&d&&a.css({bottom:"auto",right:"auto",width:c+"px",height:d+"px"}).offset(e)},enable:function(){var a=this.options.button;a.removeClass("webuploader-pick-disable"),this.refresh()},disable:function(){var a=this.options.button;this.getRuntime().getContainer().css({top:"-99999px"}),a.addClass("webuploader-pick-disable")},destroy:function(){var b=this.options.button;f(a).off("resize",this._resizeHandler),b.removeClass("webuploader-pick-disable webuploader-pick-hover webuploader-pick")}}),e}),b("widgets/filepicker",["base","uploader","lib/filepicker","widgets/widget"],function(a,b,c){var d=a.$;return d.extend(b.options,{pick:null,accept:null}),b.register({name:"picker",init:function(a){return this.pickers=[],a.pick&&this.addBtn(a.pick)},refresh:function(){d.each(this.pickers,function(){this.refresh()})},addBtn:function(b){var e=this,f=e.options,g=f.accept,h=[];if(b)return d.isPlainObject(b)||(b={id:b}),d(b.id).each(function(){var i,j,k;k=a.Deferred(),i=d.extend({},b,{accept:d.isPlainObject(g)?[g]:g,swf:f.swf,runtimeOrder:f.runtimeOrder,id:this}),j=new c(i),j.once("ready",k.resolve),j.on("select",function(a){e.owner.request("add-file",[a])}),j.init(),e.pickers.push(j),h.push(k.promise())}),a.when.apply(a,h)},disable:function(){d.each(this.pickers,function(){this.disable()})},enable:function(){d.each(this.pickers,function(){this.enable()})},destroy:function(){d.each(this.pickers,function(){this.destroy()}),this.pickers=null}})}),b("lib/image",["base","runtime/client","lib/blob"],function(a,b,c){function d(a){this.options=e.extend({},d.options,a),b.call(this,"Image"),this.on("load",function(){this._info=this.exec("info"),this._meta=this.exec("meta")})}var e=a.$;return d.options={quality:90,crop:!1,preserveHeaders:!1,allowMagnify:!1},a.inherits(b,{constructor:d,info:function(a){return a?(this._info=a,this):this._info},meta:function(a){return a?(this._meta=a,this):this._meta},loadFromBlob:function(a){var b=this,c=a.getRuid();this.connectRuntime(c,function(){b.exec("init",b.options),b.exec("loadFromBlob",a)})},resize:function(){var b=a.slice(arguments);return this.exec.apply(this,["resize"].concat(b))},crop:function(){var b=a.slice(arguments);return this.exec.apply(this,["crop"].concat(b))},getAsDataUrl:function(a){return this.exec("getAsDataUrl",a)},getAsBlob:function(a){var b=this.exec("getAsBlob",a);return new c(this.getRuid(),b)}}),d}),b("widgets/image",["base","uploader","lib/image","widgets/widget"],function(a,b,c){var d,e=a.$;return d=function(a){var b=0,c=[],d=function(){for(var d;c.length&&a>b;)d=c.shift(),b+=d[0],d[1]()};return function(a,e,f){c.push([e,f]),a.once("destroy",function(){b-=e,setTimeout(d,1)}),setTimeout(d,1)}}(5242880),e.extend(b.options,{thumb:{width:110,height:110,quality:70,allowMagnify:!0,crop:!0,preserveHeaders:!1,type:"image/jpeg"},compress:{width:1600,height:1600,quality:90,allowMagnify:!1,crop:!1,preserveHeaders:!0}}),b.register({name:"image",makeThumb:function(a,b,f,g){var h,i;return a=this.request("get-file",a),a.type.match(/^image/)?(h=e.extend({},this.options.thumb),e.isPlainObject(f)&&(h=e.extend(h,f),f=null),f=f||h.width,g=g||h.height,i=new c(h),i.once("load",function(){a._info=a._info||i.info(),a._meta=a._meta||i.meta(),1>=f&&f>0&&(f=a._info.width*f),1>=g&&g>0&&(g=a._info.height*g),i.resize(f,g)}),i.once("complete",function(){b(!1,i.getAsDataUrl(h.type)),i.destroy()}),i.once("error",function(a){b(a||!0),i.destroy()}),void d(i,a.source.size,function(){a._info&&i.info(a._info),a._meta&&i.meta(a._meta),i.loadFromBlob(a.source)})):void b(!0)},beforeSendFile:function(b){var d,f,g=this.options.compress||this.options.resize,h=g&&g.compressSize||0,i=g&&g.noCompressIfLarger||!1;return b=this.request("get-file",b),!g||!~"image/jpeg,image/jpg".indexOf(b.type)||b.size=a&&a>0&&(a=b._info.width*a),1>=c&&c>0&&(c=b._info.height*c),d.resize(a,c)}),d.once("complete",function(){var a,c;try{a=d.getAsBlob(g.type),c=b.size,(!i||a.sizeb;b++)if(c=this._queue[b],a===c.getStatus())return c;return null},sort:function(a){"function"==typeof a&&this._queue.sort(a)},getFiles:function(){for(var a,b=[].slice.call(arguments,0),c=[],d=0,f=this._queue.length;f>d;d++)a=this._queue[d],(!b.length||~e.inArray(a.getStatus(),b))&&c.push(a);return c},removeFile:function(a){var b=this._map[a.id];b&&(delete this._map[a.id],a.destroy(),this.stats.numofDeleted++)},_fileAdded:function(a){var b=this,c=this._map[a.id];c||(this._map[a.id]=a,a.on("statuschange",function(a,c){b._onFileStatusChange(a,c)}))},_onFileStatusChange:function(a,b){var c=this.stats;switch(b){case f.PROGRESS:c.numOfProgress--;break;case f.QUEUED:c.numOfQueue--;break;case f.ERROR:c.numOfUploadFailed--;break;case f.INVALID:c.numOfInvalid--;break;case f.INTERRUPT:c.numofInterrupt--}switch(a){case f.QUEUED:c.numOfQueue++;break;case f.PROGRESS:c.numOfProgress++;break;case f.ERROR:c.numOfUploadFailed++;break;case f.COMPLETE:c.numOfSuccess++;break;case f.CANCELLED:c.numOfCancel++;break;case f.INVALID:c.numOfInvalid++;break;case f.INTERRUPT:c.numofInterrupt++}}}),b.installTo(d.prototype),d}),b("widgets/queue",["base","uploader","queue","file","lib/file","runtime/client","widgets/widget"],function(a,b,c,d,e,f){var g=a.$,h=/\.\w+$/,i=d.Status;return b.register({name:"queue",init:function(b){var d,e,h,i,j,k,l,m=this;if(g.isPlainObject(b.accept)&&(b.accept=[b.accept]),b.accept){for(j=[],h=0,e=b.accept.length;e>h;h++)i=b.accept[h].extensions,i&&j.push(i);j.length&&(k="\\."+j.join(",").replace(/,/g,"$|\\.").replace(/\*/g,".*")+"$"),m.accept=new RegExp(k,"i")}return m.queue=new c,m.stats=m.queue.stats,"html5"===this.request("predict-runtime-type")?(d=a.Deferred(),this.placeholder=l=new f("Placeholder"),l.connectRuntime({runtimeOrder:"html5"},function(){m._ruid=l.getRuid(),d.resolve()}),d.promise()):void 0},_wrapFile:function(a){if(!(a instanceof d)){if(!(a instanceof e)){if(!this._ruid)throw new Error("Can't add external files.");a=new e(this._ruid,a)}a=new d(a)}return a},acceptFile:function(a){var b=!a||!a.size||this.accept&&h.exec(a.name)&&!this.accept.test(a.name);return!b},_addFile:function(a){var b=this;return a=b._wrapFile(a),b.owner.trigger("beforeFileQueued",a)?b.acceptFile(a)?(b.queue.append(a),b.owner.trigger("fileQueued",a),a):void b.owner.trigger("error","Q_TYPE_DENIED",a):void 0},getFile:function(a){return this.queue.getFile(a)},addFile:function(a){var b=this;a.length||(a=[a]),a=g.map(a,function(a){return b._addFile(a)}),b.owner.trigger("filesQueued",a),b.options.auto&&setTimeout(function(){b.request("start-upload")},20)},getStats:function(){return this.stats},removeFile:function(a,b){var c=this;a=a.id?a:c.queue.getFile(a),this.request("cancel-file",a),b&&this.queue.removeFile(a)},getFiles:function(){return this.queue.getFiles.apply(this.queue,arguments)},fetchFile:function(){return this.queue.fetch.apply(this.queue,arguments)},retry:function(a,b){var c,d,e,f=this;if(a)return a=a.id?a:f.queue.getFile(a),a.setStatus(i.QUEUED),void(b||f.request("start-upload"));for(c=f.queue.getFiles(i.ERROR),d=0,e=c.length;e>d;d++)a=c[d],a.setStatus(i.QUEUED);f.request("start-upload")},sortFiles:function(){return this.queue.sort.apply(this.queue,arguments)},reset:function(){this.owner.trigger("reset"),this.queue=new c,this.stats=this.queue.stats},destroy:function(){this.reset(),this.placeholder&&this.placeholder.destroy()}})}),b("widgets/runtime",["uploader","runtime/runtime","widgets/widget"],function(a,b){return a.support=function(){return b.hasRuntime.apply(b,arguments)},a.register({name:"runtime",init:function(){if(!this.predictRuntimeType())throw Error("Runtime Error")},predictRuntimeType:function(){var a,c,d=this.options.runtimeOrder||b.orders,e=this.type;if(!e)for(d=d.split(/\s*,\s*/g),a=0,c=d.length;c>a;a++)if(b.hasRuntime(d[a])){this.type=e=d[a];break}return e}})}),b("lib/transport",["base","runtime/client","mediator"],function(a,b,c){function d(a){var c=this;a=c.options=e.extend(!0,{},d.options,a||{}),b.call(this,"Transport"),this._blob=null,this._formData=a.formData||{},this._headers=a.headers||{},this.on("progress",this._timeout),this.on("load error",function(){c.trigger("progress",1),clearTimeout(c._timer)})}var e=a.$;return d.options={server:"",method:"POST",withCredentials:!1,fileVal:"file",timeout:12e4,formData:{},headers:{},sendAsBinary:!1},e.extend(d.prototype,{appendBlob:function(a,b,c){var d=this,e=d.options;d.getRuid()&&d.disconnectRuntime(),d.connectRuntime(b.ruid,function(){d.exec("init")}),d._blob=b,e.fileVal=a||e.fileVal,e.filename=c||e.filename},append:function(a,b){"object"==typeof a?e.extend(this._formData,a):this._formData[a]=b},setRequestHeader:function(a,b){"object"==typeof a?e.extend(this._headers,a):this._headers[a]=b},send:function(a){this.exec("send",a),this._timeout()},abort:function(){return clearTimeout(this._timer),this.exec("abort")},destroy:function(){this.trigger("destroy"),this.off(),this.exec("destroy"),this.disconnectRuntime()},getResponse:function(){return this.exec("getResponse")},getResponseAsJson:function(){return this.exec("getResponseAsJson")},getStatus:function(){return this.exec("getStatus")},_timeout:function(){var a=this,b=a.options.timeout;b&&(clearTimeout(a._timer),a._timer=setTimeout(function(){a.abort(),a.trigger("error","timeout")},b))}}),c.installTo(d.prototype),d}),b("widgets/upload",["base","uploader","file","lib/transport","widgets/widget"],function(a,b,c,d){function e(a,b){var c,d,e=[],f=a.source,g=f.size,h=b?Math.ceil(g/b):1,i=0,j=0;for(d={file:a,has:function(){return!!e.length},shift:function(){return e.shift()},unshift:function(a){e.unshift(a)}};h>j;)c=Math.min(b,g-i),e.push({file:a,start:i,end:b?i+c:g,total:g,chunks:h,chunk:j++,cuted:d}),i+=c;return a.blocks=e.concat(),a.remaning=e.length,d}var f=a.$,g=a.isPromise,h=c.Status;f.extend(b.options,{prepareNextFile:!1,chunked:!1,chunkSize:5242880,chunkRetry:2,threads:3,formData:{}}),b.register({name:"upload",init:function(){var b=this.owner,c=this;this.runing=!1,this.progress=!1,b.on("startUpload",function(){c.progress=!0}).on("uploadFinished",function(){c.progress=!1}),this.pool=[],this.stack=[],this.pending=[],this.remaning=0,this.__tick=a.bindFn(this._tick,this),b.on("uploadComplete",function(a){a.blocks&&f.each(a.blocks,function(a,b){b.transport&&(b.transport.abort(),b.transport.destroy()),delete b.transport}),delete a.blocks,delete a.remaning})},reset:function(){this.request("stop-upload",!0),this.runing=!1,this.pool=[],this.stack=[],this.pending=[],this.remaning=0,this._trigged=!1,this._promise=null},startUpload:function(b){var c=this;if(f.each(c.request("get-files",h.INVALID),function(){c.request("remove-file",this)}),b)if(b=b.id?b:c.request("get-file",b),b.getStatus()===h.INTERRUPT)f.each(c.pool,function(a,c){c.file===b&&c.transport&&c.transport.send()}),b.setStatus(h.QUEUED);else{if(b.getStatus()===h.PROGRESS)return;b.setStatus(h.QUEUED)}else f.each(c.request("get-files",[h.INITED]),function(){this.setStatus(h.QUEUED)});if(!c.runing){c.runing=!0;var d=[];f.each(c.pool,function(a,b){var e=b.file;e.getStatus()===h.INTERRUPT&&(d.push(e),c._trigged=!1,b.transport&&b.transport.send())});for(var b;b=d.shift();)b.setStatus(h.PROGRESS);b||f.each(c.request("get-files",h.INTERRUPT),function(){this.setStatus(h.PROGRESS)}),c._trigged=!1,a.nextTick(c.__tick),c.owner.trigger("startUpload")}},stopUpload:function(b,c){var d=this;if(b===!0&&(c=b,b=null),d.runing!==!1){if(b){if(b=b.id?b:d.request("get-file",b),b.getStatus()!==h.PROGRESS&&b.getStatus()!==h.QUEUED)return;return b.setStatus(h.INTERRUPT),f.each(d.pool,function(a,c){c.file===b&&(c.transport&&c.transport.abort(),d._putback(c),d._popBlock(c))}),a.nextTick(d.__tick)}d.runing=!1,this._promise&&this._promise.file&&this._promise.file.setStatus(h.INTERRUPT),c&&f.each(d.pool,function(a,b){b.transport&&b.transport.abort(),b.file.setStatus(h.INTERRUPT)}),d.owner.trigger("stopUpload")}},cancelFile:function(a){a=a.id?a:this.request("get-file",a),a.blocks&&f.each(a.blocks,function(a,b){var c=b.transport;c&&(c.abort(),c.destroy(),delete b.transport)}),a.setStatus(h.CANCELLED),this.owner.trigger("fileDequeued",a)},isInProgress:function(){return!!this.progress},_getStats:function(){return this.request("get-stats")},skipFile:function(a,b){a=a.id?a:this.request("get-file",a),a.setStatus(b||h.COMPLETE),a.skipped=!0,a.blocks&&f.each(a.blocks,function(a,b){var c=b.transport;c&&(c.abort(),c.destroy(),delete b.transport)}),this.owner.trigger("uploadSkip",a)},_tick:function(){var b,c,d=this,e=d.options;return d._promise?d._promise.always(d.__tick):void(d.pool.length1&&~"http,abort".indexOf(a)&&b.retried1&&f.extend(m,{chunks:b.chunks,chunk:b.chunk}),i.trigger("uploadBeforeSend",b,m,n),l.appendBlob(j.fileVal,b.blob,k.name),l.append(m),l.setRequestHeader(n),l.send()},_finishFile:function(a,b,c){var d=this.owner;return d.request("after-send-file",arguments,function(){a.setStatus(h.COMPLETE),d.trigger("uploadSuccess",a,b,c)}).fail(function(b){a.getStatus()===h.PROGRESS&&a.setStatus(h.ERROR,b),d.trigger("uploadError",a,b) +}).always(function(){d.trigger("uploadComplete",a)})},updateFileProgress:function(a){var b=0,c=0;a.blocks&&(f.each(a.blocks,function(a,b){c+=(b.percentage||0)*(b.end-b.start)}),b=c/a.size,this.owner.trigger("uploadProgress",a,b||0))}})}),b("widgets/validator",["base","uploader","file","widgets/widget"],function(a,b,c){var d,e=a.$,f={};return d={addValidator:function(a,b){f[a]=b},removeValidator:function(a){delete f[a]}},b.register({name:"validator",init:function(){var b=this;a.nextTick(function(){e.each(f,function(){this.call(b.owner)})})}}),d.addValidator("fileNumLimit",function(){var a=this,b=a.options,c=0,d=parseInt(b.fileNumLimit,10),e=!0;d&&(a.on("beforeFileQueued",function(a){return c>=d&&e&&(e=!1,this.trigger("error","Q_EXCEED_NUM_LIMIT",d,a),setTimeout(function(){e=!0},1)),c>=d?!1:!0}),a.on("fileQueued",function(){c++}),a.on("fileDequeued",function(){c--}),a.on("reset",function(){c=0}))}),d.addValidator("fileSizeLimit",function(){var a=this,b=a.options,c=0,d=parseInt(b.fileSizeLimit,10),e=!0;d&&(a.on("beforeFileQueued",function(a){var b=c+a.size>d;return b&&e&&(e=!1,this.trigger("error","Q_EXCEED_SIZE_LIMIT",d,a),setTimeout(function(){e=!0},1)),b?!1:!0}),a.on("fileQueued",function(a){c+=a.size}),a.on("fileDequeued",function(a){c-=a.size}),a.on("reset",function(){c=0}))}),d.addValidator("fileSingleSizeLimit",function(){var a=this,b=a.options,d=b.fileSingleSizeLimit;d&&a.on("beforeFileQueued",function(a){return a.size>d?(a.setStatus(c.Status.INVALID,"exceed_size"),this.trigger("error","F_EXCEED_SIZE",d,a),!1):void 0})}),d.addValidator("duplicate",function(){function a(a){for(var b,c=0,d=0,e=a.length;e>d;d++)b=a.charCodeAt(d),c=b+(c<<6)+(c<<16)-c;return c}var b=this,c=b.options,d={};c.duplicate||(b.on("beforeFileQueued",function(b){var c=b.__hash||(b.__hash=a(b.name+b.size+b.lastModifiedDate));return d[c]?(this.trigger("error","F_DUPLICATE",b),!1):void 0}),b.on("fileQueued",function(a){var b=a.__hash;b&&(d[b]=!0)}),b.on("fileDequeued",function(a){var b=a.__hash;b&&delete d[b]}),b.on("reset",function(){d={}}))}),d}),b("runtime/compbase",[],function(){function a(a,b){this.owner=a,this.options=a.options,this.getRuntime=function(){return b},this.getRuid=function(){return b.uid},this.trigger=function(){return a.trigger.apply(a,arguments)}}return a}),b("runtime/html5/runtime",["base","runtime/runtime","runtime/compbase"],function(b,c,d){function e(){var a={},d=this,e=this.destroy;c.apply(d,arguments),d.type=f,d.exec=function(c,e){var f,h=this,i=h.uid,j=b.slice(arguments,2);return g[c]&&(f=a[i]=a[i]||new g[c](h,d),f[e])?f[e].apply(f,j):void 0},d.destroy=function(){return e&&e.apply(this,arguments)}}var f="html5",g={};return b.inherits(c,{constructor:e,init:function(){var a=this;setTimeout(function(){a.trigger("ready")},1)}}),e.register=function(a,c){var e=g[a]=b.inherits(d,c);return e},a.Blob&&a.FileReader&&a.DataView&&c.addRuntime(f,e),e}),b("runtime/html5/blob",["runtime/html5/runtime","lib/blob"],function(a,b){return a.register("Blob",{slice:function(a,c){var d=this.owner.source,e=d.slice||d.webkitSlice||d.mozSlice;return d=e.call(d,a,c),new b(this.getRuid(),d)}})}),b("runtime/html5/dnd",["base","runtime/html5/runtime","lib/file"],function(a,b,c){var d=a.$,e="webuploader-dnd-";return b.register("DragAndDrop",{init:function(){var b=this.elem=this.options.container;this.dragEnterHandler=a.bindFn(this._dragEnterHandler,this),this.dragOverHandler=a.bindFn(this._dragOverHandler,this),this.dragLeaveHandler=a.bindFn(this._dragLeaveHandler,this),this.dropHandler=a.bindFn(this._dropHandler,this),this.dndOver=!1,b.on("dragenter",this.dragEnterHandler),b.on("dragover",this.dragOverHandler),b.on("dragleave",this.dragLeaveHandler),b.on("drop",this.dropHandler),this.options.disableGlobalDnd&&(d(document).on("dragover",this.dragOverHandler),d(document).on("drop",this.dropHandler))},_dragEnterHandler:function(a){var b,c=this,d=c._denied||!1;return a=a.originalEvent||a,c.dndOver||(c.dndOver=!0,b=a.dataTransfer.items,b&&b.length&&(c._denied=d=!c.trigger("accept",b)),c.elem.addClass(e+"over"),c.elem[d?"addClass":"removeClass"](e+"denied")),a.dataTransfer.dropEffect=d?"none":"copy",!1},_dragOverHandler:function(a){var b=this.elem.parent().get(0);return b&&!d.contains(b,a.currentTarget)?!1:(clearTimeout(this._leaveTimer),this._dragEnterHandler.call(this,a),!1)},_dragLeaveHandler:function(){var a,b=this;return a=function(){b.dndOver=!1,b.elem.removeClass(e+"over "+e+"denied")},clearTimeout(b._leaveTimer),b._leaveTimer=setTimeout(a,100),!1},_dropHandler:function(a){var b,f,g=this,h=g.getRuid(),i=g.elem.parent().get(0);if(i&&!d.contains(i,a.currentTarget))return!1;a=a.originalEvent||a,b=a.dataTransfer;try{f=b.getData("text/html")}catch(j){}return f?void 0:(g._getTansferFiles(b,function(a){g.trigger("drop",d.map(a,function(a){return new c(h,a)}))}),g.dndOver=!1,g.elem.removeClass(e+"over"),!1)},_getTansferFiles:function(b,c){var d,e,f,g,h,i,j,k=[],l=[];for(d=b.items,e=b.files,j=!(!d||!d[0].webkitGetAsEntry),h=0,i=e.length;i>h;h++)f=e[h],g=d&&d[h],j&&g.webkitGetAsEntry().isDirectory?l.push(this._traverseDirectoryTree(g.webkitGetAsEntry(),k)):k.push(f);a.when.apply(a,l).done(function(){k.length&&c(k)})},_traverseDirectoryTree:function(b,c){var d=a.Deferred(),e=this;return b.isFile?b.file(function(a){c.push(a),d.resolve()}):b.isDirectory&&b.createReader().readEntries(function(b){var f,g=b.length,h=[],i=[];for(f=0;g>f;f++)h.push(e._traverseDirectoryTree(b[f],i));a.when.apply(a,h).then(function(){c.push.apply(c,i),d.resolve()},d.reject)}),d.promise()},destroy:function(){var a=this.elem;a&&(a.off("dragenter",this.dragEnterHandler),a.off("dragover",this.dragOverHandler),a.off("dragleave",this.dragLeaveHandler),a.off("drop",this.dropHandler),this.options.disableGlobalDnd&&(d(document).off("dragover",this.dragOverHandler),d(document).off("drop",this.dropHandler)))}})}),b("runtime/html5/filepaste",["base","runtime/html5/runtime","lib/file"],function(a,b,c){return b.register("FilePaste",{init:function(){var b,c,d,e,f=this.options,g=this.elem=f.container,h=".*";if(f.accept){for(b=[],c=0,d=f.accept.length;d>c;c++)e=f.accept[c].mimeTypes,e&&b.push(e);b.length&&(h=b.join(","),h=h.replace(/,/g,"|").replace(/\*/g,".*"))}this.accept=h=new RegExp(h,"i"),this.hander=a.bindFn(this._pasteHander,this),g.on("paste",this.hander)},_pasteHander:function(a){var b,d,e,f,g,h=[],i=this.getRuid();for(a=a.originalEvent||a,b=a.clipboardData.items,f=0,g=b.length;g>f;f++)d=b[f],"file"===d.kind&&(e=d.getAsFile())&&h.push(new c(i,e));h.length&&(a.preventDefault(),a.stopPropagation(),this.trigger("paste",h))},destroy:function(){this.elem.off("paste",this.hander)}})}),b("runtime/html5/filepicker",["base","runtime/html5/runtime"],function(a,b){var c=a.$;return b.register("FilePicker",{init:function(){var a,b,d,e,f=this.getRuntime().getContainer(),g=this,h=g.owner,i=g.options,j=this.label=c(document.createElement("label")),k=this.input=c(document.createElement("input"));if(k.attr("type","file"),k.attr("name",i.name),k.addClass("webuploader-element-invisible"),j.on("click",function(){k.trigger("click")}),j.css({opacity:0,width:"100%",height:"100%",display:"block",cursor:"pointer",background:"#ffffff"}),i.multiple&&k.attr("multiple","multiple"),i.accept&&i.accept.length>0){for(a=[],b=0,d=i.accept.length;d>b;b++)a.push(i.accept[b].mimeTypes);k.attr("accept",a.join(","))}f.append(k),f.append(j),e=function(a){h.trigger(a.type)},k.on("change",function(a){var b,d=arguments.callee;g.files=a.target.files,b=this.cloneNode(!0),b.value=null,this.parentNode.replaceChild(b,this),k.off(),k=c(b).on("change",d).on("mouseenter mouseleave",e),h.trigger("change")}),j.on("mouseenter mouseleave",e)},getFiles:function(){return this.files},destroy:function(){this.input.off(),this.label.off()}})}),b("runtime/html5/util",["base"],function(b){var c=a.createObjectURL&&a||a.URL&&URL.revokeObjectURL&&URL||a.webkitURL,d=b.noop,e=d;return c&&(d=function(){return c.createObjectURL.apply(c,arguments)},e=function(){return c.revokeObjectURL.apply(c,arguments)}),{createObjectURL:d,revokeObjectURL:e,dataURL2Blob:function(a){var b,c,d,e,f,g;for(g=a.split(","),b=~g[0].indexOf("base64")?atob(g[1]):decodeURIComponent(g[1]),d=new ArrayBuffer(b.length),c=new Uint8Array(d),e=0;ei&&(d=h.getUint16(i),d>=65504&&65519>=d||65534===d)&&(e=h.getUint16(i+2)+2,!(i+e>h.byteLength));){if(f=b.parsers[d],!c&&f)for(g=0;g6&&(l.imageHead=a.slice?a.slice(2,k):new Uint8Array(a).subarray(2,k))}return l}},updateImageHead:function(a,b){var c,d,e,f=this._parse(a,!0);return e=2,f.imageHead&&(e=2+f.imageHead.byteLength),d=a.slice?a.slice(e):new Uint8Array(a).subarray(e),c=new Uint8Array(b.byteLength+2+d.byteLength),c[0]=255,c[1]=216,c.set(new Uint8Array(b),2),c.set(new Uint8Array(d),b.byteLength+2),c.buffer}},a.parseMeta=function(){return b.parse.apply(b,arguments)},a.updateImageHead=function(){return b.updateImageHead.apply(b,arguments)},b}),b("runtime/html5/imagemeta/exif",["base","runtime/html5/imagemeta"],function(a,b){var c={};return c.ExifMap=function(){return this},c.ExifMap.prototype.map={Orientation:274},c.ExifMap.prototype.get=function(a){return this[a]||this[this.map[a]]},c.exifTagTypes={1:{getValue:function(a,b){return a.getUint8(b)},size:1},2:{getValue:function(a,b){return String.fromCharCode(a.getUint8(b))},size:1,ascii:!0},3:{getValue:function(a,b,c){return a.getUint16(b,c)},size:2},4:{getValue:function(a,b,c){return a.getUint32(b,c)},size:4},5:{getValue:function(a,b,c){return a.getUint32(b,c)/a.getUint32(b+4,c)},size:8},9:{getValue:function(a,b,c){return a.getInt32(b,c)},size:4},10:{getValue:function(a,b,c){return a.getInt32(b,c)/a.getInt32(b+4,c)},size:8}},c.exifTagTypes[7]=c.exifTagTypes[1],c.getExifValue=function(b,d,e,f,g,h){var i,j,k,l,m,n,o=c.exifTagTypes[f];if(!o)return void a.log("Invalid Exif data: Invalid tag type.");if(i=o.size*g,j=i>4?d+b.getUint32(e+8,h):e+8,j+i>b.byteLength)return void a.log("Invalid Exif data: Invalid data offset.");if(1===g)return o.getValue(b,j,h);for(k=[],l=0;g>l;l+=1)k[l]=o.getValue(b,j+l*o.size,h);if(o.ascii){for(m="",l=0;lb.byteLength)return void a.log("Invalid Exif data: Invalid directory offset.");if(g=b.getUint16(d,e),h=d+2+12*g,h+4>b.byteLength)return void a.log("Invalid Exif data: Invalid directory size.");for(i=0;g>i;i+=1)this.parseExifTag(b,c,d+2+12*i,e,f);return b.getUint32(h,e)},c.parseExifData=function(b,d,e,f){var g,h,i=d+10;if(1165519206===b.getUint32(d+4)){if(i+8>b.byteLength)return void a.log("Invalid Exif data: Invalid segment size.");if(0!==b.getUint16(d+8))return void a.log("Invalid Exif data: Missing byte alignment offset.");switch(b.getUint16(i)){case 18761:g=!0;break;case 19789:g=!1;break;default:return void a.log("Invalid Exif data: Invalid byte alignment marker.")}if(42!==b.getUint16(i+2,g))return void a.log("Invalid Exif data: Missing TIFF marker.");h=b.getUint32(i+4,g),f.exif=new c.ExifMap,h=c.parseExifTags(b,i,i+h,g,f)}},b.parsers[65505].push(c.parseExifData),c}),b("runtime/html5/image",["base","runtime/html5/runtime","runtime/html5/util"],function(a,b,c){var d="data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs%3D";return b.register("Image",{modified:!1,init:function(){var a=this,b=new Image;b.onload=function(){a._info={type:a.type,width:this.width,height:this.height},a._metas||"image/jpeg"!==a.type?a.owner.trigger("load"):c.parseMeta(a._blob,function(b,c){a._metas=c,a.owner.trigger("load")})},b.onerror=function(){a.owner.trigger("error")},a._img=b},loadFromBlob:function(a){var b=this,d=b._img;b._blob=a,b.type=a.type,d.src=c.createObjectURL(a.getSource()),b.owner.once("load",function(){c.revokeObjectURL(d.src)})},resize:function(a,b){var c=this._canvas||(this._canvas=document.createElement("canvas"));this._resize(this._img,c,a,b),this._blob=null,this.modified=!0,this.owner.trigger("complete","resize")},crop:function(a,b,c,d,e){var f=this._canvas||(this._canvas=document.createElement("canvas")),g=this.options,h=this._img,i=h.naturalWidth,j=h.naturalHeight,k=this.getOrientation();e=e||1,f.width=c,f.height=d,g.preserveHeaders||this._rotate2Orientaion(f,k),this._renderImageToCanvas(f,h,-a,-b,i*e,j*e),this._blob=null,this.modified=!0,this.owner.trigger("complete","crop")},getAsBlob:function(a){var b,d=this._blob,e=this.options;if(a=a||this.type,this.modified||this.type!==a){if(b=this._canvas,"image/jpeg"===a){if(d=c.canvasToDataUrl(b,a,e.quality),e.preserveHeaders&&this._metas&&this._metas.imageHead)return d=c.dataURL2ArrayBuffer(d),d=c.updateImageHead(d,this._metas.imageHead),d=c.arrayBufferToBlob(d,a)}else d=c.canvasToDataUrl(b,a);d=c.dataURL2Blob(d)}return d},getAsDataUrl:function(a){var b=this.options;return a=a||this.type,"image/jpeg"===a?c.canvasToDataUrl(this._canvas,a,b.quality):this._canvas.toDataURL(a)},getOrientation:function(){return this._metas&&this._metas.exif&&this._metas.exif.get("Orientation")||1},info:function(a){return a?(this._info=a,this):this._info},meta:function(a){return a?(this._meta=a,this):this._meta},destroy:function(){var a=this._canvas;this._img.onload=null,a&&(a.getContext("2d").clearRect(0,0,a.width,a.height),a.width=a.height=0,this._canvas=null),this._img.src=d,this._img=this._blob=null},_resize:function(a,b,c,d){var e,f,g,h,i,j=this.options,k=a.width,l=a.height,m=this.getOrientation();~[5,6,7,8].indexOf(m)&&(c^=d,d^=c,c^=d),e=Math[j.crop?"max":"min"](c/k,d/l),j.allowMagnify||(e=Math.min(1,e)),f=k*e,g=l*e,j.crop?(b.width=c,b.height=d):(b.width=f,b.height=g),h=(b.width-f)/2,i=(b.height-g)/2,j.preserveHeaders||this._rotate2Orientaion(b,m),this._renderImageToCanvas(b,a,h,i,f,g)},_rotate2Orientaion:function(a,b){var c=a.width,d=a.height,e=a.getContext("2d");switch(b){case 5:case 6:case 7:case 8:a.width=d,a.height=c}switch(b){case 2:e.translate(c,0),e.scale(-1,1);break;case 3:e.translate(c,d),e.rotate(Math.PI);break;case 4:e.translate(0,d),e.scale(1,-1);break;case 5:e.rotate(.5*Math.PI),e.scale(1,-1);break;case 6:e.rotate(.5*Math.PI),e.translate(0,-d);break;case 7:e.rotate(.5*Math.PI),e.translate(c,-d),e.scale(-1,1);break;case 8:e.rotate(-.5*Math.PI),e.translate(-c,0)}},_renderImageToCanvas:function(){function b(a,b,c){var d,e,f,g=document.createElement("canvas"),h=g.getContext("2d"),i=0,j=c,k=c;for(g.width=1,g.height=c,h.drawImage(a,0,0),d=h.getImageData(0,0,1,c).data;k>i;)e=d[4*(k-1)+3],0===e?j=k:i=k,k=j+i>>1;return f=k/c,0===f?1:f}function c(a){var b,c,d=a.naturalWidth,e=a.naturalHeight;return d*e>1048576?(b=document.createElement("canvas"),b.width=b.height=1,c=b.getContext("2d"),c.drawImage(a,-d+1,0),0===c.getImageData(0,0,1,1).data[3]):!1}return a.os.ios?a.os.ios>=7?function(a,c,d,e,f,g){var h=c.naturalWidth,i=c.naturalHeight,j=b(c,h,i);return a.getContext("2d").drawImage(c,0,0,h*j,i*j,d,e,f,g)}:function(a,d,e,f,g,h){var i,j,k,l,m,n,o,p=d.naturalWidth,q=d.naturalHeight,r=a.getContext("2d"),s=c(d),t="image/jpeg"===this.type,u=1024,v=0,w=0;for(s&&(p/=2,q/=2),r.save(),i=document.createElement("canvas"),i.width=i.height=u,j=i.getContext("2d"),k=t?b(d,p,q):1,l=Math.ceil(u*g/p),m=Math.ceil(u*h/q/k);q>v;){for(n=0,o=0;p>n;)j.clearRect(0,0,u,u),j.drawImage(d,-n,-v),r.drawImage(i,0,0,u,u,e+o,f+w,l,m),n+=u,o+=l;v+=u,w+=m}r.restore(),i=j=null}:function(b){var c=a.slice(arguments,1),d=b.getContext("2d");d.drawImage.apply(d,c)}}()})}),b("runtime/html5/transport",["base","runtime/html5/runtime"],function(a,b){var c=a.noop,d=a.$;return b.register("Transport",{init:function(){this._status=0,this._response=null},send:function(){var b,c,e,f=this.owner,g=this.options,h=this._initAjax(),i=f._blob,j=g.server;g.sendAsBinary?(j+=(/\?/.test(j)?"&":"?")+d.param(f._formData),c=i.getSource()):(b=new FormData,d.each(f._formData,function(a,c){b.append(a,c)}),b.append(g.fileVal,i.getSource(),g.filename||f._formData.name||"")),g.withCredentials&&"withCredentials"in h?(h.open(g.method,j,!0),h.withCredentials=!0):h.open(g.method,j),this._setRequestHeader(h,g.headers),c?(h.overrideMimeType&&h.overrideMimeType("application/octet-stream"),a.os.android?(e=new FileReader,e.onload=function(){h.send(this.result),e=e.onload=null},e.readAsArrayBuffer(c)):h.send(c)):h.send(b)},getResponse:function(){return this._response},getResponseAsJson:function(){return this._parseJson(this._response)},getStatus:function(){return this._status},abort:function(){var a=this._xhr;a&&(a.upload.onprogress=c,a.onreadystatechange=c,a.abort(),this._xhr=a=null)},destroy:function(){this.abort()},_initAjax:function(){var a=this,b=new XMLHttpRequest,d=this.options;return!d.withCredentials||"withCredentials"in b||"undefined"==typeof XDomainRequest||(b=new XDomainRequest),b.upload.onprogress=function(b){var c=0;return b.lengthComputable&&(c=b.loaded/b.total),a.trigger("progress",c)},b.onreadystatechange=function(){return 4===b.readyState?(b.upload.onprogress=c,b.onreadystatechange=c,a._xhr=null,a._status=b.status,b.status>=200&&b.status<300?(a._response=b.responseText,a.trigger("load")):b.status>=500&&b.status<600?(a._response=b.responseText,a.trigger("error","server")):a.trigger("error",a._status?"http":"abort")):void 0},a._xhr=b,b},_setRequestHeader:function(a,b){d.each(b,function(b,c){a.setRequestHeader(b,c)})},_parseJson:function(a){var b;try{b=JSON.parse(a)}catch(c){b={}}return b}})}),b("preset/html5only",["base","widgets/filednd","widgets/filepaste","widgets/filepicker","widgets/image","widgets/queue","widgets/runtime","widgets/upload","widgets/validator","runtime/html5/blob","runtime/html5/dnd","runtime/html5/filepaste","runtime/html5/filepicker","runtime/html5/imagemeta/exif","runtime/html5/image","runtime/html5/transport"],function(a){return a}),b("webuploader",["preset/html5only"],function(a){return a}),c("webuploader")}); \ No newline at end of file diff --git a/static/plugins/webuploader/webuploader.js b/static/plugins/webuploader/webuploader.js new file mode 100644 index 00000000..e1a483b4 --- /dev/null +++ b/static/plugins/webuploader/webuploader.js @@ -0,0 +1,8106 @@ +/*! WebUploader 0.1.5 */ + + +/** + * @fileOverview 让内部各个部件的代码可以用[amd](https://github.com/amdjs/amdjs-api/wiki/AMD)模块定义方式组织起来。 + * + * AMD API 内部的简单不完全实现,请忽略。只有当WebUploader被合并成一个文件的时候才会引入。 + */ +(function( root, factory ) { + var modules = {}, + + // 内部require, 简单不完全实现。 + // https://github.com/amdjs/amdjs-api/wiki/require + _require = function( deps, callback ) { + var args, len, i; + + // 如果deps不是数组,则直接返回指定module + if ( typeof deps === 'string' ) { + return getModule( deps ); + } else { + args = []; + for( len = deps.length, i = 0; i < len; i++ ) { + args.push( getModule( deps[ i ] ) ); + } + + return callback.apply( null, args ); + } + }, + + // 内部define,暂时不支持不指定id. + _define = function( id, deps, factory ) { + if ( arguments.length === 2 ) { + factory = deps; + deps = null; + } + + _require( deps || [], function() { + setModule( id, factory, arguments ); + }); + }, + + // 设置module, 兼容CommonJs写法。 + setModule = function( id, factory, args ) { + var module = { + exports: factory + }, + returned; + + if ( typeof factory === 'function' ) { + args.length || (args = [ _require, module.exports, module ]); + returned = factory.apply( null, args ); + returned !== undefined && (module.exports = returned); + } + + modules[ id ] = module.exports; + }, + + // 根据id获取module + getModule = function( id ) { + var module = modules[ id ] || root[ id ]; + + if ( !module ) { + throw new Error( '`' + id + '` is undefined' ); + } + + return module; + }, + + // 将所有modules,将路径ids装换成对象。 + exportsTo = function( obj ) { + var key, host, parts, part, last, ucFirst; + + // make the first character upper case. + ucFirst = function( str ) { + return str && (str.charAt( 0 ).toUpperCase() + str.substr( 1 )); + }; + + for ( key in modules ) { + host = obj; + + if ( !modules.hasOwnProperty( key ) ) { + continue; + } + + parts = key.split('/'); + last = ucFirst( parts.pop() ); + + while( (part = ucFirst( parts.shift() )) ) { + host[ part ] = host[ part ] || {}; + host = host[ part ]; + } + + host[ last ] = modules[ key ]; + } + + return obj; + }, + + makeExport = function( dollar ) { + root.__dollar = dollar; + + // exports every module. + return exportsTo( factory( root, _define, _require ) ); + }, + + origin; + + if ( typeof module === 'object' && typeof module.exports === 'object' ) { + + // For CommonJS and CommonJS-like environments where a proper window is present, + module.exports = makeExport(); + } else if ( typeof define === 'function' && define.amd ) { + + // Allow using this built library as an AMD module + // in another project. That other project will only + // see this AMD call, not the internal modules in + // the closure below. + define([ 'jquery' ], makeExport ); + } else { + + // Browser globals case. Just assign the + // result to a property on the global. + origin = root.WebUploader; + root.WebUploader = makeExport(); + root.WebUploader.noConflict = function() { + root.WebUploader = origin; + }; + } +})( window, function( window, define, require ) { + + + /** + * @fileOverview jQuery or Zepto + */ + define('dollar-third',[],function() { + var $ = window.__dollar || window.jQuery || window.Zepto; + + if ( !$ ) { + throw new Error('jQuery or Zepto not found!'); + } + + return $; + }); + /** + * @fileOverview Dom 操作相关 + */ + define('dollar',[ + 'dollar-third' + ], function( _ ) { + return _; + }); + /** + * @fileOverview 使用jQuery的Promise + */ + define('promise-third',[ + 'dollar' + ], function( $ ) { + return { + Deferred: $.Deferred, + when: $.when, + + isPromise: function( anything ) { + return anything && typeof anything.then === 'function'; + } + }; + }); + /** + * @fileOverview Promise/A+ + */ + define('promise',[ + 'promise-third' + ], function( _ ) { + return _; + }); + /** + * @fileOverview 基础类方法。 + */ + + /** + * Web Uploader内部类的详细说明,以下提及的功能类,都可以在`WebUploader`这个变量中访问到。 + * + * As you know, Web Uploader的每个文件都是用过[AMD](https://github.com/amdjs/amdjs-api/wiki/AMD)规范中的`define`组织起来的, 每个Module都会有个module id. + * 默认module id为该文件的路径,而此路径将会转化成名字空间存放在WebUploader中。如: + * + * * module `base`:WebUploader.Base + * * module `file`: WebUploader.File + * * module `lib/dnd`: WebUploader.Lib.Dnd + * * module `runtime/html5/dnd`: WebUploader.Runtime.Html5.Dnd + * + * + * 以下文档中对类的使用可能省略掉了`WebUploader`前缀。 + * @module WebUploader + * @title WebUploader API文档 + */ + define('base',[ + 'dollar', + 'promise' + ], function( $, promise ) { + + var noop = function() {}, + call = Function.call; + + // http://jsperf.com/uncurrythis + // 反科里化 + function uncurryThis( fn ) { + return function() { + return call.apply( fn, arguments ); + }; + } + + function bindFn( fn, context ) { + return function() { + return fn.apply( context, arguments ); + }; + } + + function createObject( proto ) { + var f; + + if ( Object.create ) { + return Object.create( proto ); + } else { + f = function() {}; + f.prototype = proto; + return new f(); + } + } + + + /** + * 基础类,提供一些简单常用的方法。 + * @class Base + */ + return { + + /** + * @property {String} version 当前版本号。 + */ + version: '0.1.5', + + /** + * @property {jQuery|Zepto} $ 引用依赖的jQuery或者Zepto对象。 + */ + $: $, + + Deferred: promise.Deferred, + + isPromise: promise.isPromise, + + when: promise.when, + + /** + * @description 简单的浏览器检查结果。 + * + * * `webkit` webkit版本号,如果浏览器为非webkit内核,此属性为`undefined`。 + * * `chrome` chrome浏览器版本号,如果浏览器为chrome,此属性为`undefined`。 + * * `ie` ie浏览器版本号,如果浏览器为非ie,此属性为`undefined`。**暂不支持ie10+** + * * `firefox` firefox浏览器版本号,如果浏览器为非firefox,此属性为`undefined`。 + * * `safari` safari浏览器版本号,如果浏览器为非safari,此属性为`undefined`。 + * * `opera` opera浏览器版本号,如果浏览器为非opera,此属性为`undefined`。 + * + * @property {Object} [browser] + */ + browser: (function( ua ) { + var ret = {}, + webkit = ua.match( /WebKit\/([\d.]+)/ ), + chrome = ua.match( /Chrome\/([\d.]+)/ ) || + ua.match( /CriOS\/([\d.]+)/ ), + + ie = ua.match( /MSIE\s([\d\.]+)/ ) || + ua.match( /(?:trident)(?:.*rv:([\w.]+))?/i ), + firefox = ua.match( /Firefox\/([\d.]+)/ ), + safari = ua.match( /Safari\/([\d.]+)/ ), + opera = ua.match( /OPR\/([\d.]+)/ ); + + webkit && (ret.webkit = parseFloat( webkit[ 1 ] )); + chrome && (ret.chrome = parseFloat( chrome[ 1 ] )); + ie && (ret.ie = parseFloat( ie[ 1 ] )); + firefox && (ret.firefox = parseFloat( firefox[ 1 ] )); + safari && (ret.safari = parseFloat( safari[ 1 ] )); + opera && (ret.opera = parseFloat( opera[ 1 ] )); + + return ret; + })( navigator.userAgent ), + + /** + * @description 操作系统检查结果。 + * + * * `android` 如果在android浏览器环境下,此值为对应的android版本号,否则为`undefined`。 + * * `ios` 如果在ios浏览器环境下,此值为对应的ios版本号,否则为`undefined`。 + * @property {Object} [os] + */ + os: (function( ua ) { + var ret = {}, + + // osx = !!ua.match( /\(Macintosh\; Intel / ), + android = ua.match( /(?:Android);?[\s\/]+([\d.]+)?/ ), + ios = ua.match( /(?:iPad|iPod|iPhone).*OS\s([\d_]+)/ ); + + // osx && (ret.osx = true); + android && (ret.android = parseFloat( android[ 1 ] )); + ios && (ret.ios = parseFloat( ios[ 1 ].replace( /_/g, '.' ) )); + + return ret; + })( navigator.userAgent ), + + /** + * 实现类与类之间的继承。 + * @method inherits + * @grammar Base.inherits( super ) => child + * @grammar Base.inherits( super, protos ) => child + * @grammar Base.inherits( super, protos, statics ) => child + * @param {Class} super 父类 + * @param {Object | Function} [protos] 子类或者对象。如果对象中包含constructor,子类将是用此属性值。 + * @param {Function} [protos.constructor] 子类构造器,不指定的话将创建个临时的直接执行父类构造器的方法。 + * @param {Object} [statics] 静态属性或方法。 + * @return {Class} 返回子类。 + * @example + * function Person() { + * console.log( 'Super' ); + * } + * Person.prototype.hello = function() { + * console.log( 'hello' ); + * }; + * + * var Manager = Base.inherits( Person, { + * world: function() { + * console.log( 'World' ); + * } + * }); + * + * // 因为没有指定构造器,父类的构造器将会执行。 + * var instance = new Manager(); // => Super + * + * // 继承子父类的方法 + * instance.hello(); // => hello + * instance.world(); // => World + * + * // 子类的__super__属性指向父类 + * console.log( Manager.__super__ === Person ); // => true + */ + inherits: function( Super, protos, staticProtos ) { + var child; + + if ( typeof protos === 'function' ) { + child = protos; + protos = null; + } else if ( protos && protos.hasOwnProperty('constructor') ) { + child = protos.constructor; + } else { + child = function() { + return Super.apply( this, arguments ); + }; + } + + // 复制静态方法 + $.extend( true, child, Super, staticProtos || {} ); + + /* jshint camelcase: false */ + + // 让子类的__super__属性指向父类。 + child.__super__ = Super.prototype; + + // 构建原型,添加原型方法或属性。 + // 暂时用Object.create实现。 + child.prototype = createObject( Super.prototype ); + protos && $.extend( true, child.prototype, protos ); + + return child; + }, + + /** + * 一个不做任何事情的方法。可以用来赋值给默认的callback. + * @method noop + */ + noop: noop, + + /** + * 返回一个新的方法,此方法将已指定的`context`来执行。 + * @grammar Base.bindFn( fn, context ) => Function + * @method bindFn + * @example + * var doSomething = function() { + * console.log( this.name ); + * }, + * obj = { + * name: 'Object Name' + * }, + * aliasFn = Base.bind( doSomething, obj ); + * + * aliasFn(); // => Object Name + * + */ + bindFn: bindFn, + + /** + * 引用Console.log如果存在的话,否则引用一个[空函数noop](#WebUploader:Base.noop)。 + * @grammar Base.log( args... ) => undefined + * @method log + */ + log: (function() { + if ( window.console ) { + return bindFn( console.log, console ); + } + return noop; + })(), + + nextTick: (function() { + + return function( cb ) { + setTimeout( cb, 1 ); + }; + + // @bug 当浏览器不在当前窗口时就停了。 + // var next = window.requestAnimationFrame || + // window.webkitRequestAnimationFrame || + // window.mozRequestAnimationFrame || + // function( cb ) { + // window.setTimeout( cb, 1000 / 60 ); + // }; + + // // fix: Uncaught TypeError: Illegal invocation + // return bindFn( next, window ); + })(), + + /** + * 被[uncurrythis](http://www.2ality.com/2011/11/uncurrying-this.html)的数组slice方法。 + * 将用来将非数组对象转化成数组对象。 + * @grammar Base.slice( target, start[, end] ) => Array + * @method slice + * @example + * function doSomthing() { + * var args = Base.slice( arguments, 1 ); + * console.log( args ); + * } + * + * doSomthing( 'ignored', 'arg2', 'arg3' ); // => Array ["arg2", "arg3"] + */ + slice: uncurryThis( [].slice ), + + /** + * 生成唯一的ID + * @method guid + * @grammar Base.guid() => String + * @grammar Base.guid( prefx ) => String + */ + guid: (function() { + var counter = 0; + + return function( prefix ) { + var guid = (+new Date()).toString( 32 ), + i = 0; + + for ( ; i < 5; i++ ) { + guid += Math.floor( Math.random() * 65535 ).toString( 32 ); + } + + return (prefix || 'wu_') + guid + (counter++).toString( 32 ); + }; + })(), + + /** + * 格式化文件大小, 输出成带单位的字符串 + * @method formatSize + * @grammar Base.formatSize( size ) => String + * @grammar Base.formatSize( size, pointLength ) => String + * @grammar Base.formatSize( size, pointLength, units ) => String + * @param {Number} size 文件大小 + * @param {Number} [pointLength=2] 精确到的小数点数。 + * @param {Array} [units=[ 'B', 'K', 'M', 'G', 'TB' ]] 单位数组。从字节,到千字节,一直往上指定。如果单位数组里面只指定了到了K(千字节),同时文件大小大于M, 此方法的输出将还是显示成多少K. + * @example + * console.log( Base.formatSize( 100 ) ); // => 100B + * console.log( Base.formatSize( 1024 ) ); // => 1.00K + * console.log( Base.formatSize( 1024, 0 ) ); // => 1K + * console.log( Base.formatSize( 1024 * 1024 ) ); // => 1.00M + * console.log( Base.formatSize( 1024 * 1024 * 1024 ) ); // => 1.00G + * console.log( Base.formatSize( 1024 * 1024 * 1024, 0, ['B', 'KB', 'MB'] ) ); // => 1024MB + */ + formatSize: function( size, pointLength, units ) { + var unit; + + units = units || [ 'B', 'K', 'M', 'G', 'TB' ]; + + while ( (unit = units.shift()) && size > 1024 ) { + size = size / 1024; + } + + return (unit === 'B' ? size : size.toFixed( pointLength || 2 )) + + unit; + } + }; + }); + /** + * 事件处理类,可以独立使用,也可以扩展给对象使用。 + * @fileOverview Mediator + */ + define('mediator',[ + 'base' + ], function( Base ) { + var $ = Base.$, + slice = [].slice, + separator = /\s+/, + protos; + + // 根据条件过滤出事件handlers. + function findHandlers( arr, name, callback, context ) { + return $.grep( arr, function( handler ) { + return handler && + (!name || handler.e === name) && + (!callback || handler.cb === callback || + handler.cb._cb === callback) && + (!context || handler.ctx === context); + }); + } + + function eachEvent( events, callback, iterator ) { + // 不支持对象,只支持多个event用空格隔开 + $.each( (events || '').split( separator ), function( _, key ) { + iterator( key, callback ); + }); + } + + function triggerHanders( events, args ) { + var stoped = false, + i = -1, + len = events.length, + handler; + + while ( ++i < len ) { + handler = events[ i ]; + + if ( handler.cb.apply( handler.ctx2, args ) === false ) { + stoped = true; + break; + } + } + + return !stoped; + } + + protos = { + + /** + * 绑定事件。 + * + * `callback`方法在执行时,arguments将会来源于trigger的时候携带的参数。如 + * ```javascript + * var obj = {}; + * + * // 使得obj有事件行为 + * Mediator.installTo( obj ); + * + * obj.on( 'testa', function( arg1, arg2 ) { + * console.log( arg1, arg2 ); // => 'arg1', 'arg2' + * }); + * + * obj.trigger( 'testa', 'arg1', 'arg2' ); + * ``` + * + * 如果`callback`中,某一个方法`return false`了,则后续的其他`callback`都不会被执行到。 + * 切会影响到`trigger`方法的返回值,为`false`。 + * + * `on`还可以用来添加一个特殊事件`all`, 这样所有的事件触发都会响应到。同时此类`callback`中的arguments有一个不同处, + * 就是第一个参数为`type`,记录当前是什么事件在触发。此类`callback`的优先级比脚低,会再正常`callback`执行完后触发。 + * ```javascript + * obj.on( 'all', function( type, arg1, arg2 ) { + * console.log( type, arg1, arg2 ); // => 'testa', 'arg1', 'arg2' + * }); + * ``` + * + * @method on + * @grammar on( name, callback[, context] ) => self + * @param {String} name 事件名,支持多个事件用空格隔开 + * @param {Function} callback 事件处理器 + * @param {Object} [context] 事件处理器的上下文。 + * @return {self} 返回自身,方便链式 + * @chainable + * @class Mediator + */ + on: function( name, callback, context ) { + var me = this, + set; + + if ( !callback ) { + return this; + } + + set = this._events || (this._events = []); + + eachEvent( name, callback, function( name, callback ) { + var handler = { e: name }; + + handler.cb = callback; + handler.ctx = context; + handler.ctx2 = context || me; + handler.id = set.length; + + set.push( handler ); + }); + + return this; + }, + + /** + * 绑定事件,且当handler执行完后,自动解除绑定。 + * @method once + * @grammar once( name, callback[, context] ) => self + * @param {String} name 事件名 + * @param {Function} callback 事件处理器 + * @param {Object} [context] 事件处理器的上下文。 + * @return {self} 返回自身,方便链式 + * @chainable + */ + once: function( name, callback, context ) { + var me = this; + + if ( !callback ) { + return me; + } + + eachEvent( name, callback, function( name, callback ) { + var once = function() { + me.off( name, once ); + return callback.apply( context || me, arguments ); + }; + + once._cb = callback; + me.on( name, once, context ); + }); + + return me; + }, + + /** + * 解除事件绑定 + * @method off + * @grammar off( [name[, callback[, context] ] ] ) => self + * @param {String} [name] 事件名 + * @param {Function} [callback] 事件处理器 + * @param {Object} [context] 事件处理器的上下文。 + * @return {self} 返回自身,方便链式 + * @chainable + */ + off: function( name, cb, ctx ) { + var events = this._events; + + if ( !events ) { + return this; + } + + if ( !name && !cb && !ctx ) { + this._events = []; + return this; + } + + eachEvent( name, cb, function( name, cb ) { + $.each( findHandlers( events, name, cb, ctx ), function() { + delete events[ this.id ]; + }); + }); + + return this; + }, + + /** + * 触发事件 + * @method trigger + * @grammar trigger( name[, args...] ) => self + * @param {String} type 事件名 + * @param {*} [...] 任意参数 + * @return {Boolean} 如果handler中return false了,则返回false, 否则返回true + */ + trigger: function( type ) { + var args, events, allEvents; + + if ( !this._events || !type ) { + return this; + } + + args = slice.call( arguments, 1 ); + events = findHandlers( this._events, type ); + allEvents = findHandlers( this._events, 'all' ); + + return triggerHanders( events, args ) && + triggerHanders( allEvents, arguments ); + } + }; + + /** + * 中介者,它本身是个单例,但可以通过[installTo](#WebUploader:Mediator:installTo)方法,使任何对象具备事件行为。 + * 主要目的是负责模块与模块之间的合作,降低耦合度。 + * + * @class Mediator + */ + return $.extend({ + + /** + * 可以通过这个接口,使任何对象具备事件功能。 + * @method installTo + * @param {Object} obj 需要具备事件行为的对象。 + * @return {Object} 返回obj. + */ + installTo: function( obj ) { + return $.extend( obj, protos ); + } + + }, protos ); + }); + /** + * @fileOverview Uploader上传类 + */ + define('uploader',[ + 'base', + 'mediator' + ], function( Base, Mediator ) { + + var $ = Base.$; + + /** + * 上传入口类。 + * @class Uploader + * @constructor + * @grammar new Uploader( opts ) => Uploader + * @example + * var uploader = WebUploader.Uploader({ + * swf: 'path_of_swf/Uploader.swf', + * + * // 开起分片上传。 + * chunked: true + * }); + */ + function Uploader( opts ) { + this.options = $.extend( true, {}, Uploader.options, opts ); + this._init( this.options ); + } + + // default Options + // widgets中有相应扩展 + Uploader.options = {}; + Mediator.installTo( Uploader.prototype ); + + // 批量添加纯命令式方法。 + $.each({ + upload: 'start-upload', + stop: 'stop-upload', + getFile: 'get-file', + getFiles: 'get-files', + addFile: 'add-file', + addFiles: 'add-file', + sort: 'sort-files', + removeFile: 'remove-file', + cancelFile: 'cancel-file', + skipFile: 'skip-file', + retry: 'retry', + isInProgress: 'is-in-progress', + makeThumb: 'make-thumb', + md5File: 'md5-file', + getDimension: 'get-dimension', + addButton: 'add-btn', + predictRuntimeType: 'predict-runtime-type', + refresh: 'refresh', + disable: 'disable', + enable: 'enable', + reset: 'reset' + }, function( fn, command ) { + Uploader.prototype[ fn ] = function() { + return this.request( command, arguments ); + }; + }); + + $.extend( Uploader.prototype, { + state: 'pending', + + _init: function( opts ) { + var me = this; + + me.request( 'init', opts, function() { + me.state = 'ready'; + me.trigger('ready'); + }); + }, + + /** + * 获取或者设置Uploader配置项。 + * @method option + * @grammar option( key ) => * + * @grammar option( key, val ) => self + * @example + * + * // 初始状态图片上传前不会压缩 + * var uploader = new WebUploader.Uploader({ + * compress: null; + * }); + * + * // 修改后图片上传前,尝试将图片压缩到1600 * 1600 + * uploader.option( 'compress', { + * width: 1600, + * height: 1600 + * }); + */ + option: function( key, val ) { + var opts = this.options; + + // setter + if ( arguments.length > 1 ) { + + if ( $.isPlainObject( val ) && + $.isPlainObject( opts[ key ] ) ) { + $.extend( opts[ key ], val ); + } else { + opts[ key ] = val; + } + + } else { // getter + return key ? opts[ key ] : opts; + } + }, + + /** + * 获取文件统计信息。返回一个包含一下信息的对象。 + * * `successNum` 上传成功的文件数 + * * `progressNum` 上传中的文件数 + * * `cancelNum` 被删除的文件数 + * * `invalidNum` 无效的文件数 + * * `uploadFailNum` 上传失败的文件数 + * * `queueNum` 还在队列中的文件数 + * * `interruptNum` 被暂停的文件数 + * @method getStats + * @grammar getStats() => Object + */ + getStats: function() { + // return this._mgr.getStats.apply( this._mgr, arguments ); + var stats = this.request('get-stats'); + + return stats ? { + successNum: stats.numOfSuccess, + progressNum: stats.numOfProgress, + + // who care? + // queueFailNum: 0, + cancelNum: stats.numOfCancel, + invalidNum: stats.numOfInvalid, + uploadFailNum: stats.numOfUploadFailed, + queueNum: stats.numOfQueue, + interruptNum: stats.numofInterrupt + } : {}; + }, + + // 需要重写此方法来来支持opts.onEvent和instance.onEvent的处理器 + trigger: function( type/*, args...*/ ) { + var args = [].slice.call( arguments, 1 ), + opts = this.options, + name = 'on' + type.substring( 0, 1 ).toUpperCase() + + type.substring( 1 ); + + if ( + // 调用通过on方法注册的handler. + Mediator.trigger.apply( this, arguments ) === false || + + // 调用opts.onEvent + $.isFunction( opts[ name ] ) && + opts[ name ].apply( this, args ) === false || + + // 调用this.onEvent + $.isFunction( this[ name ] ) && + this[ name ].apply( this, args ) === false || + + // 广播所有uploader的事件。 + Mediator.trigger.apply( Mediator, + [ this, type ].concat( args ) ) === false ) { + + return false; + } + + return true; + }, + + /** + * 销毁 webuploader 实例 + * @method destroy + * @grammar destroy() => undefined + */ + destroy: function() { + this.request( 'destroy', arguments ); + this.off(); + }, + + // widgets/widget.js将补充此方法的详细文档。 + request: Base.noop + }); + + /** + * 创建Uploader实例,等同于new Uploader( opts ); + * @method create + * @class Base + * @static + * @grammar Base.create( opts ) => Uploader + */ + Base.create = Uploader.create = function( opts ) { + return new Uploader( opts ); + }; + + // 暴露Uploader,可以通过它来扩展业务逻辑。 + Base.Uploader = Uploader; + + return Uploader; + }); + /** + * @fileOverview Runtime管理器,负责Runtime的选择, 连接 + */ + define('runtime/runtime',[ + 'base', + 'mediator' + ], function( Base, Mediator ) { + + var $ = Base.$, + factories = {}, + + // 获取对象的第一个key + getFirstKey = function( obj ) { + for ( var key in obj ) { + if ( obj.hasOwnProperty( key ) ) { + return key; + } + } + return null; + }; + + // 接口类。 + function Runtime( options ) { + this.options = $.extend({ + container: document.body + }, options ); + this.uid = Base.guid('rt_'); + } + + $.extend( Runtime.prototype, { + + getContainer: function() { + var opts = this.options, + parent, container; + + if ( this._container ) { + return this._container; + } + + parent = $( opts.container || document.body ); + container = $( document.createElement('div') ); + + container.attr( 'id', 'rt_' + this.uid ); + container.css({ + position: 'absolute', + top: '0px', + left: '0px', + width: '1px', + height: '1px', + overflow: 'hidden' + }); + + parent.append( container ); + parent.addClass('webuploader-container'); + this._container = container; + this._parent = parent; + return container; + }, + + init: Base.noop, + exec: Base.noop, + + destroy: function() { + this._container && this._container.remove(); + this._parent && this._parent.removeClass('webuploader-container'); + this.off(); + } + }); + + Runtime.orders = 'html5,flash'; + + + /** + * 添加Runtime实现。 + * @param {String} type 类型 + * @param {Runtime} factory 具体Runtime实现。 + */ + Runtime.addRuntime = function( type, factory ) { + factories[ type ] = factory; + }; + + Runtime.hasRuntime = function( type ) { + return !!(type ? factories[ type ] : getFirstKey( factories )); + }; + + Runtime.create = function( opts, orders ) { + var type, runtime; + + orders = orders || Runtime.orders; + $.each( orders.split( /\s*,\s*/g ), function() { + if ( factories[ this ] ) { + type = this; + return false; + } + }); + + type = type || getFirstKey( factories ); + + if ( !type ) { + throw new Error('Runtime Error'); + } + + runtime = new factories[ type ]( opts ); + return runtime; + }; + + Mediator.installTo( Runtime.prototype ); + return Runtime; + }); + + /** + * @fileOverview Runtime管理器,负责Runtime的选择, 连接 + */ + define('runtime/client',[ + 'base', + 'mediator', + 'runtime/runtime' + ], function( Base, Mediator, Runtime ) { + + var cache; + + cache = (function() { + var obj = {}; + + return { + add: function( runtime ) { + obj[ runtime.uid ] = runtime; + }, + + get: function( ruid, standalone ) { + var i; + + if ( ruid ) { + return obj[ ruid ]; + } + + for ( i in obj ) { + // 有些类型不能重用,比如filepicker. + if ( standalone && obj[ i ].__standalone ) { + continue; + } + + return obj[ i ]; + } + + return null; + }, + + remove: function( runtime ) { + delete obj[ runtime.uid ]; + } + }; + })(); + + function RuntimeClient( component, standalone ) { + var deferred = Base.Deferred(), + runtime; + + this.uid = Base.guid('client_'); + + // 允许runtime没有初始化之前,注册一些方法在初始化后执行。 + this.runtimeReady = function( cb ) { + return deferred.done( cb ); + }; + + this.connectRuntime = function( opts, cb ) { + + // already connected. + if ( runtime ) { + throw new Error('already connected!'); + } + + deferred.done( cb ); + + if ( typeof opts === 'string' && cache.get( opts ) ) { + runtime = cache.get( opts ); + } + + // 像filePicker只能独立存在,不能公用。 + runtime = runtime || cache.get( null, standalone ); + + // 需要创建 + if ( !runtime ) { + runtime = Runtime.create( opts, opts.runtimeOrder ); + runtime.__promise = deferred.promise(); + runtime.once( 'ready', deferred.resolve ); + runtime.init(); + cache.add( runtime ); + runtime.__client = 1; + } else { + // 来自cache + Base.$.extend( runtime.options, opts ); + runtime.__promise.then( deferred.resolve ); + runtime.__client++; + } + + standalone && (runtime.__standalone = standalone); + return runtime; + }; + + this.getRuntime = function() { + return runtime; + }; + + this.disconnectRuntime = function() { + if ( !runtime ) { + return; + } + + runtime.__client--; + + if ( runtime.__client <= 0 ) { + cache.remove( runtime ); + delete runtime.__promise; + runtime.destroy(); + } + + runtime = null; + }; + + this.exec = function() { + if ( !runtime ) { + return; + } + + var args = Base.slice( arguments ); + component && args.unshift( component ); + + return runtime.exec.apply( this, args ); + }; + + this.getRuid = function() { + return runtime && runtime.uid; + }; + + this.destroy = (function( destroy ) { + return function() { + destroy && destroy.apply( this, arguments ); + this.trigger('destroy'); + this.off(); + this.exec('destroy'); + this.disconnectRuntime(); + }; + })( this.destroy ); + } + + Mediator.installTo( RuntimeClient.prototype ); + return RuntimeClient; + }); + /** + * @fileOverview 错误信息 + */ + define('lib/dnd',[ + 'base', + 'mediator', + 'runtime/client' + ], function( Base, Mediator, RuntimeClent ) { + + var $ = Base.$; + + function DragAndDrop( opts ) { + opts = this.options = $.extend({}, DragAndDrop.options, opts ); + + opts.container = $( opts.container ); + + if ( !opts.container.length ) { + return; + } + + RuntimeClent.call( this, 'DragAndDrop' ); + } + + DragAndDrop.options = { + accept: null, + disableGlobalDnd: false + }; + + Base.inherits( RuntimeClent, { + constructor: DragAndDrop, + + init: function() { + var me = this; + + me.connectRuntime( me.options, function() { + me.exec('init'); + me.trigger('ready'); + }); + } + }); + + Mediator.installTo( DragAndDrop.prototype ); + + return DragAndDrop; + }); + /** + * @fileOverview 组件基类。 + */ + define('widgets/widget',[ + 'base', + 'uploader' + ], function( Base, Uploader ) { + + var $ = Base.$, + _init = Uploader.prototype._init, + _destroy = Uploader.prototype.destroy, + IGNORE = {}, + widgetClass = []; + + function isArrayLike( obj ) { + if ( !obj ) { + return false; + } + + var length = obj.length, + type = $.type( obj ); + + if ( obj.nodeType === 1 && length ) { + return true; + } + + return type === 'array' || type !== 'function' && type !== 'string' && + (length === 0 || typeof length === 'number' && length > 0 && + (length - 1) in obj); + } + + function Widget( uploader ) { + this.owner = uploader; + this.options = uploader.options; + } + + $.extend( Widget.prototype, { + + init: Base.noop, + + // 类Backbone的事件监听声明,监听uploader实例上的事件 + // widget直接无法监听事件,事件只能通过uploader来传递 + invoke: function( apiName, args ) { + + /* + { + 'make-thumb': 'makeThumb' + } + */ + var map = this.responseMap; + + // 如果无API响应声明则忽略 + if ( !map || !(apiName in map) || !(map[ apiName ] in this) || + !$.isFunction( this[ map[ apiName ] ] ) ) { + + return IGNORE; + } + + return this[ map[ apiName ] ].apply( this, args ); + + }, + + /** + * 发送命令。当传入`callback`或者`handler`中返回`promise`时。返回一个当所有`handler`中的promise都完成后完成的新`promise`。 + * @method request + * @grammar request( command, args ) => * | Promise + * @grammar request( command, args, callback ) => Promise + * @for Uploader + */ + request: function() { + return this.owner.request.apply( this.owner, arguments ); + } + }); + + // 扩展Uploader. + $.extend( Uploader.prototype, { + + /** + * @property {String | Array} [disableWidgets=undefined] + * @namespace options + * @for Uploader + * @description 默认所有 Uploader.register 了的 widget 都会被加载,如果禁用某一部分,请通过此 option 指定黑名单。 + */ + + // 覆写_init用来初始化widgets + _init: function() { + var me = this, + widgets = me._widgets = [], + deactives = me.options.disableWidgets || ''; + + $.each( widgetClass, function( _, klass ) { + (!deactives || !~deactives.indexOf( klass._name )) && + widgets.push( new klass( me ) ); + }); + + return _init.apply( me, arguments ); + }, + + request: function( apiName, args, callback ) { + var i = 0, + widgets = this._widgets, + len = widgets && widgets.length, + rlts = [], + dfds = [], + widget, rlt, promise, key; + + args = isArrayLike( args ) ? args : [ args ]; + + for ( ; i < len; i++ ) { + widget = widgets[ i ]; + rlt = widget.invoke( apiName, args ); + + if ( rlt !== IGNORE ) { + + // Deferred对象 + if ( Base.isPromise( rlt ) ) { + dfds.push( rlt ); + } else { + rlts.push( rlt ); + } + } + } + + // 如果有callback,则用异步方式。 + if ( callback || dfds.length ) { + promise = Base.when.apply( Base, dfds ); + key = promise.pipe ? 'pipe' : 'then'; + + // 很重要不能删除。删除了会死循环。 + // 保证执行顺序。让callback总是在下一个 tick 中执行。 + return promise[ key ](function() { + var deferred = Base.Deferred(), + args = arguments; + + if ( args.length === 1 ) { + args = args[ 0 ]; + } + + setTimeout(function() { + deferred.resolve( args ); + }, 1 ); + + return deferred.promise(); + })[ callback ? key : 'done' ]( callback || Base.noop ); + } else { + return rlts[ 0 ]; + } + }, + + destroy: function() { + _destroy.apply( this, arguments ); + this._widgets = null; + } + }); + + /** + * 添加组件 + * @grammar Uploader.register(proto); + * @grammar Uploader.register(map, proto); + * @param {object} responseMap API 名称与函数实现的映射 + * @param {object} proto 组件原型,构造函数通过 constructor 属性定义 + * @method Uploader.register + * @for Uploader + * @example + * Uploader.register({ + * 'make-thumb': 'makeThumb' + * }, { + * init: function( options ) {}, + * makeThumb: function() {} + * }); + * + * Uploader.register({ + * 'make-thumb': function() { + * + * } + * }); + */ + Uploader.register = Widget.register = function( responseMap, widgetProto ) { + var map = { init: 'init', destroy: 'destroy', name: 'anonymous' }, + klass; + + if ( arguments.length === 1 ) { + widgetProto = responseMap; + + // 自动生成 map 表。 + $.each(widgetProto, function(key) { + if ( key[0] === '_' || key === 'name' ) { + key === 'name' && (map.name = widgetProto.name); + return; + } + + map[key.replace(/[A-Z]/g, '-$&').toLowerCase()] = key; + }); + + } else { + map = $.extend( map, responseMap ); + } + + widgetProto.responseMap = map; + klass = Base.inherits( Widget, widgetProto ); + klass._name = map.name; + widgetClass.push( klass ); + + return klass; + }; + + /** + * 删除插件,只有在注册时指定了名字的才能被删除。 + * @grammar Uploader.unRegister(name); + * @param {string} name 组件名字 + * @method Uploader.unRegister + * @for Uploader + * @example + * + * Uploader.register({ + * name: 'custom', + * + * 'make-thumb': function() { + * + * } + * }); + * + * Uploader.unRegister('custom'); + */ + Uploader.unRegister = Widget.unRegister = function( name ) { + if ( !name || name === 'anonymous' ) { + return; + } + + // 删除指定的插件。 + for ( var i = widgetClass.length; i--; ) { + if ( widgetClass[i]._name === name ) { + widgetClass.splice(i, 1) + } + } + }; + + return Widget; + }); + /** + * @fileOverview DragAndDrop Widget。 + */ + define('widgets/filednd',[ + 'base', + 'uploader', + 'lib/dnd', + 'widgets/widget' + ], function( Base, Uploader, Dnd ) { + var $ = Base.$; + + Uploader.options.dnd = ''; + + /** + * @property {Selector} [dnd=undefined] 指定Drag And Drop拖拽的容器,如果不指定,则不启动。 + * @namespace options + * @for Uploader + */ + + /** + * @property {Selector} [disableGlobalDnd=false] 是否禁掉整个页面的拖拽功能,如果不禁用,图片拖进来的时候会默认被浏览器打开。 + * @namespace options + * @for Uploader + */ + + /** + * @event dndAccept + * @param {DataTransferItemList} items DataTransferItem + * @description 阻止此事件可以拒绝某些类型的文件拖入进来。目前只有 chrome 提供这样的 API,且只能通过 mime-type 验证。 + * @for Uploader + */ + return Uploader.register({ + name: 'dnd', + + init: function( opts ) { + + if ( !opts.dnd || + this.request('predict-runtime-type') !== 'html5' ) { + return; + } + + var me = this, + deferred = Base.Deferred(), + options = $.extend({}, { + disableGlobalDnd: opts.disableGlobalDnd, + container: opts.dnd, + accept: opts.accept + }), + dnd; + + this.dnd = dnd = new Dnd( options ); + + dnd.once( 'ready', deferred.resolve ); + dnd.on( 'drop', function( files ) { + me.request( 'add-file', [ files ]); + }); + + // 检测文件是否全部允许添加。 + dnd.on( 'accept', function( items ) { + return me.owner.trigger( 'dndAccept', items ); + }); + + dnd.init(); + + return deferred.promise(); + }, + + destroy: function() { + this.dnd && this.dnd.destroy(); + } + }); + }); + + /** + * @fileOverview 错误信息 + */ + define('lib/filepaste',[ + 'base', + 'mediator', + 'runtime/client' + ], function( Base, Mediator, RuntimeClent ) { + + var $ = Base.$; + + function FilePaste( opts ) { + opts = this.options = $.extend({}, opts ); + opts.container = $( opts.container || document.body ); + RuntimeClent.call( this, 'FilePaste' ); + } + + Base.inherits( RuntimeClent, { + constructor: FilePaste, + + init: function() { + var me = this; + + me.connectRuntime( me.options, function() { + me.exec('init'); + me.trigger('ready'); + }); + } + }); + + Mediator.installTo( FilePaste.prototype ); + + return FilePaste; + }); + /** + * @fileOverview 组件基类。 + */ + define('widgets/filepaste',[ + 'base', + 'uploader', + 'lib/filepaste', + 'widgets/widget' + ], function( Base, Uploader, FilePaste ) { + var $ = Base.$; + + /** + * @property {Selector} [paste=undefined] 指定监听paste事件的容器,如果不指定,不启用此功能。此功能为通过粘贴来添加截屏的图片。建议设置为`document.body`. + * @namespace options + * @for Uploader + */ + return Uploader.register({ + name: 'paste', + + init: function( opts ) { + + if ( !opts.paste || + this.request('predict-runtime-type') !== 'html5' ) { + return; + } + + var me = this, + deferred = Base.Deferred(), + options = $.extend({}, { + container: opts.paste, + accept: opts.accept + }), + paste; + + this.paste = paste = new FilePaste( options ); + + paste.once( 'ready', deferred.resolve ); + paste.on( 'paste', function( files ) { + me.owner.request( 'add-file', [ files ]); + }); + paste.init(); + + return deferred.promise(); + }, + + destroy: function() { + this.paste && this.paste.destroy(); + } + }); + }); + /** + * @fileOverview Blob + */ + define('lib/blob',[ + 'base', + 'runtime/client' + ], function( Base, RuntimeClient ) { + + function Blob( ruid, source ) { + var me = this; + + me.source = source; + me.ruid = ruid; + this.size = source.size || 0; + + // 如果没有指定 mimetype, 但是知道文件后缀。 + if ( !source.type && this.ext && + ~'jpg,jpeg,png,gif,bmp'.indexOf( this.ext ) ) { + this.type = 'image/' + (this.ext === 'jpg' ? 'jpeg' : this.ext); + } else { + this.type = source.type || 'application/octet-stream'; + } + + RuntimeClient.call( me, 'Blob' ); + this.uid = source.uid || this.uid; + + if ( ruid ) { + me.connectRuntime( ruid ); + } + } + + Base.inherits( RuntimeClient, { + constructor: Blob, + + slice: function( start, end ) { + return this.exec( 'slice', start, end ); + }, + + getSource: function() { + return this.source; + } + }); + + return Blob; + }); + /** + * 为了统一化Flash的File和HTML5的File而存在。 + * 以至于要调用Flash里面的File,也可以像调用HTML5版本的File一下。 + * @fileOverview File + */ + define('lib/file',[ + 'base', + 'lib/blob' + ], function( Base, Blob ) { + + var uid = 1, + rExt = /\.([^.]+)$/; + + function File( ruid, file ) { + var ext; + + this.name = file.name || ('untitled' + uid++); + ext = rExt.exec( file.name ) ? RegExp.$1.toLowerCase() : ''; + + // todo 支持其他类型文件的转换。 + // 如果有 mimetype, 但是文件名里面没有找出后缀规律 + if ( !ext && file.type ) { + ext = /\/(jpg|jpeg|png|gif|bmp)$/i.exec( file.type ) ? + RegExp.$1.toLowerCase() : ''; + this.name += '.' + ext; + } + + this.ext = ext; + this.lastModifiedDate = file.lastModifiedDate || + (new Date()).toLocaleString(); + + Blob.apply( this, arguments ); + } + + return Base.inherits( Blob, File ); + }); + + /** + * @fileOverview 错误信息 + */ + define('lib/filepicker',[ + 'base', + 'runtime/client', + 'lib/file' + ], function( Base, RuntimeClent, File ) { + + var $ = Base.$; + + function FilePicker( opts ) { + opts = this.options = $.extend({}, FilePicker.options, opts ); + + opts.container = $( opts.id ); + + if ( !opts.container.length ) { + throw new Error('按钮指定错误'); + } + + opts.innerHTML = opts.innerHTML || opts.label || + opts.container.html() || ''; + + opts.button = $( opts.button || document.createElement('div') ); + opts.button.html( opts.innerHTML ); + opts.container.html( opts.button ); + + RuntimeClent.call( this, 'FilePicker', true ); + } + + FilePicker.options = { + button: null, + container: null, + label: null, + innerHTML: null, + multiple: true, + accept: null, + name: 'file' + }; + + Base.inherits( RuntimeClent, { + constructor: FilePicker, + + init: function() { + var me = this, + opts = me.options, + button = opts.button; + + button.addClass('webuploader-pick'); + + me.on( 'all', function( type ) { + var files; + + switch ( type ) { + case 'mouseenter': + button.addClass('webuploader-pick-hover'); + break; + + case 'mouseleave': + button.removeClass('webuploader-pick-hover'); + break; + + case 'change': + files = me.exec('getFiles'); + me.trigger( 'select', $.map( files, function( file ) { + file = new File( me.getRuid(), file ); + + // 记录来源。 + file._refer = opts.container; + return file; + }), opts.container ); + break; + } + }); + + me.connectRuntime( opts, function() { + me.refresh(); + me.exec( 'init', opts ); + me.trigger('ready'); + }); + + this._resizeHandler = Base.bindFn( this.refresh, this ); + $( window ).on( 'resize', this._resizeHandler ); + }, + + refresh: function() { + var shimContainer = this.getRuntime().getContainer(), + button = this.options.button, + width = button.outerWidth ? + button.outerWidth() : button.width(), + + height = button.outerHeight ? + button.outerHeight() : button.height(), + + pos = button.offset(); + + width && height && shimContainer.css({ + bottom: 'auto', + right: 'auto', + width: width + 'px', + height: height + 'px' + }).offset( pos ); + }, + + enable: function() { + var btn = this.options.button; + + btn.removeClass('webuploader-pick-disable'); + this.refresh(); + }, + + disable: function() { + var btn = this.options.button; + + this.getRuntime().getContainer().css({ + top: '-99999px' + }); + + btn.addClass('webuploader-pick-disable'); + }, + + destroy: function() { + var btn = this.options.button; + $( window ).off( 'resize', this._resizeHandler ); + btn.removeClass('webuploader-pick-disable webuploader-pick-hover ' + + 'webuploader-pick'); + } + }); + + return FilePicker; + }); + + /** + * @fileOverview 文件选择相关 + */ + define('widgets/filepicker',[ + 'base', + 'uploader', + 'lib/filepicker', + 'widgets/widget' + ], function( Base, Uploader, FilePicker ) { + var $ = Base.$; + + $.extend( Uploader.options, { + + /** + * @property {Selector | Object} [pick=undefined] + * @namespace options + * @for Uploader + * @description 指定选择文件的按钮容器,不指定则不创建按钮。 + * + * * `id` {Seletor|dom} 指定选择文件的按钮容器,不指定则不创建按钮。**注意** 这里虽然写的是 id, 但是不是只支持 id, 还支持 class, 或者 dom 节点。 + * * `label` {String} 请采用 `innerHTML` 代替 + * * `innerHTML` {String} 指定按钮文字。不指定时优先从指定的容器中看是否自带文字。 + * * `multiple` {Boolean} 是否开起同时选择多个文件能力。 + */ + pick: null, + + /** + * @property {Arroy} [accept=null] + * @namespace options + * @for Uploader + * @description 指定接受哪些类型的文件。 由于目前还有ext转mimeType表,所以这里需要分开指定。 + * + * * `title` {String} 文字描述 + * * `extensions` {String} 允许的文件后缀,不带点,多个用逗号分割。 + * * `mimeTypes` {String} 多个用逗号分割。 + * + * 如: + * + * ``` + * { + * title: 'Images', + * extensions: 'gif,jpg,jpeg,bmp,png', + * mimeTypes: 'image/*' + * } + * ``` + */ + accept: null/*{ + title: 'Images', + extensions: 'gif,jpg,jpeg,bmp,png', + mimeTypes: 'image/*' + }*/ + }); + + return Uploader.register({ + name: 'picker', + + init: function( opts ) { + this.pickers = []; + return opts.pick && this.addBtn( opts.pick ); + }, + + refresh: function() { + $.each( this.pickers, function() { + this.refresh(); + }); + }, + + /** + * @method addButton + * @for Uploader + * @grammar addButton( pick ) => Promise + * @description + * 添加文件选择按钮,如果一个按钮不够,需要调用此方法来添加。参数跟[options.pick](#WebUploader:Uploader:options)一致。 + * @example + * uploader.addButton({ + * id: '#btnContainer', + * innerHTML: '选择文件' + * }); + */ + addBtn: function( pick ) { + var me = this, + opts = me.options, + accept = opts.accept, + promises = []; + + if ( !pick ) { + return; + } + + $.isPlainObject( pick ) || (pick = { + id: pick + }); + + $( pick.id ).each(function() { + var options, picker, deferred; + + deferred = Base.Deferred(); + + options = $.extend({}, pick, { + accept: $.isPlainObject( accept ) ? [ accept ] : accept, + swf: opts.swf, + runtimeOrder: opts.runtimeOrder, + id: this + }); + + picker = new FilePicker( options ); + + picker.once( 'ready', deferred.resolve ); + picker.on( 'select', function( files ) { + me.owner.request( 'add-file', [ files ]); + }); + picker.init(); + + me.pickers.push( picker ); + + promises.push( deferred.promise() ); + }); + + return Base.when.apply( Base, promises ); + }, + + disable: function() { + $.each( this.pickers, function() { + this.disable(); + }); + }, + + enable: function() { + $.each( this.pickers, function() { + this.enable(); + }); + }, + + destroy: function() { + $.each( this.pickers, function() { + this.destroy(); + }); + this.pickers = null; + } + }); + }); + /** + * @fileOverview Image + */ + define('lib/image',[ + 'base', + 'runtime/client', + 'lib/blob' + ], function( Base, RuntimeClient, Blob ) { + var $ = Base.$; + + // 构造器。 + function Image( opts ) { + this.options = $.extend({}, Image.options, opts ); + RuntimeClient.call( this, 'Image' ); + + this.on( 'load', function() { + this._info = this.exec('info'); + this._meta = this.exec('meta'); + }); + } + + // 默认选项。 + Image.options = { + + // 默认的图片处理质量 + quality: 90, + + // 是否裁剪 + crop: false, + + // 是否保留头部信息 + preserveHeaders: false, + + // 是否允许放大。 + allowMagnify: false + }; + + // 继承RuntimeClient. + Base.inherits( RuntimeClient, { + constructor: Image, + + info: function( val ) { + + // setter + if ( val ) { + this._info = val; + return this; + } + + // getter + return this._info; + }, + + meta: function( val ) { + + // setter + if ( val ) { + this._meta = val; + return this; + } + + // getter + return this._meta; + }, + + loadFromBlob: function( blob ) { + var me = this, + ruid = blob.getRuid(); + + this.connectRuntime( ruid, function() { + me.exec( 'init', me.options ); + me.exec( 'loadFromBlob', blob ); + }); + }, + + resize: function() { + var args = Base.slice( arguments ); + return this.exec.apply( this, [ 'resize' ].concat( args ) ); + }, + + crop: function() { + var args = Base.slice( arguments ); + return this.exec.apply( this, [ 'crop' ].concat( args ) ); + }, + + getAsDataUrl: function( type ) { + return this.exec( 'getAsDataUrl', type ); + }, + + getAsBlob: function( type ) { + var blob = this.exec( 'getAsBlob', type ); + + return new Blob( this.getRuid(), blob ); + } + }); + + return Image; + }); + /** + * @fileOverview 图片操作, 负责预览图片和上传前压缩图片 + */ + define('widgets/image',[ + 'base', + 'uploader', + 'lib/image', + 'widgets/widget' + ], function( Base, Uploader, Image ) { + + var $ = Base.$, + throttle; + + // 根据要处理的文件大小来节流,一次不能处理太多,会卡。 + throttle = (function( max ) { + var occupied = 0, + waiting = [], + tick = function() { + var item; + + while ( waiting.length && occupied < max ) { + item = waiting.shift(); + occupied += item[ 0 ]; + item[ 1 ](); + } + }; + + return function( emiter, size, cb ) { + waiting.push([ size, cb ]); + emiter.once( 'destroy', function() { + occupied -= size; + setTimeout( tick, 1 ); + }); + setTimeout( tick, 1 ); + }; + })( 5 * 1024 * 1024 ); + + $.extend( Uploader.options, { + + /** + * @property {Object} [thumb] + * @namespace options + * @for Uploader + * @description 配置生成缩略图的选项。 + * + * 默认为: + * + * ```javascript + * { + * width: 110, + * height: 110, + * + * // 图片质量,只有type为`image/jpeg`的时候才有效。 + * quality: 70, + * + * // 是否允许放大,如果想要生成小图的时候不失真,此选项应该设置为false. + * allowMagnify: true, + * + * // 是否允许裁剪。 + * crop: true, + * + * // 为空的话则保留原有图片格式。 + * // 否则强制转换成指定的类型。 + * type: 'image/jpeg' + * } + * ``` + */ + thumb: { + width: 110, + height: 110, + quality: 70, + allowMagnify: true, + crop: true, + preserveHeaders: false, + + // 为空的话则保留原有图片格式。 + // 否则强制转换成指定的类型。 + // IE 8下面 base64 大小不能超过 32K 否则预览失败,而非 jpeg 编码的图片很可 + // 能会超过 32k, 所以这里设置成预览的时候都是 image/jpeg + type: 'image/jpeg' + }, + + /** + * @property {Object} [compress] + * @namespace options + * @for Uploader + * @description 配置压缩的图片的选项。如果此选项为`false`, 则图片在上传前不进行压缩。 + * + * 默认为: + * + * ```javascript + * { + * width: 1600, + * height: 1600, + * + * // 图片质量,只有type为`image/jpeg`的时候才有效。 + * quality: 90, + * + * // 是否允许放大,如果想要生成小图的时候不失真,此选项应该设置为false. + * allowMagnify: false, + * + * // 是否允许裁剪。 + * crop: false, + * + * // 是否保留头部meta信息。 + * preserveHeaders: true, + * + * // 如果发现压缩后文件大小比原来还大,则使用原来图片 + * // 此属性可能会影响图片自动纠正功能 + * noCompressIfLarger: false, + * + * // 单位字节,如果图片大小小于此值,不会采用压缩。 + * compressSize: 0 + * } + * ``` + */ + compress: { + width: 1600, + height: 1600, + quality: 90, + allowMagnify: false, + crop: false, + preserveHeaders: true + } + }); + + return Uploader.register({ + + name: 'image', + + + /** + * 生成缩略图,此过程为异步,所以需要传入`callback`。 + * 通常情况在图片加入队里后调用此方法来生成预览图以增强交互效果。 + * + * 当 width 或者 height 的值介于 0 - 1 时,被当成百分比使用。 + * + * `callback`中可以接收到两个参数。 + * * 第一个为error,如果生成缩略图有错误,此error将为真。 + * * 第二个为ret, 缩略图的Data URL值。 + * + * **注意** + * Date URL在IE6/7中不支持,所以不用调用此方法了,直接显示一张暂不支持预览图片好了。 + * 也可以借助服务端,将 base64 数据传给服务端,生成一个临时文件供预览。 + * + * @method makeThumb + * @grammar makeThumb( file, callback ) => undefined + * @grammar makeThumb( file, callback, width, height ) => undefined + * @for Uploader + * @example + * + * uploader.on( 'fileQueued', function( file ) { + * var $li = ...; + * + * uploader.makeThumb( file, function( error, ret ) { + * if ( error ) { + * $li.text('预览错误'); + * } else { + * $li.append(''); + * } + * }); + * + * }); + */ + makeThumb: function( file, cb, width, height ) { + var opts, image; + + file = this.request( 'get-file', file ); + + // 只预览图片格式。 + if ( !file.type.match( /^image/ ) ) { + cb( true ); + return; + } + + opts = $.extend({}, this.options.thumb ); + + // 如果传入的是object. + if ( $.isPlainObject( width ) ) { + opts = $.extend( opts, width ); + width = null; + } + + width = width || opts.width; + height = height || opts.height; + + image = new Image( opts ); + + image.once( 'load', function() { + file._info = file._info || image.info(); + file._meta = file._meta || image.meta(); + + // 如果 width 的值介于 0 - 1 + // 说明设置的是百分比。 + if ( width <= 1 && width > 0 ) { + width = file._info.width * width; + } + + // 同样的规则应用于 height + if ( height <= 1 && height > 0 ) { + height = file._info.height * height; + } + + image.resize( width, height ); + }); + + // 当 resize 完后 + image.once( 'complete', function() { + cb( false, image.getAsDataUrl( opts.type ) ); + image.destroy(); + }); + + image.once( 'error', function( reason ) { + cb( reason || true ); + image.destroy(); + }); + + throttle( image, file.source.size, function() { + file._info && image.info( file._info ); + file._meta && image.meta( file._meta ); + image.loadFromBlob( file.source ); + }); + }, + + beforeSendFile: function( file ) { + var opts = this.options.compress || this.options.resize, + compressSize = opts && opts.compressSize || 0, + noCompressIfLarger = opts && opts.noCompressIfLarger || false, + image, deferred; + + file = this.request( 'get-file', file ); + + // 只压缩 jpeg 图片格式。 + // gif 可能会丢失针 + // bmp png 基本上尺寸都不大,且压缩比比较小。 + if ( !opts || !~'image/jpeg,image/jpg'.indexOf( file.type ) || + file.size < compressSize || + file._compressed ) { + return; + } + + opts = $.extend({}, opts ); + deferred = Base.Deferred(); + + image = new Image( opts ); + + deferred.always(function() { + image.destroy(); + image = null; + }); + image.once( 'error', deferred.reject ); + image.once( 'load', function() { + var width = opts.width, + height = opts.height; + + file._info = file._info || image.info(); + file._meta = file._meta || image.meta(); + + // 如果 width 的值介于 0 - 1 + // 说明设置的是百分比。 + if ( width <= 1 && width > 0 ) { + width = file._info.width * width; + } + + // 同样的规则应用于 height + if ( height <= 1 && height > 0 ) { + height = file._info.height * height; + } + + image.resize( width, height ); + }); + + image.once( 'complete', function() { + var blob, size; + + // 移动端 UC / qq 浏览器的无图模式下 + // ctx.getImageData 处理大图的时候会报 Exception + // INDEX_SIZE_ERR: DOM Exception 1 + try { + blob = image.getAsBlob( opts.type ); + + size = file.size; + + // 如果压缩后,比原来还大则不用压缩后的。 + if ( !noCompressIfLarger || blob.size < size ) { + // file.source.destroy && file.source.destroy(); + file.source = blob; + file.size = blob.size; + + file.trigger( 'resize', blob.size, size ); + } + + // 标记,避免重复压缩。 + file._compressed = true; + deferred.resolve(); + } catch ( e ) { + // 出错了直接继续,让其上传原始图片 + deferred.resolve(); + } + }); + + file._info && image.info( file._info ); + file._meta && image.meta( file._meta ); + + image.loadFromBlob( file.source ); + return deferred.promise(); + } + }); + }); + /** + * @fileOverview 文件属性封装 + */ + define('file',[ + 'base', + 'mediator' + ], function( Base, Mediator ) { + + var $ = Base.$, + idPrefix = 'WU_FILE_', + idSuffix = 0, + rExt = /\.([^.]+)$/, + statusMap = {}; + + function gid() { + return idPrefix + idSuffix++; + } + + /** + * 文件类 + * @class File + * @constructor 构造函数 + * @grammar new File( source ) => File + * @param {Lib.File} source [lib.File](#Lib.File)实例, 此source对象是带有Runtime信息的。 + */ + function WUFile( source ) { + + /** + * 文件名,包括扩展名(后缀) + * @property name + * @type {string} + */ + this.name = source.name || 'Untitled'; + + /** + * 文件体积(字节) + * @property size + * @type {uint} + * @default 0 + */ + this.size = source.size || 0; + + /** + * 文件MIMETYPE类型,与文件类型的对应关系请参考[http://t.cn/z8ZnFny](http://t.cn/z8ZnFny) + * @property type + * @type {string} + * @default 'application/octet-stream' + */ + this.type = source.type || 'application/octet-stream'; + + /** + * 文件最后修改日期 + * @property lastModifiedDate + * @type {int} + * @default 当前时间戳 + */ + this.lastModifiedDate = source.lastModifiedDate || (new Date() * 1); + + /** + * 文件ID,每个对象具有唯一ID,与文件名无关 + * @property id + * @type {string} + */ + this.id = gid(); + + /** + * 文件扩展名,通过文件名获取,例如test.png的扩展名为png + * @property ext + * @type {string} + */ + this.ext = rExt.exec( this.name ) ? RegExp.$1 : ''; + + + /** + * 状态文字说明。在不同的status语境下有不同的用途。 + * @property statusText + * @type {string} + */ + this.statusText = ''; + + // 存储文件状态,防止通过属性直接修改 + statusMap[ this.id ] = WUFile.Status.INITED; + + this.source = source; + this.loaded = 0; + + this.on( 'error', function( msg ) { + this.setStatus( WUFile.Status.ERROR, msg ); + }); + } + + $.extend( WUFile.prototype, { + + /** + * 设置状态,状态变化时会触发`change`事件。 + * @method setStatus + * @grammar setStatus( status[, statusText] ); + * @param {File.Status|String} status [文件状态值](#WebUploader:File:File.Status) + * @param {String} [statusText=''] 状态说明,常在error时使用,用http, abort,server等来标记是由于什么原因导致文件错误。 + */ + setStatus: function( status, text ) { + + var prevStatus = statusMap[ this.id ]; + + typeof text !== 'undefined' && (this.statusText = text); + + if ( status !== prevStatus ) { + statusMap[ this.id ] = status; + /** + * 文件状态变化 + * @event statuschange + */ + this.trigger( 'statuschange', status, prevStatus ); + } + + }, + + /** + * 获取文件状态 + * @return {File.Status} + * @example + 文件状态具体包括以下几种类型: + { + // 初始化 + INITED: 0, + // 已入队列 + QUEUED: 1, + // 正在上传 + PROGRESS: 2, + // 上传出错 + ERROR: 3, + // 上传成功 + COMPLETE: 4, + // 上传取消 + CANCELLED: 5 + } + */ + getStatus: function() { + return statusMap[ this.id ]; + }, + + /** + * 获取文件原始信息。 + * @return {*} + */ + getSource: function() { + return this.source; + }, + + destroy: function() { + this.off(); + delete statusMap[ this.id ]; + } + }); + + Mediator.installTo( WUFile.prototype ); + + /** + * 文件状态值,具体包括以下几种类型: + * * `inited` 初始状态 + * * `queued` 已经进入队列, 等待上传 + * * `progress` 上传中 + * * `complete` 上传完成。 + * * `error` 上传出错,可重试 + * * `interrupt` 上传中断,可续传。 + * * `invalid` 文件不合格,不能重试上传。会自动从队列中移除。 + * * `cancelled` 文件被移除。 + * @property {Object} Status + * @namespace File + * @class File + * @static + */ + WUFile.Status = { + INITED: 'inited', // 初始状态 + QUEUED: 'queued', // 已经进入队列, 等待上传 + PROGRESS: 'progress', // 上传中 + ERROR: 'error', // 上传出错,可重试 + COMPLETE: 'complete', // 上传完成。 + CANCELLED: 'cancelled', // 上传取消。 + INTERRUPT: 'interrupt', // 上传中断,可续传。 + INVALID: 'invalid' // 文件不合格,不能重试上传。 + }; + + return WUFile; + }); + + /** + * @fileOverview 文件队列 + */ + define('queue',[ + 'base', + 'mediator', + 'file' + ], function( Base, Mediator, WUFile ) { + + var $ = Base.$, + STATUS = WUFile.Status; + + /** + * 文件队列, 用来存储各个状态中的文件。 + * @class Queue + * @extends Mediator + */ + function Queue() { + + /** + * 统计文件数。 + * * `numOfQueue` 队列中的文件数。 + * * `numOfSuccess` 上传成功的文件数 + * * `numOfCancel` 被取消的文件数 + * * `numOfProgress` 正在上传中的文件数 + * * `numOfUploadFailed` 上传错误的文件数。 + * * `numOfInvalid` 无效的文件数。 + * * `numofDeleted` 被移除的文件数。 + * @property {Object} stats + */ + this.stats = { + numOfQueue: 0, + numOfSuccess: 0, + numOfCancel: 0, + numOfProgress: 0, + numOfUploadFailed: 0, + numOfInvalid: 0, + numofDeleted: 0, + numofInterrupt: 0 + }; + + // 上传队列,仅包括等待上传的文件 + this._queue = []; + + // 存储所有文件 + this._map = {}; + } + + $.extend( Queue.prototype, { + + /** + * 将新文件加入对队列尾部 + * + * @method append + * @param {File} file 文件对象 + */ + append: function( file ) { + this._queue.push( file ); + this._fileAdded( file ); + return this; + }, + + /** + * 将新文件加入对队列头部 + * + * @method prepend + * @param {File} file 文件对象 + */ + prepend: function( file ) { + this._queue.unshift( file ); + this._fileAdded( file ); + return this; + }, + + /** + * 获取文件对象 + * + * @method getFile + * @param {String} fileId 文件ID + * @return {File} + */ + getFile: function( fileId ) { + if ( typeof fileId !== 'string' ) { + return fileId; + } + return this._map[ fileId ]; + }, + + /** + * 从队列中取出一个指定状态的文件。 + * @grammar fetch( status ) => File + * @method fetch + * @param {String} status [文件状态值](#WebUploader:File:File.Status) + * @return {File} [File](#WebUploader:File) + */ + fetch: function( status ) { + var len = this._queue.length, + i, file; + + status = status || STATUS.QUEUED; + + for ( i = 0; i < len; i++ ) { + file = this._queue[ i ]; + + if ( status === file.getStatus() ) { + return file; + } + } + + return null; + }, + + /** + * 对队列进行排序,能够控制文件上传顺序。 + * @grammar sort( fn ) => undefined + * @method sort + * @param {Function} fn 排序方法 + */ + sort: function( fn ) { + if ( typeof fn === 'function' ) { + this._queue.sort( fn ); + } + }, + + /** + * 获取指定类型的文件列表, 列表中每一个成员为[File](#WebUploader:File)对象。 + * @grammar getFiles( [status1[, status2 ...]] ) => Array + * @method getFiles + * @param {String} [status] [文件状态值](#WebUploader:File:File.Status) + */ + getFiles: function() { + var sts = [].slice.call( arguments, 0 ), + ret = [], + i = 0, + len = this._queue.length, + file; + + for ( ; i < len; i++ ) { + file = this._queue[ i ]; + + if ( sts.length && !~$.inArray( file.getStatus(), sts ) ) { + continue; + } + + ret.push( file ); + } + + return ret; + }, + + /** + * 在队列中删除文件。 + * @grammar removeFile( file ) => Array + * @method removeFile + * @param {File} 文件对象。 + */ + removeFile: function( file ) { + var me = this, + existing = this._map[ file.id ]; + + if ( existing ) { + delete this._map[ file.id ]; + file.destroy(); + this.stats.numofDeleted++; + } + }, + + _fileAdded: function( file ) { + var me = this, + existing = this._map[ file.id ]; + + if ( !existing ) { + this._map[ file.id ] = file; + + file.on( 'statuschange', function( cur, pre ) { + me._onFileStatusChange( cur, pre ); + }); + } + }, + + _onFileStatusChange: function( curStatus, preStatus ) { + var stats = this.stats; + + switch ( preStatus ) { + case STATUS.PROGRESS: + stats.numOfProgress--; + break; + + case STATUS.QUEUED: + stats.numOfQueue --; + break; + + case STATUS.ERROR: + stats.numOfUploadFailed--; + break; + + case STATUS.INVALID: + stats.numOfInvalid--; + break; + + case STATUS.INTERRUPT: + stats.numofInterrupt--; + break; + } + + switch ( curStatus ) { + case STATUS.QUEUED: + stats.numOfQueue++; + break; + + case STATUS.PROGRESS: + stats.numOfProgress++; + break; + + case STATUS.ERROR: + stats.numOfUploadFailed++; + break; + + case STATUS.COMPLETE: + stats.numOfSuccess++; + break; + + case STATUS.CANCELLED: + stats.numOfCancel++; + break; + + + case STATUS.INVALID: + stats.numOfInvalid++; + break; + + case STATUS.INTERRUPT: + stats.numofInterrupt++; + break; + } + } + + }); + + Mediator.installTo( Queue.prototype ); + + return Queue; + }); + /** + * @fileOverview 队列 + */ + define('widgets/queue',[ + 'base', + 'uploader', + 'queue', + 'file', + 'lib/file', + 'runtime/client', + 'widgets/widget' + ], function( Base, Uploader, Queue, WUFile, File, RuntimeClient ) { + + var $ = Base.$, + rExt = /\.\w+$/, + Status = WUFile.Status; + + return Uploader.register({ + name: 'queue', + + init: function( opts ) { + var me = this, + deferred, len, i, item, arr, accept, runtime; + + if ( $.isPlainObject( opts.accept ) ) { + opts.accept = [ opts.accept ]; + } + + // accept中的中生成匹配正则。 + if ( opts.accept ) { + arr = []; + + for ( i = 0, len = opts.accept.length; i < len; i++ ) { + item = opts.accept[ i ].extensions; + item && arr.push( item ); + } + + if ( arr.length ) { + accept = '\\.' + arr.join(',') + .replace( /,/g, '$|\\.' ) + .replace( /\*/g, '.*' ) + '$'; + } + + me.accept = new RegExp( accept, 'i' ); + } + + me.queue = new Queue(); + me.stats = me.queue.stats; + + // 如果当前不是html5运行时,那就算了。 + // 不执行后续操作 + if ( this.request('predict-runtime-type') !== 'html5' ) { + return; + } + + // 创建一个 html5 运行时的 placeholder + // 以至于外部添加原生 File 对象的时候能正确包裹一下供 webuploader 使用。 + deferred = Base.Deferred(); + this.placeholder = runtime = new RuntimeClient('Placeholder'); + runtime.connectRuntime({ + runtimeOrder: 'html5' + }, function() { + me._ruid = runtime.getRuid(); + deferred.resolve(); + }); + return deferred.promise(); + }, + + + // 为了支持外部直接添加一个原生File对象。 + _wrapFile: function( file ) { + if ( !(file instanceof WUFile) ) { + + if ( !(file instanceof File) ) { + if ( !this._ruid ) { + throw new Error('Can\'t add external files.'); + } + file = new File( this._ruid, file ); + } + + file = new WUFile( file ); + } + + return file; + }, + + // 判断文件是否可以被加入队列 + acceptFile: function( file ) { + var invalid = !file || !file.size || this.accept && + + // 如果名字中有后缀,才做后缀白名单处理。 + rExt.exec( file.name ) && !this.accept.test( file.name ); + + return !invalid; + }, + + + /** + * @event beforeFileQueued + * @param {File} file File对象 + * @description 当文件被加入队列之前触发,此事件的handler返回值为`false`,则此文件不会被添加进入队列。 + * @for Uploader + */ + + /** + * @event fileQueued + * @param {File} file File对象 + * @description 当文件被加入队列以后触发。 + * @for Uploader + */ + + _addFile: function( file ) { + var me = this; + + file = me._wrapFile( file ); + + // 不过类型判断允许不允许,先派送 `beforeFileQueued` + if ( !me.owner.trigger( 'beforeFileQueued', file ) ) { + return; + } + + // 类型不匹配,则派送错误事件,并返回。 + if ( !me.acceptFile( file ) ) { + me.owner.trigger( 'error', 'Q_TYPE_DENIED', file ); + return; + } + + me.queue.append( file ); + me.owner.trigger( 'fileQueued', file ); + return file; + }, + + getFile: function( fileId ) { + return this.queue.getFile( fileId ); + }, + + /** + * @event filesQueued + * @param {File} files 数组,内容为原始File(lib/File)对象。 + * @description 当一批文件添加进队列以后触发。 + * @for Uploader + */ + + /** + * @property {Boolean} [auto=false] + * @namespace options + * @for Uploader + * @description 设置为 true 后,不需要手动调用上传,有文件选择即开始上传。 + * + */ + + /** + * @method addFiles + * @grammar addFiles( file ) => undefined + * @grammar addFiles( [file1, file2 ...] ) => undefined + * @param {Array of File or File} [files] Files 对象 数组 + * @description 添加文件到队列 + * @for Uploader + */ + addFile: function( files ) { + var me = this; + + if ( !files.length ) { + files = [ files ]; + } + + files = $.map( files, function( file ) { + return me._addFile( file ); + }); + + me.owner.trigger( 'filesQueued', files ); + + if ( me.options.auto ) { + setTimeout(function() { + me.request('start-upload'); + }, 20 ); + } + }, + + getStats: function() { + return this.stats; + }, + + /** + * @event fileDequeued + * @param {File} file File对象 + * @description 当文件被移除队列后触发。 + * @for Uploader + */ + + /** + * @method removeFile + * @grammar removeFile( file ) => undefined + * @grammar removeFile( id ) => undefined + * @grammar removeFile( file, true ) => undefined + * @grammar removeFile( id, true ) => undefined + * @param {File|id} file File对象或这File对象的id + * @description 移除某一文件, 默认只会标记文件状态为已取消,如果第二个参数为 `true` 则会从 queue 中移除。 + * @for Uploader + * @example + * + * $li.on('click', '.remove-this', function() { + * uploader.removeFile( file ); + * }) + */ + removeFile: function( file, remove ) { + var me = this; + + file = file.id ? file : me.queue.getFile( file ); + + this.request( 'cancel-file', file ); + + if ( remove ) { + this.queue.removeFile( file ); + } + }, + + /** + * @method getFiles + * @grammar getFiles() => Array + * @grammar getFiles( status1, status2, status... ) => Array + * @description 返回指定状态的文件集合,不传参数将返回所有状态的文件。 + * @for Uploader + * @example + * console.log( uploader.getFiles() ); // => all files + * console.log( uploader.getFiles('error') ) // => all error files. + */ + getFiles: function() { + return this.queue.getFiles.apply( this.queue, arguments ); + }, + + fetchFile: function() { + return this.queue.fetch.apply( this.queue, arguments ); + }, + + /** + * @method retry + * @grammar retry() => undefined + * @grammar retry( file ) => undefined + * @description 重试上传,重试指定文件,或者从出错的文件开始重新上传。 + * @for Uploader + * @example + * function retry() { + * uploader.retry(); + * } + */ + retry: function( file, noForceStart ) { + var me = this, + files, i, len; + + if ( file ) { + file = file.id ? file : me.queue.getFile( file ); + file.setStatus( Status.QUEUED ); + noForceStart || me.request('start-upload'); + return; + } + + files = me.queue.getFiles( Status.ERROR ); + i = 0; + len = files.length; + + for ( ; i < len; i++ ) { + file = files[ i ]; + file.setStatus( Status.QUEUED ); + } + + me.request('start-upload'); + }, + + /** + * @method sort + * @grammar sort( fn ) => undefined + * @description 排序队列中的文件,在上传之前调整可以控制上传顺序。 + * @for Uploader + */ + sortFiles: function() { + return this.queue.sort.apply( this.queue, arguments ); + }, + + /** + * @event reset + * @description 当 uploader 被重置的时候触发。 + * @for Uploader + */ + + /** + * @method reset + * @grammar reset() => undefined + * @description 重置uploader。目前只重置了队列。 + * @for Uploader + * @example + * uploader.reset(); + */ + reset: function() { + this.owner.trigger('reset'); + this.queue = new Queue(); + this.stats = this.queue.stats; + }, + + destroy: function() { + this.reset(); + this.placeholder && this.placeholder.destroy(); + } + }); + + }); + /** + * @fileOverview 添加获取Runtime相关信息的方法。 + */ + define('widgets/runtime',[ + 'uploader', + 'runtime/runtime', + 'widgets/widget' + ], function( Uploader, Runtime ) { + + Uploader.support = function() { + return Runtime.hasRuntime.apply( Runtime, arguments ); + }; + + /** + * @property {Object} [runtimeOrder=html5,flash] + * @namespace options + * @for Uploader + * @description 指定运行时启动顺序。默认会想尝试 html5 是否支持,如果支持则使用 html5, 否则则使用 flash. + * + * 可以将此值设置成 `flash`,来强制使用 flash 运行时。 + */ + + return Uploader.register({ + name: 'runtime', + + init: function() { + if ( !this.predictRuntimeType() ) { + throw Error('Runtime Error'); + } + }, + + /** + * 预测Uploader将采用哪个`Runtime` + * @grammar predictRuntimeType() => String + * @method predictRuntimeType + * @for Uploader + */ + predictRuntimeType: function() { + var orders = this.options.runtimeOrder || Runtime.orders, + type = this.type, + i, len; + + if ( !type ) { + orders = orders.split( /\s*,\s*/g ); + + for ( i = 0, len = orders.length; i < len; i++ ) { + if ( Runtime.hasRuntime( orders[ i ] ) ) { + this.type = type = orders[ i ]; + break; + } + } + } + + return type; + } + }); + }); + /** + * @fileOverview Transport + */ + define('lib/transport',[ + 'base', + 'runtime/client', + 'mediator' + ], function( Base, RuntimeClient, Mediator ) { + + var $ = Base.$; + + function Transport( opts ) { + var me = this; + + opts = me.options = $.extend( true, {}, Transport.options, opts || {} ); + RuntimeClient.call( this, 'Transport' ); + + this._blob = null; + this._formData = opts.formData || {}; + this._headers = opts.headers || {}; + + this.on( 'progress', this._timeout ); + this.on( 'load error', function() { + me.trigger( 'progress', 1 ); + clearTimeout( me._timer ); + }); + } + + Transport.options = { + server: '', + method: 'POST', + + // 跨域时,是否允许携带cookie, 只有html5 runtime才有效 + withCredentials: false, + fileVal: 'file', + timeout: 2 * 60 * 1000, // 2分钟 + formData: {}, + headers: {}, + sendAsBinary: false + }; + + $.extend( Transport.prototype, { + + // 添加Blob, 只能添加一次,最后一次有效。 + appendBlob: function( key, blob, filename ) { + var me = this, + opts = me.options; + + if ( me.getRuid() ) { + me.disconnectRuntime(); + } + + // 连接到blob归属的同一个runtime. + me.connectRuntime( blob.ruid, function() { + me.exec('init'); + }); + + me._blob = blob; + opts.fileVal = key || opts.fileVal; + opts.filename = filename || opts.filename; + }, + + // 添加其他字段 + append: function( key, value ) { + if ( typeof key === 'object' ) { + $.extend( this._formData, key ); + } else { + this._formData[ key ] = value; + } + }, + + setRequestHeader: function( key, value ) { + if ( typeof key === 'object' ) { + $.extend( this._headers, key ); + } else { + this._headers[ key ] = value; + } + }, + + send: function( method ) { + this.exec( 'send', method ); + this._timeout(); + }, + + abort: function() { + clearTimeout( this._timer ); + return this.exec('abort'); + }, + + destroy: function() { + this.trigger('destroy'); + this.off(); + this.exec('destroy'); + this.disconnectRuntime(); + }, + + getResponse: function() { + return this.exec('getResponse'); + }, + + getResponseAsJson: function() { + return this.exec('getResponseAsJson'); + }, + + getStatus: function() { + return this.exec('getStatus'); + }, + + _timeout: function() { + var me = this, + duration = me.options.timeout; + + if ( !duration ) { + return; + } + + clearTimeout( me._timer ); + me._timer = setTimeout(function() { + me.abort(); + me.trigger( 'error', 'timeout' ); + }, duration ); + } + + }); + + // 让Transport具备事件功能。 + Mediator.installTo( Transport.prototype ); + + return Transport; + }); + /** + * @fileOverview 负责文件上传相关。 + */ + define('widgets/upload',[ + 'base', + 'uploader', + 'file', + 'lib/transport', + 'widgets/widget' + ], function( Base, Uploader, WUFile, Transport ) { + + var $ = Base.$, + isPromise = Base.isPromise, + Status = WUFile.Status; + + // 添加默认配置项 + $.extend( Uploader.options, { + + + /** + * @property {Boolean} [prepareNextFile=false] + * @namespace options + * @for Uploader + * @description 是否允许在文件传输时提前把下一个文件准备好。 + * 对于一个文件的准备工作比较耗时,比如图片压缩,md5序列化。 + * 如果能提前在当前文件传输期处理,可以节省总体耗时。 + */ + prepareNextFile: false, + + /** + * @property {Boolean} [chunked=false] + * @namespace options + * @for Uploader + * @description 是否要分片处理大文件上传。 + */ + chunked: false, + + /** + * @property {Boolean} [chunkSize=5242880] + * @namespace options + * @for Uploader + * @description 如果要分片,分多大一片? 默认大小为5M. + */ + chunkSize: 5 * 1024 * 1024, + + /** + * @property {Boolean} [chunkRetry=2] + * @namespace options + * @for Uploader + * @description 如果某个分片由于网络问题出错,允许自动重传多少次? + */ + chunkRetry: 2, + + /** + * @property {Boolean} [threads=3] + * @namespace options + * @for Uploader + * @description 上传并发数。允许同时最大上传进程数。 + */ + threads: 3, + + + /** + * @property {Object} [formData={}] + * @namespace options + * @for Uploader + * @description 文件上传请求的参数表,每次发送都会发送此对象中的参数。 + */ + formData: {} + + /** + * @property {Object} [fileVal='file'] + * @namespace options + * @for Uploader + * @description 设置文件上传域的name。 + */ + + /** + * @property {Object} [method='POST'] + * @namespace options + * @for Uploader + * @description 文件上传方式,`POST`或者`GET`。 + */ + + /** + * @property {Object} [sendAsBinary=false] + * @namespace options + * @for Uploader + * @description 是否已二进制的流的方式发送文件,这样整个上传内容`php://input`都为文件内容, + * 其他参数在$_GET数组中。 + */ + }); + + // 负责将文件切片。 + function CuteFile( file, chunkSize ) { + var pending = [], + blob = file.source, + total = blob.size, + chunks = chunkSize ? Math.ceil( total / chunkSize ) : 1, + start = 0, + index = 0, + len, api; + + api = { + file: file, + + has: function() { + return !!pending.length; + }, + + shift: function() { + return pending.shift(); + }, + + unshift: function( block ) { + pending.unshift( block ); + } + }; + + while ( index < chunks ) { + len = Math.min( chunkSize, total - start ); + + pending.push({ + file: file, + start: start, + end: chunkSize ? (start + len) : total, + total: total, + chunks: chunks, + chunk: index++, + cuted: api + }); + start += len; + } + + file.blocks = pending.concat(); + file.remaning = pending.length; + + return api; + } + + Uploader.register({ + name: 'upload', + + init: function() { + var owner = this.owner, + me = this; + + this.runing = false; + this.progress = false; + + owner + .on( 'startUpload', function() { + me.progress = true; + }) + .on( 'uploadFinished', function() { + me.progress = false; + }); + + // 记录当前正在传的数据,跟threads相关 + this.pool = []; + + // 缓存分好片的文件。 + this.stack = []; + + // 缓存即将上传的文件。 + this.pending = []; + + // 跟踪还有多少分片在上传中但是没有完成上传。 + this.remaning = 0; + this.__tick = Base.bindFn( this._tick, this ); + + owner.on( 'uploadComplete', function( file ) { + + // 把其他块取消了。 + file.blocks && $.each( file.blocks, function( _, v ) { + v.transport && (v.transport.abort(), v.transport.destroy()); + delete v.transport; + }); + + delete file.blocks; + delete file.remaning; + }); + }, + + reset: function() { + this.request( 'stop-upload', true ); + this.runing = false; + this.pool = []; + this.stack = []; + this.pending = []; + this.remaning = 0; + this._trigged = false; + this._promise = null; + }, + + /** + * @event startUpload + * @description 当开始上传流程时触发。 + * @for Uploader + */ + + /** + * 开始上传。此方法可以从初始状态调用开始上传流程,也可以从暂停状态调用,继续上传流程。 + * + * 可以指定开始某一个文件。 + * @grammar upload() => undefined + * @grammar upload( file | fileId) => undefined + * @method upload + * @for Uploader + */ + startUpload: function(file) { + var me = this; + + // 移出invalid的文件 + $.each( me.request( 'get-files', Status.INVALID ), function() { + me.request( 'remove-file', this ); + }); + + // 如果指定了开始某个文件,则只开始指定文件。 + if ( file ) { + file = file.id ? file : me.request( 'get-file', file ); + + if (file.getStatus() === Status.INTERRUPT) { + $.each( me.pool, function( _, v ) { + + // 之前暂停过。 + if (v.file !== file) { + return; + } + + v.transport && v.transport.send(); + }); + + file.setStatus( Status.QUEUED ); + } else if (file.getStatus() === Status.PROGRESS) { + return; + } else { + file.setStatus( Status.QUEUED ); + } + } else { + $.each( me.request( 'get-files', [ Status.INITED ] ), function() { + this.setStatus( Status.QUEUED ); + }); + } + + if ( me.runing ) { + return; + } + + me.runing = true; + + var files = []; + + // 如果有暂停的,则续传 + $.each( me.pool, function( _, v ) { + var file = v.file; + + if ( file.getStatus() === Status.INTERRUPT ) { + files.push(file); + me._trigged = false; + v.transport && v.transport.send(); + } + }); + + var file; + while ( (file = files.shift()) ) { + file.setStatus( Status.PROGRESS ); + } + + file || $.each( me.request( 'get-files', + Status.INTERRUPT ), function() { + this.setStatus( Status.PROGRESS ); + }); + + me._trigged = false; + Base.nextTick( me.__tick ); + me.owner.trigger('startUpload'); + }, + + /** + * @event stopUpload + * @description 当开始上传流程暂停时触发。 + * @for Uploader + */ + + /** + * 暂停上传。第一个参数为是否中断上传当前正在上传的文件。 + * + * 如果第一个参数是文件,则只暂停指定文件。 + * @grammar stop() => undefined + * @grammar stop( true ) => undefined + * @grammar stop( file ) => undefined + * @method stop + * @for Uploader + */ + stopUpload: function( file, interrupt ) { + var me = this; + + if (file === true) { + interrupt = file; + file = null; + } + + if ( me.runing === false ) { + return; + } + + // 如果只是暂停某个文件。 + if ( file ) { + file = file.id ? file : me.request( 'get-file', file ); + + if ( file.getStatus() !== Status.PROGRESS && + file.getStatus() !== Status.QUEUED ) { + return; + } + + file.setStatus( Status.INTERRUPT ); + $.each( me.pool, function( _, v ) { + + // 只 abort 指定的文件。 + if (v.file !== file) { + return; + } + + v.transport && v.transport.abort(); + me._putback(v); + me._popBlock(v); + }); + + return Base.nextTick( me.__tick ); + } + + me.runing = false; + + if (this._promise && this._promise.file) { + this._promise.file.setStatus( Status.INTERRUPT ); + } + + interrupt && $.each( me.pool, function( _, v ) { + v.transport && v.transport.abort(); + v.file.setStatus( Status.INTERRUPT ); + }); + + me.owner.trigger('stopUpload'); + }, + + /** + * @method cancelFile + * @grammar cancelFile( file ) => undefined + * @grammar cancelFile( id ) => undefined + * @param {File|id} file File对象或这File对象的id + * @description 标记文件状态为已取消, 同时将中断文件传输。 + * @for Uploader + * @example + * + * $li.on('click', '.remove-this', function() { + * uploader.cancelFile( file ); + * }) + */ + cancelFile: function( file ) { + file = file.id ? file : this.request( 'get-file', file ); + + // 如果正在上传。 + file.blocks && $.each( file.blocks, function( _, v ) { + var _tr = v.transport; + + if ( _tr ) { + _tr.abort(); + _tr.destroy(); + delete v.transport; + } + }); + + file.setStatus( Status.CANCELLED ); + this.owner.trigger( 'fileDequeued', file ); + }, + + /** + * 判断`Uplaode`r是否正在上传中。 + * @grammar isInProgress() => Boolean + * @method isInProgress + * @for Uploader + */ + isInProgress: function() { + return !!this.progress; + }, + + _getStats: function() { + return this.request('get-stats'); + }, + + /** + * 掉过一个文件上传,直接标记指定文件为已上传状态。 + * @grammar skipFile( file ) => undefined + * @method skipFile + * @for Uploader + */ + skipFile: function( file, status ) { + file = file.id ? file : this.request( 'get-file', file ); + + file.setStatus( status || Status.COMPLETE ); + file.skipped = true; + + // 如果正在上传。 + file.blocks && $.each( file.blocks, function( _, v ) { + var _tr = v.transport; + + if ( _tr ) { + _tr.abort(); + _tr.destroy(); + delete v.transport; + } + }); + + this.owner.trigger( 'uploadSkip', file ); + }, + + /** + * @event uploadFinished + * @description 当所有文件上传结束时触发。 + * @for Uploader + */ + _tick: function() { + var me = this, + opts = me.options, + fn, val; + + // 上一个promise还没有结束,则等待完成后再执行。 + if ( me._promise ) { + return me._promise.always( me.__tick ); + } + + // 还有位置,且还有文件要处理的话。 + if ( me.pool.length < opts.threads && (val = me._nextBlock()) ) { + me._trigged = false; + + fn = function( val ) { + me._promise = null; + + // 有可能是reject过来的,所以要检测val的类型。 + val && val.file && me._startSend( val ); + Base.nextTick( me.__tick ); + }; + + me._promise = isPromise( val ) ? val.always( fn ) : fn( val ); + + // 没有要上传的了,且没有正在传输的了。 + } else if ( !me.remaning && !me._getStats().numOfQueue && + !me._getStats().numofInterrupt ) { + me.runing = false; + + me._trigged || Base.nextTick(function() { + me.owner.trigger('uploadFinished'); + }); + me._trigged = true; + } + }, + + _putback: function(block) { + var idx; + + block.cuted.unshift(block); + idx = this.stack.indexOf(block.cuted); + + if (!~idx) { + this.stack.unshift(block.cuted); + } + }, + + _getStack: function() { + var i = 0, + act; + + while ( (act = this.stack[ i++ ]) ) { + if ( act.has() && act.file.getStatus() === Status.PROGRESS ) { + return act; + } else if (!act.has() || + act.file.getStatus() !== Status.PROGRESS && + act.file.getStatus() !== Status.INTERRUPT ) { + + // 把已经处理完了的,或者,状态为非 progress(上传中)、 + // interupt(暂停中) 的移除。 + this.stack.splice( --i, 1 ); + } + } + + return null; + }, + + _nextBlock: function() { + var me = this, + opts = me.options, + act, next, done, preparing; + + // 如果当前文件还有没有需要传输的,则直接返回剩下的。 + if ( (act = this._getStack()) ) { + + // 是否提前准备下一个文件 + if ( opts.prepareNextFile && !me.pending.length ) { + me._prepareNextFile(); + } + + return act.shift(); + + // 否则,如果正在运行,则准备下一个文件,并等待完成后返回下个分片。 + } else if ( me.runing ) { + + // 如果缓存中有,则直接在缓存中取,没有则去queue中取。 + if ( !me.pending.length && me._getStats().numOfQueue ) { + me._prepareNextFile(); + } + + next = me.pending.shift(); + done = function( file ) { + if ( !file ) { + return null; + } + + act = CuteFile( file, opts.chunked ? opts.chunkSize : 0 ); + me.stack.push(act); + return act.shift(); + }; + + // 文件可能还在prepare中,也有可能已经完全准备好了。 + if ( isPromise( next) ) { + preparing = next.file; + next = next[ next.pipe ? 'pipe' : 'then' ]( done ); + next.file = preparing; + return next; + } + + return done( next ); + } + }, + + + /** + * @event uploadStart + * @param {File} file File对象 + * @description 某个文件开始上传前触发,一个文件只会触发一次。 + * @for Uploader + */ + _prepareNextFile: function() { + var me = this, + file = me.request('fetch-file'), + pending = me.pending, + promise; + + if ( file ) { + promise = me.request( 'before-send-file', file, function() { + + // 有可能文件被skip掉了。文件被skip掉后,状态坑定不是Queued. + if ( file.getStatus() === Status.PROGRESS || + file.getStatus() === Status.INTERRUPT ) { + return file; + } + + return me._finishFile( file ); + }); + + me.owner.trigger( 'uploadStart', file ); + file.setStatus( Status.PROGRESS ); + + promise.file = file; + + // 如果还在pending中,则替换成文件本身。 + promise.done(function() { + var idx = $.inArray( promise, pending ); + + ~idx && pending.splice( idx, 1, file ); + }); + + // befeore-send-file的钩子就有错误发生。 + promise.fail(function( reason ) { + file.setStatus( Status.ERROR, reason ); + me.owner.trigger( 'uploadError', file, reason ); + me.owner.trigger( 'uploadComplete', file ); + }); + + pending.push( promise ); + } + }, + + // 让出位置了,可以让其他分片开始上传 + _popBlock: function( block ) { + var idx = $.inArray( block, this.pool ); + + this.pool.splice( idx, 1 ); + block.file.remaning--; + this.remaning--; + }, + + // 开始上传,可以被掉过。如果promise被reject了,则表示跳过此分片。 + _startSend: function( block ) { + var me = this, + file = block.file, + promise; + + // 有可能在 before-send-file 的 promise 期间改变了文件状态。 + // 如:暂停,取消 + // 我们不能中断 promise, 但是可以在 promise 完后,不做上传操作。 + if ( file.getStatus() !== Status.PROGRESS ) { + + // 如果是中断,则还需要放回去。 + if (file.getStatus() === Status.INTERRUPT) { + me._putback(block); + } + + return; + } + + me.pool.push( block ); + me.remaning++; + + // 如果没有分片,则直接使用原始的。 + // 不会丢失content-type信息。 + block.blob = block.chunks === 1 ? file.source : + file.source.slice( block.start, block.end ); + + // hook, 每个分片发送之前可能要做些异步的事情。 + promise = me.request( 'before-send', block, function() { + + // 有可能文件已经上传出错了,所以不需要再传输了。 + if ( file.getStatus() === Status.PROGRESS ) { + me._doSend( block ); + } else { + me._popBlock( block ); + Base.nextTick( me.__tick ); + } + }); + + // 如果为fail了,则跳过此分片。 + promise.fail(function() { + if ( file.remaning === 1 ) { + me._finishFile( file ).always(function() { + block.percentage = 1; + me._popBlock( block ); + me.owner.trigger( 'uploadComplete', file ); + Base.nextTick( me.__tick ); + }); + } else { + block.percentage = 1; + me.updateFileProgress( file ); + me._popBlock( block ); + Base.nextTick( me.__tick ); + } + }); + }, + + + /** + * @event uploadBeforeSend + * @param {Object} object + * @param {Object} data 默认的上传参数,可以扩展此对象来控制上传参数。 + * @param {Object} headers 可以扩展此对象来控制上传头部。 + * @description 当某个文件的分块在发送前触发,主要用来询问是否要添加附带参数,大文件在开起分片上传的前提下此事件可能会触发多次。 + * @for Uploader + */ + + /** + * @event uploadAccept + * @param {Object} object + * @param {Object} ret 服务端的返回数据,json格式,如果服务端不是json格式,从ret._raw中取数据,自行解析。 + * @description 当某个文件上传到服务端响应后,会派送此事件来询问服务端响应是否有效。如果此事件handler返回值为`false`, 则此文件将派送`server`类型的`uploadError`事件。 + * @for Uploader + */ + + /** + * @event uploadProgress + * @param {File} file File对象 + * @param {Number} percentage 上传进度 + * @description 上传过程中触发,携带上传进度。 + * @for Uploader + */ + + + /** + * @event uploadError + * @param {File} file File对象 + * @param {String} reason 出错的code + * @description 当文件上传出错时触发。 + * @for Uploader + */ + + /** + * @event uploadSuccess + * @param {File} file File对象 + * @param {Object} response 服务端返回的数据 + * @description 当文件上传成功时触发。 + * @for Uploader + */ + + /** + * @event uploadComplete + * @param {File} [file] File对象 + * @description 不管成功或者失败,文件上传完成时触发。 + * @for Uploader + */ + + // 做上传操作。 + _doSend: function( block ) { + var me = this, + owner = me.owner, + opts = me.options, + file = block.file, + tr = new Transport( opts ), + data = $.extend({}, opts.formData ), + headers = $.extend({}, opts.headers ), + requestAccept, ret; + + block.transport = tr; + + tr.on( 'destroy', function() { + delete block.transport; + me._popBlock( block ); + Base.nextTick( me.__tick ); + }); + + // 广播上传进度。以文件为单位。 + tr.on( 'progress', function( percentage ) { + block.percentage = percentage; + me.updateFileProgress( file ); + }); + + // 用来询问,是否返回的结果是有错误的。 + requestAccept = function( reject ) { + var fn; + + ret = tr.getResponseAsJson() || {}; + ret._raw = tr.getResponse(); + fn = function( value ) { + reject = value; + }; + + // 服务端响应了,不代表成功了,询问是否响应正确。 + if ( !owner.trigger( 'uploadAccept', block, ret, fn ) ) { + reject = reject || 'server'; + } + + return reject; + }; + + // 尝试重试,然后广播文件上传出错。 + tr.on( 'error', function( type, flag ) { + block.retried = block.retried || 0; + + // 自动重试 + if ( block.chunks > 1 && ~'http,abort'.indexOf( type ) && + block.retried < opts.chunkRetry ) { + + block.retried++; + tr.send(); + + } else { + + // http status 500 ~ 600 + if ( !flag && type === 'server' ) { + type = requestAccept( type ); + } + + file.setStatus( Status.ERROR, type ); + owner.trigger( 'uploadError', file, type ); + owner.trigger( 'uploadComplete', file ); + } + }); + + // 上传成功 + tr.on( 'load', function() { + var reason; + + // 如果非预期,转向上传出错。 + if ( (reason = requestAccept()) ) { + tr.trigger( 'error', reason, true ); + return; + } + + // 全部上传完成。 + if ( file.remaning === 1 ) { + me._finishFile( file, ret ); + } else { + tr.destroy(); + } + }); + + // 配置默认的上传字段。 + data = $.extend( data, { + id: file.id, + name: file.name, + type: file.type, + lastModifiedDate: file.lastModifiedDate, + size: file.size + }); + + block.chunks > 1 && $.extend( data, { + chunks: block.chunks, + chunk: block.chunk + }); + + // 在发送之间可以添加字段什么的。。。 + // 如果默认的字段不够使用,可以通过监听此事件来扩展 + owner.trigger( 'uploadBeforeSend', block, data, headers ); + + // 开始发送。 + tr.appendBlob( opts.fileVal, block.blob, file.name ); + tr.append( data ); + tr.setRequestHeader( headers ); + tr.send(); + }, + + // 完成上传。 + _finishFile: function( file, ret, hds ) { + var owner = this.owner; + + return owner + .request( 'after-send-file', arguments, function() { + file.setStatus( Status.COMPLETE ); + owner.trigger( 'uploadSuccess', file, ret, hds ); + }) + .fail(function( reason ) { + + // 如果外部已经标记为invalid什么的,不再改状态。 + if ( file.getStatus() === Status.PROGRESS ) { + file.setStatus( Status.ERROR, reason ); + } + + owner.trigger( 'uploadError', file, reason ); + }) + .always(function() { + owner.trigger( 'uploadComplete', file ); + }); + }, + + updateFileProgress: function(file) { + var totalPercent = 0, + uploaded = 0; + + if (!file.blocks) { + return; + } + + $.each( file.blocks, function( _, v ) { + uploaded += (v.percentage || 0) * (v.end - v.start); + }); + + totalPercent = uploaded / file.size; + this.owner.trigger( 'uploadProgress', file, totalPercent || 0 ); + } + + }); + }); + /** + * @fileOverview 各种验证,包括文件总大小是否超出、单文件是否超出和文件是否重复。 + */ + + define('widgets/validator',[ + 'base', + 'uploader', + 'file', + 'widgets/widget' + ], function( Base, Uploader, WUFile ) { + + var $ = Base.$, + validators = {}, + api; + + /** + * @event error + * @param {String} type 错误类型。 + * @description 当validate不通过时,会以派送错误事件的形式通知调用者。通过`upload.on('error', handler)`可以捕获到此类错误,目前有以下错误会在特定的情况下派送错来。 + * + * * `Q_EXCEED_NUM_LIMIT` 在设置了`fileNumLimit`且尝试给`uploader`添加的文件数量超出这个值时派送。 + * * `Q_EXCEED_SIZE_LIMIT` 在设置了`Q_EXCEED_SIZE_LIMIT`且尝试给`uploader`添加的文件总大小超出这个值时派送。 + * * `Q_TYPE_DENIED` 当文件类型不满足时触发。。 + * @for Uploader + */ + + // 暴露给外面的api + api = { + + // 添加验证器 + addValidator: function( type, cb ) { + validators[ type ] = cb; + }, + + // 移除验证器 + removeValidator: function( type ) { + delete validators[ type ]; + } + }; + + // 在Uploader初始化的时候启动Validators的初始化 + Uploader.register({ + name: 'validator', + + init: function() { + var me = this; + Base.nextTick(function() { + $.each( validators, function() { + this.call( me.owner ); + }); + }); + } + }); + + /** + * @property {int} [fileNumLimit=undefined] + * @namespace options + * @for Uploader + * @description 验证文件总数量, 超出则不允许加入队列。 + */ + api.addValidator( 'fileNumLimit', function() { + var uploader = this, + opts = uploader.options, + count = 0, + max = parseInt( opts.fileNumLimit, 10 ), + flag = true; + + if ( !max ) { + return; + } + + uploader.on( 'beforeFileQueued', function( file ) { + + if ( count >= max && flag ) { + flag = false; + this.trigger( 'error', 'Q_EXCEED_NUM_LIMIT', max, file ); + setTimeout(function() { + flag = true; + }, 1 ); + } + + return count >= max ? false : true; + }); + + uploader.on( 'fileQueued', function() { + count++; + }); + + uploader.on( 'fileDequeued', function() { + count--; + }); + + uploader.on( 'reset', function() { + count = 0; + }); + }); + + + /** + * @property {int} [fileSizeLimit=undefined] + * @namespace options + * @for Uploader + * @description 验证文件总大小是否超出限制, 超出则不允许加入队列。 + */ + api.addValidator( 'fileSizeLimit', function() { + var uploader = this, + opts = uploader.options, + count = 0, + max = parseInt( opts.fileSizeLimit, 10 ), + flag = true; + + if ( !max ) { + return; + } + + uploader.on( 'beforeFileQueued', function( file ) { + var invalid = count + file.size > max; + + if ( invalid && flag ) { + flag = false; + this.trigger( 'error', 'Q_EXCEED_SIZE_LIMIT', max, file ); + setTimeout(function() { + flag = true; + }, 1 ); + } + + return invalid ? false : true; + }); + + uploader.on( 'fileQueued', function( file ) { + count += file.size; + }); + + uploader.on( 'fileDequeued', function( file ) { + count -= file.size; + }); + + uploader.on( 'reset', function() { + count = 0; + }); + }); + + /** + * @property {int} [fileSingleSizeLimit=undefined] + * @namespace options + * @for Uploader + * @description 验证单个文件大小是否超出限制, 超出则不允许加入队列。 + */ + api.addValidator( 'fileSingleSizeLimit', function() { + var uploader = this, + opts = uploader.options, + max = opts.fileSingleSizeLimit; + + if ( !max ) { + return; + } + + uploader.on( 'beforeFileQueued', function( file ) { + + if ( file.size > max ) { + file.setStatus( WUFile.Status.INVALID, 'exceed_size' ); + this.trigger( 'error', 'F_EXCEED_SIZE', max, file ); + return false; + } + + }); + + }); + + /** + * @property {Boolean} [duplicate=undefined] + * @namespace options + * @for Uploader + * @description 去重, 根据文件名字、文件大小和最后修改时间来生成hash Key. + */ + api.addValidator( 'duplicate', function() { + var uploader = this, + opts = uploader.options, + mapping = {}; + + if ( opts.duplicate ) { + return; + } + + function hashString( str ) { + var hash = 0, + i = 0, + len = str.length, + _char; + + for ( ; i < len; i++ ) { + _char = str.charCodeAt( i ); + hash = _char + (hash << 6) + (hash << 16) - hash; + } + + return hash; + } + + uploader.on( 'beforeFileQueued', function( file ) { + var hash = file.__hash || (file.__hash = hashString( file.name + + file.size + file.lastModifiedDate )); + + // 已经重复了 + if ( mapping[ hash ] ) { + this.trigger( 'error', 'F_DUPLICATE', file ); + return false; + } + }); + + uploader.on( 'fileQueued', function( file ) { + var hash = file.__hash; + + hash && (mapping[ hash ] = true); + }); + + uploader.on( 'fileDequeued', function( file ) { + var hash = file.__hash; + + hash && (delete mapping[ hash ]); + }); + + uploader.on( 'reset', function() { + mapping = {}; + }); + }); + + return api; + }); + + /** + * @fileOverview Md5 + */ + define('lib/md5',[ + 'runtime/client', + 'mediator' + ], function( RuntimeClient, Mediator ) { + + function Md5() { + RuntimeClient.call( this, 'Md5' ); + } + + // 让 Md5 具备事件功能。 + Mediator.installTo( Md5.prototype ); + + Md5.prototype.loadFromBlob = function( blob ) { + var me = this; + + if ( me.getRuid() ) { + me.disconnectRuntime(); + } + + // 连接到blob归属的同一个runtime. + me.connectRuntime( blob.ruid, function() { + me.exec('init'); + me.exec( 'loadFromBlob', blob ); + }); + }; + + Md5.prototype.getResult = function() { + return this.exec('getResult'); + }; + + return Md5; + }); + /** + * @fileOverview 图片操作, 负责预览图片和上传前压缩图片 + */ + define('widgets/md5',[ + 'base', + 'uploader', + 'lib/md5', + 'lib/blob', + 'widgets/widget' + ], function( Base, Uploader, Md5, Blob ) { + + return Uploader.register({ + name: 'md5', + + + /** + * 计算文件 md5 值,返回一个 promise 对象,可以监听 progress 进度。 + * + * + * @method md5File + * @grammar md5File( file[, start[, end]] ) => promise + * @for Uploader + * @example + * + * uploader.on( 'fileQueued', function( file ) { + * var $li = ...; + * + * uploader.md5File( file ) + * + * // 及时显示进度 + * .progress(function(percentage) { + * console.log('Percentage:', percentage); + * }) + * + * // 完成 + * .then(function(val) { + * console.log('md5 result:', val); + * }); + * + * }); + */ + md5File: function( file, start, end ) { + var md5 = new Md5(), + deferred = Base.Deferred(), + blob = (file instanceof Blob) ? file : + this.request( 'get-file', file ).source; + + md5.on( 'progress load', function( e ) { + e = e || {}; + deferred.notify( e.total ? e.loaded / e.total : 1 ); + }); + + md5.on( 'complete', function() { + deferred.resolve( md5.getResult() ); + }); + + md5.on( 'error', function( reason ) { + deferred.reject( reason ); + }); + + if ( arguments.length > 1 ) { + start = start || 0; + end = end || 0; + start < 0 && (start = blob.size + start); + end < 0 && (end = blob.size + end); + end = Math.min( end, blob.size ); + blob = blob.slice( start, end ); + } + + md5.loadFromBlob( blob ); + + return deferred.promise(); + } + }); + }); + /** + * @fileOverview Runtime管理器,负责Runtime的选择, 连接 + */ + define('runtime/compbase',[],function() { + + function CompBase( owner, runtime ) { + + this.owner = owner; + this.options = owner.options; + + this.getRuntime = function() { + return runtime; + }; + + this.getRuid = function() { + return runtime.uid; + }; + + this.trigger = function() { + return owner.trigger.apply( owner, arguments ); + }; + } + + return CompBase; + }); + /** + * @fileOverview Html5Runtime + */ + define('runtime/html5/runtime',[ + 'base', + 'runtime/runtime', + 'runtime/compbase' + ], function( Base, Runtime, CompBase ) { + + var type = 'html5', + components = {}; + + function Html5Runtime() { + var pool = {}, + me = this, + destroy = this.destroy; + + Runtime.apply( me, arguments ); + me.type = type; + + + // 这个方法的调用者,实际上是RuntimeClient + me.exec = function( comp, fn/*, args...*/) { + var client = this, + uid = client.uid, + args = Base.slice( arguments, 2 ), + instance; + + if ( components[ comp ] ) { + instance = pool[ uid ] = pool[ uid ] || + new components[ comp ]( client, me ); + + if ( instance[ fn ] ) { + return instance[ fn ].apply( instance, args ); + } + } + }; + + me.destroy = function() { + // @todo 删除池子中的所有实例 + return destroy && destroy.apply( this, arguments ); + }; + } + + Base.inherits( Runtime, { + constructor: Html5Runtime, + + // 不需要连接其他程序,直接执行callback + init: function() { + var me = this; + setTimeout(function() { + me.trigger('ready'); + }, 1 ); + } + + }); + + // 注册Components + Html5Runtime.register = function( name, component ) { + var klass = components[ name ] = Base.inherits( CompBase, component ); + return klass; + }; + + // 注册html5运行时。 + // 只有在支持的前提下注册。 + if ( window.Blob && window.FileReader && window.DataView ) { + Runtime.addRuntime( type, Html5Runtime ); + } + + return Html5Runtime; + }); + /** + * @fileOverview Blob Html实现 + */ + define('runtime/html5/blob',[ + 'runtime/html5/runtime', + 'lib/blob' + ], function( Html5Runtime, Blob ) { + + return Html5Runtime.register( 'Blob', { + slice: function( start, end ) { + var blob = this.owner.source, + slice = blob.slice || blob.webkitSlice || blob.mozSlice; + + blob = slice.call( blob, start, end ); + + return new Blob( this.getRuid(), blob ); + } + }); + }); + /** + * @fileOverview FilePaste + */ + define('runtime/html5/dnd',[ + 'base', + 'runtime/html5/runtime', + 'lib/file' + ], function( Base, Html5Runtime, File ) { + + var $ = Base.$, + prefix = 'webuploader-dnd-'; + + return Html5Runtime.register( 'DragAndDrop', { + init: function() { + var elem = this.elem = this.options.container; + + this.dragEnterHandler = Base.bindFn( this._dragEnterHandler, this ); + this.dragOverHandler = Base.bindFn( this._dragOverHandler, this ); + this.dragLeaveHandler = Base.bindFn( this._dragLeaveHandler, this ); + this.dropHandler = Base.bindFn( this._dropHandler, this ); + this.dndOver = false; + + elem.on( 'dragenter', this.dragEnterHandler ); + elem.on( 'dragover', this.dragOverHandler ); + elem.on( 'dragleave', this.dragLeaveHandler ); + elem.on( 'drop', this.dropHandler ); + + if ( this.options.disableGlobalDnd ) { + $( document ).on( 'dragover', this.dragOverHandler ); + $( document ).on( 'drop', this.dropHandler ); + } + }, + + _dragEnterHandler: function( e ) { + var me = this, + denied = me._denied || false, + items; + + e = e.originalEvent || e; + + if ( !me.dndOver ) { + me.dndOver = true; + + // 注意只有 chrome 支持。 + items = e.dataTransfer.items; + + if ( items && items.length ) { + me._denied = denied = !me.trigger( 'accept', items ); + } + + me.elem.addClass( prefix + 'over' ); + me.elem[ denied ? 'addClass' : + 'removeClass' ]( prefix + 'denied' ); + } + + e.dataTransfer.dropEffect = denied ? 'none' : 'copy'; + + return false; + }, + + _dragOverHandler: function( e ) { + // 只处理框内的。 + var parentElem = this.elem.parent().get( 0 ); + if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) { + return false; + } + + clearTimeout( this._leaveTimer ); + this._dragEnterHandler.call( this, e ); + + return false; + }, + + _dragLeaveHandler: function() { + var me = this, + handler; + + handler = function() { + me.dndOver = false; + me.elem.removeClass( prefix + 'over ' + prefix + 'denied' ); + }; + + clearTimeout( me._leaveTimer ); + me._leaveTimer = setTimeout( handler, 100 ); + return false; + }, + + _dropHandler: function( e ) { + var me = this, + ruid = me.getRuid(), + parentElem = me.elem.parent().get( 0 ), + dataTransfer, data; + + // 只处理框内的。 + if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) { + return false; + } + + e = e.originalEvent || e; + dataTransfer = e.dataTransfer; + + // 如果是页面内拖拽,还不能处理,不阻止事件。 + // 此处 ie11 下会报参数错误, + try { + data = dataTransfer.getData('text/html'); + } catch( err ) { + } + + if ( data ) { + return; + } + + me._getTansferFiles( dataTransfer, function( results ) { + me.trigger( 'drop', $.map( results, function( file ) { + return new File( ruid, file ); + }) ); + }); + + me.dndOver = false; + me.elem.removeClass( prefix + 'over' ); + return false; + }, + + // 如果传入 callback 则去查看文件夹,否则只管当前文件夹。 + _getTansferFiles: function( dataTransfer, callback ) { + var results = [], + promises = [], + items, files, file, item, i, len, canAccessFolder; + + items = dataTransfer.items; + files = dataTransfer.files; + + canAccessFolder = !!(items && items[ 0 ].webkitGetAsEntry); + + for ( i = 0, len = files.length; i < len; i++ ) { + file = files[ i ]; + item = items && items[ i ]; + + if ( canAccessFolder && item.webkitGetAsEntry().isDirectory ) { + + promises.push( this._traverseDirectoryTree( + item.webkitGetAsEntry(), results ) ); + } else { + results.push( file ); + } + } + + Base.when.apply( Base, promises ).done(function() { + + if ( !results.length ) { + return; + } + + callback( results ); + }); + }, + + _traverseDirectoryTree: function( entry, results ) { + var deferred = Base.Deferred(), + me = this; + + if ( entry.isFile ) { + entry.file(function( file ) { + results.push( file ); + deferred.resolve(); + }); + } else if ( entry.isDirectory ) { + entry.createReader().readEntries(function( entries ) { + var len = entries.length, + promises = [], + arr = [], // 为了保证顺序。 + i; + + for ( i = 0; i < len; i++ ) { + promises.push( me._traverseDirectoryTree( + entries[ i ], arr ) ); + } + + Base.when.apply( Base, promises ).then(function() { + results.push.apply( results, arr ); + deferred.resolve(); + }, deferred.reject ); + }); + } + + return deferred.promise(); + }, + + destroy: function() { + var elem = this.elem; + + // 还没 init 就调用 destroy + if (!elem) { + return; + } + + elem.off( 'dragenter', this.dragEnterHandler ); + elem.off( 'dragover', this.dragOverHandler ); + elem.off( 'dragleave', this.dragLeaveHandler ); + elem.off( 'drop', this.dropHandler ); + + if ( this.options.disableGlobalDnd ) { + $( document ).off( 'dragover', this.dragOverHandler ); + $( document ).off( 'drop', this.dropHandler ); + } + } + }); + }); + + /** + * @fileOverview FilePaste + */ + define('runtime/html5/filepaste',[ + 'base', + 'runtime/html5/runtime', + 'lib/file' + ], function( Base, Html5Runtime, File ) { + + return Html5Runtime.register( 'FilePaste', { + init: function() { + var opts = this.options, + elem = this.elem = opts.container, + accept = '.*', + arr, i, len, item; + + // accetp的mimeTypes中生成匹配正则。 + if ( opts.accept ) { + arr = []; + + for ( i = 0, len = opts.accept.length; i < len; i++ ) { + item = opts.accept[ i ].mimeTypes; + item && arr.push( item ); + } + + if ( arr.length ) { + accept = arr.join(','); + accept = accept.replace( /,/g, '|' ).replace( /\*/g, '.*' ); + } + } + this.accept = accept = new RegExp( accept, 'i' ); + this.hander = Base.bindFn( this._pasteHander, this ); + elem.on( 'paste', this.hander ); + }, + + _pasteHander: function( e ) { + var allowed = [], + ruid = this.getRuid(), + items, item, blob, i, len; + + e = e.originalEvent || e; + items = e.clipboardData.items; + + for ( i = 0, len = items.length; i < len; i++ ) { + item = items[ i ]; + + if ( item.kind !== 'file' || !(blob = item.getAsFile()) ) { + continue; + } + + allowed.push( new File( ruid, blob ) ); + } + + if ( allowed.length ) { + // 不阻止非文件粘贴(文字粘贴)的事件冒泡 + e.preventDefault(); + e.stopPropagation(); + this.trigger( 'paste', allowed ); + } + }, + + destroy: function() { + this.elem.off( 'paste', this.hander ); + } + }); + }); + + /** + * @fileOverview FilePicker + */ + define('runtime/html5/filepicker',[ + 'base', + 'runtime/html5/runtime' + ], function( Base, Html5Runtime ) { + + var $ = Base.$; + + return Html5Runtime.register( 'FilePicker', { + init: function() { + var container = this.getRuntime().getContainer(), + me = this, + owner = me.owner, + opts = me.options, + label = this.label = $( document.createElement('label') ), + input = this.input = $( document.createElement('input') ), + arr, i, len, mouseHandler; + + input.attr( 'type', 'file' ); + input.attr( 'name', opts.name ); + input.addClass('webuploader-element-invisible'); + + label.on( 'click', function() { + input.trigger('click'); + }); + + label.css({ + opacity: 0, + width: '100%', + height: '100%', + display: 'block', + cursor: 'pointer', + background: '#ffffff' + }); + + if ( opts.multiple ) { + input.attr( 'multiple', 'multiple' ); + } + + // @todo Firefox不支持单独指定后缀 + if ( opts.accept && opts.accept.length > 0 ) { + arr = []; + + for ( i = 0, len = opts.accept.length; i < len; i++ ) { + arr.push( opts.accept[ i ].mimeTypes ); + } + + input.attr( 'accept', arr.join(',') ); + } + + container.append( input ); + container.append( label ); + + mouseHandler = function( e ) { + owner.trigger( e.type ); + }; + + input.on( 'change', function( e ) { + var fn = arguments.callee, + clone; + + me.files = e.target.files; + + // reset input + clone = this.cloneNode( true ); + clone.value = null; + this.parentNode.replaceChild( clone, this ); + + input.off(); + input = $( clone ).on( 'change', fn ) + .on( 'mouseenter mouseleave', mouseHandler ); + + owner.trigger('change'); + }); + + label.on( 'mouseenter mouseleave', mouseHandler ); + + }, + + + getFiles: function() { + return this.files; + }, + + destroy: function() { + this.input.off(); + this.label.off(); + } + }); + }); + /** + * Terms: + * + * Uint8Array, FileReader, BlobBuilder, atob, ArrayBuffer + * @fileOverview Image控件 + */ + define('runtime/html5/util',[ + 'base' + ], function( Base ) { + + var urlAPI = window.createObjectURL && window || + window.URL && URL.revokeObjectURL && URL || + window.webkitURL, + createObjectURL = Base.noop, + revokeObjectURL = createObjectURL; + + if ( urlAPI ) { + + // 更安全的方式调用,比如android里面就能把context改成其他的对象。 + createObjectURL = function() { + return urlAPI.createObjectURL.apply( urlAPI, arguments ); + }; + + revokeObjectURL = function() { + return urlAPI.revokeObjectURL.apply( urlAPI, arguments ); + }; + } + + return { + createObjectURL: createObjectURL, + revokeObjectURL: revokeObjectURL, + + dataURL2Blob: function( dataURI ) { + var byteStr, intArray, ab, i, mimetype, parts; + + parts = dataURI.split(','); + + if ( ~parts[ 0 ].indexOf('base64') ) { + byteStr = atob( parts[ 1 ] ); + } else { + byteStr = decodeURIComponent( parts[ 1 ] ); + } + + ab = new ArrayBuffer( byteStr.length ); + intArray = new Uint8Array( ab ); + + for ( i = 0; i < byteStr.length; i++ ) { + intArray[ i ] = byteStr.charCodeAt( i ); + } + + mimetype = parts[ 0 ].split(':')[ 1 ].split(';')[ 0 ]; + + return this.arrayBufferToBlob( ab, mimetype ); + }, + + dataURL2ArrayBuffer: function( dataURI ) { + var byteStr, intArray, i, parts; + + parts = dataURI.split(','); + + if ( ~parts[ 0 ].indexOf('base64') ) { + byteStr = atob( parts[ 1 ] ); + } else { + byteStr = decodeURIComponent( parts[ 1 ] ); + } + + intArray = new Uint8Array( byteStr.length ); + + for ( i = 0; i < byteStr.length; i++ ) { + intArray[ i ] = byteStr.charCodeAt( i ); + } + + return intArray.buffer; + }, + + arrayBufferToBlob: function( buffer, type ) { + var builder = window.BlobBuilder || window.WebKitBlobBuilder, + bb; + + // android不支持直接new Blob, 只能借助blobbuilder. + if ( builder ) { + bb = new builder(); + bb.append( buffer ); + return bb.getBlob( type ); + } + + return new Blob([ buffer ], type ? { type: type } : {} ); + }, + + // 抽出来主要是为了解决android下面canvas.toDataUrl不支持jpeg. + // 你得到的结果是png. + canvasToDataUrl: function( canvas, type, quality ) { + return canvas.toDataURL( type, quality / 100 ); + }, + + // imagemeat会复写这个方法,如果用户选择加载那个文件了的话。 + parseMeta: function( blob, callback ) { + callback( false, {}); + }, + + // imagemeat会复写这个方法,如果用户选择加载那个文件了的话。 + updateImageHead: function( data ) { + return data; + } + }; + }); + /** + * Terms: + * + * Uint8Array, FileReader, BlobBuilder, atob, ArrayBuffer + * @fileOverview Image控件 + */ + define('runtime/html5/imagemeta',[ + 'runtime/html5/util' + ], function( Util ) { + + var api; + + api = { + parsers: { + 0xffe1: [] + }, + + maxMetaDataSize: 262144, + + parse: function( blob, cb ) { + var me = this, + fr = new FileReader(); + + fr.onload = function() { + cb( false, me._parse( this.result ) ); + fr = fr.onload = fr.onerror = null; + }; + + fr.onerror = function( e ) { + cb( e.message ); + fr = fr.onload = fr.onerror = null; + }; + + blob = blob.slice( 0, me.maxMetaDataSize ); + fr.readAsArrayBuffer( blob.getSource() ); + }, + + _parse: function( buffer, noParse ) { + if ( buffer.byteLength < 6 ) { + return; + } + + var dataview = new DataView( buffer ), + offset = 2, + maxOffset = dataview.byteLength - 4, + headLength = offset, + ret = {}, + markerBytes, markerLength, parsers, i; + + if ( dataview.getUint16( 0 ) === 0xffd8 ) { + + while ( offset < maxOffset ) { + markerBytes = dataview.getUint16( offset ); + + if ( markerBytes >= 0xffe0 && markerBytes <= 0xffef || + markerBytes === 0xfffe ) { + + markerLength = dataview.getUint16( offset + 2 ) + 2; + + if ( offset + markerLength > dataview.byteLength ) { + break; + } + + parsers = api.parsers[ markerBytes ]; + + if ( !noParse && parsers ) { + for ( i = 0; i < parsers.length; i += 1 ) { + parsers[ i ].call( api, dataview, offset, + markerLength, ret ); + } + } + + offset += markerLength; + headLength = offset; + } else { + break; + } + } + + if ( headLength > 6 ) { + if ( buffer.slice ) { + ret.imageHead = buffer.slice( 2, headLength ); + } else { + // Workaround for IE10, which does not yet + // support ArrayBuffer.slice: + ret.imageHead = new Uint8Array( buffer ) + .subarray( 2, headLength ); + } + } + } + + return ret; + }, + + updateImageHead: function( buffer, head ) { + var data = this._parse( buffer, true ), + buf1, buf2, bodyoffset; + + + bodyoffset = 2; + if ( data.imageHead ) { + bodyoffset = 2 + data.imageHead.byteLength; + } + + if ( buffer.slice ) { + buf2 = buffer.slice( bodyoffset ); + } else { + buf2 = new Uint8Array( buffer ).subarray( bodyoffset ); + } + + buf1 = new Uint8Array( head.byteLength + 2 + buf2.byteLength ); + + buf1[ 0 ] = 0xFF; + buf1[ 1 ] = 0xD8; + buf1.set( new Uint8Array( head ), 2 ); + buf1.set( new Uint8Array( buf2 ), head.byteLength + 2 ); + + return buf1.buffer; + } + }; + + Util.parseMeta = function() { + return api.parse.apply( api, arguments ); + }; + + Util.updateImageHead = function() { + return api.updateImageHead.apply( api, arguments ); + }; + + return api; + }); + /** + * 代码来自于:https://github.com/blueimp/JavaScript-Load-Image + * 暂时项目中只用了orientation. + * + * 去除了 Exif Sub IFD Pointer, GPS Info IFD Pointer, Exif Thumbnail. + * @fileOverview EXIF解析 + */ + + // Sample + // ==================================== + // Make : Apple + // Model : iPhone 4S + // Orientation : 1 + // XResolution : 72 [72/1] + // YResolution : 72 [72/1] + // ResolutionUnit : 2 + // Software : QuickTime 7.7.1 + // DateTime : 2013:09:01 22:53:55 + // ExifIFDPointer : 190 + // ExposureTime : 0.058823529411764705 [1/17] + // FNumber : 2.4 [12/5] + // ExposureProgram : Normal program + // ISOSpeedRatings : 800 + // ExifVersion : 0220 + // DateTimeOriginal : 2013:09:01 22:52:51 + // DateTimeDigitized : 2013:09:01 22:52:51 + // ComponentsConfiguration : YCbCr + // ShutterSpeedValue : 4.058893515764426 + // ApertureValue : 2.5260688216892597 [4845/1918] + // BrightnessValue : -0.3126686601998395 + // MeteringMode : Pattern + // Flash : Flash did not fire, compulsory flash mode + // FocalLength : 4.28 [107/25] + // SubjectArea : [4 values] + // FlashpixVersion : 0100 + // ColorSpace : 1 + // PixelXDimension : 2448 + // PixelYDimension : 3264 + // SensingMethod : One-chip color area sensor + // ExposureMode : 0 + // WhiteBalance : Auto white balance + // FocalLengthIn35mmFilm : 35 + // SceneCaptureType : Standard + define('runtime/html5/imagemeta/exif',[ + 'base', + 'runtime/html5/imagemeta' + ], function( Base, ImageMeta ) { + + var EXIF = {}; + + EXIF.ExifMap = function() { + return this; + }; + + EXIF.ExifMap.prototype.map = { + 'Orientation': 0x0112 + }; + + EXIF.ExifMap.prototype.get = function( id ) { + return this[ id ] || this[ this.map[ id ] ]; + }; + + EXIF.exifTagTypes = { + // byte, 8-bit unsigned int: + 1: { + getValue: function( dataView, dataOffset ) { + return dataView.getUint8( dataOffset ); + }, + size: 1 + }, + + // ascii, 8-bit byte: + 2: { + getValue: function( dataView, dataOffset ) { + return String.fromCharCode( dataView.getUint8( dataOffset ) ); + }, + size: 1, + ascii: true + }, + + // short, 16 bit int: + 3: { + getValue: function( dataView, dataOffset, littleEndian ) { + return dataView.getUint16( dataOffset, littleEndian ); + }, + size: 2 + }, + + // long, 32 bit int: + 4: { + getValue: function( dataView, dataOffset, littleEndian ) { + return dataView.getUint32( dataOffset, littleEndian ); + }, + size: 4 + }, + + // rational = two long values, + // first is numerator, second is denominator: + 5: { + getValue: function( dataView, dataOffset, littleEndian ) { + return dataView.getUint32( dataOffset, littleEndian ) / + dataView.getUint32( dataOffset + 4, littleEndian ); + }, + size: 8 + }, + + // slong, 32 bit signed int: + 9: { + getValue: function( dataView, dataOffset, littleEndian ) { + return dataView.getInt32( dataOffset, littleEndian ); + }, + size: 4 + }, + + // srational, two slongs, first is numerator, second is denominator: + 10: { + getValue: function( dataView, dataOffset, littleEndian ) { + return dataView.getInt32( dataOffset, littleEndian ) / + dataView.getInt32( dataOffset + 4, littleEndian ); + }, + size: 8 + } + }; + + // undefined, 8-bit byte, value depending on field: + EXIF.exifTagTypes[ 7 ] = EXIF.exifTagTypes[ 1 ]; + + EXIF.getExifValue = function( dataView, tiffOffset, offset, type, length, + littleEndian ) { + + var tagType = EXIF.exifTagTypes[ type ], + tagSize, dataOffset, values, i, str, c; + + if ( !tagType ) { + Base.log('Invalid Exif data: Invalid tag type.'); + return; + } + + tagSize = tagType.size * length; + + // Determine if the value is contained in the dataOffset bytes, + // or if the value at the dataOffset is a pointer to the actual data: + dataOffset = tagSize > 4 ? tiffOffset + dataView.getUint32( offset + 8, + littleEndian ) : (offset + 8); + + if ( dataOffset + tagSize > dataView.byteLength ) { + Base.log('Invalid Exif data: Invalid data offset.'); + return; + } + + if ( length === 1 ) { + return tagType.getValue( dataView, dataOffset, littleEndian ); + } + + values = []; + + for ( i = 0; i < length; i += 1 ) { + values[ i ] = tagType.getValue( dataView, + dataOffset + i * tagType.size, littleEndian ); + } + + if ( tagType.ascii ) { + str = ''; + + // Concatenate the chars: + for ( i = 0; i < values.length; i += 1 ) { + c = values[ i ]; + + // Ignore the terminating NULL byte(s): + if ( c === '\u0000' ) { + break; + } + str += c; + } + + return str; + } + return values; + }; + + EXIF.parseExifTag = function( dataView, tiffOffset, offset, littleEndian, + data ) { + + var tag = dataView.getUint16( offset, littleEndian ); + data.exif[ tag ] = EXIF.getExifValue( dataView, tiffOffset, offset, + dataView.getUint16( offset + 2, littleEndian ), // tag type + dataView.getUint32( offset + 4, littleEndian ), // tag length + littleEndian ); + }; + + EXIF.parseExifTags = function( dataView, tiffOffset, dirOffset, + littleEndian, data ) { + + var tagsNumber, dirEndOffset, i; + + if ( dirOffset + 6 > dataView.byteLength ) { + Base.log('Invalid Exif data: Invalid directory offset.'); + return; + } + + tagsNumber = dataView.getUint16( dirOffset, littleEndian ); + dirEndOffset = dirOffset + 2 + 12 * tagsNumber; + + if ( dirEndOffset + 4 > dataView.byteLength ) { + Base.log('Invalid Exif data: Invalid directory size.'); + return; + } + + for ( i = 0; i < tagsNumber; i += 1 ) { + this.parseExifTag( dataView, tiffOffset, + dirOffset + 2 + 12 * i, // tag offset + littleEndian, data ); + } + + // Return the offset to the next directory: + return dataView.getUint32( dirEndOffset, littleEndian ); + }; + + // EXIF.getExifThumbnail = function(dataView, offset, length) { + // var hexData, + // i, + // b; + // if (!length || offset + length > dataView.byteLength) { + // Base.log('Invalid Exif data: Invalid thumbnail data.'); + // return; + // } + // hexData = []; + // for (i = 0; i < length; i += 1) { + // b = dataView.getUint8(offset + i); + // hexData.push((b < 16 ? '0' : '') + b.toString(16)); + // } + // return 'data:image/jpeg,%' + hexData.join('%'); + // }; + + EXIF.parseExifData = function( dataView, offset, length, data ) { + + var tiffOffset = offset + 10, + littleEndian, dirOffset; + + // Check for the ASCII code for "Exif" (0x45786966): + if ( dataView.getUint32( offset + 4 ) !== 0x45786966 ) { + // No Exif data, might be XMP data instead + return; + } + if ( tiffOffset + 8 > dataView.byteLength ) { + Base.log('Invalid Exif data: Invalid segment size.'); + return; + } + + // Check for the two null bytes: + if ( dataView.getUint16( offset + 8 ) !== 0x0000 ) { + Base.log('Invalid Exif data: Missing byte alignment offset.'); + return; + } + + // Check the byte alignment: + switch ( dataView.getUint16( tiffOffset ) ) { + case 0x4949: + littleEndian = true; + break; + + case 0x4D4D: + littleEndian = false; + break; + + default: + Base.log('Invalid Exif data: Invalid byte alignment marker.'); + return; + } + + // Check for the TIFF tag marker (0x002A): + if ( dataView.getUint16( tiffOffset + 2, littleEndian ) !== 0x002A ) { + Base.log('Invalid Exif data: Missing TIFF marker.'); + return; + } + + // Retrieve the directory offset bytes, usually 0x00000008 or 8 decimal: + dirOffset = dataView.getUint32( tiffOffset + 4, littleEndian ); + // Create the exif object to store the tags: + data.exif = new EXIF.ExifMap(); + // Parse the tags of the main image directory and retrieve the + // offset to the next directory, usually the thumbnail directory: + dirOffset = EXIF.parseExifTags( dataView, tiffOffset, + tiffOffset + dirOffset, littleEndian, data ); + + // 尝试读取缩略图 + // if ( dirOffset ) { + // thumbnailData = {exif: {}}; + // dirOffset = EXIF.parseExifTags( + // dataView, + // tiffOffset, + // tiffOffset + dirOffset, + // littleEndian, + // thumbnailData + // ); + + // // Check for JPEG Thumbnail offset: + // if (thumbnailData.exif[0x0201]) { + // data.exif.Thumbnail = EXIF.getExifThumbnail( + // dataView, + // tiffOffset + thumbnailData.exif[0x0201], + // thumbnailData.exif[0x0202] // Thumbnail data length + // ); + // } + // } + }; + + ImageMeta.parsers[ 0xffe1 ].push( EXIF.parseExifData ); + return EXIF; + }); + /** + * 这个方式性能不行,但是可以解决android里面的toDataUrl的bug + * android里面toDataUrl('image/jpege')得到的结果却是png. + * + * 所以这里没辙,只能借助这个工具 + * @fileOverview jpeg encoder + */ + define('runtime/html5/jpegencoder',[], function( require, exports, module ) { + + /* + Copyright (c) 2008, Adobe Systems Incorporated + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of Adobe Systems Incorporated nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + /* + JPEG encoder ported to JavaScript and optimized by Andreas Ritter, www.bytestrom.eu, 11/2009 + + Basic GUI blocking jpeg encoder + */ + + function JPEGEncoder(quality) { + var self = this; + var fround = Math.round; + var ffloor = Math.floor; + var YTable = new Array(64); + var UVTable = new Array(64); + var fdtbl_Y = new Array(64); + var fdtbl_UV = new Array(64); + var YDC_HT; + var UVDC_HT; + var YAC_HT; + var UVAC_HT; + + var bitcode = new Array(65535); + var category = new Array(65535); + var outputfDCTQuant = new Array(64); + var DU = new Array(64); + var byteout = []; + var bytenew = 0; + var bytepos = 7; + + var YDU = new Array(64); + var UDU = new Array(64); + var VDU = new Array(64); + var clt = new Array(256); + var RGB_YUV_TABLE = new Array(2048); + var currentQuality; + + var ZigZag = [ + 0, 1, 5, 6,14,15,27,28, + 2, 4, 7,13,16,26,29,42, + 3, 8,12,17,25,30,41,43, + 9,11,18,24,31,40,44,53, + 10,19,23,32,39,45,52,54, + 20,22,33,38,46,51,55,60, + 21,34,37,47,50,56,59,61, + 35,36,48,49,57,58,62,63 + ]; + + var std_dc_luminance_nrcodes = [0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0]; + var std_dc_luminance_values = [0,1,2,3,4,5,6,7,8,9,10,11]; + var std_ac_luminance_nrcodes = [0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d]; + var std_ac_luminance_values = [ + 0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12, + 0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07, + 0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08, + 0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0, + 0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16, + 0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28, + 0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39, + 0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49, + 0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59, + 0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69, + 0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79, + 0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89, + 0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98, + 0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7, + 0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6, + 0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5, + 0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4, + 0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2, + 0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea, + 0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8, + 0xf9,0xfa + ]; + + var std_dc_chrominance_nrcodes = [0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0]; + var std_dc_chrominance_values = [0,1,2,3,4,5,6,7,8,9,10,11]; + var std_ac_chrominance_nrcodes = [0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77]; + var std_ac_chrominance_values = [ + 0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21, + 0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71, + 0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91, + 0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0, + 0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34, + 0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26, + 0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38, + 0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48, + 0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58, + 0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68, + 0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78, + 0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87, + 0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96, + 0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5, + 0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4, + 0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3, + 0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2, + 0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda, + 0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9, + 0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8, + 0xf9,0xfa + ]; + + function initQuantTables(sf){ + var YQT = [ + 16, 11, 10, 16, 24, 40, 51, 61, + 12, 12, 14, 19, 26, 58, 60, 55, + 14, 13, 16, 24, 40, 57, 69, 56, + 14, 17, 22, 29, 51, 87, 80, 62, + 18, 22, 37, 56, 68,109,103, 77, + 24, 35, 55, 64, 81,104,113, 92, + 49, 64, 78, 87,103,121,120,101, + 72, 92, 95, 98,112,100,103, 99 + ]; + + for (var i = 0; i < 64; i++) { + var t = ffloor((YQT[i]*sf+50)/100); + if (t < 1) { + t = 1; + } else if (t > 255) { + t = 255; + } + YTable[ZigZag[i]] = t; + } + var UVQT = [ + 17, 18, 24, 47, 99, 99, 99, 99, + 18, 21, 26, 66, 99, 99, 99, 99, + 24, 26, 56, 99, 99, 99, 99, 99, + 47, 66, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99 + ]; + for (var j = 0; j < 64; j++) { + var u = ffloor((UVQT[j]*sf+50)/100); + if (u < 1) { + u = 1; + } else if (u > 255) { + u = 255; + } + UVTable[ZigZag[j]] = u; + } + var aasf = [ + 1.0, 1.387039845, 1.306562965, 1.175875602, + 1.0, 0.785694958, 0.541196100, 0.275899379 + ]; + var k = 0; + for (var row = 0; row < 8; row++) + { + for (var col = 0; col < 8; col++) + { + fdtbl_Y[k] = (1.0 / (YTable [ZigZag[k]] * aasf[row] * aasf[col] * 8.0)); + fdtbl_UV[k] = (1.0 / (UVTable[ZigZag[k]] * aasf[row] * aasf[col] * 8.0)); + k++; + } + } + } + + function computeHuffmanTbl(nrcodes, std_table){ + var codevalue = 0; + var pos_in_table = 0; + var HT = new Array(); + for (var k = 1; k <= 16; k++) { + for (var j = 1; j <= nrcodes[k]; j++) { + HT[std_table[pos_in_table]] = []; + HT[std_table[pos_in_table]][0] = codevalue; + HT[std_table[pos_in_table]][1] = k; + pos_in_table++; + codevalue++; + } + codevalue*=2; + } + return HT; + } + + function initHuffmanTbl() + { + YDC_HT = computeHuffmanTbl(std_dc_luminance_nrcodes,std_dc_luminance_values); + UVDC_HT = computeHuffmanTbl(std_dc_chrominance_nrcodes,std_dc_chrominance_values); + YAC_HT = computeHuffmanTbl(std_ac_luminance_nrcodes,std_ac_luminance_values); + UVAC_HT = computeHuffmanTbl(std_ac_chrominance_nrcodes,std_ac_chrominance_values); + } + + function initCategoryNumber() + { + var nrlower = 1; + var nrupper = 2; + for (var cat = 1; cat <= 15; cat++) { + //Positive numbers + for (var nr = nrlower; nr>0] = 38470 * i; + RGB_YUV_TABLE[(i+ 512)>>0] = 7471 * i + 0x8000; + RGB_YUV_TABLE[(i+ 768)>>0] = -11059 * i; + RGB_YUV_TABLE[(i+1024)>>0] = -21709 * i; + RGB_YUV_TABLE[(i+1280)>>0] = 32768 * i + 0x807FFF; + RGB_YUV_TABLE[(i+1536)>>0] = -27439 * i; + RGB_YUV_TABLE[(i+1792)>>0] = - 5329 * i; + } + } + + // IO functions + function writeBits(bs) + { + var value = bs[0]; + var posval = bs[1]-1; + while ( posval >= 0 ) { + if (value & (1 << posval) ) { + bytenew |= (1 << bytepos); + } + posval--; + bytepos--; + if (bytepos < 0) { + if (bytenew == 0xFF) { + writeByte(0xFF); + writeByte(0); + } + else { + writeByte(bytenew); + } + bytepos=7; + bytenew=0; + } + } + } + + function writeByte(value) + { + byteout.push(clt[value]); // write char directly instead of converting later + } + + function writeWord(value) + { + writeByte((value>>8)&0xFF); + writeByte((value )&0xFF); + } + + // DCT & quantization core + function fDCTQuant(data, fdtbl) + { + var d0, d1, d2, d3, d4, d5, d6, d7; + /* Pass 1: process rows. */ + var dataOff=0; + var i; + var I8 = 8; + var I64 = 64; + for (i=0; i 0.0) ? ((fDCTQuant + 0.5)|0) : ((fDCTQuant - 0.5)|0); + //outputfDCTQuant[i] = fround(fDCTQuant); + + } + return outputfDCTQuant; + } + + function writeAPP0() + { + writeWord(0xFFE0); // marker + writeWord(16); // length + writeByte(0x4A); // J + writeByte(0x46); // F + writeByte(0x49); // I + writeByte(0x46); // F + writeByte(0); // = "JFIF",'\0' + writeByte(1); // versionhi + writeByte(1); // versionlo + writeByte(0); // xyunits + writeWord(1); // xdensity + writeWord(1); // ydensity + writeByte(0); // thumbnwidth + writeByte(0); // thumbnheight + } + + function writeSOF0(width, height) + { + writeWord(0xFFC0); // marker + writeWord(17); // length, truecolor YUV JPG + writeByte(8); // precision + writeWord(height); + writeWord(width); + writeByte(3); // nrofcomponents + writeByte(1); // IdY + writeByte(0x11); // HVY + writeByte(0); // QTY + writeByte(2); // IdU + writeByte(0x11); // HVU + writeByte(1); // QTU + writeByte(3); // IdV + writeByte(0x11); // HVV + writeByte(1); // QTV + } + + function writeDQT() + { + writeWord(0xFFDB); // marker + writeWord(132); // length + writeByte(0); + for (var i=0; i<64; i++) { + writeByte(YTable[i]); + } + writeByte(1); + for (var j=0; j<64; j++) { + writeByte(UVTable[j]); + } + } + + function writeDHT() + { + writeWord(0xFFC4); // marker + writeWord(0x01A2); // length + + writeByte(0); // HTYDCinfo + for (var i=0; i<16; i++) { + writeByte(std_dc_luminance_nrcodes[i+1]); + } + for (var j=0; j<=11; j++) { + writeByte(std_dc_luminance_values[j]); + } + + writeByte(0x10); // HTYACinfo + for (var k=0; k<16; k++) { + writeByte(std_ac_luminance_nrcodes[k+1]); + } + for (var l=0; l<=161; l++) { + writeByte(std_ac_luminance_values[l]); + } + + writeByte(1); // HTUDCinfo + for (var m=0; m<16; m++) { + writeByte(std_dc_chrominance_nrcodes[m+1]); + } + for (var n=0; n<=11; n++) { + writeByte(std_dc_chrominance_values[n]); + } + + writeByte(0x11); // HTUACinfo + for (var o=0; o<16; o++) { + writeByte(std_ac_chrominance_nrcodes[o+1]); + } + for (var p=0; p<=161; p++) { + writeByte(std_ac_chrominance_values[p]); + } + } + + function writeSOS() + { + writeWord(0xFFDA); // marker + writeWord(12); // length + writeByte(3); // nrofcomponents + writeByte(1); // IdY + writeByte(0); // HTY + writeByte(2); // IdU + writeByte(0x11); // HTU + writeByte(3); // IdV + writeByte(0x11); // HTV + writeByte(0); // Ss + writeByte(0x3f); // Se + writeByte(0); // Bf + } + + function processDU(CDU, fdtbl, DC, HTDC, HTAC){ + var EOB = HTAC[0x00]; + var M16zeroes = HTAC[0xF0]; + var pos; + var I16 = 16; + var I63 = 63; + var I64 = 64; + var DU_DCT = fDCTQuant(CDU, fdtbl); + //ZigZag reorder + for (var j=0;j0)&&(DU[end0pos]==0); end0pos--) {}; + //end0pos = first element in reverse order !=0 + if ( end0pos == 0) { + writeBits(EOB); + return DC; + } + var i = 1; + var lng; + while ( i <= end0pos ) { + var startpos = i; + for (; (DU[i]==0) && (i<=end0pos); ++i) {} + var nrzeroes = i-startpos; + if ( nrzeroes >= I16 ) { + lng = nrzeroes>>4; + for (var nrmarker=1; nrmarker <= lng; ++nrmarker) + writeBits(M16zeroes); + nrzeroes = nrzeroes&0xF; + } + pos = 32767+DU[i]; + writeBits(HTAC[(nrzeroes<<4)+category[pos]]); + writeBits(bitcode[pos]); + i++; + } + if ( end0pos != I63 ) { + writeBits(EOB); + } + return DC; + } + + function initCharLookupTable(){ + var sfcc = String.fromCharCode; + for(var i=0; i < 256; i++){ ///// ACHTUNG // 255 + clt[i] = sfcc(i); + } + } + + this.encode = function(image,quality) // image data object + { + // var time_start = new Date().getTime(); + + if(quality) setQuality(quality); + + // Initialize bit writer + byteout = new Array(); + bytenew=0; + bytepos=7; + + // Add JPEG headers + writeWord(0xFFD8); // SOI + writeAPP0(); + writeDQT(); + writeSOF0(image.width,image.height); + writeDHT(); + writeSOS(); + + + // Encode 8x8 macroblocks + var DCY=0; + var DCU=0; + var DCV=0; + + bytenew=0; + bytepos=7; + + + this.encode.displayName = "_encode_"; + + var imageData = image.data; + var width = image.width; + var height = image.height; + + var quadWidth = width*4; + var tripleWidth = width*3; + + var x, y = 0; + var r, g, b; + var start,p, col,row,pos; + while(y < height){ + x = 0; + while(x < quadWidth){ + start = quadWidth * y + x; + p = start; + col = -1; + row = 0; + + for(pos=0; pos < 64; pos++){ + row = pos >> 3;// /8 + col = ( pos & 7 ) * 4; // %8 + p = start + ( row * quadWidth ) + col; + + if(y+row >= height){ // padding bottom + p-= (quadWidth*(y+1+row-height)); + } + + if(x+col >= quadWidth){ // padding right + p-= ((x+col) - quadWidth +4) + } + + r = imageData[ p++ ]; + g = imageData[ p++ ]; + b = imageData[ p++ ]; + + + /* // calculate YUV values dynamically + YDU[pos]=((( 0.29900)*r+( 0.58700)*g+( 0.11400)*b))-128; //-0x80 + UDU[pos]=(((-0.16874)*r+(-0.33126)*g+( 0.50000)*b)); + VDU[pos]=((( 0.50000)*r+(-0.41869)*g+(-0.08131)*b)); + */ + + // use lookup table (slightly faster) + YDU[pos] = ((RGB_YUV_TABLE[r] + RGB_YUV_TABLE[(g + 256)>>0] + RGB_YUV_TABLE[(b + 512)>>0]) >> 16)-128; + UDU[pos] = ((RGB_YUV_TABLE[(r + 768)>>0] + RGB_YUV_TABLE[(g + 1024)>>0] + RGB_YUV_TABLE[(b + 1280)>>0]) >> 16)-128; + VDU[pos] = ((RGB_YUV_TABLE[(r + 1280)>>0] + RGB_YUV_TABLE[(g + 1536)>>0] + RGB_YUV_TABLE[(b + 1792)>>0]) >> 16)-128; + + } + + DCY = processDU(YDU, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCU = processDU(UDU, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); + DCV = processDU(VDU, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); + x+=32; + } + y+=8; + } + + + //////////////////////////////////////////////////////////////// + + // Do the bit alignment of the EOI marker + if ( bytepos >= 0 ) { + var fillbits = []; + fillbits[1] = bytepos+1; + fillbits[0] = (1<<(bytepos+1))-1; + writeBits(fillbits); + } + + writeWord(0xFFD9); //EOI + + var jpegDataUri = 'data:image/jpeg;base64,' + btoa(byteout.join('')); + + byteout = []; + + // benchmarking + // var duration = new Date().getTime() - time_start; + // console.log('Encoding time: '+ currentQuality + 'ms'); + // + + return jpegDataUri + } + + function setQuality(quality){ + if (quality <= 0) { + quality = 1; + } + if (quality > 100) { + quality = 100; + } + + if(currentQuality == quality) return // don't recalc if unchanged + + var sf = 0; + if (quality < 50) { + sf = Math.floor(5000 / quality); + } else { + sf = Math.floor(200 - quality*2); + } + + initQuantTables(sf); + currentQuality = quality; + // console.log('Quality set to: '+quality +'%'); + } + + function init(){ + // var time_start = new Date().getTime(); + if(!quality) quality = 50; + // Create tables + initCharLookupTable() + initHuffmanTbl(); + initCategoryNumber(); + initRGBYUVTable(); + + setQuality(quality); + // var duration = new Date().getTime() - time_start; + // console.log('Initialization '+ duration + 'ms'); + } + + init(); + + }; + + JPEGEncoder.encode = function( data, quality ) { + var encoder = new JPEGEncoder( quality ); + + return encoder.encode( data ); + } + + return JPEGEncoder; + }); + /** + * @fileOverview Fix android canvas.toDataUrl bug. + */ + define('runtime/html5/androidpatch',[ + 'runtime/html5/util', + 'runtime/html5/jpegencoder', + 'base' + ], function( Util, encoder, Base ) { + var origin = Util.canvasToDataUrl, + supportJpeg; + + Util.canvasToDataUrl = function( canvas, type, quality ) { + var ctx, w, h, fragement, parts; + + // 非android手机直接跳过。 + if ( !Base.os.android ) { + return origin.apply( null, arguments ); + } + + // 检测是否canvas支持jpeg导出,根据数据格式来判断。 + // JPEG 前两位分别是:255, 216 + if ( type === 'image/jpeg' && typeof supportJpeg === 'undefined' ) { + fragement = origin.apply( null, arguments ); + + parts = fragement.split(','); + + if ( ~parts[ 0 ].indexOf('base64') ) { + fragement = atob( parts[ 1 ] ); + } else { + fragement = decodeURIComponent( parts[ 1 ] ); + } + + fragement = fragement.substring( 0, 2 ); + + supportJpeg = fragement.charCodeAt( 0 ) === 255 && + fragement.charCodeAt( 1 ) === 216; + } + + // 只有在android环境下才修复 + if ( type === 'image/jpeg' && !supportJpeg ) { + w = canvas.width; + h = canvas.height; + ctx = canvas.getContext('2d'); + + return encoder.encode( ctx.getImageData( 0, 0, w, h ), quality ); + } + + return origin.apply( null, arguments ); + }; + }); + /** + * @fileOverview Image + */ + define('runtime/html5/image',[ + 'base', + 'runtime/html5/runtime', + 'runtime/html5/util' + ], function( Base, Html5Runtime, Util ) { + + var BLANK = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs%3D'; + + return Html5Runtime.register( 'Image', { + + // flag: 标记是否被修改过。 + modified: false, + + init: function() { + var me = this, + img = new Image(); + + img.onload = function() { + + me._info = { + type: me.type, + width: this.width, + height: this.height + }; + + // 读取meta信息。 + if ( !me._metas && 'image/jpeg' === me.type ) { + Util.parseMeta( me._blob, function( error, ret ) { + me._metas = ret; + me.owner.trigger('load'); + }); + } else { + me.owner.trigger('load'); + } + }; + + img.onerror = function() { + me.owner.trigger('error'); + }; + + me._img = img; + }, + + loadFromBlob: function( blob ) { + var me = this, + img = me._img; + + me._blob = blob; + me.type = blob.type; + img.src = Util.createObjectURL( blob.getSource() ); + me.owner.once( 'load', function() { + Util.revokeObjectURL( img.src ); + }); + }, + + resize: function( width, height ) { + var canvas = this._canvas || + (this._canvas = document.createElement('canvas')); + + this._resize( this._img, canvas, width, height ); + this._blob = null; // 没用了,可以删掉了。 + this.modified = true; + this.owner.trigger( 'complete', 'resize' ); + }, + + crop: function( x, y, w, h, s ) { + var cvs = this._canvas || + (this._canvas = document.createElement('canvas')), + opts = this.options, + img = this._img, + iw = img.naturalWidth, + ih = img.naturalHeight, + orientation = this.getOrientation(); + + s = s || 1; + + // todo 解决 orientation 的问题。 + // values that require 90 degree rotation + // if ( ~[ 5, 6, 7, 8 ].indexOf( orientation ) ) { + + // switch ( orientation ) { + // case 6: + // tmp = x; + // x = y; + // y = iw * s - tmp - w; + // console.log(ih * s, tmp, w) + // break; + // } + + // (w ^= h, h ^= w, w ^= h); + // } + + cvs.width = w; + cvs.height = h; + + opts.preserveHeaders || this._rotate2Orientaion( cvs, orientation ); + this._renderImageToCanvas( cvs, img, -x, -y, iw * s, ih * s ); + + this._blob = null; // 没用了,可以删掉了。 + this.modified = true; + this.owner.trigger( 'complete', 'crop' ); + }, + + getAsBlob: function( type ) { + var blob = this._blob, + opts = this.options, + canvas; + + type = type || this.type; + + // blob需要重新生成。 + if ( this.modified || this.type !== type ) { + canvas = this._canvas; + + if ( type === 'image/jpeg' ) { + + blob = Util.canvasToDataUrl( canvas, type, opts.quality ); + + if ( opts.preserveHeaders && this._metas && + this._metas.imageHead ) { + + blob = Util.dataURL2ArrayBuffer( blob ); + blob = Util.updateImageHead( blob, + this._metas.imageHead ); + blob = Util.arrayBufferToBlob( blob, type ); + return blob; + } + } else { + blob = Util.canvasToDataUrl( canvas, type ); + } + + blob = Util.dataURL2Blob( blob ); + } + + return blob; + }, + + getAsDataUrl: function( type ) { + var opts = this.options; + + type = type || this.type; + + if ( type === 'image/jpeg' ) { + return Util.canvasToDataUrl( this._canvas, type, opts.quality ); + } else { + return this._canvas.toDataURL( type ); + } + }, + + getOrientation: function() { + return this._metas && this._metas.exif && + this._metas.exif.get('Orientation') || 1; + }, + + info: function( val ) { + + // setter + if ( val ) { + this._info = val; + return this; + } + + // getter + return this._info; + }, + + meta: function( val ) { + + // setter + if ( val ) { + this._meta = val; + return this; + } + + // getter + return this._meta; + }, + + destroy: function() { + var canvas = this._canvas; + this._img.onload = null; + + if ( canvas ) { + canvas.getContext('2d') + .clearRect( 0, 0, canvas.width, canvas.height ); + canvas.width = canvas.height = 0; + this._canvas = null; + } + + // 释放内存。非常重要,否则释放不了image的内存。 + this._img.src = BLANK; + this._img = this._blob = null; + }, + + _resize: function( img, cvs, width, height ) { + var opts = this.options, + naturalWidth = img.width, + naturalHeight = img.height, + orientation = this.getOrientation(), + scale, w, h, x, y; + + // values that require 90 degree rotation + if ( ~[ 5, 6, 7, 8 ].indexOf( orientation ) ) { + + // 交换width, height的值。 + width ^= height; + height ^= width; + width ^= height; + } + + scale = Math[ opts.crop ? 'max' : 'min' ]( width / naturalWidth, + height / naturalHeight ); + + // 不允许放大。 + opts.allowMagnify || (scale = Math.min( 1, scale )); + + w = naturalWidth * scale; + h = naturalHeight * scale; + + if ( opts.crop ) { + cvs.width = width; + cvs.height = height; + } else { + cvs.width = w; + cvs.height = h; + } + + x = (cvs.width - w) / 2; + y = (cvs.height - h) / 2; + + opts.preserveHeaders || this._rotate2Orientaion( cvs, orientation ); + + this._renderImageToCanvas( cvs, img, x, y, w, h ); + }, + + _rotate2Orientaion: function( canvas, orientation ) { + var width = canvas.width, + height = canvas.height, + ctx = canvas.getContext('2d'); + + switch ( orientation ) { + case 5: + case 6: + case 7: + case 8: + canvas.width = height; + canvas.height = width; + break; + } + + switch ( orientation ) { + case 2: // horizontal flip + ctx.translate( width, 0 ); + ctx.scale( -1, 1 ); + break; + + case 3: // 180 rotate left + ctx.translate( width, height ); + ctx.rotate( Math.PI ); + break; + + case 4: // vertical flip + ctx.translate( 0, height ); + ctx.scale( 1, -1 ); + break; + + case 5: // vertical flip + 90 rotate right + ctx.rotate( 0.5 * Math.PI ); + ctx.scale( 1, -1 ); + break; + + case 6: // 90 rotate right + ctx.rotate( 0.5 * Math.PI ); + ctx.translate( 0, -height ); + break; + + case 7: // horizontal flip + 90 rotate right + ctx.rotate( 0.5 * Math.PI ); + ctx.translate( width, -height ); + ctx.scale( -1, 1 ); + break; + + case 8: // 90 rotate left + ctx.rotate( -0.5 * Math.PI ); + ctx.translate( -width, 0 ); + break; + } + }, + + // https://github.com/stomita/ios-imagefile-megapixel/ + // blob/master/src/megapix-image.js + _renderImageToCanvas: (function() { + + // 如果不是ios, 不需要这么复杂! + if ( !Base.os.ios ) { + return function( canvas ) { + var args = Base.slice( arguments, 1 ), + ctx = canvas.getContext('2d'); + + ctx.drawImage.apply( ctx, args ); + }; + } + + /** + * Detecting vertical squash in loaded image. + * Fixes a bug which squash image vertically while drawing into + * canvas for some images. + */ + function detectVerticalSquash( img, iw, ih ) { + var canvas = document.createElement('canvas'), + ctx = canvas.getContext('2d'), + sy = 0, + ey = ih, + py = ih, + data, alpha, ratio; + + + canvas.width = 1; + canvas.height = ih; + ctx.drawImage( img, 0, 0 ); + data = ctx.getImageData( 0, 0, 1, ih ).data; + + // search image edge pixel position in case + // it is squashed vertically. + while ( py > sy ) { + alpha = data[ (py - 1) * 4 + 3 ]; + + if ( alpha === 0 ) { + ey = py; + } else { + sy = py; + } + + py = (ey + sy) >> 1; + } + + ratio = (py / ih); + return (ratio === 0) ? 1 : ratio; + } + + // fix ie7 bug + // http://stackoverflow.com/questions/11929099/ + // html5-canvas-drawimage-ratio-bug-ios + if ( Base.os.ios >= 7 ) { + return function( canvas, img, x, y, w, h ) { + var iw = img.naturalWidth, + ih = img.naturalHeight, + vertSquashRatio = detectVerticalSquash( img, iw, ih ); + + return canvas.getContext('2d').drawImage( img, 0, 0, + iw * vertSquashRatio, ih * vertSquashRatio, + x, y, w, h ); + }; + } + + /** + * Detect subsampling in loaded image. + * In iOS, larger images than 2M pixels may be + * subsampled in rendering. + */ + function detectSubsampling( img ) { + var iw = img.naturalWidth, + ih = img.naturalHeight, + canvas, ctx; + + // subsampling may happen overmegapixel image + if ( iw * ih > 1024 * 1024 ) { + canvas = document.createElement('canvas'); + canvas.width = canvas.height = 1; + ctx = canvas.getContext('2d'); + ctx.drawImage( img, -iw + 1, 0 ); + + // subsampled image becomes half smaller in rendering size. + // check alpha channel value to confirm image is covering + // edge pixel or not. if alpha value is 0 + // image is not covering, hence subsampled. + return ctx.getImageData( 0, 0, 1, 1 ).data[ 3 ] === 0; + } else { + return false; + } + } + + + return function( canvas, img, x, y, width, height ) { + var iw = img.naturalWidth, + ih = img.naturalHeight, + ctx = canvas.getContext('2d'), + subsampled = detectSubsampling( img ), + doSquash = this.type === 'image/jpeg', + d = 1024, + sy = 0, + dy = 0, + tmpCanvas, tmpCtx, vertSquashRatio, dw, dh, sx, dx; + + if ( subsampled ) { + iw /= 2; + ih /= 2; + } + + ctx.save(); + tmpCanvas = document.createElement('canvas'); + tmpCanvas.width = tmpCanvas.height = d; + + tmpCtx = tmpCanvas.getContext('2d'); + vertSquashRatio = doSquash ? + detectVerticalSquash( img, iw, ih ) : 1; + + dw = Math.ceil( d * width / iw ); + dh = Math.ceil( d * height / ih / vertSquashRatio ); + + while ( sy < ih ) { + sx = 0; + dx = 0; + while ( sx < iw ) { + tmpCtx.clearRect( 0, 0, d, d ); + tmpCtx.drawImage( img, -sx, -sy ); + ctx.drawImage( tmpCanvas, 0, 0, d, d, + x + dx, y + dy, dw, dh ); + sx += d; + dx += dw; + } + sy += d; + dy += dh; + } + ctx.restore(); + tmpCanvas = tmpCtx = null; + }; + })() + }); + }); + /** + * @fileOverview Transport + * @todo 支持chunked传输,优势: + * 可以将大文件分成小块,挨个传输,可以提高大文件成功率,当失败的时候,也只需要重传那小部分, + * 而不需要重头再传一次。另外断点续传也需要用chunked方式。 + */ + define('runtime/html5/transport',[ + 'base', + 'runtime/html5/runtime' + ], function( Base, Html5Runtime ) { + + var noop = Base.noop, + $ = Base.$; + + return Html5Runtime.register( 'Transport', { + init: function() { + this._status = 0; + this._response = null; + }, + + send: function() { + var owner = this.owner, + opts = this.options, + xhr = this._initAjax(), + blob = owner._blob, + server = opts.server, + formData, binary, fr; + + if ( opts.sendAsBinary ) { + server += (/\?/.test( server ) ? '&' : '?') + + $.param( owner._formData ); + + binary = blob.getSource(); + } else { + formData = new FormData(); + $.each( owner._formData, function( k, v ) { + formData.append( k, v ); + }); + + formData.append( opts.fileVal, blob.getSource(), + opts.filename || owner._formData.name || '' ); + } + + if ( opts.withCredentials && 'withCredentials' in xhr ) { + xhr.open( opts.method, server, true ); + xhr.withCredentials = true; + } else { + xhr.open( opts.method, server ); + } + + this._setRequestHeader( xhr, opts.headers ); + + if ( binary ) { + // 强制设置成 content-type 为文件流。 + xhr.overrideMimeType && + xhr.overrideMimeType('application/octet-stream'); + + // android直接发送blob会导致服务端接收到的是空文件。 + // bug详情。 + // https://code.google.com/p/android/issues/detail?id=39882 + // 所以先用fileReader读取出来再通过arraybuffer的方式发送。 + if ( Base.os.android ) { + fr = new FileReader(); + + fr.onload = function() { + xhr.send( this.result ); + fr = fr.onload = null; + }; + + fr.readAsArrayBuffer( binary ); + } else { + xhr.send( binary ); + } + } else { + xhr.send( formData ); + } + }, + + getResponse: function() { + return this._response; + }, + + getResponseAsJson: function() { + return this._parseJson( this._response ); + }, + + getStatus: function() { + return this._status; + }, + + abort: function() { + var xhr = this._xhr; + + if ( xhr ) { + xhr.upload.onprogress = noop; + xhr.onreadystatechange = noop; + xhr.abort(); + + this._xhr = xhr = null; + } + }, + + destroy: function() { + this.abort(); + }, + + _initAjax: function() { + var me = this, + xhr = new XMLHttpRequest(), + opts = this.options; + + if ( opts.withCredentials && !('withCredentials' in xhr) && + typeof XDomainRequest !== 'undefined' ) { + xhr = new XDomainRequest(); + } + + xhr.upload.onprogress = function( e ) { + var percentage = 0; + + if ( e.lengthComputable ) { + percentage = e.loaded / e.total; + } + + return me.trigger( 'progress', percentage ); + }; + + xhr.onreadystatechange = function() { + + if ( xhr.readyState !== 4 ) { + return; + } + + xhr.upload.onprogress = noop; + xhr.onreadystatechange = noop; + me._xhr = null; + me._status = xhr.status; + + if ( xhr.status >= 200 && xhr.status < 300 ) { + me._response = xhr.responseText; + return me.trigger('load'); + } else if ( xhr.status >= 500 && xhr.status < 600 ) { + me._response = xhr.responseText; + return me.trigger( 'error', 'server' ); + } + + + return me.trigger( 'error', me._status ? 'http' : 'abort' ); + }; + + me._xhr = xhr; + return xhr; + }, + + _setRequestHeader: function( xhr, headers ) { + $.each( headers, function( key, val ) { + xhr.setRequestHeader( key, val ); + }); + }, + + _parseJson: function( str ) { + var json; + + try { + json = JSON.parse( str ); + } catch ( ex ) { + json = {}; + } + + return json; + } + }); + }); + /** + * @fileOverview Transport flash实现 + */ + define('runtime/html5/md5',[ + 'runtime/html5/runtime' + ], function( FlashRuntime ) { + + /* + * Fastest md5 implementation around (JKM md5) + * Credits: Joseph Myers + * + * @see http://www.myersdaily.org/joseph/javascript/md5-text.html + * @see http://jsperf.com/md5-shootout/7 + */ + + /* this function is much faster, + so if possible we use it. Some IEs + are the only ones I know of that + need the idiotic second function, + generated by an if clause. */ + var add32 = function (a, b) { + return (a + b) & 0xFFFFFFFF; + }, + + cmn = function (q, a, b, x, s, t) { + a = add32(add32(a, q), add32(x, t)); + return add32((a << s) | (a >>> (32 - s)), b); + }, + + ff = function (a, b, c, d, x, s, t) { + return cmn((b & c) | ((~b) & d), a, b, x, s, t); + }, + + gg = function (a, b, c, d, x, s, t) { + return cmn((b & d) | (c & (~d)), a, b, x, s, t); + }, + + hh = function (a, b, c, d, x, s, t) { + return cmn(b ^ c ^ d, a, b, x, s, t); + }, + + ii = function (a, b, c, d, x, s, t) { + return cmn(c ^ (b | (~d)), a, b, x, s, t); + }, + + md5cycle = function (x, k) { + var a = x[0], + b = x[1], + c = x[2], + d = x[3]; + + a = ff(a, b, c, d, k[0], 7, -680876936); + d = ff(d, a, b, c, k[1], 12, -389564586); + c = ff(c, d, a, b, k[2], 17, 606105819); + b = ff(b, c, d, a, k[3], 22, -1044525330); + a = ff(a, b, c, d, k[4], 7, -176418897); + d = ff(d, a, b, c, k[5], 12, 1200080426); + c = ff(c, d, a, b, k[6], 17, -1473231341); + b = ff(b, c, d, a, k[7], 22, -45705983); + a = ff(a, b, c, d, k[8], 7, 1770035416); + d = ff(d, a, b, c, k[9], 12, -1958414417); + c = ff(c, d, a, b, k[10], 17, -42063); + b = ff(b, c, d, a, k[11], 22, -1990404162); + a = ff(a, b, c, d, k[12], 7, 1804603682); + d = ff(d, a, b, c, k[13], 12, -40341101); + c = ff(c, d, a, b, k[14], 17, -1502002290); + b = ff(b, c, d, a, k[15], 22, 1236535329); + + a = gg(a, b, c, d, k[1], 5, -165796510); + d = gg(d, a, b, c, k[6], 9, -1069501632); + c = gg(c, d, a, b, k[11], 14, 643717713); + b = gg(b, c, d, a, k[0], 20, -373897302); + a = gg(a, b, c, d, k[5], 5, -701558691); + d = gg(d, a, b, c, k[10], 9, 38016083); + c = gg(c, d, a, b, k[15], 14, -660478335); + b = gg(b, c, d, a, k[4], 20, -405537848); + a = gg(a, b, c, d, k[9], 5, 568446438); + d = gg(d, a, b, c, k[14], 9, -1019803690); + c = gg(c, d, a, b, k[3], 14, -187363961); + b = gg(b, c, d, a, k[8], 20, 1163531501); + a = gg(a, b, c, d, k[13], 5, -1444681467); + d = gg(d, a, b, c, k[2], 9, -51403784); + c = gg(c, d, a, b, k[7], 14, 1735328473); + b = gg(b, c, d, a, k[12], 20, -1926607734); + + a = hh(a, b, c, d, k[5], 4, -378558); + d = hh(d, a, b, c, k[8], 11, -2022574463); + c = hh(c, d, a, b, k[11], 16, 1839030562); + b = hh(b, c, d, a, k[14], 23, -35309556); + a = hh(a, b, c, d, k[1], 4, -1530992060); + d = hh(d, a, b, c, k[4], 11, 1272893353); + c = hh(c, d, a, b, k[7], 16, -155497632); + b = hh(b, c, d, a, k[10], 23, -1094730640); + a = hh(a, b, c, d, k[13], 4, 681279174); + d = hh(d, a, b, c, k[0], 11, -358537222); + c = hh(c, d, a, b, k[3], 16, -722521979); + b = hh(b, c, d, a, k[6], 23, 76029189); + a = hh(a, b, c, d, k[9], 4, -640364487); + d = hh(d, a, b, c, k[12], 11, -421815835); + c = hh(c, d, a, b, k[15], 16, 530742520); + b = hh(b, c, d, a, k[2], 23, -995338651); + + a = ii(a, b, c, d, k[0], 6, -198630844); + d = ii(d, a, b, c, k[7], 10, 1126891415); + c = ii(c, d, a, b, k[14], 15, -1416354905); + b = ii(b, c, d, a, k[5], 21, -57434055); + a = ii(a, b, c, d, k[12], 6, 1700485571); + d = ii(d, a, b, c, k[3], 10, -1894986606); + c = ii(c, d, a, b, k[10], 15, -1051523); + b = ii(b, c, d, a, k[1], 21, -2054922799); + a = ii(a, b, c, d, k[8], 6, 1873313359); + d = ii(d, a, b, c, k[15], 10, -30611744); + c = ii(c, d, a, b, k[6], 15, -1560198380); + b = ii(b, c, d, a, k[13], 21, 1309151649); + a = ii(a, b, c, d, k[4], 6, -145523070); + d = ii(d, a, b, c, k[11], 10, -1120210379); + c = ii(c, d, a, b, k[2], 15, 718787259); + b = ii(b, c, d, a, k[9], 21, -343485551); + + x[0] = add32(a, x[0]); + x[1] = add32(b, x[1]); + x[2] = add32(c, x[2]); + x[3] = add32(d, x[3]); + }, + + /* there needs to be support for Unicode here, + * unless we pretend that we can redefine the MD-5 + * algorithm for multi-byte characters (perhaps + * by adding every four 16-bit characters and + * shortening the sum to 32 bits). Otherwise + * I suggest performing MD-5 as if every character + * was two bytes--e.g., 0040 0025 = @%--but then + * how will an ordinary MD-5 sum be matched? + * There is no way to standardize text to something + * like UTF-8 before transformation; speed cost is + * utterly prohibitive. The JavaScript standard + * itself needs to look at this: it should start + * providing access to strings as preformed UTF-8 + * 8-bit unsigned value arrays. + */ + md5blk = function (s) { + var md5blks = [], + i; /* Andy King said do it this way. */ + + for (i = 0; i < 64; i += 4) { + md5blks[i >> 2] = s.charCodeAt(i) + (s.charCodeAt(i + 1) << 8) + (s.charCodeAt(i + 2) << 16) + (s.charCodeAt(i + 3) << 24); + } + return md5blks; + }, + + md5blk_array = function (a) { + var md5blks = [], + i; /* Andy King said do it this way. */ + + for (i = 0; i < 64; i += 4) { + md5blks[i >> 2] = a[i] + (a[i + 1] << 8) + (a[i + 2] << 16) + (a[i + 3] << 24); + } + return md5blks; + }, + + md51 = function (s) { + var n = s.length, + state = [1732584193, -271733879, -1732584194, 271733878], + i, + length, + tail, + tmp, + lo, + hi; + + for (i = 64; i <= n; i += 64) { + md5cycle(state, md5blk(s.substring(i - 64, i))); + } + s = s.substring(i - 64); + length = s.length; + tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + for (i = 0; i < length; i += 1) { + tail[i >> 2] |= s.charCodeAt(i) << ((i % 4) << 3); + } + tail[i >> 2] |= 0x80 << ((i % 4) << 3); + if (i > 55) { + md5cycle(state, tail); + for (i = 0; i < 16; i += 1) { + tail[i] = 0; + } + } + + // Beware that the final length might not fit in 32 bits so we take care of that + tmp = n * 8; + tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/); + lo = parseInt(tmp[2], 16); + hi = parseInt(tmp[1], 16) || 0; + + tail[14] = lo; + tail[15] = hi; + + md5cycle(state, tail); + return state; + }, + + md51_array = function (a) { + var n = a.length, + state = [1732584193, -271733879, -1732584194, 271733878], + i, + length, + tail, + tmp, + lo, + hi; + + for (i = 64; i <= n; i += 64) { + md5cycle(state, md5blk_array(a.subarray(i - 64, i))); + } + + // Not sure if it is a bug, however IE10 will always produce a sub array of length 1 + // containing the last element of the parent array if the sub array specified starts + // beyond the length of the parent array - weird. + // https://connect.microsoft.com/IE/feedback/details/771452/typed-array-subarray-issue + a = (i - 64) < n ? a.subarray(i - 64) : new Uint8Array(0); + + length = a.length; + tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + for (i = 0; i < length; i += 1) { + tail[i >> 2] |= a[i] << ((i % 4) << 3); + } + + tail[i >> 2] |= 0x80 << ((i % 4) << 3); + if (i > 55) { + md5cycle(state, tail); + for (i = 0; i < 16; i += 1) { + tail[i] = 0; + } + } + + // Beware that the final length might not fit in 32 bits so we take care of that + tmp = n * 8; + tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/); + lo = parseInt(tmp[2], 16); + hi = parseInt(tmp[1], 16) || 0; + + tail[14] = lo; + tail[15] = hi; + + md5cycle(state, tail); + + return state; + }, + + hex_chr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'], + + rhex = function (n) { + var s = '', + j; + for (j = 0; j < 4; j += 1) { + s += hex_chr[(n >> (j * 8 + 4)) & 0x0F] + hex_chr[(n >> (j * 8)) & 0x0F]; + } + return s; + }, + + hex = function (x) { + var i; + for (i = 0; i < x.length; i += 1) { + x[i] = rhex(x[i]); + } + return x.join(''); + }, + + md5 = function (s) { + return hex(md51(s)); + }, + + + + //////////////////////////////////////////////////////////////////////////// + + /** + * SparkMD5 OOP implementation. + * + * Use this class to perform an incremental md5, otherwise use the + * static methods instead. + */ + SparkMD5 = function () { + // call reset to init the instance + this.reset(); + }; + + + // In some cases the fast add32 function cannot be used.. + if (md5('hello') !== '5d41402abc4b2a76b9719d911017c592') { + add32 = function (x, y) { + var lsw = (x & 0xFFFF) + (y & 0xFFFF), + msw = (x >> 16) + (y >> 16) + (lsw >> 16); + return (msw << 16) | (lsw & 0xFFFF); + }; + } + + + /** + * Appends a string. + * A conversion will be applied if an utf8 string is detected. + * + * @param {String} str The string to be appended + * + * @return {SparkMD5} The instance itself + */ + SparkMD5.prototype.append = function (str) { + // converts the string to utf8 bytes if necessary + if (/[\u0080-\uFFFF]/.test(str)) { + str = unescape(encodeURIComponent(str)); + } + + // then append as binary + this.appendBinary(str); + + return this; + }; + + /** + * Appends a binary string. + * + * @param {String} contents The binary string to be appended + * + * @return {SparkMD5} The instance itself + */ + SparkMD5.prototype.appendBinary = function (contents) { + this._buff += contents; + this._length += contents.length; + + var length = this._buff.length, + i; + + for (i = 64; i <= length; i += 64) { + md5cycle(this._state, md5blk(this._buff.substring(i - 64, i))); + } + + this._buff = this._buff.substr(i - 64); + + return this; + }; + + /** + * Finishes the incremental computation, reseting the internal state and + * returning the result. + * Use the raw parameter to obtain the raw result instead of the hex one. + * + * @param {Boolean} raw True to get the raw result, false to get the hex result + * + * @return {String|Array} The result + */ + SparkMD5.prototype.end = function (raw) { + var buff = this._buff, + length = buff.length, + i, + tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + ret; + + for (i = 0; i < length; i += 1) { + tail[i >> 2] |= buff.charCodeAt(i) << ((i % 4) << 3); + } + + this._finish(tail, length); + ret = !!raw ? this._state : hex(this._state); + + this.reset(); + + return ret; + }; + + /** + * Finish the final calculation based on the tail. + * + * @param {Array} tail The tail (will be modified) + * @param {Number} length The length of the remaining buffer + */ + SparkMD5.prototype._finish = function (tail, length) { + var i = length, + tmp, + lo, + hi; + + tail[i >> 2] |= 0x80 << ((i % 4) << 3); + if (i > 55) { + md5cycle(this._state, tail); + for (i = 0; i < 16; i += 1) { + tail[i] = 0; + } + } + + // Do the final computation based on the tail and length + // Beware that the final length may not fit in 32 bits so we take care of that + tmp = this._length * 8; + tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/); + lo = parseInt(tmp[2], 16); + hi = parseInt(tmp[1], 16) || 0; + + tail[14] = lo; + tail[15] = hi; + md5cycle(this._state, tail); + }; + + /** + * Resets the internal state of the computation. + * + * @return {SparkMD5} The instance itself + */ + SparkMD5.prototype.reset = function () { + this._buff = ""; + this._length = 0; + this._state = [1732584193, -271733879, -1732584194, 271733878]; + + return this; + }; + + /** + * Releases memory used by the incremental buffer and other aditional + * resources. If you plan to use the instance again, use reset instead. + */ + SparkMD5.prototype.destroy = function () { + delete this._state; + delete this._buff; + delete this._length; + }; + + + /** + * Performs the md5 hash on a string. + * A conversion will be applied if utf8 string is detected. + * + * @param {String} str The string + * @param {Boolean} raw True to get the raw result, false to get the hex result + * + * @return {String|Array} The result + */ + SparkMD5.hash = function (str, raw) { + // converts the string to utf8 bytes if necessary + if (/[\u0080-\uFFFF]/.test(str)) { + str = unescape(encodeURIComponent(str)); + } + + var hash = md51(str); + + return !!raw ? hash : hex(hash); + }; + + /** + * Performs the md5 hash on a binary string. + * + * @param {String} content The binary string + * @param {Boolean} raw True to get the raw result, false to get the hex result + * + * @return {String|Array} The result + */ + SparkMD5.hashBinary = function (content, raw) { + var hash = md51(content); + + return !!raw ? hash : hex(hash); + }; + + /** + * SparkMD5 OOP implementation for array buffers. + * + * Use this class to perform an incremental md5 ONLY for array buffers. + */ + SparkMD5.ArrayBuffer = function () { + // call reset to init the instance + this.reset(); + }; + + //////////////////////////////////////////////////////////////////////////// + + /** + * Appends an array buffer. + * + * @param {ArrayBuffer} arr The array to be appended + * + * @return {SparkMD5.ArrayBuffer} The instance itself + */ + SparkMD5.ArrayBuffer.prototype.append = function (arr) { + // TODO: we could avoid the concatenation here but the algorithm would be more complex + // if you find yourself needing extra performance, please make a PR. + var buff = this._concatArrayBuffer(this._buff, arr), + length = buff.length, + i; + + this._length += arr.byteLength; + + for (i = 64; i <= length; i += 64) { + md5cycle(this._state, md5blk_array(buff.subarray(i - 64, i))); + } + + // Avoids IE10 weirdness (documented above) + this._buff = (i - 64) < length ? buff.subarray(i - 64) : new Uint8Array(0); + + return this; + }; + + /** + * Finishes the incremental computation, reseting the internal state and + * returning the result. + * Use the raw parameter to obtain the raw result instead of the hex one. + * + * @param {Boolean} raw True to get the raw result, false to get the hex result + * + * @return {String|Array} The result + */ + SparkMD5.ArrayBuffer.prototype.end = function (raw) { + var buff = this._buff, + length = buff.length, + tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + i, + ret; + + for (i = 0; i < length; i += 1) { + tail[i >> 2] |= buff[i] << ((i % 4) << 3); + } + + this._finish(tail, length); + ret = !!raw ? this._state : hex(this._state); + + this.reset(); + + return ret; + }; + + SparkMD5.ArrayBuffer.prototype._finish = SparkMD5.prototype._finish; + + /** + * Resets the internal state of the computation. + * + * @return {SparkMD5.ArrayBuffer} The instance itself + */ + SparkMD5.ArrayBuffer.prototype.reset = function () { + this._buff = new Uint8Array(0); + this._length = 0; + this._state = [1732584193, -271733879, -1732584194, 271733878]; + + return this; + }; + + /** + * Releases memory used by the incremental buffer and other aditional + * resources. If you plan to use the instance again, use reset instead. + */ + SparkMD5.ArrayBuffer.prototype.destroy = SparkMD5.prototype.destroy; + + /** + * Concats two array buffers, returning a new one. + * + * @param {ArrayBuffer} first The first array buffer + * @param {ArrayBuffer} second The second array buffer + * + * @return {ArrayBuffer} The new array buffer + */ + SparkMD5.ArrayBuffer.prototype._concatArrayBuffer = function (first, second) { + var firstLength = first.length, + result = new Uint8Array(firstLength + second.byteLength); + + result.set(first); + result.set(new Uint8Array(second), firstLength); + + return result; + }; + + /** + * Performs the md5 hash on an array buffer. + * + * @param {ArrayBuffer} arr The array buffer + * @param {Boolean} raw True to get the raw result, false to get the hex result + * + * @return {String|Array} The result + */ + SparkMD5.ArrayBuffer.hash = function (arr, raw) { + var hash = md51_array(new Uint8Array(arr)); + + return !!raw ? hash : hex(hash); + }; + + return FlashRuntime.register( 'Md5', { + init: function() { + // do nothing. + }, + + loadFromBlob: function( file ) { + var blob = file.getSource(), + chunkSize = 2 * 1024 * 1024, + chunks = Math.ceil( blob.size / chunkSize ), + chunk = 0, + owner = this.owner, + spark = new SparkMD5.ArrayBuffer(), + me = this, + blobSlice = blob.mozSlice || blob.webkitSlice || blob.slice, + loadNext, fr; + + fr = new FileReader(); + + loadNext = function() { + var start, end; + + start = chunk * chunkSize; + end = Math.min( start + chunkSize, blob.size ); + + fr.onload = function( e ) { + spark.append( e.target.result ); + owner.trigger( 'progress', { + total: file.size, + loaded: end + }); + }; + + fr.onloadend = function() { + fr.onloadend = fr.onload = null; + + if ( ++chunk < chunks ) { + setTimeout( loadNext, 1 ); + } else { + setTimeout(function(){ + owner.trigger('load'); + me.result = spark.end(); + loadNext = file = blob = spark = null; + owner.trigger('complete'); + }, 50 ); + } + }; + + fr.readAsArrayBuffer( blobSlice.call( blob, start, end ) ); + }; + + loadNext(); + }, + + getResult: function() { + return this.result; + } + }); + }); + /** + * @fileOverview FlashRuntime + */ + define('runtime/flash/runtime',[ + 'base', + 'runtime/runtime', + 'runtime/compbase' + ], function( Base, Runtime, CompBase ) { + + var $ = Base.$, + type = 'flash', + components = {}; + + + function getFlashVersion() { + var version; + + try { + version = navigator.plugins[ 'Shockwave Flash' ]; + version = version.description; + } catch ( ex ) { + try { + version = new ActiveXObject('ShockwaveFlash.ShockwaveFlash') + .GetVariable('$version'); + } catch ( ex2 ) { + version = '0.0'; + } + } + version = version.match( /\d+/g ); + return parseFloat( version[ 0 ] + '.' + version[ 1 ], 10 ); + } + + function FlashRuntime() { + var pool = {}, + clients = {}, + destroy = this.destroy, + me = this, + jsreciver = Base.guid('webuploader_'); + + Runtime.apply( me, arguments ); + me.type = type; + + + // 这个方法的调用者,实际上是RuntimeClient + me.exec = function( comp, fn/*, args...*/ ) { + var client = this, + uid = client.uid, + args = Base.slice( arguments, 2 ), + instance; + + clients[ uid ] = client; + + if ( components[ comp ] ) { + if ( !pool[ uid ] ) { + pool[ uid ] = new components[ comp ]( client, me ); + } + + instance = pool[ uid ]; + + if ( instance[ fn ] ) { + return instance[ fn ].apply( instance, args ); + } + } + + return me.flashExec.apply( client, arguments ); + }; + + function handler( evt, obj ) { + var type = evt.type || evt, + parts, uid; + + parts = type.split('::'); + uid = parts[ 0 ]; + type = parts[ 1 ]; + + // console.log.apply( console, arguments ); + + if ( type === 'Ready' && uid === me.uid ) { + me.trigger('ready'); + } else if ( clients[ uid ] ) { + clients[ uid ].trigger( type.toLowerCase(), evt, obj ); + } + + // Base.log( evt, obj ); + } + + // flash的接受器。 + window[ jsreciver ] = function() { + var args = arguments; + + // 为了能捕获得到。 + setTimeout(function() { + handler.apply( null, args ); + }, 1 ); + }; + + this.jsreciver = jsreciver; + + this.destroy = function() { + // @todo 删除池子中的所有实例 + return destroy && destroy.apply( this, arguments ); + }; + + this.flashExec = function( comp, fn ) { + var flash = me.getFlash(), + args = Base.slice( arguments, 2 ); + + return flash.exec( this.uid, comp, fn, args ); + }; + + // @todo + } + + Base.inherits( Runtime, { + constructor: FlashRuntime, + + init: function() { + var container = this.getContainer(), + opts = this.options, + html; + + // if not the minimal height, shims are not initialized + // in older browsers (e.g FF3.6, IE6,7,8, Safari 4.0,5.0, etc) + container.css({ + position: 'absolute', + top: '-8px', + left: '-8px', + width: '9px', + height: '9px', + overflow: 'hidden' + }); + + // insert flash object + html = '' + + '' + + '' + + '' + + ''; + + container.html( html ); + }, + + getFlash: function() { + if ( this._flash ) { + return this._flash; + } + + this._flash = $( '#' + this.uid ).get( 0 ); + return this._flash; + } + + }); + + FlashRuntime.register = function( name, component ) { + component = components[ name ] = Base.inherits( CompBase, $.extend({ + + // @todo fix this later + flashExec: function() { + var owner = this.owner, + runtime = this.getRuntime(); + + return runtime.flashExec.apply( owner, arguments ); + } + }, component ) ); + + return component; + }; + + if ( getFlashVersion() >= 11.4 ) { + Runtime.addRuntime( type, FlashRuntime ); + } + + return FlashRuntime; + }); + /** + * @fileOverview FilePicker + */ + define('runtime/flash/filepicker',[ + 'base', + 'runtime/flash/runtime' + ], function( Base, FlashRuntime ) { + var $ = Base.$; + + return FlashRuntime.register( 'FilePicker', { + init: function( opts ) { + var copy = $.extend({}, opts ), + len, i; + + // 修复Flash再没有设置title的情况下无法弹出flash文件选择框的bug. + len = copy.accept && copy.accept.length; + for ( i = 0; i < len; i++ ) { + if ( !copy.accept[ i ].title ) { + copy.accept[ i ].title = 'Files'; + } + } + + delete copy.button; + delete copy.id; + delete copy.container; + + this.flashExec( 'FilePicker', 'init', copy ); + }, + + destroy: function() { + this.flashExec( 'FilePicker', 'destroy' ); + } + }); + }); + /** + * @fileOverview 图片压缩 + */ + define('runtime/flash/image',[ + 'runtime/flash/runtime' + ], function( FlashRuntime ) { + + return FlashRuntime.register( 'Image', { + // init: function( options ) { + // var owner = this.owner; + + // this.flashExec( 'Image', 'init', options ); + // owner.on( 'load', function() { + // debugger; + // }); + // }, + + loadFromBlob: function( blob ) { + var owner = this.owner; + + owner.info() && this.flashExec( 'Image', 'info', owner.info() ); + owner.meta() && this.flashExec( 'Image', 'meta', owner.meta() ); + + this.flashExec( 'Image', 'loadFromBlob', blob.uid ); + } + }); + }); + /** + * @fileOverview Transport flash实现 + */ + define('runtime/flash/transport',[ + 'base', + 'runtime/flash/runtime', + 'runtime/client' + ], function( Base, FlashRuntime, RuntimeClient ) { + var $ = Base.$; + + return FlashRuntime.register( 'Transport', { + init: function() { + this._status = 0; + this._response = null; + this._responseJson = null; + }, + + send: function() { + var owner = this.owner, + opts = this.options, + xhr = this._initAjax(), + blob = owner._blob, + server = opts.server, + binary; + + xhr.connectRuntime( blob.ruid ); + + if ( opts.sendAsBinary ) { + server += (/\?/.test( server ) ? '&' : '?') + + $.param( owner._formData ); + + binary = blob.uid; + } else { + $.each( owner._formData, function( k, v ) { + xhr.exec( 'append', k, v ); + }); + + xhr.exec( 'appendBlob', opts.fileVal, blob.uid, + opts.filename || owner._formData.name || '' ); + } + + this._setRequestHeader( xhr, opts.headers ); + xhr.exec( 'send', { + method: opts.method, + url: server, + forceURLStream: opts.forceURLStream, + mimeType: 'application/octet-stream' + }, binary ); + }, + + getStatus: function() { + return this._status; + }, + + getResponse: function() { + return this._response || ''; + }, + + getResponseAsJson: function() { + return this._responseJson; + }, + + abort: function() { + var xhr = this._xhr; + + if ( xhr ) { + xhr.exec('abort'); + xhr.destroy(); + this._xhr = xhr = null; + } + }, + + destroy: function() { + this.abort(); + }, + + _initAjax: function() { + var me = this, + xhr = new RuntimeClient('XMLHttpRequest'); + + xhr.on( 'uploadprogress progress', function( e ) { + var percent = e.loaded / e.total; + percent = Math.min( 1, Math.max( 0, percent ) ); + return me.trigger( 'progress', percent ); + }); + + xhr.on( 'load', function() { + var status = xhr.exec('getStatus'), + readBody = false, + err = '', + p; + + xhr.off(); + me._xhr = null; + + if ( status >= 200 && status < 300 ) { + readBody = true; + } else if ( status >= 500 && status < 600 ) { + readBody = true; + err = 'server'; + } else { + err = 'http'; + } + + if ( readBody ) { + me._response = xhr.exec('getResponse'); + me._response = decodeURIComponent( me._response ); + + // flash 处理可能存在 bug, 没辙只能靠 js 了 + // try { + // me._responseJson = xhr.exec('getResponseAsJson'); + // } catch ( error ) { + + p = window.JSON && window.JSON.parse || function( s ) { + try { + return new Function('return ' + s).call(); + } catch ( err ) { + return {}; + } + }; + me._responseJson = me._response ? p(me._response) : {}; + + // } + } + + xhr.destroy(); + xhr = null; + + return err ? me.trigger( 'error', err ) : me.trigger('load'); + }); + + xhr.on( 'error', function() { + xhr.off(); + me._xhr = null; + me.trigger( 'error', 'http' ); + }); + + me._xhr = xhr; + return xhr; + }, + + _setRequestHeader: function( xhr, headers ) { + $.each( headers, function( key, val ) { + xhr.exec( 'setRequestHeader', key, val ); + }); + } + }); + }); + /** + * @fileOverview Blob Html实现 + */ + define('runtime/flash/blob',[ + 'runtime/flash/runtime', + 'lib/blob' + ], function( FlashRuntime, Blob ) { + + return FlashRuntime.register( 'Blob', { + slice: function( start, end ) { + var blob = this.flashExec( 'Blob', 'slice', start, end ); + + return new Blob( blob.uid, blob ); + } + }); + }); + /** + * @fileOverview Md5 flash实现 + */ + define('runtime/flash/md5',[ + 'runtime/flash/runtime' + ], function( FlashRuntime ) { + + return FlashRuntime.register( 'Md5', { + init: function() { + // do nothing. + }, + + loadFromBlob: function( blob ) { + return this.flashExec( 'Md5', 'loadFromBlob', blob.uid ); + } + }); + }); + /** + * @fileOverview 完全版本。 + */ + define('preset/all',[ + 'base', + + // widgets + 'widgets/filednd', + 'widgets/filepaste', + 'widgets/filepicker', + 'widgets/image', + 'widgets/queue', + 'widgets/runtime', + 'widgets/upload', + 'widgets/validator', + 'widgets/md5', + + // runtimes + // html5 + 'runtime/html5/blob', + 'runtime/html5/dnd', + 'runtime/html5/filepaste', + 'runtime/html5/filepicker', + 'runtime/html5/imagemeta/exif', + 'runtime/html5/androidpatch', + 'runtime/html5/image', + 'runtime/html5/transport', + 'runtime/html5/md5', + + // flash + 'runtime/flash/filepicker', + 'runtime/flash/image', + 'runtime/flash/transport', + 'runtime/flash/blob', + 'runtime/flash/md5' + ], function( Base ) { + return Base; + }); + /** + * @fileOverview 日志组件,主要用来收集错误信息,可以帮助 webuploader 更好的定位问题和发展。 + * + * 如果您不想要启用此功能,请在打包的时候去掉 log 模块。 + * + * 或者可以在初始化的时候通过 options.disableWidgets 属性禁用。 + * + * 如: + * WebUploader.create({ + * ... + * + * disableWidgets: 'log', + * + * ... + * }) + */ + define('widgets/log',[ + 'base', + 'uploader', + 'widgets/widget' + ], function( Base, Uploader ) { + var $ = Base.$, + logUrl = ' http://static.tieba.baidu.com/tb/pms/img/st.gif??', + product = (location.hostname || location.host || 'protected').toLowerCase(), + + // 只针对 baidu 内部产品用户做统计功能。 + enable = product && /baidu/i.exec(product), + base; + + if (!enable) { + return; + } + + base = { + dv: 3, + master: 'webuploader', + online: /test/.exec(product) ? 0 : 1, + module: '', + product: product, + type: 0 + }; + + function send(data) { + var obj = $.extend({}, base, data), + url = logUrl.replace(/^(.*)\?/, '$1' + $.param( obj )), + image = new Image(); + + image.src = url; + } + + return Uploader.register({ + name: 'log', + + init: function() { + var owner = this.owner, + count = 0, + size = 0; + + owner + .on('error', function(code) { + send({ + type: 2, + c_error_code: code + }); + }) + .on('uploadError', function(file, reason) { + send({ + type: 2, + c_error_code: 'UPLOAD_ERROR', + c_reason: '' + reason + }); + }) + .on('uploadComplete', function(file) { + count++; + size += file.size; + }). + on('uploadFinished', function() { + send({ + c_count: count, + c_size: size + }); + count = size = 0; + }); + + send({ + c_usage: 1 + }); + } + }); + }); + /** + * @fileOverview Uploader上传类 + */ + define('webuploader',[ + 'preset/all', + 'widgets/log' + ], function( preset ) { + return preset; + }); + return require('webuploader'); +}); diff --git a/static/plugins/webuploader/webuploader.min.js b/static/plugins/webuploader/webuploader.min.js new file mode 100644 index 00000000..b7476bea --- /dev/null +++ b/static/plugins/webuploader/webuploader.min.js @@ -0,0 +1,3 @@ +/* WebUploader 0.1.5 */!function(a,b){var c,d={},e=function(a,b){var c,d,e;if("string"==typeof a)return h(a);for(c=[],d=a.length,e=0;d>e;e++)c.push(h(a[e]));return b.apply(null,c)},f=function(a,b,c){2===arguments.length&&(c=b,b=null),e(b||[],function(){g(a,c,arguments)})},g=function(a,b,c){var f,g={exports:b};"function"==typeof b&&(c.length||(c=[e,g.exports,g]),f=b.apply(null,c),void 0!==f&&(g.exports=f)),d[a]=g.exports},h=function(b){var c=d[b]||a[b];if(!c)throw new Error("`"+b+"` is undefined");return c},i=function(a){var b,c,e,f,g,h;h=function(a){return a&&a.charAt(0).toUpperCase()+a.substr(1)};for(b in d)if(c=a,d.hasOwnProperty(b)){for(e=b.split("/"),g=h(e.pop());f=h(e.shift());)c[f]=c[f]||{},c=c[f];c[g]=d[b]}return a},j=function(c){return a.__dollar=c,i(b(a,f,e))};"object"==typeof module&&"object"==typeof module.exports?module.exports=j():"function"==typeof define&&define.amd?define(["jquery"],j):(c=a.WebUploader,a.WebUploader=j(),a.WebUploader.noConflict=function(){a.WebUploader=c})}(window,function(a,b,c){return b("dollar-third",[],function(){var b=a.__dollar||a.jQuery||a.Zepto;if(!b)throw new Error("jQuery or Zepto not found!");return b}),b("dollar",["dollar-third"],function(a){return a}),b("promise-third",["dollar"],function(a){return{Deferred:a.Deferred,when:a.when,isPromise:function(a){return a&&"function"==typeof a.then}}}),b("promise",["promise-third"],function(a){return a}),b("base",["dollar","promise"],function(b,c){function d(a){return function(){return h.apply(a,arguments)}}function e(a,b){return function(){return a.apply(b,arguments)}}function f(a){var b;return Object.create?Object.create(a):(b=function(){},b.prototype=a,new b)}var g=function(){},h=Function.call;return{version:"0.1.5",$:b,Deferred:c.Deferred,isPromise:c.isPromise,when:c.when,browser:function(a){var b={},c=a.match(/WebKit\/([\d.]+)/),d=a.match(/Chrome\/([\d.]+)/)||a.match(/CriOS\/([\d.]+)/),e=a.match(/MSIE\s([\d\.]+)/)||a.match(/(?:trident)(?:.*rv:([\w.]+))?/i),f=a.match(/Firefox\/([\d.]+)/),g=a.match(/Safari\/([\d.]+)/),h=a.match(/OPR\/([\d.]+)/);return c&&(b.webkit=parseFloat(c[1])),d&&(b.chrome=parseFloat(d[1])),e&&(b.ie=parseFloat(e[1])),f&&(b.firefox=parseFloat(f[1])),g&&(b.safari=parseFloat(g[1])),h&&(b.opera=parseFloat(h[1])),b}(navigator.userAgent),os:function(a){var b={},c=a.match(/(?:Android);?[\s\/]+([\d.]+)?/),d=a.match(/(?:iPad|iPod|iPhone).*OS\s([\d_]+)/);return c&&(b.android=parseFloat(c[1])),d&&(b.ios=parseFloat(d[1].replace(/_/g,"."))),b}(navigator.userAgent),inherits:function(a,c,d){var e;return"function"==typeof c?(e=c,c=null):e=c&&c.hasOwnProperty("constructor")?c.constructor:function(){return a.apply(this,arguments)},b.extend(!0,e,a,d||{}),e.__super__=a.prototype,e.prototype=f(a.prototype),c&&b.extend(!0,e.prototype,c),e},noop:g,bindFn:e,log:function(){return a.console?e(console.log,console):g}(),nextTick:function(){return function(a){setTimeout(a,1)}}(),slice:d([].slice),guid:function(){var a=0;return function(b){for(var c=(+new Date).toString(32),d=0;5>d;d++)c+=Math.floor(65535*Math.random()).toString(32);return(b||"wu_")+c+(a++).toString(32)}}(),formatSize:function(a,b,c){var d;for(c=c||["B","K","M","G","TB"];(d=c.shift())&&a>1024;)a/=1024;return("B"===d?a:a.toFixed(b||2))+d}}}),b("mediator",["base"],function(a){function b(a,b,c,d){return f.grep(a,function(a){return!(!a||b&&a.e!==b||c&&a.cb!==c&&a.cb._cb!==c||d&&a.ctx!==d)})}function c(a,b,c){f.each((a||"").split(h),function(a,d){c(d,b)})}function d(a,b){for(var c,d=!1,e=-1,f=a.length;++e1?void(d.isPlainObject(b)&&d.isPlainObject(c[a])?d.extend(c[a],b):c[a]=b):a?c[a]:c},getStats:function(){var a=this.request("get-stats");return a?{successNum:a.numOfSuccess,progressNum:a.numOfProgress,cancelNum:a.numOfCancel,invalidNum:a.numOfInvalid,uploadFailNum:a.numOfUploadFailed,queueNum:a.numOfQueue,interruptNum:a.numofInterrupt}:{}},trigger:function(a){var c=[].slice.call(arguments,1),e=this.options,f="on"+a.substring(0,1).toUpperCase()+a.substring(1);return b.trigger.apply(this,arguments)===!1||d.isFunction(e[f])&&e[f].apply(this,c)===!1||d.isFunction(this[f])&&this[f].apply(this,c)===!1||b.trigger.apply(b,[this,a].concat(c))===!1?!1:!0},destroy:function(){this.request("destroy",arguments),this.off()},request:a.noop}),a.create=c.create=function(a){return new c(a)},a.Uploader=c,c}),b("runtime/runtime",["base","mediator"],function(a,b){function c(b){this.options=d.extend({container:document.body},b),this.uid=a.guid("rt_")}var d=a.$,e={},f=function(a){for(var b in a)if(a.hasOwnProperty(b))return b;return null};return d.extend(c.prototype,{getContainer:function(){var a,b,c=this.options;return this._container?this._container:(a=d(c.container||document.body),b=d(document.createElement("div")),b.attr("id","rt_"+this.uid),b.css({position:"absolute",top:"0px",left:"0px",width:"1px",height:"1px",overflow:"hidden"}),a.append(b),a.addClass("webuploader-container"),this._container=b,this._parent=a,b)},init:a.noop,exec:a.noop,destroy:function(){this._container&&this._container.remove(),this._parent&&this._parent.removeClass("webuploader-container"),this.off()}}),c.orders="html5,flash",c.addRuntime=function(a,b){e[a]=b},c.hasRuntime=function(a){return!!(a?e[a]:f(e))},c.create=function(a,b){var g,h;if(b=b||c.orders,d.each(b.split(/\s*,\s*/g),function(){return e[this]?(g=this,!1):void 0}),g=g||f(e),!g)throw new Error("Runtime Error");return h=new e[g](a)},b.installTo(c.prototype),c}),b("runtime/client",["base","mediator","runtime/runtime"],function(a,b,c){function d(b,d){var f,g=a.Deferred();this.uid=a.guid("client_"),this.runtimeReady=function(a){return g.done(a)},this.connectRuntime=function(b,h){if(f)throw new Error("already connected!");return g.done(h),"string"==typeof b&&e.get(b)&&(f=e.get(b)),f=f||e.get(null,d),f?(a.$.extend(f.options,b),f.__promise.then(g.resolve),f.__client++):(f=c.create(b,b.runtimeOrder),f.__promise=g.promise(),f.once("ready",g.resolve),f.init(),e.add(f),f.__client=1),d&&(f.__standalone=d),f},this.getRuntime=function(){return f},this.disconnectRuntime=function(){f&&(f.__client--,f.__client<=0&&(e.remove(f),delete f.__promise,f.destroy()),f=null)},this.exec=function(){if(f){var c=a.slice(arguments);return b&&c.unshift(b),f.exec.apply(this,c)}},this.getRuid=function(){return f&&f.uid},this.destroy=function(a){return function(){a&&a.apply(this,arguments),this.trigger("destroy"),this.off(),this.exec("destroy"),this.disconnectRuntime()}}(this.destroy)}var e;return e=function(){var a={};return{add:function(b){a[b.uid]=b},get:function(b,c){var d;if(b)return a[b];for(d in a)if(!c||!a[d].__standalone)return a[d];return null},remove:function(b){delete a[b.uid]}}}(),b.installTo(d.prototype),d}),b("lib/dnd",["base","mediator","runtime/client"],function(a,b,c){function d(a){a=this.options=e.extend({},d.options,a),a.container=e(a.container),a.container.length&&c.call(this,"DragAndDrop")}var e=a.$;return d.options={accept:null,disableGlobalDnd:!1},a.inherits(c,{constructor:d,init:function(){var a=this;a.connectRuntime(a.options,function(){a.exec("init"),a.trigger("ready")})}}),b.installTo(d.prototype),d}),b("widgets/widget",["base","uploader"],function(a,b){function c(a){if(!a)return!1;var b=a.length,c=e.type(a);return 1===a.nodeType&&b?!0:"array"===c||"function"!==c&&"string"!==c&&(0===b||"number"==typeof b&&b>0&&b-1 in a)}function d(a){this.owner=a,this.options=a.options}var e=a.$,f=b.prototype._init,g=b.prototype.destroy,h={},i=[];return e.extend(d.prototype,{init:a.noop,invoke:function(a,b){var c=this.responseMap;return c&&a in c&&c[a]in this&&e.isFunction(this[c[a]])?this[c[a]].apply(this,b):h},request:function(){return this.owner.request.apply(this.owner,arguments)}}),e.extend(b.prototype,{_init:function(){var a=this,b=a._widgets=[],c=a.options.disableWidgets||"";return e.each(i,function(d,e){(!c||!~c.indexOf(e._name))&&b.push(new e(a))}),f.apply(a,arguments)},request:function(b,d,e){var f,g,i,j,k=0,l=this._widgets,m=l&&l.length,n=[],o=[];for(d=c(d)?d:[d];m>k;k++)f=l[k],g=f.invoke(b,d),g!==h&&(a.isPromise(g)?o.push(g):n.push(g));return e||o.length?(i=a.when.apply(a,o),j=i.pipe?"pipe":"then",i[j](function(){var b=a.Deferred(),c=arguments;return 1===c.length&&(c=c[0]),setTimeout(function(){b.resolve(c)},1),b.promise()})[e?j:"done"](e||a.noop)):n[0]},destroy:function(){g.apply(this,arguments),this._widgets=null}}),b.register=d.register=function(b,c){var f,g={init:"init",destroy:"destroy",name:"anonymous"};return 1===arguments.length?(c=b,e.each(c,function(a){return"_"===a[0]||"name"===a?void("name"===a&&(g.name=c.name)):void(g[a.replace(/[A-Z]/g,"-$&").toLowerCase()]=a)})):g=e.extend(g,b),c.responseMap=g,f=a.inherits(d,c),f._name=g.name,i.push(f),f},b.unRegister=d.unRegister=function(a){if(a&&"anonymous"!==a)for(var b=i.length;b--;)i[b]._name===a&&i.splice(b,1)},d}),b("widgets/filednd",["base","uploader","lib/dnd","widgets/widget"],function(a,b,c){var d=a.$;return b.options.dnd="",b.register({name:"dnd",init:function(b){if(b.dnd&&"html5"===this.request("predict-runtime-type")){var e,f=this,g=a.Deferred(),h=d.extend({},{disableGlobalDnd:b.disableGlobalDnd,container:b.dnd,accept:b.accept});return this.dnd=e=new c(h),e.once("ready",g.resolve),e.on("drop",function(a){f.request("add-file",[a])}),e.on("accept",function(a){return f.owner.trigger("dndAccept",a)}),e.init(),g.promise()}},destroy:function(){this.dnd&&this.dnd.destroy()}})}),b("lib/filepaste",["base","mediator","runtime/client"],function(a,b,c){function d(a){a=this.options=e.extend({},a),a.container=e(a.container||document.body),c.call(this,"FilePaste")}var e=a.$;return a.inherits(c,{constructor:d,init:function(){var a=this;a.connectRuntime(a.options,function(){a.exec("init"),a.trigger("ready")})}}),b.installTo(d.prototype),d}),b("widgets/filepaste",["base","uploader","lib/filepaste","widgets/widget"],function(a,b,c){var d=a.$;return b.register({name:"paste",init:function(b){if(b.paste&&"html5"===this.request("predict-runtime-type")){var e,f=this,g=a.Deferred(),h=d.extend({},{container:b.paste,accept:b.accept});return this.paste=e=new c(h),e.once("ready",g.resolve),e.on("paste",function(a){f.owner.request("add-file",[a])}),e.init(),g.promise()}},destroy:function(){this.paste&&this.paste.destroy()}})}),b("lib/blob",["base","runtime/client"],function(a,b){function c(a,c){var d=this;d.source=c,d.ruid=a,this.size=c.size||0,this.type=!c.type&&this.ext&&~"jpg,jpeg,png,gif,bmp".indexOf(this.ext)?"image/"+("jpg"===this.ext?"jpeg":this.ext):c.type||"application/octet-stream",b.call(d,"Blob"),this.uid=c.uid||this.uid,a&&d.connectRuntime(a)}return a.inherits(b,{constructor:c,slice:function(a,b){return this.exec("slice",a,b)},getSource:function(){return this.source}}),c}),b("lib/file",["base","lib/blob"],function(a,b){function c(a,c){var f;this.name=c.name||"untitled"+d++,f=e.exec(c.name)?RegExp.$1.toLowerCase():"",!f&&c.type&&(f=/\/(jpg|jpeg|png|gif|bmp)$/i.exec(c.type)?RegExp.$1.toLowerCase():"",this.name+="."+f),this.ext=f,this.lastModifiedDate=c.lastModifiedDate||(new Date).toLocaleString(),b.apply(this,arguments)}var d=1,e=/\.([^.]+)$/;return a.inherits(b,c)}),b("lib/filepicker",["base","runtime/client","lib/file"],function(b,c,d){function e(a){if(a=this.options=f.extend({},e.options,a),a.container=f(a.id),!a.container.length)throw new Error("按钮指定错误");a.innerHTML=a.innerHTML||a.label||a.container.html()||"",a.button=f(a.button||document.createElement("div")),a.button.html(a.innerHTML),a.container.html(a.button),c.call(this,"FilePicker",!0)}var f=b.$;return e.options={button:null,container:null,label:null,innerHTML:null,multiple:!0,accept:null,name:"file"},b.inherits(c,{constructor:e,init:function(){var c=this,e=c.options,g=e.button;g.addClass("webuploader-pick"),c.on("all",function(a){var b;switch(a){case"mouseenter":g.addClass("webuploader-pick-hover");break;case"mouseleave":g.removeClass("webuploader-pick-hover");break;case"change":b=c.exec("getFiles"),c.trigger("select",f.map(b,function(a){return a=new d(c.getRuid(),a),a._refer=e.container,a}),e.container)}}),c.connectRuntime(e,function(){c.refresh(),c.exec("init",e),c.trigger("ready")}),this._resizeHandler=b.bindFn(this.refresh,this),f(a).on("resize",this._resizeHandler)},refresh:function(){var a=this.getRuntime().getContainer(),b=this.options.button,c=b.outerWidth?b.outerWidth():b.width(),d=b.outerHeight?b.outerHeight():b.height(),e=b.offset();c&&d&&a.css({bottom:"auto",right:"auto",width:c+"px",height:d+"px"}).offset(e)},enable:function(){var a=this.options.button;a.removeClass("webuploader-pick-disable"),this.refresh()},disable:function(){var a=this.options.button;this.getRuntime().getContainer().css({top:"-99999px"}),a.addClass("webuploader-pick-disable")},destroy:function(){var b=this.options.button;f(a).off("resize",this._resizeHandler),b.removeClass("webuploader-pick-disable webuploader-pick-hover webuploader-pick")}}),e}),b("widgets/filepicker",["base","uploader","lib/filepicker","widgets/widget"],function(a,b,c){var d=a.$;return d.extend(b.options,{pick:null,accept:null}),b.register({name:"picker",init:function(a){return this.pickers=[],a.pick&&this.addBtn(a.pick)},refresh:function(){d.each(this.pickers,function(){this.refresh()})},addBtn:function(b){var e=this,f=e.options,g=f.accept,h=[];if(b)return d.isPlainObject(b)||(b={id:b}),d(b.id).each(function(){var i,j,k;k=a.Deferred(),i=d.extend({},b,{accept:d.isPlainObject(g)?[g]:g,swf:f.swf,runtimeOrder:f.runtimeOrder,id:this}),j=new c(i),j.once("ready",k.resolve),j.on("select",function(a){e.owner.request("add-file",[a])}),j.init(),e.pickers.push(j),h.push(k.promise())}),a.when.apply(a,h)},disable:function(){d.each(this.pickers,function(){this.disable()})},enable:function(){d.each(this.pickers,function(){this.enable()})},destroy:function(){d.each(this.pickers,function(){this.destroy()}),this.pickers=null}})}),b("lib/image",["base","runtime/client","lib/blob"],function(a,b,c){function d(a){this.options=e.extend({},d.options,a),b.call(this,"Image"),this.on("load",function(){this._info=this.exec("info"),this._meta=this.exec("meta")})}var e=a.$;return d.options={quality:90,crop:!1,preserveHeaders:!1,allowMagnify:!1},a.inherits(b,{constructor:d,info:function(a){return a?(this._info=a,this):this._info},meta:function(a){return a?(this._meta=a,this):this._meta},loadFromBlob:function(a){var b=this,c=a.getRuid();this.connectRuntime(c,function(){b.exec("init",b.options),b.exec("loadFromBlob",a)})},resize:function(){var b=a.slice(arguments);return this.exec.apply(this,["resize"].concat(b))},crop:function(){var b=a.slice(arguments);return this.exec.apply(this,["crop"].concat(b))},getAsDataUrl:function(a){return this.exec("getAsDataUrl",a)},getAsBlob:function(a){var b=this.exec("getAsBlob",a);return new c(this.getRuid(),b)}}),d}),b("widgets/image",["base","uploader","lib/image","widgets/widget"],function(a,b,c){var d,e=a.$;return d=function(a){var b=0,c=[],d=function(){for(var d;c.length&&a>b;)d=c.shift(),b+=d[0],d[1]()};return function(a,e,f){c.push([e,f]),a.once("destroy",function(){b-=e,setTimeout(d,1)}),setTimeout(d,1)}}(5242880),e.extend(b.options,{thumb:{width:110,height:110,quality:70,allowMagnify:!0,crop:!0,preserveHeaders:!1,type:"image/jpeg"},compress:{width:1600,height:1600,quality:90,allowMagnify:!1,crop:!1,preserveHeaders:!0}}),b.register({name:"image",makeThumb:function(a,b,f,g){var h,i;return a=this.request("get-file",a),a.type.match(/^image/)?(h=e.extend({},this.options.thumb),e.isPlainObject(f)&&(h=e.extend(h,f),f=null),f=f||h.width,g=g||h.height,i=new c(h),i.once("load",function(){a._info=a._info||i.info(),a._meta=a._meta||i.meta(),1>=f&&f>0&&(f=a._info.width*f),1>=g&&g>0&&(g=a._info.height*g),i.resize(f,g)}),i.once("complete",function(){b(!1,i.getAsDataUrl(h.type)),i.destroy()}),i.once("error",function(a){b(a||!0),i.destroy()}),void d(i,a.source.size,function(){a._info&&i.info(a._info),a._meta&&i.meta(a._meta),i.loadFromBlob(a.source)})):void b(!0)},beforeSendFile:function(b){var d,f,g=this.options.compress||this.options.resize,h=g&&g.compressSize||0,i=g&&g.noCompressIfLarger||!1;return b=this.request("get-file",b),!g||!~"image/jpeg,image/jpg".indexOf(b.type)||b.size=a&&a>0&&(a=b._info.width*a),1>=c&&c>0&&(c=b._info.height*c),d.resize(a,c)}),d.once("complete",function(){var a,c;try{a=d.getAsBlob(g.type),c=b.size,(!i||a.sizeb;b++)if(c=this._queue[b],a===c.getStatus())return c;return null},sort:function(a){"function"==typeof a&&this._queue.sort(a)},getFiles:function(){for(var a,b=[].slice.call(arguments,0),c=[],d=0,f=this._queue.length;f>d;d++)a=this._queue[d],(!b.length||~e.inArray(a.getStatus(),b))&&c.push(a);return c},removeFile:function(a){var b=this._map[a.id];b&&(delete this._map[a.id],a.destroy(),this.stats.numofDeleted++)},_fileAdded:function(a){var b=this,c=this._map[a.id];c||(this._map[a.id]=a,a.on("statuschange",function(a,c){b._onFileStatusChange(a,c)}))},_onFileStatusChange:function(a,b){var c=this.stats;switch(b){case f.PROGRESS:c.numOfProgress--;break;case f.QUEUED:c.numOfQueue--;break;case f.ERROR:c.numOfUploadFailed--;break;case f.INVALID:c.numOfInvalid--;break;case f.INTERRUPT:c.numofInterrupt--}switch(a){case f.QUEUED:c.numOfQueue++;break;case f.PROGRESS:c.numOfProgress++;break;case f.ERROR:c.numOfUploadFailed++;break;case f.COMPLETE:c.numOfSuccess++;break;case f.CANCELLED:c.numOfCancel++;break;case f.INVALID:c.numOfInvalid++;break;case f.INTERRUPT:c.numofInterrupt++}}}),b.installTo(d.prototype),d}),b("widgets/queue",["base","uploader","queue","file","lib/file","runtime/client","widgets/widget"],function(a,b,c,d,e,f){var g=a.$,h=/\.\w+$/,i=d.Status;return b.register({name:"queue",init:function(b){var d,e,h,i,j,k,l,m=this;if(g.isPlainObject(b.accept)&&(b.accept=[b.accept]),b.accept){for(j=[],h=0,e=b.accept.length;e>h;h++)i=b.accept[h].extensions,i&&j.push(i);j.length&&(k="\\."+j.join(",").replace(/,/g,"$|\\.").replace(/\*/g,".*")+"$"),m.accept=new RegExp(k,"i")}return m.queue=new c,m.stats=m.queue.stats,"html5"===this.request("predict-runtime-type")?(d=a.Deferred(),this.placeholder=l=new f("Placeholder"),l.connectRuntime({runtimeOrder:"html5"},function(){m._ruid=l.getRuid(),d.resolve()}),d.promise()):void 0},_wrapFile:function(a){if(!(a instanceof d)){if(!(a instanceof e)){if(!this._ruid)throw new Error("Can't add external files.");a=new e(this._ruid,a)}a=new d(a)}return a},acceptFile:function(a){var b=!a||!a.size||this.accept&&h.exec(a.name)&&!this.accept.test(a.name);return!b},_addFile:function(a){var b=this;return a=b._wrapFile(a),b.owner.trigger("beforeFileQueued",a)?b.acceptFile(a)?(b.queue.append(a),b.owner.trigger("fileQueued",a),a):void b.owner.trigger("error","Q_TYPE_DENIED",a):void 0},getFile:function(a){return this.queue.getFile(a)},addFile:function(a){var b=this;a.length||(a=[a]),a=g.map(a,function(a){return b._addFile(a)}),b.owner.trigger("filesQueued",a),b.options.auto&&setTimeout(function(){b.request("start-upload")},20)},getStats:function(){return this.stats},removeFile:function(a,b){var c=this;a=a.id?a:c.queue.getFile(a),this.request("cancel-file",a),b&&this.queue.removeFile(a)},getFiles:function(){return this.queue.getFiles.apply(this.queue,arguments)},fetchFile:function(){return this.queue.fetch.apply(this.queue,arguments)},retry:function(a,b){var c,d,e,f=this;if(a)return a=a.id?a:f.queue.getFile(a),a.setStatus(i.QUEUED),void(b||f.request("start-upload"));for(c=f.queue.getFiles(i.ERROR),d=0,e=c.length;e>d;d++)a=c[d],a.setStatus(i.QUEUED);f.request("start-upload")},sortFiles:function(){return this.queue.sort.apply(this.queue,arguments)},reset:function(){this.owner.trigger("reset"),this.queue=new c,this.stats=this.queue.stats},destroy:function(){this.reset(),this.placeholder&&this.placeholder.destroy()}})}),b("widgets/runtime",["uploader","runtime/runtime","widgets/widget"],function(a,b){return a.support=function(){return b.hasRuntime.apply(b,arguments)},a.register({name:"runtime",init:function(){if(!this.predictRuntimeType())throw Error("Runtime Error")},predictRuntimeType:function(){var a,c,d=this.options.runtimeOrder||b.orders,e=this.type;if(!e)for(d=d.split(/\s*,\s*/g),a=0,c=d.length;c>a;a++)if(b.hasRuntime(d[a])){this.type=e=d[a];break}return e}})}),b("lib/transport",["base","runtime/client","mediator"],function(a,b,c){function d(a){var c=this;a=c.options=e.extend(!0,{},d.options,a||{}),b.call(this,"Transport"),this._blob=null,this._formData=a.formData||{},this._headers=a.headers||{},this.on("progress",this._timeout),this.on("load error",function(){c.trigger("progress",1),clearTimeout(c._timer)})}var e=a.$;return d.options={server:"",method:"POST",withCredentials:!1,fileVal:"file",timeout:12e4,formData:{},headers:{},sendAsBinary:!1},e.extend(d.prototype,{appendBlob:function(a,b,c){var d=this,e=d.options;d.getRuid()&&d.disconnectRuntime(),d.connectRuntime(b.ruid,function(){d.exec("init")}),d._blob=b,e.fileVal=a||e.fileVal,e.filename=c||e.filename},append:function(a,b){"object"==typeof a?e.extend(this._formData,a):this._formData[a]=b},setRequestHeader:function(a,b){"object"==typeof a?e.extend(this._headers,a):this._headers[a]=b},send:function(a){this.exec("send",a),this._timeout()},abort:function(){return clearTimeout(this._timer),this.exec("abort")},destroy:function(){this.trigger("destroy"),this.off(),this.exec("destroy"),this.disconnectRuntime()},getResponse:function(){return this.exec("getResponse")},getResponseAsJson:function(){return this.exec("getResponseAsJson")},getStatus:function(){return this.exec("getStatus")},_timeout:function(){var a=this,b=a.options.timeout;b&&(clearTimeout(a._timer),a._timer=setTimeout(function(){a.abort(),a.trigger("error","timeout")},b))}}),c.installTo(d.prototype),d}),b("widgets/upload",["base","uploader","file","lib/transport","widgets/widget"],function(a,b,c,d){function e(a,b){var c,d,e=[],f=a.source,g=f.size,h=b?Math.ceil(g/b):1,i=0,j=0;for(d={file:a,has:function(){return!!e.length},shift:function(){return e.shift()},unshift:function(a){e.unshift(a)}};h>j;)c=Math.min(b,g-i),e.push({file:a,start:i,end:b?i+c:g,total:g,chunks:h,chunk:j++,cuted:d}),i+=c;return a.blocks=e.concat(),a.remaning=e.length,d}var f=a.$,g=a.isPromise,h=c.Status;f.extend(b.options,{prepareNextFile:!1,chunked:!1,chunkSize:5242880,chunkRetry:2,threads:3,formData:{}}),b.register({name:"upload",init:function(){var b=this.owner,c=this;this.runing=!1,this.progress=!1,b.on("startUpload",function(){c.progress=!0}).on("uploadFinished",function(){c.progress=!1}),this.pool=[],this.stack=[],this.pending=[],this.remaning=0,this.__tick=a.bindFn(this._tick,this),b.on("uploadComplete",function(a){a.blocks&&f.each(a.blocks,function(a,b){b.transport&&(b.transport.abort(),b.transport.destroy()),delete b.transport}),delete a.blocks,delete a.remaning})},reset:function(){this.request("stop-upload",!0),this.runing=!1,this.pool=[],this.stack=[],this.pending=[],this.remaning=0,this._trigged=!1,this._promise=null},startUpload:function(b){var c=this;if(f.each(c.request("get-files",h.INVALID),function(){c.request("remove-file",this)}),b)if(b=b.id?b:c.request("get-file",b),b.getStatus()===h.INTERRUPT)f.each(c.pool,function(a,c){c.file===b&&c.transport&&c.transport.send()}),b.setStatus(h.QUEUED);else{if(b.getStatus()===h.PROGRESS)return;b.setStatus(h.QUEUED)}else f.each(c.request("get-files",[h.INITED]),function(){this.setStatus(h.QUEUED)});if(!c.runing){c.runing=!0;var d=[];f.each(c.pool,function(a,b){var e=b.file;e.getStatus()===h.INTERRUPT&&(d.push(e),c._trigged=!1,b.transport&&b.transport.send())});for(var b;b=d.shift();)b.setStatus(h.PROGRESS);b||f.each(c.request("get-files",h.INTERRUPT),function(){this.setStatus(h.PROGRESS)}),c._trigged=!1,a.nextTick(c.__tick),c.owner.trigger("startUpload")}},stopUpload:function(b,c){var d=this;if(b===!0&&(c=b,b=null),d.runing!==!1){if(b){if(b=b.id?b:d.request("get-file",b),b.getStatus()!==h.PROGRESS&&b.getStatus()!==h.QUEUED)return;return b.setStatus(h.INTERRUPT),f.each(d.pool,function(a,c){c.file===b&&(c.transport&&c.transport.abort(),d._putback(c),d._popBlock(c))}),a.nextTick(d.__tick)}d.runing=!1,this._promise&&this._promise.file&&this._promise.file.setStatus(h.INTERRUPT),c&&f.each(d.pool,function(a,b){b.transport&&b.transport.abort(),b.file.setStatus(h.INTERRUPT)}),d.owner.trigger("stopUpload")}},cancelFile:function(a){a=a.id?a:this.request("get-file",a),a.blocks&&f.each(a.blocks,function(a,b){var c=b.transport;c&&(c.abort(),c.destroy(),delete b.transport)}),a.setStatus(h.CANCELLED),this.owner.trigger("fileDequeued",a)},isInProgress:function(){return!!this.progress},_getStats:function(){return this.request("get-stats")},skipFile:function(a,b){a=a.id?a:this.request("get-file",a),a.setStatus(b||h.COMPLETE),a.skipped=!0,a.blocks&&f.each(a.blocks,function(a,b){var c=b.transport;c&&(c.abort(),c.destroy(),delete b.transport)}),this.owner.trigger("uploadSkip",a)},_tick:function(){var b,c,d=this,e=d.options;return d._promise?d._promise.always(d.__tick):void(d.pool.length1&&~"http,abort".indexOf(a)&&b.retried1&&f.extend(m,{chunks:b.chunks,chunk:b.chunk}),i.trigger("uploadBeforeSend",b,m,n),l.appendBlob(j.fileVal,b.blob,k.name),l.append(m),l.setRequestHeader(n),l.send()},_finishFile:function(a,b,c){var d=this.owner;return d.request("after-send-file",arguments,function(){a.setStatus(h.COMPLETE),d.trigger("uploadSuccess",a,b,c)}).fail(function(b){a.getStatus()===h.PROGRESS&&a.setStatus(h.ERROR,b),d.trigger("uploadError",a,b) +}).always(function(){d.trigger("uploadComplete",a)})},updateFileProgress:function(a){var b=0,c=0;a.blocks&&(f.each(a.blocks,function(a,b){c+=(b.percentage||0)*(b.end-b.start)}),b=c/a.size,this.owner.trigger("uploadProgress",a,b||0))}})}),b("widgets/validator",["base","uploader","file","widgets/widget"],function(a,b,c){var d,e=a.$,f={};return d={addValidator:function(a,b){f[a]=b},removeValidator:function(a){delete f[a]}},b.register({name:"validator",init:function(){var b=this;a.nextTick(function(){e.each(f,function(){this.call(b.owner)})})}}),d.addValidator("fileNumLimit",function(){var a=this,b=a.options,c=0,d=parseInt(b.fileNumLimit,10),e=!0;d&&(a.on("beforeFileQueued",function(a){return c>=d&&e&&(e=!1,this.trigger("error","Q_EXCEED_NUM_LIMIT",d,a),setTimeout(function(){e=!0},1)),c>=d?!1:!0}),a.on("fileQueued",function(){c++}),a.on("fileDequeued",function(){c--}),a.on("reset",function(){c=0}))}),d.addValidator("fileSizeLimit",function(){var a=this,b=a.options,c=0,d=parseInt(b.fileSizeLimit,10),e=!0;d&&(a.on("beforeFileQueued",function(a){var b=c+a.size>d;return b&&e&&(e=!1,this.trigger("error","Q_EXCEED_SIZE_LIMIT",d,a),setTimeout(function(){e=!0},1)),b?!1:!0}),a.on("fileQueued",function(a){c+=a.size}),a.on("fileDequeued",function(a){c-=a.size}),a.on("reset",function(){c=0}))}),d.addValidator("fileSingleSizeLimit",function(){var a=this,b=a.options,d=b.fileSingleSizeLimit;d&&a.on("beforeFileQueued",function(a){return a.size>d?(a.setStatus(c.Status.INVALID,"exceed_size"),this.trigger("error","F_EXCEED_SIZE",d,a),!1):void 0})}),d.addValidator("duplicate",function(){function a(a){for(var b,c=0,d=0,e=a.length;e>d;d++)b=a.charCodeAt(d),c=b+(c<<6)+(c<<16)-c;return c}var b=this,c=b.options,d={};c.duplicate||(b.on("beforeFileQueued",function(b){var c=b.__hash||(b.__hash=a(b.name+b.size+b.lastModifiedDate));return d[c]?(this.trigger("error","F_DUPLICATE",b),!1):void 0}),b.on("fileQueued",function(a){var b=a.__hash;b&&(d[b]=!0)}),b.on("fileDequeued",function(a){var b=a.__hash;b&&delete d[b]}),b.on("reset",function(){d={}}))}),d}),b("lib/md5",["runtime/client","mediator"],function(a,b){function c(){a.call(this,"Md5")}return b.installTo(c.prototype),c.prototype.loadFromBlob=function(a){var b=this;b.getRuid()&&b.disconnectRuntime(),b.connectRuntime(a.ruid,function(){b.exec("init"),b.exec("loadFromBlob",a)})},c.prototype.getResult=function(){return this.exec("getResult")},c}),b("widgets/md5",["base","uploader","lib/md5","lib/blob","widgets/widget"],function(a,b,c,d){return b.register({name:"md5",md5File:function(b,e,f){var g=new c,h=a.Deferred(),i=b instanceof d?b:this.request("get-file",b).source;return g.on("progress load",function(a){a=a||{},h.notify(a.total?a.loaded/a.total:1)}),g.on("complete",function(){h.resolve(g.getResult())}),g.on("error",function(a){h.reject(a)}),arguments.length>1&&(e=e||0,f=f||0,0>e&&(e=i.size+e),0>f&&(f=i.size+f),f=Math.min(f,i.size),i=i.slice(e,f)),g.loadFromBlob(i),h.promise()}})}),b("runtime/compbase",[],function(){function a(a,b){this.owner=a,this.options=a.options,this.getRuntime=function(){return b},this.getRuid=function(){return b.uid},this.trigger=function(){return a.trigger.apply(a,arguments)}}return a}),b("runtime/html5/runtime",["base","runtime/runtime","runtime/compbase"],function(b,c,d){function e(){var a={},d=this,e=this.destroy;c.apply(d,arguments),d.type=f,d.exec=function(c,e){var f,h=this,i=h.uid,j=b.slice(arguments,2);return g[c]&&(f=a[i]=a[i]||new g[c](h,d),f[e])?f[e].apply(f,j):void 0},d.destroy=function(){return e&&e.apply(this,arguments)}}var f="html5",g={};return b.inherits(c,{constructor:e,init:function(){var a=this;setTimeout(function(){a.trigger("ready")},1)}}),e.register=function(a,c){var e=g[a]=b.inherits(d,c);return e},a.Blob&&a.FileReader&&a.DataView&&c.addRuntime(f,e),e}),b("runtime/html5/blob",["runtime/html5/runtime","lib/blob"],function(a,b){return a.register("Blob",{slice:function(a,c){var d=this.owner.source,e=d.slice||d.webkitSlice||d.mozSlice;return d=e.call(d,a,c),new b(this.getRuid(),d)}})}),b("runtime/html5/dnd",["base","runtime/html5/runtime","lib/file"],function(a,b,c){var d=a.$,e="webuploader-dnd-";return b.register("DragAndDrop",{init:function(){var b=this.elem=this.options.container;this.dragEnterHandler=a.bindFn(this._dragEnterHandler,this),this.dragOverHandler=a.bindFn(this._dragOverHandler,this),this.dragLeaveHandler=a.bindFn(this._dragLeaveHandler,this),this.dropHandler=a.bindFn(this._dropHandler,this),this.dndOver=!1,b.on("dragenter",this.dragEnterHandler),b.on("dragover",this.dragOverHandler),b.on("dragleave",this.dragLeaveHandler),b.on("drop",this.dropHandler),this.options.disableGlobalDnd&&(d(document).on("dragover",this.dragOverHandler),d(document).on("drop",this.dropHandler))},_dragEnterHandler:function(a){var b,c=this,d=c._denied||!1;return a=a.originalEvent||a,c.dndOver||(c.dndOver=!0,b=a.dataTransfer.items,b&&b.length&&(c._denied=d=!c.trigger("accept",b)),c.elem.addClass(e+"over"),c.elem[d?"addClass":"removeClass"](e+"denied")),a.dataTransfer.dropEffect=d?"none":"copy",!1},_dragOverHandler:function(a){var b=this.elem.parent().get(0);return b&&!d.contains(b,a.currentTarget)?!1:(clearTimeout(this._leaveTimer),this._dragEnterHandler.call(this,a),!1)},_dragLeaveHandler:function(){var a,b=this;return a=function(){b.dndOver=!1,b.elem.removeClass(e+"over "+e+"denied")},clearTimeout(b._leaveTimer),b._leaveTimer=setTimeout(a,100),!1},_dropHandler:function(a){var b,f,g=this,h=g.getRuid(),i=g.elem.parent().get(0);if(i&&!d.contains(i,a.currentTarget))return!1;a=a.originalEvent||a,b=a.dataTransfer;try{f=b.getData("text/html")}catch(j){}return f?void 0:(g._getTansferFiles(b,function(a){g.trigger("drop",d.map(a,function(a){return new c(h,a)}))}),g.dndOver=!1,g.elem.removeClass(e+"over"),!1)},_getTansferFiles:function(b,c){var d,e,f,g,h,i,j,k=[],l=[];for(d=b.items,e=b.files,j=!(!d||!d[0].webkitGetAsEntry),h=0,i=e.length;i>h;h++)f=e[h],g=d&&d[h],j&&g.webkitGetAsEntry().isDirectory?l.push(this._traverseDirectoryTree(g.webkitGetAsEntry(),k)):k.push(f);a.when.apply(a,l).done(function(){k.length&&c(k)})},_traverseDirectoryTree:function(b,c){var d=a.Deferred(),e=this;return b.isFile?b.file(function(a){c.push(a),d.resolve()}):b.isDirectory&&b.createReader().readEntries(function(b){var f,g=b.length,h=[],i=[];for(f=0;g>f;f++)h.push(e._traverseDirectoryTree(b[f],i));a.when.apply(a,h).then(function(){c.push.apply(c,i),d.resolve()},d.reject)}),d.promise()},destroy:function(){var a=this.elem;a&&(a.off("dragenter",this.dragEnterHandler),a.off("dragover",this.dragOverHandler),a.off("dragleave",this.dragLeaveHandler),a.off("drop",this.dropHandler),this.options.disableGlobalDnd&&(d(document).off("dragover",this.dragOverHandler),d(document).off("drop",this.dropHandler)))}})}),b("runtime/html5/filepaste",["base","runtime/html5/runtime","lib/file"],function(a,b,c){return b.register("FilePaste",{init:function(){var b,c,d,e,f=this.options,g=this.elem=f.container,h=".*";if(f.accept){for(b=[],c=0,d=f.accept.length;d>c;c++)e=f.accept[c].mimeTypes,e&&b.push(e);b.length&&(h=b.join(","),h=h.replace(/,/g,"|").replace(/\*/g,".*"))}this.accept=h=new RegExp(h,"i"),this.hander=a.bindFn(this._pasteHander,this),g.on("paste",this.hander)},_pasteHander:function(a){var b,d,e,f,g,h=[],i=this.getRuid();for(a=a.originalEvent||a,b=a.clipboardData.items,f=0,g=b.length;g>f;f++)d=b[f],"file"===d.kind&&(e=d.getAsFile())&&h.push(new c(i,e));h.length&&(a.preventDefault(),a.stopPropagation(),this.trigger("paste",h))},destroy:function(){this.elem.off("paste",this.hander)}})}),b("runtime/html5/filepicker",["base","runtime/html5/runtime"],function(a,b){var c=a.$;return b.register("FilePicker",{init:function(){var a,b,d,e,f=this.getRuntime().getContainer(),g=this,h=g.owner,i=g.options,j=this.label=c(document.createElement("label")),k=this.input=c(document.createElement("input"));if(k.attr("type","file"),k.attr("name",i.name),k.addClass("webuploader-element-invisible"),j.on("click",function(){k.trigger("click")}),j.css({opacity:0,width:"100%",height:"100%",display:"block",cursor:"pointer",background:"#ffffff"}),i.multiple&&k.attr("multiple","multiple"),i.accept&&i.accept.length>0){for(a=[],b=0,d=i.accept.length;d>b;b++)a.push(i.accept[b].mimeTypes);k.attr("accept",a.join(","))}f.append(k),f.append(j),e=function(a){h.trigger(a.type)},k.on("change",function(a){var b,d=arguments.callee;g.files=a.target.files,b=this.cloneNode(!0),b.value=null,this.parentNode.replaceChild(b,this),k.off(),k=c(b).on("change",d).on("mouseenter mouseleave",e),h.trigger("change")}),j.on("mouseenter mouseleave",e)},getFiles:function(){return this.files},destroy:function(){this.input.off(),this.label.off()}})}),b("runtime/html5/util",["base"],function(b){var c=a.createObjectURL&&a||a.URL&&URL.revokeObjectURL&&URL||a.webkitURL,d=b.noop,e=d;return c&&(d=function(){return c.createObjectURL.apply(c,arguments)},e=function(){return c.revokeObjectURL.apply(c,arguments)}),{createObjectURL:d,revokeObjectURL:e,dataURL2Blob:function(a){var b,c,d,e,f,g;for(g=a.split(","),b=~g[0].indexOf("base64")?atob(g[1]):decodeURIComponent(g[1]),d=new ArrayBuffer(b.length),c=new Uint8Array(d),e=0;ei&&(d=h.getUint16(i),d>=65504&&65519>=d||65534===d)&&(e=h.getUint16(i+2)+2,!(i+e>h.byteLength));){if(f=b.parsers[d],!c&&f)for(g=0;g6&&(l.imageHead=a.slice?a.slice(2,k):new Uint8Array(a).subarray(2,k))}return l}},updateImageHead:function(a,b){var c,d,e,f=this._parse(a,!0);return e=2,f.imageHead&&(e=2+f.imageHead.byteLength),d=a.slice?a.slice(e):new Uint8Array(a).subarray(e),c=new Uint8Array(b.byteLength+2+d.byteLength),c[0]=255,c[1]=216,c.set(new Uint8Array(b),2),c.set(new Uint8Array(d),b.byteLength+2),c.buffer}},a.parseMeta=function(){return b.parse.apply(b,arguments)},a.updateImageHead=function(){return b.updateImageHead.apply(b,arguments)},b}),b("runtime/html5/imagemeta/exif",["base","runtime/html5/imagemeta"],function(a,b){var c={};return c.ExifMap=function(){return this},c.ExifMap.prototype.map={Orientation:274},c.ExifMap.prototype.get=function(a){return this[a]||this[this.map[a]]},c.exifTagTypes={1:{getValue:function(a,b){return a.getUint8(b)},size:1},2:{getValue:function(a,b){return String.fromCharCode(a.getUint8(b))},size:1,ascii:!0},3:{getValue:function(a,b,c){return a.getUint16(b,c)},size:2},4:{getValue:function(a,b,c){return a.getUint32(b,c)},size:4},5:{getValue:function(a,b,c){return a.getUint32(b,c)/a.getUint32(b+4,c)},size:8},9:{getValue:function(a,b,c){return a.getInt32(b,c)},size:4},10:{getValue:function(a,b,c){return a.getInt32(b,c)/a.getInt32(b+4,c)},size:8}},c.exifTagTypes[7]=c.exifTagTypes[1],c.getExifValue=function(b,d,e,f,g,h){var i,j,k,l,m,n,o=c.exifTagTypes[f];if(!o)return void a.log("Invalid Exif data: Invalid tag type.");if(i=o.size*g,j=i>4?d+b.getUint32(e+8,h):e+8,j+i>b.byteLength)return void a.log("Invalid Exif data: Invalid data offset.");if(1===g)return o.getValue(b,j,h);for(k=[],l=0;g>l;l+=1)k[l]=o.getValue(b,j+l*o.size,h);if(o.ascii){for(m="",l=0;lb.byteLength)return void a.log("Invalid Exif data: Invalid directory offset.");if(g=b.getUint16(d,e),h=d+2+12*g,h+4>b.byteLength)return void a.log("Invalid Exif data: Invalid directory size.");for(i=0;g>i;i+=1)this.parseExifTag(b,c,d+2+12*i,e,f);return b.getUint32(h,e)},c.parseExifData=function(b,d,e,f){var g,h,i=d+10;if(1165519206===b.getUint32(d+4)){if(i+8>b.byteLength)return void a.log("Invalid Exif data: Invalid segment size.");if(0!==b.getUint16(d+8))return void a.log("Invalid Exif data: Missing byte alignment offset.");switch(b.getUint16(i)){case 18761:g=!0;break;case 19789:g=!1;break;default:return void a.log("Invalid Exif data: Invalid byte alignment marker.")}if(42!==b.getUint16(i+2,g))return void a.log("Invalid Exif data: Missing TIFF marker.");h=b.getUint32(i+4,g),f.exif=new c.ExifMap,h=c.parseExifTags(b,i,i+h,g,f)}},b.parsers[65505].push(c.parseExifData),c}),b("runtime/html5/jpegencoder",[],function(){function a(a){function b(a){for(var b=[16,11,10,16,24,40,51,61,12,12,14,19,26,58,60,55,14,13,16,24,40,57,69,56,14,17,22,29,51,87,80,62,18,22,37,56,68,109,103,77,24,35,55,64,81,104,113,92,49,64,78,87,103,121,120,101,72,92,95,98,112,100,103,99],c=0;64>c;c++){var d=y((b[c]*a+50)/100);1>d?d=1:d>255&&(d=255),z[P[c]]=d}for(var e=[17,18,24,47,99,99,99,99,18,21,26,66,99,99,99,99,24,26,56,99,99,99,99,99,47,66,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99],f=0;64>f;f++){var g=y((e[f]*a+50)/100);1>g?g=1:g>255&&(g=255),A[P[f]]=g}for(var h=[1,1.387039845,1.306562965,1.175875602,1,.785694958,.5411961,.275899379],i=0,j=0;8>j;j++)for(var k=0;8>k;k++)B[i]=1/(z[P[i]]*h[j]*h[k]*8),C[i]=1/(A[P[i]]*h[j]*h[k]*8),i++}function c(a,b){for(var c=0,d=0,e=new Array,f=1;16>=f;f++){for(var g=1;g<=a[f];g++)e[b[d]]=[],e[b[d]][0]=c,e[b[d]][1]=f,d++,c++;c*=2}return e}function d(){t=c(Q,R),u=c(U,V),v=c(S,T),w=c(W,X)}function e(){for(var a=1,b=2,c=1;15>=c;c++){for(var d=a;b>d;d++)E[32767+d]=c,D[32767+d]=[],D[32767+d][1]=c,D[32767+d][0]=d;for(var e=-(b-1);-a>=e;e++)E[32767+e]=c,D[32767+e]=[],D[32767+e][1]=c,D[32767+e][0]=b-1+e;a<<=1,b<<=1}}function f(){for(var a=0;256>a;a++)O[a]=19595*a,O[a+256>>0]=38470*a,O[a+512>>0]=7471*a+32768,O[a+768>>0]=-11059*a,O[a+1024>>0]=-21709*a,O[a+1280>>0]=32768*a+8421375,O[a+1536>>0]=-27439*a,O[a+1792>>0]=-5329*a}function g(a){for(var b=a[0],c=a[1]-1;c>=0;)b&1<J&&(255==I?(h(255),h(0)):h(I),J=7,I=0)}function h(a){H.push(N[a])}function i(a){h(a>>8&255),h(255&a)}function j(a,b){var c,d,e,f,g,h,i,j,k,l=0,m=8,n=64;for(k=0;m>k;++k){c=a[l],d=a[l+1],e=a[l+2],f=a[l+3],g=a[l+4],h=a[l+5],i=a[l+6],j=a[l+7];var o=c+j,p=c-j,q=d+i,r=d-i,s=e+h,t=e-h,u=f+g,v=f-g,w=o+u,x=o-u,y=q+s,z=q-s;a[l]=w+y,a[l+4]=w-y;var A=.707106781*(z+x);a[l+2]=x+A,a[l+6]=x-A,w=v+t,y=t+r,z=r+p;var B=.382683433*(w-z),C=.5411961*w+B,D=1.306562965*z+B,E=.707106781*y,G=p+E,H=p-E;a[l+5]=H+C,a[l+3]=H-C,a[l+1]=G+D,a[l+7]=G-D,l+=8}for(l=0,k=0;m>k;++k){c=a[l],d=a[l+8],e=a[l+16],f=a[l+24],g=a[l+32],h=a[l+40],i=a[l+48],j=a[l+56];var I=c+j,J=c-j,K=d+i,L=d-i,M=e+h,N=e-h,O=f+g,P=f-g,Q=I+O,R=I-O,S=K+M,T=K-M;a[l]=Q+S,a[l+32]=Q-S;var U=.707106781*(T+R);a[l+16]=R+U,a[l+48]=R-U,Q=P+N,S=N+L,T=L+J;var V=.382683433*(Q-T),W=.5411961*Q+V,X=1.306562965*T+V,Y=.707106781*S,Z=J+Y,$=J-Y;a[l+40]=$+W,a[l+24]=$-W,a[l+8]=Z+X,a[l+56]=Z-X,l++}var _;for(k=0;n>k;++k)_=a[k]*b[k],F[k]=_>0?_+.5|0:_-.5|0;return F}function k(){i(65504),i(16),h(74),h(70),h(73),h(70),h(0),h(1),h(1),h(0),i(1),i(1),h(0),h(0)}function l(a,b){i(65472),i(17),h(8),i(b),i(a),h(3),h(1),h(17),h(0),h(2),h(17),h(1),h(3),h(17),h(1)}function m(){i(65499),i(132),h(0);for(var a=0;64>a;a++)h(z[a]);h(1);for(var b=0;64>b;b++)h(A[b])}function n(){i(65476),i(418),h(0);for(var a=0;16>a;a++)h(Q[a+1]);for(var b=0;11>=b;b++)h(R[b]);h(16);for(var c=0;16>c;c++)h(S[c+1]);for(var d=0;161>=d;d++)h(T[d]);h(1);for(var e=0;16>e;e++)h(U[e+1]);for(var f=0;11>=f;f++)h(V[f]);h(17);for(var g=0;16>g;g++)h(W[g+1]);for(var j=0;161>=j;j++)h(X[j])}function o(){i(65498),i(12),h(3),h(1),h(0),h(2),h(17),h(3),h(17),h(0),h(63),h(0)}function p(a,b,c,d,e){for(var f,h=e[0],i=e[240],k=16,l=63,m=64,n=j(a,b),o=0;m>o;++o)G[P[o]]=n[o];var p=G[0]-c;c=G[0],0==p?g(d[0]):(f=32767+p,g(d[E[f]]),g(D[f]));for(var q=63;q>0&&0==G[q];q--);if(0==q)return g(h),c;for(var r,s=1;q>=s;){for(var t=s;0==G[s]&&q>=s;++s);var u=s-t;if(u>=k){r=u>>4;for(var v=1;r>=v;++v)g(i);u=15&u}f=32767+G[s],g(e[(u<<4)+E[f]]),g(D[f]),s++}return q!=l&&g(h),c}function q(){for(var a=String.fromCharCode,b=0;256>b;b++)N[b]=a(b)}function r(a){if(0>=a&&(a=1),a>100&&(a=100),x!=a){var c=0;c=Math.floor(50>a?5e3/a:200-2*a),b(c),x=a}}function s(){a||(a=50),q(),d(),e(),f(),r(a)}var t,u,v,w,x,y=(Math.round,Math.floor),z=new Array(64),A=new Array(64),B=new Array(64),C=new Array(64),D=new Array(65535),E=new Array(65535),F=new Array(64),G=new Array(64),H=[],I=0,J=7,K=new Array(64),L=new Array(64),M=new Array(64),N=new Array(256),O=new Array(2048),P=[0,1,5,6,14,15,27,28,2,4,7,13,16,26,29,42,3,8,12,17,25,30,41,43,9,11,18,24,31,40,44,53,10,19,23,32,39,45,52,54,20,22,33,38,46,51,55,60,21,34,37,47,50,56,59,61,35,36,48,49,57,58,62,63],Q=[0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0],R=[0,1,2,3,4,5,6,7,8,9,10,11],S=[0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,125],T=[1,2,3,0,4,17,5,18,33,49,65,6,19,81,97,7,34,113,20,50,129,145,161,8,35,66,177,193,21,82,209,240,36,51,98,114,130,9,10,22,23,24,25,26,37,38,39,40,41,42,52,53,54,55,56,57,58,67,68,69,70,71,72,73,74,83,84,85,86,87,88,89,90,99,100,101,102,103,104,105,106,115,116,117,118,119,120,121,122,131,132,133,134,135,136,137,138,146,147,148,149,150,151,152,153,154,162,163,164,165,166,167,168,169,170,178,179,180,181,182,183,184,185,186,194,195,196,197,198,199,200,201,202,210,211,212,213,214,215,216,217,218,225,226,227,228,229,230,231,232,233,234,241,242,243,244,245,246,247,248,249,250],U=[0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0],V=[0,1,2,3,4,5,6,7,8,9,10,11],W=[0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,119],X=[0,1,2,3,17,4,5,33,49,6,18,65,81,7,97,113,19,34,50,129,8,20,66,145,161,177,193,9,35,51,82,240,21,98,114,209,10,22,36,52,225,37,241,23,24,25,26,38,39,40,41,42,53,54,55,56,57,58,67,68,69,70,71,72,73,74,83,84,85,86,87,88,89,90,99,100,101,102,103,104,105,106,115,116,117,118,119,120,121,122,130,131,132,133,134,135,136,137,138,146,147,148,149,150,151,152,153,154,162,163,164,165,166,167,168,169,170,178,179,180,181,182,183,184,185,186,194,195,196,197,198,199,200,201,202,210,211,212,213,214,215,216,217,218,226,227,228,229,230,231,232,233,234,242,243,244,245,246,247,248,249,250];this.encode=function(a,b){b&&r(b),H=new Array,I=0,J=7,i(65496),k(),m(),l(a.width,a.height),n(),o();var c=0,d=0,e=0;I=0,J=7,this.encode.displayName="_encode_";for(var f,h,j,q,s,x,y,z,A,D=a.data,E=a.width,F=a.height,G=4*E,N=0;F>N;){for(f=0;G>f;){for(s=G*N+f,x=s,y=-1,z=0,A=0;64>A;A++)z=A>>3,y=4*(7&A),x=s+z*G+y,N+z>=F&&(x-=G*(N+1+z-F)),f+y>=G&&(x-=f+y-G+4),h=D[x++],j=D[x++],q=D[x++],K[A]=(O[h]+O[j+256>>0]+O[q+512>>0]>>16)-128,L[A]=(O[h+768>>0]+O[j+1024>>0]+O[q+1280>>0]>>16)-128,M[A]=(O[h+1280>>0]+O[j+1536>>0]+O[q+1792>>0]>>16)-128;c=p(K,B,c,t,v),d=p(L,C,d,u,w),e=p(M,C,e,u,w),f+=32}N+=8}if(J>=0){var P=[];P[1]=J+1,P[0]=(1<i;)e=d[4*(k-1)+3],0===e?j=k:i=k,k=j+i>>1;return f=k/c,0===f?1:f}function c(a){var b,c,d=a.naturalWidth,e=a.naturalHeight;return d*e>1048576?(b=document.createElement("canvas"),b.width=b.height=1,c=b.getContext("2d"),c.drawImage(a,-d+1,0),0===c.getImageData(0,0,1,1).data[3]):!1}return a.os.ios?a.os.ios>=7?function(a,c,d,e,f,g){var h=c.naturalWidth,i=c.naturalHeight,j=b(c,h,i);return a.getContext("2d").drawImage(c,0,0,h*j,i*j,d,e,f,g)}:function(a,d,e,f,g,h){var i,j,k,l,m,n,o,p=d.naturalWidth,q=d.naturalHeight,r=a.getContext("2d"),s=c(d),t="image/jpeg"===this.type,u=1024,v=0,w=0;for(s&&(p/=2,q/=2),r.save(),i=document.createElement("canvas"),i.width=i.height=u,j=i.getContext("2d"),k=t?b(d,p,q):1,l=Math.ceil(u*g/p),m=Math.ceil(u*h/q/k);q>v;){for(n=0,o=0;p>n;)j.clearRect(0,0,u,u),j.drawImage(d,-n,-v),r.drawImage(i,0,0,u,u,e+o,f+w,l,m),n+=u,o+=l;v+=u,w+=m}r.restore(),i=j=null}:function(b){var c=a.slice(arguments,1),d=b.getContext("2d");d.drawImage.apply(d,c)}}()})}),b("runtime/html5/transport",["base","runtime/html5/runtime"],function(a,b){var c=a.noop,d=a.$;return b.register("Transport",{init:function(){this._status=0,this._response=null},send:function(){var b,c,e,f=this.owner,g=this.options,h=this._initAjax(),i=f._blob,j=g.server;g.sendAsBinary?(j+=(/\?/.test(j)?"&":"?")+d.param(f._formData),c=i.getSource()):(b=new FormData,d.each(f._formData,function(a,c){b.append(a,c)}),b.append(g.fileVal,i.getSource(),g.filename||f._formData.name||"")),g.withCredentials&&"withCredentials"in h?(h.open(g.method,j,!0),h.withCredentials=!0):h.open(g.method,j),this._setRequestHeader(h,g.headers),c?(h.overrideMimeType&&h.overrideMimeType("application/octet-stream"),a.os.android?(e=new FileReader,e.onload=function(){h.send(this.result),e=e.onload=null},e.readAsArrayBuffer(c)):h.send(c)):h.send(b)},getResponse:function(){return this._response},getResponseAsJson:function(){return this._parseJson(this._response)},getStatus:function(){return this._status},abort:function(){var a=this._xhr;a&&(a.upload.onprogress=c,a.onreadystatechange=c,a.abort(),this._xhr=a=null)},destroy:function(){this.abort()},_initAjax:function(){var a=this,b=new XMLHttpRequest,d=this.options;return!d.withCredentials||"withCredentials"in b||"undefined"==typeof XDomainRequest||(b=new XDomainRequest),b.upload.onprogress=function(b){var c=0;return b.lengthComputable&&(c=b.loaded/b.total),a.trigger("progress",c)},b.onreadystatechange=function(){return 4===b.readyState?(b.upload.onprogress=c,b.onreadystatechange=c,a._xhr=null,a._status=b.status,b.status>=200&&b.status<300?(a._response=b.responseText,a.trigger("load")):b.status>=500&&b.status<600?(a._response=b.responseText,a.trigger("error","server")):a.trigger("error",a._status?"http":"abort")):void 0},a._xhr=b,b},_setRequestHeader:function(a,b){d.each(b,function(b,c){a.setRequestHeader(b,c)})},_parseJson:function(a){var b;try{b=JSON.parse(a)}catch(c){b={}}return b}})}),b("runtime/html5/md5",["runtime/html5/runtime"],function(a){var b=function(a,b){return a+b&4294967295},c=function(a,c,d,e,f,g){return c=b(b(c,a),b(e,g)),b(c<>>32-f,d)},d=function(a,b,d,e,f,g,h){return c(b&d|~b&e,a,b,f,g,h)},e=function(a,b,d,e,f,g,h){return c(b&e|d&~e,a,b,f,g,h)},f=function(a,b,d,e,f,g,h){return c(b^d^e,a,b,f,g,h)},g=function(a,b,d,e,f,g,h){return c(d^(b|~e),a,b,f,g,h)},h=function(a,c){var h=a[0],i=a[1],j=a[2],k=a[3];h=d(h,i,j,k,c[0],7,-680876936),k=d(k,h,i,j,c[1],12,-389564586),j=d(j,k,h,i,c[2],17,606105819),i=d(i,j,k,h,c[3],22,-1044525330),h=d(h,i,j,k,c[4],7,-176418897),k=d(k,h,i,j,c[5],12,1200080426),j=d(j,k,h,i,c[6],17,-1473231341),i=d(i,j,k,h,c[7],22,-45705983),h=d(h,i,j,k,c[8],7,1770035416),k=d(k,h,i,j,c[9],12,-1958414417),j=d(j,k,h,i,c[10],17,-42063),i=d(i,j,k,h,c[11],22,-1990404162),h=d(h,i,j,k,c[12],7,1804603682),k=d(k,h,i,j,c[13],12,-40341101),j=d(j,k,h,i,c[14],17,-1502002290),i=d(i,j,k,h,c[15],22,1236535329),h=e(h,i,j,k,c[1],5,-165796510),k=e(k,h,i,j,c[6],9,-1069501632),j=e(j,k,h,i,c[11],14,643717713),i=e(i,j,k,h,c[0],20,-373897302),h=e(h,i,j,k,c[5],5,-701558691),k=e(k,h,i,j,c[10],9,38016083),j=e(j,k,h,i,c[15],14,-660478335),i=e(i,j,k,h,c[4],20,-405537848),h=e(h,i,j,k,c[9],5,568446438),k=e(k,h,i,j,c[14],9,-1019803690),j=e(j,k,h,i,c[3],14,-187363961),i=e(i,j,k,h,c[8],20,1163531501),h=e(h,i,j,k,c[13],5,-1444681467),k=e(k,h,i,j,c[2],9,-51403784),j=e(j,k,h,i,c[7],14,1735328473),i=e(i,j,k,h,c[12],20,-1926607734),h=f(h,i,j,k,c[5],4,-378558),k=f(k,h,i,j,c[8],11,-2022574463),j=f(j,k,h,i,c[11],16,1839030562),i=f(i,j,k,h,c[14],23,-35309556),h=f(h,i,j,k,c[1],4,-1530992060),k=f(k,h,i,j,c[4],11,1272893353),j=f(j,k,h,i,c[7],16,-155497632),i=f(i,j,k,h,c[10],23,-1094730640),h=f(h,i,j,k,c[13],4,681279174),k=f(k,h,i,j,c[0],11,-358537222),j=f(j,k,h,i,c[3],16,-722521979),i=f(i,j,k,h,c[6],23,76029189),h=f(h,i,j,k,c[9],4,-640364487),k=f(k,h,i,j,c[12],11,-421815835),j=f(j,k,h,i,c[15],16,530742520),i=f(i,j,k,h,c[2],23,-995338651),h=g(h,i,j,k,c[0],6,-198630844),k=g(k,h,i,j,c[7],10,1126891415),j=g(j,k,h,i,c[14],15,-1416354905),i=g(i,j,k,h,c[5],21,-57434055),h=g(h,i,j,k,c[12],6,1700485571),k=g(k,h,i,j,c[3],10,-1894986606),j=g(j,k,h,i,c[10],15,-1051523),i=g(i,j,k,h,c[1],21,-2054922799),h=g(h,i,j,k,c[8],6,1873313359),k=g(k,h,i,j,c[15],10,-30611744),j=g(j,k,h,i,c[6],15,-1560198380),i=g(i,j,k,h,c[13],21,1309151649),h=g(h,i,j,k,c[4],6,-145523070),k=g(k,h,i,j,c[11],10,-1120210379),j=g(j,k,h,i,c[2],15,718787259),i=g(i,j,k,h,c[9],21,-343485551),a[0]=b(h,a[0]),a[1]=b(i,a[1]),a[2]=b(j,a[2]),a[3]=b(k,a[3])},i=function(a){var b,c=[];for(b=0;64>b;b+=4)c[b>>2]=a.charCodeAt(b)+(a.charCodeAt(b+1)<<8)+(a.charCodeAt(b+2)<<16)+(a.charCodeAt(b+3)<<24);return c},j=function(a){var b,c=[];for(b=0;64>b;b+=4)c[b>>2]=a[b]+(a[b+1]<<8)+(a[b+2]<<16)+(a[b+3]<<24);return c},k=function(a){var b,c,d,e,f,g,j=a.length,k=[1732584193,-271733879,-1732584194,271733878];for(b=64;j>=b;b+=64)h(k,i(a.substring(b-64,b)));for(a=a.substring(b-64),c=a.length,d=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],b=0;c>b;b+=1)d[b>>2]|=a.charCodeAt(b)<<(b%4<<3);if(d[b>>2]|=128<<(b%4<<3),b>55)for(h(k,d),b=0;16>b;b+=1)d[b]=0;return e=8*j,e=e.toString(16).match(/(.*?)(.{0,8})$/),f=parseInt(e[2],16),g=parseInt(e[1],16)||0,d[14]=f,d[15]=g,h(k,d),k},l=function(a){var b,c,d,e,f,g,i=a.length,k=[1732584193,-271733879,-1732584194,271733878];for(b=64;i>=b;b+=64)h(k,j(a.subarray(b-64,b)));for(a=i>b-64?a.subarray(b-64):new Uint8Array(0),c=a.length,d=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],b=0;c>b;b+=1)d[b>>2]|=a[b]<<(b%4<<3);if(d[b>>2]|=128<<(b%4<<3),b>55)for(h(k,d),b=0;16>b;b+=1)d[b]=0;return e=8*i,e=e.toString(16).match(/(.*?)(.{0,8})$/),f=parseInt(e[2],16),g=parseInt(e[1],16)||0,d[14]=f,d[15]=g,h(k,d),k},m=["0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"],n=function(a){var b,c="";for(b=0;4>b;b+=1)c+=m[a>>8*b+4&15]+m[a>>8*b&15];return c},o=function(a){var b;for(b=0;b>16)+(b>>16)+(c>>16);return d<<16|65535&c}),q.prototype.append=function(a){return/[\u0080-\uFFFF]/.test(a)&&(a=unescape(encodeURIComponent(a))),this.appendBinary(a),this},q.prototype.appendBinary=function(a){this._buff+=a,this._length+=a.length;var b,c=this._buff.length;for(b=64;c>=b;b+=64)h(this._state,i(this._buff.substring(b-64,b)));return this._buff=this._buff.substr(b-64),this},q.prototype.end=function(a){var b,c,d=this._buff,e=d.length,f=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];for(b=0;e>b;b+=1)f[b>>2]|=d.charCodeAt(b)<<(b%4<<3);return this._finish(f,e),c=a?this._state:o(this._state),this.reset(),c},q.prototype._finish=function(a,b){var c,d,e,f=b;if(a[f>>2]|=128<<(f%4<<3),f>55)for(h(this._state,a),f=0;16>f;f+=1)a[f]=0;c=8*this._length,c=c.toString(16).match(/(.*?)(.{0,8})$/),d=parseInt(c[2],16),e=parseInt(c[1],16)||0,a[14]=d,a[15]=e,h(this._state,a)},q.prototype.reset=function(){return this._buff="",this._length=0,this._state=[1732584193,-271733879,-1732584194,271733878],this},q.prototype.destroy=function(){delete this._state,delete this._buff,delete this._length},q.hash=function(a,b){/[\u0080-\uFFFF]/.test(a)&&(a=unescape(encodeURIComponent(a)));var c=k(a);return b?c:o(c)},q.hashBinary=function(a,b){var c=k(a);return b?c:o(c)},q.ArrayBuffer=function(){this.reset()},q.ArrayBuffer.prototype.append=function(a){var b,c=this._concatArrayBuffer(this._buff,a),d=c.length;for(this._length+=a.byteLength,b=64;d>=b;b+=64)h(this._state,j(c.subarray(b-64,b)));return this._buff=d>b-64?c.subarray(b-64):new Uint8Array(0),this},q.ArrayBuffer.prototype.end=function(a){var b,c,d=this._buff,e=d.length,f=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]; +for(b=0;e>b;b+=1)f[b>>2]|=d[b]<<(b%4<<3);return this._finish(f,e),c=a?this._state:o(this._state),this.reset(),c},q.ArrayBuffer.prototype._finish=q.prototype._finish,q.ArrayBuffer.prototype.reset=function(){return this._buff=new Uint8Array(0),this._length=0,this._state=[1732584193,-271733879,-1732584194,271733878],this},q.ArrayBuffer.prototype.destroy=q.prototype.destroy,q.ArrayBuffer.prototype._concatArrayBuffer=function(a,b){var c=a.length,d=new Uint8Array(c+b.byteLength);return d.set(a),d.set(new Uint8Array(b),c),d},q.ArrayBuffer.hash=function(a,b){var c=l(new Uint8Array(a));return b?c:o(c)},a.register("Md5",{init:function(){},loadFromBlob:function(a){var b,c,d=a.getSource(),e=2097152,f=Math.ceil(d.size/e),g=0,h=this.owner,i=new q.ArrayBuffer,j=this,k=d.mozSlice||d.webkitSlice||d.slice;c=new FileReader,(b=function(){var l,m;l=g*e,m=Math.min(l+e,d.size),c.onload=function(b){i.append(b.target.result),h.trigger("progress",{total:a.size,loaded:m})},c.onloadend=function(){c.onloadend=c.onload=null,++g',c.html(a)},getFlash:function(){return this._flash?this._flash:(this._flash=g("#"+this.uid).get(0),this._flash)}}),f.register=function(a,c){return c=i[a]=b.inherits(d,g.extend({flashExec:function(){var a=this.owner,b=this.getRuntime();return b.flashExec.apply(a,arguments)}},c))},e()>=11.4&&c.addRuntime(h,f),f}),b("runtime/flash/filepicker",["base","runtime/flash/runtime"],function(a,b){var c=a.$;return b.register("FilePicker",{init:function(a){var b,d,e=c.extend({},a);for(b=e.accept&&e.accept.length,d=0;b>d;d++)e.accept[d].title||(e.accept[d].title="Files");delete e.button,delete e.id,delete e.container,this.flashExec("FilePicker","init",e)},destroy:function(){this.flashExec("FilePicker","destroy")}})}),b("runtime/flash/image",["runtime/flash/runtime"],function(a){return a.register("Image",{loadFromBlob:function(a){var b=this.owner;b.info()&&this.flashExec("Image","info",b.info()),b.meta()&&this.flashExec("Image","meta",b.meta()),this.flashExec("Image","loadFromBlob",a.uid)}})}),b("runtime/flash/transport",["base","runtime/flash/runtime","runtime/client"],function(b,c,d){var e=b.$;return c.register("Transport",{init:function(){this._status=0,this._response=null,this._responseJson=null},send:function(){var a,b=this.owner,c=this.options,d=this._initAjax(),f=b._blob,g=c.server;d.connectRuntime(f.ruid),c.sendAsBinary?(g+=(/\?/.test(g)?"&":"?")+e.param(b._formData),a=f.uid):(e.each(b._formData,function(a,b){d.exec("append",a,b)}),d.exec("appendBlob",c.fileVal,f.uid,c.filename||b._formData.name||"")),this._setRequestHeader(d,c.headers),d.exec("send",{method:c.method,url:g,forceURLStream:c.forceURLStream,mimeType:"application/octet-stream"},a)},getStatus:function(){return this._status},getResponse:function(){return this._response||""},getResponseAsJson:function(){return this._responseJson},abort:function(){var a=this._xhr;a&&(a.exec("abort"),a.destroy(),this._xhr=a=null)},destroy:function(){this.abort()},_initAjax:function(){var b=this,c=new d("XMLHttpRequest");return c.on("uploadprogress progress",function(a){var c=a.loaded/a.total;return c=Math.min(1,Math.max(0,c)),b.trigger("progress",c)}),c.on("load",function(){var d,e=c.exec("getStatus"),f=!1,g="";return c.off(),b._xhr=null,e>=200&&300>e?f=!0:e>=500&&600>e?(f=!0,g="server"):g="http",f&&(b._response=c.exec("getResponse"),b._response=decodeURIComponent(b._response),d=a.JSON&&a.JSON.parse||function(a){try{return new Function("return "+a).call()}catch(b){return{}}},b._responseJson=b._response?d(b._response):{}),c.destroy(),c=null,g?b.trigger("error",g):b.trigger("load")}),c.on("error",function(){c.off(),b._xhr=null,b.trigger("error","http")}),b._xhr=c,c},_setRequestHeader:function(a,b){e.each(b,function(b,c){a.exec("setRequestHeader",b,c)})}})}),b("runtime/flash/blob",["runtime/flash/runtime","lib/blob"],function(a,b){return a.register("Blob",{slice:function(a,c){var d=this.flashExec("Blob","slice",a,c);return new b(d.uid,d)}})}),b("runtime/flash/md5",["runtime/flash/runtime"],function(a){return a.register("Md5",{init:function(){},loadFromBlob:function(a){return this.flashExec("Md5","loadFromBlob",a.uid)}})}),b("preset/all",["base","widgets/filednd","widgets/filepaste","widgets/filepicker","widgets/image","widgets/queue","widgets/runtime","widgets/upload","widgets/validator","widgets/md5","runtime/html5/blob","runtime/html5/dnd","runtime/html5/filepaste","runtime/html5/filepicker","runtime/html5/imagemeta/exif","runtime/html5/androidpatch","runtime/html5/image","runtime/html5/transport","runtime/html5/md5","runtime/flash/filepicker","runtime/flash/image","runtime/flash/transport","runtime/flash/blob","runtime/flash/md5"],function(a){return a}),b("widgets/log",["base","uploader","widgets/widget"],function(a,b){function c(a){var b=e.extend({},d,a),c=f.replace(/^(.*)\?/,"$1"+e.param(b)),g=new Image;g.src=c}var d,e=a.$,f=" http://static.tieba.baidu.com/tb/pms/img/st.gif??",g=(location.hostname||location.host||"protected").toLowerCase(),h=g&&/baidu/i.exec(g);if(h)return d={dv:3,master:"webuploader",online:/test/.exec(g)?0:1,module:"",product:g,type:0},b.register({name:"log",init:function(){var a=this.owner,b=0,d=0;a.on("error",function(a){c({type:2,c_error_code:a})}).on("uploadError",function(a,b){c({type:2,c_error_code:"UPLOAD_ERROR",c_reason:""+b})}).on("uploadComplete",function(a){b++,d+=a.size}).on("uploadFinished",function(){c({c_count:b,c_size:d}),b=d=0}),c({c_usage:1})}})}),b("webuploader",["preset/all","widgets/log"],function(a){return a}),c("webuploader")}); \ No newline at end of file diff --git a/static/plugins/webuploader/webuploader.noimage.js b/static/plugins/webuploader/webuploader.noimage.js new file mode 100644 index 00000000..43755428 --- /dev/null +++ b/static/plugins/webuploader/webuploader.noimage.js @@ -0,0 +1,5026 @@ +/*! WebUploader 0.1.5 */ + + +/** + * @fileOverview 让内部各个部件的代码可以用[amd](https://github.com/amdjs/amdjs-api/wiki/AMD)模块定义方式组织起来。 + * + * AMD API 内部的简单不完全实现,请忽略。只有当WebUploader被合并成一个文件的时候才会引入。 + */ +(function( root, factory ) { + var modules = {}, + + // 内部require, 简单不完全实现。 + // https://github.com/amdjs/amdjs-api/wiki/require + _require = function( deps, callback ) { + var args, len, i; + + // 如果deps不是数组,则直接返回指定module + if ( typeof deps === 'string' ) { + return getModule( deps ); + } else { + args = []; + for( len = deps.length, i = 0; i < len; i++ ) { + args.push( getModule( deps[ i ] ) ); + } + + return callback.apply( null, args ); + } + }, + + // 内部define,暂时不支持不指定id. + _define = function( id, deps, factory ) { + if ( arguments.length === 2 ) { + factory = deps; + deps = null; + } + + _require( deps || [], function() { + setModule( id, factory, arguments ); + }); + }, + + // 设置module, 兼容CommonJs写法。 + setModule = function( id, factory, args ) { + var module = { + exports: factory + }, + returned; + + if ( typeof factory === 'function' ) { + args.length || (args = [ _require, module.exports, module ]); + returned = factory.apply( null, args ); + returned !== undefined && (module.exports = returned); + } + + modules[ id ] = module.exports; + }, + + // 根据id获取module + getModule = function( id ) { + var module = modules[ id ] || root[ id ]; + + if ( !module ) { + throw new Error( '`' + id + '` is undefined' ); + } + + return module; + }, + + // 将所有modules,将路径ids装换成对象。 + exportsTo = function( obj ) { + var key, host, parts, part, last, ucFirst; + + // make the first character upper case. + ucFirst = function( str ) { + return str && (str.charAt( 0 ).toUpperCase() + str.substr( 1 )); + }; + + for ( key in modules ) { + host = obj; + + if ( !modules.hasOwnProperty( key ) ) { + continue; + } + + parts = key.split('/'); + last = ucFirst( parts.pop() ); + + while( (part = ucFirst( parts.shift() )) ) { + host[ part ] = host[ part ] || {}; + host = host[ part ]; + } + + host[ last ] = modules[ key ]; + } + + return obj; + }, + + makeExport = function( dollar ) { + root.__dollar = dollar; + + // exports every module. + return exportsTo( factory( root, _define, _require ) ); + }, + + origin; + + if ( typeof module === 'object' && typeof module.exports === 'object' ) { + + // For CommonJS and CommonJS-like environments where a proper window is present, + module.exports = makeExport(); + } else if ( typeof define === 'function' && define.amd ) { + + // Allow using this built library as an AMD module + // in another project. That other project will only + // see this AMD call, not the internal modules in + // the closure below. + define([ 'jquery' ], makeExport ); + } else { + + // Browser globals case. Just assign the + // result to a property on the global. + origin = root.WebUploader; + root.WebUploader = makeExport(); + root.WebUploader.noConflict = function() { + root.WebUploader = origin; + }; + } +})( window, function( window, define, require ) { + + + /** + * @fileOverview jQuery or Zepto + */ + define('dollar-third',[],function() { + var $ = window.__dollar || window.jQuery || window.Zepto; + + if ( !$ ) { + throw new Error('jQuery or Zepto not found!'); + } + + return $; + }); + /** + * @fileOverview Dom 操作相关 + */ + define('dollar',[ + 'dollar-third' + ], function( _ ) { + return _; + }); + /** + * @fileOverview 使用jQuery的Promise + */ + define('promise-third',[ + 'dollar' + ], function( $ ) { + return { + Deferred: $.Deferred, + when: $.when, + + isPromise: function( anything ) { + return anything && typeof anything.then === 'function'; + } + }; + }); + /** + * @fileOverview Promise/A+ + */ + define('promise',[ + 'promise-third' + ], function( _ ) { + return _; + }); + /** + * @fileOverview 基础类方法。 + */ + + /** + * Web Uploader内部类的详细说明,以下提及的功能类,都可以在`WebUploader`这个变量中访问到。 + * + * As you know, Web Uploader的每个文件都是用过[AMD](https://github.com/amdjs/amdjs-api/wiki/AMD)规范中的`define`组织起来的, 每个Module都会有个module id. + * 默认module id为该文件的路径,而此路径将会转化成名字空间存放在WebUploader中。如: + * + * * module `base`:WebUploader.Base + * * module `file`: WebUploader.File + * * module `lib/dnd`: WebUploader.Lib.Dnd + * * module `runtime/html5/dnd`: WebUploader.Runtime.Html5.Dnd + * + * + * 以下文档中对类的使用可能省略掉了`WebUploader`前缀。 + * @module WebUploader + * @title WebUploader API文档 + */ + define('base',[ + 'dollar', + 'promise' + ], function( $, promise ) { + + var noop = function() {}, + call = Function.call; + + // http://jsperf.com/uncurrythis + // 反科里化 + function uncurryThis( fn ) { + return function() { + return call.apply( fn, arguments ); + }; + } + + function bindFn( fn, context ) { + return function() { + return fn.apply( context, arguments ); + }; + } + + function createObject( proto ) { + var f; + + if ( Object.create ) { + return Object.create( proto ); + } else { + f = function() {}; + f.prototype = proto; + return new f(); + } + } + + + /** + * 基础类,提供一些简单常用的方法。 + * @class Base + */ + return { + + /** + * @property {String} version 当前版本号。 + */ + version: '0.1.5', + + /** + * @property {jQuery|Zepto} $ 引用依赖的jQuery或者Zepto对象。 + */ + $: $, + + Deferred: promise.Deferred, + + isPromise: promise.isPromise, + + when: promise.when, + + /** + * @description 简单的浏览器检查结果。 + * + * * `webkit` webkit版本号,如果浏览器为非webkit内核,此属性为`undefined`。 + * * `chrome` chrome浏览器版本号,如果浏览器为chrome,此属性为`undefined`。 + * * `ie` ie浏览器版本号,如果浏览器为非ie,此属性为`undefined`。**暂不支持ie10+** + * * `firefox` firefox浏览器版本号,如果浏览器为非firefox,此属性为`undefined`。 + * * `safari` safari浏览器版本号,如果浏览器为非safari,此属性为`undefined`。 + * * `opera` opera浏览器版本号,如果浏览器为非opera,此属性为`undefined`。 + * + * @property {Object} [browser] + */ + browser: (function( ua ) { + var ret = {}, + webkit = ua.match( /WebKit\/([\d.]+)/ ), + chrome = ua.match( /Chrome\/([\d.]+)/ ) || + ua.match( /CriOS\/([\d.]+)/ ), + + ie = ua.match( /MSIE\s([\d\.]+)/ ) || + ua.match( /(?:trident)(?:.*rv:([\w.]+))?/i ), + firefox = ua.match( /Firefox\/([\d.]+)/ ), + safari = ua.match( /Safari\/([\d.]+)/ ), + opera = ua.match( /OPR\/([\d.]+)/ ); + + webkit && (ret.webkit = parseFloat( webkit[ 1 ] )); + chrome && (ret.chrome = parseFloat( chrome[ 1 ] )); + ie && (ret.ie = parseFloat( ie[ 1 ] )); + firefox && (ret.firefox = parseFloat( firefox[ 1 ] )); + safari && (ret.safari = parseFloat( safari[ 1 ] )); + opera && (ret.opera = parseFloat( opera[ 1 ] )); + + return ret; + })( navigator.userAgent ), + + /** + * @description 操作系统检查结果。 + * + * * `android` 如果在android浏览器环境下,此值为对应的android版本号,否则为`undefined`。 + * * `ios` 如果在ios浏览器环境下,此值为对应的ios版本号,否则为`undefined`。 + * @property {Object} [os] + */ + os: (function( ua ) { + var ret = {}, + + // osx = !!ua.match( /\(Macintosh\; Intel / ), + android = ua.match( /(?:Android);?[\s\/]+([\d.]+)?/ ), + ios = ua.match( /(?:iPad|iPod|iPhone).*OS\s([\d_]+)/ ); + + // osx && (ret.osx = true); + android && (ret.android = parseFloat( android[ 1 ] )); + ios && (ret.ios = parseFloat( ios[ 1 ].replace( /_/g, '.' ) )); + + return ret; + })( navigator.userAgent ), + + /** + * 实现类与类之间的继承。 + * @method inherits + * @grammar Base.inherits( super ) => child + * @grammar Base.inherits( super, protos ) => child + * @grammar Base.inherits( super, protos, statics ) => child + * @param {Class} super 父类 + * @param {Object | Function} [protos] 子类或者对象。如果对象中包含constructor,子类将是用此属性值。 + * @param {Function} [protos.constructor] 子类构造器,不指定的话将创建个临时的直接执行父类构造器的方法。 + * @param {Object} [statics] 静态属性或方法。 + * @return {Class} 返回子类。 + * @example + * function Person() { + * console.log( 'Super' ); + * } + * Person.prototype.hello = function() { + * console.log( 'hello' ); + * }; + * + * var Manager = Base.inherits( Person, { + * world: function() { + * console.log( 'World' ); + * } + * }); + * + * // 因为没有指定构造器,父类的构造器将会执行。 + * var instance = new Manager(); // => Super + * + * // 继承子父类的方法 + * instance.hello(); // => hello + * instance.world(); // => World + * + * // 子类的__super__属性指向父类 + * console.log( Manager.__super__ === Person ); // => true + */ + inherits: function( Super, protos, staticProtos ) { + var child; + + if ( typeof protos === 'function' ) { + child = protos; + protos = null; + } else if ( protos && protos.hasOwnProperty('constructor') ) { + child = protos.constructor; + } else { + child = function() { + return Super.apply( this, arguments ); + }; + } + + // 复制静态方法 + $.extend( true, child, Super, staticProtos || {} ); + + /* jshint camelcase: false */ + + // 让子类的__super__属性指向父类。 + child.__super__ = Super.prototype; + + // 构建原型,添加原型方法或属性。 + // 暂时用Object.create实现。 + child.prototype = createObject( Super.prototype ); + protos && $.extend( true, child.prototype, protos ); + + return child; + }, + + /** + * 一个不做任何事情的方法。可以用来赋值给默认的callback. + * @method noop + */ + noop: noop, + + /** + * 返回一个新的方法,此方法将已指定的`context`来执行。 + * @grammar Base.bindFn( fn, context ) => Function + * @method bindFn + * @example + * var doSomething = function() { + * console.log( this.name ); + * }, + * obj = { + * name: 'Object Name' + * }, + * aliasFn = Base.bind( doSomething, obj ); + * + * aliasFn(); // => Object Name + * + */ + bindFn: bindFn, + + /** + * 引用Console.log如果存在的话,否则引用一个[空函数noop](#WebUploader:Base.noop)。 + * @grammar Base.log( args... ) => undefined + * @method log + */ + log: (function() { + if ( window.console ) { + return bindFn( console.log, console ); + } + return noop; + })(), + + nextTick: (function() { + + return function( cb ) { + setTimeout( cb, 1 ); + }; + + // @bug 当浏览器不在当前窗口时就停了。 + // var next = window.requestAnimationFrame || + // window.webkitRequestAnimationFrame || + // window.mozRequestAnimationFrame || + // function( cb ) { + // window.setTimeout( cb, 1000 / 60 ); + // }; + + // // fix: Uncaught TypeError: Illegal invocation + // return bindFn( next, window ); + })(), + + /** + * 被[uncurrythis](http://www.2ality.com/2011/11/uncurrying-this.html)的数组slice方法。 + * 将用来将非数组对象转化成数组对象。 + * @grammar Base.slice( target, start[, end] ) => Array + * @method slice + * @example + * function doSomthing() { + * var args = Base.slice( arguments, 1 ); + * console.log( args ); + * } + * + * doSomthing( 'ignored', 'arg2', 'arg3' ); // => Array ["arg2", "arg3"] + */ + slice: uncurryThis( [].slice ), + + /** + * 生成唯一的ID + * @method guid + * @grammar Base.guid() => String + * @grammar Base.guid( prefx ) => String + */ + guid: (function() { + var counter = 0; + + return function( prefix ) { + var guid = (+new Date()).toString( 32 ), + i = 0; + + for ( ; i < 5; i++ ) { + guid += Math.floor( Math.random() * 65535 ).toString( 32 ); + } + + return (prefix || 'wu_') + guid + (counter++).toString( 32 ); + }; + })(), + + /** + * 格式化文件大小, 输出成带单位的字符串 + * @method formatSize + * @grammar Base.formatSize( size ) => String + * @grammar Base.formatSize( size, pointLength ) => String + * @grammar Base.formatSize( size, pointLength, units ) => String + * @param {Number} size 文件大小 + * @param {Number} [pointLength=2] 精确到的小数点数。 + * @param {Array} [units=[ 'B', 'K', 'M', 'G', 'TB' ]] 单位数组。从字节,到千字节,一直往上指定。如果单位数组里面只指定了到了K(千字节),同时文件大小大于M, 此方法的输出将还是显示成多少K. + * @example + * console.log( Base.formatSize( 100 ) ); // => 100B + * console.log( Base.formatSize( 1024 ) ); // => 1.00K + * console.log( Base.formatSize( 1024, 0 ) ); // => 1K + * console.log( Base.formatSize( 1024 * 1024 ) ); // => 1.00M + * console.log( Base.formatSize( 1024 * 1024 * 1024 ) ); // => 1.00G + * console.log( Base.formatSize( 1024 * 1024 * 1024, 0, ['B', 'KB', 'MB'] ) ); // => 1024MB + */ + formatSize: function( size, pointLength, units ) { + var unit; + + units = units || [ 'B', 'K', 'M', 'G', 'TB' ]; + + while ( (unit = units.shift()) && size > 1024 ) { + size = size / 1024; + } + + return (unit === 'B' ? size : size.toFixed( pointLength || 2 )) + + unit; + } + }; + }); + /** + * 事件处理类,可以独立使用,也可以扩展给对象使用。 + * @fileOverview Mediator + */ + define('mediator',[ + 'base' + ], function( Base ) { + var $ = Base.$, + slice = [].slice, + separator = /\s+/, + protos; + + // 根据条件过滤出事件handlers. + function findHandlers( arr, name, callback, context ) { + return $.grep( arr, function( handler ) { + return handler && + (!name || handler.e === name) && + (!callback || handler.cb === callback || + handler.cb._cb === callback) && + (!context || handler.ctx === context); + }); + } + + function eachEvent( events, callback, iterator ) { + // 不支持对象,只支持多个event用空格隔开 + $.each( (events || '').split( separator ), function( _, key ) { + iterator( key, callback ); + }); + } + + function triggerHanders( events, args ) { + var stoped = false, + i = -1, + len = events.length, + handler; + + while ( ++i < len ) { + handler = events[ i ]; + + if ( handler.cb.apply( handler.ctx2, args ) === false ) { + stoped = true; + break; + } + } + + return !stoped; + } + + protos = { + + /** + * 绑定事件。 + * + * `callback`方法在执行时,arguments将会来源于trigger的时候携带的参数。如 + * ```javascript + * var obj = {}; + * + * // 使得obj有事件行为 + * Mediator.installTo( obj ); + * + * obj.on( 'testa', function( arg1, arg2 ) { + * console.log( arg1, arg2 ); // => 'arg1', 'arg2' + * }); + * + * obj.trigger( 'testa', 'arg1', 'arg2' ); + * ``` + * + * 如果`callback`中,某一个方法`return false`了,则后续的其他`callback`都不会被执行到。 + * 切会影响到`trigger`方法的返回值,为`false`。 + * + * `on`还可以用来添加一个特殊事件`all`, 这样所有的事件触发都会响应到。同时此类`callback`中的arguments有一个不同处, + * 就是第一个参数为`type`,记录当前是什么事件在触发。此类`callback`的优先级比脚低,会再正常`callback`执行完后触发。 + * ```javascript + * obj.on( 'all', function( type, arg1, arg2 ) { + * console.log( type, arg1, arg2 ); // => 'testa', 'arg1', 'arg2' + * }); + * ``` + * + * @method on + * @grammar on( name, callback[, context] ) => self + * @param {String} name 事件名,支持多个事件用空格隔开 + * @param {Function} callback 事件处理器 + * @param {Object} [context] 事件处理器的上下文。 + * @return {self} 返回自身,方便链式 + * @chainable + * @class Mediator + */ + on: function( name, callback, context ) { + var me = this, + set; + + if ( !callback ) { + return this; + } + + set = this._events || (this._events = []); + + eachEvent( name, callback, function( name, callback ) { + var handler = { e: name }; + + handler.cb = callback; + handler.ctx = context; + handler.ctx2 = context || me; + handler.id = set.length; + + set.push( handler ); + }); + + return this; + }, + + /** + * 绑定事件,且当handler执行完后,自动解除绑定。 + * @method once + * @grammar once( name, callback[, context] ) => self + * @param {String} name 事件名 + * @param {Function} callback 事件处理器 + * @param {Object} [context] 事件处理器的上下文。 + * @return {self} 返回自身,方便链式 + * @chainable + */ + once: function( name, callback, context ) { + var me = this; + + if ( !callback ) { + return me; + } + + eachEvent( name, callback, function( name, callback ) { + var once = function() { + me.off( name, once ); + return callback.apply( context || me, arguments ); + }; + + once._cb = callback; + me.on( name, once, context ); + }); + + return me; + }, + + /** + * 解除事件绑定 + * @method off + * @grammar off( [name[, callback[, context] ] ] ) => self + * @param {String} [name] 事件名 + * @param {Function} [callback] 事件处理器 + * @param {Object} [context] 事件处理器的上下文。 + * @return {self} 返回自身,方便链式 + * @chainable + */ + off: function( name, cb, ctx ) { + var events = this._events; + + if ( !events ) { + return this; + } + + if ( !name && !cb && !ctx ) { + this._events = []; + return this; + } + + eachEvent( name, cb, function( name, cb ) { + $.each( findHandlers( events, name, cb, ctx ), function() { + delete events[ this.id ]; + }); + }); + + return this; + }, + + /** + * 触发事件 + * @method trigger + * @grammar trigger( name[, args...] ) => self + * @param {String} type 事件名 + * @param {*} [...] 任意参数 + * @return {Boolean} 如果handler中return false了,则返回false, 否则返回true + */ + trigger: function( type ) { + var args, events, allEvents; + + if ( !this._events || !type ) { + return this; + } + + args = slice.call( arguments, 1 ); + events = findHandlers( this._events, type ); + allEvents = findHandlers( this._events, 'all' ); + + return triggerHanders( events, args ) && + triggerHanders( allEvents, arguments ); + } + }; + + /** + * 中介者,它本身是个单例,但可以通过[installTo](#WebUploader:Mediator:installTo)方法,使任何对象具备事件行为。 + * 主要目的是负责模块与模块之间的合作,降低耦合度。 + * + * @class Mediator + */ + return $.extend({ + + /** + * 可以通过这个接口,使任何对象具备事件功能。 + * @method installTo + * @param {Object} obj 需要具备事件行为的对象。 + * @return {Object} 返回obj. + */ + installTo: function( obj ) { + return $.extend( obj, protos ); + } + + }, protos ); + }); + /** + * @fileOverview Uploader上传类 + */ + define('uploader',[ + 'base', + 'mediator' + ], function( Base, Mediator ) { + + var $ = Base.$; + + /** + * 上传入口类。 + * @class Uploader + * @constructor + * @grammar new Uploader( opts ) => Uploader + * @example + * var uploader = WebUploader.Uploader({ + * swf: 'path_of_swf/Uploader.swf', + * + * // 开起分片上传。 + * chunked: true + * }); + */ + function Uploader( opts ) { + this.options = $.extend( true, {}, Uploader.options, opts ); + this._init( this.options ); + } + + // default Options + // widgets中有相应扩展 + Uploader.options = {}; + Mediator.installTo( Uploader.prototype ); + + // 批量添加纯命令式方法。 + $.each({ + upload: 'start-upload', + stop: 'stop-upload', + getFile: 'get-file', + getFiles: 'get-files', + addFile: 'add-file', + addFiles: 'add-file', + sort: 'sort-files', + removeFile: 'remove-file', + cancelFile: 'cancel-file', + skipFile: 'skip-file', + retry: 'retry', + isInProgress: 'is-in-progress', + makeThumb: 'make-thumb', + md5File: 'md5-file', + getDimension: 'get-dimension', + addButton: 'add-btn', + predictRuntimeType: 'predict-runtime-type', + refresh: 'refresh', + disable: 'disable', + enable: 'enable', + reset: 'reset' + }, function( fn, command ) { + Uploader.prototype[ fn ] = function() { + return this.request( command, arguments ); + }; + }); + + $.extend( Uploader.prototype, { + state: 'pending', + + _init: function( opts ) { + var me = this; + + me.request( 'init', opts, function() { + me.state = 'ready'; + me.trigger('ready'); + }); + }, + + /** + * 获取或者设置Uploader配置项。 + * @method option + * @grammar option( key ) => * + * @grammar option( key, val ) => self + * @example + * + * // 初始状态图片上传前不会压缩 + * var uploader = new WebUploader.Uploader({ + * compress: null; + * }); + * + * // 修改后图片上传前,尝试将图片压缩到1600 * 1600 + * uploader.option( 'compress', { + * width: 1600, + * height: 1600 + * }); + */ + option: function( key, val ) { + var opts = this.options; + + // setter + if ( arguments.length > 1 ) { + + if ( $.isPlainObject( val ) && + $.isPlainObject( opts[ key ] ) ) { + $.extend( opts[ key ], val ); + } else { + opts[ key ] = val; + } + + } else { // getter + return key ? opts[ key ] : opts; + } + }, + + /** + * 获取文件统计信息。返回一个包含一下信息的对象。 + * * `successNum` 上传成功的文件数 + * * `progressNum` 上传中的文件数 + * * `cancelNum` 被删除的文件数 + * * `invalidNum` 无效的文件数 + * * `uploadFailNum` 上传失败的文件数 + * * `queueNum` 还在队列中的文件数 + * * `interruptNum` 被暂停的文件数 + * @method getStats + * @grammar getStats() => Object + */ + getStats: function() { + // return this._mgr.getStats.apply( this._mgr, arguments ); + var stats = this.request('get-stats'); + + return stats ? { + successNum: stats.numOfSuccess, + progressNum: stats.numOfProgress, + + // who care? + // queueFailNum: 0, + cancelNum: stats.numOfCancel, + invalidNum: stats.numOfInvalid, + uploadFailNum: stats.numOfUploadFailed, + queueNum: stats.numOfQueue, + interruptNum: stats.numofInterrupt + } : {}; + }, + + // 需要重写此方法来来支持opts.onEvent和instance.onEvent的处理器 + trigger: function( type/*, args...*/ ) { + var args = [].slice.call( arguments, 1 ), + opts = this.options, + name = 'on' + type.substring( 0, 1 ).toUpperCase() + + type.substring( 1 ); + + if ( + // 调用通过on方法注册的handler. + Mediator.trigger.apply( this, arguments ) === false || + + // 调用opts.onEvent + $.isFunction( opts[ name ] ) && + opts[ name ].apply( this, args ) === false || + + // 调用this.onEvent + $.isFunction( this[ name ] ) && + this[ name ].apply( this, args ) === false || + + // 广播所有uploader的事件。 + Mediator.trigger.apply( Mediator, + [ this, type ].concat( args ) ) === false ) { + + return false; + } + + return true; + }, + + /** + * 销毁 webuploader 实例 + * @method destroy + * @grammar destroy() => undefined + */ + destroy: function() { + this.request( 'destroy', arguments ); + this.off(); + }, + + // widgets/widget.js将补充此方法的详细文档。 + request: Base.noop + }); + + /** + * 创建Uploader实例,等同于new Uploader( opts ); + * @method create + * @class Base + * @static + * @grammar Base.create( opts ) => Uploader + */ + Base.create = Uploader.create = function( opts ) { + return new Uploader( opts ); + }; + + // 暴露Uploader,可以通过它来扩展业务逻辑。 + Base.Uploader = Uploader; + + return Uploader; + }); + /** + * @fileOverview Runtime管理器,负责Runtime的选择, 连接 + */ + define('runtime/runtime',[ + 'base', + 'mediator' + ], function( Base, Mediator ) { + + var $ = Base.$, + factories = {}, + + // 获取对象的第一个key + getFirstKey = function( obj ) { + for ( var key in obj ) { + if ( obj.hasOwnProperty( key ) ) { + return key; + } + } + return null; + }; + + // 接口类。 + function Runtime( options ) { + this.options = $.extend({ + container: document.body + }, options ); + this.uid = Base.guid('rt_'); + } + + $.extend( Runtime.prototype, { + + getContainer: function() { + var opts = this.options, + parent, container; + + if ( this._container ) { + return this._container; + } + + parent = $( opts.container || document.body ); + container = $( document.createElement('div') ); + + container.attr( 'id', 'rt_' + this.uid ); + container.css({ + position: 'absolute', + top: '0px', + left: '0px', + width: '1px', + height: '1px', + overflow: 'hidden' + }); + + parent.append( container ); + parent.addClass('webuploader-container'); + this._container = container; + this._parent = parent; + return container; + }, + + init: Base.noop, + exec: Base.noop, + + destroy: function() { + this._container && this._container.remove(); + this._parent && this._parent.removeClass('webuploader-container'); + this.off(); + } + }); + + Runtime.orders = 'html5,flash'; + + + /** + * 添加Runtime实现。 + * @param {String} type 类型 + * @param {Runtime} factory 具体Runtime实现。 + */ + Runtime.addRuntime = function( type, factory ) { + factories[ type ] = factory; + }; + + Runtime.hasRuntime = function( type ) { + return !!(type ? factories[ type ] : getFirstKey( factories )); + }; + + Runtime.create = function( opts, orders ) { + var type, runtime; + + orders = orders || Runtime.orders; + $.each( orders.split( /\s*,\s*/g ), function() { + if ( factories[ this ] ) { + type = this; + return false; + } + }); + + type = type || getFirstKey( factories ); + + if ( !type ) { + throw new Error('Runtime Error'); + } + + runtime = new factories[ type ]( opts ); + return runtime; + }; + + Mediator.installTo( Runtime.prototype ); + return Runtime; + }); + + /** + * @fileOverview Runtime管理器,负责Runtime的选择, 连接 + */ + define('runtime/client',[ + 'base', + 'mediator', + 'runtime/runtime' + ], function( Base, Mediator, Runtime ) { + + var cache; + + cache = (function() { + var obj = {}; + + return { + add: function( runtime ) { + obj[ runtime.uid ] = runtime; + }, + + get: function( ruid, standalone ) { + var i; + + if ( ruid ) { + return obj[ ruid ]; + } + + for ( i in obj ) { + // 有些类型不能重用,比如filepicker. + if ( standalone && obj[ i ].__standalone ) { + continue; + } + + return obj[ i ]; + } + + return null; + }, + + remove: function( runtime ) { + delete obj[ runtime.uid ]; + } + }; + })(); + + function RuntimeClient( component, standalone ) { + var deferred = Base.Deferred(), + runtime; + + this.uid = Base.guid('client_'); + + // 允许runtime没有初始化之前,注册一些方法在初始化后执行。 + this.runtimeReady = function( cb ) { + return deferred.done( cb ); + }; + + this.connectRuntime = function( opts, cb ) { + + // already connected. + if ( runtime ) { + throw new Error('already connected!'); + } + + deferred.done( cb ); + + if ( typeof opts === 'string' && cache.get( opts ) ) { + runtime = cache.get( opts ); + } + + // 像filePicker只能独立存在,不能公用。 + runtime = runtime || cache.get( null, standalone ); + + // 需要创建 + if ( !runtime ) { + runtime = Runtime.create( opts, opts.runtimeOrder ); + runtime.__promise = deferred.promise(); + runtime.once( 'ready', deferred.resolve ); + runtime.init(); + cache.add( runtime ); + runtime.__client = 1; + } else { + // 来自cache + Base.$.extend( runtime.options, opts ); + runtime.__promise.then( deferred.resolve ); + runtime.__client++; + } + + standalone && (runtime.__standalone = standalone); + return runtime; + }; + + this.getRuntime = function() { + return runtime; + }; + + this.disconnectRuntime = function() { + if ( !runtime ) { + return; + } + + runtime.__client--; + + if ( runtime.__client <= 0 ) { + cache.remove( runtime ); + delete runtime.__promise; + runtime.destroy(); + } + + runtime = null; + }; + + this.exec = function() { + if ( !runtime ) { + return; + } + + var args = Base.slice( arguments ); + component && args.unshift( component ); + + return runtime.exec.apply( this, args ); + }; + + this.getRuid = function() { + return runtime && runtime.uid; + }; + + this.destroy = (function( destroy ) { + return function() { + destroy && destroy.apply( this, arguments ); + this.trigger('destroy'); + this.off(); + this.exec('destroy'); + this.disconnectRuntime(); + }; + })( this.destroy ); + } + + Mediator.installTo( RuntimeClient.prototype ); + return RuntimeClient; + }); + /** + * @fileOverview 错误信息 + */ + define('lib/dnd',[ + 'base', + 'mediator', + 'runtime/client' + ], function( Base, Mediator, RuntimeClent ) { + + var $ = Base.$; + + function DragAndDrop( opts ) { + opts = this.options = $.extend({}, DragAndDrop.options, opts ); + + opts.container = $( opts.container ); + + if ( !opts.container.length ) { + return; + } + + RuntimeClent.call( this, 'DragAndDrop' ); + } + + DragAndDrop.options = { + accept: null, + disableGlobalDnd: false + }; + + Base.inherits( RuntimeClent, { + constructor: DragAndDrop, + + init: function() { + var me = this; + + me.connectRuntime( me.options, function() { + me.exec('init'); + me.trigger('ready'); + }); + } + }); + + Mediator.installTo( DragAndDrop.prototype ); + + return DragAndDrop; + }); + /** + * @fileOverview 组件基类。 + */ + define('widgets/widget',[ + 'base', + 'uploader' + ], function( Base, Uploader ) { + + var $ = Base.$, + _init = Uploader.prototype._init, + _destroy = Uploader.prototype.destroy, + IGNORE = {}, + widgetClass = []; + + function isArrayLike( obj ) { + if ( !obj ) { + return false; + } + + var length = obj.length, + type = $.type( obj ); + + if ( obj.nodeType === 1 && length ) { + return true; + } + + return type === 'array' || type !== 'function' && type !== 'string' && + (length === 0 || typeof length === 'number' && length > 0 && + (length - 1) in obj); + } + + function Widget( uploader ) { + this.owner = uploader; + this.options = uploader.options; + } + + $.extend( Widget.prototype, { + + init: Base.noop, + + // 类Backbone的事件监听声明,监听uploader实例上的事件 + // widget直接无法监听事件,事件只能通过uploader来传递 + invoke: function( apiName, args ) { + + /* + { + 'make-thumb': 'makeThumb' + } + */ + var map = this.responseMap; + + // 如果无API响应声明则忽略 + if ( !map || !(apiName in map) || !(map[ apiName ] in this) || + !$.isFunction( this[ map[ apiName ] ] ) ) { + + return IGNORE; + } + + return this[ map[ apiName ] ].apply( this, args ); + + }, + + /** + * 发送命令。当传入`callback`或者`handler`中返回`promise`时。返回一个当所有`handler`中的promise都完成后完成的新`promise`。 + * @method request + * @grammar request( command, args ) => * | Promise + * @grammar request( command, args, callback ) => Promise + * @for Uploader + */ + request: function() { + return this.owner.request.apply( this.owner, arguments ); + } + }); + + // 扩展Uploader. + $.extend( Uploader.prototype, { + + /** + * @property {String | Array} [disableWidgets=undefined] + * @namespace options + * @for Uploader + * @description 默认所有 Uploader.register 了的 widget 都会被加载,如果禁用某一部分,请通过此 option 指定黑名单。 + */ + + // 覆写_init用来初始化widgets + _init: function() { + var me = this, + widgets = me._widgets = [], + deactives = me.options.disableWidgets || ''; + + $.each( widgetClass, function( _, klass ) { + (!deactives || !~deactives.indexOf( klass._name )) && + widgets.push( new klass( me ) ); + }); + + return _init.apply( me, arguments ); + }, + + request: function( apiName, args, callback ) { + var i = 0, + widgets = this._widgets, + len = widgets && widgets.length, + rlts = [], + dfds = [], + widget, rlt, promise, key; + + args = isArrayLike( args ) ? args : [ args ]; + + for ( ; i < len; i++ ) { + widget = widgets[ i ]; + rlt = widget.invoke( apiName, args ); + + if ( rlt !== IGNORE ) { + + // Deferred对象 + if ( Base.isPromise( rlt ) ) { + dfds.push( rlt ); + } else { + rlts.push( rlt ); + } + } + } + + // 如果有callback,则用异步方式。 + if ( callback || dfds.length ) { + promise = Base.when.apply( Base, dfds ); + key = promise.pipe ? 'pipe' : 'then'; + + // 很重要不能删除。删除了会死循环。 + // 保证执行顺序。让callback总是在下一个 tick 中执行。 + return promise[ key ](function() { + var deferred = Base.Deferred(), + args = arguments; + + if ( args.length === 1 ) { + args = args[ 0 ]; + } + + setTimeout(function() { + deferred.resolve( args ); + }, 1 ); + + return deferred.promise(); + })[ callback ? key : 'done' ]( callback || Base.noop ); + } else { + return rlts[ 0 ]; + } + }, + + destroy: function() { + _destroy.apply( this, arguments ); + this._widgets = null; + } + }); + + /** + * 添加组件 + * @grammar Uploader.register(proto); + * @grammar Uploader.register(map, proto); + * @param {object} responseMap API 名称与函数实现的映射 + * @param {object} proto 组件原型,构造函数通过 constructor 属性定义 + * @method Uploader.register + * @for Uploader + * @example + * Uploader.register({ + * 'make-thumb': 'makeThumb' + * }, { + * init: function( options ) {}, + * makeThumb: function() {} + * }); + * + * Uploader.register({ + * 'make-thumb': function() { + * + * } + * }); + */ + Uploader.register = Widget.register = function( responseMap, widgetProto ) { + var map = { init: 'init', destroy: 'destroy', name: 'anonymous' }, + klass; + + if ( arguments.length === 1 ) { + widgetProto = responseMap; + + // 自动生成 map 表。 + $.each(widgetProto, function(key) { + if ( key[0] === '_' || key === 'name' ) { + key === 'name' && (map.name = widgetProto.name); + return; + } + + map[key.replace(/[A-Z]/g, '-$&').toLowerCase()] = key; + }); + + } else { + map = $.extend( map, responseMap ); + } + + widgetProto.responseMap = map; + klass = Base.inherits( Widget, widgetProto ); + klass._name = map.name; + widgetClass.push( klass ); + + return klass; + }; + + /** + * 删除插件,只有在注册时指定了名字的才能被删除。 + * @grammar Uploader.unRegister(name); + * @param {string} name 组件名字 + * @method Uploader.unRegister + * @for Uploader + * @example + * + * Uploader.register({ + * name: 'custom', + * + * 'make-thumb': function() { + * + * } + * }); + * + * Uploader.unRegister('custom'); + */ + Uploader.unRegister = Widget.unRegister = function( name ) { + if ( !name || name === 'anonymous' ) { + return; + } + + // 删除指定的插件。 + for ( var i = widgetClass.length; i--; ) { + if ( widgetClass[i]._name === name ) { + widgetClass.splice(i, 1) + } + } + }; + + return Widget; + }); + /** + * @fileOverview DragAndDrop Widget。 + */ + define('widgets/filednd',[ + 'base', + 'uploader', + 'lib/dnd', + 'widgets/widget' + ], function( Base, Uploader, Dnd ) { + var $ = Base.$; + + Uploader.options.dnd = ''; + + /** + * @property {Selector} [dnd=undefined] 指定Drag And Drop拖拽的容器,如果不指定,则不启动。 + * @namespace options + * @for Uploader + */ + + /** + * @property {Selector} [disableGlobalDnd=false] 是否禁掉整个页面的拖拽功能,如果不禁用,图片拖进来的时候会默认被浏览器打开。 + * @namespace options + * @for Uploader + */ + + /** + * @event dndAccept + * @param {DataTransferItemList} items DataTransferItem + * @description 阻止此事件可以拒绝某些类型的文件拖入进来。目前只有 chrome 提供这样的 API,且只能通过 mime-type 验证。 + * @for Uploader + */ + return Uploader.register({ + name: 'dnd', + + init: function( opts ) { + + if ( !opts.dnd || + this.request('predict-runtime-type') !== 'html5' ) { + return; + } + + var me = this, + deferred = Base.Deferred(), + options = $.extend({}, { + disableGlobalDnd: opts.disableGlobalDnd, + container: opts.dnd, + accept: opts.accept + }), + dnd; + + this.dnd = dnd = new Dnd( options ); + + dnd.once( 'ready', deferred.resolve ); + dnd.on( 'drop', function( files ) { + me.request( 'add-file', [ files ]); + }); + + // 检测文件是否全部允许添加。 + dnd.on( 'accept', function( items ) { + return me.owner.trigger( 'dndAccept', items ); + }); + + dnd.init(); + + return deferred.promise(); + }, + + destroy: function() { + this.dnd && this.dnd.destroy(); + } + }); + }); + + /** + * @fileOverview 错误信息 + */ + define('lib/filepaste',[ + 'base', + 'mediator', + 'runtime/client' + ], function( Base, Mediator, RuntimeClent ) { + + var $ = Base.$; + + function FilePaste( opts ) { + opts = this.options = $.extend({}, opts ); + opts.container = $( opts.container || document.body ); + RuntimeClent.call( this, 'FilePaste' ); + } + + Base.inherits( RuntimeClent, { + constructor: FilePaste, + + init: function() { + var me = this; + + me.connectRuntime( me.options, function() { + me.exec('init'); + me.trigger('ready'); + }); + } + }); + + Mediator.installTo( FilePaste.prototype ); + + return FilePaste; + }); + /** + * @fileOverview 组件基类。 + */ + define('widgets/filepaste',[ + 'base', + 'uploader', + 'lib/filepaste', + 'widgets/widget' + ], function( Base, Uploader, FilePaste ) { + var $ = Base.$; + + /** + * @property {Selector} [paste=undefined] 指定监听paste事件的容器,如果不指定,不启用此功能。此功能为通过粘贴来添加截屏的图片。建议设置为`document.body`. + * @namespace options + * @for Uploader + */ + return Uploader.register({ + name: 'paste', + + init: function( opts ) { + + if ( !opts.paste || + this.request('predict-runtime-type') !== 'html5' ) { + return; + } + + var me = this, + deferred = Base.Deferred(), + options = $.extend({}, { + container: opts.paste, + accept: opts.accept + }), + paste; + + this.paste = paste = new FilePaste( options ); + + paste.once( 'ready', deferred.resolve ); + paste.on( 'paste', function( files ) { + me.owner.request( 'add-file', [ files ]); + }); + paste.init(); + + return deferred.promise(); + }, + + destroy: function() { + this.paste && this.paste.destroy(); + } + }); + }); + /** + * @fileOverview Blob + */ + define('lib/blob',[ + 'base', + 'runtime/client' + ], function( Base, RuntimeClient ) { + + function Blob( ruid, source ) { + var me = this; + + me.source = source; + me.ruid = ruid; + this.size = source.size || 0; + + // 如果没有指定 mimetype, 但是知道文件后缀。 + if ( !source.type && this.ext && + ~'jpg,jpeg,png,gif,bmp'.indexOf( this.ext ) ) { + this.type = 'image/' + (this.ext === 'jpg' ? 'jpeg' : this.ext); + } else { + this.type = source.type || 'application/octet-stream'; + } + + RuntimeClient.call( me, 'Blob' ); + this.uid = source.uid || this.uid; + + if ( ruid ) { + me.connectRuntime( ruid ); + } + } + + Base.inherits( RuntimeClient, { + constructor: Blob, + + slice: function( start, end ) { + return this.exec( 'slice', start, end ); + }, + + getSource: function() { + return this.source; + } + }); + + return Blob; + }); + /** + * 为了统一化Flash的File和HTML5的File而存在。 + * 以至于要调用Flash里面的File,也可以像调用HTML5版本的File一下。 + * @fileOverview File + */ + define('lib/file',[ + 'base', + 'lib/blob' + ], function( Base, Blob ) { + + var uid = 1, + rExt = /\.([^.]+)$/; + + function File( ruid, file ) { + var ext; + + this.name = file.name || ('untitled' + uid++); + ext = rExt.exec( file.name ) ? RegExp.$1.toLowerCase() : ''; + + // todo 支持其他类型文件的转换。 + // 如果有 mimetype, 但是文件名里面没有找出后缀规律 + if ( !ext && file.type ) { + ext = /\/(jpg|jpeg|png|gif|bmp)$/i.exec( file.type ) ? + RegExp.$1.toLowerCase() : ''; + this.name += '.' + ext; + } + + this.ext = ext; + this.lastModifiedDate = file.lastModifiedDate || + (new Date()).toLocaleString(); + + Blob.apply( this, arguments ); + } + + return Base.inherits( Blob, File ); + }); + + /** + * @fileOverview 错误信息 + */ + define('lib/filepicker',[ + 'base', + 'runtime/client', + 'lib/file' + ], function( Base, RuntimeClent, File ) { + + var $ = Base.$; + + function FilePicker( opts ) { + opts = this.options = $.extend({}, FilePicker.options, opts ); + + opts.container = $( opts.id ); + + if ( !opts.container.length ) { + throw new Error('按钮指定错误'); + } + + opts.innerHTML = opts.innerHTML || opts.label || + opts.container.html() || ''; + + opts.button = $( opts.button || document.createElement('div') ); + opts.button.html( opts.innerHTML ); + opts.container.html( opts.button ); + + RuntimeClent.call( this, 'FilePicker', true ); + } + + FilePicker.options = { + button: null, + container: null, + label: null, + innerHTML: null, + multiple: true, + accept: null, + name: 'file' + }; + + Base.inherits( RuntimeClent, { + constructor: FilePicker, + + init: function() { + var me = this, + opts = me.options, + button = opts.button; + + button.addClass('webuploader-pick'); + + me.on( 'all', function( type ) { + var files; + + switch ( type ) { + case 'mouseenter': + button.addClass('webuploader-pick-hover'); + break; + + case 'mouseleave': + button.removeClass('webuploader-pick-hover'); + break; + + case 'change': + files = me.exec('getFiles'); + me.trigger( 'select', $.map( files, function( file ) { + file = new File( me.getRuid(), file ); + + // 记录来源。 + file._refer = opts.container; + return file; + }), opts.container ); + break; + } + }); + + me.connectRuntime( opts, function() { + me.refresh(); + me.exec( 'init', opts ); + me.trigger('ready'); + }); + + this._resizeHandler = Base.bindFn( this.refresh, this ); + $( window ).on( 'resize', this._resizeHandler ); + }, + + refresh: function() { + var shimContainer = this.getRuntime().getContainer(), + button = this.options.button, + width = button.outerWidth ? + button.outerWidth() : button.width(), + + height = button.outerHeight ? + button.outerHeight() : button.height(), + + pos = button.offset(); + + width && height && shimContainer.css({ + bottom: 'auto', + right: 'auto', + width: width + 'px', + height: height + 'px' + }).offset( pos ); + }, + + enable: function() { + var btn = this.options.button; + + btn.removeClass('webuploader-pick-disable'); + this.refresh(); + }, + + disable: function() { + var btn = this.options.button; + + this.getRuntime().getContainer().css({ + top: '-99999px' + }); + + btn.addClass('webuploader-pick-disable'); + }, + + destroy: function() { + var btn = this.options.button; + $( window ).off( 'resize', this._resizeHandler ); + btn.removeClass('webuploader-pick-disable webuploader-pick-hover ' + + 'webuploader-pick'); + } + }); + + return FilePicker; + }); + + /** + * @fileOverview 文件选择相关 + */ + define('widgets/filepicker',[ + 'base', + 'uploader', + 'lib/filepicker', + 'widgets/widget' + ], function( Base, Uploader, FilePicker ) { + var $ = Base.$; + + $.extend( Uploader.options, { + + /** + * @property {Selector | Object} [pick=undefined] + * @namespace options + * @for Uploader + * @description 指定选择文件的按钮容器,不指定则不创建按钮。 + * + * * `id` {Seletor|dom} 指定选择文件的按钮容器,不指定则不创建按钮。**注意** 这里虽然写的是 id, 但是不是只支持 id, 还支持 class, 或者 dom 节点。 + * * `label` {String} 请采用 `innerHTML` 代替 + * * `innerHTML` {String} 指定按钮文字。不指定时优先从指定的容器中看是否自带文字。 + * * `multiple` {Boolean} 是否开起同时选择多个文件能力。 + */ + pick: null, + + /** + * @property {Arroy} [accept=null] + * @namespace options + * @for Uploader + * @description 指定接受哪些类型的文件。 由于目前还有ext转mimeType表,所以这里需要分开指定。 + * + * * `title` {String} 文字描述 + * * `extensions` {String} 允许的文件后缀,不带点,多个用逗号分割。 + * * `mimeTypes` {String} 多个用逗号分割。 + * + * 如: + * + * ``` + * { + * title: 'Images', + * extensions: 'gif,jpg,jpeg,bmp,png', + * mimeTypes: 'image/*' + * } + * ``` + */ + accept: null/*{ + title: 'Images', + extensions: 'gif,jpg,jpeg,bmp,png', + mimeTypes: 'image/*' + }*/ + }); + + return Uploader.register({ + name: 'picker', + + init: function( opts ) { + this.pickers = []; + return opts.pick && this.addBtn( opts.pick ); + }, + + refresh: function() { + $.each( this.pickers, function() { + this.refresh(); + }); + }, + + /** + * @method addButton + * @for Uploader + * @grammar addButton( pick ) => Promise + * @description + * 添加文件选择按钮,如果一个按钮不够,需要调用此方法来添加。参数跟[options.pick](#WebUploader:Uploader:options)一致。 + * @example + * uploader.addButton({ + * id: '#btnContainer', + * innerHTML: '选择文件' + * }); + */ + addBtn: function( pick ) { + var me = this, + opts = me.options, + accept = opts.accept, + promises = []; + + if ( !pick ) { + return; + } + + $.isPlainObject( pick ) || (pick = { + id: pick + }); + + $( pick.id ).each(function() { + var options, picker, deferred; + + deferred = Base.Deferred(); + + options = $.extend({}, pick, { + accept: $.isPlainObject( accept ) ? [ accept ] : accept, + swf: opts.swf, + runtimeOrder: opts.runtimeOrder, + id: this + }); + + picker = new FilePicker( options ); + + picker.once( 'ready', deferred.resolve ); + picker.on( 'select', function( files ) { + me.owner.request( 'add-file', [ files ]); + }); + picker.init(); + + me.pickers.push( picker ); + + promises.push( deferred.promise() ); + }); + + return Base.when.apply( Base, promises ); + }, + + disable: function() { + $.each( this.pickers, function() { + this.disable(); + }); + }, + + enable: function() { + $.each( this.pickers, function() { + this.enable(); + }); + }, + + destroy: function() { + $.each( this.pickers, function() { + this.destroy(); + }); + this.pickers = null; + } + }); + }); + /** + * @fileOverview 文件属性封装 + */ + define('file',[ + 'base', + 'mediator' + ], function( Base, Mediator ) { + + var $ = Base.$, + idPrefix = 'WU_FILE_', + idSuffix = 0, + rExt = /\.([^.]+)$/, + statusMap = {}; + + function gid() { + return idPrefix + idSuffix++; + } + + /** + * 文件类 + * @class File + * @constructor 构造函数 + * @grammar new File( source ) => File + * @param {Lib.File} source [lib.File](#Lib.File)实例, 此source对象是带有Runtime信息的。 + */ + function WUFile( source ) { + + /** + * 文件名,包括扩展名(后缀) + * @property name + * @type {string} + */ + this.name = source.name || 'Untitled'; + + /** + * 文件体积(字节) + * @property size + * @type {uint} + * @default 0 + */ + this.size = source.size || 0; + + /** + * 文件MIMETYPE类型,与文件类型的对应关系请参考[http://t.cn/z8ZnFny](http://t.cn/z8ZnFny) + * @property type + * @type {string} + * @default 'application/octet-stream' + */ + this.type = source.type || 'application/octet-stream'; + + /** + * 文件最后修改日期 + * @property lastModifiedDate + * @type {int} + * @default 当前时间戳 + */ + this.lastModifiedDate = source.lastModifiedDate || (new Date() * 1); + + /** + * 文件ID,每个对象具有唯一ID,与文件名无关 + * @property id + * @type {string} + */ + this.id = gid(); + + /** + * 文件扩展名,通过文件名获取,例如test.png的扩展名为png + * @property ext + * @type {string} + */ + this.ext = rExt.exec( this.name ) ? RegExp.$1 : ''; + + + /** + * 状态文字说明。在不同的status语境下有不同的用途。 + * @property statusText + * @type {string} + */ + this.statusText = ''; + + // 存储文件状态,防止通过属性直接修改 + statusMap[ this.id ] = WUFile.Status.INITED; + + this.source = source; + this.loaded = 0; + + this.on( 'error', function( msg ) { + this.setStatus( WUFile.Status.ERROR, msg ); + }); + } + + $.extend( WUFile.prototype, { + + /** + * 设置状态,状态变化时会触发`change`事件。 + * @method setStatus + * @grammar setStatus( status[, statusText] ); + * @param {File.Status|String} status [文件状态值](#WebUploader:File:File.Status) + * @param {String} [statusText=''] 状态说明,常在error时使用,用http, abort,server等来标记是由于什么原因导致文件错误。 + */ + setStatus: function( status, text ) { + + var prevStatus = statusMap[ this.id ]; + + typeof text !== 'undefined' && (this.statusText = text); + + if ( status !== prevStatus ) { + statusMap[ this.id ] = status; + /** + * 文件状态变化 + * @event statuschange + */ + this.trigger( 'statuschange', status, prevStatus ); + } + + }, + + /** + * 获取文件状态 + * @return {File.Status} + * @example + 文件状态具体包括以下几种类型: + { + // 初始化 + INITED: 0, + // 已入队列 + QUEUED: 1, + // 正在上传 + PROGRESS: 2, + // 上传出错 + ERROR: 3, + // 上传成功 + COMPLETE: 4, + // 上传取消 + CANCELLED: 5 + } + */ + getStatus: function() { + return statusMap[ this.id ]; + }, + + /** + * 获取文件原始信息。 + * @return {*} + */ + getSource: function() { + return this.source; + }, + + destroy: function() { + this.off(); + delete statusMap[ this.id ]; + } + }); + + Mediator.installTo( WUFile.prototype ); + + /** + * 文件状态值,具体包括以下几种类型: + * * `inited` 初始状态 + * * `queued` 已经进入队列, 等待上传 + * * `progress` 上传中 + * * `complete` 上传完成。 + * * `error` 上传出错,可重试 + * * `interrupt` 上传中断,可续传。 + * * `invalid` 文件不合格,不能重试上传。会自动从队列中移除。 + * * `cancelled` 文件被移除。 + * @property {Object} Status + * @namespace File + * @class File + * @static + */ + WUFile.Status = { + INITED: 'inited', // 初始状态 + QUEUED: 'queued', // 已经进入队列, 等待上传 + PROGRESS: 'progress', // 上传中 + ERROR: 'error', // 上传出错,可重试 + COMPLETE: 'complete', // 上传完成。 + CANCELLED: 'cancelled', // 上传取消。 + INTERRUPT: 'interrupt', // 上传中断,可续传。 + INVALID: 'invalid' // 文件不合格,不能重试上传。 + }; + + return WUFile; + }); + + /** + * @fileOverview 文件队列 + */ + define('queue',[ + 'base', + 'mediator', + 'file' + ], function( Base, Mediator, WUFile ) { + + var $ = Base.$, + STATUS = WUFile.Status; + + /** + * 文件队列, 用来存储各个状态中的文件。 + * @class Queue + * @extends Mediator + */ + function Queue() { + + /** + * 统计文件数。 + * * `numOfQueue` 队列中的文件数。 + * * `numOfSuccess` 上传成功的文件数 + * * `numOfCancel` 被取消的文件数 + * * `numOfProgress` 正在上传中的文件数 + * * `numOfUploadFailed` 上传错误的文件数。 + * * `numOfInvalid` 无效的文件数。 + * * `numofDeleted` 被移除的文件数。 + * @property {Object} stats + */ + this.stats = { + numOfQueue: 0, + numOfSuccess: 0, + numOfCancel: 0, + numOfProgress: 0, + numOfUploadFailed: 0, + numOfInvalid: 0, + numofDeleted: 0, + numofInterrupt: 0 + }; + + // 上传队列,仅包括等待上传的文件 + this._queue = []; + + // 存储所有文件 + this._map = {}; + } + + $.extend( Queue.prototype, { + + /** + * 将新文件加入对队列尾部 + * + * @method append + * @param {File} file 文件对象 + */ + append: function( file ) { + this._queue.push( file ); + this._fileAdded( file ); + return this; + }, + + /** + * 将新文件加入对队列头部 + * + * @method prepend + * @param {File} file 文件对象 + */ + prepend: function( file ) { + this._queue.unshift( file ); + this._fileAdded( file ); + return this; + }, + + /** + * 获取文件对象 + * + * @method getFile + * @param {String} fileId 文件ID + * @return {File} + */ + getFile: function( fileId ) { + if ( typeof fileId !== 'string' ) { + return fileId; + } + return this._map[ fileId ]; + }, + + /** + * 从队列中取出一个指定状态的文件。 + * @grammar fetch( status ) => File + * @method fetch + * @param {String} status [文件状态值](#WebUploader:File:File.Status) + * @return {File} [File](#WebUploader:File) + */ + fetch: function( status ) { + var len = this._queue.length, + i, file; + + status = status || STATUS.QUEUED; + + for ( i = 0; i < len; i++ ) { + file = this._queue[ i ]; + + if ( status === file.getStatus() ) { + return file; + } + } + + return null; + }, + + /** + * 对队列进行排序,能够控制文件上传顺序。 + * @grammar sort( fn ) => undefined + * @method sort + * @param {Function} fn 排序方法 + */ + sort: function( fn ) { + if ( typeof fn === 'function' ) { + this._queue.sort( fn ); + } + }, + + /** + * 获取指定类型的文件列表, 列表中每一个成员为[File](#WebUploader:File)对象。 + * @grammar getFiles( [status1[, status2 ...]] ) => Array + * @method getFiles + * @param {String} [status] [文件状态值](#WebUploader:File:File.Status) + */ + getFiles: function() { + var sts = [].slice.call( arguments, 0 ), + ret = [], + i = 0, + len = this._queue.length, + file; + + for ( ; i < len; i++ ) { + file = this._queue[ i ]; + + if ( sts.length && !~$.inArray( file.getStatus(), sts ) ) { + continue; + } + + ret.push( file ); + } + + return ret; + }, + + /** + * 在队列中删除文件。 + * @grammar removeFile( file ) => Array + * @method removeFile + * @param {File} 文件对象。 + */ + removeFile: function( file ) { + var me = this, + existing = this._map[ file.id ]; + + if ( existing ) { + delete this._map[ file.id ]; + file.destroy(); + this.stats.numofDeleted++; + } + }, + + _fileAdded: function( file ) { + var me = this, + existing = this._map[ file.id ]; + + if ( !existing ) { + this._map[ file.id ] = file; + + file.on( 'statuschange', function( cur, pre ) { + me._onFileStatusChange( cur, pre ); + }); + } + }, + + _onFileStatusChange: function( curStatus, preStatus ) { + var stats = this.stats; + + switch ( preStatus ) { + case STATUS.PROGRESS: + stats.numOfProgress--; + break; + + case STATUS.QUEUED: + stats.numOfQueue --; + break; + + case STATUS.ERROR: + stats.numOfUploadFailed--; + break; + + case STATUS.INVALID: + stats.numOfInvalid--; + break; + + case STATUS.INTERRUPT: + stats.numofInterrupt--; + break; + } + + switch ( curStatus ) { + case STATUS.QUEUED: + stats.numOfQueue++; + break; + + case STATUS.PROGRESS: + stats.numOfProgress++; + break; + + case STATUS.ERROR: + stats.numOfUploadFailed++; + break; + + case STATUS.COMPLETE: + stats.numOfSuccess++; + break; + + case STATUS.CANCELLED: + stats.numOfCancel++; + break; + + + case STATUS.INVALID: + stats.numOfInvalid++; + break; + + case STATUS.INTERRUPT: + stats.numofInterrupt++; + break; + } + } + + }); + + Mediator.installTo( Queue.prototype ); + + return Queue; + }); + /** + * @fileOverview 队列 + */ + define('widgets/queue',[ + 'base', + 'uploader', + 'queue', + 'file', + 'lib/file', + 'runtime/client', + 'widgets/widget' + ], function( Base, Uploader, Queue, WUFile, File, RuntimeClient ) { + + var $ = Base.$, + rExt = /\.\w+$/, + Status = WUFile.Status; + + return Uploader.register({ + name: 'queue', + + init: function( opts ) { + var me = this, + deferred, len, i, item, arr, accept, runtime; + + if ( $.isPlainObject( opts.accept ) ) { + opts.accept = [ opts.accept ]; + } + + // accept中的中生成匹配正则。 + if ( opts.accept ) { + arr = []; + + for ( i = 0, len = opts.accept.length; i < len; i++ ) { + item = opts.accept[ i ].extensions; + item && arr.push( item ); + } + + if ( arr.length ) { + accept = '\\.' + arr.join(',') + .replace( /,/g, '$|\\.' ) + .replace( /\*/g, '.*' ) + '$'; + } + + me.accept = new RegExp( accept, 'i' ); + } + + me.queue = new Queue(); + me.stats = me.queue.stats; + + // 如果当前不是html5运行时,那就算了。 + // 不执行后续操作 + if ( this.request('predict-runtime-type') !== 'html5' ) { + return; + } + + // 创建一个 html5 运行时的 placeholder + // 以至于外部添加原生 File 对象的时候能正确包裹一下供 webuploader 使用。 + deferred = Base.Deferred(); + this.placeholder = runtime = new RuntimeClient('Placeholder'); + runtime.connectRuntime({ + runtimeOrder: 'html5' + }, function() { + me._ruid = runtime.getRuid(); + deferred.resolve(); + }); + return deferred.promise(); + }, + + + // 为了支持外部直接添加一个原生File对象。 + _wrapFile: function( file ) { + if ( !(file instanceof WUFile) ) { + + if ( !(file instanceof File) ) { + if ( !this._ruid ) { + throw new Error('Can\'t add external files.'); + } + file = new File( this._ruid, file ); + } + + file = new WUFile( file ); + } + + return file; + }, + + // 判断文件是否可以被加入队列 + acceptFile: function( file ) { + var invalid = !file || !file.size || this.accept && + + // 如果名字中有后缀,才做后缀白名单处理。 + rExt.exec( file.name ) && !this.accept.test( file.name ); + + return !invalid; + }, + + + /** + * @event beforeFileQueued + * @param {File} file File对象 + * @description 当文件被加入队列之前触发,此事件的handler返回值为`false`,则此文件不会被添加进入队列。 + * @for Uploader + */ + + /** + * @event fileQueued + * @param {File} file File对象 + * @description 当文件被加入队列以后触发。 + * @for Uploader + */ + + _addFile: function( file ) { + var me = this; + + file = me._wrapFile( file ); + + // 不过类型判断允许不允许,先派送 `beforeFileQueued` + if ( !me.owner.trigger( 'beforeFileQueued', file ) ) { + return; + } + + // 类型不匹配,则派送错误事件,并返回。 + if ( !me.acceptFile( file ) ) { + me.owner.trigger( 'error', 'Q_TYPE_DENIED', file ); + return; + } + + me.queue.append( file ); + me.owner.trigger( 'fileQueued', file ); + return file; + }, + + getFile: function( fileId ) { + return this.queue.getFile( fileId ); + }, + + /** + * @event filesQueued + * @param {File} files 数组,内容为原始File(lib/File)对象。 + * @description 当一批文件添加进队列以后触发。 + * @for Uploader + */ + + /** + * @property {Boolean} [auto=false] + * @namespace options + * @for Uploader + * @description 设置为 true 后,不需要手动调用上传,有文件选择即开始上传。 + * + */ + + /** + * @method addFiles + * @grammar addFiles( file ) => undefined + * @grammar addFiles( [file1, file2 ...] ) => undefined + * @param {Array of File or File} [files] Files 对象 数组 + * @description 添加文件到队列 + * @for Uploader + */ + addFile: function( files ) { + var me = this; + + if ( !files.length ) { + files = [ files ]; + } + + files = $.map( files, function( file ) { + return me._addFile( file ); + }); + + me.owner.trigger( 'filesQueued', files ); + + if ( me.options.auto ) { + setTimeout(function() { + me.request('start-upload'); + }, 20 ); + } + }, + + getStats: function() { + return this.stats; + }, + + /** + * @event fileDequeued + * @param {File} file File对象 + * @description 当文件被移除队列后触发。 + * @for Uploader + */ + + /** + * @method removeFile + * @grammar removeFile( file ) => undefined + * @grammar removeFile( id ) => undefined + * @grammar removeFile( file, true ) => undefined + * @grammar removeFile( id, true ) => undefined + * @param {File|id} file File对象或这File对象的id + * @description 移除某一文件, 默认只会标记文件状态为已取消,如果第二个参数为 `true` 则会从 queue 中移除。 + * @for Uploader + * @example + * + * $li.on('click', '.remove-this', function() { + * uploader.removeFile( file ); + * }) + */ + removeFile: function( file, remove ) { + var me = this; + + file = file.id ? file : me.queue.getFile( file ); + + this.request( 'cancel-file', file ); + + if ( remove ) { + this.queue.removeFile( file ); + } + }, + + /** + * @method getFiles + * @grammar getFiles() => Array + * @grammar getFiles( status1, status2, status... ) => Array + * @description 返回指定状态的文件集合,不传参数将返回所有状态的文件。 + * @for Uploader + * @example + * console.log( uploader.getFiles() ); // => all files + * console.log( uploader.getFiles('error') ) // => all error files. + */ + getFiles: function() { + return this.queue.getFiles.apply( this.queue, arguments ); + }, + + fetchFile: function() { + return this.queue.fetch.apply( this.queue, arguments ); + }, + + /** + * @method retry + * @grammar retry() => undefined + * @grammar retry( file ) => undefined + * @description 重试上传,重试指定文件,或者从出错的文件开始重新上传。 + * @for Uploader + * @example + * function retry() { + * uploader.retry(); + * } + */ + retry: function( file, noForceStart ) { + var me = this, + files, i, len; + + if ( file ) { + file = file.id ? file : me.queue.getFile( file ); + file.setStatus( Status.QUEUED ); + noForceStart || me.request('start-upload'); + return; + } + + files = me.queue.getFiles( Status.ERROR ); + i = 0; + len = files.length; + + for ( ; i < len; i++ ) { + file = files[ i ]; + file.setStatus( Status.QUEUED ); + } + + me.request('start-upload'); + }, + + /** + * @method sort + * @grammar sort( fn ) => undefined + * @description 排序队列中的文件,在上传之前调整可以控制上传顺序。 + * @for Uploader + */ + sortFiles: function() { + return this.queue.sort.apply( this.queue, arguments ); + }, + + /** + * @event reset + * @description 当 uploader 被重置的时候触发。 + * @for Uploader + */ + + /** + * @method reset + * @grammar reset() => undefined + * @description 重置uploader。目前只重置了队列。 + * @for Uploader + * @example + * uploader.reset(); + */ + reset: function() { + this.owner.trigger('reset'); + this.queue = new Queue(); + this.stats = this.queue.stats; + }, + + destroy: function() { + this.reset(); + this.placeholder && this.placeholder.destroy(); + } + }); + + }); + /** + * @fileOverview 添加获取Runtime相关信息的方法。 + */ + define('widgets/runtime',[ + 'uploader', + 'runtime/runtime', + 'widgets/widget' + ], function( Uploader, Runtime ) { + + Uploader.support = function() { + return Runtime.hasRuntime.apply( Runtime, arguments ); + }; + + /** + * @property {Object} [runtimeOrder=html5,flash] + * @namespace options + * @for Uploader + * @description 指定运行时启动顺序。默认会想尝试 html5 是否支持,如果支持则使用 html5, 否则则使用 flash. + * + * 可以将此值设置成 `flash`,来强制使用 flash 运行时。 + */ + + return Uploader.register({ + name: 'runtime', + + init: function() { + if ( !this.predictRuntimeType() ) { + throw Error('Runtime Error'); + } + }, + + /** + * 预测Uploader将采用哪个`Runtime` + * @grammar predictRuntimeType() => String + * @method predictRuntimeType + * @for Uploader + */ + predictRuntimeType: function() { + var orders = this.options.runtimeOrder || Runtime.orders, + type = this.type, + i, len; + + if ( !type ) { + orders = orders.split( /\s*,\s*/g ); + + for ( i = 0, len = orders.length; i < len; i++ ) { + if ( Runtime.hasRuntime( orders[ i ] ) ) { + this.type = type = orders[ i ]; + break; + } + } + } + + return type; + } + }); + }); + /** + * @fileOverview Transport + */ + define('lib/transport',[ + 'base', + 'runtime/client', + 'mediator' + ], function( Base, RuntimeClient, Mediator ) { + + var $ = Base.$; + + function Transport( opts ) { + var me = this; + + opts = me.options = $.extend( true, {}, Transport.options, opts || {} ); + RuntimeClient.call( this, 'Transport' ); + + this._blob = null; + this._formData = opts.formData || {}; + this._headers = opts.headers || {}; + + this.on( 'progress', this._timeout ); + this.on( 'load error', function() { + me.trigger( 'progress', 1 ); + clearTimeout( me._timer ); + }); + } + + Transport.options = { + server: '', + method: 'POST', + + // 跨域时,是否允许携带cookie, 只有html5 runtime才有效 + withCredentials: false, + fileVal: 'file', + timeout: 2 * 60 * 1000, // 2分钟 + formData: {}, + headers: {}, + sendAsBinary: false + }; + + $.extend( Transport.prototype, { + + // 添加Blob, 只能添加一次,最后一次有效。 + appendBlob: function( key, blob, filename ) { + var me = this, + opts = me.options; + + if ( me.getRuid() ) { + me.disconnectRuntime(); + } + + // 连接到blob归属的同一个runtime. + me.connectRuntime( blob.ruid, function() { + me.exec('init'); + }); + + me._blob = blob; + opts.fileVal = key || opts.fileVal; + opts.filename = filename || opts.filename; + }, + + // 添加其他字段 + append: function( key, value ) { + if ( typeof key === 'object' ) { + $.extend( this._formData, key ); + } else { + this._formData[ key ] = value; + } + }, + + setRequestHeader: function( key, value ) { + if ( typeof key === 'object' ) { + $.extend( this._headers, key ); + } else { + this._headers[ key ] = value; + } + }, + + send: function( method ) { + this.exec( 'send', method ); + this._timeout(); + }, + + abort: function() { + clearTimeout( this._timer ); + return this.exec('abort'); + }, + + destroy: function() { + this.trigger('destroy'); + this.off(); + this.exec('destroy'); + this.disconnectRuntime(); + }, + + getResponse: function() { + return this.exec('getResponse'); + }, + + getResponseAsJson: function() { + return this.exec('getResponseAsJson'); + }, + + getStatus: function() { + return this.exec('getStatus'); + }, + + _timeout: function() { + var me = this, + duration = me.options.timeout; + + if ( !duration ) { + return; + } + + clearTimeout( me._timer ); + me._timer = setTimeout(function() { + me.abort(); + me.trigger( 'error', 'timeout' ); + }, duration ); + } + + }); + + // 让Transport具备事件功能。 + Mediator.installTo( Transport.prototype ); + + return Transport; + }); + /** + * @fileOverview 负责文件上传相关。 + */ + define('widgets/upload',[ + 'base', + 'uploader', + 'file', + 'lib/transport', + 'widgets/widget' + ], function( Base, Uploader, WUFile, Transport ) { + + var $ = Base.$, + isPromise = Base.isPromise, + Status = WUFile.Status; + + // 添加默认配置项 + $.extend( Uploader.options, { + + + /** + * @property {Boolean} [prepareNextFile=false] + * @namespace options + * @for Uploader + * @description 是否允许在文件传输时提前把下一个文件准备好。 + * 对于一个文件的准备工作比较耗时,比如图片压缩,md5序列化。 + * 如果能提前在当前文件传输期处理,可以节省总体耗时。 + */ + prepareNextFile: false, + + /** + * @property {Boolean} [chunked=false] + * @namespace options + * @for Uploader + * @description 是否要分片处理大文件上传。 + */ + chunked: false, + + /** + * @property {Boolean} [chunkSize=5242880] + * @namespace options + * @for Uploader + * @description 如果要分片,分多大一片? 默认大小为5M. + */ + chunkSize: 5 * 1024 * 1024, + + /** + * @property {Boolean} [chunkRetry=2] + * @namespace options + * @for Uploader + * @description 如果某个分片由于网络问题出错,允许自动重传多少次? + */ + chunkRetry: 2, + + /** + * @property {Boolean} [threads=3] + * @namespace options + * @for Uploader + * @description 上传并发数。允许同时最大上传进程数。 + */ + threads: 3, + + + /** + * @property {Object} [formData={}] + * @namespace options + * @for Uploader + * @description 文件上传请求的参数表,每次发送都会发送此对象中的参数。 + */ + formData: {} + + /** + * @property {Object} [fileVal='file'] + * @namespace options + * @for Uploader + * @description 设置文件上传域的name。 + */ + + /** + * @property {Object} [method='POST'] + * @namespace options + * @for Uploader + * @description 文件上传方式,`POST`或者`GET`。 + */ + + /** + * @property {Object} [sendAsBinary=false] + * @namespace options + * @for Uploader + * @description 是否已二进制的流的方式发送文件,这样整个上传内容`php://input`都为文件内容, + * 其他参数在$_GET数组中。 + */ + }); + + // 负责将文件切片。 + function CuteFile( file, chunkSize ) { + var pending = [], + blob = file.source, + total = blob.size, + chunks = chunkSize ? Math.ceil( total / chunkSize ) : 1, + start = 0, + index = 0, + len, api; + + api = { + file: file, + + has: function() { + return !!pending.length; + }, + + shift: function() { + return pending.shift(); + }, + + unshift: function( block ) { + pending.unshift( block ); + } + }; + + while ( index < chunks ) { + len = Math.min( chunkSize, total - start ); + + pending.push({ + file: file, + start: start, + end: chunkSize ? (start + len) : total, + total: total, + chunks: chunks, + chunk: index++, + cuted: api + }); + start += len; + } + + file.blocks = pending.concat(); + file.remaning = pending.length; + + return api; + } + + Uploader.register({ + name: 'upload', + + init: function() { + var owner = this.owner, + me = this; + + this.runing = false; + this.progress = false; + + owner + .on( 'startUpload', function() { + me.progress = true; + }) + .on( 'uploadFinished', function() { + me.progress = false; + }); + + // 记录当前正在传的数据,跟threads相关 + this.pool = []; + + // 缓存分好片的文件。 + this.stack = []; + + // 缓存即将上传的文件。 + this.pending = []; + + // 跟踪还有多少分片在上传中但是没有完成上传。 + this.remaning = 0; + this.__tick = Base.bindFn( this._tick, this ); + + owner.on( 'uploadComplete', function( file ) { + + // 把其他块取消了。 + file.blocks && $.each( file.blocks, function( _, v ) { + v.transport && (v.transport.abort(), v.transport.destroy()); + delete v.transport; + }); + + delete file.blocks; + delete file.remaning; + }); + }, + + reset: function() { + this.request( 'stop-upload', true ); + this.runing = false; + this.pool = []; + this.stack = []; + this.pending = []; + this.remaning = 0; + this._trigged = false; + this._promise = null; + }, + + /** + * @event startUpload + * @description 当开始上传流程时触发。 + * @for Uploader + */ + + /** + * 开始上传。此方法可以从初始状态调用开始上传流程,也可以从暂停状态调用,继续上传流程。 + * + * 可以指定开始某一个文件。 + * @grammar upload() => undefined + * @grammar upload( file | fileId) => undefined + * @method upload + * @for Uploader + */ + startUpload: function(file) { + var me = this; + + // 移出invalid的文件 + $.each( me.request( 'get-files', Status.INVALID ), function() { + me.request( 'remove-file', this ); + }); + + // 如果指定了开始某个文件,则只开始指定文件。 + if ( file ) { + file = file.id ? file : me.request( 'get-file', file ); + + if (file.getStatus() === Status.INTERRUPT) { + $.each( me.pool, function( _, v ) { + + // 之前暂停过。 + if (v.file !== file) { + return; + } + + v.transport && v.transport.send(); + }); + + file.setStatus( Status.QUEUED ); + } else if (file.getStatus() === Status.PROGRESS) { + return; + } else { + file.setStatus( Status.QUEUED ); + } + } else { + $.each( me.request( 'get-files', [ Status.INITED ] ), function() { + this.setStatus( Status.QUEUED ); + }); + } + + if ( me.runing ) { + return; + } + + me.runing = true; + + var files = []; + + // 如果有暂停的,则续传 + $.each( me.pool, function( _, v ) { + var file = v.file; + + if ( file.getStatus() === Status.INTERRUPT ) { + files.push(file); + me._trigged = false; + v.transport && v.transport.send(); + } + }); + + var file; + while ( (file = files.shift()) ) { + file.setStatus( Status.PROGRESS ); + } + + file || $.each( me.request( 'get-files', + Status.INTERRUPT ), function() { + this.setStatus( Status.PROGRESS ); + }); + + me._trigged = false; + Base.nextTick( me.__tick ); + me.owner.trigger('startUpload'); + }, + + /** + * @event stopUpload + * @description 当开始上传流程暂停时触发。 + * @for Uploader + */ + + /** + * 暂停上传。第一个参数为是否中断上传当前正在上传的文件。 + * + * 如果第一个参数是文件,则只暂停指定文件。 + * @grammar stop() => undefined + * @grammar stop( true ) => undefined + * @grammar stop( file ) => undefined + * @method stop + * @for Uploader + */ + stopUpload: function( file, interrupt ) { + var me = this; + + if (file === true) { + interrupt = file; + file = null; + } + + if ( me.runing === false ) { + return; + } + + // 如果只是暂停某个文件。 + if ( file ) { + file = file.id ? file : me.request( 'get-file', file ); + + if ( file.getStatus() !== Status.PROGRESS && + file.getStatus() !== Status.QUEUED ) { + return; + } + + file.setStatus( Status.INTERRUPT ); + $.each( me.pool, function( _, v ) { + + // 只 abort 指定的文件。 + if (v.file !== file) { + return; + } + + v.transport && v.transport.abort(); + me._putback(v); + me._popBlock(v); + }); + + return Base.nextTick( me.__tick ); + } + + me.runing = false; + + if (this._promise && this._promise.file) { + this._promise.file.setStatus( Status.INTERRUPT ); + } + + interrupt && $.each( me.pool, function( _, v ) { + v.transport && v.transport.abort(); + v.file.setStatus( Status.INTERRUPT ); + }); + + me.owner.trigger('stopUpload'); + }, + + /** + * @method cancelFile + * @grammar cancelFile( file ) => undefined + * @grammar cancelFile( id ) => undefined + * @param {File|id} file File对象或这File对象的id + * @description 标记文件状态为已取消, 同时将中断文件传输。 + * @for Uploader + * @example + * + * $li.on('click', '.remove-this', function() { + * uploader.cancelFile( file ); + * }) + */ + cancelFile: function( file ) { + file = file.id ? file : this.request( 'get-file', file ); + + // 如果正在上传。 + file.blocks && $.each( file.blocks, function( _, v ) { + var _tr = v.transport; + + if ( _tr ) { + _tr.abort(); + _tr.destroy(); + delete v.transport; + } + }); + + file.setStatus( Status.CANCELLED ); + this.owner.trigger( 'fileDequeued', file ); + }, + + /** + * 判断`Uplaode`r是否正在上传中。 + * @grammar isInProgress() => Boolean + * @method isInProgress + * @for Uploader + */ + isInProgress: function() { + return !!this.progress; + }, + + _getStats: function() { + return this.request('get-stats'); + }, + + /** + * 掉过一个文件上传,直接标记指定文件为已上传状态。 + * @grammar skipFile( file ) => undefined + * @method skipFile + * @for Uploader + */ + skipFile: function( file, status ) { + file = file.id ? file : this.request( 'get-file', file ); + + file.setStatus( status || Status.COMPLETE ); + file.skipped = true; + + // 如果正在上传。 + file.blocks && $.each( file.blocks, function( _, v ) { + var _tr = v.transport; + + if ( _tr ) { + _tr.abort(); + _tr.destroy(); + delete v.transport; + } + }); + + this.owner.trigger( 'uploadSkip', file ); + }, + + /** + * @event uploadFinished + * @description 当所有文件上传结束时触发。 + * @for Uploader + */ + _tick: function() { + var me = this, + opts = me.options, + fn, val; + + // 上一个promise还没有结束,则等待完成后再执行。 + if ( me._promise ) { + return me._promise.always( me.__tick ); + } + + // 还有位置,且还有文件要处理的话。 + if ( me.pool.length < opts.threads && (val = me._nextBlock()) ) { + me._trigged = false; + + fn = function( val ) { + me._promise = null; + + // 有可能是reject过来的,所以要检测val的类型。 + val && val.file && me._startSend( val ); + Base.nextTick( me.__tick ); + }; + + me._promise = isPromise( val ) ? val.always( fn ) : fn( val ); + + // 没有要上传的了,且没有正在传输的了。 + } else if ( !me.remaning && !me._getStats().numOfQueue && + !me._getStats().numofInterrupt ) { + me.runing = false; + + me._trigged || Base.nextTick(function() { + me.owner.trigger('uploadFinished'); + }); + me._trigged = true; + } + }, + + _putback: function(block) { + var idx; + + block.cuted.unshift(block); + idx = this.stack.indexOf(block.cuted); + + if (!~idx) { + this.stack.unshift(block.cuted); + } + }, + + _getStack: function() { + var i = 0, + act; + + while ( (act = this.stack[ i++ ]) ) { + if ( act.has() && act.file.getStatus() === Status.PROGRESS ) { + return act; + } else if (!act.has() || + act.file.getStatus() !== Status.PROGRESS && + act.file.getStatus() !== Status.INTERRUPT ) { + + // 把已经处理完了的,或者,状态为非 progress(上传中)、 + // interupt(暂停中) 的移除。 + this.stack.splice( --i, 1 ); + } + } + + return null; + }, + + _nextBlock: function() { + var me = this, + opts = me.options, + act, next, done, preparing; + + // 如果当前文件还有没有需要传输的,则直接返回剩下的。 + if ( (act = this._getStack()) ) { + + // 是否提前准备下一个文件 + if ( opts.prepareNextFile && !me.pending.length ) { + me._prepareNextFile(); + } + + return act.shift(); + + // 否则,如果正在运行,则准备下一个文件,并等待完成后返回下个分片。 + } else if ( me.runing ) { + + // 如果缓存中有,则直接在缓存中取,没有则去queue中取。 + if ( !me.pending.length && me._getStats().numOfQueue ) { + me._prepareNextFile(); + } + + next = me.pending.shift(); + done = function( file ) { + if ( !file ) { + return null; + } + + act = CuteFile( file, opts.chunked ? opts.chunkSize : 0 ); + me.stack.push(act); + return act.shift(); + }; + + // 文件可能还在prepare中,也有可能已经完全准备好了。 + if ( isPromise( next) ) { + preparing = next.file; + next = next[ next.pipe ? 'pipe' : 'then' ]( done ); + next.file = preparing; + return next; + } + + return done( next ); + } + }, + + + /** + * @event uploadStart + * @param {File} file File对象 + * @description 某个文件开始上传前触发,一个文件只会触发一次。 + * @for Uploader + */ + _prepareNextFile: function() { + var me = this, + file = me.request('fetch-file'), + pending = me.pending, + promise; + + if ( file ) { + promise = me.request( 'before-send-file', file, function() { + + // 有可能文件被skip掉了。文件被skip掉后,状态坑定不是Queued. + if ( file.getStatus() === Status.PROGRESS || + file.getStatus() === Status.INTERRUPT ) { + return file; + } + + return me._finishFile( file ); + }); + + me.owner.trigger( 'uploadStart', file ); + file.setStatus( Status.PROGRESS ); + + promise.file = file; + + // 如果还在pending中,则替换成文件本身。 + promise.done(function() { + var idx = $.inArray( promise, pending ); + + ~idx && pending.splice( idx, 1, file ); + }); + + // befeore-send-file的钩子就有错误发生。 + promise.fail(function( reason ) { + file.setStatus( Status.ERROR, reason ); + me.owner.trigger( 'uploadError', file, reason ); + me.owner.trigger( 'uploadComplete', file ); + }); + + pending.push( promise ); + } + }, + + // 让出位置了,可以让其他分片开始上传 + _popBlock: function( block ) { + var idx = $.inArray( block, this.pool ); + + this.pool.splice( idx, 1 ); + block.file.remaning--; + this.remaning--; + }, + + // 开始上传,可以被掉过。如果promise被reject了,则表示跳过此分片。 + _startSend: function( block ) { + var me = this, + file = block.file, + promise; + + // 有可能在 before-send-file 的 promise 期间改变了文件状态。 + // 如:暂停,取消 + // 我们不能中断 promise, 但是可以在 promise 完后,不做上传操作。 + if ( file.getStatus() !== Status.PROGRESS ) { + + // 如果是中断,则还需要放回去。 + if (file.getStatus() === Status.INTERRUPT) { + me._putback(block); + } + + return; + } + + me.pool.push( block ); + me.remaning++; + + // 如果没有分片,则直接使用原始的。 + // 不会丢失content-type信息。 + block.blob = block.chunks === 1 ? file.source : + file.source.slice( block.start, block.end ); + + // hook, 每个分片发送之前可能要做些异步的事情。 + promise = me.request( 'before-send', block, function() { + + // 有可能文件已经上传出错了,所以不需要再传输了。 + if ( file.getStatus() === Status.PROGRESS ) { + me._doSend( block ); + } else { + me._popBlock( block ); + Base.nextTick( me.__tick ); + } + }); + + // 如果为fail了,则跳过此分片。 + promise.fail(function() { + if ( file.remaning === 1 ) { + me._finishFile( file ).always(function() { + block.percentage = 1; + me._popBlock( block ); + me.owner.trigger( 'uploadComplete', file ); + Base.nextTick( me.__tick ); + }); + } else { + block.percentage = 1; + me.updateFileProgress( file ); + me._popBlock( block ); + Base.nextTick( me.__tick ); + } + }); + }, + + + /** + * @event uploadBeforeSend + * @param {Object} object + * @param {Object} data 默认的上传参数,可以扩展此对象来控制上传参数。 + * @param {Object} headers 可以扩展此对象来控制上传头部。 + * @description 当某个文件的分块在发送前触发,主要用来询问是否要添加附带参数,大文件在开起分片上传的前提下此事件可能会触发多次。 + * @for Uploader + */ + + /** + * @event uploadAccept + * @param {Object} object + * @param {Object} ret 服务端的返回数据,json格式,如果服务端不是json格式,从ret._raw中取数据,自行解析。 + * @description 当某个文件上传到服务端响应后,会派送此事件来询问服务端响应是否有效。如果此事件handler返回值为`false`, 则此文件将派送`server`类型的`uploadError`事件。 + * @for Uploader + */ + + /** + * @event uploadProgress + * @param {File} file File对象 + * @param {Number} percentage 上传进度 + * @description 上传过程中触发,携带上传进度。 + * @for Uploader + */ + + + /** + * @event uploadError + * @param {File} file File对象 + * @param {String} reason 出错的code + * @description 当文件上传出错时触发。 + * @for Uploader + */ + + /** + * @event uploadSuccess + * @param {File} file File对象 + * @param {Object} response 服务端返回的数据 + * @description 当文件上传成功时触发。 + * @for Uploader + */ + + /** + * @event uploadComplete + * @param {File} [file] File对象 + * @description 不管成功或者失败,文件上传完成时触发。 + * @for Uploader + */ + + // 做上传操作。 + _doSend: function( block ) { + var me = this, + owner = me.owner, + opts = me.options, + file = block.file, + tr = new Transport( opts ), + data = $.extend({}, opts.formData ), + headers = $.extend({}, opts.headers ), + requestAccept, ret; + + block.transport = tr; + + tr.on( 'destroy', function() { + delete block.transport; + me._popBlock( block ); + Base.nextTick( me.__tick ); + }); + + // 广播上传进度。以文件为单位。 + tr.on( 'progress', function( percentage ) { + block.percentage = percentage; + me.updateFileProgress( file ); + }); + + // 用来询问,是否返回的结果是有错误的。 + requestAccept = function( reject ) { + var fn; + + ret = tr.getResponseAsJson() || {}; + ret._raw = tr.getResponse(); + fn = function( value ) { + reject = value; + }; + + // 服务端响应了,不代表成功了,询问是否响应正确。 + if ( !owner.trigger( 'uploadAccept', block, ret, fn ) ) { + reject = reject || 'server'; + } + + return reject; + }; + + // 尝试重试,然后广播文件上传出错。 + tr.on( 'error', function( type, flag ) { + block.retried = block.retried || 0; + + // 自动重试 + if ( block.chunks > 1 && ~'http,abort'.indexOf( type ) && + block.retried < opts.chunkRetry ) { + + block.retried++; + tr.send(); + + } else { + + // http status 500 ~ 600 + if ( !flag && type === 'server' ) { + type = requestAccept( type ); + } + + file.setStatus( Status.ERROR, type ); + owner.trigger( 'uploadError', file, type ); + owner.trigger( 'uploadComplete', file ); + } + }); + + // 上传成功 + tr.on( 'load', function() { + var reason; + + // 如果非预期,转向上传出错。 + if ( (reason = requestAccept()) ) { + tr.trigger( 'error', reason, true ); + return; + } + + // 全部上传完成。 + if ( file.remaning === 1 ) { + me._finishFile( file, ret ); + } else { + tr.destroy(); + } + }); + + // 配置默认的上传字段。 + data = $.extend( data, { + id: file.id, + name: file.name, + type: file.type, + lastModifiedDate: file.lastModifiedDate, + size: file.size + }); + + block.chunks > 1 && $.extend( data, { + chunks: block.chunks, + chunk: block.chunk + }); + + // 在发送之间可以添加字段什么的。。。 + // 如果默认的字段不够使用,可以通过监听此事件来扩展 + owner.trigger( 'uploadBeforeSend', block, data, headers ); + + // 开始发送。 + tr.appendBlob( opts.fileVal, block.blob, file.name ); + tr.append( data ); + tr.setRequestHeader( headers ); + tr.send(); + }, + + // 完成上传。 + _finishFile: function( file, ret, hds ) { + var owner = this.owner; + + return owner + .request( 'after-send-file', arguments, function() { + file.setStatus( Status.COMPLETE ); + owner.trigger( 'uploadSuccess', file, ret, hds ); + }) + .fail(function( reason ) { + + // 如果外部已经标记为invalid什么的,不再改状态。 + if ( file.getStatus() === Status.PROGRESS ) { + file.setStatus( Status.ERROR, reason ); + } + + owner.trigger( 'uploadError', file, reason ); + }) + .always(function() { + owner.trigger( 'uploadComplete', file ); + }); + }, + + updateFileProgress: function(file) { + var totalPercent = 0, + uploaded = 0; + + if (!file.blocks) { + return; + } + + $.each( file.blocks, function( _, v ) { + uploaded += (v.percentage || 0) * (v.end - v.start); + }); + + totalPercent = uploaded / file.size; + this.owner.trigger( 'uploadProgress', file, totalPercent || 0 ); + } + + }); + }); + /** + * @fileOverview 各种验证,包括文件总大小是否超出、单文件是否超出和文件是否重复。 + */ + + define('widgets/validator',[ + 'base', + 'uploader', + 'file', + 'widgets/widget' + ], function( Base, Uploader, WUFile ) { + + var $ = Base.$, + validators = {}, + api; + + /** + * @event error + * @param {String} type 错误类型。 + * @description 当validate不通过时,会以派送错误事件的形式通知调用者。通过`upload.on('error', handler)`可以捕获到此类错误,目前有以下错误会在特定的情况下派送错来。 + * + * * `Q_EXCEED_NUM_LIMIT` 在设置了`fileNumLimit`且尝试给`uploader`添加的文件数量超出这个值时派送。 + * * `Q_EXCEED_SIZE_LIMIT` 在设置了`Q_EXCEED_SIZE_LIMIT`且尝试给`uploader`添加的文件总大小超出这个值时派送。 + * * `Q_TYPE_DENIED` 当文件类型不满足时触发。。 + * @for Uploader + */ + + // 暴露给外面的api + api = { + + // 添加验证器 + addValidator: function( type, cb ) { + validators[ type ] = cb; + }, + + // 移除验证器 + removeValidator: function( type ) { + delete validators[ type ]; + } + }; + + // 在Uploader初始化的时候启动Validators的初始化 + Uploader.register({ + name: 'validator', + + init: function() { + var me = this; + Base.nextTick(function() { + $.each( validators, function() { + this.call( me.owner ); + }); + }); + } + }); + + /** + * @property {int} [fileNumLimit=undefined] + * @namespace options + * @for Uploader + * @description 验证文件总数量, 超出则不允许加入队列。 + */ + api.addValidator( 'fileNumLimit', function() { + var uploader = this, + opts = uploader.options, + count = 0, + max = parseInt( opts.fileNumLimit, 10 ), + flag = true; + + if ( !max ) { + return; + } + + uploader.on( 'beforeFileQueued', function( file ) { + + if ( count >= max && flag ) { + flag = false; + this.trigger( 'error', 'Q_EXCEED_NUM_LIMIT', max, file ); + setTimeout(function() { + flag = true; + }, 1 ); + } + + return count >= max ? false : true; + }); + + uploader.on( 'fileQueued', function() { + count++; + }); + + uploader.on( 'fileDequeued', function() { + count--; + }); + + uploader.on( 'reset', function() { + count = 0; + }); + }); + + + /** + * @property {int} [fileSizeLimit=undefined] + * @namespace options + * @for Uploader + * @description 验证文件总大小是否超出限制, 超出则不允许加入队列。 + */ + api.addValidator( 'fileSizeLimit', function() { + var uploader = this, + opts = uploader.options, + count = 0, + max = parseInt( opts.fileSizeLimit, 10 ), + flag = true; + + if ( !max ) { + return; + } + + uploader.on( 'beforeFileQueued', function( file ) { + var invalid = count + file.size > max; + + if ( invalid && flag ) { + flag = false; + this.trigger( 'error', 'Q_EXCEED_SIZE_LIMIT', max, file ); + setTimeout(function() { + flag = true; + }, 1 ); + } + + return invalid ? false : true; + }); + + uploader.on( 'fileQueued', function( file ) { + count += file.size; + }); + + uploader.on( 'fileDequeued', function( file ) { + count -= file.size; + }); + + uploader.on( 'reset', function() { + count = 0; + }); + }); + + /** + * @property {int} [fileSingleSizeLimit=undefined] + * @namespace options + * @for Uploader + * @description 验证单个文件大小是否超出限制, 超出则不允许加入队列。 + */ + api.addValidator( 'fileSingleSizeLimit', function() { + var uploader = this, + opts = uploader.options, + max = opts.fileSingleSizeLimit; + + if ( !max ) { + return; + } + + uploader.on( 'beforeFileQueued', function( file ) { + + if ( file.size > max ) { + file.setStatus( WUFile.Status.INVALID, 'exceed_size' ); + this.trigger( 'error', 'F_EXCEED_SIZE', max, file ); + return false; + } + + }); + + }); + + /** + * @property {Boolean} [duplicate=undefined] + * @namespace options + * @for Uploader + * @description 去重, 根据文件名字、文件大小和最后修改时间来生成hash Key. + */ + api.addValidator( 'duplicate', function() { + var uploader = this, + opts = uploader.options, + mapping = {}; + + if ( opts.duplicate ) { + return; + } + + function hashString( str ) { + var hash = 0, + i = 0, + len = str.length, + _char; + + for ( ; i < len; i++ ) { + _char = str.charCodeAt( i ); + hash = _char + (hash << 6) + (hash << 16) - hash; + } + + return hash; + } + + uploader.on( 'beforeFileQueued', function( file ) { + var hash = file.__hash || (file.__hash = hashString( file.name + + file.size + file.lastModifiedDate )); + + // 已经重复了 + if ( mapping[ hash ] ) { + this.trigger( 'error', 'F_DUPLICATE', file ); + return false; + } + }); + + uploader.on( 'fileQueued', function( file ) { + var hash = file.__hash; + + hash && (mapping[ hash ] = true); + }); + + uploader.on( 'fileDequeued', function( file ) { + var hash = file.__hash; + + hash && (delete mapping[ hash ]); + }); + + uploader.on( 'reset', function() { + mapping = {}; + }); + }); + + return api; + }); + + /** + * @fileOverview Runtime管理器,负责Runtime的选择, 连接 + */ + define('runtime/compbase',[],function() { + + function CompBase( owner, runtime ) { + + this.owner = owner; + this.options = owner.options; + + this.getRuntime = function() { + return runtime; + }; + + this.getRuid = function() { + return runtime.uid; + }; + + this.trigger = function() { + return owner.trigger.apply( owner, arguments ); + }; + } + + return CompBase; + }); + /** + * @fileOverview Html5Runtime + */ + define('runtime/html5/runtime',[ + 'base', + 'runtime/runtime', + 'runtime/compbase' + ], function( Base, Runtime, CompBase ) { + + var type = 'html5', + components = {}; + + function Html5Runtime() { + var pool = {}, + me = this, + destroy = this.destroy; + + Runtime.apply( me, arguments ); + me.type = type; + + + // 这个方法的调用者,实际上是RuntimeClient + me.exec = function( comp, fn/*, args...*/) { + var client = this, + uid = client.uid, + args = Base.slice( arguments, 2 ), + instance; + + if ( components[ comp ] ) { + instance = pool[ uid ] = pool[ uid ] || + new components[ comp ]( client, me ); + + if ( instance[ fn ] ) { + return instance[ fn ].apply( instance, args ); + } + } + }; + + me.destroy = function() { + // @todo 删除池子中的所有实例 + return destroy && destroy.apply( this, arguments ); + }; + } + + Base.inherits( Runtime, { + constructor: Html5Runtime, + + // 不需要连接其他程序,直接执行callback + init: function() { + var me = this; + setTimeout(function() { + me.trigger('ready'); + }, 1 ); + } + + }); + + // 注册Components + Html5Runtime.register = function( name, component ) { + var klass = components[ name ] = Base.inherits( CompBase, component ); + return klass; + }; + + // 注册html5运行时。 + // 只有在支持的前提下注册。 + if ( window.Blob && window.FileReader && window.DataView ) { + Runtime.addRuntime( type, Html5Runtime ); + } + + return Html5Runtime; + }); + /** + * @fileOverview Blob Html实现 + */ + define('runtime/html5/blob',[ + 'runtime/html5/runtime', + 'lib/blob' + ], function( Html5Runtime, Blob ) { + + return Html5Runtime.register( 'Blob', { + slice: function( start, end ) { + var blob = this.owner.source, + slice = blob.slice || blob.webkitSlice || blob.mozSlice; + + blob = slice.call( blob, start, end ); + + return new Blob( this.getRuid(), blob ); + } + }); + }); + /** + * @fileOverview FilePaste + */ + define('runtime/html5/dnd',[ + 'base', + 'runtime/html5/runtime', + 'lib/file' + ], function( Base, Html5Runtime, File ) { + + var $ = Base.$, + prefix = 'webuploader-dnd-'; + + return Html5Runtime.register( 'DragAndDrop', { + init: function() { + var elem = this.elem = this.options.container; + + this.dragEnterHandler = Base.bindFn( this._dragEnterHandler, this ); + this.dragOverHandler = Base.bindFn( this._dragOverHandler, this ); + this.dragLeaveHandler = Base.bindFn( this._dragLeaveHandler, this ); + this.dropHandler = Base.bindFn( this._dropHandler, this ); + this.dndOver = false; + + elem.on( 'dragenter', this.dragEnterHandler ); + elem.on( 'dragover', this.dragOverHandler ); + elem.on( 'dragleave', this.dragLeaveHandler ); + elem.on( 'drop', this.dropHandler ); + + if ( this.options.disableGlobalDnd ) { + $( document ).on( 'dragover', this.dragOverHandler ); + $( document ).on( 'drop', this.dropHandler ); + } + }, + + _dragEnterHandler: function( e ) { + var me = this, + denied = me._denied || false, + items; + + e = e.originalEvent || e; + + if ( !me.dndOver ) { + me.dndOver = true; + + // 注意只有 chrome 支持。 + items = e.dataTransfer.items; + + if ( items && items.length ) { + me._denied = denied = !me.trigger( 'accept', items ); + } + + me.elem.addClass( prefix + 'over' ); + me.elem[ denied ? 'addClass' : + 'removeClass' ]( prefix + 'denied' ); + } + + e.dataTransfer.dropEffect = denied ? 'none' : 'copy'; + + return false; + }, + + _dragOverHandler: function( e ) { + // 只处理框内的。 + var parentElem = this.elem.parent().get( 0 ); + if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) { + return false; + } + + clearTimeout( this._leaveTimer ); + this._dragEnterHandler.call( this, e ); + + return false; + }, + + _dragLeaveHandler: function() { + var me = this, + handler; + + handler = function() { + me.dndOver = false; + me.elem.removeClass( prefix + 'over ' + prefix + 'denied' ); + }; + + clearTimeout( me._leaveTimer ); + me._leaveTimer = setTimeout( handler, 100 ); + return false; + }, + + _dropHandler: function( e ) { + var me = this, + ruid = me.getRuid(), + parentElem = me.elem.parent().get( 0 ), + dataTransfer, data; + + // 只处理框内的。 + if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) { + return false; + } + + e = e.originalEvent || e; + dataTransfer = e.dataTransfer; + + // 如果是页面内拖拽,还不能处理,不阻止事件。 + // 此处 ie11 下会报参数错误, + try { + data = dataTransfer.getData('text/html'); + } catch( err ) { + } + + if ( data ) { + return; + } + + me._getTansferFiles( dataTransfer, function( results ) { + me.trigger( 'drop', $.map( results, function( file ) { + return new File( ruid, file ); + }) ); + }); + + me.dndOver = false; + me.elem.removeClass( prefix + 'over' ); + return false; + }, + + // 如果传入 callback 则去查看文件夹,否则只管当前文件夹。 + _getTansferFiles: function( dataTransfer, callback ) { + var results = [], + promises = [], + items, files, file, item, i, len, canAccessFolder; + + items = dataTransfer.items; + files = dataTransfer.files; + + canAccessFolder = !!(items && items[ 0 ].webkitGetAsEntry); + + for ( i = 0, len = files.length; i < len; i++ ) { + file = files[ i ]; + item = items && items[ i ]; + + if ( canAccessFolder && item.webkitGetAsEntry().isDirectory ) { + + promises.push( this._traverseDirectoryTree( + item.webkitGetAsEntry(), results ) ); + } else { + results.push( file ); + } + } + + Base.when.apply( Base, promises ).done(function() { + + if ( !results.length ) { + return; + } + + callback( results ); + }); + }, + + _traverseDirectoryTree: function( entry, results ) { + var deferred = Base.Deferred(), + me = this; + + if ( entry.isFile ) { + entry.file(function( file ) { + results.push( file ); + deferred.resolve(); + }); + } else if ( entry.isDirectory ) { + entry.createReader().readEntries(function( entries ) { + var len = entries.length, + promises = [], + arr = [], // 为了保证顺序。 + i; + + for ( i = 0; i < len; i++ ) { + promises.push( me._traverseDirectoryTree( + entries[ i ], arr ) ); + } + + Base.when.apply( Base, promises ).then(function() { + results.push.apply( results, arr ); + deferred.resolve(); + }, deferred.reject ); + }); + } + + return deferred.promise(); + }, + + destroy: function() { + var elem = this.elem; + + // 还没 init 就调用 destroy + if (!elem) { + return; + } + + elem.off( 'dragenter', this.dragEnterHandler ); + elem.off( 'dragover', this.dragOverHandler ); + elem.off( 'dragleave', this.dragLeaveHandler ); + elem.off( 'drop', this.dropHandler ); + + if ( this.options.disableGlobalDnd ) { + $( document ).off( 'dragover', this.dragOverHandler ); + $( document ).off( 'drop', this.dropHandler ); + } + } + }); + }); + + /** + * @fileOverview FilePaste + */ + define('runtime/html5/filepaste',[ + 'base', + 'runtime/html5/runtime', + 'lib/file' + ], function( Base, Html5Runtime, File ) { + + return Html5Runtime.register( 'FilePaste', { + init: function() { + var opts = this.options, + elem = this.elem = opts.container, + accept = '.*', + arr, i, len, item; + + // accetp的mimeTypes中生成匹配正则。 + if ( opts.accept ) { + arr = []; + + for ( i = 0, len = opts.accept.length; i < len; i++ ) { + item = opts.accept[ i ].mimeTypes; + item && arr.push( item ); + } + + if ( arr.length ) { + accept = arr.join(','); + accept = accept.replace( /,/g, '|' ).replace( /\*/g, '.*' ); + } + } + this.accept = accept = new RegExp( accept, 'i' ); + this.hander = Base.bindFn( this._pasteHander, this ); + elem.on( 'paste', this.hander ); + }, + + _pasteHander: function( e ) { + var allowed = [], + ruid = this.getRuid(), + items, item, blob, i, len; + + e = e.originalEvent || e; + items = e.clipboardData.items; + + for ( i = 0, len = items.length; i < len; i++ ) { + item = items[ i ]; + + if ( item.kind !== 'file' || !(blob = item.getAsFile()) ) { + continue; + } + + allowed.push( new File( ruid, blob ) ); + } + + if ( allowed.length ) { + // 不阻止非文件粘贴(文字粘贴)的事件冒泡 + e.preventDefault(); + e.stopPropagation(); + this.trigger( 'paste', allowed ); + } + }, + + destroy: function() { + this.elem.off( 'paste', this.hander ); + } + }); + }); + + /** + * @fileOverview FilePicker + */ + define('runtime/html5/filepicker',[ + 'base', + 'runtime/html5/runtime' + ], function( Base, Html5Runtime ) { + + var $ = Base.$; + + return Html5Runtime.register( 'FilePicker', { + init: function() { + var container = this.getRuntime().getContainer(), + me = this, + owner = me.owner, + opts = me.options, + label = this.label = $( document.createElement('label') ), + input = this.input = $( document.createElement('input') ), + arr, i, len, mouseHandler; + + input.attr( 'type', 'file' ); + input.attr( 'name', opts.name ); + input.addClass('webuploader-element-invisible'); + + label.on( 'click', function() { + input.trigger('click'); + }); + + label.css({ + opacity: 0, + width: '100%', + height: '100%', + display: 'block', + cursor: 'pointer', + background: '#ffffff' + }); + + if ( opts.multiple ) { + input.attr( 'multiple', 'multiple' ); + } + + // @todo Firefox不支持单独指定后缀 + if ( opts.accept && opts.accept.length > 0 ) { + arr = []; + + for ( i = 0, len = opts.accept.length; i < len; i++ ) { + arr.push( opts.accept[ i ].mimeTypes ); + } + + input.attr( 'accept', arr.join(',') ); + } + + container.append( input ); + container.append( label ); + + mouseHandler = function( e ) { + owner.trigger( e.type ); + }; + + input.on( 'change', function( e ) { + var fn = arguments.callee, + clone; + + me.files = e.target.files; + + // reset input + clone = this.cloneNode( true ); + clone.value = null; + this.parentNode.replaceChild( clone, this ); + + input.off(); + input = $( clone ).on( 'change', fn ) + .on( 'mouseenter mouseleave', mouseHandler ); + + owner.trigger('change'); + }); + + label.on( 'mouseenter mouseleave', mouseHandler ); + + }, + + + getFiles: function() { + return this.files; + }, + + destroy: function() { + this.input.off(); + this.label.off(); + } + }); + }); + /** + * @fileOverview Transport + * @todo 支持chunked传输,优势: + * 可以将大文件分成小块,挨个传输,可以提高大文件成功率,当失败的时候,也只需要重传那小部分, + * 而不需要重头再传一次。另外断点续传也需要用chunked方式。 + */ + define('runtime/html5/transport',[ + 'base', + 'runtime/html5/runtime' + ], function( Base, Html5Runtime ) { + + var noop = Base.noop, + $ = Base.$; + + return Html5Runtime.register( 'Transport', { + init: function() { + this._status = 0; + this._response = null; + }, + + send: function() { + var owner = this.owner, + opts = this.options, + xhr = this._initAjax(), + blob = owner._blob, + server = opts.server, + formData, binary, fr; + + if ( opts.sendAsBinary ) { + server += (/\?/.test( server ) ? '&' : '?') + + $.param( owner._formData ); + + binary = blob.getSource(); + } else { + formData = new FormData(); + $.each( owner._formData, function( k, v ) { + formData.append( k, v ); + }); + + formData.append( opts.fileVal, blob.getSource(), + opts.filename || owner._formData.name || '' ); + } + + if ( opts.withCredentials && 'withCredentials' in xhr ) { + xhr.open( opts.method, server, true ); + xhr.withCredentials = true; + } else { + xhr.open( opts.method, server ); + } + + this._setRequestHeader( xhr, opts.headers ); + + if ( binary ) { + // 强制设置成 content-type 为文件流。 + xhr.overrideMimeType && + xhr.overrideMimeType('application/octet-stream'); + + // android直接发送blob会导致服务端接收到的是空文件。 + // bug详情。 + // https://code.google.com/p/android/issues/detail?id=39882 + // 所以先用fileReader读取出来再通过arraybuffer的方式发送。 + if ( Base.os.android ) { + fr = new FileReader(); + + fr.onload = function() { + xhr.send( this.result ); + fr = fr.onload = null; + }; + + fr.readAsArrayBuffer( binary ); + } else { + xhr.send( binary ); + } + } else { + xhr.send( formData ); + } + }, + + getResponse: function() { + return this._response; + }, + + getResponseAsJson: function() { + return this._parseJson( this._response ); + }, + + getStatus: function() { + return this._status; + }, + + abort: function() { + var xhr = this._xhr; + + if ( xhr ) { + xhr.upload.onprogress = noop; + xhr.onreadystatechange = noop; + xhr.abort(); + + this._xhr = xhr = null; + } + }, + + destroy: function() { + this.abort(); + }, + + _initAjax: function() { + var me = this, + xhr = new XMLHttpRequest(), + opts = this.options; + + if ( opts.withCredentials && !('withCredentials' in xhr) && + typeof XDomainRequest !== 'undefined' ) { + xhr = new XDomainRequest(); + } + + xhr.upload.onprogress = function( e ) { + var percentage = 0; + + if ( e.lengthComputable ) { + percentage = e.loaded / e.total; + } + + return me.trigger( 'progress', percentage ); + }; + + xhr.onreadystatechange = function() { + + if ( xhr.readyState !== 4 ) { + return; + } + + xhr.upload.onprogress = noop; + xhr.onreadystatechange = noop; + me._xhr = null; + me._status = xhr.status; + + if ( xhr.status >= 200 && xhr.status < 300 ) { + me._response = xhr.responseText; + return me.trigger('load'); + } else if ( xhr.status >= 500 && xhr.status < 600 ) { + me._response = xhr.responseText; + return me.trigger( 'error', 'server' ); + } + + + return me.trigger( 'error', me._status ? 'http' : 'abort' ); + }; + + me._xhr = xhr; + return xhr; + }, + + _setRequestHeader: function( xhr, headers ) { + $.each( headers, function( key, val ) { + xhr.setRequestHeader( key, val ); + }); + }, + + _parseJson: function( str ) { + var json; + + try { + json = JSON.parse( str ); + } catch ( ex ) { + json = {}; + } + + return json; + } + }); + }); + /** + * @fileOverview FlashRuntime + */ + define('runtime/flash/runtime',[ + 'base', + 'runtime/runtime', + 'runtime/compbase' + ], function( Base, Runtime, CompBase ) { + + var $ = Base.$, + type = 'flash', + components = {}; + + + function getFlashVersion() { + var version; + + try { + version = navigator.plugins[ 'Shockwave Flash' ]; + version = version.description; + } catch ( ex ) { + try { + version = new ActiveXObject('ShockwaveFlash.ShockwaveFlash') + .GetVariable('$version'); + } catch ( ex2 ) { + version = '0.0'; + } + } + version = version.match( /\d+/g ); + return parseFloat( version[ 0 ] + '.' + version[ 1 ], 10 ); + } + + function FlashRuntime() { + var pool = {}, + clients = {}, + destroy = this.destroy, + me = this, + jsreciver = Base.guid('webuploader_'); + + Runtime.apply( me, arguments ); + me.type = type; + + + // 这个方法的调用者,实际上是RuntimeClient + me.exec = function( comp, fn/*, args...*/ ) { + var client = this, + uid = client.uid, + args = Base.slice( arguments, 2 ), + instance; + + clients[ uid ] = client; + + if ( components[ comp ] ) { + if ( !pool[ uid ] ) { + pool[ uid ] = new components[ comp ]( client, me ); + } + + instance = pool[ uid ]; + + if ( instance[ fn ] ) { + return instance[ fn ].apply( instance, args ); + } + } + + return me.flashExec.apply( client, arguments ); + }; + + function handler( evt, obj ) { + var type = evt.type || evt, + parts, uid; + + parts = type.split('::'); + uid = parts[ 0 ]; + type = parts[ 1 ]; + + // console.log.apply( console, arguments ); + + if ( type === 'Ready' && uid === me.uid ) { + me.trigger('ready'); + } else if ( clients[ uid ] ) { + clients[ uid ].trigger( type.toLowerCase(), evt, obj ); + } + + // Base.log( evt, obj ); + } + + // flash的接受器。 + window[ jsreciver ] = function() { + var args = arguments; + + // 为了能捕获得到。 + setTimeout(function() { + handler.apply( null, args ); + }, 1 ); + }; + + this.jsreciver = jsreciver; + + this.destroy = function() { + // @todo 删除池子中的所有实例 + return destroy && destroy.apply( this, arguments ); + }; + + this.flashExec = function( comp, fn ) { + var flash = me.getFlash(), + args = Base.slice( arguments, 2 ); + + return flash.exec( this.uid, comp, fn, args ); + }; + + // @todo + } + + Base.inherits( Runtime, { + constructor: FlashRuntime, + + init: function() { + var container = this.getContainer(), + opts = this.options, + html; + + // if not the minimal height, shims are not initialized + // in older browsers (e.g FF3.6, IE6,7,8, Safari 4.0,5.0, etc) + container.css({ + position: 'absolute', + top: '-8px', + left: '-8px', + width: '9px', + height: '9px', + overflow: 'hidden' + }); + + // insert flash object + html = '' + + '' + + '' + + '' + + ''; + + container.html( html ); + }, + + getFlash: function() { + if ( this._flash ) { + return this._flash; + } + + this._flash = $( '#' + this.uid ).get( 0 ); + return this._flash; + } + + }); + + FlashRuntime.register = function( name, component ) { + component = components[ name ] = Base.inherits( CompBase, $.extend({ + + // @todo fix this later + flashExec: function() { + var owner = this.owner, + runtime = this.getRuntime(); + + return runtime.flashExec.apply( owner, arguments ); + } + }, component ) ); + + return component; + }; + + if ( getFlashVersion() >= 11.4 ) { + Runtime.addRuntime( type, FlashRuntime ); + } + + return FlashRuntime; + }); + /** + * @fileOverview FilePicker + */ + define('runtime/flash/filepicker',[ + 'base', + 'runtime/flash/runtime' + ], function( Base, FlashRuntime ) { + var $ = Base.$; + + return FlashRuntime.register( 'FilePicker', { + init: function( opts ) { + var copy = $.extend({}, opts ), + len, i; + + // 修复Flash再没有设置title的情况下无法弹出flash文件选择框的bug. + len = copy.accept && copy.accept.length; + for ( i = 0; i < len; i++ ) { + if ( !copy.accept[ i ].title ) { + copy.accept[ i ].title = 'Files'; + } + } + + delete copy.button; + delete copy.id; + delete copy.container; + + this.flashExec( 'FilePicker', 'init', copy ); + }, + + destroy: function() { + this.flashExec( 'FilePicker', 'destroy' ); + } + }); + }); + /** + * @fileOverview Transport flash实现 + */ + define('runtime/flash/transport',[ + 'base', + 'runtime/flash/runtime', + 'runtime/client' + ], function( Base, FlashRuntime, RuntimeClient ) { + var $ = Base.$; + + return FlashRuntime.register( 'Transport', { + init: function() { + this._status = 0; + this._response = null; + this._responseJson = null; + }, + + send: function() { + var owner = this.owner, + opts = this.options, + xhr = this._initAjax(), + blob = owner._blob, + server = opts.server, + binary; + + xhr.connectRuntime( blob.ruid ); + + if ( opts.sendAsBinary ) { + server += (/\?/.test( server ) ? '&' : '?') + + $.param( owner._formData ); + + binary = blob.uid; + } else { + $.each( owner._formData, function( k, v ) { + xhr.exec( 'append', k, v ); + }); + + xhr.exec( 'appendBlob', opts.fileVal, blob.uid, + opts.filename || owner._formData.name || '' ); + } + + this._setRequestHeader( xhr, opts.headers ); + xhr.exec( 'send', { + method: opts.method, + url: server, + forceURLStream: opts.forceURLStream, + mimeType: 'application/octet-stream' + }, binary ); + }, + + getStatus: function() { + return this._status; + }, + + getResponse: function() { + return this._response || ''; + }, + + getResponseAsJson: function() { + return this._responseJson; + }, + + abort: function() { + var xhr = this._xhr; + + if ( xhr ) { + xhr.exec('abort'); + xhr.destroy(); + this._xhr = xhr = null; + } + }, + + destroy: function() { + this.abort(); + }, + + _initAjax: function() { + var me = this, + xhr = new RuntimeClient('XMLHttpRequest'); + + xhr.on( 'uploadprogress progress', function( e ) { + var percent = e.loaded / e.total; + percent = Math.min( 1, Math.max( 0, percent ) ); + return me.trigger( 'progress', percent ); + }); + + xhr.on( 'load', function() { + var status = xhr.exec('getStatus'), + readBody = false, + err = '', + p; + + xhr.off(); + me._xhr = null; + + if ( status >= 200 && status < 300 ) { + readBody = true; + } else if ( status >= 500 && status < 600 ) { + readBody = true; + err = 'server'; + } else { + err = 'http'; + } + + if ( readBody ) { + me._response = xhr.exec('getResponse'); + me._response = decodeURIComponent( me._response ); + + // flash 处理可能存在 bug, 没辙只能靠 js 了 + // try { + // me._responseJson = xhr.exec('getResponseAsJson'); + // } catch ( error ) { + + p = window.JSON && window.JSON.parse || function( s ) { + try { + return new Function('return ' + s).call(); + } catch ( err ) { + return {}; + } + }; + me._responseJson = me._response ? p(me._response) : {}; + + // } + } + + xhr.destroy(); + xhr = null; + + return err ? me.trigger( 'error', err ) : me.trigger('load'); + }); + + xhr.on( 'error', function() { + xhr.off(); + me._xhr = null; + me.trigger( 'error', 'http' ); + }); + + me._xhr = xhr; + return xhr; + }, + + _setRequestHeader: function( xhr, headers ) { + $.each( headers, function( key, val ) { + xhr.exec( 'setRequestHeader', key, val ); + }); + } + }); + }); + /** + * @fileOverview Blob Html实现 + */ + define('runtime/flash/blob',[ + 'runtime/flash/runtime', + 'lib/blob' + ], function( FlashRuntime, Blob ) { + + return FlashRuntime.register( 'Blob', { + slice: function( start, end ) { + var blob = this.flashExec( 'Blob', 'slice', start, end ); + + return new Blob( blob.uid, blob ); + } + }); + }); + /** + * @fileOverview 没有图像处理的版本。 + */ + define('preset/withoutimage',[ + 'base', + + // widgets + 'widgets/filednd', + 'widgets/filepaste', + 'widgets/filepicker', + 'widgets/queue', + 'widgets/runtime', + 'widgets/upload', + 'widgets/validator', + + // runtimes + // html5 + 'runtime/html5/blob', + 'runtime/html5/dnd', + 'runtime/html5/filepaste', + 'runtime/html5/filepicker', + 'runtime/html5/transport', + + // flash + 'runtime/flash/filepicker', + 'runtime/flash/transport', + 'runtime/flash/blob' + ], function( Base ) { + return Base; + }); + define('webuploader',[ + 'preset/withoutimage' + ], function( preset ) { + return preset; + }); + return require('webuploader'); +}); diff --git a/static/plugins/webuploader/webuploader.noimage.min.js b/static/plugins/webuploader/webuploader.noimage.min.js new file mode 100644 index 00000000..1eaca480 --- /dev/null +++ b/static/plugins/webuploader/webuploader.noimage.min.js @@ -0,0 +1,2 @@ +/* WebUploader 0.1.5 */!function(a,b){var c,d={},e=function(a,b){var c,d,e;if("string"==typeof a)return h(a);for(c=[],d=a.length,e=0;d>e;e++)c.push(h(a[e]));return b.apply(null,c)},f=function(a,b,c){2===arguments.length&&(c=b,b=null),e(b||[],function(){g(a,c,arguments)})},g=function(a,b,c){var f,g={exports:b};"function"==typeof b&&(c.length||(c=[e,g.exports,g]),f=b.apply(null,c),void 0!==f&&(g.exports=f)),d[a]=g.exports},h=function(b){var c=d[b]||a[b];if(!c)throw new Error("`"+b+"` is undefined");return c},i=function(a){var b,c,e,f,g,h;h=function(a){return a&&a.charAt(0).toUpperCase()+a.substr(1)};for(b in d)if(c=a,d.hasOwnProperty(b)){for(e=b.split("/"),g=h(e.pop());f=h(e.shift());)c[f]=c[f]||{},c=c[f];c[g]=d[b]}return a},j=function(c){return a.__dollar=c,i(b(a,f,e))};"object"==typeof module&&"object"==typeof module.exports?module.exports=j():"function"==typeof define&&define.amd?define(["jquery"],j):(c=a.WebUploader,a.WebUploader=j(),a.WebUploader.noConflict=function(){a.WebUploader=c})}(window,function(a,b,c){return b("dollar-third",[],function(){var b=a.__dollar||a.jQuery||a.Zepto;if(!b)throw new Error("jQuery or Zepto not found!");return b}),b("dollar",["dollar-third"],function(a){return a}),b("promise-third",["dollar"],function(a){return{Deferred:a.Deferred,when:a.when,isPromise:function(a){return a&&"function"==typeof a.then}}}),b("promise",["promise-third"],function(a){return a}),b("base",["dollar","promise"],function(b,c){function d(a){return function(){return h.apply(a,arguments)}}function e(a,b){return function(){return a.apply(b,arguments)}}function f(a){var b;return Object.create?Object.create(a):(b=function(){},b.prototype=a,new b)}var g=function(){},h=Function.call;return{version:"0.1.5",$:b,Deferred:c.Deferred,isPromise:c.isPromise,when:c.when,browser:function(a){var b={},c=a.match(/WebKit\/([\d.]+)/),d=a.match(/Chrome\/([\d.]+)/)||a.match(/CriOS\/([\d.]+)/),e=a.match(/MSIE\s([\d\.]+)/)||a.match(/(?:trident)(?:.*rv:([\w.]+))?/i),f=a.match(/Firefox\/([\d.]+)/),g=a.match(/Safari\/([\d.]+)/),h=a.match(/OPR\/([\d.]+)/);return c&&(b.webkit=parseFloat(c[1])),d&&(b.chrome=parseFloat(d[1])),e&&(b.ie=parseFloat(e[1])),f&&(b.firefox=parseFloat(f[1])),g&&(b.safari=parseFloat(g[1])),h&&(b.opera=parseFloat(h[1])),b}(navigator.userAgent),os:function(a){var b={},c=a.match(/(?:Android);?[\s\/]+([\d.]+)?/),d=a.match(/(?:iPad|iPod|iPhone).*OS\s([\d_]+)/);return c&&(b.android=parseFloat(c[1])),d&&(b.ios=parseFloat(d[1].replace(/_/g,"."))),b}(navigator.userAgent),inherits:function(a,c,d){var e;return"function"==typeof c?(e=c,c=null):e=c&&c.hasOwnProperty("constructor")?c.constructor:function(){return a.apply(this,arguments)},b.extend(!0,e,a,d||{}),e.__super__=a.prototype,e.prototype=f(a.prototype),c&&b.extend(!0,e.prototype,c),e},noop:g,bindFn:e,log:function(){return a.console?e(console.log,console):g}(),nextTick:function(){return function(a){setTimeout(a,1)}}(),slice:d([].slice),guid:function(){var a=0;return function(b){for(var c=(+new Date).toString(32),d=0;5>d;d++)c+=Math.floor(65535*Math.random()).toString(32);return(b||"wu_")+c+(a++).toString(32)}}(),formatSize:function(a,b,c){var d;for(c=c||["B","K","M","G","TB"];(d=c.shift())&&a>1024;)a/=1024;return("B"===d?a:a.toFixed(b||2))+d}}}),b("mediator",["base"],function(a){function b(a,b,c,d){return f.grep(a,function(a){return!(!a||b&&a.e!==b||c&&a.cb!==c&&a.cb._cb!==c||d&&a.ctx!==d)})}function c(a,b,c){f.each((a||"").split(h),function(a,d){c(d,b)})}function d(a,b){for(var c,d=!1,e=-1,f=a.length;++e1?void(d.isPlainObject(b)&&d.isPlainObject(c[a])?d.extend(c[a],b):c[a]=b):a?c[a]:c},getStats:function(){var a=this.request("get-stats");return a?{successNum:a.numOfSuccess,progressNum:a.numOfProgress,cancelNum:a.numOfCancel,invalidNum:a.numOfInvalid,uploadFailNum:a.numOfUploadFailed,queueNum:a.numOfQueue,interruptNum:a.numofInterrupt}:{}},trigger:function(a){var c=[].slice.call(arguments,1),e=this.options,f="on"+a.substring(0,1).toUpperCase()+a.substring(1);return b.trigger.apply(this,arguments)===!1||d.isFunction(e[f])&&e[f].apply(this,c)===!1||d.isFunction(this[f])&&this[f].apply(this,c)===!1||b.trigger.apply(b,[this,a].concat(c))===!1?!1:!0},destroy:function(){this.request("destroy",arguments),this.off()},request:a.noop}),a.create=c.create=function(a){return new c(a)},a.Uploader=c,c}),b("runtime/runtime",["base","mediator"],function(a,b){function c(b){this.options=d.extend({container:document.body},b),this.uid=a.guid("rt_")}var d=a.$,e={},f=function(a){for(var b in a)if(a.hasOwnProperty(b))return b;return null};return d.extend(c.prototype,{getContainer:function(){var a,b,c=this.options;return this._container?this._container:(a=d(c.container||document.body),b=d(document.createElement("div")),b.attr("id","rt_"+this.uid),b.css({position:"absolute",top:"0px",left:"0px",width:"1px",height:"1px",overflow:"hidden"}),a.append(b),a.addClass("webuploader-container"),this._container=b,this._parent=a,b)},init:a.noop,exec:a.noop,destroy:function(){this._container&&this._container.remove(),this._parent&&this._parent.removeClass("webuploader-container"),this.off()}}),c.orders="html5,flash",c.addRuntime=function(a,b){e[a]=b},c.hasRuntime=function(a){return!!(a?e[a]:f(e))},c.create=function(a,b){var g,h;if(b=b||c.orders,d.each(b.split(/\s*,\s*/g),function(){return e[this]?(g=this,!1):void 0}),g=g||f(e),!g)throw new Error("Runtime Error");return h=new e[g](a)},b.installTo(c.prototype),c}),b("runtime/client",["base","mediator","runtime/runtime"],function(a,b,c){function d(b,d){var f,g=a.Deferred();this.uid=a.guid("client_"),this.runtimeReady=function(a){return g.done(a)},this.connectRuntime=function(b,h){if(f)throw new Error("already connected!");return g.done(h),"string"==typeof b&&e.get(b)&&(f=e.get(b)),f=f||e.get(null,d),f?(a.$.extend(f.options,b),f.__promise.then(g.resolve),f.__client++):(f=c.create(b,b.runtimeOrder),f.__promise=g.promise(),f.once("ready",g.resolve),f.init(),e.add(f),f.__client=1),d&&(f.__standalone=d),f},this.getRuntime=function(){return f},this.disconnectRuntime=function(){f&&(f.__client--,f.__client<=0&&(e.remove(f),delete f.__promise,f.destroy()),f=null)},this.exec=function(){if(f){var c=a.slice(arguments);return b&&c.unshift(b),f.exec.apply(this,c)}},this.getRuid=function(){return f&&f.uid},this.destroy=function(a){return function(){a&&a.apply(this,arguments),this.trigger("destroy"),this.off(),this.exec("destroy"),this.disconnectRuntime()}}(this.destroy)}var e;return e=function(){var a={};return{add:function(b){a[b.uid]=b},get:function(b,c){var d;if(b)return a[b];for(d in a)if(!c||!a[d].__standalone)return a[d];return null},remove:function(b){delete a[b.uid]}}}(),b.installTo(d.prototype),d}),b("lib/dnd",["base","mediator","runtime/client"],function(a,b,c){function d(a){a=this.options=e.extend({},d.options,a),a.container=e(a.container),a.container.length&&c.call(this,"DragAndDrop")}var e=a.$;return d.options={accept:null,disableGlobalDnd:!1},a.inherits(c,{constructor:d,init:function(){var a=this;a.connectRuntime(a.options,function(){a.exec("init"),a.trigger("ready")})}}),b.installTo(d.prototype),d}),b("widgets/widget",["base","uploader"],function(a,b){function c(a){if(!a)return!1;var b=a.length,c=e.type(a);return 1===a.nodeType&&b?!0:"array"===c||"function"!==c&&"string"!==c&&(0===b||"number"==typeof b&&b>0&&b-1 in a)}function d(a){this.owner=a,this.options=a.options}var e=a.$,f=b.prototype._init,g=b.prototype.destroy,h={},i=[];return e.extend(d.prototype,{init:a.noop,invoke:function(a,b){var c=this.responseMap;return c&&a in c&&c[a]in this&&e.isFunction(this[c[a]])?this[c[a]].apply(this,b):h},request:function(){return this.owner.request.apply(this.owner,arguments)}}),e.extend(b.prototype,{_init:function(){var a=this,b=a._widgets=[],c=a.options.disableWidgets||"";return e.each(i,function(d,e){(!c||!~c.indexOf(e._name))&&b.push(new e(a))}),f.apply(a,arguments)},request:function(b,d,e){var f,g,i,j,k=0,l=this._widgets,m=l&&l.length,n=[],o=[];for(d=c(d)?d:[d];m>k;k++)f=l[k],g=f.invoke(b,d),g!==h&&(a.isPromise(g)?o.push(g):n.push(g));return e||o.length?(i=a.when.apply(a,o),j=i.pipe?"pipe":"then",i[j](function(){var b=a.Deferred(),c=arguments;return 1===c.length&&(c=c[0]),setTimeout(function(){b.resolve(c)},1),b.promise()})[e?j:"done"](e||a.noop)):n[0]},destroy:function(){g.apply(this,arguments),this._widgets=null}}),b.register=d.register=function(b,c){var f,g={init:"init",destroy:"destroy",name:"anonymous"};return 1===arguments.length?(c=b,e.each(c,function(a){return"_"===a[0]||"name"===a?void("name"===a&&(g.name=c.name)):void(g[a.replace(/[A-Z]/g,"-$&").toLowerCase()]=a)})):g=e.extend(g,b),c.responseMap=g,f=a.inherits(d,c),f._name=g.name,i.push(f),f},b.unRegister=d.unRegister=function(a){if(a&&"anonymous"!==a)for(var b=i.length;b--;)i[b]._name===a&&i.splice(b,1)},d}),b("widgets/filednd",["base","uploader","lib/dnd","widgets/widget"],function(a,b,c){var d=a.$;return b.options.dnd="",b.register({name:"dnd",init:function(b){if(b.dnd&&"html5"===this.request("predict-runtime-type")){var e,f=this,g=a.Deferred(),h=d.extend({},{disableGlobalDnd:b.disableGlobalDnd,container:b.dnd,accept:b.accept});return this.dnd=e=new c(h),e.once("ready",g.resolve),e.on("drop",function(a){f.request("add-file",[a])}),e.on("accept",function(a){return f.owner.trigger("dndAccept",a)}),e.init(),g.promise()}},destroy:function(){this.dnd&&this.dnd.destroy()}})}),b("lib/filepaste",["base","mediator","runtime/client"],function(a,b,c){function d(a){a=this.options=e.extend({},a),a.container=e(a.container||document.body),c.call(this,"FilePaste")}var e=a.$;return a.inherits(c,{constructor:d,init:function(){var a=this;a.connectRuntime(a.options,function(){a.exec("init"),a.trigger("ready")})}}),b.installTo(d.prototype),d}),b("widgets/filepaste",["base","uploader","lib/filepaste","widgets/widget"],function(a,b,c){var d=a.$;return b.register({name:"paste",init:function(b){if(b.paste&&"html5"===this.request("predict-runtime-type")){var e,f=this,g=a.Deferred(),h=d.extend({},{container:b.paste,accept:b.accept});return this.paste=e=new c(h),e.once("ready",g.resolve),e.on("paste",function(a){f.owner.request("add-file",[a])}),e.init(),g.promise()}},destroy:function(){this.paste&&this.paste.destroy()}})}),b("lib/blob",["base","runtime/client"],function(a,b){function c(a,c){var d=this;d.source=c,d.ruid=a,this.size=c.size||0,this.type=!c.type&&this.ext&&~"jpg,jpeg,png,gif,bmp".indexOf(this.ext)?"image/"+("jpg"===this.ext?"jpeg":this.ext):c.type||"application/octet-stream",b.call(d,"Blob"),this.uid=c.uid||this.uid,a&&d.connectRuntime(a)}return a.inherits(b,{constructor:c,slice:function(a,b){return this.exec("slice",a,b)},getSource:function(){return this.source}}),c}),b("lib/file",["base","lib/blob"],function(a,b){function c(a,c){var f;this.name=c.name||"untitled"+d++,f=e.exec(c.name)?RegExp.$1.toLowerCase():"",!f&&c.type&&(f=/\/(jpg|jpeg|png|gif|bmp)$/i.exec(c.type)?RegExp.$1.toLowerCase():"",this.name+="."+f),this.ext=f,this.lastModifiedDate=c.lastModifiedDate||(new Date).toLocaleString(),b.apply(this,arguments)}var d=1,e=/\.([^.]+)$/;return a.inherits(b,c)}),b("lib/filepicker",["base","runtime/client","lib/file"],function(b,c,d){function e(a){if(a=this.options=f.extend({},e.options,a),a.container=f(a.id),!a.container.length)throw new Error("按钮指定错误");a.innerHTML=a.innerHTML||a.label||a.container.html()||"",a.button=f(a.button||document.createElement("div")),a.button.html(a.innerHTML),a.container.html(a.button),c.call(this,"FilePicker",!0)}var f=b.$;return e.options={button:null,container:null,label:null,innerHTML:null,multiple:!0,accept:null,name:"file"},b.inherits(c,{constructor:e,init:function(){var c=this,e=c.options,g=e.button;g.addClass("webuploader-pick"),c.on("all",function(a){var b;switch(a){case"mouseenter":g.addClass("webuploader-pick-hover");break;case"mouseleave":g.removeClass("webuploader-pick-hover");break;case"change":b=c.exec("getFiles"),c.trigger("select",f.map(b,function(a){return a=new d(c.getRuid(),a),a._refer=e.container,a}),e.container)}}),c.connectRuntime(e,function(){c.refresh(),c.exec("init",e),c.trigger("ready")}),this._resizeHandler=b.bindFn(this.refresh,this),f(a).on("resize",this._resizeHandler)},refresh:function(){var a=this.getRuntime().getContainer(),b=this.options.button,c=b.outerWidth?b.outerWidth():b.width(),d=b.outerHeight?b.outerHeight():b.height(),e=b.offset();c&&d&&a.css({bottom:"auto",right:"auto",width:c+"px",height:d+"px"}).offset(e)},enable:function(){var a=this.options.button;a.removeClass("webuploader-pick-disable"),this.refresh()},disable:function(){var a=this.options.button;this.getRuntime().getContainer().css({top:"-99999px"}),a.addClass("webuploader-pick-disable")},destroy:function(){var b=this.options.button;f(a).off("resize",this._resizeHandler),b.removeClass("webuploader-pick-disable webuploader-pick-hover webuploader-pick")}}),e}),b("widgets/filepicker",["base","uploader","lib/filepicker","widgets/widget"],function(a,b,c){var d=a.$;return d.extend(b.options,{pick:null,accept:null}),b.register({name:"picker",init:function(a){return this.pickers=[],a.pick&&this.addBtn(a.pick)},refresh:function(){d.each(this.pickers,function(){this.refresh()})},addBtn:function(b){var e=this,f=e.options,g=f.accept,h=[];if(b)return d.isPlainObject(b)||(b={id:b}),d(b.id).each(function(){var i,j,k;k=a.Deferred(),i=d.extend({},b,{accept:d.isPlainObject(g)?[g]:g,swf:f.swf,runtimeOrder:f.runtimeOrder,id:this}),j=new c(i),j.once("ready",k.resolve),j.on("select",function(a){e.owner.request("add-file",[a])}),j.init(),e.pickers.push(j),h.push(k.promise())}),a.when.apply(a,h)},disable:function(){d.each(this.pickers,function(){this.disable()})},enable:function(){d.each(this.pickers,function(){this.enable()})},destroy:function(){d.each(this.pickers,function(){this.destroy()}),this.pickers=null}})}),b("file",["base","mediator"],function(a,b){function c(){return f+g++}function d(a){this.name=a.name||"Untitled",this.size=a.size||0,this.type=a.type||"application/octet-stream",this.lastModifiedDate=a.lastModifiedDate||1*new Date,this.id=c(),this.ext=h.exec(this.name)?RegExp.$1:"",this.statusText="",i[this.id]=d.Status.INITED,this.source=a,this.loaded=0,this.on("error",function(a){this.setStatus(d.Status.ERROR,a)})}var e=a.$,f="WU_FILE_",g=0,h=/\.([^.]+)$/,i={};return e.extend(d.prototype,{setStatus:function(a,b){var c=i[this.id];"undefined"!=typeof b&&(this.statusText=b),a!==c&&(i[this.id]=a,this.trigger("statuschange",a,c))},getStatus:function(){return i[this.id]},getSource:function(){return this.source},destroy:function(){this.off(),delete i[this.id]}}),b.installTo(d.prototype),d.Status={INITED:"inited",QUEUED:"queued",PROGRESS:"progress",ERROR:"error",COMPLETE:"complete",CANCELLED:"cancelled",INTERRUPT:"interrupt",INVALID:"invalid"},d}),b("queue",["base","mediator","file"],function(a,b,c){function d(){this.stats={numOfQueue:0,numOfSuccess:0,numOfCancel:0,numOfProgress:0,numOfUploadFailed:0,numOfInvalid:0,numofDeleted:0,numofInterrupt:0},this._queue=[],this._map={}}var e=a.$,f=c.Status;return e.extend(d.prototype,{append:function(a){return this._queue.push(a),this._fileAdded(a),this},prepend:function(a){return this._queue.unshift(a),this._fileAdded(a),this},getFile:function(a){return"string"!=typeof a?a:this._map[a]},fetch:function(a){var b,c,d=this._queue.length;for(a=a||f.QUEUED,b=0;d>b;b++)if(c=this._queue[b],a===c.getStatus())return c;return null},sort:function(a){"function"==typeof a&&this._queue.sort(a)},getFiles:function(){for(var a,b=[].slice.call(arguments,0),c=[],d=0,f=this._queue.length;f>d;d++)a=this._queue[d],(!b.length||~e.inArray(a.getStatus(),b))&&c.push(a);return c},removeFile:function(a){var b=this._map[a.id];b&&(delete this._map[a.id],a.destroy(),this.stats.numofDeleted++)},_fileAdded:function(a){var b=this,c=this._map[a.id];c||(this._map[a.id]=a,a.on("statuschange",function(a,c){b._onFileStatusChange(a,c)}))},_onFileStatusChange:function(a,b){var c=this.stats;switch(b){case f.PROGRESS:c.numOfProgress--;break;case f.QUEUED:c.numOfQueue--;break;case f.ERROR:c.numOfUploadFailed--;break;case f.INVALID:c.numOfInvalid--;break;case f.INTERRUPT:c.numofInterrupt--}switch(a){case f.QUEUED:c.numOfQueue++;break;case f.PROGRESS:c.numOfProgress++;break;case f.ERROR:c.numOfUploadFailed++;break;case f.COMPLETE:c.numOfSuccess++;break;case f.CANCELLED:c.numOfCancel++;break;case f.INVALID:c.numOfInvalid++;break;case f.INTERRUPT:c.numofInterrupt++}}}),b.installTo(d.prototype),d}),b("widgets/queue",["base","uploader","queue","file","lib/file","runtime/client","widgets/widget"],function(a,b,c,d,e,f){var g=a.$,h=/\.\w+$/,i=d.Status;return b.register({name:"queue",init:function(b){var d,e,h,i,j,k,l,m=this;if(g.isPlainObject(b.accept)&&(b.accept=[b.accept]),b.accept){for(j=[],h=0,e=b.accept.length;e>h;h++)i=b.accept[h].extensions,i&&j.push(i);j.length&&(k="\\."+j.join(",").replace(/,/g,"$|\\.").replace(/\*/g,".*")+"$"),m.accept=new RegExp(k,"i")}return m.queue=new c,m.stats=m.queue.stats,"html5"===this.request("predict-runtime-type")?(d=a.Deferred(),this.placeholder=l=new f("Placeholder"),l.connectRuntime({runtimeOrder:"html5"},function(){m._ruid=l.getRuid(),d.resolve()}),d.promise()):void 0},_wrapFile:function(a){if(!(a instanceof d)){if(!(a instanceof e)){if(!this._ruid)throw new Error("Can't add external files.");a=new e(this._ruid,a)}a=new d(a)}return a},acceptFile:function(a){var b=!a||!a.size||this.accept&&h.exec(a.name)&&!this.accept.test(a.name);return!b},_addFile:function(a){var b=this;return a=b._wrapFile(a),b.owner.trigger("beforeFileQueued",a)?b.acceptFile(a)?(b.queue.append(a),b.owner.trigger("fileQueued",a),a):void b.owner.trigger("error","Q_TYPE_DENIED",a):void 0},getFile:function(a){return this.queue.getFile(a)},addFile:function(a){var b=this;a.length||(a=[a]),a=g.map(a,function(a){return b._addFile(a)}),b.owner.trigger("filesQueued",a),b.options.auto&&setTimeout(function(){b.request("start-upload")},20)},getStats:function(){return this.stats},removeFile:function(a,b){var c=this;a=a.id?a:c.queue.getFile(a),this.request("cancel-file",a),b&&this.queue.removeFile(a)},getFiles:function(){return this.queue.getFiles.apply(this.queue,arguments)},fetchFile:function(){return this.queue.fetch.apply(this.queue,arguments)},retry:function(a,b){var c,d,e,f=this;if(a)return a=a.id?a:f.queue.getFile(a),a.setStatus(i.QUEUED),void(b||f.request("start-upload"));for(c=f.queue.getFiles(i.ERROR),d=0,e=c.length;e>d;d++)a=c[d],a.setStatus(i.QUEUED);f.request("start-upload")},sortFiles:function(){return this.queue.sort.apply(this.queue,arguments)},reset:function(){this.owner.trigger("reset"),this.queue=new c,this.stats=this.queue.stats},destroy:function(){this.reset(),this.placeholder&&this.placeholder.destroy()}})}),b("widgets/runtime",["uploader","runtime/runtime","widgets/widget"],function(a,b){return a.support=function(){return b.hasRuntime.apply(b,arguments)},a.register({name:"runtime",init:function(){if(!this.predictRuntimeType())throw Error("Runtime Error")},predictRuntimeType:function(){var a,c,d=this.options.runtimeOrder||b.orders,e=this.type;if(!e)for(d=d.split(/\s*,\s*/g),a=0,c=d.length;c>a;a++)if(b.hasRuntime(d[a])){this.type=e=d[a];break}return e}})}),b("lib/transport",["base","runtime/client","mediator"],function(a,b,c){function d(a){var c=this;a=c.options=e.extend(!0,{},d.options,a||{}),b.call(this,"Transport"),this._blob=null,this._formData=a.formData||{},this._headers=a.headers||{},this.on("progress",this._timeout),this.on("load error",function(){c.trigger("progress",1),clearTimeout(c._timer)})}var e=a.$;return d.options={server:"",method:"POST",withCredentials:!1,fileVal:"file",timeout:12e4,formData:{},headers:{},sendAsBinary:!1},e.extend(d.prototype,{appendBlob:function(a,b,c){var d=this,e=d.options;d.getRuid()&&d.disconnectRuntime(),d.connectRuntime(b.ruid,function(){d.exec("init")}),d._blob=b,e.fileVal=a||e.fileVal,e.filename=c||e.filename},append:function(a,b){"object"==typeof a?e.extend(this._formData,a):this._formData[a]=b},setRequestHeader:function(a,b){"object"==typeof a?e.extend(this._headers,a):this._headers[a]=b},send:function(a){this.exec("send",a),this._timeout()},abort:function(){return clearTimeout(this._timer),this.exec("abort")},destroy:function(){this.trigger("destroy"),this.off(),this.exec("destroy"),this.disconnectRuntime()},getResponse:function(){return this.exec("getResponse")},getResponseAsJson:function(){return this.exec("getResponseAsJson")},getStatus:function(){return this.exec("getStatus")},_timeout:function(){var a=this,b=a.options.timeout;b&&(clearTimeout(a._timer),a._timer=setTimeout(function(){a.abort(),a.trigger("error","timeout")},b))}}),c.installTo(d.prototype),d}),b("widgets/upload",["base","uploader","file","lib/transport","widgets/widget"],function(a,b,c,d){function e(a,b){var c,d,e=[],f=a.source,g=f.size,h=b?Math.ceil(g/b):1,i=0,j=0;for(d={file:a,has:function(){return!!e.length},shift:function(){return e.shift()},unshift:function(a){e.unshift(a)}};h>j;)c=Math.min(b,g-i),e.push({file:a,start:i,end:b?i+c:g,total:g,chunks:h,chunk:j++,cuted:d}),i+=c;return a.blocks=e.concat(),a.remaning=e.length,d}var f=a.$,g=a.isPromise,h=c.Status;f.extend(b.options,{prepareNextFile:!1,chunked:!1,chunkSize:5242880,chunkRetry:2,threads:3,formData:{}}),b.register({name:"upload",init:function(){var b=this.owner,c=this;this.runing=!1,this.progress=!1,b.on("startUpload",function(){c.progress=!0}).on("uploadFinished",function(){c.progress=!1}),this.pool=[],this.stack=[],this.pending=[],this.remaning=0,this.__tick=a.bindFn(this._tick,this),b.on("uploadComplete",function(a){a.blocks&&f.each(a.blocks,function(a,b){b.transport&&(b.transport.abort(),b.transport.destroy()),delete b.transport}),delete a.blocks,delete a.remaning})},reset:function(){this.request("stop-upload",!0),this.runing=!1,this.pool=[],this.stack=[],this.pending=[],this.remaning=0,this._trigged=!1,this._promise=null},startUpload:function(b){var c=this;if(f.each(c.request("get-files",h.INVALID),function(){c.request("remove-file",this)}),b)if(b=b.id?b:c.request("get-file",b),b.getStatus()===h.INTERRUPT)f.each(c.pool,function(a,c){c.file===b&&c.transport&&c.transport.send()}),b.setStatus(h.QUEUED);else{if(b.getStatus()===h.PROGRESS)return;b.setStatus(h.QUEUED)}else f.each(c.request("get-files",[h.INITED]),function(){this.setStatus(h.QUEUED)});if(!c.runing){c.runing=!0;var d=[];f.each(c.pool,function(a,b){var e=b.file;e.getStatus()===h.INTERRUPT&&(d.push(e),c._trigged=!1,b.transport&&b.transport.send())});for(var b;b=d.shift();)b.setStatus(h.PROGRESS);b||f.each(c.request("get-files",h.INTERRUPT),function(){this.setStatus(h.PROGRESS)}),c._trigged=!1,a.nextTick(c.__tick),c.owner.trigger("startUpload")}},stopUpload:function(b,c){var d=this;if(b===!0&&(c=b,b=null),d.runing!==!1){if(b){if(b=b.id?b:d.request("get-file",b),b.getStatus()!==h.PROGRESS&&b.getStatus()!==h.QUEUED)return;return b.setStatus(h.INTERRUPT),f.each(d.pool,function(a,c){c.file===b&&(c.transport&&c.transport.abort(),d._putback(c),d._popBlock(c))}),a.nextTick(d.__tick)}d.runing=!1,this._promise&&this._promise.file&&this._promise.file.setStatus(h.INTERRUPT),c&&f.each(d.pool,function(a,b){b.transport&&b.transport.abort(),b.file.setStatus(h.INTERRUPT)}),d.owner.trigger("stopUpload")}},cancelFile:function(a){a=a.id?a:this.request("get-file",a),a.blocks&&f.each(a.blocks,function(a,b){var c=b.transport;c&&(c.abort(),c.destroy(),delete b.transport)}),a.setStatus(h.CANCELLED),this.owner.trigger("fileDequeued",a)},isInProgress:function(){return!!this.progress},_getStats:function(){return this.request("get-stats")},skipFile:function(a,b){a=a.id?a:this.request("get-file",a),a.setStatus(b||h.COMPLETE),a.skipped=!0,a.blocks&&f.each(a.blocks,function(a,b){var c=b.transport;c&&(c.abort(),c.destroy(),delete b.transport)}),this.owner.trigger("uploadSkip",a)},_tick:function(){var b,c,d=this,e=d.options;return d._promise?d._promise.always(d.__tick):void(d.pool.length1&&~"http,abort".indexOf(a)&&b.retried1&&f.extend(m,{chunks:b.chunks,chunk:b.chunk}),i.trigger("uploadBeforeSend",b,m,n),l.appendBlob(j.fileVal,b.blob,k.name),l.append(m),l.setRequestHeader(n),l.send()},_finishFile:function(a,b,c){var d=this.owner;return d.request("after-send-file",arguments,function(){a.setStatus(h.COMPLETE),d.trigger("uploadSuccess",a,b,c)}).fail(function(b){a.getStatus()===h.PROGRESS&&a.setStatus(h.ERROR,b),d.trigger("uploadError",a,b)}).always(function(){d.trigger("uploadComplete",a)})},updateFileProgress:function(a){var b=0,c=0;a.blocks&&(f.each(a.blocks,function(a,b){c+=(b.percentage||0)*(b.end-b.start)}),b=c/a.size,this.owner.trigger("uploadProgress",a,b||0))}})}),b("widgets/validator",["base","uploader","file","widgets/widget"],function(a,b,c){var d,e=a.$,f={};return d={addValidator:function(a,b){f[a]=b},removeValidator:function(a){delete f[a]}},b.register({name:"validator",init:function(){var b=this;a.nextTick(function(){e.each(f,function(){this.call(b.owner)})})}}),d.addValidator("fileNumLimit",function(){var a=this,b=a.options,c=0,d=parseInt(b.fileNumLimit,10),e=!0;d&&(a.on("beforeFileQueued",function(a){return c>=d&&e&&(e=!1,this.trigger("error","Q_EXCEED_NUM_LIMIT",d,a),setTimeout(function(){e=!0},1)),c>=d?!1:!0}),a.on("fileQueued",function(){c++}),a.on("fileDequeued",function(){c--}),a.on("reset",function(){c=0}))}),d.addValidator("fileSizeLimit",function(){var a=this,b=a.options,c=0,d=parseInt(b.fileSizeLimit,10),e=!0;d&&(a.on("beforeFileQueued",function(a){var b=c+a.size>d;return b&&e&&(e=!1,this.trigger("error","Q_EXCEED_SIZE_LIMIT",d,a),setTimeout(function(){e=!0},1)),b?!1:!0}),a.on("fileQueued",function(a){c+=a.size}),a.on("fileDequeued",function(a){c-=a.size}),a.on("reset",function(){c=0}))}),d.addValidator("fileSingleSizeLimit",function(){var a=this,b=a.options,d=b.fileSingleSizeLimit;d&&a.on("beforeFileQueued",function(a){return a.size>d?(a.setStatus(c.Status.INVALID,"exceed_size"),this.trigger("error","F_EXCEED_SIZE",d,a),!1):void 0})}),d.addValidator("duplicate",function(){function a(a){for(var b,c=0,d=0,e=a.length;e>d;d++)b=a.charCodeAt(d),c=b+(c<<6)+(c<<16)-c;return c}var b=this,c=b.options,d={};c.duplicate||(b.on("beforeFileQueued",function(b){var c=b.__hash||(b.__hash=a(b.name+b.size+b.lastModifiedDate));return d[c]?(this.trigger("error","F_DUPLICATE",b),!1):void 0}),b.on("fileQueued",function(a){var b=a.__hash;b&&(d[b]=!0)}),b.on("fileDequeued",function(a){var b=a.__hash;b&&delete d[b]}),b.on("reset",function(){d={}}))}),d}),b("runtime/compbase",[],function(){function a(a,b){this.owner=a,this.options=a.options,this.getRuntime=function(){return b},this.getRuid=function(){return b.uid},this.trigger=function(){return a.trigger.apply(a,arguments)}}return a}),b("runtime/html5/runtime",["base","runtime/runtime","runtime/compbase"],function(b,c,d){function e(){var a={},d=this,e=this.destroy;c.apply(d,arguments),d.type=f,d.exec=function(c,e){var f,h=this,i=h.uid,j=b.slice(arguments,2);return g[c]&&(f=a[i]=a[i]||new g[c](h,d),f[e])?f[e].apply(f,j):void 0},d.destroy=function(){return e&&e.apply(this,arguments)}}var f="html5",g={};return b.inherits(c,{constructor:e,init:function(){var a=this;setTimeout(function(){a.trigger("ready")},1)}}),e.register=function(a,c){var e=g[a]=b.inherits(d,c);return e},a.Blob&&a.FileReader&&a.DataView&&c.addRuntime(f,e),e}),b("runtime/html5/blob",["runtime/html5/runtime","lib/blob"],function(a,b){return a.register("Blob",{slice:function(a,c){var d=this.owner.source,e=d.slice||d.webkitSlice||d.mozSlice; +return d=e.call(d,a,c),new b(this.getRuid(),d)}})}),b("runtime/html5/dnd",["base","runtime/html5/runtime","lib/file"],function(a,b,c){var d=a.$,e="webuploader-dnd-";return b.register("DragAndDrop",{init:function(){var b=this.elem=this.options.container;this.dragEnterHandler=a.bindFn(this._dragEnterHandler,this),this.dragOverHandler=a.bindFn(this._dragOverHandler,this),this.dragLeaveHandler=a.bindFn(this._dragLeaveHandler,this),this.dropHandler=a.bindFn(this._dropHandler,this),this.dndOver=!1,b.on("dragenter",this.dragEnterHandler),b.on("dragover",this.dragOverHandler),b.on("dragleave",this.dragLeaveHandler),b.on("drop",this.dropHandler),this.options.disableGlobalDnd&&(d(document).on("dragover",this.dragOverHandler),d(document).on("drop",this.dropHandler))},_dragEnterHandler:function(a){var b,c=this,d=c._denied||!1;return a=a.originalEvent||a,c.dndOver||(c.dndOver=!0,b=a.dataTransfer.items,b&&b.length&&(c._denied=d=!c.trigger("accept",b)),c.elem.addClass(e+"over"),c.elem[d?"addClass":"removeClass"](e+"denied")),a.dataTransfer.dropEffect=d?"none":"copy",!1},_dragOverHandler:function(a){var b=this.elem.parent().get(0);return b&&!d.contains(b,a.currentTarget)?!1:(clearTimeout(this._leaveTimer),this._dragEnterHandler.call(this,a),!1)},_dragLeaveHandler:function(){var a,b=this;return a=function(){b.dndOver=!1,b.elem.removeClass(e+"over "+e+"denied")},clearTimeout(b._leaveTimer),b._leaveTimer=setTimeout(a,100),!1},_dropHandler:function(a){var b,f,g=this,h=g.getRuid(),i=g.elem.parent().get(0);if(i&&!d.contains(i,a.currentTarget))return!1;a=a.originalEvent||a,b=a.dataTransfer;try{f=b.getData("text/html")}catch(j){}return f?void 0:(g._getTansferFiles(b,function(a){g.trigger("drop",d.map(a,function(a){return new c(h,a)}))}),g.dndOver=!1,g.elem.removeClass(e+"over"),!1)},_getTansferFiles:function(b,c){var d,e,f,g,h,i,j,k=[],l=[];for(d=b.items,e=b.files,j=!(!d||!d[0].webkitGetAsEntry),h=0,i=e.length;i>h;h++)f=e[h],g=d&&d[h],j&&g.webkitGetAsEntry().isDirectory?l.push(this._traverseDirectoryTree(g.webkitGetAsEntry(),k)):k.push(f);a.when.apply(a,l).done(function(){k.length&&c(k)})},_traverseDirectoryTree:function(b,c){var d=a.Deferred(),e=this;return b.isFile?b.file(function(a){c.push(a),d.resolve()}):b.isDirectory&&b.createReader().readEntries(function(b){var f,g=b.length,h=[],i=[];for(f=0;g>f;f++)h.push(e._traverseDirectoryTree(b[f],i));a.when.apply(a,h).then(function(){c.push.apply(c,i),d.resolve()},d.reject)}),d.promise()},destroy:function(){var a=this.elem;a&&(a.off("dragenter",this.dragEnterHandler),a.off("dragover",this.dragOverHandler),a.off("dragleave",this.dragLeaveHandler),a.off("drop",this.dropHandler),this.options.disableGlobalDnd&&(d(document).off("dragover",this.dragOverHandler),d(document).off("drop",this.dropHandler)))}})}),b("runtime/html5/filepaste",["base","runtime/html5/runtime","lib/file"],function(a,b,c){return b.register("FilePaste",{init:function(){var b,c,d,e,f=this.options,g=this.elem=f.container,h=".*";if(f.accept){for(b=[],c=0,d=f.accept.length;d>c;c++)e=f.accept[c].mimeTypes,e&&b.push(e);b.length&&(h=b.join(","),h=h.replace(/,/g,"|").replace(/\*/g,".*"))}this.accept=h=new RegExp(h,"i"),this.hander=a.bindFn(this._pasteHander,this),g.on("paste",this.hander)},_pasteHander:function(a){var b,d,e,f,g,h=[],i=this.getRuid();for(a=a.originalEvent||a,b=a.clipboardData.items,f=0,g=b.length;g>f;f++)d=b[f],"file"===d.kind&&(e=d.getAsFile())&&h.push(new c(i,e));h.length&&(a.preventDefault(),a.stopPropagation(),this.trigger("paste",h))},destroy:function(){this.elem.off("paste",this.hander)}})}),b("runtime/html5/filepicker",["base","runtime/html5/runtime"],function(a,b){var c=a.$;return b.register("FilePicker",{init:function(){var a,b,d,e,f=this.getRuntime().getContainer(),g=this,h=g.owner,i=g.options,j=this.label=c(document.createElement("label")),k=this.input=c(document.createElement("input"));if(k.attr("type","file"),k.attr("name",i.name),k.addClass("webuploader-element-invisible"),j.on("click",function(){k.trigger("click")}),j.css({opacity:0,width:"100%",height:"100%",display:"block",cursor:"pointer",background:"#ffffff"}),i.multiple&&k.attr("multiple","multiple"),i.accept&&i.accept.length>0){for(a=[],b=0,d=i.accept.length;d>b;b++)a.push(i.accept[b].mimeTypes);k.attr("accept",a.join(","))}f.append(k),f.append(j),e=function(a){h.trigger(a.type)},k.on("change",function(a){var b,d=arguments.callee;g.files=a.target.files,b=this.cloneNode(!0),b.value=null,this.parentNode.replaceChild(b,this),k.off(),k=c(b).on("change",d).on("mouseenter mouseleave",e),h.trigger("change")}),j.on("mouseenter mouseleave",e)},getFiles:function(){return this.files},destroy:function(){this.input.off(),this.label.off()}})}),b("runtime/html5/transport",["base","runtime/html5/runtime"],function(a,b){var c=a.noop,d=a.$;return b.register("Transport",{init:function(){this._status=0,this._response=null},send:function(){var b,c,e,f=this.owner,g=this.options,h=this._initAjax(),i=f._blob,j=g.server;g.sendAsBinary?(j+=(/\?/.test(j)?"&":"?")+d.param(f._formData),c=i.getSource()):(b=new FormData,d.each(f._formData,function(a,c){b.append(a,c)}),b.append(g.fileVal,i.getSource(),g.filename||f._formData.name||"")),g.withCredentials&&"withCredentials"in h?(h.open(g.method,j,!0),h.withCredentials=!0):h.open(g.method,j),this._setRequestHeader(h,g.headers),c?(h.overrideMimeType&&h.overrideMimeType("application/octet-stream"),a.os.android?(e=new FileReader,e.onload=function(){h.send(this.result),e=e.onload=null},e.readAsArrayBuffer(c)):h.send(c)):h.send(b)},getResponse:function(){return this._response},getResponseAsJson:function(){return this._parseJson(this._response)},getStatus:function(){return this._status},abort:function(){var a=this._xhr;a&&(a.upload.onprogress=c,a.onreadystatechange=c,a.abort(),this._xhr=a=null)},destroy:function(){this.abort()},_initAjax:function(){var a=this,b=new XMLHttpRequest,d=this.options;return!d.withCredentials||"withCredentials"in b||"undefined"==typeof XDomainRequest||(b=new XDomainRequest),b.upload.onprogress=function(b){var c=0;return b.lengthComputable&&(c=b.loaded/b.total),a.trigger("progress",c)},b.onreadystatechange=function(){return 4===b.readyState?(b.upload.onprogress=c,b.onreadystatechange=c,a._xhr=null,a._status=b.status,b.status>=200&&b.status<300?(a._response=b.responseText,a.trigger("load")):b.status>=500&&b.status<600?(a._response=b.responseText,a.trigger("error","server")):a.trigger("error",a._status?"http":"abort")):void 0},a._xhr=b,b},_setRequestHeader:function(a,b){d.each(b,function(b,c){a.setRequestHeader(b,c)})},_parseJson:function(a){var b;try{b=JSON.parse(a)}catch(c){b={}}return b}})}),b("runtime/flash/runtime",["base","runtime/runtime","runtime/compbase"],function(b,c,d){function e(){var a;try{a=navigator.plugins["Shockwave Flash"],a=a.description}catch(b){try{a=new ActiveXObject("ShockwaveFlash.ShockwaveFlash").GetVariable("$version")}catch(c){a="0.0"}}return a=a.match(/\d+/g),parseFloat(a[0]+"."+a[1],10)}function f(){function d(a,b){var c,d,e=a.type||a;c=e.split("::"),d=c[0],e=c[1],"Ready"===e&&d===j.uid?j.trigger("ready"):f[d]&&f[d].trigger(e.toLowerCase(),a,b)}var e={},f={},g=this.destroy,j=this,k=b.guid("webuploader_");c.apply(j,arguments),j.type=h,j.exec=function(a,c){var d,g=this,h=g.uid,k=b.slice(arguments,2);return f[h]=g,i[a]&&(e[h]||(e[h]=new i[a](g,j)),d=e[h],d[c])?d[c].apply(d,k):j.flashExec.apply(g,arguments)},a[k]=function(){var a=arguments;setTimeout(function(){d.apply(null,a)},1)},this.jsreciver=k,this.destroy=function(){return g&&g.apply(this,arguments)},this.flashExec=function(a,c){var d=j.getFlash(),e=b.slice(arguments,2);return d.exec(this.uid,a,c,e)}}var g=b.$,h="flash",i={};return b.inherits(c,{constructor:f,init:function(){var a,c=this.getContainer(),d=this.options;c.css({position:"absolute",top:"-8px",left:"-8px",width:"9px",height:"9px",overflow:"hidden"}),a='',c.html(a)},getFlash:function(){return this._flash?this._flash:(this._flash=g("#"+this.uid).get(0),this._flash)}}),f.register=function(a,c){return c=i[a]=b.inherits(d,g.extend({flashExec:function(){var a=this.owner,b=this.getRuntime();return b.flashExec.apply(a,arguments)}},c))},e()>=11.4&&c.addRuntime(h,f),f}),b("runtime/flash/filepicker",["base","runtime/flash/runtime"],function(a,b){var c=a.$;return b.register("FilePicker",{init:function(a){var b,d,e=c.extend({},a);for(b=e.accept&&e.accept.length,d=0;b>d;d++)e.accept[d].title||(e.accept[d].title="Files");delete e.button,delete e.id,delete e.container,this.flashExec("FilePicker","init",e)},destroy:function(){this.flashExec("FilePicker","destroy")}})}),b("runtime/flash/transport",["base","runtime/flash/runtime","runtime/client"],function(b,c,d){var e=b.$;return c.register("Transport",{init:function(){this._status=0,this._response=null,this._responseJson=null},send:function(){var a,b=this.owner,c=this.options,d=this._initAjax(),f=b._blob,g=c.server;d.connectRuntime(f.ruid),c.sendAsBinary?(g+=(/\?/.test(g)?"&":"?")+e.param(b._formData),a=f.uid):(e.each(b._formData,function(a,b){d.exec("append",a,b)}),d.exec("appendBlob",c.fileVal,f.uid,c.filename||b._formData.name||"")),this._setRequestHeader(d,c.headers),d.exec("send",{method:c.method,url:g,forceURLStream:c.forceURLStream,mimeType:"application/octet-stream"},a)},getStatus:function(){return this._status},getResponse:function(){return this._response||""},getResponseAsJson:function(){return this._responseJson},abort:function(){var a=this._xhr;a&&(a.exec("abort"),a.destroy(),this._xhr=a=null)},destroy:function(){this.abort()},_initAjax:function(){var b=this,c=new d("XMLHttpRequest");return c.on("uploadprogress progress",function(a){var c=a.loaded/a.total;return c=Math.min(1,Math.max(0,c)),b.trigger("progress",c)}),c.on("load",function(){var d,e=c.exec("getStatus"),f=!1,g="";return c.off(),b._xhr=null,e>=200&&300>e?f=!0:e>=500&&600>e?(f=!0,g="server"):g="http",f&&(b._response=c.exec("getResponse"),b._response=decodeURIComponent(b._response),d=a.JSON&&a.JSON.parse||function(a){try{return new Function("return "+a).call()}catch(b){return{}}},b._responseJson=b._response?d(b._response):{}),c.destroy(),c=null,g?b.trigger("error",g):b.trigger("load")}),c.on("error",function(){c.off(),b._xhr=null,b.trigger("error","http")}),b._xhr=c,c},_setRequestHeader:function(a,b){e.each(b,function(b,c){a.exec("setRequestHeader",b,c)})}})}),b("runtime/flash/blob",["runtime/flash/runtime","lib/blob"],function(a,b){return a.register("Blob",{slice:function(a,c){var d=this.flashExec("Blob","slice",a,c);return new b(d.uid,d)}})}),b("preset/withoutimage",["base","widgets/filednd","widgets/filepaste","widgets/filepicker","widgets/queue","widgets/runtime","widgets/upload","widgets/validator","runtime/html5/blob","runtime/html5/dnd","runtime/html5/filepaste","runtime/html5/filepicker","runtime/html5/transport","runtime/flash/filepicker","runtime/flash/transport","runtime/flash/blob"],function(a){return a}),b("webuploader",["preset/withoutimage"],function(a){return a}),c("webuploader")}); \ No newline at end of file diff --git a/static/plugins/webuploader/webuploader.nolog.js b/static/plugins/webuploader/webuploader.nolog.js new file mode 100644 index 00000000..294e9e03 --- /dev/null +++ b/static/plugins/webuploader/webuploader.nolog.js @@ -0,0 +1,8012 @@ +/*! WebUploader 0.1.5 */ + + +/** + * @fileOverview 让内部各个部件的代码可以用[amd](https://github.com/amdjs/amdjs-api/wiki/AMD)模块定义方式组织起来。 + * + * AMD API 内部的简单不完全实现,请忽略。只有当WebUploader被合并成一个文件的时候才会引入。 + */ +(function( root, factory ) { + var modules = {}, + + // 内部require, 简单不完全实现。 + // https://github.com/amdjs/amdjs-api/wiki/require + _require = function( deps, callback ) { + var args, len, i; + + // 如果deps不是数组,则直接返回指定module + if ( typeof deps === 'string' ) { + return getModule( deps ); + } else { + args = []; + for( len = deps.length, i = 0; i < len; i++ ) { + args.push( getModule( deps[ i ] ) ); + } + + return callback.apply( null, args ); + } + }, + + // 内部define,暂时不支持不指定id. + _define = function( id, deps, factory ) { + if ( arguments.length === 2 ) { + factory = deps; + deps = null; + } + + _require( deps || [], function() { + setModule( id, factory, arguments ); + }); + }, + + // 设置module, 兼容CommonJs写法。 + setModule = function( id, factory, args ) { + var module = { + exports: factory + }, + returned; + + if ( typeof factory === 'function' ) { + args.length || (args = [ _require, module.exports, module ]); + returned = factory.apply( null, args ); + returned !== undefined && (module.exports = returned); + } + + modules[ id ] = module.exports; + }, + + // 根据id获取module + getModule = function( id ) { + var module = modules[ id ] || root[ id ]; + + if ( !module ) { + throw new Error( '`' + id + '` is undefined' ); + } + + return module; + }, + + // 将所有modules,将路径ids装换成对象。 + exportsTo = function( obj ) { + var key, host, parts, part, last, ucFirst; + + // make the first character upper case. + ucFirst = function( str ) { + return str && (str.charAt( 0 ).toUpperCase() + str.substr( 1 )); + }; + + for ( key in modules ) { + host = obj; + + if ( !modules.hasOwnProperty( key ) ) { + continue; + } + + parts = key.split('/'); + last = ucFirst( parts.pop() ); + + while( (part = ucFirst( parts.shift() )) ) { + host[ part ] = host[ part ] || {}; + host = host[ part ]; + } + + host[ last ] = modules[ key ]; + } + + return obj; + }, + + makeExport = function( dollar ) { + root.__dollar = dollar; + + // exports every module. + return exportsTo( factory( root, _define, _require ) ); + }, + + origin; + + if ( typeof module === 'object' && typeof module.exports === 'object' ) { + + // For CommonJS and CommonJS-like environments where a proper window is present, + module.exports = makeExport(); + } else if ( typeof define === 'function' && define.amd ) { + + // Allow using this built library as an AMD module + // in another project. That other project will only + // see this AMD call, not the internal modules in + // the closure below. + define([ 'jquery' ], makeExport ); + } else { + + // Browser globals case. Just assign the + // result to a property on the global. + origin = root.WebUploader; + root.WebUploader = makeExport(); + root.WebUploader.noConflict = function() { + root.WebUploader = origin; + }; + } +})( window, function( window, define, require ) { + + + /** + * @fileOverview jQuery or Zepto + */ + define('dollar-third',[],function() { + var $ = window.__dollar || window.jQuery || window.Zepto; + + if ( !$ ) { + throw new Error('jQuery or Zepto not found!'); + } + + return $; + }); + /** + * @fileOverview Dom 操作相关 + */ + define('dollar',[ + 'dollar-third' + ], function( _ ) { + return _; + }); + /** + * @fileOverview 使用jQuery的Promise + */ + define('promise-third',[ + 'dollar' + ], function( $ ) { + return { + Deferred: $.Deferred, + when: $.when, + + isPromise: function( anything ) { + return anything && typeof anything.then === 'function'; + } + }; + }); + /** + * @fileOverview Promise/A+ + */ + define('promise',[ + 'promise-third' + ], function( _ ) { + return _; + }); + /** + * @fileOverview 基础类方法。 + */ + + /** + * Web Uploader内部类的详细说明,以下提及的功能类,都可以在`WebUploader`这个变量中访问到。 + * + * As you know, Web Uploader的每个文件都是用过[AMD](https://github.com/amdjs/amdjs-api/wiki/AMD)规范中的`define`组织起来的, 每个Module都会有个module id. + * 默认module id为该文件的路径,而此路径将会转化成名字空间存放在WebUploader中。如: + * + * * module `base`:WebUploader.Base + * * module `file`: WebUploader.File + * * module `lib/dnd`: WebUploader.Lib.Dnd + * * module `runtime/html5/dnd`: WebUploader.Runtime.Html5.Dnd + * + * + * 以下文档中对类的使用可能省略掉了`WebUploader`前缀。 + * @module WebUploader + * @title WebUploader API文档 + */ + define('base',[ + 'dollar', + 'promise' + ], function( $, promise ) { + + var noop = function() {}, + call = Function.call; + + // http://jsperf.com/uncurrythis + // 反科里化 + function uncurryThis( fn ) { + return function() { + return call.apply( fn, arguments ); + }; + } + + function bindFn( fn, context ) { + return function() { + return fn.apply( context, arguments ); + }; + } + + function createObject( proto ) { + var f; + + if ( Object.create ) { + return Object.create( proto ); + } else { + f = function() {}; + f.prototype = proto; + return new f(); + } + } + + + /** + * 基础类,提供一些简单常用的方法。 + * @class Base + */ + return { + + /** + * @property {String} version 当前版本号。 + */ + version: '0.1.5', + + /** + * @property {jQuery|Zepto} $ 引用依赖的jQuery或者Zepto对象。 + */ + $: $, + + Deferred: promise.Deferred, + + isPromise: promise.isPromise, + + when: promise.when, + + /** + * @description 简单的浏览器检查结果。 + * + * * `webkit` webkit版本号,如果浏览器为非webkit内核,此属性为`undefined`。 + * * `chrome` chrome浏览器版本号,如果浏览器为chrome,此属性为`undefined`。 + * * `ie` ie浏览器版本号,如果浏览器为非ie,此属性为`undefined`。**暂不支持ie10+** + * * `firefox` firefox浏览器版本号,如果浏览器为非firefox,此属性为`undefined`。 + * * `safari` safari浏览器版本号,如果浏览器为非safari,此属性为`undefined`。 + * * `opera` opera浏览器版本号,如果浏览器为非opera,此属性为`undefined`。 + * + * @property {Object} [browser] + */ + browser: (function( ua ) { + var ret = {}, + webkit = ua.match( /WebKit\/([\d.]+)/ ), + chrome = ua.match( /Chrome\/([\d.]+)/ ) || + ua.match( /CriOS\/([\d.]+)/ ), + + ie = ua.match( /MSIE\s([\d\.]+)/ ) || + ua.match( /(?:trident)(?:.*rv:([\w.]+))?/i ), + firefox = ua.match( /Firefox\/([\d.]+)/ ), + safari = ua.match( /Safari\/([\d.]+)/ ), + opera = ua.match( /OPR\/([\d.]+)/ ); + + webkit && (ret.webkit = parseFloat( webkit[ 1 ] )); + chrome && (ret.chrome = parseFloat( chrome[ 1 ] )); + ie && (ret.ie = parseFloat( ie[ 1 ] )); + firefox && (ret.firefox = parseFloat( firefox[ 1 ] )); + safari && (ret.safari = parseFloat( safari[ 1 ] )); + opera && (ret.opera = parseFloat( opera[ 1 ] )); + + return ret; + })( navigator.userAgent ), + + /** + * @description 操作系统检查结果。 + * + * * `android` 如果在android浏览器环境下,此值为对应的android版本号,否则为`undefined`。 + * * `ios` 如果在ios浏览器环境下,此值为对应的ios版本号,否则为`undefined`。 + * @property {Object} [os] + */ + os: (function( ua ) { + var ret = {}, + + // osx = !!ua.match( /\(Macintosh\; Intel / ), + android = ua.match( /(?:Android);?[\s\/]+([\d.]+)?/ ), + ios = ua.match( /(?:iPad|iPod|iPhone).*OS\s([\d_]+)/ ); + + // osx && (ret.osx = true); + android && (ret.android = parseFloat( android[ 1 ] )); + ios && (ret.ios = parseFloat( ios[ 1 ].replace( /_/g, '.' ) )); + + return ret; + })( navigator.userAgent ), + + /** + * 实现类与类之间的继承。 + * @method inherits + * @grammar Base.inherits( super ) => child + * @grammar Base.inherits( super, protos ) => child + * @grammar Base.inherits( super, protos, statics ) => child + * @param {Class} super 父类 + * @param {Object | Function} [protos] 子类或者对象。如果对象中包含constructor,子类将是用此属性值。 + * @param {Function} [protos.constructor] 子类构造器,不指定的话将创建个临时的直接执行父类构造器的方法。 + * @param {Object} [statics] 静态属性或方法。 + * @return {Class} 返回子类。 + * @example + * function Person() { + * console.log( 'Super' ); + * } + * Person.prototype.hello = function() { + * console.log( 'hello' ); + * }; + * + * var Manager = Base.inherits( Person, { + * world: function() { + * console.log( 'World' ); + * } + * }); + * + * // 因为没有指定构造器,父类的构造器将会执行。 + * var instance = new Manager(); // => Super + * + * // 继承子父类的方法 + * instance.hello(); // => hello + * instance.world(); // => World + * + * // 子类的__super__属性指向父类 + * console.log( Manager.__super__ === Person ); // => true + */ + inherits: function( Super, protos, staticProtos ) { + var child; + + if ( typeof protos === 'function' ) { + child = protos; + protos = null; + } else if ( protos && protos.hasOwnProperty('constructor') ) { + child = protos.constructor; + } else { + child = function() { + return Super.apply( this, arguments ); + }; + } + + // 复制静态方法 + $.extend( true, child, Super, staticProtos || {} ); + + /* jshint camelcase: false */ + + // 让子类的__super__属性指向父类。 + child.__super__ = Super.prototype; + + // 构建原型,添加原型方法或属性。 + // 暂时用Object.create实现。 + child.prototype = createObject( Super.prototype ); + protos && $.extend( true, child.prototype, protos ); + + return child; + }, + + /** + * 一个不做任何事情的方法。可以用来赋值给默认的callback. + * @method noop + */ + noop: noop, + + /** + * 返回一个新的方法,此方法将已指定的`context`来执行。 + * @grammar Base.bindFn( fn, context ) => Function + * @method bindFn + * @example + * var doSomething = function() { + * console.log( this.name ); + * }, + * obj = { + * name: 'Object Name' + * }, + * aliasFn = Base.bind( doSomething, obj ); + * + * aliasFn(); // => Object Name + * + */ + bindFn: bindFn, + + /** + * 引用Console.log如果存在的话,否则引用一个[空函数noop](#WebUploader:Base.noop)。 + * @grammar Base.log( args... ) => undefined + * @method log + */ + log: (function() { + if ( window.console ) { + return bindFn( console.log, console ); + } + return noop; + })(), + + nextTick: (function() { + + return function( cb ) { + setTimeout( cb, 1 ); + }; + + // @bug 当浏览器不在当前窗口时就停了。 + // var next = window.requestAnimationFrame || + // window.webkitRequestAnimationFrame || + // window.mozRequestAnimationFrame || + // function( cb ) { + // window.setTimeout( cb, 1000 / 60 ); + // }; + + // // fix: Uncaught TypeError: Illegal invocation + // return bindFn( next, window ); + })(), + + /** + * 被[uncurrythis](http://www.2ality.com/2011/11/uncurrying-this.html)的数组slice方法。 + * 将用来将非数组对象转化成数组对象。 + * @grammar Base.slice( target, start[, end] ) => Array + * @method slice + * @example + * function doSomthing() { + * var args = Base.slice( arguments, 1 ); + * console.log( args ); + * } + * + * doSomthing( 'ignored', 'arg2', 'arg3' ); // => Array ["arg2", "arg3"] + */ + slice: uncurryThis( [].slice ), + + /** + * 生成唯一的ID + * @method guid + * @grammar Base.guid() => String + * @grammar Base.guid( prefx ) => String + */ + guid: (function() { + var counter = 0; + + return function( prefix ) { + var guid = (+new Date()).toString( 32 ), + i = 0; + + for ( ; i < 5; i++ ) { + guid += Math.floor( Math.random() * 65535 ).toString( 32 ); + } + + return (prefix || 'wu_') + guid + (counter++).toString( 32 ); + }; + })(), + + /** + * 格式化文件大小, 输出成带单位的字符串 + * @method formatSize + * @grammar Base.formatSize( size ) => String + * @grammar Base.formatSize( size, pointLength ) => String + * @grammar Base.formatSize( size, pointLength, units ) => String + * @param {Number} size 文件大小 + * @param {Number} [pointLength=2] 精确到的小数点数。 + * @param {Array} [units=[ 'B', 'K', 'M', 'G', 'TB' ]] 单位数组。从字节,到千字节,一直往上指定。如果单位数组里面只指定了到了K(千字节),同时文件大小大于M, 此方法的输出将还是显示成多少K. + * @example + * console.log( Base.formatSize( 100 ) ); // => 100B + * console.log( Base.formatSize( 1024 ) ); // => 1.00K + * console.log( Base.formatSize( 1024, 0 ) ); // => 1K + * console.log( Base.formatSize( 1024 * 1024 ) ); // => 1.00M + * console.log( Base.formatSize( 1024 * 1024 * 1024 ) ); // => 1.00G + * console.log( Base.formatSize( 1024 * 1024 * 1024, 0, ['B', 'KB', 'MB'] ) ); // => 1024MB + */ + formatSize: function( size, pointLength, units ) { + var unit; + + units = units || [ 'B', 'K', 'M', 'G', 'TB' ]; + + while ( (unit = units.shift()) && size > 1024 ) { + size = size / 1024; + } + + return (unit === 'B' ? size : size.toFixed( pointLength || 2 )) + + unit; + } + }; + }); + /** + * 事件处理类,可以独立使用,也可以扩展给对象使用。 + * @fileOverview Mediator + */ + define('mediator',[ + 'base' + ], function( Base ) { + var $ = Base.$, + slice = [].slice, + separator = /\s+/, + protos; + + // 根据条件过滤出事件handlers. + function findHandlers( arr, name, callback, context ) { + return $.grep( arr, function( handler ) { + return handler && + (!name || handler.e === name) && + (!callback || handler.cb === callback || + handler.cb._cb === callback) && + (!context || handler.ctx === context); + }); + } + + function eachEvent( events, callback, iterator ) { + // 不支持对象,只支持多个event用空格隔开 + $.each( (events || '').split( separator ), function( _, key ) { + iterator( key, callback ); + }); + } + + function triggerHanders( events, args ) { + var stoped = false, + i = -1, + len = events.length, + handler; + + while ( ++i < len ) { + handler = events[ i ]; + + if ( handler.cb.apply( handler.ctx2, args ) === false ) { + stoped = true; + break; + } + } + + return !stoped; + } + + protos = { + + /** + * 绑定事件。 + * + * `callback`方法在执行时,arguments将会来源于trigger的时候携带的参数。如 + * ```javascript + * var obj = {}; + * + * // 使得obj有事件行为 + * Mediator.installTo( obj ); + * + * obj.on( 'testa', function( arg1, arg2 ) { + * console.log( arg1, arg2 ); // => 'arg1', 'arg2' + * }); + * + * obj.trigger( 'testa', 'arg1', 'arg2' ); + * ``` + * + * 如果`callback`中,某一个方法`return false`了,则后续的其他`callback`都不会被执行到。 + * 切会影响到`trigger`方法的返回值,为`false`。 + * + * `on`还可以用来添加一个特殊事件`all`, 这样所有的事件触发都会响应到。同时此类`callback`中的arguments有一个不同处, + * 就是第一个参数为`type`,记录当前是什么事件在触发。此类`callback`的优先级比脚低,会再正常`callback`执行完后触发。 + * ```javascript + * obj.on( 'all', function( type, arg1, arg2 ) { + * console.log( type, arg1, arg2 ); // => 'testa', 'arg1', 'arg2' + * }); + * ``` + * + * @method on + * @grammar on( name, callback[, context] ) => self + * @param {String} name 事件名,支持多个事件用空格隔开 + * @param {Function} callback 事件处理器 + * @param {Object} [context] 事件处理器的上下文。 + * @return {self} 返回自身,方便链式 + * @chainable + * @class Mediator + */ + on: function( name, callback, context ) { + var me = this, + set; + + if ( !callback ) { + return this; + } + + set = this._events || (this._events = []); + + eachEvent( name, callback, function( name, callback ) { + var handler = { e: name }; + + handler.cb = callback; + handler.ctx = context; + handler.ctx2 = context || me; + handler.id = set.length; + + set.push( handler ); + }); + + return this; + }, + + /** + * 绑定事件,且当handler执行完后,自动解除绑定。 + * @method once + * @grammar once( name, callback[, context] ) => self + * @param {String} name 事件名 + * @param {Function} callback 事件处理器 + * @param {Object} [context] 事件处理器的上下文。 + * @return {self} 返回自身,方便链式 + * @chainable + */ + once: function( name, callback, context ) { + var me = this; + + if ( !callback ) { + return me; + } + + eachEvent( name, callback, function( name, callback ) { + var once = function() { + me.off( name, once ); + return callback.apply( context || me, arguments ); + }; + + once._cb = callback; + me.on( name, once, context ); + }); + + return me; + }, + + /** + * 解除事件绑定 + * @method off + * @grammar off( [name[, callback[, context] ] ] ) => self + * @param {String} [name] 事件名 + * @param {Function} [callback] 事件处理器 + * @param {Object} [context] 事件处理器的上下文。 + * @return {self} 返回自身,方便链式 + * @chainable + */ + off: function( name, cb, ctx ) { + var events = this._events; + + if ( !events ) { + return this; + } + + if ( !name && !cb && !ctx ) { + this._events = []; + return this; + } + + eachEvent( name, cb, function( name, cb ) { + $.each( findHandlers( events, name, cb, ctx ), function() { + delete events[ this.id ]; + }); + }); + + return this; + }, + + /** + * 触发事件 + * @method trigger + * @grammar trigger( name[, args...] ) => self + * @param {String} type 事件名 + * @param {*} [...] 任意参数 + * @return {Boolean} 如果handler中return false了,则返回false, 否则返回true + */ + trigger: function( type ) { + var args, events, allEvents; + + if ( !this._events || !type ) { + return this; + } + + args = slice.call( arguments, 1 ); + events = findHandlers( this._events, type ); + allEvents = findHandlers( this._events, 'all' ); + + return triggerHanders( events, args ) && + triggerHanders( allEvents, arguments ); + } + }; + + /** + * 中介者,它本身是个单例,但可以通过[installTo](#WebUploader:Mediator:installTo)方法,使任何对象具备事件行为。 + * 主要目的是负责模块与模块之间的合作,降低耦合度。 + * + * @class Mediator + */ + return $.extend({ + + /** + * 可以通过这个接口,使任何对象具备事件功能。 + * @method installTo + * @param {Object} obj 需要具备事件行为的对象。 + * @return {Object} 返回obj. + */ + installTo: function( obj ) { + return $.extend( obj, protos ); + } + + }, protos ); + }); + /** + * @fileOverview Uploader上传类 + */ + define('uploader',[ + 'base', + 'mediator' + ], function( Base, Mediator ) { + + var $ = Base.$; + + /** + * 上传入口类。 + * @class Uploader + * @constructor + * @grammar new Uploader( opts ) => Uploader + * @example + * var uploader = WebUploader.Uploader({ + * swf: 'path_of_swf/Uploader.swf', + * + * // 开起分片上传。 + * chunked: true + * }); + */ + function Uploader( opts ) { + this.options = $.extend( true, {}, Uploader.options, opts ); + this._init( this.options ); + } + + // default Options + // widgets中有相应扩展 + Uploader.options = {}; + Mediator.installTo( Uploader.prototype ); + + // 批量添加纯命令式方法。 + $.each({ + upload: 'start-upload', + stop: 'stop-upload', + getFile: 'get-file', + getFiles: 'get-files', + addFile: 'add-file', + addFiles: 'add-file', + sort: 'sort-files', + removeFile: 'remove-file', + cancelFile: 'cancel-file', + skipFile: 'skip-file', + retry: 'retry', + isInProgress: 'is-in-progress', + makeThumb: 'make-thumb', + md5File: 'md5-file', + getDimension: 'get-dimension', + addButton: 'add-btn', + predictRuntimeType: 'predict-runtime-type', + refresh: 'refresh', + disable: 'disable', + enable: 'enable', + reset: 'reset' + }, function( fn, command ) { + Uploader.prototype[ fn ] = function() { + return this.request( command, arguments ); + }; + }); + + $.extend( Uploader.prototype, { + state: 'pending', + + _init: function( opts ) { + var me = this; + + me.request( 'init', opts, function() { + me.state = 'ready'; + me.trigger('ready'); + }); + }, + + /** + * 获取或者设置Uploader配置项。 + * @method option + * @grammar option( key ) => * + * @grammar option( key, val ) => self + * @example + * + * // 初始状态图片上传前不会压缩 + * var uploader = new WebUploader.Uploader({ + * compress: null; + * }); + * + * // 修改后图片上传前,尝试将图片压缩到1600 * 1600 + * uploader.option( 'compress', { + * width: 1600, + * height: 1600 + * }); + */ + option: function( key, val ) { + var opts = this.options; + + // setter + if ( arguments.length > 1 ) { + + if ( $.isPlainObject( val ) && + $.isPlainObject( opts[ key ] ) ) { + $.extend( opts[ key ], val ); + } else { + opts[ key ] = val; + } + + } else { // getter + return key ? opts[ key ] : opts; + } + }, + + /** + * 获取文件统计信息。返回一个包含一下信息的对象。 + * * `successNum` 上传成功的文件数 + * * `progressNum` 上传中的文件数 + * * `cancelNum` 被删除的文件数 + * * `invalidNum` 无效的文件数 + * * `uploadFailNum` 上传失败的文件数 + * * `queueNum` 还在队列中的文件数 + * * `interruptNum` 被暂停的文件数 + * @method getStats + * @grammar getStats() => Object + */ + getStats: function() { + // return this._mgr.getStats.apply( this._mgr, arguments ); + var stats = this.request('get-stats'); + + return stats ? { + successNum: stats.numOfSuccess, + progressNum: stats.numOfProgress, + + // who care? + // queueFailNum: 0, + cancelNum: stats.numOfCancel, + invalidNum: stats.numOfInvalid, + uploadFailNum: stats.numOfUploadFailed, + queueNum: stats.numOfQueue, + interruptNum: stats.numofInterrupt + } : {}; + }, + + // 需要重写此方法来来支持opts.onEvent和instance.onEvent的处理器 + trigger: function( type/*, args...*/ ) { + var args = [].slice.call( arguments, 1 ), + opts = this.options, + name = 'on' + type.substring( 0, 1 ).toUpperCase() + + type.substring( 1 ); + + if ( + // 调用通过on方法注册的handler. + Mediator.trigger.apply( this, arguments ) === false || + + // 调用opts.onEvent + $.isFunction( opts[ name ] ) && + opts[ name ].apply( this, args ) === false || + + // 调用this.onEvent + $.isFunction( this[ name ] ) && + this[ name ].apply( this, args ) === false || + + // 广播所有uploader的事件。 + Mediator.trigger.apply( Mediator, + [ this, type ].concat( args ) ) === false ) { + + return false; + } + + return true; + }, + + /** + * 销毁 webuploader 实例 + * @method destroy + * @grammar destroy() => undefined + */ + destroy: function() { + this.request( 'destroy', arguments ); + this.off(); + }, + + // widgets/widget.js将补充此方法的详细文档。 + request: Base.noop + }); + + /** + * 创建Uploader实例,等同于new Uploader( opts ); + * @method create + * @class Base + * @static + * @grammar Base.create( opts ) => Uploader + */ + Base.create = Uploader.create = function( opts ) { + return new Uploader( opts ); + }; + + // 暴露Uploader,可以通过它来扩展业务逻辑。 + Base.Uploader = Uploader; + + return Uploader; + }); + /** + * @fileOverview Runtime管理器,负责Runtime的选择, 连接 + */ + define('runtime/runtime',[ + 'base', + 'mediator' + ], function( Base, Mediator ) { + + var $ = Base.$, + factories = {}, + + // 获取对象的第一个key + getFirstKey = function( obj ) { + for ( var key in obj ) { + if ( obj.hasOwnProperty( key ) ) { + return key; + } + } + return null; + }; + + // 接口类。 + function Runtime( options ) { + this.options = $.extend({ + container: document.body + }, options ); + this.uid = Base.guid('rt_'); + } + + $.extend( Runtime.prototype, { + + getContainer: function() { + var opts = this.options, + parent, container; + + if ( this._container ) { + return this._container; + } + + parent = $( opts.container || document.body ); + container = $( document.createElement('div') ); + + container.attr( 'id', 'rt_' + this.uid ); + container.css({ + position: 'absolute', + top: '0px', + left: '0px', + width: '1px', + height: '1px', + overflow: 'hidden' + }); + + parent.append( container ); + parent.addClass('webuploader-container'); + this._container = container; + this._parent = parent; + return container; + }, + + init: Base.noop, + exec: Base.noop, + + destroy: function() { + this._container && this._container.remove(); + this._parent && this._parent.removeClass('webuploader-container'); + this.off(); + } + }); + + Runtime.orders = 'html5,flash'; + + + /** + * 添加Runtime实现。 + * @param {String} type 类型 + * @param {Runtime} factory 具体Runtime实现。 + */ + Runtime.addRuntime = function( type, factory ) { + factories[ type ] = factory; + }; + + Runtime.hasRuntime = function( type ) { + return !!(type ? factories[ type ] : getFirstKey( factories )); + }; + + Runtime.create = function( opts, orders ) { + var type, runtime; + + orders = orders || Runtime.orders; + $.each( orders.split( /\s*,\s*/g ), function() { + if ( factories[ this ] ) { + type = this; + return false; + } + }); + + type = type || getFirstKey( factories ); + + if ( !type ) { + throw new Error('Runtime Error'); + } + + runtime = new factories[ type ]( opts ); + return runtime; + }; + + Mediator.installTo( Runtime.prototype ); + return Runtime; + }); + + /** + * @fileOverview Runtime管理器,负责Runtime的选择, 连接 + */ + define('runtime/client',[ + 'base', + 'mediator', + 'runtime/runtime' + ], function( Base, Mediator, Runtime ) { + + var cache; + + cache = (function() { + var obj = {}; + + return { + add: function( runtime ) { + obj[ runtime.uid ] = runtime; + }, + + get: function( ruid, standalone ) { + var i; + + if ( ruid ) { + return obj[ ruid ]; + } + + for ( i in obj ) { + // 有些类型不能重用,比如filepicker. + if ( standalone && obj[ i ].__standalone ) { + continue; + } + + return obj[ i ]; + } + + return null; + }, + + remove: function( runtime ) { + delete obj[ runtime.uid ]; + } + }; + })(); + + function RuntimeClient( component, standalone ) { + var deferred = Base.Deferred(), + runtime; + + this.uid = Base.guid('client_'); + + // 允许runtime没有初始化之前,注册一些方法在初始化后执行。 + this.runtimeReady = function( cb ) { + return deferred.done( cb ); + }; + + this.connectRuntime = function( opts, cb ) { + + // already connected. + if ( runtime ) { + throw new Error('already connected!'); + } + + deferred.done( cb ); + + if ( typeof opts === 'string' && cache.get( opts ) ) { + runtime = cache.get( opts ); + } + + // 像filePicker只能独立存在,不能公用。 + runtime = runtime || cache.get( null, standalone ); + + // 需要创建 + if ( !runtime ) { + runtime = Runtime.create( opts, opts.runtimeOrder ); + runtime.__promise = deferred.promise(); + runtime.once( 'ready', deferred.resolve ); + runtime.init(); + cache.add( runtime ); + runtime.__client = 1; + } else { + // 来自cache + Base.$.extend( runtime.options, opts ); + runtime.__promise.then( deferred.resolve ); + runtime.__client++; + } + + standalone && (runtime.__standalone = standalone); + return runtime; + }; + + this.getRuntime = function() { + return runtime; + }; + + this.disconnectRuntime = function() { + if ( !runtime ) { + return; + } + + runtime.__client--; + + if ( runtime.__client <= 0 ) { + cache.remove( runtime ); + delete runtime.__promise; + runtime.destroy(); + } + + runtime = null; + }; + + this.exec = function() { + if ( !runtime ) { + return; + } + + var args = Base.slice( arguments ); + component && args.unshift( component ); + + return runtime.exec.apply( this, args ); + }; + + this.getRuid = function() { + return runtime && runtime.uid; + }; + + this.destroy = (function( destroy ) { + return function() { + destroy && destroy.apply( this, arguments ); + this.trigger('destroy'); + this.off(); + this.exec('destroy'); + this.disconnectRuntime(); + }; + })( this.destroy ); + } + + Mediator.installTo( RuntimeClient.prototype ); + return RuntimeClient; + }); + /** + * @fileOverview 错误信息 + */ + define('lib/dnd',[ + 'base', + 'mediator', + 'runtime/client' + ], function( Base, Mediator, RuntimeClent ) { + + var $ = Base.$; + + function DragAndDrop( opts ) { + opts = this.options = $.extend({}, DragAndDrop.options, opts ); + + opts.container = $( opts.container ); + + if ( !opts.container.length ) { + return; + } + + RuntimeClent.call( this, 'DragAndDrop' ); + } + + DragAndDrop.options = { + accept: null, + disableGlobalDnd: false + }; + + Base.inherits( RuntimeClent, { + constructor: DragAndDrop, + + init: function() { + var me = this; + + me.connectRuntime( me.options, function() { + me.exec('init'); + me.trigger('ready'); + }); + } + }); + + Mediator.installTo( DragAndDrop.prototype ); + + return DragAndDrop; + }); + /** + * @fileOverview 组件基类。 + */ + define('widgets/widget',[ + 'base', + 'uploader' + ], function( Base, Uploader ) { + + var $ = Base.$, + _init = Uploader.prototype._init, + _destroy = Uploader.prototype.destroy, + IGNORE = {}, + widgetClass = []; + + function isArrayLike( obj ) { + if ( !obj ) { + return false; + } + + var length = obj.length, + type = $.type( obj ); + + if ( obj.nodeType === 1 && length ) { + return true; + } + + return type === 'array' || type !== 'function' && type !== 'string' && + (length === 0 || typeof length === 'number' && length > 0 && + (length - 1) in obj); + } + + function Widget( uploader ) { + this.owner = uploader; + this.options = uploader.options; + } + + $.extend( Widget.prototype, { + + init: Base.noop, + + // 类Backbone的事件监听声明,监听uploader实例上的事件 + // widget直接无法监听事件,事件只能通过uploader来传递 + invoke: function( apiName, args ) { + + /* + { + 'make-thumb': 'makeThumb' + } + */ + var map = this.responseMap; + + // 如果无API响应声明则忽略 + if ( !map || !(apiName in map) || !(map[ apiName ] in this) || + !$.isFunction( this[ map[ apiName ] ] ) ) { + + return IGNORE; + } + + return this[ map[ apiName ] ].apply( this, args ); + + }, + + /** + * 发送命令。当传入`callback`或者`handler`中返回`promise`时。返回一个当所有`handler`中的promise都完成后完成的新`promise`。 + * @method request + * @grammar request( command, args ) => * | Promise + * @grammar request( command, args, callback ) => Promise + * @for Uploader + */ + request: function() { + return this.owner.request.apply( this.owner, arguments ); + } + }); + + // 扩展Uploader. + $.extend( Uploader.prototype, { + + /** + * @property {String | Array} [disableWidgets=undefined] + * @namespace options + * @for Uploader + * @description 默认所有 Uploader.register 了的 widget 都会被加载,如果禁用某一部分,请通过此 option 指定黑名单。 + */ + + // 覆写_init用来初始化widgets + _init: function() { + var me = this, + widgets = me._widgets = [], + deactives = me.options.disableWidgets || ''; + + $.each( widgetClass, function( _, klass ) { + (!deactives || !~deactives.indexOf( klass._name )) && + widgets.push( new klass( me ) ); + }); + + return _init.apply( me, arguments ); + }, + + request: function( apiName, args, callback ) { + var i = 0, + widgets = this._widgets, + len = widgets && widgets.length, + rlts = [], + dfds = [], + widget, rlt, promise, key; + + args = isArrayLike( args ) ? args : [ args ]; + + for ( ; i < len; i++ ) { + widget = widgets[ i ]; + rlt = widget.invoke( apiName, args ); + + if ( rlt !== IGNORE ) { + + // Deferred对象 + if ( Base.isPromise( rlt ) ) { + dfds.push( rlt ); + } else { + rlts.push( rlt ); + } + } + } + + // 如果有callback,则用异步方式。 + if ( callback || dfds.length ) { + promise = Base.when.apply( Base, dfds ); + key = promise.pipe ? 'pipe' : 'then'; + + // 很重要不能删除。删除了会死循环。 + // 保证执行顺序。让callback总是在下一个 tick 中执行。 + return promise[ key ](function() { + var deferred = Base.Deferred(), + args = arguments; + + if ( args.length === 1 ) { + args = args[ 0 ]; + } + + setTimeout(function() { + deferred.resolve( args ); + }, 1 ); + + return deferred.promise(); + })[ callback ? key : 'done' ]( callback || Base.noop ); + } else { + return rlts[ 0 ]; + } + }, + + destroy: function() { + _destroy.apply( this, arguments ); + this._widgets = null; + } + }); + + /** + * 添加组件 + * @grammar Uploader.register(proto); + * @grammar Uploader.register(map, proto); + * @param {object} responseMap API 名称与函数实现的映射 + * @param {object} proto 组件原型,构造函数通过 constructor 属性定义 + * @method Uploader.register + * @for Uploader + * @example + * Uploader.register({ + * 'make-thumb': 'makeThumb' + * }, { + * init: function( options ) {}, + * makeThumb: function() {} + * }); + * + * Uploader.register({ + * 'make-thumb': function() { + * + * } + * }); + */ + Uploader.register = Widget.register = function( responseMap, widgetProto ) { + var map = { init: 'init', destroy: 'destroy', name: 'anonymous' }, + klass; + + if ( arguments.length === 1 ) { + widgetProto = responseMap; + + // 自动生成 map 表。 + $.each(widgetProto, function(key) { + if ( key[0] === '_' || key === 'name' ) { + key === 'name' && (map.name = widgetProto.name); + return; + } + + map[key.replace(/[A-Z]/g, '-$&').toLowerCase()] = key; + }); + + } else { + map = $.extend( map, responseMap ); + } + + widgetProto.responseMap = map; + klass = Base.inherits( Widget, widgetProto ); + klass._name = map.name; + widgetClass.push( klass ); + + return klass; + }; + + /** + * 删除插件,只有在注册时指定了名字的才能被删除。 + * @grammar Uploader.unRegister(name); + * @param {string} name 组件名字 + * @method Uploader.unRegister + * @for Uploader + * @example + * + * Uploader.register({ + * name: 'custom', + * + * 'make-thumb': function() { + * + * } + * }); + * + * Uploader.unRegister('custom'); + */ + Uploader.unRegister = Widget.unRegister = function( name ) { + if ( !name || name === 'anonymous' ) { + return; + } + + // 删除指定的插件。 + for ( var i = widgetClass.length; i--; ) { + if ( widgetClass[i]._name === name ) { + widgetClass.splice(i, 1) + } + } + }; + + return Widget; + }); + /** + * @fileOverview DragAndDrop Widget。 + */ + define('widgets/filednd',[ + 'base', + 'uploader', + 'lib/dnd', + 'widgets/widget' + ], function( Base, Uploader, Dnd ) { + var $ = Base.$; + + Uploader.options.dnd = ''; + + /** + * @property {Selector} [dnd=undefined] 指定Drag And Drop拖拽的容器,如果不指定,则不启动。 + * @namespace options + * @for Uploader + */ + + /** + * @property {Selector} [disableGlobalDnd=false] 是否禁掉整个页面的拖拽功能,如果不禁用,图片拖进来的时候会默认被浏览器打开。 + * @namespace options + * @for Uploader + */ + + /** + * @event dndAccept + * @param {DataTransferItemList} items DataTransferItem + * @description 阻止此事件可以拒绝某些类型的文件拖入进来。目前只有 chrome 提供这样的 API,且只能通过 mime-type 验证。 + * @for Uploader + */ + return Uploader.register({ + name: 'dnd', + + init: function( opts ) { + + if ( !opts.dnd || + this.request('predict-runtime-type') !== 'html5' ) { + return; + } + + var me = this, + deferred = Base.Deferred(), + options = $.extend({}, { + disableGlobalDnd: opts.disableGlobalDnd, + container: opts.dnd, + accept: opts.accept + }), + dnd; + + this.dnd = dnd = new Dnd( options ); + + dnd.once( 'ready', deferred.resolve ); + dnd.on( 'drop', function( files ) { + me.request( 'add-file', [ files ]); + }); + + // 检测文件是否全部允许添加。 + dnd.on( 'accept', function( items ) { + return me.owner.trigger( 'dndAccept', items ); + }); + + dnd.init(); + + return deferred.promise(); + }, + + destroy: function() { + this.dnd && this.dnd.destroy(); + } + }); + }); + + /** + * @fileOverview 错误信息 + */ + define('lib/filepaste',[ + 'base', + 'mediator', + 'runtime/client' + ], function( Base, Mediator, RuntimeClent ) { + + var $ = Base.$; + + function FilePaste( opts ) { + opts = this.options = $.extend({}, opts ); + opts.container = $( opts.container || document.body ); + RuntimeClent.call( this, 'FilePaste' ); + } + + Base.inherits( RuntimeClent, { + constructor: FilePaste, + + init: function() { + var me = this; + + me.connectRuntime( me.options, function() { + me.exec('init'); + me.trigger('ready'); + }); + } + }); + + Mediator.installTo( FilePaste.prototype ); + + return FilePaste; + }); + /** + * @fileOverview 组件基类。 + */ + define('widgets/filepaste',[ + 'base', + 'uploader', + 'lib/filepaste', + 'widgets/widget' + ], function( Base, Uploader, FilePaste ) { + var $ = Base.$; + + /** + * @property {Selector} [paste=undefined] 指定监听paste事件的容器,如果不指定,不启用此功能。此功能为通过粘贴来添加截屏的图片。建议设置为`document.body`. + * @namespace options + * @for Uploader + */ + return Uploader.register({ + name: 'paste', + + init: function( opts ) { + + if ( !opts.paste || + this.request('predict-runtime-type') !== 'html5' ) { + return; + } + + var me = this, + deferred = Base.Deferred(), + options = $.extend({}, { + container: opts.paste, + accept: opts.accept + }), + paste; + + this.paste = paste = new FilePaste( options ); + + paste.once( 'ready', deferred.resolve ); + paste.on( 'paste', function( files ) { + me.owner.request( 'add-file', [ files ]); + }); + paste.init(); + + return deferred.promise(); + }, + + destroy: function() { + this.paste && this.paste.destroy(); + } + }); + }); + /** + * @fileOverview Blob + */ + define('lib/blob',[ + 'base', + 'runtime/client' + ], function( Base, RuntimeClient ) { + + function Blob( ruid, source ) { + var me = this; + + me.source = source; + me.ruid = ruid; + this.size = source.size || 0; + + // 如果没有指定 mimetype, 但是知道文件后缀。 + if ( !source.type && this.ext && + ~'jpg,jpeg,png,gif,bmp'.indexOf( this.ext ) ) { + this.type = 'image/' + (this.ext === 'jpg' ? 'jpeg' : this.ext); + } else { + this.type = source.type || 'application/octet-stream'; + } + + RuntimeClient.call( me, 'Blob' ); + this.uid = source.uid || this.uid; + + if ( ruid ) { + me.connectRuntime( ruid ); + } + } + + Base.inherits( RuntimeClient, { + constructor: Blob, + + slice: function( start, end ) { + return this.exec( 'slice', start, end ); + }, + + getSource: function() { + return this.source; + } + }); + + return Blob; + }); + /** + * 为了统一化Flash的File和HTML5的File而存在。 + * 以至于要调用Flash里面的File,也可以像调用HTML5版本的File一下。 + * @fileOverview File + */ + define('lib/file',[ + 'base', + 'lib/blob' + ], function( Base, Blob ) { + + var uid = 1, + rExt = /\.([^.]+)$/; + + function File( ruid, file ) { + var ext; + + this.name = file.name || ('untitled' + uid++); + ext = rExt.exec( file.name ) ? RegExp.$1.toLowerCase() : ''; + + // todo 支持其他类型文件的转换。 + // 如果有 mimetype, 但是文件名里面没有找出后缀规律 + if ( !ext && file.type ) { + ext = /\/(jpg|jpeg|png|gif|bmp)$/i.exec( file.type ) ? + RegExp.$1.toLowerCase() : ''; + this.name += '.' + ext; + } + + this.ext = ext; + this.lastModifiedDate = file.lastModifiedDate || + (new Date()).toLocaleString(); + + Blob.apply( this, arguments ); + } + + return Base.inherits( Blob, File ); + }); + + /** + * @fileOverview 错误信息 + */ + define('lib/filepicker',[ + 'base', + 'runtime/client', + 'lib/file' + ], function( Base, RuntimeClent, File ) { + + var $ = Base.$; + + function FilePicker( opts ) { + opts = this.options = $.extend({}, FilePicker.options, opts ); + + opts.container = $( opts.id ); + + if ( !opts.container.length ) { + throw new Error('按钮指定错误'); + } + + opts.innerHTML = opts.innerHTML || opts.label || + opts.container.html() || ''; + + opts.button = $( opts.button || document.createElement('div') ); + opts.button.html( opts.innerHTML ); + opts.container.html( opts.button ); + + RuntimeClent.call( this, 'FilePicker', true ); + } + + FilePicker.options = { + button: null, + container: null, + label: null, + innerHTML: null, + multiple: true, + accept: null, + name: 'file' + }; + + Base.inherits( RuntimeClent, { + constructor: FilePicker, + + init: function() { + var me = this, + opts = me.options, + button = opts.button; + + button.addClass('webuploader-pick'); + + me.on( 'all', function( type ) { + var files; + + switch ( type ) { + case 'mouseenter': + button.addClass('webuploader-pick-hover'); + break; + + case 'mouseleave': + button.removeClass('webuploader-pick-hover'); + break; + + case 'change': + files = me.exec('getFiles'); + me.trigger( 'select', $.map( files, function( file ) { + file = new File( me.getRuid(), file ); + + // 记录来源。 + file._refer = opts.container; + return file; + }), opts.container ); + break; + } + }); + + me.connectRuntime( opts, function() { + me.refresh(); + me.exec( 'init', opts ); + me.trigger('ready'); + }); + + this._resizeHandler = Base.bindFn( this.refresh, this ); + $( window ).on( 'resize', this._resizeHandler ); + }, + + refresh: function() { + var shimContainer = this.getRuntime().getContainer(), + button = this.options.button, + width = button.outerWidth ? + button.outerWidth() : button.width(), + + height = button.outerHeight ? + button.outerHeight() : button.height(), + + pos = button.offset(); + + width && height && shimContainer.css({ + bottom: 'auto', + right: 'auto', + width: width + 'px', + height: height + 'px' + }).offset( pos ); + }, + + enable: function() { + var btn = this.options.button; + + btn.removeClass('webuploader-pick-disable'); + this.refresh(); + }, + + disable: function() { + var btn = this.options.button; + + this.getRuntime().getContainer().css({ + top: '-99999px' + }); + + btn.addClass('webuploader-pick-disable'); + }, + + destroy: function() { + var btn = this.options.button; + $( window ).off( 'resize', this._resizeHandler ); + btn.removeClass('webuploader-pick-disable webuploader-pick-hover ' + + 'webuploader-pick'); + } + }); + + return FilePicker; + }); + + /** + * @fileOverview 文件选择相关 + */ + define('widgets/filepicker',[ + 'base', + 'uploader', + 'lib/filepicker', + 'widgets/widget' + ], function( Base, Uploader, FilePicker ) { + var $ = Base.$; + + $.extend( Uploader.options, { + + /** + * @property {Selector | Object} [pick=undefined] + * @namespace options + * @for Uploader + * @description 指定选择文件的按钮容器,不指定则不创建按钮。 + * + * * `id` {Seletor|dom} 指定选择文件的按钮容器,不指定则不创建按钮。**注意** 这里虽然写的是 id, 但是不是只支持 id, 还支持 class, 或者 dom 节点。 + * * `label` {String} 请采用 `innerHTML` 代替 + * * `innerHTML` {String} 指定按钮文字。不指定时优先从指定的容器中看是否自带文字。 + * * `multiple` {Boolean} 是否开起同时选择多个文件能力。 + */ + pick: null, + + /** + * @property {Arroy} [accept=null] + * @namespace options + * @for Uploader + * @description 指定接受哪些类型的文件。 由于目前还有ext转mimeType表,所以这里需要分开指定。 + * + * * `title` {String} 文字描述 + * * `extensions` {String} 允许的文件后缀,不带点,多个用逗号分割。 + * * `mimeTypes` {String} 多个用逗号分割。 + * + * 如: + * + * ``` + * { + * title: 'Images', + * extensions: 'gif,jpg,jpeg,bmp,png', + * mimeTypes: 'image/*' + * } + * ``` + */ + accept: null/*{ + title: 'Images', + extensions: 'gif,jpg,jpeg,bmp,png', + mimeTypes: 'image/*' + }*/ + }); + + return Uploader.register({ + name: 'picker', + + init: function( opts ) { + this.pickers = []; + return opts.pick && this.addBtn( opts.pick ); + }, + + refresh: function() { + $.each( this.pickers, function() { + this.refresh(); + }); + }, + + /** + * @method addButton + * @for Uploader + * @grammar addButton( pick ) => Promise + * @description + * 添加文件选择按钮,如果一个按钮不够,需要调用此方法来添加。参数跟[options.pick](#WebUploader:Uploader:options)一致。 + * @example + * uploader.addButton({ + * id: '#btnContainer', + * innerHTML: '选择文件' + * }); + */ + addBtn: function( pick ) { + var me = this, + opts = me.options, + accept = opts.accept, + promises = []; + + if ( !pick ) { + return; + } + + $.isPlainObject( pick ) || (pick = { + id: pick + }); + + $( pick.id ).each(function() { + var options, picker, deferred; + + deferred = Base.Deferred(); + + options = $.extend({}, pick, { + accept: $.isPlainObject( accept ) ? [ accept ] : accept, + swf: opts.swf, + runtimeOrder: opts.runtimeOrder, + id: this + }); + + picker = new FilePicker( options ); + + picker.once( 'ready', deferred.resolve ); + picker.on( 'select', function( files ) { + me.owner.request( 'add-file', [ files ]); + }); + picker.init(); + + me.pickers.push( picker ); + + promises.push( deferred.promise() ); + }); + + return Base.when.apply( Base, promises ); + }, + + disable: function() { + $.each( this.pickers, function() { + this.disable(); + }); + }, + + enable: function() { + $.each( this.pickers, function() { + this.enable(); + }); + }, + + destroy: function() { + $.each( this.pickers, function() { + this.destroy(); + }); + this.pickers = null; + } + }); + }); + /** + * @fileOverview Image + */ + define('lib/image',[ + 'base', + 'runtime/client', + 'lib/blob' + ], function( Base, RuntimeClient, Blob ) { + var $ = Base.$; + + // 构造器。 + function Image( opts ) { + this.options = $.extend({}, Image.options, opts ); + RuntimeClient.call( this, 'Image' ); + + this.on( 'load', function() { + this._info = this.exec('info'); + this._meta = this.exec('meta'); + }); + } + + // 默认选项。 + Image.options = { + + // 默认的图片处理质量 + quality: 90, + + // 是否裁剪 + crop: false, + + // 是否保留头部信息 + preserveHeaders: false, + + // 是否允许放大。 + allowMagnify: false + }; + + // 继承RuntimeClient. + Base.inherits( RuntimeClient, { + constructor: Image, + + info: function( val ) { + + // setter + if ( val ) { + this._info = val; + return this; + } + + // getter + return this._info; + }, + + meta: function( val ) { + + // setter + if ( val ) { + this._meta = val; + return this; + } + + // getter + return this._meta; + }, + + loadFromBlob: function( blob ) { + var me = this, + ruid = blob.getRuid(); + + this.connectRuntime( ruid, function() { + me.exec( 'init', me.options ); + me.exec( 'loadFromBlob', blob ); + }); + }, + + resize: function() { + var args = Base.slice( arguments ); + return this.exec.apply( this, [ 'resize' ].concat( args ) ); + }, + + crop: function() { + var args = Base.slice( arguments ); + return this.exec.apply( this, [ 'crop' ].concat( args ) ); + }, + + getAsDataUrl: function( type ) { + return this.exec( 'getAsDataUrl', type ); + }, + + getAsBlob: function( type ) { + var blob = this.exec( 'getAsBlob', type ); + + return new Blob( this.getRuid(), blob ); + } + }); + + return Image; + }); + /** + * @fileOverview 图片操作, 负责预览图片和上传前压缩图片 + */ + define('widgets/image',[ + 'base', + 'uploader', + 'lib/image', + 'widgets/widget' + ], function( Base, Uploader, Image ) { + + var $ = Base.$, + throttle; + + // 根据要处理的文件大小来节流,一次不能处理太多,会卡。 + throttle = (function( max ) { + var occupied = 0, + waiting = [], + tick = function() { + var item; + + while ( waiting.length && occupied < max ) { + item = waiting.shift(); + occupied += item[ 0 ]; + item[ 1 ](); + } + }; + + return function( emiter, size, cb ) { + waiting.push([ size, cb ]); + emiter.once( 'destroy', function() { + occupied -= size; + setTimeout( tick, 1 ); + }); + setTimeout( tick, 1 ); + }; + })( 5 * 1024 * 1024 ); + + $.extend( Uploader.options, { + + /** + * @property {Object} [thumb] + * @namespace options + * @for Uploader + * @description 配置生成缩略图的选项。 + * + * 默认为: + * + * ```javascript + * { + * width: 110, + * height: 110, + * + * // 图片质量,只有type为`image/jpeg`的时候才有效。 + * quality: 70, + * + * // 是否允许放大,如果想要生成小图的时候不失真,此选项应该设置为false. + * allowMagnify: true, + * + * // 是否允许裁剪。 + * crop: true, + * + * // 为空的话则保留原有图片格式。 + * // 否则强制转换成指定的类型。 + * type: 'image/jpeg' + * } + * ``` + */ + thumb: { + width: 110, + height: 110, + quality: 70, + allowMagnify: true, + crop: true, + preserveHeaders: false, + + // 为空的话则保留原有图片格式。 + // 否则强制转换成指定的类型。 + // IE 8下面 base64 大小不能超过 32K 否则预览失败,而非 jpeg 编码的图片很可 + // 能会超过 32k, 所以这里设置成预览的时候都是 image/jpeg + type: 'image/jpeg' + }, + + /** + * @property {Object} [compress] + * @namespace options + * @for Uploader + * @description 配置压缩的图片的选项。如果此选项为`false`, 则图片在上传前不进行压缩。 + * + * 默认为: + * + * ```javascript + * { + * width: 1600, + * height: 1600, + * + * // 图片质量,只有type为`image/jpeg`的时候才有效。 + * quality: 90, + * + * // 是否允许放大,如果想要生成小图的时候不失真,此选项应该设置为false. + * allowMagnify: false, + * + * // 是否允许裁剪。 + * crop: false, + * + * // 是否保留头部meta信息。 + * preserveHeaders: true, + * + * // 如果发现压缩后文件大小比原来还大,则使用原来图片 + * // 此属性可能会影响图片自动纠正功能 + * noCompressIfLarger: false, + * + * // 单位字节,如果图片大小小于此值,不会采用压缩。 + * compressSize: 0 + * } + * ``` + */ + compress: { + width: 1600, + height: 1600, + quality: 90, + allowMagnify: false, + crop: false, + preserveHeaders: true + } + }); + + return Uploader.register({ + + name: 'image', + + + /** + * 生成缩略图,此过程为异步,所以需要传入`callback`。 + * 通常情况在图片加入队里后调用此方法来生成预览图以增强交互效果。 + * + * 当 width 或者 height 的值介于 0 - 1 时,被当成百分比使用。 + * + * `callback`中可以接收到两个参数。 + * * 第一个为error,如果生成缩略图有错误,此error将为真。 + * * 第二个为ret, 缩略图的Data URL值。 + * + * **注意** + * Date URL在IE6/7中不支持,所以不用调用此方法了,直接显示一张暂不支持预览图片好了。 + * 也可以借助服务端,将 base64 数据传给服务端,生成一个临时文件供预览。 + * + * @method makeThumb + * @grammar makeThumb( file, callback ) => undefined + * @grammar makeThumb( file, callback, width, height ) => undefined + * @for Uploader + * @example + * + * uploader.on( 'fileQueued', function( file ) { + * var $li = ...; + * + * uploader.makeThumb( file, function( error, ret ) { + * if ( error ) { + * $li.text('预览错误'); + * } else { + * $li.append(''); + * } + * }); + * + * }); + */ + makeThumb: function( file, cb, width, height ) { + var opts, image; + + file = this.request( 'get-file', file ); + + // 只预览图片格式。 + if ( !file.type.match( /^image/ ) ) { + cb( true ); + return; + } + + opts = $.extend({}, this.options.thumb ); + + // 如果传入的是object. + if ( $.isPlainObject( width ) ) { + opts = $.extend( opts, width ); + width = null; + } + + width = width || opts.width; + height = height || opts.height; + + image = new Image( opts ); + + image.once( 'load', function() { + file._info = file._info || image.info(); + file._meta = file._meta || image.meta(); + + // 如果 width 的值介于 0 - 1 + // 说明设置的是百分比。 + if ( width <= 1 && width > 0 ) { + width = file._info.width * width; + } + + // 同样的规则应用于 height + if ( height <= 1 && height > 0 ) { + height = file._info.height * height; + } + + image.resize( width, height ); + }); + + // 当 resize 完后 + image.once( 'complete', function() { + cb( false, image.getAsDataUrl( opts.type ) ); + image.destroy(); + }); + + image.once( 'error', function( reason ) { + cb( reason || true ); + image.destroy(); + }); + + throttle( image, file.source.size, function() { + file._info && image.info( file._info ); + file._meta && image.meta( file._meta ); + image.loadFromBlob( file.source ); + }); + }, + + beforeSendFile: function( file ) { + var opts = this.options.compress || this.options.resize, + compressSize = opts && opts.compressSize || 0, + noCompressIfLarger = opts && opts.noCompressIfLarger || false, + image, deferred; + + file = this.request( 'get-file', file ); + + // 只压缩 jpeg 图片格式。 + // gif 可能会丢失针 + // bmp png 基本上尺寸都不大,且压缩比比较小。 + if ( !opts || !~'image/jpeg,image/jpg'.indexOf( file.type ) || + file.size < compressSize || + file._compressed ) { + return; + } + + opts = $.extend({}, opts ); + deferred = Base.Deferred(); + + image = new Image( opts ); + + deferred.always(function() { + image.destroy(); + image = null; + }); + image.once( 'error', deferred.reject ); + image.once( 'load', function() { + var width = opts.width, + height = opts.height; + + file._info = file._info || image.info(); + file._meta = file._meta || image.meta(); + + // 如果 width 的值介于 0 - 1 + // 说明设置的是百分比。 + if ( width <= 1 && width > 0 ) { + width = file._info.width * width; + } + + // 同样的规则应用于 height + if ( height <= 1 && height > 0 ) { + height = file._info.height * height; + } + + image.resize( width, height ); + }); + + image.once( 'complete', function() { + var blob, size; + + // 移动端 UC / qq 浏览器的无图模式下 + // ctx.getImageData 处理大图的时候会报 Exception + // INDEX_SIZE_ERR: DOM Exception 1 + try { + blob = image.getAsBlob( opts.type ); + + size = file.size; + + // 如果压缩后,比原来还大则不用压缩后的。 + if ( !noCompressIfLarger || blob.size < size ) { + // file.source.destroy && file.source.destroy(); + file.source = blob; + file.size = blob.size; + + file.trigger( 'resize', blob.size, size ); + } + + // 标记,避免重复压缩。 + file._compressed = true; + deferred.resolve(); + } catch ( e ) { + // 出错了直接继续,让其上传原始图片 + deferred.resolve(); + } + }); + + file._info && image.info( file._info ); + file._meta && image.meta( file._meta ); + + image.loadFromBlob( file.source ); + return deferred.promise(); + } + }); + }); + /** + * @fileOverview 文件属性封装 + */ + define('file',[ + 'base', + 'mediator' + ], function( Base, Mediator ) { + + var $ = Base.$, + idPrefix = 'WU_FILE_', + idSuffix = 0, + rExt = /\.([^.]+)$/, + statusMap = {}; + + function gid() { + return idPrefix + idSuffix++; + } + + /** + * 文件类 + * @class File + * @constructor 构造函数 + * @grammar new File( source ) => File + * @param {Lib.File} source [lib.File](#Lib.File)实例, 此source对象是带有Runtime信息的。 + */ + function WUFile( source ) { + + /** + * 文件名,包括扩展名(后缀) + * @property name + * @type {string} + */ + this.name = source.name || 'Untitled'; + + /** + * 文件体积(字节) + * @property size + * @type {uint} + * @default 0 + */ + this.size = source.size || 0; + + /** + * 文件MIMETYPE类型,与文件类型的对应关系请参考[http://t.cn/z8ZnFny](http://t.cn/z8ZnFny) + * @property type + * @type {string} + * @default 'application/octet-stream' + */ + this.type = source.type || 'application/octet-stream'; + + /** + * 文件最后修改日期 + * @property lastModifiedDate + * @type {int} + * @default 当前时间戳 + */ + this.lastModifiedDate = source.lastModifiedDate || (new Date() * 1); + + /** + * 文件ID,每个对象具有唯一ID,与文件名无关 + * @property id + * @type {string} + */ + this.id = gid(); + + /** + * 文件扩展名,通过文件名获取,例如test.png的扩展名为png + * @property ext + * @type {string} + */ + this.ext = rExt.exec( this.name ) ? RegExp.$1 : ''; + + + /** + * 状态文字说明。在不同的status语境下有不同的用途。 + * @property statusText + * @type {string} + */ + this.statusText = ''; + + // 存储文件状态,防止通过属性直接修改 + statusMap[ this.id ] = WUFile.Status.INITED; + + this.source = source; + this.loaded = 0; + + this.on( 'error', function( msg ) { + this.setStatus( WUFile.Status.ERROR, msg ); + }); + } + + $.extend( WUFile.prototype, { + + /** + * 设置状态,状态变化时会触发`change`事件。 + * @method setStatus + * @grammar setStatus( status[, statusText] ); + * @param {File.Status|String} status [文件状态值](#WebUploader:File:File.Status) + * @param {String} [statusText=''] 状态说明,常在error时使用,用http, abort,server等来标记是由于什么原因导致文件错误。 + */ + setStatus: function( status, text ) { + + var prevStatus = statusMap[ this.id ]; + + typeof text !== 'undefined' && (this.statusText = text); + + if ( status !== prevStatus ) { + statusMap[ this.id ] = status; + /** + * 文件状态变化 + * @event statuschange + */ + this.trigger( 'statuschange', status, prevStatus ); + } + + }, + + /** + * 获取文件状态 + * @return {File.Status} + * @example + 文件状态具体包括以下几种类型: + { + // 初始化 + INITED: 0, + // 已入队列 + QUEUED: 1, + // 正在上传 + PROGRESS: 2, + // 上传出错 + ERROR: 3, + // 上传成功 + COMPLETE: 4, + // 上传取消 + CANCELLED: 5 + } + */ + getStatus: function() { + return statusMap[ this.id ]; + }, + + /** + * 获取文件原始信息。 + * @return {*} + */ + getSource: function() { + return this.source; + }, + + destroy: function() { + this.off(); + delete statusMap[ this.id ]; + } + }); + + Mediator.installTo( WUFile.prototype ); + + /** + * 文件状态值,具体包括以下几种类型: + * * `inited` 初始状态 + * * `queued` 已经进入队列, 等待上传 + * * `progress` 上传中 + * * `complete` 上传完成。 + * * `error` 上传出错,可重试 + * * `interrupt` 上传中断,可续传。 + * * `invalid` 文件不合格,不能重试上传。会自动从队列中移除。 + * * `cancelled` 文件被移除。 + * @property {Object} Status + * @namespace File + * @class File + * @static + */ + WUFile.Status = { + INITED: 'inited', // 初始状态 + QUEUED: 'queued', // 已经进入队列, 等待上传 + PROGRESS: 'progress', // 上传中 + ERROR: 'error', // 上传出错,可重试 + COMPLETE: 'complete', // 上传完成。 + CANCELLED: 'cancelled', // 上传取消。 + INTERRUPT: 'interrupt', // 上传中断,可续传。 + INVALID: 'invalid' // 文件不合格,不能重试上传。 + }; + + return WUFile; + }); + + /** + * @fileOverview 文件队列 + */ + define('queue',[ + 'base', + 'mediator', + 'file' + ], function( Base, Mediator, WUFile ) { + + var $ = Base.$, + STATUS = WUFile.Status; + + /** + * 文件队列, 用来存储各个状态中的文件。 + * @class Queue + * @extends Mediator + */ + function Queue() { + + /** + * 统计文件数。 + * * `numOfQueue` 队列中的文件数。 + * * `numOfSuccess` 上传成功的文件数 + * * `numOfCancel` 被取消的文件数 + * * `numOfProgress` 正在上传中的文件数 + * * `numOfUploadFailed` 上传错误的文件数。 + * * `numOfInvalid` 无效的文件数。 + * * `numofDeleted` 被移除的文件数。 + * @property {Object} stats + */ + this.stats = { + numOfQueue: 0, + numOfSuccess: 0, + numOfCancel: 0, + numOfProgress: 0, + numOfUploadFailed: 0, + numOfInvalid: 0, + numofDeleted: 0, + numofInterrupt: 0 + }; + + // 上传队列,仅包括等待上传的文件 + this._queue = []; + + // 存储所有文件 + this._map = {}; + } + + $.extend( Queue.prototype, { + + /** + * 将新文件加入对队列尾部 + * + * @method append + * @param {File} file 文件对象 + */ + append: function( file ) { + this._queue.push( file ); + this._fileAdded( file ); + return this; + }, + + /** + * 将新文件加入对队列头部 + * + * @method prepend + * @param {File} file 文件对象 + */ + prepend: function( file ) { + this._queue.unshift( file ); + this._fileAdded( file ); + return this; + }, + + /** + * 获取文件对象 + * + * @method getFile + * @param {String} fileId 文件ID + * @return {File} + */ + getFile: function( fileId ) { + if ( typeof fileId !== 'string' ) { + return fileId; + } + return this._map[ fileId ]; + }, + + /** + * 从队列中取出一个指定状态的文件。 + * @grammar fetch( status ) => File + * @method fetch + * @param {String} status [文件状态值](#WebUploader:File:File.Status) + * @return {File} [File](#WebUploader:File) + */ + fetch: function( status ) { + var len = this._queue.length, + i, file; + + status = status || STATUS.QUEUED; + + for ( i = 0; i < len; i++ ) { + file = this._queue[ i ]; + + if ( status === file.getStatus() ) { + return file; + } + } + + return null; + }, + + /** + * 对队列进行排序,能够控制文件上传顺序。 + * @grammar sort( fn ) => undefined + * @method sort + * @param {Function} fn 排序方法 + */ + sort: function( fn ) { + if ( typeof fn === 'function' ) { + this._queue.sort( fn ); + } + }, + + /** + * 获取指定类型的文件列表, 列表中每一个成员为[File](#WebUploader:File)对象。 + * @grammar getFiles( [status1[, status2 ...]] ) => Array + * @method getFiles + * @param {String} [status] [文件状态值](#WebUploader:File:File.Status) + */ + getFiles: function() { + var sts = [].slice.call( arguments, 0 ), + ret = [], + i = 0, + len = this._queue.length, + file; + + for ( ; i < len; i++ ) { + file = this._queue[ i ]; + + if ( sts.length && !~$.inArray( file.getStatus(), sts ) ) { + continue; + } + + ret.push( file ); + } + + return ret; + }, + + /** + * 在队列中删除文件。 + * @grammar removeFile( file ) => Array + * @method removeFile + * @param {File} 文件对象。 + */ + removeFile: function( file ) { + var me = this, + existing = this._map[ file.id ]; + + if ( existing ) { + delete this._map[ file.id ]; + file.destroy(); + this.stats.numofDeleted++; + } + }, + + _fileAdded: function( file ) { + var me = this, + existing = this._map[ file.id ]; + + if ( !existing ) { + this._map[ file.id ] = file; + + file.on( 'statuschange', function( cur, pre ) { + me._onFileStatusChange( cur, pre ); + }); + } + }, + + _onFileStatusChange: function( curStatus, preStatus ) { + var stats = this.stats; + + switch ( preStatus ) { + case STATUS.PROGRESS: + stats.numOfProgress--; + break; + + case STATUS.QUEUED: + stats.numOfQueue --; + break; + + case STATUS.ERROR: + stats.numOfUploadFailed--; + break; + + case STATUS.INVALID: + stats.numOfInvalid--; + break; + + case STATUS.INTERRUPT: + stats.numofInterrupt--; + break; + } + + switch ( curStatus ) { + case STATUS.QUEUED: + stats.numOfQueue++; + break; + + case STATUS.PROGRESS: + stats.numOfProgress++; + break; + + case STATUS.ERROR: + stats.numOfUploadFailed++; + break; + + case STATUS.COMPLETE: + stats.numOfSuccess++; + break; + + case STATUS.CANCELLED: + stats.numOfCancel++; + break; + + + case STATUS.INVALID: + stats.numOfInvalid++; + break; + + case STATUS.INTERRUPT: + stats.numofInterrupt++; + break; + } + } + + }); + + Mediator.installTo( Queue.prototype ); + + return Queue; + }); + /** + * @fileOverview 队列 + */ + define('widgets/queue',[ + 'base', + 'uploader', + 'queue', + 'file', + 'lib/file', + 'runtime/client', + 'widgets/widget' + ], function( Base, Uploader, Queue, WUFile, File, RuntimeClient ) { + + var $ = Base.$, + rExt = /\.\w+$/, + Status = WUFile.Status; + + return Uploader.register({ + name: 'queue', + + init: function( opts ) { + var me = this, + deferred, len, i, item, arr, accept, runtime; + + if ( $.isPlainObject( opts.accept ) ) { + opts.accept = [ opts.accept ]; + } + + // accept中的中生成匹配正则。 + if ( opts.accept ) { + arr = []; + + for ( i = 0, len = opts.accept.length; i < len; i++ ) { + item = opts.accept[ i ].extensions; + item && arr.push( item ); + } + + if ( arr.length ) { + accept = '\\.' + arr.join(',') + .replace( /,/g, '$|\\.' ) + .replace( /\*/g, '.*' ) + '$'; + } + + me.accept = new RegExp( accept, 'i' ); + } + + me.queue = new Queue(); + me.stats = me.queue.stats; + + // 如果当前不是html5运行时,那就算了。 + // 不执行后续操作 + if ( this.request('predict-runtime-type') !== 'html5' ) { + return; + } + + // 创建一个 html5 运行时的 placeholder + // 以至于外部添加原生 File 对象的时候能正确包裹一下供 webuploader 使用。 + deferred = Base.Deferred(); + this.placeholder = runtime = new RuntimeClient('Placeholder'); + runtime.connectRuntime({ + runtimeOrder: 'html5' + }, function() { + me._ruid = runtime.getRuid(); + deferred.resolve(); + }); + return deferred.promise(); + }, + + + // 为了支持外部直接添加一个原生File对象。 + _wrapFile: function( file ) { + if ( !(file instanceof WUFile) ) { + + if ( !(file instanceof File) ) { + if ( !this._ruid ) { + throw new Error('Can\'t add external files.'); + } + file = new File( this._ruid, file ); + } + + file = new WUFile( file ); + } + + return file; + }, + + // 判断文件是否可以被加入队列 + acceptFile: function( file ) { + var invalid = !file || !file.size || this.accept && + + // 如果名字中有后缀,才做后缀白名单处理。 + rExt.exec( file.name ) && !this.accept.test( file.name ); + + return !invalid; + }, + + + /** + * @event beforeFileQueued + * @param {File} file File对象 + * @description 当文件被加入队列之前触发,此事件的handler返回值为`false`,则此文件不会被添加进入队列。 + * @for Uploader + */ + + /** + * @event fileQueued + * @param {File} file File对象 + * @description 当文件被加入队列以后触发。 + * @for Uploader + */ + + _addFile: function( file ) { + var me = this; + + file = me._wrapFile( file ); + + // 不过类型判断允许不允许,先派送 `beforeFileQueued` + if ( !me.owner.trigger( 'beforeFileQueued', file ) ) { + return; + } + + // 类型不匹配,则派送错误事件,并返回。 + if ( !me.acceptFile( file ) ) { + me.owner.trigger( 'error', 'Q_TYPE_DENIED', file ); + return; + } + + me.queue.append( file ); + me.owner.trigger( 'fileQueued', file ); + return file; + }, + + getFile: function( fileId ) { + return this.queue.getFile( fileId ); + }, + + /** + * @event filesQueued + * @param {File} files 数组,内容为原始File(lib/File)对象。 + * @description 当一批文件添加进队列以后触发。 + * @for Uploader + */ + + /** + * @property {Boolean} [auto=false] + * @namespace options + * @for Uploader + * @description 设置为 true 后,不需要手动调用上传,有文件选择即开始上传。 + * + */ + + /** + * @method addFiles + * @grammar addFiles( file ) => undefined + * @grammar addFiles( [file1, file2 ...] ) => undefined + * @param {Array of File or File} [files] Files 对象 数组 + * @description 添加文件到队列 + * @for Uploader + */ + addFile: function( files ) { + var me = this; + + if ( !files.length ) { + files = [ files ]; + } + + files = $.map( files, function( file ) { + return me._addFile( file ); + }); + + me.owner.trigger( 'filesQueued', files ); + + if ( me.options.auto ) { + setTimeout(function() { + me.request('start-upload'); + }, 20 ); + } + }, + + getStats: function() { + return this.stats; + }, + + /** + * @event fileDequeued + * @param {File} file File对象 + * @description 当文件被移除队列后触发。 + * @for Uploader + */ + + /** + * @method removeFile + * @grammar removeFile( file ) => undefined + * @grammar removeFile( id ) => undefined + * @grammar removeFile( file, true ) => undefined + * @grammar removeFile( id, true ) => undefined + * @param {File|id} file File对象或这File对象的id + * @description 移除某一文件, 默认只会标记文件状态为已取消,如果第二个参数为 `true` 则会从 queue 中移除。 + * @for Uploader + * @example + * + * $li.on('click', '.remove-this', function() { + * uploader.removeFile( file ); + * }) + */ + removeFile: function( file, remove ) { + var me = this; + + file = file.id ? file : me.queue.getFile( file ); + + this.request( 'cancel-file', file ); + + if ( remove ) { + this.queue.removeFile( file ); + } + }, + + /** + * @method getFiles + * @grammar getFiles() => Array + * @grammar getFiles( status1, status2, status... ) => Array + * @description 返回指定状态的文件集合,不传参数将返回所有状态的文件。 + * @for Uploader + * @example + * console.log( uploader.getFiles() ); // => all files + * console.log( uploader.getFiles('error') ) // => all error files. + */ + getFiles: function() { + return this.queue.getFiles.apply( this.queue, arguments ); + }, + + fetchFile: function() { + return this.queue.fetch.apply( this.queue, arguments ); + }, + + /** + * @method retry + * @grammar retry() => undefined + * @grammar retry( file ) => undefined + * @description 重试上传,重试指定文件,或者从出错的文件开始重新上传。 + * @for Uploader + * @example + * function retry() { + * uploader.retry(); + * } + */ + retry: function( file, noForceStart ) { + var me = this, + files, i, len; + + if ( file ) { + file = file.id ? file : me.queue.getFile( file ); + file.setStatus( Status.QUEUED ); + noForceStart || me.request('start-upload'); + return; + } + + files = me.queue.getFiles( Status.ERROR ); + i = 0; + len = files.length; + + for ( ; i < len; i++ ) { + file = files[ i ]; + file.setStatus( Status.QUEUED ); + } + + me.request('start-upload'); + }, + + /** + * @method sort + * @grammar sort( fn ) => undefined + * @description 排序队列中的文件,在上传之前调整可以控制上传顺序。 + * @for Uploader + */ + sortFiles: function() { + return this.queue.sort.apply( this.queue, arguments ); + }, + + /** + * @event reset + * @description 当 uploader 被重置的时候触发。 + * @for Uploader + */ + + /** + * @method reset + * @grammar reset() => undefined + * @description 重置uploader。目前只重置了队列。 + * @for Uploader + * @example + * uploader.reset(); + */ + reset: function() { + this.owner.trigger('reset'); + this.queue = new Queue(); + this.stats = this.queue.stats; + }, + + destroy: function() { + this.reset(); + this.placeholder && this.placeholder.destroy(); + } + }); + + }); + /** + * @fileOverview 添加获取Runtime相关信息的方法。 + */ + define('widgets/runtime',[ + 'uploader', + 'runtime/runtime', + 'widgets/widget' + ], function( Uploader, Runtime ) { + + Uploader.support = function() { + return Runtime.hasRuntime.apply( Runtime, arguments ); + }; + + /** + * @property {Object} [runtimeOrder=html5,flash] + * @namespace options + * @for Uploader + * @description 指定运行时启动顺序。默认会想尝试 html5 是否支持,如果支持则使用 html5, 否则则使用 flash. + * + * 可以将此值设置成 `flash`,来强制使用 flash 运行时。 + */ + + return Uploader.register({ + name: 'runtime', + + init: function() { + if ( !this.predictRuntimeType() ) { + throw Error('Runtime Error'); + } + }, + + /** + * 预测Uploader将采用哪个`Runtime` + * @grammar predictRuntimeType() => String + * @method predictRuntimeType + * @for Uploader + */ + predictRuntimeType: function() { + var orders = this.options.runtimeOrder || Runtime.orders, + type = this.type, + i, len; + + if ( !type ) { + orders = orders.split( /\s*,\s*/g ); + + for ( i = 0, len = orders.length; i < len; i++ ) { + if ( Runtime.hasRuntime( orders[ i ] ) ) { + this.type = type = orders[ i ]; + break; + } + } + } + + return type; + } + }); + }); + /** + * @fileOverview Transport + */ + define('lib/transport',[ + 'base', + 'runtime/client', + 'mediator' + ], function( Base, RuntimeClient, Mediator ) { + + var $ = Base.$; + + function Transport( opts ) { + var me = this; + + opts = me.options = $.extend( true, {}, Transport.options, opts || {} ); + RuntimeClient.call( this, 'Transport' ); + + this._blob = null; + this._formData = opts.formData || {}; + this._headers = opts.headers || {}; + + this.on( 'progress', this._timeout ); + this.on( 'load error', function() { + me.trigger( 'progress', 1 ); + clearTimeout( me._timer ); + }); + } + + Transport.options = { + server: '', + method: 'POST', + + // 跨域时,是否允许携带cookie, 只有html5 runtime才有效 + withCredentials: false, + fileVal: 'file', + timeout: 2 * 60 * 1000, // 2分钟 + formData: {}, + headers: {}, + sendAsBinary: false + }; + + $.extend( Transport.prototype, { + + // 添加Blob, 只能添加一次,最后一次有效。 + appendBlob: function( key, blob, filename ) { + var me = this, + opts = me.options; + + if ( me.getRuid() ) { + me.disconnectRuntime(); + } + + // 连接到blob归属的同一个runtime. + me.connectRuntime( blob.ruid, function() { + me.exec('init'); + }); + + me._blob = blob; + opts.fileVal = key || opts.fileVal; + opts.filename = filename || opts.filename; + }, + + // 添加其他字段 + append: function( key, value ) { + if ( typeof key === 'object' ) { + $.extend( this._formData, key ); + } else { + this._formData[ key ] = value; + } + }, + + setRequestHeader: function( key, value ) { + if ( typeof key === 'object' ) { + $.extend( this._headers, key ); + } else { + this._headers[ key ] = value; + } + }, + + send: function( method ) { + this.exec( 'send', method ); + this._timeout(); + }, + + abort: function() { + clearTimeout( this._timer ); + return this.exec('abort'); + }, + + destroy: function() { + this.trigger('destroy'); + this.off(); + this.exec('destroy'); + this.disconnectRuntime(); + }, + + getResponse: function() { + return this.exec('getResponse'); + }, + + getResponseAsJson: function() { + return this.exec('getResponseAsJson'); + }, + + getStatus: function() { + return this.exec('getStatus'); + }, + + _timeout: function() { + var me = this, + duration = me.options.timeout; + + if ( !duration ) { + return; + } + + clearTimeout( me._timer ); + me._timer = setTimeout(function() { + me.abort(); + me.trigger( 'error', 'timeout' ); + }, duration ); + } + + }); + + // 让Transport具备事件功能。 + Mediator.installTo( Transport.prototype ); + + return Transport; + }); + /** + * @fileOverview 负责文件上传相关。 + */ + define('widgets/upload',[ + 'base', + 'uploader', + 'file', + 'lib/transport', + 'widgets/widget' + ], function( Base, Uploader, WUFile, Transport ) { + + var $ = Base.$, + isPromise = Base.isPromise, + Status = WUFile.Status; + + // 添加默认配置项 + $.extend( Uploader.options, { + + + /** + * @property {Boolean} [prepareNextFile=false] + * @namespace options + * @for Uploader + * @description 是否允许在文件传输时提前把下一个文件准备好。 + * 对于一个文件的准备工作比较耗时,比如图片压缩,md5序列化。 + * 如果能提前在当前文件传输期处理,可以节省总体耗时。 + */ + prepareNextFile: false, + + /** + * @property {Boolean} [chunked=false] + * @namespace options + * @for Uploader + * @description 是否要分片处理大文件上传。 + */ + chunked: false, + + /** + * @property {Boolean} [chunkSize=5242880] + * @namespace options + * @for Uploader + * @description 如果要分片,分多大一片? 默认大小为5M. + */ + chunkSize: 5 * 1024 * 1024, + + /** + * @property {Boolean} [chunkRetry=2] + * @namespace options + * @for Uploader + * @description 如果某个分片由于网络问题出错,允许自动重传多少次? + */ + chunkRetry: 2, + + /** + * @property {Boolean} [threads=3] + * @namespace options + * @for Uploader + * @description 上传并发数。允许同时最大上传进程数。 + */ + threads: 3, + + + /** + * @property {Object} [formData={}] + * @namespace options + * @for Uploader + * @description 文件上传请求的参数表,每次发送都会发送此对象中的参数。 + */ + formData: {} + + /** + * @property {Object} [fileVal='file'] + * @namespace options + * @for Uploader + * @description 设置文件上传域的name。 + */ + + /** + * @property {Object} [method='POST'] + * @namespace options + * @for Uploader + * @description 文件上传方式,`POST`或者`GET`。 + */ + + /** + * @property {Object} [sendAsBinary=false] + * @namespace options + * @for Uploader + * @description 是否已二进制的流的方式发送文件,这样整个上传内容`php://input`都为文件内容, + * 其他参数在$_GET数组中。 + */ + }); + + // 负责将文件切片。 + function CuteFile( file, chunkSize ) { + var pending = [], + blob = file.source, + total = blob.size, + chunks = chunkSize ? Math.ceil( total / chunkSize ) : 1, + start = 0, + index = 0, + len, api; + + api = { + file: file, + + has: function() { + return !!pending.length; + }, + + shift: function() { + return pending.shift(); + }, + + unshift: function( block ) { + pending.unshift( block ); + } + }; + + while ( index < chunks ) { + len = Math.min( chunkSize, total - start ); + + pending.push({ + file: file, + start: start, + end: chunkSize ? (start + len) : total, + total: total, + chunks: chunks, + chunk: index++, + cuted: api + }); + start += len; + } + + file.blocks = pending.concat(); + file.remaning = pending.length; + + return api; + } + + Uploader.register({ + name: 'upload', + + init: function() { + var owner = this.owner, + me = this; + + this.runing = false; + this.progress = false; + + owner + .on( 'startUpload', function() { + me.progress = true; + }) + .on( 'uploadFinished', function() { + me.progress = false; + }); + + // 记录当前正在传的数据,跟threads相关 + this.pool = []; + + // 缓存分好片的文件。 + this.stack = []; + + // 缓存即将上传的文件。 + this.pending = []; + + // 跟踪还有多少分片在上传中但是没有完成上传。 + this.remaning = 0; + this.__tick = Base.bindFn( this._tick, this ); + + owner.on( 'uploadComplete', function( file ) { + + // 把其他块取消了。 + file.blocks && $.each( file.blocks, function( _, v ) { + v.transport && (v.transport.abort(), v.transport.destroy()); + delete v.transport; + }); + + delete file.blocks; + delete file.remaning; + }); + }, + + reset: function() { + this.request( 'stop-upload', true ); + this.runing = false; + this.pool = []; + this.stack = []; + this.pending = []; + this.remaning = 0; + this._trigged = false; + this._promise = null; + }, + + /** + * @event startUpload + * @description 当开始上传流程时触发。 + * @for Uploader + */ + + /** + * 开始上传。此方法可以从初始状态调用开始上传流程,也可以从暂停状态调用,继续上传流程。 + * + * 可以指定开始某一个文件。 + * @grammar upload() => undefined + * @grammar upload( file | fileId) => undefined + * @method upload + * @for Uploader + */ + startUpload: function(file) { + var me = this; + + // 移出invalid的文件 + $.each( me.request( 'get-files', Status.INVALID ), function() { + me.request( 'remove-file', this ); + }); + + // 如果指定了开始某个文件,则只开始指定文件。 + if ( file ) { + file = file.id ? file : me.request( 'get-file', file ); + + if (file.getStatus() === Status.INTERRUPT) { + $.each( me.pool, function( _, v ) { + + // 之前暂停过。 + if (v.file !== file) { + return; + } + + v.transport && v.transport.send(); + }); + + file.setStatus( Status.QUEUED ); + } else if (file.getStatus() === Status.PROGRESS) { + return; + } else { + file.setStatus( Status.QUEUED ); + } + } else { + $.each( me.request( 'get-files', [ Status.INITED ] ), function() { + this.setStatus( Status.QUEUED ); + }); + } + + if ( me.runing ) { + return; + } + + me.runing = true; + + var files = []; + + // 如果有暂停的,则续传 + $.each( me.pool, function( _, v ) { + var file = v.file; + + if ( file.getStatus() === Status.INTERRUPT ) { + files.push(file); + me._trigged = false; + v.transport && v.transport.send(); + } + }); + + var file; + while ( (file = files.shift()) ) { + file.setStatus( Status.PROGRESS ); + } + + file || $.each( me.request( 'get-files', + Status.INTERRUPT ), function() { + this.setStatus( Status.PROGRESS ); + }); + + me._trigged = false; + Base.nextTick( me.__tick ); + me.owner.trigger('startUpload'); + }, + + /** + * @event stopUpload + * @description 当开始上传流程暂停时触发。 + * @for Uploader + */ + + /** + * 暂停上传。第一个参数为是否中断上传当前正在上传的文件。 + * + * 如果第一个参数是文件,则只暂停指定文件。 + * @grammar stop() => undefined + * @grammar stop( true ) => undefined + * @grammar stop( file ) => undefined + * @method stop + * @for Uploader + */ + stopUpload: function( file, interrupt ) { + var me = this; + + if (file === true) { + interrupt = file; + file = null; + } + + if ( me.runing === false ) { + return; + } + + // 如果只是暂停某个文件。 + if ( file ) { + file = file.id ? file : me.request( 'get-file', file ); + + if ( file.getStatus() !== Status.PROGRESS && + file.getStatus() !== Status.QUEUED ) { + return; + } + + file.setStatus( Status.INTERRUPT ); + $.each( me.pool, function( _, v ) { + + // 只 abort 指定的文件。 + if (v.file !== file) { + return; + } + + v.transport && v.transport.abort(); + me._putback(v); + me._popBlock(v); + }); + + return Base.nextTick( me.__tick ); + } + + me.runing = false; + + if (this._promise && this._promise.file) { + this._promise.file.setStatus( Status.INTERRUPT ); + } + + interrupt && $.each( me.pool, function( _, v ) { + v.transport && v.transport.abort(); + v.file.setStatus( Status.INTERRUPT ); + }); + + me.owner.trigger('stopUpload'); + }, + + /** + * @method cancelFile + * @grammar cancelFile( file ) => undefined + * @grammar cancelFile( id ) => undefined + * @param {File|id} file File对象或这File对象的id + * @description 标记文件状态为已取消, 同时将中断文件传输。 + * @for Uploader + * @example + * + * $li.on('click', '.remove-this', function() { + * uploader.cancelFile( file ); + * }) + */ + cancelFile: function( file ) { + file = file.id ? file : this.request( 'get-file', file ); + + // 如果正在上传。 + file.blocks && $.each( file.blocks, function( _, v ) { + var _tr = v.transport; + + if ( _tr ) { + _tr.abort(); + _tr.destroy(); + delete v.transport; + } + }); + + file.setStatus( Status.CANCELLED ); + this.owner.trigger( 'fileDequeued', file ); + }, + + /** + * 判断`Uplaode`r是否正在上传中。 + * @grammar isInProgress() => Boolean + * @method isInProgress + * @for Uploader + */ + isInProgress: function() { + return !!this.progress; + }, + + _getStats: function() { + return this.request('get-stats'); + }, + + /** + * 掉过一个文件上传,直接标记指定文件为已上传状态。 + * @grammar skipFile( file ) => undefined + * @method skipFile + * @for Uploader + */ + skipFile: function( file, status ) { + file = file.id ? file : this.request( 'get-file', file ); + + file.setStatus( status || Status.COMPLETE ); + file.skipped = true; + + // 如果正在上传。 + file.blocks && $.each( file.blocks, function( _, v ) { + var _tr = v.transport; + + if ( _tr ) { + _tr.abort(); + _tr.destroy(); + delete v.transport; + } + }); + + this.owner.trigger( 'uploadSkip', file ); + }, + + /** + * @event uploadFinished + * @description 当所有文件上传结束时触发。 + * @for Uploader + */ + _tick: function() { + var me = this, + opts = me.options, + fn, val; + + // 上一个promise还没有结束,则等待完成后再执行。 + if ( me._promise ) { + return me._promise.always( me.__tick ); + } + + // 还有位置,且还有文件要处理的话。 + if ( me.pool.length < opts.threads && (val = me._nextBlock()) ) { + me._trigged = false; + + fn = function( val ) { + me._promise = null; + + // 有可能是reject过来的,所以要检测val的类型。 + val && val.file && me._startSend( val ); + Base.nextTick( me.__tick ); + }; + + me._promise = isPromise( val ) ? val.always( fn ) : fn( val ); + + // 没有要上传的了,且没有正在传输的了。 + } else if ( !me.remaning && !me._getStats().numOfQueue && + !me._getStats().numofInterrupt ) { + me.runing = false; + + me._trigged || Base.nextTick(function() { + me.owner.trigger('uploadFinished'); + }); + me._trigged = true; + } + }, + + _putback: function(block) { + var idx; + + block.cuted.unshift(block); + idx = this.stack.indexOf(block.cuted); + + if (!~idx) { + this.stack.unshift(block.cuted); + } + }, + + _getStack: function() { + var i = 0, + act; + + while ( (act = this.stack[ i++ ]) ) { + if ( act.has() && act.file.getStatus() === Status.PROGRESS ) { + return act; + } else if (!act.has() || + act.file.getStatus() !== Status.PROGRESS && + act.file.getStatus() !== Status.INTERRUPT ) { + + // 把已经处理完了的,或者,状态为非 progress(上传中)、 + // interupt(暂停中) 的移除。 + this.stack.splice( --i, 1 ); + } + } + + return null; + }, + + _nextBlock: function() { + var me = this, + opts = me.options, + act, next, done, preparing; + + // 如果当前文件还有没有需要传输的,则直接返回剩下的。 + if ( (act = this._getStack()) ) { + + // 是否提前准备下一个文件 + if ( opts.prepareNextFile && !me.pending.length ) { + me._prepareNextFile(); + } + + return act.shift(); + + // 否则,如果正在运行,则准备下一个文件,并等待完成后返回下个分片。 + } else if ( me.runing ) { + + // 如果缓存中有,则直接在缓存中取,没有则去queue中取。 + if ( !me.pending.length && me._getStats().numOfQueue ) { + me._prepareNextFile(); + } + + next = me.pending.shift(); + done = function( file ) { + if ( !file ) { + return null; + } + + act = CuteFile( file, opts.chunked ? opts.chunkSize : 0 ); + me.stack.push(act); + return act.shift(); + }; + + // 文件可能还在prepare中,也有可能已经完全准备好了。 + if ( isPromise( next) ) { + preparing = next.file; + next = next[ next.pipe ? 'pipe' : 'then' ]( done ); + next.file = preparing; + return next; + } + + return done( next ); + } + }, + + + /** + * @event uploadStart + * @param {File} file File对象 + * @description 某个文件开始上传前触发,一个文件只会触发一次。 + * @for Uploader + */ + _prepareNextFile: function() { + var me = this, + file = me.request('fetch-file'), + pending = me.pending, + promise; + + if ( file ) { + promise = me.request( 'before-send-file', file, function() { + + // 有可能文件被skip掉了。文件被skip掉后,状态坑定不是Queued. + if ( file.getStatus() === Status.PROGRESS || + file.getStatus() === Status.INTERRUPT ) { + return file; + } + + return me._finishFile( file ); + }); + + me.owner.trigger( 'uploadStart', file ); + file.setStatus( Status.PROGRESS ); + + promise.file = file; + + // 如果还在pending中,则替换成文件本身。 + promise.done(function() { + var idx = $.inArray( promise, pending ); + + ~idx && pending.splice( idx, 1, file ); + }); + + // befeore-send-file的钩子就有错误发生。 + promise.fail(function( reason ) { + file.setStatus( Status.ERROR, reason ); + me.owner.trigger( 'uploadError', file, reason ); + me.owner.trigger( 'uploadComplete', file ); + }); + + pending.push( promise ); + } + }, + + // 让出位置了,可以让其他分片开始上传 + _popBlock: function( block ) { + var idx = $.inArray( block, this.pool ); + + this.pool.splice( idx, 1 ); + block.file.remaning--; + this.remaning--; + }, + + // 开始上传,可以被掉过。如果promise被reject了,则表示跳过此分片。 + _startSend: function( block ) { + var me = this, + file = block.file, + promise; + + // 有可能在 before-send-file 的 promise 期间改变了文件状态。 + // 如:暂停,取消 + // 我们不能中断 promise, 但是可以在 promise 完后,不做上传操作。 + if ( file.getStatus() !== Status.PROGRESS ) { + + // 如果是中断,则还需要放回去。 + if (file.getStatus() === Status.INTERRUPT) { + me._putback(block); + } + + return; + } + + me.pool.push( block ); + me.remaning++; + + // 如果没有分片,则直接使用原始的。 + // 不会丢失content-type信息。 + block.blob = block.chunks === 1 ? file.source : + file.source.slice( block.start, block.end ); + + // hook, 每个分片发送之前可能要做些异步的事情。 + promise = me.request( 'before-send', block, function() { + + // 有可能文件已经上传出错了,所以不需要再传输了。 + if ( file.getStatus() === Status.PROGRESS ) { + me._doSend( block ); + } else { + me._popBlock( block ); + Base.nextTick( me.__tick ); + } + }); + + // 如果为fail了,则跳过此分片。 + promise.fail(function() { + if ( file.remaning === 1 ) { + me._finishFile( file ).always(function() { + block.percentage = 1; + me._popBlock( block ); + me.owner.trigger( 'uploadComplete', file ); + Base.nextTick( me.__tick ); + }); + } else { + block.percentage = 1; + me.updateFileProgress( file ); + me._popBlock( block ); + Base.nextTick( me.__tick ); + } + }); + }, + + + /** + * @event uploadBeforeSend + * @param {Object} object + * @param {Object} data 默认的上传参数,可以扩展此对象来控制上传参数。 + * @param {Object} headers 可以扩展此对象来控制上传头部。 + * @description 当某个文件的分块在发送前触发,主要用来询问是否要添加附带参数,大文件在开起分片上传的前提下此事件可能会触发多次。 + * @for Uploader + */ + + /** + * @event uploadAccept + * @param {Object} object + * @param {Object} ret 服务端的返回数据,json格式,如果服务端不是json格式,从ret._raw中取数据,自行解析。 + * @description 当某个文件上传到服务端响应后,会派送此事件来询问服务端响应是否有效。如果此事件handler返回值为`false`, 则此文件将派送`server`类型的`uploadError`事件。 + * @for Uploader + */ + + /** + * @event uploadProgress + * @param {File} file File对象 + * @param {Number} percentage 上传进度 + * @description 上传过程中触发,携带上传进度。 + * @for Uploader + */ + + + /** + * @event uploadError + * @param {File} file File对象 + * @param {String} reason 出错的code + * @description 当文件上传出错时触发。 + * @for Uploader + */ + + /** + * @event uploadSuccess + * @param {File} file File对象 + * @param {Object} response 服务端返回的数据 + * @description 当文件上传成功时触发。 + * @for Uploader + */ + + /** + * @event uploadComplete + * @param {File} [file] File对象 + * @description 不管成功或者失败,文件上传完成时触发。 + * @for Uploader + */ + + // 做上传操作。 + _doSend: function( block ) { + var me = this, + owner = me.owner, + opts = me.options, + file = block.file, + tr = new Transport( opts ), + data = $.extend({}, opts.formData ), + headers = $.extend({}, opts.headers ), + requestAccept, ret; + + block.transport = tr; + + tr.on( 'destroy', function() { + delete block.transport; + me._popBlock( block ); + Base.nextTick( me.__tick ); + }); + + // 广播上传进度。以文件为单位。 + tr.on( 'progress', function( percentage ) { + block.percentage = percentage; + me.updateFileProgress( file ); + }); + + // 用来询问,是否返回的结果是有错误的。 + requestAccept = function( reject ) { + var fn; + + ret = tr.getResponseAsJson() || {}; + ret._raw = tr.getResponse(); + fn = function( value ) { + reject = value; + }; + + // 服务端响应了,不代表成功了,询问是否响应正确。 + if ( !owner.trigger( 'uploadAccept', block, ret, fn ) ) { + reject = reject || 'server'; + } + + return reject; + }; + + // 尝试重试,然后广播文件上传出错。 + tr.on( 'error', function( type, flag ) { + block.retried = block.retried || 0; + + // 自动重试 + if ( block.chunks > 1 && ~'http,abort'.indexOf( type ) && + block.retried < opts.chunkRetry ) { + + block.retried++; + tr.send(); + + } else { + + // http status 500 ~ 600 + if ( !flag && type === 'server' ) { + type = requestAccept( type ); + } + + file.setStatus( Status.ERROR, type ); + owner.trigger( 'uploadError', file, type ); + owner.trigger( 'uploadComplete', file ); + } + }); + + // 上传成功 + tr.on( 'load', function() { + var reason; + + // 如果非预期,转向上传出错。 + if ( (reason = requestAccept()) ) { + tr.trigger( 'error', reason, true ); + return; + } + + // 全部上传完成。 + if ( file.remaning === 1 ) { + me._finishFile( file, ret ); + } else { + tr.destroy(); + } + }); + + // 配置默认的上传字段。 + data = $.extend( data, { + id: file.id, + name: file.name, + type: file.type, + lastModifiedDate: file.lastModifiedDate, + size: file.size + }); + + block.chunks > 1 && $.extend( data, { + chunks: block.chunks, + chunk: block.chunk + }); + + // 在发送之间可以添加字段什么的。。。 + // 如果默认的字段不够使用,可以通过监听此事件来扩展 + owner.trigger( 'uploadBeforeSend', block, data, headers ); + + // 开始发送。 + tr.appendBlob( opts.fileVal, block.blob, file.name ); + tr.append( data ); + tr.setRequestHeader( headers ); + tr.send(); + }, + + // 完成上传。 + _finishFile: function( file, ret, hds ) { + var owner = this.owner; + + return owner + .request( 'after-send-file', arguments, function() { + file.setStatus( Status.COMPLETE ); + owner.trigger( 'uploadSuccess', file, ret, hds ); + }) + .fail(function( reason ) { + + // 如果外部已经标记为invalid什么的,不再改状态。 + if ( file.getStatus() === Status.PROGRESS ) { + file.setStatus( Status.ERROR, reason ); + } + + owner.trigger( 'uploadError', file, reason ); + }) + .always(function() { + owner.trigger( 'uploadComplete', file ); + }); + }, + + updateFileProgress: function(file) { + var totalPercent = 0, + uploaded = 0; + + if (!file.blocks) { + return; + } + + $.each( file.blocks, function( _, v ) { + uploaded += (v.percentage || 0) * (v.end - v.start); + }); + + totalPercent = uploaded / file.size; + this.owner.trigger( 'uploadProgress', file, totalPercent || 0 ); + } + + }); + }); + /** + * @fileOverview 各种验证,包括文件总大小是否超出、单文件是否超出和文件是否重复。 + */ + + define('widgets/validator',[ + 'base', + 'uploader', + 'file', + 'widgets/widget' + ], function( Base, Uploader, WUFile ) { + + var $ = Base.$, + validators = {}, + api; + + /** + * @event error + * @param {String} type 错误类型。 + * @description 当validate不通过时,会以派送错误事件的形式通知调用者。通过`upload.on('error', handler)`可以捕获到此类错误,目前有以下错误会在特定的情况下派送错来。 + * + * * `Q_EXCEED_NUM_LIMIT` 在设置了`fileNumLimit`且尝试给`uploader`添加的文件数量超出这个值时派送。 + * * `Q_EXCEED_SIZE_LIMIT` 在设置了`Q_EXCEED_SIZE_LIMIT`且尝试给`uploader`添加的文件总大小超出这个值时派送。 + * * `Q_TYPE_DENIED` 当文件类型不满足时触发。。 + * @for Uploader + */ + + // 暴露给外面的api + api = { + + // 添加验证器 + addValidator: function( type, cb ) { + validators[ type ] = cb; + }, + + // 移除验证器 + removeValidator: function( type ) { + delete validators[ type ]; + } + }; + + // 在Uploader初始化的时候启动Validators的初始化 + Uploader.register({ + name: 'validator', + + init: function() { + var me = this; + Base.nextTick(function() { + $.each( validators, function() { + this.call( me.owner ); + }); + }); + } + }); + + /** + * @property {int} [fileNumLimit=undefined] + * @namespace options + * @for Uploader + * @description 验证文件总数量, 超出则不允许加入队列。 + */ + api.addValidator( 'fileNumLimit', function() { + var uploader = this, + opts = uploader.options, + count = 0, + max = parseInt( opts.fileNumLimit, 10 ), + flag = true; + + if ( !max ) { + return; + } + + uploader.on( 'beforeFileQueued', function( file ) { + + if ( count >= max && flag ) { + flag = false; + this.trigger( 'error', 'Q_EXCEED_NUM_LIMIT', max, file ); + setTimeout(function() { + flag = true; + }, 1 ); + } + + return count >= max ? false : true; + }); + + uploader.on( 'fileQueued', function() { + count++; + }); + + uploader.on( 'fileDequeued', function() { + count--; + }); + + uploader.on( 'reset', function() { + count = 0; + }); + }); + + + /** + * @property {int} [fileSizeLimit=undefined] + * @namespace options + * @for Uploader + * @description 验证文件总大小是否超出限制, 超出则不允许加入队列。 + */ + api.addValidator( 'fileSizeLimit', function() { + var uploader = this, + opts = uploader.options, + count = 0, + max = parseInt( opts.fileSizeLimit, 10 ), + flag = true; + + if ( !max ) { + return; + } + + uploader.on( 'beforeFileQueued', function( file ) { + var invalid = count + file.size > max; + + if ( invalid && flag ) { + flag = false; + this.trigger( 'error', 'Q_EXCEED_SIZE_LIMIT', max, file ); + setTimeout(function() { + flag = true; + }, 1 ); + } + + return invalid ? false : true; + }); + + uploader.on( 'fileQueued', function( file ) { + count += file.size; + }); + + uploader.on( 'fileDequeued', function( file ) { + count -= file.size; + }); + + uploader.on( 'reset', function() { + count = 0; + }); + }); + + /** + * @property {int} [fileSingleSizeLimit=undefined] + * @namespace options + * @for Uploader + * @description 验证单个文件大小是否超出限制, 超出则不允许加入队列。 + */ + api.addValidator( 'fileSingleSizeLimit', function() { + var uploader = this, + opts = uploader.options, + max = opts.fileSingleSizeLimit; + + if ( !max ) { + return; + } + + uploader.on( 'beforeFileQueued', function( file ) { + + if ( file.size > max ) { + file.setStatus( WUFile.Status.INVALID, 'exceed_size' ); + this.trigger( 'error', 'F_EXCEED_SIZE', max, file ); + return false; + } + + }); + + }); + + /** + * @property {Boolean} [duplicate=undefined] + * @namespace options + * @for Uploader + * @description 去重, 根据文件名字、文件大小和最后修改时间来生成hash Key. + */ + api.addValidator( 'duplicate', function() { + var uploader = this, + opts = uploader.options, + mapping = {}; + + if ( opts.duplicate ) { + return; + } + + function hashString( str ) { + var hash = 0, + i = 0, + len = str.length, + _char; + + for ( ; i < len; i++ ) { + _char = str.charCodeAt( i ); + hash = _char + (hash << 6) + (hash << 16) - hash; + } + + return hash; + } + + uploader.on( 'beforeFileQueued', function( file ) { + var hash = file.__hash || (file.__hash = hashString( file.name + + file.size + file.lastModifiedDate )); + + // 已经重复了 + if ( mapping[ hash ] ) { + this.trigger( 'error', 'F_DUPLICATE', file ); + return false; + } + }); + + uploader.on( 'fileQueued', function( file ) { + var hash = file.__hash; + + hash && (mapping[ hash ] = true); + }); + + uploader.on( 'fileDequeued', function( file ) { + var hash = file.__hash; + + hash && (delete mapping[ hash ]); + }); + + uploader.on( 'reset', function() { + mapping = {}; + }); + }); + + return api; + }); + + /** + * @fileOverview Md5 + */ + define('lib/md5',[ + 'runtime/client', + 'mediator' + ], function( RuntimeClient, Mediator ) { + + function Md5() { + RuntimeClient.call( this, 'Md5' ); + } + + // 让 Md5 具备事件功能。 + Mediator.installTo( Md5.prototype ); + + Md5.prototype.loadFromBlob = function( blob ) { + var me = this; + + if ( me.getRuid() ) { + me.disconnectRuntime(); + } + + // 连接到blob归属的同一个runtime. + me.connectRuntime( blob.ruid, function() { + me.exec('init'); + me.exec( 'loadFromBlob', blob ); + }); + }; + + Md5.prototype.getResult = function() { + return this.exec('getResult'); + }; + + return Md5; + }); + /** + * @fileOverview 图片操作, 负责预览图片和上传前压缩图片 + */ + define('widgets/md5',[ + 'base', + 'uploader', + 'lib/md5', + 'lib/blob', + 'widgets/widget' + ], function( Base, Uploader, Md5, Blob ) { + + return Uploader.register({ + name: 'md5', + + + /** + * 计算文件 md5 值,返回一个 promise 对象,可以监听 progress 进度。 + * + * + * @method md5File + * @grammar md5File( file[, start[, end]] ) => promise + * @for Uploader + * @example + * + * uploader.on( 'fileQueued', function( file ) { + * var $li = ...; + * + * uploader.md5File( file ) + * + * // 及时显示进度 + * .progress(function(percentage) { + * console.log('Percentage:', percentage); + * }) + * + * // 完成 + * .then(function(val) { + * console.log('md5 result:', val); + * }); + * + * }); + */ + md5File: function( file, start, end ) { + var md5 = new Md5(), + deferred = Base.Deferred(), + blob = (file instanceof Blob) ? file : + this.request( 'get-file', file ).source; + + md5.on( 'progress load', function( e ) { + e = e || {}; + deferred.notify( e.total ? e.loaded / e.total : 1 ); + }); + + md5.on( 'complete', function() { + deferred.resolve( md5.getResult() ); + }); + + md5.on( 'error', function( reason ) { + deferred.reject( reason ); + }); + + if ( arguments.length > 1 ) { + start = start || 0; + end = end || 0; + start < 0 && (start = blob.size + start); + end < 0 && (end = blob.size + end); + end = Math.min( end, blob.size ); + blob = blob.slice( start, end ); + } + + md5.loadFromBlob( blob ); + + return deferred.promise(); + } + }); + }); + /** + * @fileOverview Runtime管理器,负责Runtime的选择, 连接 + */ + define('runtime/compbase',[],function() { + + function CompBase( owner, runtime ) { + + this.owner = owner; + this.options = owner.options; + + this.getRuntime = function() { + return runtime; + }; + + this.getRuid = function() { + return runtime.uid; + }; + + this.trigger = function() { + return owner.trigger.apply( owner, arguments ); + }; + } + + return CompBase; + }); + /** + * @fileOverview Html5Runtime + */ + define('runtime/html5/runtime',[ + 'base', + 'runtime/runtime', + 'runtime/compbase' + ], function( Base, Runtime, CompBase ) { + + var type = 'html5', + components = {}; + + function Html5Runtime() { + var pool = {}, + me = this, + destroy = this.destroy; + + Runtime.apply( me, arguments ); + me.type = type; + + + // 这个方法的调用者,实际上是RuntimeClient + me.exec = function( comp, fn/*, args...*/) { + var client = this, + uid = client.uid, + args = Base.slice( arguments, 2 ), + instance; + + if ( components[ comp ] ) { + instance = pool[ uid ] = pool[ uid ] || + new components[ comp ]( client, me ); + + if ( instance[ fn ] ) { + return instance[ fn ].apply( instance, args ); + } + } + }; + + me.destroy = function() { + // @todo 删除池子中的所有实例 + return destroy && destroy.apply( this, arguments ); + }; + } + + Base.inherits( Runtime, { + constructor: Html5Runtime, + + // 不需要连接其他程序,直接执行callback + init: function() { + var me = this; + setTimeout(function() { + me.trigger('ready'); + }, 1 ); + } + + }); + + // 注册Components + Html5Runtime.register = function( name, component ) { + var klass = components[ name ] = Base.inherits( CompBase, component ); + return klass; + }; + + // 注册html5运行时。 + // 只有在支持的前提下注册。 + if ( window.Blob && window.FileReader && window.DataView ) { + Runtime.addRuntime( type, Html5Runtime ); + } + + return Html5Runtime; + }); + /** + * @fileOverview Blob Html实现 + */ + define('runtime/html5/blob',[ + 'runtime/html5/runtime', + 'lib/blob' + ], function( Html5Runtime, Blob ) { + + return Html5Runtime.register( 'Blob', { + slice: function( start, end ) { + var blob = this.owner.source, + slice = blob.slice || blob.webkitSlice || blob.mozSlice; + + blob = slice.call( blob, start, end ); + + return new Blob( this.getRuid(), blob ); + } + }); + }); + /** + * @fileOverview FilePaste + */ + define('runtime/html5/dnd',[ + 'base', + 'runtime/html5/runtime', + 'lib/file' + ], function( Base, Html5Runtime, File ) { + + var $ = Base.$, + prefix = 'webuploader-dnd-'; + + return Html5Runtime.register( 'DragAndDrop', { + init: function() { + var elem = this.elem = this.options.container; + + this.dragEnterHandler = Base.bindFn( this._dragEnterHandler, this ); + this.dragOverHandler = Base.bindFn( this._dragOverHandler, this ); + this.dragLeaveHandler = Base.bindFn( this._dragLeaveHandler, this ); + this.dropHandler = Base.bindFn( this._dropHandler, this ); + this.dndOver = false; + + elem.on( 'dragenter', this.dragEnterHandler ); + elem.on( 'dragover', this.dragOverHandler ); + elem.on( 'dragleave', this.dragLeaveHandler ); + elem.on( 'drop', this.dropHandler ); + + if ( this.options.disableGlobalDnd ) { + $( document ).on( 'dragover', this.dragOverHandler ); + $( document ).on( 'drop', this.dropHandler ); + } + }, + + _dragEnterHandler: function( e ) { + var me = this, + denied = me._denied || false, + items; + + e = e.originalEvent || e; + + if ( !me.dndOver ) { + me.dndOver = true; + + // 注意只有 chrome 支持。 + items = e.dataTransfer.items; + + if ( items && items.length ) { + me._denied = denied = !me.trigger( 'accept', items ); + } + + me.elem.addClass( prefix + 'over' ); + me.elem[ denied ? 'addClass' : + 'removeClass' ]( prefix + 'denied' ); + } + + e.dataTransfer.dropEffect = denied ? 'none' : 'copy'; + + return false; + }, + + _dragOverHandler: function( e ) { + // 只处理框内的。 + var parentElem = this.elem.parent().get( 0 ); + if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) { + return false; + } + + clearTimeout( this._leaveTimer ); + this._dragEnterHandler.call( this, e ); + + return false; + }, + + _dragLeaveHandler: function() { + var me = this, + handler; + + handler = function() { + me.dndOver = false; + me.elem.removeClass( prefix + 'over ' + prefix + 'denied' ); + }; + + clearTimeout( me._leaveTimer ); + me._leaveTimer = setTimeout( handler, 100 ); + return false; + }, + + _dropHandler: function( e ) { + var me = this, + ruid = me.getRuid(), + parentElem = me.elem.parent().get( 0 ), + dataTransfer, data; + + // 只处理框内的。 + if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) { + return false; + } + + e = e.originalEvent || e; + dataTransfer = e.dataTransfer; + + // 如果是页面内拖拽,还不能处理,不阻止事件。 + // 此处 ie11 下会报参数错误, + try { + data = dataTransfer.getData('text/html'); + } catch( err ) { + } + + if ( data ) { + return; + } + + me._getTansferFiles( dataTransfer, function( results ) { + me.trigger( 'drop', $.map( results, function( file ) { + return new File( ruid, file ); + }) ); + }); + + me.dndOver = false; + me.elem.removeClass( prefix + 'over' ); + return false; + }, + + // 如果传入 callback 则去查看文件夹,否则只管当前文件夹。 + _getTansferFiles: function( dataTransfer, callback ) { + var results = [], + promises = [], + items, files, file, item, i, len, canAccessFolder; + + items = dataTransfer.items; + files = dataTransfer.files; + + canAccessFolder = !!(items && items[ 0 ].webkitGetAsEntry); + + for ( i = 0, len = files.length; i < len; i++ ) { + file = files[ i ]; + item = items && items[ i ]; + + if ( canAccessFolder && item.webkitGetAsEntry().isDirectory ) { + + promises.push( this._traverseDirectoryTree( + item.webkitGetAsEntry(), results ) ); + } else { + results.push( file ); + } + } + + Base.when.apply( Base, promises ).done(function() { + + if ( !results.length ) { + return; + } + + callback( results ); + }); + }, + + _traverseDirectoryTree: function( entry, results ) { + var deferred = Base.Deferred(), + me = this; + + if ( entry.isFile ) { + entry.file(function( file ) { + results.push( file ); + deferred.resolve(); + }); + } else if ( entry.isDirectory ) { + entry.createReader().readEntries(function( entries ) { + var len = entries.length, + promises = [], + arr = [], // 为了保证顺序。 + i; + + for ( i = 0; i < len; i++ ) { + promises.push( me._traverseDirectoryTree( + entries[ i ], arr ) ); + } + + Base.when.apply( Base, promises ).then(function() { + results.push.apply( results, arr ); + deferred.resolve(); + }, deferred.reject ); + }); + } + + return deferred.promise(); + }, + + destroy: function() { + var elem = this.elem; + + // 还没 init 就调用 destroy + if (!elem) { + return; + } + + elem.off( 'dragenter', this.dragEnterHandler ); + elem.off( 'dragover', this.dragOverHandler ); + elem.off( 'dragleave', this.dragLeaveHandler ); + elem.off( 'drop', this.dropHandler ); + + if ( this.options.disableGlobalDnd ) { + $( document ).off( 'dragover', this.dragOverHandler ); + $( document ).off( 'drop', this.dropHandler ); + } + } + }); + }); + + /** + * @fileOverview FilePaste + */ + define('runtime/html5/filepaste',[ + 'base', + 'runtime/html5/runtime', + 'lib/file' + ], function( Base, Html5Runtime, File ) { + + return Html5Runtime.register( 'FilePaste', { + init: function() { + var opts = this.options, + elem = this.elem = opts.container, + accept = '.*', + arr, i, len, item; + + // accetp的mimeTypes中生成匹配正则。 + if ( opts.accept ) { + arr = []; + + for ( i = 0, len = opts.accept.length; i < len; i++ ) { + item = opts.accept[ i ].mimeTypes; + item && arr.push( item ); + } + + if ( arr.length ) { + accept = arr.join(','); + accept = accept.replace( /,/g, '|' ).replace( /\*/g, '.*' ); + } + } + this.accept = accept = new RegExp( accept, 'i' ); + this.hander = Base.bindFn( this._pasteHander, this ); + elem.on( 'paste', this.hander ); + }, + + _pasteHander: function( e ) { + var allowed = [], + ruid = this.getRuid(), + items, item, blob, i, len; + + e = e.originalEvent || e; + items = e.clipboardData.items; + + for ( i = 0, len = items.length; i < len; i++ ) { + item = items[ i ]; + + if ( item.kind !== 'file' || !(blob = item.getAsFile()) ) { + continue; + } + + allowed.push( new File( ruid, blob ) ); + } + + if ( allowed.length ) { + // 不阻止非文件粘贴(文字粘贴)的事件冒泡 + e.preventDefault(); + e.stopPropagation(); + this.trigger( 'paste', allowed ); + } + }, + + destroy: function() { + this.elem.off( 'paste', this.hander ); + } + }); + }); + + /** + * @fileOverview FilePicker + */ + define('runtime/html5/filepicker',[ + 'base', + 'runtime/html5/runtime' + ], function( Base, Html5Runtime ) { + + var $ = Base.$; + + return Html5Runtime.register( 'FilePicker', { + init: function() { + var container = this.getRuntime().getContainer(), + me = this, + owner = me.owner, + opts = me.options, + label = this.label = $( document.createElement('label') ), + input = this.input = $( document.createElement('input') ), + arr, i, len, mouseHandler; + + input.attr( 'type', 'file' ); + input.attr( 'name', opts.name ); + input.addClass('webuploader-element-invisible'); + + label.on( 'click', function() { + input.trigger('click'); + }); + + label.css({ + opacity: 0, + width: '100%', + height: '100%', + display: 'block', + cursor: 'pointer', + background: '#ffffff' + }); + + if ( opts.multiple ) { + input.attr( 'multiple', 'multiple' ); + } + + // @todo Firefox不支持单独指定后缀 + if ( opts.accept && opts.accept.length > 0 ) { + arr = []; + + for ( i = 0, len = opts.accept.length; i < len; i++ ) { + arr.push( opts.accept[ i ].mimeTypes ); + } + + input.attr( 'accept', arr.join(',') ); + } + + container.append( input ); + container.append( label ); + + mouseHandler = function( e ) { + owner.trigger( e.type ); + }; + + input.on( 'change', function( e ) { + var fn = arguments.callee, + clone; + + me.files = e.target.files; + + // reset input + clone = this.cloneNode( true ); + clone.value = null; + this.parentNode.replaceChild( clone, this ); + + input.off(); + input = $( clone ).on( 'change', fn ) + .on( 'mouseenter mouseleave', mouseHandler ); + + owner.trigger('change'); + }); + + label.on( 'mouseenter mouseleave', mouseHandler ); + + }, + + + getFiles: function() { + return this.files; + }, + + destroy: function() { + this.input.off(); + this.label.off(); + } + }); + }); + /** + * Terms: + * + * Uint8Array, FileReader, BlobBuilder, atob, ArrayBuffer + * @fileOverview Image控件 + */ + define('runtime/html5/util',[ + 'base' + ], function( Base ) { + + var urlAPI = window.createObjectURL && window || + window.URL && URL.revokeObjectURL && URL || + window.webkitURL, + createObjectURL = Base.noop, + revokeObjectURL = createObjectURL; + + if ( urlAPI ) { + + // 更安全的方式调用,比如android里面就能把context改成其他的对象。 + createObjectURL = function() { + return urlAPI.createObjectURL.apply( urlAPI, arguments ); + }; + + revokeObjectURL = function() { + return urlAPI.revokeObjectURL.apply( urlAPI, arguments ); + }; + } + + return { + createObjectURL: createObjectURL, + revokeObjectURL: revokeObjectURL, + + dataURL2Blob: function( dataURI ) { + var byteStr, intArray, ab, i, mimetype, parts; + + parts = dataURI.split(','); + + if ( ~parts[ 0 ].indexOf('base64') ) { + byteStr = atob( parts[ 1 ] ); + } else { + byteStr = decodeURIComponent( parts[ 1 ] ); + } + + ab = new ArrayBuffer( byteStr.length ); + intArray = new Uint8Array( ab ); + + for ( i = 0; i < byteStr.length; i++ ) { + intArray[ i ] = byteStr.charCodeAt( i ); + } + + mimetype = parts[ 0 ].split(':')[ 1 ].split(';')[ 0 ]; + + return this.arrayBufferToBlob( ab, mimetype ); + }, + + dataURL2ArrayBuffer: function( dataURI ) { + var byteStr, intArray, i, parts; + + parts = dataURI.split(','); + + if ( ~parts[ 0 ].indexOf('base64') ) { + byteStr = atob( parts[ 1 ] ); + } else { + byteStr = decodeURIComponent( parts[ 1 ] ); + } + + intArray = new Uint8Array( byteStr.length ); + + for ( i = 0; i < byteStr.length; i++ ) { + intArray[ i ] = byteStr.charCodeAt( i ); + } + + return intArray.buffer; + }, + + arrayBufferToBlob: function( buffer, type ) { + var builder = window.BlobBuilder || window.WebKitBlobBuilder, + bb; + + // android不支持直接new Blob, 只能借助blobbuilder. + if ( builder ) { + bb = new builder(); + bb.append( buffer ); + return bb.getBlob( type ); + } + + return new Blob([ buffer ], type ? { type: type } : {} ); + }, + + // 抽出来主要是为了解决android下面canvas.toDataUrl不支持jpeg. + // 你得到的结果是png. + canvasToDataUrl: function( canvas, type, quality ) { + return canvas.toDataURL( type, quality / 100 ); + }, + + // imagemeat会复写这个方法,如果用户选择加载那个文件了的话。 + parseMeta: function( blob, callback ) { + callback( false, {}); + }, + + // imagemeat会复写这个方法,如果用户选择加载那个文件了的话。 + updateImageHead: function( data ) { + return data; + } + }; + }); + /** + * Terms: + * + * Uint8Array, FileReader, BlobBuilder, atob, ArrayBuffer + * @fileOverview Image控件 + */ + define('runtime/html5/imagemeta',[ + 'runtime/html5/util' + ], function( Util ) { + + var api; + + api = { + parsers: { + 0xffe1: [] + }, + + maxMetaDataSize: 262144, + + parse: function( blob, cb ) { + var me = this, + fr = new FileReader(); + + fr.onload = function() { + cb( false, me._parse( this.result ) ); + fr = fr.onload = fr.onerror = null; + }; + + fr.onerror = function( e ) { + cb( e.message ); + fr = fr.onload = fr.onerror = null; + }; + + blob = blob.slice( 0, me.maxMetaDataSize ); + fr.readAsArrayBuffer( blob.getSource() ); + }, + + _parse: function( buffer, noParse ) { + if ( buffer.byteLength < 6 ) { + return; + } + + var dataview = new DataView( buffer ), + offset = 2, + maxOffset = dataview.byteLength - 4, + headLength = offset, + ret = {}, + markerBytes, markerLength, parsers, i; + + if ( dataview.getUint16( 0 ) === 0xffd8 ) { + + while ( offset < maxOffset ) { + markerBytes = dataview.getUint16( offset ); + + if ( markerBytes >= 0xffe0 && markerBytes <= 0xffef || + markerBytes === 0xfffe ) { + + markerLength = dataview.getUint16( offset + 2 ) + 2; + + if ( offset + markerLength > dataview.byteLength ) { + break; + } + + parsers = api.parsers[ markerBytes ]; + + if ( !noParse && parsers ) { + for ( i = 0; i < parsers.length; i += 1 ) { + parsers[ i ].call( api, dataview, offset, + markerLength, ret ); + } + } + + offset += markerLength; + headLength = offset; + } else { + break; + } + } + + if ( headLength > 6 ) { + if ( buffer.slice ) { + ret.imageHead = buffer.slice( 2, headLength ); + } else { + // Workaround for IE10, which does not yet + // support ArrayBuffer.slice: + ret.imageHead = new Uint8Array( buffer ) + .subarray( 2, headLength ); + } + } + } + + return ret; + }, + + updateImageHead: function( buffer, head ) { + var data = this._parse( buffer, true ), + buf1, buf2, bodyoffset; + + + bodyoffset = 2; + if ( data.imageHead ) { + bodyoffset = 2 + data.imageHead.byteLength; + } + + if ( buffer.slice ) { + buf2 = buffer.slice( bodyoffset ); + } else { + buf2 = new Uint8Array( buffer ).subarray( bodyoffset ); + } + + buf1 = new Uint8Array( head.byteLength + 2 + buf2.byteLength ); + + buf1[ 0 ] = 0xFF; + buf1[ 1 ] = 0xD8; + buf1.set( new Uint8Array( head ), 2 ); + buf1.set( new Uint8Array( buf2 ), head.byteLength + 2 ); + + return buf1.buffer; + } + }; + + Util.parseMeta = function() { + return api.parse.apply( api, arguments ); + }; + + Util.updateImageHead = function() { + return api.updateImageHead.apply( api, arguments ); + }; + + return api; + }); + /** + * 代码来自于:https://github.com/blueimp/JavaScript-Load-Image + * 暂时项目中只用了orientation. + * + * 去除了 Exif Sub IFD Pointer, GPS Info IFD Pointer, Exif Thumbnail. + * @fileOverview EXIF解析 + */ + + // Sample + // ==================================== + // Make : Apple + // Model : iPhone 4S + // Orientation : 1 + // XResolution : 72 [72/1] + // YResolution : 72 [72/1] + // ResolutionUnit : 2 + // Software : QuickTime 7.7.1 + // DateTime : 2013:09:01 22:53:55 + // ExifIFDPointer : 190 + // ExposureTime : 0.058823529411764705 [1/17] + // FNumber : 2.4 [12/5] + // ExposureProgram : Normal program + // ISOSpeedRatings : 800 + // ExifVersion : 0220 + // DateTimeOriginal : 2013:09:01 22:52:51 + // DateTimeDigitized : 2013:09:01 22:52:51 + // ComponentsConfiguration : YCbCr + // ShutterSpeedValue : 4.058893515764426 + // ApertureValue : 2.5260688216892597 [4845/1918] + // BrightnessValue : -0.3126686601998395 + // MeteringMode : Pattern + // Flash : Flash did not fire, compulsory flash mode + // FocalLength : 4.28 [107/25] + // SubjectArea : [4 values] + // FlashpixVersion : 0100 + // ColorSpace : 1 + // PixelXDimension : 2448 + // PixelYDimension : 3264 + // SensingMethod : One-chip color area sensor + // ExposureMode : 0 + // WhiteBalance : Auto white balance + // FocalLengthIn35mmFilm : 35 + // SceneCaptureType : Standard + define('runtime/html5/imagemeta/exif',[ + 'base', + 'runtime/html5/imagemeta' + ], function( Base, ImageMeta ) { + + var EXIF = {}; + + EXIF.ExifMap = function() { + return this; + }; + + EXIF.ExifMap.prototype.map = { + 'Orientation': 0x0112 + }; + + EXIF.ExifMap.prototype.get = function( id ) { + return this[ id ] || this[ this.map[ id ] ]; + }; + + EXIF.exifTagTypes = { + // byte, 8-bit unsigned int: + 1: { + getValue: function( dataView, dataOffset ) { + return dataView.getUint8( dataOffset ); + }, + size: 1 + }, + + // ascii, 8-bit byte: + 2: { + getValue: function( dataView, dataOffset ) { + return String.fromCharCode( dataView.getUint8( dataOffset ) ); + }, + size: 1, + ascii: true + }, + + // short, 16 bit int: + 3: { + getValue: function( dataView, dataOffset, littleEndian ) { + return dataView.getUint16( dataOffset, littleEndian ); + }, + size: 2 + }, + + // long, 32 bit int: + 4: { + getValue: function( dataView, dataOffset, littleEndian ) { + return dataView.getUint32( dataOffset, littleEndian ); + }, + size: 4 + }, + + // rational = two long values, + // first is numerator, second is denominator: + 5: { + getValue: function( dataView, dataOffset, littleEndian ) { + return dataView.getUint32( dataOffset, littleEndian ) / + dataView.getUint32( dataOffset + 4, littleEndian ); + }, + size: 8 + }, + + // slong, 32 bit signed int: + 9: { + getValue: function( dataView, dataOffset, littleEndian ) { + return dataView.getInt32( dataOffset, littleEndian ); + }, + size: 4 + }, + + // srational, two slongs, first is numerator, second is denominator: + 10: { + getValue: function( dataView, dataOffset, littleEndian ) { + return dataView.getInt32( dataOffset, littleEndian ) / + dataView.getInt32( dataOffset + 4, littleEndian ); + }, + size: 8 + } + }; + + // undefined, 8-bit byte, value depending on field: + EXIF.exifTagTypes[ 7 ] = EXIF.exifTagTypes[ 1 ]; + + EXIF.getExifValue = function( dataView, tiffOffset, offset, type, length, + littleEndian ) { + + var tagType = EXIF.exifTagTypes[ type ], + tagSize, dataOffset, values, i, str, c; + + if ( !tagType ) { + Base.log('Invalid Exif data: Invalid tag type.'); + return; + } + + tagSize = tagType.size * length; + + // Determine if the value is contained in the dataOffset bytes, + // or if the value at the dataOffset is a pointer to the actual data: + dataOffset = tagSize > 4 ? tiffOffset + dataView.getUint32( offset + 8, + littleEndian ) : (offset + 8); + + if ( dataOffset + tagSize > dataView.byteLength ) { + Base.log('Invalid Exif data: Invalid data offset.'); + return; + } + + if ( length === 1 ) { + return tagType.getValue( dataView, dataOffset, littleEndian ); + } + + values = []; + + for ( i = 0; i < length; i += 1 ) { + values[ i ] = tagType.getValue( dataView, + dataOffset + i * tagType.size, littleEndian ); + } + + if ( tagType.ascii ) { + str = ''; + + // Concatenate the chars: + for ( i = 0; i < values.length; i += 1 ) { + c = values[ i ]; + + // Ignore the terminating NULL byte(s): + if ( c === '\u0000' ) { + break; + } + str += c; + } + + return str; + } + return values; + }; + + EXIF.parseExifTag = function( dataView, tiffOffset, offset, littleEndian, + data ) { + + var tag = dataView.getUint16( offset, littleEndian ); + data.exif[ tag ] = EXIF.getExifValue( dataView, tiffOffset, offset, + dataView.getUint16( offset + 2, littleEndian ), // tag type + dataView.getUint32( offset + 4, littleEndian ), // tag length + littleEndian ); + }; + + EXIF.parseExifTags = function( dataView, tiffOffset, dirOffset, + littleEndian, data ) { + + var tagsNumber, dirEndOffset, i; + + if ( dirOffset + 6 > dataView.byteLength ) { + Base.log('Invalid Exif data: Invalid directory offset.'); + return; + } + + tagsNumber = dataView.getUint16( dirOffset, littleEndian ); + dirEndOffset = dirOffset + 2 + 12 * tagsNumber; + + if ( dirEndOffset + 4 > dataView.byteLength ) { + Base.log('Invalid Exif data: Invalid directory size.'); + return; + } + + for ( i = 0; i < tagsNumber; i += 1 ) { + this.parseExifTag( dataView, tiffOffset, + dirOffset + 2 + 12 * i, // tag offset + littleEndian, data ); + } + + // Return the offset to the next directory: + return dataView.getUint32( dirEndOffset, littleEndian ); + }; + + // EXIF.getExifThumbnail = function(dataView, offset, length) { + // var hexData, + // i, + // b; + // if (!length || offset + length > dataView.byteLength) { + // Base.log('Invalid Exif data: Invalid thumbnail data.'); + // return; + // } + // hexData = []; + // for (i = 0; i < length; i += 1) { + // b = dataView.getUint8(offset + i); + // hexData.push((b < 16 ? '0' : '') + b.toString(16)); + // } + // return 'data:image/jpeg,%' + hexData.join('%'); + // }; + + EXIF.parseExifData = function( dataView, offset, length, data ) { + + var tiffOffset = offset + 10, + littleEndian, dirOffset; + + // Check for the ASCII code for "Exif" (0x45786966): + if ( dataView.getUint32( offset + 4 ) !== 0x45786966 ) { + // No Exif data, might be XMP data instead + return; + } + if ( tiffOffset + 8 > dataView.byteLength ) { + Base.log('Invalid Exif data: Invalid segment size.'); + return; + } + + // Check for the two null bytes: + if ( dataView.getUint16( offset + 8 ) !== 0x0000 ) { + Base.log('Invalid Exif data: Missing byte alignment offset.'); + return; + } + + // Check the byte alignment: + switch ( dataView.getUint16( tiffOffset ) ) { + case 0x4949: + littleEndian = true; + break; + + case 0x4D4D: + littleEndian = false; + break; + + default: + Base.log('Invalid Exif data: Invalid byte alignment marker.'); + return; + } + + // Check for the TIFF tag marker (0x002A): + if ( dataView.getUint16( tiffOffset + 2, littleEndian ) !== 0x002A ) { + Base.log('Invalid Exif data: Missing TIFF marker.'); + return; + } + + // Retrieve the directory offset bytes, usually 0x00000008 or 8 decimal: + dirOffset = dataView.getUint32( tiffOffset + 4, littleEndian ); + // Create the exif object to store the tags: + data.exif = new EXIF.ExifMap(); + // Parse the tags of the main image directory and retrieve the + // offset to the next directory, usually the thumbnail directory: + dirOffset = EXIF.parseExifTags( dataView, tiffOffset, + tiffOffset + dirOffset, littleEndian, data ); + + // 尝试读取缩略图 + // if ( dirOffset ) { + // thumbnailData = {exif: {}}; + // dirOffset = EXIF.parseExifTags( + // dataView, + // tiffOffset, + // tiffOffset + dirOffset, + // littleEndian, + // thumbnailData + // ); + + // // Check for JPEG Thumbnail offset: + // if (thumbnailData.exif[0x0201]) { + // data.exif.Thumbnail = EXIF.getExifThumbnail( + // dataView, + // tiffOffset + thumbnailData.exif[0x0201], + // thumbnailData.exif[0x0202] // Thumbnail data length + // ); + // } + // } + }; + + ImageMeta.parsers[ 0xffe1 ].push( EXIF.parseExifData ); + return EXIF; + }); + /** + * 这个方式性能不行,但是可以解决android里面的toDataUrl的bug + * android里面toDataUrl('image/jpege')得到的结果却是png. + * + * 所以这里没辙,只能借助这个工具 + * @fileOverview jpeg encoder + */ + define('runtime/html5/jpegencoder',[], function( require, exports, module ) { + + /* + Copyright (c) 2008, Adobe Systems Incorporated + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of Adobe Systems Incorporated nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + /* + JPEG encoder ported to JavaScript and optimized by Andreas Ritter, www.bytestrom.eu, 11/2009 + + Basic GUI blocking jpeg encoder + */ + + function JPEGEncoder(quality) { + var self = this; + var fround = Math.round; + var ffloor = Math.floor; + var YTable = new Array(64); + var UVTable = new Array(64); + var fdtbl_Y = new Array(64); + var fdtbl_UV = new Array(64); + var YDC_HT; + var UVDC_HT; + var YAC_HT; + var UVAC_HT; + + var bitcode = new Array(65535); + var category = new Array(65535); + var outputfDCTQuant = new Array(64); + var DU = new Array(64); + var byteout = []; + var bytenew = 0; + var bytepos = 7; + + var YDU = new Array(64); + var UDU = new Array(64); + var VDU = new Array(64); + var clt = new Array(256); + var RGB_YUV_TABLE = new Array(2048); + var currentQuality; + + var ZigZag = [ + 0, 1, 5, 6,14,15,27,28, + 2, 4, 7,13,16,26,29,42, + 3, 8,12,17,25,30,41,43, + 9,11,18,24,31,40,44,53, + 10,19,23,32,39,45,52,54, + 20,22,33,38,46,51,55,60, + 21,34,37,47,50,56,59,61, + 35,36,48,49,57,58,62,63 + ]; + + var std_dc_luminance_nrcodes = [0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0]; + var std_dc_luminance_values = [0,1,2,3,4,5,6,7,8,9,10,11]; + var std_ac_luminance_nrcodes = [0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d]; + var std_ac_luminance_values = [ + 0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12, + 0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07, + 0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08, + 0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0, + 0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16, + 0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28, + 0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39, + 0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49, + 0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59, + 0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69, + 0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79, + 0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89, + 0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98, + 0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7, + 0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6, + 0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5, + 0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4, + 0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2, + 0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea, + 0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8, + 0xf9,0xfa + ]; + + var std_dc_chrominance_nrcodes = [0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0]; + var std_dc_chrominance_values = [0,1,2,3,4,5,6,7,8,9,10,11]; + var std_ac_chrominance_nrcodes = [0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77]; + var std_ac_chrominance_values = [ + 0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21, + 0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71, + 0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91, + 0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0, + 0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34, + 0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26, + 0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38, + 0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48, + 0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58, + 0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68, + 0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78, + 0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87, + 0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96, + 0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5, + 0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4, + 0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3, + 0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2, + 0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda, + 0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9, + 0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8, + 0xf9,0xfa + ]; + + function initQuantTables(sf){ + var YQT = [ + 16, 11, 10, 16, 24, 40, 51, 61, + 12, 12, 14, 19, 26, 58, 60, 55, + 14, 13, 16, 24, 40, 57, 69, 56, + 14, 17, 22, 29, 51, 87, 80, 62, + 18, 22, 37, 56, 68,109,103, 77, + 24, 35, 55, 64, 81,104,113, 92, + 49, 64, 78, 87,103,121,120,101, + 72, 92, 95, 98,112,100,103, 99 + ]; + + for (var i = 0; i < 64; i++) { + var t = ffloor((YQT[i]*sf+50)/100); + if (t < 1) { + t = 1; + } else if (t > 255) { + t = 255; + } + YTable[ZigZag[i]] = t; + } + var UVQT = [ + 17, 18, 24, 47, 99, 99, 99, 99, + 18, 21, 26, 66, 99, 99, 99, 99, + 24, 26, 56, 99, 99, 99, 99, 99, + 47, 66, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99 + ]; + for (var j = 0; j < 64; j++) { + var u = ffloor((UVQT[j]*sf+50)/100); + if (u < 1) { + u = 1; + } else if (u > 255) { + u = 255; + } + UVTable[ZigZag[j]] = u; + } + var aasf = [ + 1.0, 1.387039845, 1.306562965, 1.175875602, + 1.0, 0.785694958, 0.541196100, 0.275899379 + ]; + var k = 0; + for (var row = 0; row < 8; row++) + { + for (var col = 0; col < 8; col++) + { + fdtbl_Y[k] = (1.0 / (YTable [ZigZag[k]] * aasf[row] * aasf[col] * 8.0)); + fdtbl_UV[k] = (1.0 / (UVTable[ZigZag[k]] * aasf[row] * aasf[col] * 8.0)); + k++; + } + } + } + + function computeHuffmanTbl(nrcodes, std_table){ + var codevalue = 0; + var pos_in_table = 0; + var HT = new Array(); + for (var k = 1; k <= 16; k++) { + for (var j = 1; j <= nrcodes[k]; j++) { + HT[std_table[pos_in_table]] = []; + HT[std_table[pos_in_table]][0] = codevalue; + HT[std_table[pos_in_table]][1] = k; + pos_in_table++; + codevalue++; + } + codevalue*=2; + } + return HT; + } + + function initHuffmanTbl() + { + YDC_HT = computeHuffmanTbl(std_dc_luminance_nrcodes,std_dc_luminance_values); + UVDC_HT = computeHuffmanTbl(std_dc_chrominance_nrcodes,std_dc_chrominance_values); + YAC_HT = computeHuffmanTbl(std_ac_luminance_nrcodes,std_ac_luminance_values); + UVAC_HT = computeHuffmanTbl(std_ac_chrominance_nrcodes,std_ac_chrominance_values); + } + + function initCategoryNumber() + { + var nrlower = 1; + var nrupper = 2; + for (var cat = 1; cat <= 15; cat++) { + //Positive numbers + for (var nr = nrlower; nr>0] = 38470 * i; + RGB_YUV_TABLE[(i+ 512)>>0] = 7471 * i + 0x8000; + RGB_YUV_TABLE[(i+ 768)>>0] = -11059 * i; + RGB_YUV_TABLE[(i+1024)>>0] = -21709 * i; + RGB_YUV_TABLE[(i+1280)>>0] = 32768 * i + 0x807FFF; + RGB_YUV_TABLE[(i+1536)>>0] = -27439 * i; + RGB_YUV_TABLE[(i+1792)>>0] = - 5329 * i; + } + } + + // IO functions + function writeBits(bs) + { + var value = bs[0]; + var posval = bs[1]-1; + while ( posval >= 0 ) { + if (value & (1 << posval) ) { + bytenew |= (1 << bytepos); + } + posval--; + bytepos--; + if (bytepos < 0) { + if (bytenew == 0xFF) { + writeByte(0xFF); + writeByte(0); + } + else { + writeByte(bytenew); + } + bytepos=7; + bytenew=0; + } + } + } + + function writeByte(value) + { + byteout.push(clt[value]); // write char directly instead of converting later + } + + function writeWord(value) + { + writeByte((value>>8)&0xFF); + writeByte((value )&0xFF); + } + + // DCT & quantization core + function fDCTQuant(data, fdtbl) + { + var d0, d1, d2, d3, d4, d5, d6, d7; + /* Pass 1: process rows. */ + var dataOff=0; + var i; + var I8 = 8; + var I64 = 64; + for (i=0; i 0.0) ? ((fDCTQuant + 0.5)|0) : ((fDCTQuant - 0.5)|0); + //outputfDCTQuant[i] = fround(fDCTQuant); + + } + return outputfDCTQuant; + } + + function writeAPP0() + { + writeWord(0xFFE0); // marker + writeWord(16); // length + writeByte(0x4A); // J + writeByte(0x46); // F + writeByte(0x49); // I + writeByte(0x46); // F + writeByte(0); // = "JFIF",'\0' + writeByte(1); // versionhi + writeByte(1); // versionlo + writeByte(0); // xyunits + writeWord(1); // xdensity + writeWord(1); // ydensity + writeByte(0); // thumbnwidth + writeByte(0); // thumbnheight + } + + function writeSOF0(width, height) + { + writeWord(0xFFC0); // marker + writeWord(17); // length, truecolor YUV JPG + writeByte(8); // precision + writeWord(height); + writeWord(width); + writeByte(3); // nrofcomponents + writeByte(1); // IdY + writeByte(0x11); // HVY + writeByte(0); // QTY + writeByte(2); // IdU + writeByte(0x11); // HVU + writeByte(1); // QTU + writeByte(3); // IdV + writeByte(0x11); // HVV + writeByte(1); // QTV + } + + function writeDQT() + { + writeWord(0xFFDB); // marker + writeWord(132); // length + writeByte(0); + for (var i=0; i<64; i++) { + writeByte(YTable[i]); + } + writeByte(1); + for (var j=0; j<64; j++) { + writeByte(UVTable[j]); + } + } + + function writeDHT() + { + writeWord(0xFFC4); // marker + writeWord(0x01A2); // length + + writeByte(0); // HTYDCinfo + for (var i=0; i<16; i++) { + writeByte(std_dc_luminance_nrcodes[i+1]); + } + for (var j=0; j<=11; j++) { + writeByte(std_dc_luminance_values[j]); + } + + writeByte(0x10); // HTYACinfo + for (var k=0; k<16; k++) { + writeByte(std_ac_luminance_nrcodes[k+1]); + } + for (var l=0; l<=161; l++) { + writeByte(std_ac_luminance_values[l]); + } + + writeByte(1); // HTUDCinfo + for (var m=0; m<16; m++) { + writeByte(std_dc_chrominance_nrcodes[m+1]); + } + for (var n=0; n<=11; n++) { + writeByte(std_dc_chrominance_values[n]); + } + + writeByte(0x11); // HTUACinfo + for (var o=0; o<16; o++) { + writeByte(std_ac_chrominance_nrcodes[o+1]); + } + for (var p=0; p<=161; p++) { + writeByte(std_ac_chrominance_values[p]); + } + } + + function writeSOS() + { + writeWord(0xFFDA); // marker + writeWord(12); // length + writeByte(3); // nrofcomponents + writeByte(1); // IdY + writeByte(0); // HTY + writeByte(2); // IdU + writeByte(0x11); // HTU + writeByte(3); // IdV + writeByte(0x11); // HTV + writeByte(0); // Ss + writeByte(0x3f); // Se + writeByte(0); // Bf + } + + function processDU(CDU, fdtbl, DC, HTDC, HTAC){ + var EOB = HTAC[0x00]; + var M16zeroes = HTAC[0xF0]; + var pos; + var I16 = 16; + var I63 = 63; + var I64 = 64; + var DU_DCT = fDCTQuant(CDU, fdtbl); + //ZigZag reorder + for (var j=0;j0)&&(DU[end0pos]==0); end0pos--) {}; + //end0pos = first element in reverse order !=0 + if ( end0pos == 0) { + writeBits(EOB); + return DC; + } + var i = 1; + var lng; + while ( i <= end0pos ) { + var startpos = i; + for (; (DU[i]==0) && (i<=end0pos); ++i) {} + var nrzeroes = i-startpos; + if ( nrzeroes >= I16 ) { + lng = nrzeroes>>4; + for (var nrmarker=1; nrmarker <= lng; ++nrmarker) + writeBits(M16zeroes); + nrzeroes = nrzeroes&0xF; + } + pos = 32767+DU[i]; + writeBits(HTAC[(nrzeroes<<4)+category[pos]]); + writeBits(bitcode[pos]); + i++; + } + if ( end0pos != I63 ) { + writeBits(EOB); + } + return DC; + } + + function initCharLookupTable(){ + var sfcc = String.fromCharCode; + for(var i=0; i < 256; i++){ ///// ACHTUNG // 255 + clt[i] = sfcc(i); + } + } + + this.encode = function(image,quality) // image data object + { + // var time_start = new Date().getTime(); + + if(quality) setQuality(quality); + + // Initialize bit writer + byteout = new Array(); + bytenew=0; + bytepos=7; + + // Add JPEG headers + writeWord(0xFFD8); // SOI + writeAPP0(); + writeDQT(); + writeSOF0(image.width,image.height); + writeDHT(); + writeSOS(); + + + // Encode 8x8 macroblocks + var DCY=0; + var DCU=0; + var DCV=0; + + bytenew=0; + bytepos=7; + + + this.encode.displayName = "_encode_"; + + var imageData = image.data; + var width = image.width; + var height = image.height; + + var quadWidth = width*4; + var tripleWidth = width*3; + + var x, y = 0; + var r, g, b; + var start,p, col,row,pos; + while(y < height){ + x = 0; + while(x < quadWidth){ + start = quadWidth * y + x; + p = start; + col = -1; + row = 0; + + for(pos=0; pos < 64; pos++){ + row = pos >> 3;// /8 + col = ( pos & 7 ) * 4; // %8 + p = start + ( row * quadWidth ) + col; + + if(y+row >= height){ // padding bottom + p-= (quadWidth*(y+1+row-height)); + } + + if(x+col >= quadWidth){ // padding right + p-= ((x+col) - quadWidth +4) + } + + r = imageData[ p++ ]; + g = imageData[ p++ ]; + b = imageData[ p++ ]; + + + /* // calculate YUV values dynamically + YDU[pos]=((( 0.29900)*r+( 0.58700)*g+( 0.11400)*b))-128; //-0x80 + UDU[pos]=(((-0.16874)*r+(-0.33126)*g+( 0.50000)*b)); + VDU[pos]=((( 0.50000)*r+(-0.41869)*g+(-0.08131)*b)); + */ + + // use lookup table (slightly faster) + YDU[pos] = ((RGB_YUV_TABLE[r] + RGB_YUV_TABLE[(g + 256)>>0] + RGB_YUV_TABLE[(b + 512)>>0]) >> 16)-128; + UDU[pos] = ((RGB_YUV_TABLE[(r + 768)>>0] + RGB_YUV_TABLE[(g + 1024)>>0] + RGB_YUV_TABLE[(b + 1280)>>0]) >> 16)-128; + VDU[pos] = ((RGB_YUV_TABLE[(r + 1280)>>0] + RGB_YUV_TABLE[(g + 1536)>>0] + RGB_YUV_TABLE[(b + 1792)>>0]) >> 16)-128; + + } + + DCY = processDU(YDU, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCU = processDU(UDU, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); + DCV = processDU(VDU, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); + x+=32; + } + y+=8; + } + + + //////////////////////////////////////////////////////////////// + + // Do the bit alignment of the EOI marker + if ( bytepos >= 0 ) { + var fillbits = []; + fillbits[1] = bytepos+1; + fillbits[0] = (1<<(bytepos+1))-1; + writeBits(fillbits); + } + + writeWord(0xFFD9); //EOI + + var jpegDataUri = 'data:image/jpeg;base64,' + btoa(byteout.join('')); + + byteout = []; + + // benchmarking + // var duration = new Date().getTime() - time_start; + // console.log('Encoding time: '+ currentQuality + 'ms'); + // + + return jpegDataUri + } + + function setQuality(quality){ + if (quality <= 0) { + quality = 1; + } + if (quality > 100) { + quality = 100; + } + + if(currentQuality == quality) return // don't recalc if unchanged + + var sf = 0; + if (quality < 50) { + sf = Math.floor(5000 / quality); + } else { + sf = Math.floor(200 - quality*2); + } + + initQuantTables(sf); + currentQuality = quality; + // console.log('Quality set to: '+quality +'%'); + } + + function init(){ + // var time_start = new Date().getTime(); + if(!quality) quality = 50; + // Create tables + initCharLookupTable() + initHuffmanTbl(); + initCategoryNumber(); + initRGBYUVTable(); + + setQuality(quality); + // var duration = new Date().getTime() - time_start; + // console.log('Initialization '+ duration + 'ms'); + } + + init(); + + }; + + JPEGEncoder.encode = function( data, quality ) { + var encoder = new JPEGEncoder( quality ); + + return encoder.encode( data ); + } + + return JPEGEncoder; + }); + /** + * @fileOverview Fix android canvas.toDataUrl bug. + */ + define('runtime/html5/androidpatch',[ + 'runtime/html5/util', + 'runtime/html5/jpegencoder', + 'base' + ], function( Util, encoder, Base ) { + var origin = Util.canvasToDataUrl, + supportJpeg; + + Util.canvasToDataUrl = function( canvas, type, quality ) { + var ctx, w, h, fragement, parts; + + // 非android手机直接跳过。 + if ( !Base.os.android ) { + return origin.apply( null, arguments ); + } + + // 检测是否canvas支持jpeg导出,根据数据格式来判断。 + // JPEG 前两位分别是:255, 216 + if ( type === 'image/jpeg' && typeof supportJpeg === 'undefined' ) { + fragement = origin.apply( null, arguments ); + + parts = fragement.split(','); + + if ( ~parts[ 0 ].indexOf('base64') ) { + fragement = atob( parts[ 1 ] ); + } else { + fragement = decodeURIComponent( parts[ 1 ] ); + } + + fragement = fragement.substring( 0, 2 ); + + supportJpeg = fragement.charCodeAt( 0 ) === 255 && + fragement.charCodeAt( 1 ) === 216; + } + + // 只有在android环境下才修复 + if ( type === 'image/jpeg' && !supportJpeg ) { + w = canvas.width; + h = canvas.height; + ctx = canvas.getContext('2d'); + + return encoder.encode( ctx.getImageData( 0, 0, w, h ), quality ); + } + + return origin.apply( null, arguments ); + }; + }); + /** + * @fileOverview Image + */ + define('runtime/html5/image',[ + 'base', + 'runtime/html5/runtime', + 'runtime/html5/util' + ], function( Base, Html5Runtime, Util ) { + + var BLANK = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs%3D'; + + return Html5Runtime.register( 'Image', { + + // flag: 标记是否被修改过。 + modified: false, + + init: function() { + var me = this, + img = new Image(); + + img.onload = function() { + + me._info = { + type: me.type, + width: this.width, + height: this.height + }; + + // 读取meta信息。 + if ( !me._metas && 'image/jpeg' === me.type ) { + Util.parseMeta( me._blob, function( error, ret ) { + me._metas = ret; + me.owner.trigger('load'); + }); + } else { + me.owner.trigger('load'); + } + }; + + img.onerror = function() { + me.owner.trigger('error'); + }; + + me._img = img; + }, + + loadFromBlob: function( blob ) { + var me = this, + img = me._img; + + me._blob = blob; + me.type = blob.type; + img.src = Util.createObjectURL( blob.getSource() ); + me.owner.once( 'load', function() { + Util.revokeObjectURL( img.src ); + }); + }, + + resize: function( width, height ) { + var canvas = this._canvas || + (this._canvas = document.createElement('canvas')); + + this._resize( this._img, canvas, width, height ); + this._blob = null; // 没用了,可以删掉了。 + this.modified = true; + this.owner.trigger( 'complete', 'resize' ); + }, + + crop: function( x, y, w, h, s ) { + var cvs = this._canvas || + (this._canvas = document.createElement('canvas')), + opts = this.options, + img = this._img, + iw = img.naturalWidth, + ih = img.naturalHeight, + orientation = this.getOrientation(); + + s = s || 1; + + // todo 解决 orientation 的问题。 + // values that require 90 degree rotation + // if ( ~[ 5, 6, 7, 8 ].indexOf( orientation ) ) { + + // switch ( orientation ) { + // case 6: + // tmp = x; + // x = y; + // y = iw * s - tmp - w; + // console.log(ih * s, tmp, w) + // break; + // } + + // (w ^= h, h ^= w, w ^= h); + // } + + cvs.width = w; + cvs.height = h; + + opts.preserveHeaders || this._rotate2Orientaion( cvs, orientation ); + this._renderImageToCanvas( cvs, img, -x, -y, iw * s, ih * s ); + + this._blob = null; // 没用了,可以删掉了。 + this.modified = true; + this.owner.trigger( 'complete', 'crop' ); + }, + + getAsBlob: function( type ) { + var blob = this._blob, + opts = this.options, + canvas; + + type = type || this.type; + + // blob需要重新生成。 + if ( this.modified || this.type !== type ) { + canvas = this._canvas; + + if ( type === 'image/jpeg' ) { + + blob = Util.canvasToDataUrl( canvas, type, opts.quality ); + + if ( opts.preserveHeaders && this._metas && + this._metas.imageHead ) { + + blob = Util.dataURL2ArrayBuffer( blob ); + blob = Util.updateImageHead( blob, + this._metas.imageHead ); + blob = Util.arrayBufferToBlob( blob, type ); + return blob; + } + } else { + blob = Util.canvasToDataUrl( canvas, type ); + } + + blob = Util.dataURL2Blob( blob ); + } + + return blob; + }, + + getAsDataUrl: function( type ) { + var opts = this.options; + + type = type || this.type; + + if ( type === 'image/jpeg' ) { + return Util.canvasToDataUrl( this._canvas, type, opts.quality ); + } else { + return this._canvas.toDataURL( type ); + } + }, + + getOrientation: function() { + return this._metas && this._metas.exif && + this._metas.exif.get('Orientation') || 1; + }, + + info: function( val ) { + + // setter + if ( val ) { + this._info = val; + return this; + } + + // getter + return this._info; + }, + + meta: function( val ) { + + // setter + if ( val ) { + this._meta = val; + return this; + } + + // getter + return this._meta; + }, + + destroy: function() { + var canvas = this._canvas; + this._img.onload = null; + + if ( canvas ) { + canvas.getContext('2d') + .clearRect( 0, 0, canvas.width, canvas.height ); + canvas.width = canvas.height = 0; + this._canvas = null; + } + + // 释放内存。非常重要,否则释放不了image的内存。 + this._img.src = BLANK; + this._img = this._blob = null; + }, + + _resize: function( img, cvs, width, height ) { + var opts = this.options, + naturalWidth = img.width, + naturalHeight = img.height, + orientation = this.getOrientation(), + scale, w, h, x, y; + + // values that require 90 degree rotation + if ( ~[ 5, 6, 7, 8 ].indexOf( orientation ) ) { + + // 交换width, height的值。 + width ^= height; + height ^= width; + width ^= height; + } + + scale = Math[ opts.crop ? 'max' : 'min' ]( width / naturalWidth, + height / naturalHeight ); + + // 不允许放大。 + opts.allowMagnify || (scale = Math.min( 1, scale )); + + w = naturalWidth * scale; + h = naturalHeight * scale; + + if ( opts.crop ) { + cvs.width = width; + cvs.height = height; + } else { + cvs.width = w; + cvs.height = h; + } + + x = (cvs.width - w) / 2; + y = (cvs.height - h) / 2; + + opts.preserveHeaders || this._rotate2Orientaion( cvs, orientation ); + + this._renderImageToCanvas( cvs, img, x, y, w, h ); + }, + + _rotate2Orientaion: function( canvas, orientation ) { + var width = canvas.width, + height = canvas.height, + ctx = canvas.getContext('2d'); + + switch ( orientation ) { + case 5: + case 6: + case 7: + case 8: + canvas.width = height; + canvas.height = width; + break; + } + + switch ( orientation ) { + case 2: // horizontal flip + ctx.translate( width, 0 ); + ctx.scale( -1, 1 ); + break; + + case 3: // 180 rotate left + ctx.translate( width, height ); + ctx.rotate( Math.PI ); + break; + + case 4: // vertical flip + ctx.translate( 0, height ); + ctx.scale( 1, -1 ); + break; + + case 5: // vertical flip + 90 rotate right + ctx.rotate( 0.5 * Math.PI ); + ctx.scale( 1, -1 ); + break; + + case 6: // 90 rotate right + ctx.rotate( 0.5 * Math.PI ); + ctx.translate( 0, -height ); + break; + + case 7: // horizontal flip + 90 rotate right + ctx.rotate( 0.5 * Math.PI ); + ctx.translate( width, -height ); + ctx.scale( -1, 1 ); + break; + + case 8: // 90 rotate left + ctx.rotate( -0.5 * Math.PI ); + ctx.translate( -width, 0 ); + break; + } + }, + + // https://github.com/stomita/ios-imagefile-megapixel/ + // blob/master/src/megapix-image.js + _renderImageToCanvas: (function() { + + // 如果不是ios, 不需要这么复杂! + if ( !Base.os.ios ) { + return function( canvas ) { + var args = Base.slice( arguments, 1 ), + ctx = canvas.getContext('2d'); + + ctx.drawImage.apply( ctx, args ); + }; + } + + /** + * Detecting vertical squash in loaded image. + * Fixes a bug which squash image vertically while drawing into + * canvas for some images. + */ + function detectVerticalSquash( img, iw, ih ) { + var canvas = document.createElement('canvas'), + ctx = canvas.getContext('2d'), + sy = 0, + ey = ih, + py = ih, + data, alpha, ratio; + + + canvas.width = 1; + canvas.height = ih; + ctx.drawImage( img, 0, 0 ); + data = ctx.getImageData( 0, 0, 1, ih ).data; + + // search image edge pixel position in case + // it is squashed vertically. + while ( py > sy ) { + alpha = data[ (py - 1) * 4 + 3 ]; + + if ( alpha === 0 ) { + ey = py; + } else { + sy = py; + } + + py = (ey + sy) >> 1; + } + + ratio = (py / ih); + return (ratio === 0) ? 1 : ratio; + } + + // fix ie7 bug + // http://stackoverflow.com/questions/11929099/ + // html5-canvas-drawimage-ratio-bug-ios + if ( Base.os.ios >= 7 ) { + return function( canvas, img, x, y, w, h ) { + var iw = img.naturalWidth, + ih = img.naturalHeight, + vertSquashRatio = detectVerticalSquash( img, iw, ih ); + + return canvas.getContext('2d').drawImage( img, 0, 0, + iw * vertSquashRatio, ih * vertSquashRatio, + x, y, w, h ); + }; + } + + /** + * Detect subsampling in loaded image. + * In iOS, larger images than 2M pixels may be + * subsampled in rendering. + */ + function detectSubsampling( img ) { + var iw = img.naturalWidth, + ih = img.naturalHeight, + canvas, ctx; + + // subsampling may happen overmegapixel image + if ( iw * ih > 1024 * 1024 ) { + canvas = document.createElement('canvas'); + canvas.width = canvas.height = 1; + ctx = canvas.getContext('2d'); + ctx.drawImage( img, -iw + 1, 0 ); + + // subsampled image becomes half smaller in rendering size. + // check alpha channel value to confirm image is covering + // edge pixel or not. if alpha value is 0 + // image is not covering, hence subsampled. + return ctx.getImageData( 0, 0, 1, 1 ).data[ 3 ] === 0; + } else { + return false; + } + } + + + return function( canvas, img, x, y, width, height ) { + var iw = img.naturalWidth, + ih = img.naturalHeight, + ctx = canvas.getContext('2d'), + subsampled = detectSubsampling( img ), + doSquash = this.type === 'image/jpeg', + d = 1024, + sy = 0, + dy = 0, + tmpCanvas, tmpCtx, vertSquashRatio, dw, dh, sx, dx; + + if ( subsampled ) { + iw /= 2; + ih /= 2; + } + + ctx.save(); + tmpCanvas = document.createElement('canvas'); + tmpCanvas.width = tmpCanvas.height = d; + + tmpCtx = tmpCanvas.getContext('2d'); + vertSquashRatio = doSquash ? + detectVerticalSquash( img, iw, ih ) : 1; + + dw = Math.ceil( d * width / iw ); + dh = Math.ceil( d * height / ih / vertSquashRatio ); + + while ( sy < ih ) { + sx = 0; + dx = 0; + while ( sx < iw ) { + tmpCtx.clearRect( 0, 0, d, d ); + tmpCtx.drawImage( img, -sx, -sy ); + ctx.drawImage( tmpCanvas, 0, 0, d, d, + x + dx, y + dy, dw, dh ); + sx += d; + dx += dw; + } + sy += d; + dy += dh; + } + ctx.restore(); + tmpCanvas = tmpCtx = null; + }; + })() + }); + }); + /** + * @fileOverview Transport + * @todo 支持chunked传输,优势: + * 可以将大文件分成小块,挨个传输,可以提高大文件成功率,当失败的时候,也只需要重传那小部分, + * 而不需要重头再传一次。另外断点续传也需要用chunked方式。 + */ + define('runtime/html5/transport',[ + 'base', + 'runtime/html5/runtime' + ], function( Base, Html5Runtime ) { + + var noop = Base.noop, + $ = Base.$; + + return Html5Runtime.register( 'Transport', { + init: function() { + this._status = 0; + this._response = null; + }, + + send: function() { + var owner = this.owner, + opts = this.options, + xhr = this._initAjax(), + blob = owner._blob, + server = opts.server, + formData, binary, fr; + + if ( opts.sendAsBinary ) { + server += (/\?/.test( server ) ? '&' : '?') + + $.param( owner._formData ); + + binary = blob.getSource(); + } else { + formData = new FormData(); + $.each( owner._formData, function( k, v ) { + formData.append( k, v ); + }); + + formData.append( opts.fileVal, blob.getSource(), + opts.filename || owner._formData.name || '' ); + } + + if ( opts.withCredentials && 'withCredentials' in xhr ) { + xhr.open( opts.method, server, true ); + xhr.withCredentials = true; + } else { + xhr.open( opts.method, server ); + } + + this._setRequestHeader( xhr, opts.headers ); + + if ( binary ) { + // 强制设置成 content-type 为文件流。 + xhr.overrideMimeType && + xhr.overrideMimeType('application/octet-stream'); + + // android直接发送blob会导致服务端接收到的是空文件。 + // bug详情。 + // https://code.google.com/p/android/issues/detail?id=39882 + // 所以先用fileReader读取出来再通过arraybuffer的方式发送。 + if ( Base.os.android ) { + fr = new FileReader(); + + fr.onload = function() { + xhr.send( this.result ); + fr = fr.onload = null; + }; + + fr.readAsArrayBuffer( binary ); + } else { + xhr.send( binary ); + } + } else { + xhr.send( formData ); + } + }, + + getResponse: function() { + return this._response; + }, + + getResponseAsJson: function() { + return this._parseJson( this._response ); + }, + + getStatus: function() { + return this._status; + }, + + abort: function() { + var xhr = this._xhr; + + if ( xhr ) { + xhr.upload.onprogress = noop; + xhr.onreadystatechange = noop; + xhr.abort(); + + this._xhr = xhr = null; + } + }, + + destroy: function() { + this.abort(); + }, + + _initAjax: function() { + var me = this, + xhr = new XMLHttpRequest(), + opts = this.options; + + if ( opts.withCredentials && !('withCredentials' in xhr) && + typeof XDomainRequest !== 'undefined' ) { + xhr = new XDomainRequest(); + } + + xhr.upload.onprogress = function( e ) { + var percentage = 0; + + if ( e.lengthComputable ) { + percentage = e.loaded / e.total; + } + + return me.trigger( 'progress', percentage ); + }; + + xhr.onreadystatechange = function() { + + if ( xhr.readyState !== 4 ) { + return; + } + + xhr.upload.onprogress = noop; + xhr.onreadystatechange = noop; + me._xhr = null; + me._status = xhr.status; + + if ( xhr.status >= 200 && xhr.status < 300 ) { + me._response = xhr.responseText; + return me.trigger('load'); + } else if ( xhr.status >= 500 && xhr.status < 600 ) { + me._response = xhr.responseText; + return me.trigger( 'error', 'server' ); + } + + + return me.trigger( 'error', me._status ? 'http' : 'abort' ); + }; + + me._xhr = xhr; + return xhr; + }, + + _setRequestHeader: function( xhr, headers ) { + $.each( headers, function( key, val ) { + xhr.setRequestHeader( key, val ); + }); + }, + + _parseJson: function( str ) { + var json; + + try { + json = JSON.parse( str ); + } catch ( ex ) { + json = {}; + } + + return json; + } + }); + }); + /** + * @fileOverview Transport flash实现 + */ + define('runtime/html5/md5',[ + 'runtime/html5/runtime' + ], function( FlashRuntime ) { + + /* + * Fastest md5 implementation around (JKM md5) + * Credits: Joseph Myers + * + * @see http://www.myersdaily.org/joseph/javascript/md5-text.html + * @see http://jsperf.com/md5-shootout/7 + */ + + /* this function is much faster, + so if possible we use it. Some IEs + are the only ones I know of that + need the idiotic second function, + generated by an if clause. */ + var add32 = function (a, b) { + return (a + b) & 0xFFFFFFFF; + }, + + cmn = function (q, a, b, x, s, t) { + a = add32(add32(a, q), add32(x, t)); + return add32((a << s) | (a >>> (32 - s)), b); + }, + + ff = function (a, b, c, d, x, s, t) { + return cmn((b & c) | ((~b) & d), a, b, x, s, t); + }, + + gg = function (a, b, c, d, x, s, t) { + return cmn((b & d) | (c & (~d)), a, b, x, s, t); + }, + + hh = function (a, b, c, d, x, s, t) { + return cmn(b ^ c ^ d, a, b, x, s, t); + }, + + ii = function (a, b, c, d, x, s, t) { + return cmn(c ^ (b | (~d)), a, b, x, s, t); + }, + + md5cycle = function (x, k) { + var a = x[0], + b = x[1], + c = x[2], + d = x[3]; + + a = ff(a, b, c, d, k[0], 7, -680876936); + d = ff(d, a, b, c, k[1], 12, -389564586); + c = ff(c, d, a, b, k[2], 17, 606105819); + b = ff(b, c, d, a, k[3], 22, -1044525330); + a = ff(a, b, c, d, k[4], 7, -176418897); + d = ff(d, a, b, c, k[5], 12, 1200080426); + c = ff(c, d, a, b, k[6], 17, -1473231341); + b = ff(b, c, d, a, k[7], 22, -45705983); + a = ff(a, b, c, d, k[8], 7, 1770035416); + d = ff(d, a, b, c, k[9], 12, -1958414417); + c = ff(c, d, a, b, k[10], 17, -42063); + b = ff(b, c, d, a, k[11], 22, -1990404162); + a = ff(a, b, c, d, k[12], 7, 1804603682); + d = ff(d, a, b, c, k[13], 12, -40341101); + c = ff(c, d, a, b, k[14], 17, -1502002290); + b = ff(b, c, d, a, k[15], 22, 1236535329); + + a = gg(a, b, c, d, k[1], 5, -165796510); + d = gg(d, a, b, c, k[6], 9, -1069501632); + c = gg(c, d, a, b, k[11], 14, 643717713); + b = gg(b, c, d, a, k[0], 20, -373897302); + a = gg(a, b, c, d, k[5], 5, -701558691); + d = gg(d, a, b, c, k[10], 9, 38016083); + c = gg(c, d, a, b, k[15], 14, -660478335); + b = gg(b, c, d, a, k[4], 20, -405537848); + a = gg(a, b, c, d, k[9], 5, 568446438); + d = gg(d, a, b, c, k[14], 9, -1019803690); + c = gg(c, d, a, b, k[3], 14, -187363961); + b = gg(b, c, d, a, k[8], 20, 1163531501); + a = gg(a, b, c, d, k[13], 5, -1444681467); + d = gg(d, a, b, c, k[2], 9, -51403784); + c = gg(c, d, a, b, k[7], 14, 1735328473); + b = gg(b, c, d, a, k[12], 20, -1926607734); + + a = hh(a, b, c, d, k[5], 4, -378558); + d = hh(d, a, b, c, k[8], 11, -2022574463); + c = hh(c, d, a, b, k[11], 16, 1839030562); + b = hh(b, c, d, a, k[14], 23, -35309556); + a = hh(a, b, c, d, k[1], 4, -1530992060); + d = hh(d, a, b, c, k[4], 11, 1272893353); + c = hh(c, d, a, b, k[7], 16, -155497632); + b = hh(b, c, d, a, k[10], 23, -1094730640); + a = hh(a, b, c, d, k[13], 4, 681279174); + d = hh(d, a, b, c, k[0], 11, -358537222); + c = hh(c, d, a, b, k[3], 16, -722521979); + b = hh(b, c, d, a, k[6], 23, 76029189); + a = hh(a, b, c, d, k[9], 4, -640364487); + d = hh(d, a, b, c, k[12], 11, -421815835); + c = hh(c, d, a, b, k[15], 16, 530742520); + b = hh(b, c, d, a, k[2], 23, -995338651); + + a = ii(a, b, c, d, k[0], 6, -198630844); + d = ii(d, a, b, c, k[7], 10, 1126891415); + c = ii(c, d, a, b, k[14], 15, -1416354905); + b = ii(b, c, d, a, k[5], 21, -57434055); + a = ii(a, b, c, d, k[12], 6, 1700485571); + d = ii(d, a, b, c, k[3], 10, -1894986606); + c = ii(c, d, a, b, k[10], 15, -1051523); + b = ii(b, c, d, a, k[1], 21, -2054922799); + a = ii(a, b, c, d, k[8], 6, 1873313359); + d = ii(d, a, b, c, k[15], 10, -30611744); + c = ii(c, d, a, b, k[6], 15, -1560198380); + b = ii(b, c, d, a, k[13], 21, 1309151649); + a = ii(a, b, c, d, k[4], 6, -145523070); + d = ii(d, a, b, c, k[11], 10, -1120210379); + c = ii(c, d, a, b, k[2], 15, 718787259); + b = ii(b, c, d, a, k[9], 21, -343485551); + + x[0] = add32(a, x[0]); + x[1] = add32(b, x[1]); + x[2] = add32(c, x[2]); + x[3] = add32(d, x[3]); + }, + + /* there needs to be support for Unicode here, + * unless we pretend that we can redefine the MD-5 + * algorithm for multi-byte characters (perhaps + * by adding every four 16-bit characters and + * shortening the sum to 32 bits). Otherwise + * I suggest performing MD-5 as if every character + * was two bytes--e.g., 0040 0025 = @%--but then + * how will an ordinary MD-5 sum be matched? + * There is no way to standardize text to something + * like UTF-8 before transformation; speed cost is + * utterly prohibitive. The JavaScript standard + * itself needs to look at this: it should start + * providing access to strings as preformed UTF-8 + * 8-bit unsigned value arrays. + */ + md5blk = function (s) { + var md5blks = [], + i; /* Andy King said do it this way. */ + + for (i = 0; i < 64; i += 4) { + md5blks[i >> 2] = s.charCodeAt(i) + (s.charCodeAt(i + 1) << 8) + (s.charCodeAt(i + 2) << 16) + (s.charCodeAt(i + 3) << 24); + } + return md5blks; + }, + + md5blk_array = function (a) { + var md5blks = [], + i; /* Andy King said do it this way. */ + + for (i = 0; i < 64; i += 4) { + md5blks[i >> 2] = a[i] + (a[i + 1] << 8) + (a[i + 2] << 16) + (a[i + 3] << 24); + } + return md5blks; + }, + + md51 = function (s) { + var n = s.length, + state = [1732584193, -271733879, -1732584194, 271733878], + i, + length, + tail, + tmp, + lo, + hi; + + for (i = 64; i <= n; i += 64) { + md5cycle(state, md5blk(s.substring(i - 64, i))); + } + s = s.substring(i - 64); + length = s.length; + tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + for (i = 0; i < length; i += 1) { + tail[i >> 2] |= s.charCodeAt(i) << ((i % 4) << 3); + } + tail[i >> 2] |= 0x80 << ((i % 4) << 3); + if (i > 55) { + md5cycle(state, tail); + for (i = 0; i < 16; i += 1) { + tail[i] = 0; + } + } + + // Beware that the final length might not fit in 32 bits so we take care of that + tmp = n * 8; + tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/); + lo = parseInt(tmp[2], 16); + hi = parseInt(tmp[1], 16) || 0; + + tail[14] = lo; + tail[15] = hi; + + md5cycle(state, tail); + return state; + }, + + md51_array = function (a) { + var n = a.length, + state = [1732584193, -271733879, -1732584194, 271733878], + i, + length, + tail, + tmp, + lo, + hi; + + for (i = 64; i <= n; i += 64) { + md5cycle(state, md5blk_array(a.subarray(i - 64, i))); + } + + // Not sure if it is a bug, however IE10 will always produce a sub array of length 1 + // containing the last element of the parent array if the sub array specified starts + // beyond the length of the parent array - weird. + // https://connect.microsoft.com/IE/feedback/details/771452/typed-array-subarray-issue + a = (i - 64) < n ? a.subarray(i - 64) : new Uint8Array(0); + + length = a.length; + tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + for (i = 0; i < length; i += 1) { + tail[i >> 2] |= a[i] << ((i % 4) << 3); + } + + tail[i >> 2] |= 0x80 << ((i % 4) << 3); + if (i > 55) { + md5cycle(state, tail); + for (i = 0; i < 16; i += 1) { + tail[i] = 0; + } + } + + // Beware that the final length might not fit in 32 bits so we take care of that + tmp = n * 8; + tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/); + lo = parseInt(tmp[2], 16); + hi = parseInt(tmp[1], 16) || 0; + + tail[14] = lo; + tail[15] = hi; + + md5cycle(state, tail); + + return state; + }, + + hex_chr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'], + + rhex = function (n) { + var s = '', + j; + for (j = 0; j < 4; j += 1) { + s += hex_chr[(n >> (j * 8 + 4)) & 0x0F] + hex_chr[(n >> (j * 8)) & 0x0F]; + } + return s; + }, + + hex = function (x) { + var i; + for (i = 0; i < x.length; i += 1) { + x[i] = rhex(x[i]); + } + return x.join(''); + }, + + md5 = function (s) { + return hex(md51(s)); + }, + + + + //////////////////////////////////////////////////////////////////////////// + + /** + * SparkMD5 OOP implementation. + * + * Use this class to perform an incremental md5, otherwise use the + * static methods instead. + */ + SparkMD5 = function () { + // call reset to init the instance + this.reset(); + }; + + + // In some cases the fast add32 function cannot be used.. + if (md5('hello') !== '5d41402abc4b2a76b9719d911017c592') { + add32 = function (x, y) { + var lsw = (x & 0xFFFF) + (y & 0xFFFF), + msw = (x >> 16) + (y >> 16) + (lsw >> 16); + return (msw << 16) | (lsw & 0xFFFF); + }; + } + + + /** + * Appends a string. + * A conversion will be applied if an utf8 string is detected. + * + * @param {String} str The string to be appended + * + * @return {SparkMD5} The instance itself + */ + SparkMD5.prototype.append = function (str) { + // converts the string to utf8 bytes if necessary + if (/[\u0080-\uFFFF]/.test(str)) { + str = unescape(encodeURIComponent(str)); + } + + // then append as binary + this.appendBinary(str); + + return this; + }; + + /** + * Appends a binary string. + * + * @param {String} contents The binary string to be appended + * + * @return {SparkMD5} The instance itself + */ + SparkMD5.prototype.appendBinary = function (contents) { + this._buff += contents; + this._length += contents.length; + + var length = this._buff.length, + i; + + for (i = 64; i <= length; i += 64) { + md5cycle(this._state, md5blk(this._buff.substring(i - 64, i))); + } + + this._buff = this._buff.substr(i - 64); + + return this; + }; + + /** + * Finishes the incremental computation, reseting the internal state and + * returning the result. + * Use the raw parameter to obtain the raw result instead of the hex one. + * + * @param {Boolean} raw True to get the raw result, false to get the hex result + * + * @return {String|Array} The result + */ + SparkMD5.prototype.end = function (raw) { + var buff = this._buff, + length = buff.length, + i, + tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + ret; + + for (i = 0; i < length; i += 1) { + tail[i >> 2] |= buff.charCodeAt(i) << ((i % 4) << 3); + } + + this._finish(tail, length); + ret = !!raw ? this._state : hex(this._state); + + this.reset(); + + return ret; + }; + + /** + * Finish the final calculation based on the tail. + * + * @param {Array} tail The tail (will be modified) + * @param {Number} length The length of the remaining buffer + */ + SparkMD5.prototype._finish = function (tail, length) { + var i = length, + tmp, + lo, + hi; + + tail[i >> 2] |= 0x80 << ((i % 4) << 3); + if (i > 55) { + md5cycle(this._state, tail); + for (i = 0; i < 16; i += 1) { + tail[i] = 0; + } + } + + // Do the final computation based on the tail and length + // Beware that the final length may not fit in 32 bits so we take care of that + tmp = this._length * 8; + tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/); + lo = parseInt(tmp[2], 16); + hi = parseInt(tmp[1], 16) || 0; + + tail[14] = lo; + tail[15] = hi; + md5cycle(this._state, tail); + }; + + /** + * Resets the internal state of the computation. + * + * @return {SparkMD5} The instance itself + */ + SparkMD5.prototype.reset = function () { + this._buff = ""; + this._length = 0; + this._state = [1732584193, -271733879, -1732584194, 271733878]; + + return this; + }; + + /** + * Releases memory used by the incremental buffer and other aditional + * resources. If you plan to use the instance again, use reset instead. + */ + SparkMD5.prototype.destroy = function () { + delete this._state; + delete this._buff; + delete this._length; + }; + + + /** + * Performs the md5 hash on a string. + * A conversion will be applied if utf8 string is detected. + * + * @param {String} str The string + * @param {Boolean} raw True to get the raw result, false to get the hex result + * + * @return {String|Array} The result + */ + SparkMD5.hash = function (str, raw) { + // converts the string to utf8 bytes if necessary + if (/[\u0080-\uFFFF]/.test(str)) { + str = unescape(encodeURIComponent(str)); + } + + var hash = md51(str); + + return !!raw ? hash : hex(hash); + }; + + /** + * Performs the md5 hash on a binary string. + * + * @param {String} content The binary string + * @param {Boolean} raw True to get the raw result, false to get the hex result + * + * @return {String|Array} The result + */ + SparkMD5.hashBinary = function (content, raw) { + var hash = md51(content); + + return !!raw ? hash : hex(hash); + }; + + /** + * SparkMD5 OOP implementation for array buffers. + * + * Use this class to perform an incremental md5 ONLY for array buffers. + */ + SparkMD5.ArrayBuffer = function () { + // call reset to init the instance + this.reset(); + }; + + //////////////////////////////////////////////////////////////////////////// + + /** + * Appends an array buffer. + * + * @param {ArrayBuffer} arr The array to be appended + * + * @return {SparkMD5.ArrayBuffer} The instance itself + */ + SparkMD5.ArrayBuffer.prototype.append = function (arr) { + // TODO: we could avoid the concatenation here but the algorithm would be more complex + // if you find yourself needing extra performance, please make a PR. + var buff = this._concatArrayBuffer(this._buff, arr), + length = buff.length, + i; + + this._length += arr.byteLength; + + for (i = 64; i <= length; i += 64) { + md5cycle(this._state, md5blk_array(buff.subarray(i - 64, i))); + } + + // Avoids IE10 weirdness (documented above) + this._buff = (i - 64) < length ? buff.subarray(i - 64) : new Uint8Array(0); + + return this; + }; + + /** + * Finishes the incremental computation, reseting the internal state and + * returning the result. + * Use the raw parameter to obtain the raw result instead of the hex one. + * + * @param {Boolean} raw True to get the raw result, false to get the hex result + * + * @return {String|Array} The result + */ + SparkMD5.ArrayBuffer.prototype.end = function (raw) { + var buff = this._buff, + length = buff.length, + tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + i, + ret; + + for (i = 0; i < length; i += 1) { + tail[i >> 2] |= buff[i] << ((i % 4) << 3); + } + + this._finish(tail, length); + ret = !!raw ? this._state : hex(this._state); + + this.reset(); + + return ret; + }; + + SparkMD5.ArrayBuffer.prototype._finish = SparkMD5.prototype._finish; + + /** + * Resets the internal state of the computation. + * + * @return {SparkMD5.ArrayBuffer} The instance itself + */ + SparkMD5.ArrayBuffer.prototype.reset = function () { + this._buff = new Uint8Array(0); + this._length = 0; + this._state = [1732584193, -271733879, -1732584194, 271733878]; + + return this; + }; + + /** + * Releases memory used by the incremental buffer and other aditional + * resources. If you plan to use the instance again, use reset instead. + */ + SparkMD5.ArrayBuffer.prototype.destroy = SparkMD5.prototype.destroy; + + /** + * Concats two array buffers, returning a new one. + * + * @param {ArrayBuffer} first The first array buffer + * @param {ArrayBuffer} second The second array buffer + * + * @return {ArrayBuffer} The new array buffer + */ + SparkMD5.ArrayBuffer.prototype._concatArrayBuffer = function (first, second) { + var firstLength = first.length, + result = new Uint8Array(firstLength + second.byteLength); + + result.set(first); + result.set(new Uint8Array(second), firstLength); + + return result; + }; + + /** + * Performs the md5 hash on an array buffer. + * + * @param {ArrayBuffer} arr The array buffer + * @param {Boolean} raw True to get the raw result, false to get the hex result + * + * @return {String|Array} The result + */ + SparkMD5.ArrayBuffer.hash = function (arr, raw) { + var hash = md51_array(new Uint8Array(arr)); + + return !!raw ? hash : hex(hash); + }; + + return FlashRuntime.register( 'Md5', { + init: function() { + // do nothing. + }, + + loadFromBlob: function( file ) { + var blob = file.getSource(), + chunkSize = 2 * 1024 * 1024, + chunks = Math.ceil( blob.size / chunkSize ), + chunk = 0, + owner = this.owner, + spark = new SparkMD5.ArrayBuffer(), + me = this, + blobSlice = blob.mozSlice || blob.webkitSlice || blob.slice, + loadNext, fr; + + fr = new FileReader(); + + loadNext = function() { + var start, end; + + start = chunk * chunkSize; + end = Math.min( start + chunkSize, blob.size ); + + fr.onload = function( e ) { + spark.append( e.target.result ); + owner.trigger( 'progress', { + total: file.size, + loaded: end + }); + }; + + fr.onloadend = function() { + fr.onloadend = fr.onload = null; + + if ( ++chunk < chunks ) { + setTimeout( loadNext, 1 ); + } else { + setTimeout(function(){ + owner.trigger('load'); + me.result = spark.end(); + loadNext = file = blob = spark = null; + owner.trigger('complete'); + }, 50 ); + } + }; + + fr.readAsArrayBuffer( blobSlice.call( blob, start, end ) ); + }; + + loadNext(); + }, + + getResult: function() { + return this.result; + } + }); + }); + /** + * @fileOverview FlashRuntime + */ + define('runtime/flash/runtime',[ + 'base', + 'runtime/runtime', + 'runtime/compbase' + ], function( Base, Runtime, CompBase ) { + + var $ = Base.$, + type = 'flash', + components = {}; + + + function getFlashVersion() { + var version; + + try { + version = navigator.plugins[ 'Shockwave Flash' ]; + version = version.description; + } catch ( ex ) { + try { + version = new ActiveXObject('ShockwaveFlash.ShockwaveFlash') + .GetVariable('$version'); + } catch ( ex2 ) { + version = '0.0'; + } + } + version = version.match( /\d+/g ); + return parseFloat( version[ 0 ] + '.' + version[ 1 ], 10 ); + } + + function FlashRuntime() { + var pool = {}, + clients = {}, + destroy = this.destroy, + me = this, + jsreciver = Base.guid('webuploader_'); + + Runtime.apply( me, arguments ); + me.type = type; + + + // 这个方法的调用者,实际上是RuntimeClient + me.exec = function( comp, fn/*, args...*/ ) { + var client = this, + uid = client.uid, + args = Base.slice( arguments, 2 ), + instance; + + clients[ uid ] = client; + + if ( components[ comp ] ) { + if ( !pool[ uid ] ) { + pool[ uid ] = new components[ comp ]( client, me ); + } + + instance = pool[ uid ]; + + if ( instance[ fn ] ) { + return instance[ fn ].apply( instance, args ); + } + } + + return me.flashExec.apply( client, arguments ); + }; + + function handler( evt, obj ) { + var type = evt.type || evt, + parts, uid; + + parts = type.split('::'); + uid = parts[ 0 ]; + type = parts[ 1 ]; + + // console.log.apply( console, arguments ); + + if ( type === 'Ready' && uid === me.uid ) { + me.trigger('ready'); + } else if ( clients[ uid ] ) { + clients[ uid ].trigger( type.toLowerCase(), evt, obj ); + } + + // Base.log( evt, obj ); + } + + // flash的接受器。 + window[ jsreciver ] = function() { + var args = arguments; + + // 为了能捕获得到。 + setTimeout(function() { + handler.apply( null, args ); + }, 1 ); + }; + + this.jsreciver = jsreciver; + + this.destroy = function() { + // @todo 删除池子中的所有实例 + return destroy && destroy.apply( this, arguments ); + }; + + this.flashExec = function( comp, fn ) { + var flash = me.getFlash(), + args = Base.slice( arguments, 2 ); + + return flash.exec( this.uid, comp, fn, args ); + }; + + // @todo + } + + Base.inherits( Runtime, { + constructor: FlashRuntime, + + init: function() { + var container = this.getContainer(), + opts = this.options, + html; + + // if not the minimal height, shims are not initialized + // in older browsers (e.g FF3.6, IE6,7,8, Safari 4.0,5.0, etc) + container.css({ + position: 'absolute', + top: '-8px', + left: '-8px', + width: '9px', + height: '9px', + overflow: 'hidden' + }); + + // insert flash object + html = '' + + '' + + '' + + '' + + ''; + + container.html( html ); + }, + + getFlash: function() { + if ( this._flash ) { + return this._flash; + } + + this._flash = $( '#' + this.uid ).get( 0 ); + return this._flash; + } + + }); + + FlashRuntime.register = function( name, component ) { + component = components[ name ] = Base.inherits( CompBase, $.extend({ + + // @todo fix this later + flashExec: function() { + var owner = this.owner, + runtime = this.getRuntime(); + + return runtime.flashExec.apply( owner, arguments ); + } + }, component ) ); + + return component; + }; + + if ( getFlashVersion() >= 11.4 ) { + Runtime.addRuntime( type, FlashRuntime ); + } + + return FlashRuntime; + }); + /** + * @fileOverview FilePicker + */ + define('runtime/flash/filepicker',[ + 'base', + 'runtime/flash/runtime' + ], function( Base, FlashRuntime ) { + var $ = Base.$; + + return FlashRuntime.register( 'FilePicker', { + init: function( opts ) { + var copy = $.extend({}, opts ), + len, i; + + // 修复Flash再没有设置title的情况下无法弹出flash文件选择框的bug. + len = copy.accept && copy.accept.length; + for ( i = 0; i < len; i++ ) { + if ( !copy.accept[ i ].title ) { + copy.accept[ i ].title = 'Files'; + } + } + + delete copy.button; + delete copy.id; + delete copy.container; + + this.flashExec( 'FilePicker', 'init', copy ); + }, + + destroy: function() { + this.flashExec( 'FilePicker', 'destroy' ); + } + }); + }); + /** + * @fileOverview 图片压缩 + */ + define('runtime/flash/image',[ + 'runtime/flash/runtime' + ], function( FlashRuntime ) { + + return FlashRuntime.register( 'Image', { + // init: function( options ) { + // var owner = this.owner; + + // this.flashExec( 'Image', 'init', options ); + // owner.on( 'load', function() { + // debugger; + // }); + // }, + + loadFromBlob: function( blob ) { + var owner = this.owner; + + owner.info() && this.flashExec( 'Image', 'info', owner.info() ); + owner.meta() && this.flashExec( 'Image', 'meta', owner.meta() ); + + this.flashExec( 'Image', 'loadFromBlob', blob.uid ); + } + }); + }); + /** + * @fileOverview Transport flash实现 + */ + define('runtime/flash/transport',[ + 'base', + 'runtime/flash/runtime', + 'runtime/client' + ], function( Base, FlashRuntime, RuntimeClient ) { + var $ = Base.$; + + return FlashRuntime.register( 'Transport', { + init: function() { + this._status = 0; + this._response = null; + this._responseJson = null; + }, + + send: function() { + var owner = this.owner, + opts = this.options, + xhr = this._initAjax(), + blob = owner._blob, + server = opts.server, + binary; + + xhr.connectRuntime( blob.ruid ); + + if ( opts.sendAsBinary ) { + server += (/\?/.test( server ) ? '&' : '?') + + $.param( owner._formData ); + + binary = blob.uid; + } else { + $.each( owner._formData, function( k, v ) { + xhr.exec( 'append', k, v ); + }); + + xhr.exec( 'appendBlob', opts.fileVal, blob.uid, + opts.filename || owner._formData.name || '' ); + } + + this._setRequestHeader( xhr, opts.headers ); + xhr.exec( 'send', { + method: opts.method, + url: server, + forceURLStream: opts.forceURLStream, + mimeType: 'application/octet-stream' + }, binary ); + }, + + getStatus: function() { + return this._status; + }, + + getResponse: function() { + return this._response || ''; + }, + + getResponseAsJson: function() { + return this._responseJson; + }, + + abort: function() { + var xhr = this._xhr; + + if ( xhr ) { + xhr.exec('abort'); + xhr.destroy(); + this._xhr = xhr = null; + } + }, + + destroy: function() { + this.abort(); + }, + + _initAjax: function() { + var me = this, + xhr = new RuntimeClient('XMLHttpRequest'); + + xhr.on( 'uploadprogress progress', function( e ) { + var percent = e.loaded / e.total; + percent = Math.min( 1, Math.max( 0, percent ) ); + return me.trigger( 'progress', percent ); + }); + + xhr.on( 'load', function() { + var status = xhr.exec('getStatus'), + readBody = false, + err = '', + p; + + xhr.off(); + me._xhr = null; + + if ( status >= 200 && status < 300 ) { + readBody = true; + } else if ( status >= 500 && status < 600 ) { + readBody = true; + err = 'server'; + } else { + err = 'http'; + } + + if ( readBody ) { + me._response = xhr.exec('getResponse'); + me._response = decodeURIComponent( me._response ); + + // flash 处理可能存在 bug, 没辙只能靠 js 了 + // try { + // me._responseJson = xhr.exec('getResponseAsJson'); + // } catch ( error ) { + + p = window.JSON && window.JSON.parse || function( s ) { + try { + return new Function('return ' + s).call(); + } catch ( err ) { + return {}; + } + }; + me._responseJson = me._response ? p(me._response) : {}; + + // } + } + + xhr.destroy(); + xhr = null; + + return err ? me.trigger( 'error', err ) : me.trigger('load'); + }); + + xhr.on( 'error', function() { + xhr.off(); + me._xhr = null; + me.trigger( 'error', 'http' ); + }); + + me._xhr = xhr; + return xhr; + }, + + _setRequestHeader: function( xhr, headers ) { + $.each( headers, function( key, val ) { + xhr.exec( 'setRequestHeader', key, val ); + }); + } + }); + }); + /** + * @fileOverview Blob Html实现 + */ + define('runtime/flash/blob',[ + 'runtime/flash/runtime', + 'lib/blob' + ], function( FlashRuntime, Blob ) { + + return FlashRuntime.register( 'Blob', { + slice: function( start, end ) { + var blob = this.flashExec( 'Blob', 'slice', start, end ); + + return new Blob( blob.uid, blob ); + } + }); + }); + /** + * @fileOverview Md5 flash实现 + */ + define('runtime/flash/md5',[ + 'runtime/flash/runtime' + ], function( FlashRuntime ) { + + return FlashRuntime.register( 'Md5', { + init: function() { + // do nothing. + }, + + loadFromBlob: function( blob ) { + return this.flashExec( 'Md5', 'loadFromBlob', blob.uid ); + } + }); + }); + /** + * @fileOverview 完全版本。 + */ + define('preset/all',[ + 'base', + + // widgets + 'widgets/filednd', + 'widgets/filepaste', + 'widgets/filepicker', + 'widgets/image', + 'widgets/queue', + 'widgets/runtime', + 'widgets/upload', + 'widgets/validator', + 'widgets/md5', + + // runtimes + // html5 + 'runtime/html5/blob', + 'runtime/html5/dnd', + 'runtime/html5/filepaste', + 'runtime/html5/filepicker', + 'runtime/html5/imagemeta/exif', + 'runtime/html5/androidpatch', + 'runtime/html5/image', + 'runtime/html5/transport', + 'runtime/html5/md5', + + // flash + 'runtime/flash/filepicker', + 'runtime/flash/image', + 'runtime/flash/transport', + 'runtime/flash/blob', + 'runtime/flash/md5' + ], function( Base ) { + return Base; + }); + define('webuploader',[ + 'preset/all' + ], function( preset ) { + return preset; + }); + return require('webuploader'); +}); diff --git a/static/plugins/webuploader/webuploader.nolog.min.js b/static/plugins/webuploader/webuploader.nolog.min.js new file mode 100644 index 00000000..a4ee44b3 --- /dev/null +++ b/static/plugins/webuploader/webuploader.nolog.min.js @@ -0,0 +1,3 @@ +/* WebUploader 0.1.5 */!function(a,b){var c,d={},e=function(a,b){var c,d,e;if("string"==typeof a)return h(a);for(c=[],d=a.length,e=0;d>e;e++)c.push(h(a[e]));return b.apply(null,c)},f=function(a,b,c){2===arguments.length&&(c=b,b=null),e(b||[],function(){g(a,c,arguments)})},g=function(a,b,c){var f,g={exports:b};"function"==typeof b&&(c.length||(c=[e,g.exports,g]),f=b.apply(null,c),void 0!==f&&(g.exports=f)),d[a]=g.exports},h=function(b){var c=d[b]||a[b];if(!c)throw new Error("`"+b+"` is undefined");return c},i=function(a){var b,c,e,f,g,h;h=function(a){return a&&a.charAt(0).toUpperCase()+a.substr(1)};for(b in d)if(c=a,d.hasOwnProperty(b)){for(e=b.split("/"),g=h(e.pop());f=h(e.shift());)c[f]=c[f]||{},c=c[f];c[g]=d[b]}return a},j=function(c){return a.__dollar=c,i(b(a,f,e))};"object"==typeof module&&"object"==typeof module.exports?module.exports=j():"function"==typeof define&&define.amd?define(["jquery"],j):(c=a.WebUploader,a.WebUploader=j(),a.WebUploader.noConflict=function(){a.WebUploader=c})}(window,function(a,b,c){return b("dollar-third",[],function(){var b=a.__dollar||a.jQuery||a.Zepto;if(!b)throw new Error("jQuery or Zepto not found!");return b}),b("dollar",["dollar-third"],function(a){return a}),b("promise-third",["dollar"],function(a){return{Deferred:a.Deferred,when:a.when,isPromise:function(a){return a&&"function"==typeof a.then}}}),b("promise",["promise-third"],function(a){return a}),b("base",["dollar","promise"],function(b,c){function d(a){return function(){return h.apply(a,arguments)}}function e(a,b){return function(){return a.apply(b,arguments)}}function f(a){var b;return Object.create?Object.create(a):(b=function(){},b.prototype=a,new b)}var g=function(){},h=Function.call;return{version:"0.1.5",$:b,Deferred:c.Deferred,isPromise:c.isPromise,when:c.when,browser:function(a){var b={},c=a.match(/WebKit\/([\d.]+)/),d=a.match(/Chrome\/([\d.]+)/)||a.match(/CriOS\/([\d.]+)/),e=a.match(/MSIE\s([\d\.]+)/)||a.match(/(?:trident)(?:.*rv:([\w.]+))?/i),f=a.match(/Firefox\/([\d.]+)/),g=a.match(/Safari\/([\d.]+)/),h=a.match(/OPR\/([\d.]+)/);return c&&(b.webkit=parseFloat(c[1])),d&&(b.chrome=parseFloat(d[1])),e&&(b.ie=parseFloat(e[1])),f&&(b.firefox=parseFloat(f[1])),g&&(b.safari=parseFloat(g[1])),h&&(b.opera=parseFloat(h[1])),b}(navigator.userAgent),os:function(a){var b={},c=a.match(/(?:Android);?[\s\/]+([\d.]+)?/),d=a.match(/(?:iPad|iPod|iPhone).*OS\s([\d_]+)/);return c&&(b.android=parseFloat(c[1])),d&&(b.ios=parseFloat(d[1].replace(/_/g,"."))),b}(navigator.userAgent),inherits:function(a,c,d){var e;return"function"==typeof c?(e=c,c=null):e=c&&c.hasOwnProperty("constructor")?c.constructor:function(){return a.apply(this,arguments)},b.extend(!0,e,a,d||{}),e.__super__=a.prototype,e.prototype=f(a.prototype),c&&b.extend(!0,e.prototype,c),e},noop:g,bindFn:e,log:function(){return a.console?e(console.log,console):g}(),nextTick:function(){return function(a){setTimeout(a,1)}}(),slice:d([].slice),guid:function(){var a=0;return function(b){for(var c=(+new Date).toString(32),d=0;5>d;d++)c+=Math.floor(65535*Math.random()).toString(32);return(b||"wu_")+c+(a++).toString(32)}}(),formatSize:function(a,b,c){var d;for(c=c||["B","K","M","G","TB"];(d=c.shift())&&a>1024;)a/=1024;return("B"===d?a:a.toFixed(b||2))+d}}}),b("mediator",["base"],function(a){function b(a,b,c,d){return f.grep(a,function(a){return!(!a||b&&a.e!==b||c&&a.cb!==c&&a.cb._cb!==c||d&&a.ctx!==d)})}function c(a,b,c){f.each((a||"").split(h),function(a,d){c(d,b)})}function d(a,b){for(var c,d=!1,e=-1,f=a.length;++e1?void(d.isPlainObject(b)&&d.isPlainObject(c[a])?d.extend(c[a],b):c[a]=b):a?c[a]:c},getStats:function(){var a=this.request("get-stats");return a?{successNum:a.numOfSuccess,progressNum:a.numOfProgress,cancelNum:a.numOfCancel,invalidNum:a.numOfInvalid,uploadFailNum:a.numOfUploadFailed,queueNum:a.numOfQueue,interruptNum:a.numofInterrupt}:{}},trigger:function(a){var c=[].slice.call(arguments,1),e=this.options,f="on"+a.substring(0,1).toUpperCase()+a.substring(1);return b.trigger.apply(this,arguments)===!1||d.isFunction(e[f])&&e[f].apply(this,c)===!1||d.isFunction(this[f])&&this[f].apply(this,c)===!1||b.trigger.apply(b,[this,a].concat(c))===!1?!1:!0},destroy:function(){this.request("destroy",arguments),this.off()},request:a.noop}),a.create=c.create=function(a){return new c(a)},a.Uploader=c,c}),b("runtime/runtime",["base","mediator"],function(a,b){function c(b){this.options=d.extend({container:document.body},b),this.uid=a.guid("rt_")}var d=a.$,e={},f=function(a){for(var b in a)if(a.hasOwnProperty(b))return b;return null};return d.extend(c.prototype,{getContainer:function(){var a,b,c=this.options;return this._container?this._container:(a=d(c.container||document.body),b=d(document.createElement("div")),b.attr("id","rt_"+this.uid),b.css({position:"absolute",top:"0px",left:"0px",width:"1px",height:"1px",overflow:"hidden"}),a.append(b),a.addClass("webuploader-container"),this._container=b,this._parent=a,b)},init:a.noop,exec:a.noop,destroy:function(){this._container&&this._container.remove(),this._parent&&this._parent.removeClass("webuploader-container"),this.off()}}),c.orders="html5,flash",c.addRuntime=function(a,b){e[a]=b},c.hasRuntime=function(a){return!!(a?e[a]:f(e))},c.create=function(a,b){var g,h;if(b=b||c.orders,d.each(b.split(/\s*,\s*/g),function(){return e[this]?(g=this,!1):void 0}),g=g||f(e),!g)throw new Error("Runtime Error");return h=new e[g](a)},b.installTo(c.prototype),c}),b("runtime/client",["base","mediator","runtime/runtime"],function(a,b,c){function d(b,d){var f,g=a.Deferred();this.uid=a.guid("client_"),this.runtimeReady=function(a){return g.done(a)},this.connectRuntime=function(b,h){if(f)throw new Error("already connected!");return g.done(h),"string"==typeof b&&e.get(b)&&(f=e.get(b)),f=f||e.get(null,d),f?(a.$.extend(f.options,b),f.__promise.then(g.resolve),f.__client++):(f=c.create(b,b.runtimeOrder),f.__promise=g.promise(),f.once("ready",g.resolve),f.init(),e.add(f),f.__client=1),d&&(f.__standalone=d),f},this.getRuntime=function(){return f},this.disconnectRuntime=function(){f&&(f.__client--,f.__client<=0&&(e.remove(f),delete f.__promise,f.destroy()),f=null)},this.exec=function(){if(f){var c=a.slice(arguments);return b&&c.unshift(b),f.exec.apply(this,c)}},this.getRuid=function(){return f&&f.uid},this.destroy=function(a){return function(){a&&a.apply(this,arguments),this.trigger("destroy"),this.off(),this.exec("destroy"),this.disconnectRuntime()}}(this.destroy)}var e;return e=function(){var a={};return{add:function(b){a[b.uid]=b},get:function(b,c){var d;if(b)return a[b];for(d in a)if(!c||!a[d].__standalone)return a[d];return null},remove:function(b){delete a[b.uid]}}}(),b.installTo(d.prototype),d}),b("lib/dnd",["base","mediator","runtime/client"],function(a,b,c){function d(a){a=this.options=e.extend({},d.options,a),a.container=e(a.container),a.container.length&&c.call(this,"DragAndDrop")}var e=a.$;return d.options={accept:null,disableGlobalDnd:!1},a.inherits(c,{constructor:d,init:function(){var a=this;a.connectRuntime(a.options,function(){a.exec("init"),a.trigger("ready")})}}),b.installTo(d.prototype),d}),b("widgets/widget",["base","uploader"],function(a,b){function c(a){if(!a)return!1;var b=a.length,c=e.type(a);return 1===a.nodeType&&b?!0:"array"===c||"function"!==c&&"string"!==c&&(0===b||"number"==typeof b&&b>0&&b-1 in a)}function d(a){this.owner=a,this.options=a.options}var e=a.$,f=b.prototype._init,g=b.prototype.destroy,h={},i=[];return e.extend(d.prototype,{init:a.noop,invoke:function(a,b){var c=this.responseMap;return c&&a in c&&c[a]in this&&e.isFunction(this[c[a]])?this[c[a]].apply(this,b):h},request:function(){return this.owner.request.apply(this.owner,arguments)}}),e.extend(b.prototype,{_init:function(){var a=this,b=a._widgets=[],c=a.options.disableWidgets||"";return e.each(i,function(d,e){(!c||!~c.indexOf(e._name))&&b.push(new e(a))}),f.apply(a,arguments)},request:function(b,d,e){var f,g,i,j,k=0,l=this._widgets,m=l&&l.length,n=[],o=[];for(d=c(d)?d:[d];m>k;k++)f=l[k],g=f.invoke(b,d),g!==h&&(a.isPromise(g)?o.push(g):n.push(g));return e||o.length?(i=a.when.apply(a,o),j=i.pipe?"pipe":"then",i[j](function(){var b=a.Deferred(),c=arguments;return 1===c.length&&(c=c[0]),setTimeout(function(){b.resolve(c)},1),b.promise()})[e?j:"done"](e||a.noop)):n[0]},destroy:function(){g.apply(this,arguments),this._widgets=null}}),b.register=d.register=function(b,c){var f,g={init:"init",destroy:"destroy",name:"anonymous"};return 1===arguments.length?(c=b,e.each(c,function(a){return"_"===a[0]||"name"===a?void("name"===a&&(g.name=c.name)):void(g[a.replace(/[A-Z]/g,"-$&").toLowerCase()]=a)})):g=e.extend(g,b),c.responseMap=g,f=a.inherits(d,c),f._name=g.name,i.push(f),f},b.unRegister=d.unRegister=function(a){if(a&&"anonymous"!==a)for(var b=i.length;b--;)i[b]._name===a&&i.splice(b,1)},d}),b("widgets/filednd",["base","uploader","lib/dnd","widgets/widget"],function(a,b,c){var d=a.$;return b.options.dnd="",b.register({name:"dnd",init:function(b){if(b.dnd&&"html5"===this.request("predict-runtime-type")){var e,f=this,g=a.Deferred(),h=d.extend({},{disableGlobalDnd:b.disableGlobalDnd,container:b.dnd,accept:b.accept});return this.dnd=e=new c(h),e.once("ready",g.resolve),e.on("drop",function(a){f.request("add-file",[a])}),e.on("accept",function(a){return f.owner.trigger("dndAccept",a)}),e.init(),g.promise()}},destroy:function(){this.dnd&&this.dnd.destroy()}})}),b("lib/filepaste",["base","mediator","runtime/client"],function(a,b,c){function d(a){a=this.options=e.extend({},a),a.container=e(a.container||document.body),c.call(this,"FilePaste")}var e=a.$;return a.inherits(c,{constructor:d,init:function(){var a=this;a.connectRuntime(a.options,function(){a.exec("init"),a.trigger("ready")})}}),b.installTo(d.prototype),d}),b("widgets/filepaste",["base","uploader","lib/filepaste","widgets/widget"],function(a,b,c){var d=a.$;return b.register({name:"paste",init:function(b){if(b.paste&&"html5"===this.request("predict-runtime-type")){var e,f=this,g=a.Deferred(),h=d.extend({},{container:b.paste,accept:b.accept});return this.paste=e=new c(h),e.once("ready",g.resolve),e.on("paste",function(a){f.owner.request("add-file",[a])}),e.init(),g.promise()}},destroy:function(){this.paste&&this.paste.destroy()}})}),b("lib/blob",["base","runtime/client"],function(a,b){function c(a,c){var d=this;d.source=c,d.ruid=a,this.size=c.size||0,this.type=!c.type&&this.ext&&~"jpg,jpeg,png,gif,bmp".indexOf(this.ext)?"image/"+("jpg"===this.ext?"jpeg":this.ext):c.type||"application/octet-stream",b.call(d,"Blob"),this.uid=c.uid||this.uid,a&&d.connectRuntime(a)}return a.inherits(b,{constructor:c,slice:function(a,b){return this.exec("slice",a,b)},getSource:function(){return this.source}}),c}),b("lib/file",["base","lib/blob"],function(a,b){function c(a,c){var f;this.name=c.name||"untitled"+d++,f=e.exec(c.name)?RegExp.$1.toLowerCase():"",!f&&c.type&&(f=/\/(jpg|jpeg|png|gif|bmp)$/i.exec(c.type)?RegExp.$1.toLowerCase():"",this.name+="."+f),this.ext=f,this.lastModifiedDate=c.lastModifiedDate||(new Date).toLocaleString(),b.apply(this,arguments)}var d=1,e=/\.([^.]+)$/;return a.inherits(b,c)}),b("lib/filepicker",["base","runtime/client","lib/file"],function(b,c,d){function e(a){if(a=this.options=f.extend({},e.options,a),a.container=f(a.id),!a.container.length)throw new Error("按钮指定错误");a.innerHTML=a.innerHTML||a.label||a.container.html()||"",a.button=f(a.button||document.createElement("div")),a.button.html(a.innerHTML),a.container.html(a.button),c.call(this,"FilePicker",!0)}var f=b.$;return e.options={button:null,container:null,label:null,innerHTML:null,multiple:!0,accept:null,name:"file"},b.inherits(c,{constructor:e,init:function(){var c=this,e=c.options,g=e.button;g.addClass("webuploader-pick"),c.on("all",function(a){var b;switch(a){case"mouseenter":g.addClass("webuploader-pick-hover");break;case"mouseleave":g.removeClass("webuploader-pick-hover");break;case"change":b=c.exec("getFiles"),c.trigger("select",f.map(b,function(a){return a=new d(c.getRuid(),a),a._refer=e.container,a}),e.container)}}),c.connectRuntime(e,function(){c.refresh(),c.exec("init",e),c.trigger("ready")}),this._resizeHandler=b.bindFn(this.refresh,this),f(a).on("resize",this._resizeHandler)},refresh:function(){var a=this.getRuntime().getContainer(),b=this.options.button,c=b.outerWidth?b.outerWidth():b.width(),d=b.outerHeight?b.outerHeight():b.height(),e=b.offset();c&&d&&a.css({bottom:"auto",right:"auto",width:c+"px",height:d+"px"}).offset(e)},enable:function(){var a=this.options.button;a.removeClass("webuploader-pick-disable"),this.refresh()},disable:function(){var a=this.options.button;this.getRuntime().getContainer().css({top:"-99999px"}),a.addClass("webuploader-pick-disable")},destroy:function(){var b=this.options.button;f(a).off("resize",this._resizeHandler),b.removeClass("webuploader-pick-disable webuploader-pick-hover webuploader-pick")}}),e}),b("widgets/filepicker",["base","uploader","lib/filepicker","widgets/widget"],function(a,b,c){var d=a.$;return d.extend(b.options,{pick:null,accept:null}),b.register({name:"picker",init:function(a){return this.pickers=[],a.pick&&this.addBtn(a.pick)},refresh:function(){d.each(this.pickers,function(){this.refresh()})},addBtn:function(b){var e=this,f=e.options,g=f.accept,h=[];if(b)return d.isPlainObject(b)||(b={id:b}),d(b.id).each(function(){var i,j,k;k=a.Deferred(),i=d.extend({},b,{accept:d.isPlainObject(g)?[g]:g,swf:f.swf,runtimeOrder:f.runtimeOrder,id:this}),j=new c(i),j.once("ready",k.resolve),j.on("select",function(a){e.owner.request("add-file",[a])}),j.init(),e.pickers.push(j),h.push(k.promise())}),a.when.apply(a,h)},disable:function(){d.each(this.pickers,function(){this.disable()})},enable:function(){d.each(this.pickers,function(){this.enable()})},destroy:function(){d.each(this.pickers,function(){this.destroy()}),this.pickers=null}})}),b("lib/image",["base","runtime/client","lib/blob"],function(a,b,c){function d(a){this.options=e.extend({},d.options,a),b.call(this,"Image"),this.on("load",function(){this._info=this.exec("info"),this._meta=this.exec("meta")})}var e=a.$;return d.options={quality:90,crop:!1,preserveHeaders:!1,allowMagnify:!1},a.inherits(b,{constructor:d,info:function(a){return a?(this._info=a,this):this._info},meta:function(a){return a?(this._meta=a,this):this._meta},loadFromBlob:function(a){var b=this,c=a.getRuid();this.connectRuntime(c,function(){b.exec("init",b.options),b.exec("loadFromBlob",a)})},resize:function(){var b=a.slice(arguments);return this.exec.apply(this,["resize"].concat(b))},crop:function(){var b=a.slice(arguments);return this.exec.apply(this,["crop"].concat(b))},getAsDataUrl:function(a){return this.exec("getAsDataUrl",a)},getAsBlob:function(a){var b=this.exec("getAsBlob",a);return new c(this.getRuid(),b)}}),d}),b("widgets/image",["base","uploader","lib/image","widgets/widget"],function(a,b,c){var d,e=a.$;return d=function(a){var b=0,c=[],d=function(){for(var d;c.length&&a>b;)d=c.shift(),b+=d[0],d[1]()};return function(a,e,f){c.push([e,f]),a.once("destroy",function(){b-=e,setTimeout(d,1)}),setTimeout(d,1)}}(5242880),e.extend(b.options,{thumb:{width:110,height:110,quality:70,allowMagnify:!0,crop:!0,preserveHeaders:!1,type:"image/jpeg"},compress:{width:1600,height:1600,quality:90,allowMagnify:!1,crop:!1,preserveHeaders:!0}}),b.register({name:"image",makeThumb:function(a,b,f,g){var h,i;return a=this.request("get-file",a),a.type.match(/^image/)?(h=e.extend({},this.options.thumb),e.isPlainObject(f)&&(h=e.extend(h,f),f=null),f=f||h.width,g=g||h.height,i=new c(h),i.once("load",function(){a._info=a._info||i.info(),a._meta=a._meta||i.meta(),1>=f&&f>0&&(f=a._info.width*f),1>=g&&g>0&&(g=a._info.height*g),i.resize(f,g)}),i.once("complete",function(){b(!1,i.getAsDataUrl(h.type)),i.destroy()}),i.once("error",function(a){b(a||!0),i.destroy()}),void d(i,a.source.size,function(){a._info&&i.info(a._info),a._meta&&i.meta(a._meta),i.loadFromBlob(a.source)})):void b(!0)},beforeSendFile:function(b){var d,f,g=this.options.compress||this.options.resize,h=g&&g.compressSize||0,i=g&&g.noCompressIfLarger||!1;return b=this.request("get-file",b),!g||!~"image/jpeg,image/jpg".indexOf(b.type)||b.size=a&&a>0&&(a=b._info.width*a),1>=c&&c>0&&(c=b._info.height*c),d.resize(a,c)}),d.once("complete",function(){var a,c;try{a=d.getAsBlob(g.type),c=b.size,(!i||a.sizeb;b++)if(c=this._queue[b],a===c.getStatus())return c;return null},sort:function(a){"function"==typeof a&&this._queue.sort(a)},getFiles:function(){for(var a,b=[].slice.call(arguments,0),c=[],d=0,f=this._queue.length;f>d;d++)a=this._queue[d],(!b.length||~e.inArray(a.getStatus(),b))&&c.push(a);return c},removeFile:function(a){var b=this._map[a.id];b&&(delete this._map[a.id],a.destroy(),this.stats.numofDeleted++)},_fileAdded:function(a){var b=this,c=this._map[a.id];c||(this._map[a.id]=a,a.on("statuschange",function(a,c){b._onFileStatusChange(a,c)}))},_onFileStatusChange:function(a,b){var c=this.stats;switch(b){case f.PROGRESS:c.numOfProgress--;break;case f.QUEUED:c.numOfQueue--;break;case f.ERROR:c.numOfUploadFailed--;break;case f.INVALID:c.numOfInvalid--;break;case f.INTERRUPT:c.numofInterrupt--}switch(a){case f.QUEUED:c.numOfQueue++;break;case f.PROGRESS:c.numOfProgress++;break;case f.ERROR:c.numOfUploadFailed++;break;case f.COMPLETE:c.numOfSuccess++;break;case f.CANCELLED:c.numOfCancel++;break;case f.INVALID:c.numOfInvalid++;break;case f.INTERRUPT:c.numofInterrupt++}}}),b.installTo(d.prototype),d}),b("widgets/queue",["base","uploader","queue","file","lib/file","runtime/client","widgets/widget"],function(a,b,c,d,e,f){var g=a.$,h=/\.\w+$/,i=d.Status;return b.register({name:"queue",init:function(b){var d,e,h,i,j,k,l,m=this;if(g.isPlainObject(b.accept)&&(b.accept=[b.accept]),b.accept){for(j=[],h=0,e=b.accept.length;e>h;h++)i=b.accept[h].extensions,i&&j.push(i);j.length&&(k="\\."+j.join(",").replace(/,/g,"$|\\.").replace(/\*/g,".*")+"$"),m.accept=new RegExp(k,"i")}return m.queue=new c,m.stats=m.queue.stats,"html5"===this.request("predict-runtime-type")?(d=a.Deferred(),this.placeholder=l=new f("Placeholder"),l.connectRuntime({runtimeOrder:"html5"},function(){m._ruid=l.getRuid(),d.resolve()}),d.promise()):void 0},_wrapFile:function(a){if(!(a instanceof d)){if(!(a instanceof e)){if(!this._ruid)throw new Error("Can't add external files.");a=new e(this._ruid,a)}a=new d(a)}return a},acceptFile:function(a){var b=!a||!a.size||this.accept&&h.exec(a.name)&&!this.accept.test(a.name);return!b},_addFile:function(a){var b=this;return a=b._wrapFile(a),b.owner.trigger("beforeFileQueued",a)?b.acceptFile(a)?(b.queue.append(a),b.owner.trigger("fileQueued",a),a):void b.owner.trigger("error","Q_TYPE_DENIED",a):void 0},getFile:function(a){return this.queue.getFile(a)},addFile:function(a){var b=this;a.length||(a=[a]),a=g.map(a,function(a){return b._addFile(a)}),b.owner.trigger("filesQueued",a),b.options.auto&&setTimeout(function(){b.request("start-upload")},20)},getStats:function(){return this.stats},removeFile:function(a,b){var c=this;a=a.id?a:c.queue.getFile(a),this.request("cancel-file",a),b&&this.queue.removeFile(a)},getFiles:function(){return this.queue.getFiles.apply(this.queue,arguments)},fetchFile:function(){return this.queue.fetch.apply(this.queue,arguments)},retry:function(a,b){var c,d,e,f=this;if(a)return a=a.id?a:f.queue.getFile(a),a.setStatus(i.QUEUED),void(b||f.request("start-upload"));for(c=f.queue.getFiles(i.ERROR),d=0,e=c.length;e>d;d++)a=c[d],a.setStatus(i.QUEUED);f.request("start-upload")},sortFiles:function(){return this.queue.sort.apply(this.queue,arguments)},reset:function(){this.owner.trigger("reset"),this.queue=new c,this.stats=this.queue.stats},destroy:function(){this.reset(),this.placeholder&&this.placeholder.destroy()}})}),b("widgets/runtime",["uploader","runtime/runtime","widgets/widget"],function(a,b){return a.support=function(){return b.hasRuntime.apply(b,arguments)},a.register({name:"runtime",init:function(){if(!this.predictRuntimeType())throw Error("Runtime Error")},predictRuntimeType:function(){var a,c,d=this.options.runtimeOrder||b.orders,e=this.type;if(!e)for(d=d.split(/\s*,\s*/g),a=0,c=d.length;c>a;a++)if(b.hasRuntime(d[a])){this.type=e=d[a];break}return e}})}),b("lib/transport",["base","runtime/client","mediator"],function(a,b,c){function d(a){var c=this;a=c.options=e.extend(!0,{},d.options,a||{}),b.call(this,"Transport"),this._blob=null,this._formData=a.formData||{},this._headers=a.headers||{},this.on("progress",this._timeout),this.on("load error",function(){c.trigger("progress",1),clearTimeout(c._timer)})}var e=a.$;return d.options={server:"",method:"POST",withCredentials:!1,fileVal:"file",timeout:12e4,formData:{},headers:{},sendAsBinary:!1},e.extend(d.prototype,{appendBlob:function(a,b,c){var d=this,e=d.options;d.getRuid()&&d.disconnectRuntime(),d.connectRuntime(b.ruid,function(){d.exec("init")}),d._blob=b,e.fileVal=a||e.fileVal,e.filename=c||e.filename},append:function(a,b){"object"==typeof a?e.extend(this._formData,a):this._formData[a]=b},setRequestHeader:function(a,b){"object"==typeof a?e.extend(this._headers,a):this._headers[a]=b},send:function(a){this.exec("send",a),this._timeout()},abort:function(){return clearTimeout(this._timer),this.exec("abort")},destroy:function(){this.trigger("destroy"),this.off(),this.exec("destroy"),this.disconnectRuntime()},getResponse:function(){return this.exec("getResponse")},getResponseAsJson:function(){return this.exec("getResponseAsJson")},getStatus:function(){return this.exec("getStatus")},_timeout:function(){var a=this,b=a.options.timeout;b&&(clearTimeout(a._timer),a._timer=setTimeout(function(){a.abort(),a.trigger("error","timeout")},b))}}),c.installTo(d.prototype),d}),b("widgets/upload",["base","uploader","file","lib/transport","widgets/widget"],function(a,b,c,d){function e(a,b){var c,d,e=[],f=a.source,g=f.size,h=b?Math.ceil(g/b):1,i=0,j=0;for(d={file:a,has:function(){return!!e.length},shift:function(){return e.shift()},unshift:function(a){e.unshift(a)}};h>j;)c=Math.min(b,g-i),e.push({file:a,start:i,end:b?i+c:g,total:g,chunks:h,chunk:j++,cuted:d}),i+=c;return a.blocks=e.concat(),a.remaning=e.length,d}var f=a.$,g=a.isPromise,h=c.Status;f.extend(b.options,{prepareNextFile:!1,chunked:!1,chunkSize:5242880,chunkRetry:2,threads:3,formData:{}}),b.register({name:"upload",init:function(){var b=this.owner,c=this;this.runing=!1,this.progress=!1,b.on("startUpload",function(){c.progress=!0}).on("uploadFinished",function(){c.progress=!1}),this.pool=[],this.stack=[],this.pending=[],this.remaning=0,this.__tick=a.bindFn(this._tick,this),b.on("uploadComplete",function(a){a.blocks&&f.each(a.blocks,function(a,b){b.transport&&(b.transport.abort(),b.transport.destroy()),delete b.transport}),delete a.blocks,delete a.remaning})},reset:function(){this.request("stop-upload",!0),this.runing=!1,this.pool=[],this.stack=[],this.pending=[],this.remaning=0,this._trigged=!1,this._promise=null},startUpload:function(b){var c=this;if(f.each(c.request("get-files",h.INVALID),function(){c.request("remove-file",this)}),b)if(b=b.id?b:c.request("get-file",b),b.getStatus()===h.INTERRUPT)f.each(c.pool,function(a,c){c.file===b&&c.transport&&c.transport.send()}),b.setStatus(h.QUEUED);else{if(b.getStatus()===h.PROGRESS)return;b.setStatus(h.QUEUED)}else f.each(c.request("get-files",[h.INITED]),function(){this.setStatus(h.QUEUED)});if(!c.runing){c.runing=!0;var d=[];f.each(c.pool,function(a,b){var e=b.file;e.getStatus()===h.INTERRUPT&&(d.push(e),c._trigged=!1,b.transport&&b.transport.send())});for(var b;b=d.shift();)b.setStatus(h.PROGRESS);b||f.each(c.request("get-files",h.INTERRUPT),function(){this.setStatus(h.PROGRESS)}),c._trigged=!1,a.nextTick(c.__tick),c.owner.trigger("startUpload")}},stopUpload:function(b,c){var d=this;if(b===!0&&(c=b,b=null),d.runing!==!1){if(b){if(b=b.id?b:d.request("get-file",b),b.getStatus()!==h.PROGRESS&&b.getStatus()!==h.QUEUED)return;return b.setStatus(h.INTERRUPT),f.each(d.pool,function(a,c){c.file===b&&(c.transport&&c.transport.abort(),d._putback(c),d._popBlock(c))}),a.nextTick(d.__tick)}d.runing=!1,this._promise&&this._promise.file&&this._promise.file.setStatus(h.INTERRUPT),c&&f.each(d.pool,function(a,b){b.transport&&b.transport.abort(),b.file.setStatus(h.INTERRUPT)}),d.owner.trigger("stopUpload")}},cancelFile:function(a){a=a.id?a:this.request("get-file",a),a.blocks&&f.each(a.blocks,function(a,b){var c=b.transport;c&&(c.abort(),c.destroy(),delete b.transport)}),a.setStatus(h.CANCELLED),this.owner.trigger("fileDequeued",a)},isInProgress:function(){return!!this.progress},_getStats:function(){return this.request("get-stats")},skipFile:function(a,b){a=a.id?a:this.request("get-file",a),a.setStatus(b||h.COMPLETE),a.skipped=!0,a.blocks&&f.each(a.blocks,function(a,b){var c=b.transport;c&&(c.abort(),c.destroy(),delete b.transport)}),this.owner.trigger("uploadSkip",a)},_tick:function(){var b,c,d=this,e=d.options;return d._promise?d._promise.always(d.__tick):void(d.pool.length1&&~"http,abort".indexOf(a)&&b.retried1&&f.extend(m,{chunks:b.chunks,chunk:b.chunk}),i.trigger("uploadBeforeSend",b,m,n),l.appendBlob(j.fileVal,b.blob,k.name),l.append(m),l.setRequestHeader(n),l.send()},_finishFile:function(a,b,c){var d=this.owner;return d.request("after-send-file",arguments,function(){a.setStatus(h.COMPLETE),d.trigger("uploadSuccess",a,b,c)}).fail(function(b){a.getStatus()===h.PROGRESS&&a.setStatus(h.ERROR,b),d.trigger("uploadError",a,b) +}).always(function(){d.trigger("uploadComplete",a)})},updateFileProgress:function(a){var b=0,c=0;a.blocks&&(f.each(a.blocks,function(a,b){c+=(b.percentage||0)*(b.end-b.start)}),b=c/a.size,this.owner.trigger("uploadProgress",a,b||0))}})}),b("widgets/validator",["base","uploader","file","widgets/widget"],function(a,b,c){var d,e=a.$,f={};return d={addValidator:function(a,b){f[a]=b},removeValidator:function(a){delete f[a]}},b.register({name:"validator",init:function(){var b=this;a.nextTick(function(){e.each(f,function(){this.call(b.owner)})})}}),d.addValidator("fileNumLimit",function(){var a=this,b=a.options,c=0,d=parseInt(b.fileNumLimit,10),e=!0;d&&(a.on("beforeFileQueued",function(a){return c>=d&&e&&(e=!1,this.trigger("error","Q_EXCEED_NUM_LIMIT",d,a),setTimeout(function(){e=!0},1)),c>=d?!1:!0}),a.on("fileQueued",function(){c++}),a.on("fileDequeued",function(){c--}),a.on("reset",function(){c=0}))}),d.addValidator("fileSizeLimit",function(){var a=this,b=a.options,c=0,d=parseInt(b.fileSizeLimit,10),e=!0;d&&(a.on("beforeFileQueued",function(a){var b=c+a.size>d;return b&&e&&(e=!1,this.trigger("error","Q_EXCEED_SIZE_LIMIT",d,a),setTimeout(function(){e=!0},1)),b?!1:!0}),a.on("fileQueued",function(a){c+=a.size}),a.on("fileDequeued",function(a){c-=a.size}),a.on("reset",function(){c=0}))}),d.addValidator("fileSingleSizeLimit",function(){var a=this,b=a.options,d=b.fileSingleSizeLimit;d&&a.on("beforeFileQueued",function(a){return a.size>d?(a.setStatus(c.Status.INVALID,"exceed_size"),this.trigger("error","F_EXCEED_SIZE",d,a),!1):void 0})}),d.addValidator("duplicate",function(){function a(a){for(var b,c=0,d=0,e=a.length;e>d;d++)b=a.charCodeAt(d),c=b+(c<<6)+(c<<16)-c;return c}var b=this,c=b.options,d={};c.duplicate||(b.on("beforeFileQueued",function(b){var c=b.__hash||(b.__hash=a(b.name+b.size+b.lastModifiedDate));return d[c]?(this.trigger("error","F_DUPLICATE",b),!1):void 0}),b.on("fileQueued",function(a){var b=a.__hash;b&&(d[b]=!0)}),b.on("fileDequeued",function(a){var b=a.__hash;b&&delete d[b]}),b.on("reset",function(){d={}}))}),d}),b("lib/md5",["runtime/client","mediator"],function(a,b){function c(){a.call(this,"Md5")}return b.installTo(c.prototype),c.prototype.loadFromBlob=function(a){var b=this;b.getRuid()&&b.disconnectRuntime(),b.connectRuntime(a.ruid,function(){b.exec("init"),b.exec("loadFromBlob",a)})},c.prototype.getResult=function(){return this.exec("getResult")},c}),b("widgets/md5",["base","uploader","lib/md5","lib/blob","widgets/widget"],function(a,b,c,d){return b.register({name:"md5",md5File:function(b,e,f){var g=new c,h=a.Deferred(),i=b instanceof d?b:this.request("get-file",b).source;return g.on("progress load",function(a){a=a||{},h.notify(a.total?a.loaded/a.total:1)}),g.on("complete",function(){h.resolve(g.getResult())}),g.on("error",function(a){h.reject(a)}),arguments.length>1&&(e=e||0,f=f||0,0>e&&(e=i.size+e),0>f&&(f=i.size+f),f=Math.min(f,i.size),i=i.slice(e,f)),g.loadFromBlob(i),h.promise()}})}),b("runtime/compbase",[],function(){function a(a,b){this.owner=a,this.options=a.options,this.getRuntime=function(){return b},this.getRuid=function(){return b.uid},this.trigger=function(){return a.trigger.apply(a,arguments)}}return a}),b("runtime/html5/runtime",["base","runtime/runtime","runtime/compbase"],function(b,c,d){function e(){var a={},d=this,e=this.destroy;c.apply(d,arguments),d.type=f,d.exec=function(c,e){var f,h=this,i=h.uid,j=b.slice(arguments,2);return g[c]&&(f=a[i]=a[i]||new g[c](h,d),f[e])?f[e].apply(f,j):void 0},d.destroy=function(){return e&&e.apply(this,arguments)}}var f="html5",g={};return b.inherits(c,{constructor:e,init:function(){var a=this;setTimeout(function(){a.trigger("ready")},1)}}),e.register=function(a,c){var e=g[a]=b.inherits(d,c);return e},a.Blob&&a.FileReader&&a.DataView&&c.addRuntime(f,e),e}),b("runtime/html5/blob",["runtime/html5/runtime","lib/blob"],function(a,b){return a.register("Blob",{slice:function(a,c){var d=this.owner.source,e=d.slice||d.webkitSlice||d.mozSlice;return d=e.call(d,a,c),new b(this.getRuid(),d)}})}),b("runtime/html5/dnd",["base","runtime/html5/runtime","lib/file"],function(a,b,c){var d=a.$,e="webuploader-dnd-";return b.register("DragAndDrop",{init:function(){var b=this.elem=this.options.container;this.dragEnterHandler=a.bindFn(this._dragEnterHandler,this),this.dragOverHandler=a.bindFn(this._dragOverHandler,this),this.dragLeaveHandler=a.bindFn(this._dragLeaveHandler,this),this.dropHandler=a.bindFn(this._dropHandler,this),this.dndOver=!1,b.on("dragenter",this.dragEnterHandler),b.on("dragover",this.dragOverHandler),b.on("dragleave",this.dragLeaveHandler),b.on("drop",this.dropHandler),this.options.disableGlobalDnd&&(d(document).on("dragover",this.dragOverHandler),d(document).on("drop",this.dropHandler))},_dragEnterHandler:function(a){var b,c=this,d=c._denied||!1;return a=a.originalEvent||a,c.dndOver||(c.dndOver=!0,b=a.dataTransfer.items,b&&b.length&&(c._denied=d=!c.trigger("accept",b)),c.elem.addClass(e+"over"),c.elem[d?"addClass":"removeClass"](e+"denied")),a.dataTransfer.dropEffect=d?"none":"copy",!1},_dragOverHandler:function(a){var b=this.elem.parent().get(0);return b&&!d.contains(b,a.currentTarget)?!1:(clearTimeout(this._leaveTimer),this._dragEnterHandler.call(this,a),!1)},_dragLeaveHandler:function(){var a,b=this;return a=function(){b.dndOver=!1,b.elem.removeClass(e+"over "+e+"denied")},clearTimeout(b._leaveTimer),b._leaveTimer=setTimeout(a,100),!1},_dropHandler:function(a){var b,f,g=this,h=g.getRuid(),i=g.elem.parent().get(0);if(i&&!d.contains(i,a.currentTarget))return!1;a=a.originalEvent||a,b=a.dataTransfer;try{f=b.getData("text/html")}catch(j){}return f?void 0:(g._getTansferFiles(b,function(a){g.trigger("drop",d.map(a,function(a){return new c(h,a)}))}),g.dndOver=!1,g.elem.removeClass(e+"over"),!1)},_getTansferFiles:function(b,c){var d,e,f,g,h,i,j,k=[],l=[];for(d=b.items,e=b.files,j=!(!d||!d[0].webkitGetAsEntry),h=0,i=e.length;i>h;h++)f=e[h],g=d&&d[h],j&&g.webkitGetAsEntry().isDirectory?l.push(this._traverseDirectoryTree(g.webkitGetAsEntry(),k)):k.push(f);a.when.apply(a,l).done(function(){k.length&&c(k)})},_traverseDirectoryTree:function(b,c){var d=a.Deferred(),e=this;return b.isFile?b.file(function(a){c.push(a),d.resolve()}):b.isDirectory&&b.createReader().readEntries(function(b){var f,g=b.length,h=[],i=[];for(f=0;g>f;f++)h.push(e._traverseDirectoryTree(b[f],i));a.when.apply(a,h).then(function(){c.push.apply(c,i),d.resolve()},d.reject)}),d.promise()},destroy:function(){var a=this.elem;a&&(a.off("dragenter",this.dragEnterHandler),a.off("dragover",this.dragOverHandler),a.off("dragleave",this.dragLeaveHandler),a.off("drop",this.dropHandler),this.options.disableGlobalDnd&&(d(document).off("dragover",this.dragOverHandler),d(document).off("drop",this.dropHandler)))}})}),b("runtime/html5/filepaste",["base","runtime/html5/runtime","lib/file"],function(a,b,c){return b.register("FilePaste",{init:function(){var b,c,d,e,f=this.options,g=this.elem=f.container,h=".*";if(f.accept){for(b=[],c=0,d=f.accept.length;d>c;c++)e=f.accept[c].mimeTypes,e&&b.push(e);b.length&&(h=b.join(","),h=h.replace(/,/g,"|").replace(/\*/g,".*"))}this.accept=h=new RegExp(h,"i"),this.hander=a.bindFn(this._pasteHander,this),g.on("paste",this.hander)},_pasteHander:function(a){var b,d,e,f,g,h=[],i=this.getRuid();for(a=a.originalEvent||a,b=a.clipboardData.items,f=0,g=b.length;g>f;f++)d=b[f],"file"===d.kind&&(e=d.getAsFile())&&h.push(new c(i,e));h.length&&(a.preventDefault(),a.stopPropagation(),this.trigger("paste",h))},destroy:function(){this.elem.off("paste",this.hander)}})}),b("runtime/html5/filepicker",["base","runtime/html5/runtime"],function(a,b){var c=a.$;return b.register("FilePicker",{init:function(){var a,b,d,e,f=this.getRuntime().getContainer(),g=this,h=g.owner,i=g.options,j=this.label=c(document.createElement("label")),k=this.input=c(document.createElement("input"));if(k.attr("type","file"),k.attr("name",i.name),k.addClass("webuploader-element-invisible"),j.on("click",function(){k.trigger("click")}),j.css({opacity:0,width:"100%",height:"100%",display:"block",cursor:"pointer",background:"#ffffff"}),i.multiple&&k.attr("multiple","multiple"),i.accept&&i.accept.length>0){for(a=[],b=0,d=i.accept.length;d>b;b++)a.push(i.accept[b].mimeTypes);k.attr("accept",a.join(","))}f.append(k),f.append(j),e=function(a){h.trigger(a.type)},k.on("change",function(a){var b,d=arguments.callee;g.files=a.target.files,b=this.cloneNode(!0),b.value=null,this.parentNode.replaceChild(b,this),k.off(),k=c(b).on("change",d).on("mouseenter mouseleave",e),h.trigger("change")}),j.on("mouseenter mouseleave",e)},getFiles:function(){return this.files},destroy:function(){this.input.off(),this.label.off()}})}),b("runtime/html5/util",["base"],function(b){var c=a.createObjectURL&&a||a.URL&&URL.revokeObjectURL&&URL||a.webkitURL,d=b.noop,e=d;return c&&(d=function(){return c.createObjectURL.apply(c,arguments)},e=function(){return c.revokeObjectURL.apply(c,arguments)}),{createObjectURL:d,revokeObjectURL:e,dataURL2Blob:function(a){var b,c,d,e,f,g;for(g=a.split(","),b=~g[0].indexOf("base64")?atob(g[1]):decodeURIComponent(g[1]),d=new ArrayBuffer(b.length),c=new Uint8Array(d),e=0;ei&&(d=h.getUint16(i),d>=65504&&65519>=d||65534===d)&&(e=h.getUint16(i+2)+2,!(i+e>h.byteLength));){if(f=b.parsers[d],!c&&f)for(g=0;g6&&(l.imageHead=a.slice?a.slice(2,k):new Uint8Array(a).subarray(2,k))}return l}},updateImageHead:function(a,b){var c,d,e,f=this._parse(a,!0);return e=2,f.imageHead&&(e=2+f.imageHead.byteLength),d=a.slice?a.slice(e):new Uint8Array(a).subarray(e),c=new Uint8Array(b.byteLength+2+d.byteLength),c[0]=255,c[1]=216,c.set(new Uint8Array(b),2),c.set(new Uint8Array(d),b.byteLength+2),c.buffer}},a.parseMeta=function(){return b.parse.apply(b,arguments)},a.updateImageHead=function(){return b.updateImageHead.apply(b,arguments)},b}),b("runtime/html5/imagemeta/exif",["base","runtime/html5/imagemeta"],function(a,b){var c={};return c.ExifMap=function(){return this},c.ExifMap.prototype.map={Orientation:274},c.ExifMap.prototype.get=function(a){return this[a]||this[this.map[a]]},c.exifTagTypes={1:{getValue:function(a,b){return a.getUint8(b)},size:1},2:{getValue:function(a,b){return String.fromCharCode(a.getUint8(b))},size:1,ascii:!0},3:{getValue:function(a,b,c){return a.getUint16(b,c)},size:2},4:{getValue:function(a,b,c){return a.getUint32(b,c)},size:4},5:{getValue:function(a,b,c){return a.getUint32(b,c)/a.getUint32(b+4,c)},size:8},9:{getValue:function(a,b,c){return a.getInt32(b,c)},size:4},10:{getValue:function(a,b,c){return a.getInt32(b,c)/a.getInt32(b+4,c)},size:8}},c.exifTagTypes[7]=c.exifTagTypes[1],c.getExifValue=function(b,d,e,f,g,h){var i,j,k,l,m,n,o=c.exifTagTypes[f];if(!o)return void a.log("Invalid Exif data: Invalid tag type.");if(i=o.size*g,j=i>4?d+b.getUint32(e+8,h):e+8,j+i>b.byteLength)return void a.log("Invalid Exif data: Invalid data offset.");if(1===g)return o.getValue(b,j,h);for(k=[],l=0;g>l;l+=1)k[l]=o.getValue(b,j+l*o.size,h);if(o.ascii){for(m="",l=0;lb.byteLength)return void a.log("Invalid Exif data: Invalid directory offset.");if(g=b.getUint16(d,e),h=d+2+12*g,h+4>b.byteLength)return void a.log("Invalid Exif data: Invalid directory size.");for(i=0;g>i;i+=1)this.parseExifTag(b,c,d+2+12*i,e,f);return b.getUint32(h,e)},c.parseExifData=function(b,d,e,f){var g,h,i=d+10;if(1165519206===b.getUint32(d+4)){if(i+8>b.byteLength)return void a.log("Invalid Exif data: Invalid segment size.");if(0!==b.getUint16(d+8))return void a.log("Invalid Exif data: Missing byte alignment offset.");switch(b.getUint16(i)){case 18761:g=!0;break;case 19789:g=!1;break;default:return void a.log("Invalid Exif data: Invalid byte alignment marker.")}if(42!==b.getUint16(i+2,g))return void a.log("Invalid Exif data: Missing TIFF marker.");h=b.getUint32(i+4,g),f.exif=new c.ExifMap,h=c.parseExifTags(b,i,i+h,g,f)}},b.parsers[65505].push(c.parseExifData),c}),b("runtime/html5/jpegencoder",[],function(){function a(a){function b(a){for(var b=[16,11,10,16,24,40,51,61,12,12,14,19,26,58,60,55,14,13,16,24,40,57,69,56,14,17,22,29,51,87,80,62,18,22,37,56,68,109,103,77,24,35,55,64,81,104,113,92,49,64,78,87,103,121,120,101,72,92,95,98,112,100,103,99],c=0;64>c;c++){var d=y((b[c]*a+50)/100);1>d?d=1:d>255&&(d=255),z[P[c]]=d}for(var e=[17,18,24,47,99,99,99,99,18,21,26,66,99,99,99,99,24,26,56,99,99,99,99,99,47,66,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99],f=0;64>f;f++){var g=y((e[f]*a+50)/100);1>g?g=1:g>255&&(g=255),A[P[f]]=g}for(var h=[1,1.387039845,1.306562965,1.175875602,1,.785694958,.5411961,.275899379],i=0,j=0;8>j;j++)for(var k=0;8>k;k++)B[i]=1/(z[P[i]]*h[j]*h[k]*8),C[i]=1/(A[P[i]]*h[j]*h[k]*8),i++}function c(a,b){for(var c=0,d=0,e=new Array,f=1;16>=f;f++){for(var g=1;g<=a[f];g++)e[b[d]]=[],e[b[d]][0]=c,e[b[d]][1]=f,d++,c++;c*=2}return e}function d(){t=c(Q,R),u=c(U,V),v=c(S,T),w=c(W,X)}function e(){for(var a=1,b=2,c=1;15>=c;c++){for(var d=a;b>d;d++)E[32767+d]=c,D[32767+d]=[],D[32767+d][1]=c,D[32767+d][0]=d;for(var e=-(b-1);-a>=e;e++)E[32767+e]=c,D[32767+e]=[],D[32767+e][1]=c,D[32767+e][0]=b-1+e;a<<=1,b<<=1}}function f(){for(var a=0;256>a;a++)O[a]=19595*a,O[a+256>>0]=38470*a,O[a+512>>0]=7471*a+32768,O[a+768>>0]=-11059*a,O[a+1024>>0]=-21709*a,O[a+1280>>0]=32768*a+8421375,O[a+1536>>0]=-27439*a,O[a+1792>>0]=-5329*a}function g(a){for(var b=a[0],c=a[1]-1;c>=0;)b&1<J&&(255==I?(h(255),h(0)):h(I),J=7,I=0)}function h(a){H.push(N[a])}function i(a){h(a>>8&255),h(255&a)}function j(a,b){var c,d,e,f,g,h,i,j,k,l=0,m=8,n=64;for(k=0;m>k;++k){c=a[l],d=a[l+1],e=a[l+2],f=a[l+3],g=a[l+4],h=a[l+5],i=a[l+6],j=a[l+7];var o=c+j,p=c-j,q=d+i,r=d-i,s=e+h,t=e-h,u=f+g,v=f-g,w=o+u,x=o-u,y=q+s,z=q-s;a[l]=w+y,a[l+4]=w-y;var A=.707106781*(z+x);a[l+2]=x+A,a[l+6]=x-A,w=v+t,y=t+r,z=r+p;var B=.382683433*(w-z),C=.5411961*w+B,D=1.306562965*z+B,E=.707106781*y,G=p+E,H=p-E;a[l+5]=H+C,a[l+3]=H-C,a[l+1]=G+D,a[l+7]=G-D,l+=8}for(l=0,k=0;m>k;++k){c=a[l],d=a[l+8],e=a[l+16],f=a[l+24],g=a[l+32],h=a[l+40],i=a[l+48],j=a[l+56];var I=c+j,J=c-j,K=d+i,L=d-i,M=e+h,N=e-h,O=f+g,P=f-g,Q=I+O,R=I-O,S=K+M,T=K-M;a[l]=Q+S,a[l+32]=Q-S;var U=.707106781*(T+R);a[l+16]=R+U,a[l+48]=R-U,Q=P+N,S=N+L,T=L+J;var V=.382683433*(Q-T),W=.5411961*Q+V,X=1.306562965*T+V,Y=.707106781*S,Z=J+Y,$=J-Y;a[l+40]=$+W,a[l+24]=$-W,a[l+8]=Z+X,a[l+56]=Z-X,l++}var _;for(k=0;n>k;++k)_=a[k]*b[k],F[k]=_>0?_+.5|0:_-.5|0;return F}function k(){i(65504),i(16),h(74),h(70),h(73),h(70),h(0),h(1),h(1),h(0),i(1),i(1),h(0),h(0)}function l(a,b){i(65472),i(17),h(8),i(b),i(a),h(3),h(1),h(17),h(0),h(2),h(17),h(1),h(3),h(17),h(1)}function m(){i(65499),i(132),h(0);for(var a=0;64>a;a++)h(z[a]);h(1);for(var b=0;64>b;b++)h(A[b])}function n(){i(65476),i(418),h(0);for(var a=0;16>a;a++)h(Q[a+1]);for(var b=0;11>=b;b++)h(R[b]);h(16);for(var c=0;16>c;c++)h(S[c+1]);for(var d=0;161>=d;d++)h(T[d]);h(1);for(var e=0;16>e;e++)h(U[e+1]);for(var f=0;11>=f;f++)h(V[f]);h(17);for(var g=0;16>g;g++)h(W[g+1]);for(var j=0;161>=j;j++)h(X[j])}function o(){i(65498),i(12),h(3),h(1),h(0),h(2),h(17),h(3),h(17),h(0),h(63),h(0)}function p(a,b,c,d,e){for(var f,h=e[0],i=e[240],k=16,l=63,m=64,n=j(a,b),o=0;m>o;++o)G[P[o]]=n[o];var p=G[0]-c;c=G[0],0==p?g(d[0]):(f=32767+p,g(d[E[f]]),g(D[f]));for(var q=63;q>0&&0==G[q];q--);if(0==q)return g(h),c;for(var r,s=1;q>=s;){for(var t=s;0==G[s]&&q>=s;++s);var u=s-t;if(u>=k){r=u>>4;for(var v=1;r>=v;++v)g(i);u=15&u}f=32767+G[s],g(e[(u<<4)+E[f]]),g(D[f]),s++}return q!=l&&g(h),c}function q(){for(var a=String.fromCharCode,b=0;256>b;b++)N[b]=a(b)}function r(a){if(0>=a&&(a=1),a>100&&(a=100),x!=a){var c=0;c=Math.floor(50>a?5e3/a:200-2*a),b(c),x=a}}function s(){a||(a=50),q(),d(),e(),f(),r(a)}var t,u,v,w,x,y=(Math.round,Math.floor),z=new Array(64),A=new Array(64),B=new Array(64),C=new Array(64),D=new Array(65535),E=new Array(65535),F=new Array(64),G=new Array(64),H=[],I=0,J=7,K=new Array(64),L=new Array(64),M=new Array(64),N=new Array(256),O=new Array(2048),P=[0,1,5,6,14,15,27,28,2,4,7,13,16,26,29,42,3,8,12,17,25,30,41,43,9,11,18,24,31,40,44,53,10,19,23,32,39,45,52,54,20,22,33,38,46,51,55,60,21,34,37,47,50,56,59,61,35,36,48,49,57,58,62,63],Q=[0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0],R=[0,1,2,3,4,5,6,7,8,9,10,11],S=[0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,125],T=[1,2,3,0,4,17,5,18,33,49,65,6,19,81,97,7,34,113,20,50,129,145,161,8,35,66,177,193,21,82,209,240,36,51,98,114,130,9,10,22,23,24,25,26,37,38,39,40,41,42,52,53,54,55,56,57,58,67,68,69,70,71,72,73,74,83,84,85,86,87,88,89,90,99,100,101,102,103,104,105,106,115,116,117,118,119,120,121,122,131,132,133,134,135,136,137,138,146,147,148,149,150,151,152,153,154,162,163,164,165,166,167,168,169,170,178,179,180,181,182,183,184,185,186,194,195,196,197,198,199,200,201,202,210,211,212,213,214,215,216,217,218,225,226,227,228,229,230,231,232,233,234,241,242,243,244,245,246,247,248,249,250],U=[0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0],V=[0,1,2,3,4,5,6,7,8,9,10,11],W=[0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,119],X=[0,1,2,3,17,4,5,33,49,6,18,65,81,7,97,113,19,34,50,129,8,20,66,145,161,177,193,9,35,51,82,240,21,98,114,209,10,22,36,52,225,37,241,23,24,25,26,38,39,40,41,42,53,54,55,56,57,58,67,68,69,70,71,72,73,74,83,84,85,86,87,88,89,90,99,100,101,102,103,104,105,106,115,116,117,118,119,120,121,122,130,131,132,133,134,135,136,137,138,146,147,148,149,150,151,152,153,154,162,163,164,165,166,167,168,169,170,178,179,180,181,182,183,184,185,186,194,195,196,197,198,199,200,201,202,210,211,212,213,214,215,216,217,218,226,227,228,229,230,231,232,233,234,242,243,244,245,246,247,248,249,250];this.encode=function(a,b){b&&r(b),H=new Array,I=0,J=7,i(65496),k(),m(),l(a.width,a.height),n(),o();var c=0,d=0,e=0;I=0,J=7,this.encode.displayName="_encode_";for(var f,h,j,q,s,x,y,z,A,D=a.data,E=a.width,F=a.height,G=4*E,N=0;F>N;){for(f=0;G>f;){for(s=G*N+f,x=s,y=-1,z=0,A=0;64>A;A++)z=A>>3,y=4*(7&A),x=s+z*G+y,N+z>=F&&(x-=G*(N+1+z-F)),f+y>=G&&(x-=f+y-G+4),h=D[x++],j=D[x++],q=D[x++],K[A]=(O[h]+O[j+256>>0]+O[q+512>>0]>>16)-128,L[A]=(O[h+768>>0]+O[j+1024>>0]+O[q+1280>>0]>>16)-128,M[A]=(O[h+1280>>0]+O[j+1536>>0]+O[q+1792>>0]>>16)-128;c=p(K,B,c,t,v),d=p(L,C,d,u,w),e=p(M,C,e,u,w),f+=32}N+=8}if(J>=0){var P=[];P[1]=J+1,P[0]=(1<i;)e=d[4*(k-1)+3],0===e?j=k:i=k,k=j+i>>1;return f=k/c,0===f?1:f}function c(a){var b,c,d=a.naturalWidth,e=a.naturalHeight;return d*e>1048576?(b=document.createElement("canvas"),b.width=b.height=1,c=b.getContext("2d"),c.drawImage(a,-d+1,0),0===c.getImageData(0,0,1,1).data[3]):!1}return a.os.ios?a.os.ios>=7?function(a,c,d,e,f,g){var h=c.naturalWidth,i=c.naturalHeight,j=b(c,h,i);return a.getContext("2d").drawImage(c,0,0,h*j,i*j,d,e,f,g)}:function(a,d,e,f,g,h){var i,j,k,l,m,n,o,p=d.naturalWidth,q=d.naturalHeight,r=a.getContext("2d"),s=c(d),t="image/jpeg"===this.type,u=1024,v=0,w=0;for(s&&(p/=2,q/=2),r.save(),i=document.createElement("canvas"),i.width=i.height=u,j=i.getContext("2d"),k=t?b(d,p,q):1,l=Math.ceil(u*g/p),m=Math.ceil(u*h/q/k);q>v;){for(n=0,o=0;p>n;)j.clearRect(0,0,u,u),j.drawImage(d,-n,-v),r.drawImage(i,0,0,u,u,e+o,f+w,l,m),n+=u,o+=l;v+=u,w+=m}r.restore(),i=j=null}:function(b){var c=a.slice(arguments,1),d=b.getContext("2d");d.drawImage.apply(d,c)}}()})}),b("runtime/html5/transport",["base","runtime/html5/runtime"],function(a,b){var c=a.noop,d=a.$;return b.register("Transport",{init:function(){this._status=0,this._response=null},send:function(){var b,c,e,f=this.owner,g=this.options,h=this._initAjax(),i=f._blob,j=g.server;g.sendAsBinary?(j+=(/\?/.test(j)?"&":"?")+d.param(f._formData),c=i.getSource()):(b=new FormData,d.each(f._formData,function(a,c){b.append(a,c)}),b.append(g.fileVal,i.getSource(),g.filename||f._formData.name||"")),g.withCredentials&&"withCredentials"in h?(h.open(g.method,j,!0),h.withCredentials=!0):h.open(g.method,j),this._setRequestHeader(h,g.headers),c?(h.overrideMimeType&&h.overrideMimeType("application/octet-stream"),a.os.android?(e=new FileReader,e.onload=function(){h.send(this.result),e=e.onload=null},e.readAsArrayBuffer(c)):h.send(c)):h.send(b)},getResponse:function(){return this._response},getResponseAsJson:function(){return this._parseJson(this._response)},getStatus:function(){return this._status},abort:function(){var a=this._xhr;a&&(a.upload.onprogress=c,a.onreadystatechange=c,a.abort(),this._xhr=a=null)},destroy:function(){this.abort()},_initAjax:function(){var a=this,b=new XMLHttpRequest,d=this.options;return!d.withCredentials||"withCredentials"in b||"undefined"==typeof XDomainRequest||(b=new XDomainRequest),b.upload.onprogress=function(b){var c=0;return b.lengthComputable&&(c=b.loaded/b.total),a.trigger("progress",c)},b.onreadystatechange=function(){return 4===b.readyState?(b.upload.onprogress=c,b.onreadystatechange=c,a._xhr=null,a._status=b.status,b.status>=200&&b.status<300?(a._response=b.responseText,a.trigger("load")):b.status>=500&&b.status<600?(a._response=b.responseText,a.trigger("error","server")):a.trigger("error",a._status?"http":"abort")):void 0},a._xhr=b,b},_setRequestHeader:function(a,b){d.each(b,function(b,c){a.setRequestHeader(b,c)})},_parseJson:function(a){var b;try{b=JSON.parse(a)}catch(c){b={}}return b}})}),b("runtime/html5/md5",["runtime/html5/runtime"],function(a){var b=function(a,b){return a+b&4294967295},c=function(a,c,d,e,f,g){return c=b(b(c,a),b(e,g)),b(c<>>32-f,d)},d=function(a,b,d,e,f,g,h){return c(b&d|~b&e,a,b,f,g,h)},e=function(a,b,d,e,f,g,h){return c(b&e|d&~e,a,b,f,g,h)},f=function(a,b,d,e,f,g,h){return c(b^d^e,a,b,f,g,h)},g=function(a,b,d,e,f,g,h){return c(d^(b|~e),a,b,f,g,h)},h=function(a,c){var h=a[0],i=a[1],j=a[2],k=a[3];h=d(h,i,j,k,c[0],7,-680876936),k=d(k,h,i,j,c[1],12,-389564586),j=d(j,k,h,i,c[2],17,606105819),i=d(i,j,k,h,c[3],22,-1044525330),h=d(h,i,j,k,c[4],7,-176418897),k=d(k,h,i,j,c[5],12,1200080426),j=d(j,k,h,i,c[6],17,-1473231341),i=d(i,j,k,h,c[7],22,-45705983),h=d(h,i,j,k,c[8],7,1770035416),k=d(k,h,i,j,c[9],12,-1958414417),j=d(j,k,h,i,c[10],17,-42063),i=d(i,j,k,h,c[11],22,-1990404162),h=d(h,i,j,k,c[12],7,1804603682),k=d(k,h,i,j,c[13],12,-40341101),j=d(j,k,h,i,c[14],17,-1502002290),i=d(i,j,k,h,c[15],22,1236535329),h=e(h,i,j,k,c[1],5,-165796510),k=e(k,h,i,j,c[6],9,-1069501632),j=e(j,k,h,i,c[11],14,643717713),i=e(i,j,k,h,c[0],20,-373897302),h=e(h,i,j,k,c[5],5,-701558691),k=e(k,h,i,j,c[10],9,38016083),j=e(j,k,h,i,c[15],14,-660478335),i=e(i,j,k,h,c[4],20,-405537848),h=e(h,i,j,k,c[9],5,568446438),k=e(k,h,i,j,c[14],9,-1019803690),j=e(j,k,h,i,c[3],14,-187363961),i=e(i,j,k,h,c[8],20,1163531501),h=e(h,i,j,k,c[13],5,-1444681467),k=e(k,h,i,j,c[2],9,-51403784),j=e(j,k,h,i,c[7],14,1735328473),i=e(i,j,k,h,c[12],20,-1926607734),h=f(h,i,j,k,c[5],4,-378558),k=f(k,h,i,j,c[8],11,-2022574463),j=f(j,k,h,i,c[11],16,1839030562),i=f(i,j,k,h,c[14],23,-35309556),h=f(h,i,j,k,c[1],4,-1530992060),k=f(k,h,i,j,c[4],11,1272893353),j=f(j,k,h,i,c[7],16,-155497632),i=f(i,j,k,h,c[10],23,-1094730640),h=f(h,i,j,k,c[13],4,681279174),k=f(k,h,i,j,c[0],11,-358537222),j=f(j,k,h,i,c[3],16,-722521979),i=f(i,j,k,h,c[6],23,76029189),h=f(h,i,j,k,c[9],4,-640364487),k=f(k,h,i,j,c[12],11,-421815835),j=f(j,k,h,i,c[15],16,530742520),i=f(i,j,k,h,c[2],23,-995338651),h=g(h,i,j,k,c[0],6,-198630844),k=g(k,h,i,j,c[7],10,1126891415),j=g(j,k,h,i,c[14],15,-1416354905),i=g(i,j,k,h,c[5],21,-57434055),h=g(h,i,j,k,c[12],6,1700485571),k=g(k,h,i,j,c[3],10,-1894986606),j=g(j,k,h,i,c[10],15,-1051523),i=g(i,j,k,h,c[1],21,-2054922799),h=g(h,i,j,k,c[8],6,1873313359),k=g(k,h,i,j,c[15],10,-30611744),j=g(j,k,h,i,c[6],15,-1560198380),i=g(i,j,k,h,c[13],21,1309151649),h=g(h,i,j,k,c[4],6,-145523070),k=g(k,h,i,j,c[11],10,-1120210379),j=g(j,k,h,i,c[2],15,718787259),i=g(i,j,k,h,c[9],21,-343485551),a[0]=b(h,a[0]),a[1]=b(i,a[1]),a[2]=b(j,a[2]),a[3]=b(k,a[3])},i=function(a){var b,c=[];for(b=0;64>b;b+=4)c[b>>2]=a.charCodeAt(b)+(a.charCodeAt(b+1)<<8)+(a.charCodeAt(b+2)<<16)+(a.charCodeAt(b+3)<<24);return c},j=function(a){var b,c=[];for(b=0;64>b;b+=4)c[b>>2]=a[b]+(a[b+1]<<8)+(a[b+2]<<16)+(a[b+3]<<24);return c},k=function(a){var b,c,d,e,f,g,j=a.length,k=[1732584193,-271733879,-1732584194,271733878];for(b=64;j>=b;b+=64)h(k,i(a.substring(b-64,b)));for(a=a.substring(b-64),c=a.length,d=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],b=0;c>b;b+=1)d[b>>2]|=a.charCodeAt(b)<<(b%4<<3);if(d[b>>2]|=128<<(b%4<<3),b>55)for(h(k,d),b=0;16>b;b+=1)d[b]=0;return e=8*j,e=e.toString(16).match(/(.*?)(.{0,8})$/),f=parseInt(e[2],16),g=parseInt(e[1],16)||0,d[14]=f,d[15]=g,h(k,d),k},l=function(a){var b,c,d,e,f,g,i=a.length,k=[1732584193,-271733879,-1732584194,271733878];for(b=64;i>=b;b+=64)h(k,j(a.subarray(b-64,b)));for(a=i>b-64?a.subarray(b-64):new Uint8Array(0),c=a.length,d=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],b=0;c>b;b+=1)d[b>>2]|=a[b]<<(b%4<<3);if(d[b>>2]|=128<<(b%4<<3),b>55)for(h(k,d),b=0;16>b;b+=1)d[b]=0;return e=8*i,e=e.toString(16).match(/(.*?)(.{0,8})$/),f=parseInt(e[2],16),g=parseInt(e[1],16)||0,d[14]=f,d[15]=g,h(k,d),k},m=["0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"],n=function(a){var b,c="";for(b=0;4>b;b+=1)c+=m[a>>8*b+4&15]+m[a>>8*b&15];return c},o=function(a){var b;for(b=0;b>16)+(b>>16)+(c>>16);return d<<16|65535&c}),q.prototype.append=function(a){return/[\u0080-\uFFFF]/.test(a)&&(a=unescape(encodeURIComponent(a))),this.appendBinary(a),this},q.prototype.appendBinary=function(a){this._buff+=a,this._length+=a.length;var b,c=this._buff.length;for(b=64;c>=b;b+=64)h(this._state,i(this._buff.substring(b-64,b)));return this._buff=this._buff.substr(b-64),this},q.prototype.end=function(a){var b,c,d=this._buff,e=d.length,f=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];for(b=0;e>b;b+=1)f[b>>2]|=d.charCodeAt(b)<<(b%4<<3);return this._finish(f,e),c=a?this._state:o(this._state),this.reset(),c},q.prototype._finish=function(a,b){var c,d,e,f=b;if(a[f>>2]|=128<<(f%4<<3),f>55)for(h(this._state,a),f=0;16>f;f+=1)a[f]=0;c=8*this._length,c=c.toString(16).match(/(.*?)(.{0,8})$/),d=parseInt(c[2],16),e=parseInt(c[1],16)||0,a[14]=d,a[15]=e,h(this._state,a)},q.prototype.reset=function(){return this._buff="",this._length=0,this._state=[1732584193,-271733879,-1732584194,271733878],this},q.prototype.destroy=function(){delete this._state,delete this._buff,delete this._length},q.hash=function(a,b){/[\u0080-\uFFFF]/.test(a)&&(a=unescape(encodeURIComponent(a)));var c=k(a);return b?c:o(c)},q.hashBinary=function(a,b){var c=k(a);return b?c:o(c)},q.ArrayBuffer=function(){this.reset()},q.ArrayBuffer.prototype.append=function(a){var b,c=this._concatArrayBuffer(this._buff,a),d=c.length;for(this._length+=a.byteLength,b=64;d>=b;b+=64)h(this._state,j(c.subarray(b-64,b)));return this._buff=d>b-64?c.subarray(b-64):new Uint8Array(0),this},q.ArrayBuffer.prototype.end=function(a){var b,c,d=this._buff,e=d.length,f=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]; +for(b=0;e>b;b+=1)f[b>>2]|=d[b]<<(b%4<<3);return this._finish(f,e),c=a?this._state:o(this._state),this.reset(),c},q.ArrayBuffer.prototype._finish=q.prototype._finish,q.ArrayBuffer.prototype.reset=function(){return this._buff=new Uint8Array(0),this._length=0,this._state=[1732584193,-271733879,-1732584194,271733878],this},q.ArrayBuffer.prototype.destroy=q.prototype.destroy,q.ArrayBuffer.prototype._concatArrayBuffer=function(a,b){var c=a.length,d=new Uint8Array(c+b.byteLength);return d.set(a),d.set(new Uint8Array(b),c),d},q.ArrayBuffer.hash=function(a,b){var c=l(new Uint8Array(a));return b?c:o(c)},a.register("Md5",{init:function(){},loadFromBlob:function(a){var b,c,d=a.getSource(),e=2097152,f=Math.ceil(d.size/e),g=0,h=this.owner,i=new q.ArrayBuffer,j=this,k=d.mozSlice||d.webkitSlice||d.slice;c=new FileReader,(b=function(){var l,m;l=g*e,m=Math.min(l+e,d.size),c.onload=function(b){i.append(b.target.result),h.trigger("progress",{total:a.size,loaded:m})},c.onloadend=function(){c.onloadend=c.onload=null,++g',c.html(a)},getFlash:function(){return this._flash?this._flash:(this._flash=g("#"+this.uid).get(0),this._flash)}}),f.register=function(a,c){return c=i[a]=b.inherits(d,g.extend({flashExec:function(){var a=this.owner,b=this.getRuntime();return b.flashExec.apply(a,arguments)}},c))},e()>=11.4&&c.addRuntime(h,f),f}),b("runtime/flash/filepicker",["base","runtime/flash/runtime"],function(a,b){var c=a.$;return b.register("FilePicker",{init:function(a){var b,d,e=c.extend({},a);for(b=e.accept&&e.accept.length,d=0;b>d;d++)e.accept[d].title||(e.accept[d].title="Files");delete e.button,delete e.id,delete e.container,this.flashExec("FilePicker","init",e)},destroy:function(){this.flashExec("FilePicker","destroy")}})}),b("runtime/flash/image",["runtime/flash/runtime"],function(a){return a.register("Image",{loadFromBlob:function(a){var b=this.owner;b.info()&&this.flashExec("Image","info",b.info()),b.meta()&&this.flashExec("Image","meta",b.meta()),this.flashExec("Image","loadFromBlob",a.uid)}})}),b("runtime/flash/transport",["base","runtime/flash/runtime","runtime/client"],function(b,c,d){var e=b.$;return c.register("Transport",{init:function(){this._status=0,this._response=null,this._responseJson=null},send:function(){var a,b=this.owner,c=this.options,d=this._initAjax(),f=b._blob,g=c.server;d.connectRuntime(f.ruid),c.sendAsBinary?(g+=(/\?/.test(g)?"&":"?")+e.param(b._formData),a=f.uid):(e.each(b._formData,function(a,b){d.exec("append",a,b)}),d.exec("appendBlob",c.fileVal,f.uid,c.filename||b._formData.name||"")),this._setRequestHeader(d,c.headers),d.exec("send",{method:c.method,url:g,forceURLStream:c.forceURLStream,mimeType:"application/octet-stream"},a)},getStatus:function(){return this._status},getResponse:function(){return this._response||""},getResponseAsJson:function(){return this._responseJson},abort:function(){var a=this._xhr;a&&(a.exec("abort"),a.destroy(),this._xhr=a=null)},destroy:function(){this.abort()},_initAjax:function(){var b=this,c=new d("XMLHttpRequest");return c.on("uploadprogress progress",function(a){var c=a.loaded/a.total;return c=Math.min(1,Math.max(0,c)),b.trigger("progress",c)}),c.on("load",function(){var d,e=c.exec("getStatus"),f=!1,g="";return c.off(),b._xhr=null,e>=200&&300>e?f=!0:e>=500&&600>e?(f=!0,g="server"):g="http",f&&(b._response=c.exec("getResponse"),b._response=decodeURIComponent(b._response),d=a.JSON&&a.JSON.parse||function(a){try{return new Function("return "+a).call()}catch(b){return{}}},b._responseJson=b._response?d(b._response):{}),c.destroy(),c=null,g?b.trigger("error",g):b.trigger("load")}),c.on("error",function(){c.off(),b._xhr=null,b.trigger("error","http")}),b._xhr=c,c},_setRequestHeader:function(a,b){e.each(b,function(b,c){a.exec("setRequestHeader",b,c)})}})}),b("runtime/flash/blob",["runtime/flash/runtime","lib/blob"],function(a,b){return a.register("Blob",{slice:function(a,c){var d=this.flashExec("Blob","slice",a,c);return new b(d.uid,d)}})}),b("runtime/flash/md5",["runtime/flash/runtime"],function(a){return a.register("Md5",{init:function(){},loadFromBlob:function(a){return this.flashExec("Md5","loadFromBlob",a.uid)}})}),b("preset/all",["base","widgets/filednd","widgets/filepaste","widgets/filepicker","widgets/image","widgets/queue","widgets/runtime","widgets/upload","widgets/validator","widgets/md5","runtime/html5/blob","runtime/html5/dnd","runtime/html5/filepaste","runtime/html5/filepicker","runtime/html5/imagemeta/exif","runtime/html5/androidpatch","runtime/html5/image","runtime/html5/transport","runtime/html5/md5","runtime/flash/filepicker","runtime/flash/image","runtime/flash/transport","runtime/flash/blob","runtime/flash/md5"],function(a){return a}),b("webuploader",["preset/all"],function(a){return a}),c("webuploader")}); \ No newline at end of file diff --git a/static/plugins/webuploader/webuploader.withoutimage.js b/static/plugins/webuploader/webuploader.withoutimage.js new file mode 100644 index 00000000..9e6e3a38 --- /dev/null +++ b/static/plugins/webuploader/webuploader.withoutimage.js @@ -0,0 +1,4993 @@ +/*! WebUploader 0.1.5 */ + + +/** + * @fileOverview 让内部各个部件的代码可以用[amd](https://github.com/amdjs/amdjs-api/wiki/AMD)模块定义方式组织起来。 + * + * AMD API 内部的简单不完全实现,请忽略。只有当WebUploader被合并成一个文件的时候才会引入。 + */ +(function( root, factory ) { + var modules = {}, + + // 内部require, 简单不完全实现。 + // https://github.com/amdjs/amdjs-api/wiki/require + _require = function( deps, callback ) { + var args, len, i; + + // 如果deps不是数组,则直接返回指定module + if ( typeof deps === 'string' ) { + return getModule( deps ); + } else { + args = []; + for( len = deps.length, i = 0; i < len; i++ ) { + args.push( getModule( deps[ i ] ) ); + } + + return callback.apply( null, args ); + } + }, + + // 内部define,暂时不支持不指定id. + _define = function( id, deps, factory ) { + if ( arguments.length === 2 ) { + factory = deps; + deps = null; + } + + _require( deps || [], function() { + setModule( id, factory, arguments ); + }); + }, + + // 设置module, 兼容CommonJs写法。 + setModule = function( id, factory, args ) { + var module = { + exports: factory + }, + returned; + + if ( typeof factory === 'function' ) { + args.length || (args = [ _require, module.exports, module ]); + returned = factory.apply( null, args ); + returned !== undefined && (module.exports = returned); + } + + modules[ id ] = module.exports; + }, + + // 根据id获取module + getModule = function( id ) { + var module = modules[ id ] || root[ id ]; + + if ( !module ) { + throw new Error( '`' + id + '` is undefined' ); + } + + return module; + }, + + // 将所有modules,将路径ids装换成对象。 + exportsTo = function( obj ) { + var key, host, parts, part, last, ucFirst; + + // make the first character upper case. + ucFirst = function( str ) { + return str && (str.charAt( 0 ).toUpperCase() + str.substr( 1 )); + }; + + for ( key in modules ) { + host = obj; + + if ( !modules.hasOwnProperty( key ) ) { + continue; + } + + parts = key.split('/'); + last = ucFirst( parts.pop() ); + + while( (part = ucFirst( parts.shift() )) ) { + host[ part ] = host[ part ] || {}; + host = host[ part ]; + } + + host[ last ] = modules[ key ]; + } + + return obj; + }, + + makeExport = function( dollar ) { + root.__dollar = dollar; + + // exports every module. + return exportsTo( factory( root, _define, _require ) ); + }, + + origin; + + if ( typeof module === 'object' && typeof module.exports === 'object' ) { + + // For CommonJS and CommonJS-like environments where a proper window is present, + module.exports = makeExport(); + } else if ( typeof define === 'function' && define.amd ) { + + // Allow using this built library as an AMD module + // in another project. That other project will only + // see this AMD call, not the internal modules in + // the closure below. + define([ 'jquery' ], makeExport ); + } else { + + // Browser globals case. Just assign the + // result to a property on the global. + origin = root.WebUploader; + root.WebUploader = makeExport(); + root.WebUploader.noConflict = function() { + root.WebUploader = origin; + }; + } +})( window, function( window, define, require ) { + + + /** + * @fileOverview jQuery or Zepto + */ + define('dollar-third',[],function() { + var $ = window.__dollar || window.jQuery || window.Zepto; + + if ( !$ ) { + throw new Error('jQuery or Zepto not found!'); + } + + return $; + }); + /** + * @fileOverview Dom 操作相关 + */ + define('dollar',[ + 'dollar-third' + ], function( _ ) { + return _; + }); + /** + * @fileOverview 使用jQuery的Promise + */ + define('promise-third',[ + 'dollar' + ], function( $ ) { + return { + Deferred: $.Deferred, + when: $.when, + + isPromise: function( anything ) { + return anything && typeof anything.then === 'function'; + } + }; + }); + /** + * @fileOverview Promise/A+ + */ + define('promise',[ + 'promise-third' + ], function( _ ) { + return _; + }); + /** + * @fileOverview 基础类方法。 + */ + + /** + * Web Uploader内部类的详细说明,以下提及的功能类,都可以在`WebUploader`这个变量中访问到。 + * + * As you know, Web Uploader的每个文件都是用过[AMD](https://github.com/amdjs/amdjs-api/wiki/AMD)规范中的`define`组织起来的, 每个Module都会有个module id. + * 默认module id为该文件的路径,而此路径将会转化成名字空间存放在WebUploader中。如: + * + * * module `base`:WebUploader.Base + * * module `file`: WebUploader.File + * * module `lib/dnd`: WebUploader.Lib.Dnd + * * module `runtime/html5/dnd`: WebUploader.Runtime.Html5.Dnd + * + * + * 以下文档中对类的使用可能省略掉了`WebUploader`前缀。 + * @module WebUploader + * @title WebUploader API文档 + */ + define('base',[ + 'dollar', + 'promise' + ], function( $, promise ) { + + var noop = function() {}, + call = Function.call; + + // http://jsperf.com/uncurrythis + // 反科里化 + function uncurryThis( fn ) { + return function() { + return call.apply( fn, arguments ); + }; + } + + function bindFn( fn, context ) { + return function() { + return fn.apply( context, arguments ); + }; + } + + function createObject( proto ) { + var f; + + if ( Object.create ) { + return Object.create( proto ); + } else { + f = function() {}; + f.prototype = proto; + return new f(); + } + } + + + /** + * 基础类,提供一些简单常用的方法。 + * @class Base + */ + return { + + /** + * @property {String} version 当前版本号。 + */ + version: '0.1.5', + + /** + * @property {jQuery|Zepto} $ 引用依赖的jQuery或者Zepto对象。 + */ + $: $, + + Deferred: promise.Deferred, + + isPromise: promise.isPromise, + + when: promise.when, + + /** + * @description 简单的浏览器检查结果。 + * + * * `webkit` webkit版本号,如果浏览器为非webkit内核,此属性为`undefined`。 + * * `chrome` chrome浏览器版本号,如果浏览器为chrome,此属性为`undefined`。 + * * `ie` ie浏览器版本号,如果浏览器为非ie,此属性为`undefined`。**暂不支持ie10+** + * * `firefox` firefox浏览器版本号,如果浏览器为非firefox,此属性为`undefined`。 + * * `safari` safari浏览器版本号,如果浏览器为非safari,此属性为`undefined`。 + * * `opera` opera浏览器版本号,如果浏览器为非opera,此属性为`undefined`。 + * + * @property {Object} [browser] + */ + browser: (function( ua ) { + var ret = {}, + webkit = ua.match( /WebKit\/([\d.]+)/ ), + chrome = ua.match( /Chrome\/([\d.]+)/ ) || + ua.match( /CriOS\/([\d.]+)/ ), + + ie = ua.match( /MSIE\s([\d\.]+)/ ) || + ua.match( /(?:trident)(?:.*rv:([\w.]+))?/i ), + firefox = ua.match( /Firefox\/([\d.]+)/ ), + safari = ua.match( /Safari\/([\d.]+)/ ), + opera = ua.match( /OPR\/([\d.]+)/ ); + + webkit && (ret.webkit = parseFloat( webkit[ 1 ] )); + chrome && (ret.chrome = parseFloat( chrome[ 1 ] )); + ie && (ret.ie = parseFloat( ie[ 1 ] )); + firefox && (ret.firefox = parseFloat( firefox[ 1 ] )); + safari && (ret.safari = parseFloat( safari[ 1 ] )); + opera && (ret.opera = parseFloat( opera[ 1 ] )); + + return ret; + })( navigator.userAgent ), + + /** + * @description 操作系统检查结果。 + * + * * `android` 如果在android浏览器环境下,此值为对应的android版本号,否则为`undefined`。 + * * `ios` 如果在ios浏览器环境下,此值为对应的ios版本号,否则为`undefined`。 + * @property {Object} [os] + */ + os: (function( ua ) { + var ret = {}, + + // osx = !!ua.match( /\(Macintosh\; Intel / ), + android = ua.match( /(?:Android);?[\s\/]+([\d.]+)?/ ), + ios = ua.match( /(?:iPad|iPod|iPhone).*OS\s([\d_]+)/ ); + + // osx && (ret.osx = true); + android && (ret.android = parseFloat( android[ 1 ] )); + ios && (ret.ios = parseFloat( ios[ 1 ].replace( /_/g, '.' ) )); + + return ret; + })( navigator.userAgent ), + + /** + * 实现类与类之间的继承。 + * @method inherits + * @grammar Base.inherits( super ) => child + * @grammar Base.inherits( super, protos ) => child + * @grammar Base.inherits( super, protos, statics ) => child + * @param {Class} super 父类 + * @param {Object | Function} [protos] 子类或者对象。如果对象中包含constructor,子类将是用此属性值。 + * @param {Function} [protos.constructor] 子类构造器,不指定的话将创建个临时的直接执行父类构造器的方法。 + * @param {Object} [statics] 静态属性或方法。 + * @return {Class} 返回子类。 + * @example + * function Person() { + * console.log( 'Super' ); + * } + * Person.prototype.hello = function() { + * console.log( 'hello' ); + * }; + * + * var Manager = Base.inherits( Person, { + * world: function() { + * console.log( 'World' ); + * } + * }); + * + * // 因为没有指定构造器,父类的构造器将会执行。 + * var instance = new Manager(); // => Super + * + * // 继承子父类的方法 + * instance.hello(); // => hello + * instance.world(); // => World + * + * // 子类的__super__属性指向父类 + * console.log( Manager.__super__ === Person ); // => true + */ + inherits: function( Super, protos, staticProtos ) { + var child; + + if ( typeof protos === 'function' ) { + child = protos; + protos = null; + } else if ( protos && protos.hasOwnProperty('constructor') ) { + child = protos.constructor; + } else { + child = function() { + return Super.apply( this, arguments ); + }; + } + + // 复制静态方法 + $.extend( true, child, Super, staticProtos || {} ); + + /* jshint camelcase: false */ + + // 让子类的__super__属性指向父类。 + child.__super__ = Super.prototype; + + // 构建原型,添加原型方法或属性。 + // 暂时用Object.create实现。 + child.prototype = createObject( Super.prototype ); + protos && $.extend( true, child.prototype, protos ); + + return child; + }, + + /** + * 一个不做任何事情的方法。可以用来赋值给默认的callback. + * @method noop + */ + noop: noop, + + /** + * 返回一个新的方法,此方法将已指定的`context`来执行。 + * @grammar Base.bindFn( fn, context ) => Function + * @method bindFn + * @example + * var doSomething = function() { + * console.log( this.name ); + * }, + * obj = { + * name: 'Object Name' + * }, + * aliasFn = Base.bind( doSomething, obj ); + * + * aliasFn(); // => Object Name + * + */ + bindFn: bindFn, + + /** + * 引用Console.log如果存在的话,否则引用一个[空函数noop](#WebUploader:Base.noop)。 + * @grammar Base.log( args... ) => undefined + * @method log + */ + log: (function() { + if ( window.console ) { + return bindFn( console.log, console ); + } + return noop; + })(), + + nextTick: (function() { + + return function( cb ) { + setTimeout( cb, 1 ); + }; + + // @bug 当浏览器不在当前窗口时就停了。 + // var next = window.requestAnimationFrame || + // window.webkitRequestAnimationFrame || + // window.mozRequestAnimationFrame || + // function( cb ) { + // window.setTimeout( cb, 1000 / 60 ); + // }; + + // // fix: Uncaught TypeError: Illegal invocation + // return bindFn( next, window ); + })(), + + /** + * 被[uncurrythis](http://www.2ality.com/2011/11/uncurrying-this.html)的数组slice方法。 + * 将用来将非数组对象转化成数组对象。 + * @grammar Base.slice( target, start[, end] ) => Array + * @method slice + * @example + * function doSomthing() { + * var args = Base.slice( arguments, 1 ); + * console.log( args ); + * } + * + * doSomthing( 'ignored', 'arg2', 'arg3' ); // => Array ["arg2", "arg3"] + */ + slice: uncurryThis( [].slice ), + + /** + * 生成唯一的ID + * @method guid + * @grammar Base.guid() => String + * @grammar Base.guid( prefx ) => String + */ + guid: (function() { + var counter = 0; + + return function( prefix ) { + var guid = (+new Date()).toString( 32 ), + i = 0; + + for ( ; i < 5; i++ ) { + guid += Math.floor( Math.random() * 65535 ).toString( 32 ); + } + + return (prefix || 'wu_') + guid + (counter++).toString( 32 ); + }; + })(), + + /** + * 格式化文件大小, 输出成带单位的字符串 + * @method formatSize + * @grammar Base.formatSize( size ) => String + * @grammar Base.formatSize( size, pointLength ) => String + * @grammar Base.formatSize( size, pointLength, units ) => String + * @param {Number} size 文件大小 + * @param {Number} [pointLength=2] 精确到的小数点数。 + * @param {Array} [units=[ 'B', 'K', 'M', 'G', 'TB' ]] 单位数组。从字节,到千字节,一直往上指定。如果单位数组里面只指定了到了K(千字节),同时文件大小大于M, 此方法的输出将还是显示成多少K. + * @example + * console.log( Base.formatSize( 100 ) ); // => 100B + * console.log( Base.formatSize( 1024 ) ); // => 1.00K + * console.log( Base.formatSize( 1024, 0 ) ); // => 1K + * console.log( Base.formatSize( 1024 * 1024 ) ); // => 1.00M + * console.log( Base.formatSize( 1024 * 1024 * 1024 ) ); // => 1.00G + * console.log( Base.formatSize( 1024 * 1024 * 1024, 0, ['B', 'KB', 'MB'] ) ); // => 1024MB + */ + formatSize: function( size, pointLength, units ) { + var unit; + + units = units || [ 'B', 'K', 'M', 'G', 'TB' ]; + + while ( (unit = units.shift()) && size > 1024 ) { + size = size / 1024; + } + + return (unit === 'B' ? size : size.toFixed( pointLength || 2 )) + + unit; + } + }; + }); + /** + * 事件处理类,可以独立使用,也可以扩展给对象使用。 + * @fileOverview Mediator + */ + define('mediator',[ + 'base' + ], function( Base ) { + var $ = Base.$, + slice = [].slice, + separator = /\s+/, + protos; + + // 根据条件过滤出事件handlers. + function findHandlers( arr, name, callback, context ) { + return $.grep( arr, function( handler ) { + return handler && + (!name || handler.e === name) && + (!callback || handler.cb === callback || + handler.cb._cb === callback) && + (!context || handler.ctx === context); + }); + } + + function eachEvent( events, callback, iterator ) { + // 不支持对象,只支持多个event用空格隔开 + $.each( (events || '').split( separator ), function( _, key ) { + iterator( key, callback ); + }); + } + + function triggerHanders( events, args ) { + var stoped = false, + i = -1, + len = events.length, + handler; + + while ( ++i < len ) { + handler = events[ i ]; + + if ( handler.cb.apply( handler.ctx2, args ) === false ) { + stoped = true; + break; + } + } + + return !stoped; + } + + protos = { + + /** + * 绑定事件。 + * + * `callback`方法在执行时,arguments将会来源于trigger的时候携带的参数。如 + * ```javascript + * var obj = {}; + * + * // 使得obj有事件行为 + * Mediator.installTo( obj ); + * + * obj.on( 'testa', function( arg1, arg2 ) { + * console.log( arg1, arg2 ); // => 'arg1', 'arg2' + * }); + * + * obj.trigger( 'testa', 'arg1', 'arg2' ); + * ``` + * + * 如果`callback`中,某一个方法`return false`了,则后续的其他`callback`都不会被执行到。 + * 切会影响到`trigger`方法的返回值,为`false`。 + * + * `on`还可以用来添加一个特殊事件`all`, 这样所有的事件触发都会响应到。同时此类`callback`中的arguments有一个不同处, + * 就是第一个参数为`type`,记录当前是什么事件在触发。此类`callback`的优先级比脚低,会再正常`callback`执行完后触发。 + * ```javascript + * obj.on( 'all', function( type, arg1, arg2 ) { + * console.log( type, arg1, arg2 ); // => 'testa', 'arg1', 'arg2' + * }); + * ``` + * + * @method on + * @grammar on( name, callback[, context] ) => self + * @param {String} name 事件名,支持多个事件用空格隔开 + * @param {Function} callback 事件处理器 + * @param {Object} [context] 事件处理器的上下文。 + * @return {self} 返回自身,方便链式 + * @chainable + * @class Mediator + */ + on: function( name, callback, context ) { + var me = this, + set; + + if ( !callback ) { + return this; + } + + set = this._events || (this._events = []); + + eachEvent( name, callback, function( name, callback ) { + var handler = { e: name }; + + handler.cb = callback; + handler.ctx = context; + handler.ctx2 = context || me; + handler.id = set.length; + + set.push( handler ); + }); + + return this; + }, + + /** + * 绑定事件,且当handler执行完后,自动解除绑定。 + * @method once + * @grammar once( name, callback[, context] ) => self + * @param {String} name 事件名 + * @param {Function} callback 事件处理器 + * @param {Object} [context] 事件处理器的上下文。 + * @return {self} 返回自身,方便链式 + * @chainable + */ + once: function( name, callback, context ) { + var me = this; + + if ( !callback ) { + return me; + } + + eachEvent( name, callback, function( name, callback ) { + var once = function() { + me.off( name, once ); + return callback.apply( context || me, arguments ); + }; + + once._cb = callback; + me.on( name, once, context ); + }); + + return me; + }, + + /** + * 解除事件绑定 + * @method off + * @grammar off( [name[, callback[, context] ] ] ) => self + * @param {String} [name] 事件名 + * @param {Function} [callback] 事件处理器 + * @param {Object} [context] 事件处理器的上下文。 + * @return {self} 返回自身,方便链式 + * @chainable + */ + off: function( name, cb, ctx ) { + var events = this._events; + + if ( !events ) { + return this; + } + + if ( !name && !cb && !ctx ) { + this._events = []; + return this; + } + + eachEvent( name, cb, function( name, cb ) { + $.each( findHandlers( events, name, cb, ctx ), function() { + delete events[ this.id ]; + }); + }); + + return this; + }, + + /** + * 触发事件 + * @method trigger + * @grammar trigger( name[, args...] ) => self + * @param {String} type 事件名 + * @param {*} [...] 任意参数 + * @return {Boolean} 如果handler中return false了,则返回false, 否则返回true + */ + trigger: function( type ) { + var args, events, allEvents; + + if ( !this._events || !type ) { + return this; + } + + args = slice.call( arguments, 1 ); + events = findHandlers( this._events, type ); + allEvents = findHandlers( this._events, 'all' ); + + return triggerHanders( events, args ) && + triggerHanders( allEvents, arguments ); + } + }; + + /** + * 中介者,它本身是个单例,但可以通过[installTo](#WebUploader:Mediator:installTo)方法,使任何对象具备事件行为。 + * 主要目的是负责模块与模块之间的合作,降低耦合度。 + * + * @class Mediator + */ + return $.extend({ + + /** + * 可以通过这个接口,使任何对象具备事件功能。 + * @method installTo + * @param {Object} obj 需要具备事件行为的对象。 + * @return {Object} 返回obj. + */ + installTo: function( obj ) { + return $.extend( obj, protos ); + } + + }, protos ); + }); + /** + * @fileOverview Uploader上传类 + */ + define('uploader',[ + 'base', + 'mediator' + ], function( Base, Mediator ) { + + var $ = Base.$; + + /** + * 上传入口类。 + * @class Uploader + * @constructor + * @grammar new Uploader( opts ) => Uploader + * @example + * var uploader = WebUploader.Uploader({ + * swf: 'path_of_swf/Uploader.swf', + * + * // 开起分片上传。 + * chunked: true + * }); + */ + function Uploader( opts ) { + this.options = $.extend( true, {}, Uploader.options, opts ); + this._init( this.options ); + } + + // default Options + // widgets中有相应扩展 + Uploader.options = {}; + Mediator.installTo( Uploader.prototype ); + + // 批量添加纯命令式方法。 + $.each({ + upload: 'start-upload', + stop: 'stop-upload', + getFile: 'get-file', + getFiles: 'get-files', + addFile: 'add-file', + addFiles: 'add-file', + sort: 'sort-files', + removeFile: 'remove-file', + cancelFile: 'cancel-file', + skipFile: 'skip-file', + retry: 'retry', + isInProgress: 'is-in-progress', + makeThumb: 'make-thumb', + md5File: 'md5-file', + getDimension: 'get-dimension', + addButton: 'add-btn', + predictRuntimeType: 'predict-runtime-type', + refresh: 'refresh', + disable: 'disable', + enable: 'enable', + reset: 'reset' + }, function( fn, command ) { + Uploader.prototype[ fn ] = function() { + return this.request( command, arguments ); + }; + }); + + $.extend( Uploader.prototype, { + state: 'pending', + + _init: function( opts ) { + var me = this; + + me.request( 'init', opts, function() { + me.state = 'ready'; + me.trigger('ready'); + }); + }, + + /** + * 获取或者设置Uploader配置项。 + * @method option + * @grammar option( key ) => * + * @grammar option( key, val ) => self + * @example + * + * // 初始状态图片上传前不会压缩 + * var uploader = new WebUploader.Uploader({ + * compress: null; + * }); + * + * // 修改后图片上传前,尝试将图片压缩到1600 * 1600 + * uploader.option( 'compress', { + * width: 1600, + * height: 1600 + * }); + */ + option: function( key, val ) { + var opts = this.options; + + // setter + if ( arguments.length > 1 ) { + + if ( $.isPlainObject( val ) && + $.isPlainObject( opts[ key ] ) ) { + $.extend( opts[ key ], val ); + } else { + opts[ key ] = val; + } + + } else { // getter + return key ? opts[ key ] : opts; + } + }, + + /** + * 获取文件统计信息。返回一个包含一下信息的对象。 + * * `successNum` 上传成功的文件数 + * * `progressNum` 上传中的文件数 + * * `cancelNum` 被删除的文件数 + * * `invalidNum` 无效的文件数 + * * `uploadFailNum` 上传失败的文件数 + * * `queueNum` 还在队列中的文件数 + * * `interruptNum` 被暂停的文件数 + * @method getStats + * @grammar getStats() => Object + */ + getStats: function() { + // return this._mgr.getStats.apply( this._mgr, arguments ); + var stats = this.request('get-stats'); + + return stats ? { + successNum: stats.numOfSuccess, + progressNum: stats.numOfProgress, + + // who care? + // queueFailNum: 0, + cancelNum: stats.numOfCancel, + invalidNum: stats.numOfInvalid, + uploadFailNum: stats.numOfUploadFailed, + queueNum: stats.numOfQueue, + interruptNum: stats.numofInterrupt + } : {}; + }, + + // 需要重写此方法来来支持opts.onEvent和instance.onEvent的处理器 + trigger: function( type/*, args...*/ ) { + var args = [].slice.call( arguments, 1 ), + opts = this.options, + name = 'on' + type.substring( 0, 1 ).toUpperCase() + + type.substring( 1 ); + + if ( + // 调用通过on方法注册的handler. + Mediator.trigger.apply( this, arguments ) === false || + + // 调用opts.onEvent + $.isFunction( opts[ name ] ) && + opts[ name ].apply( this, args ) === false || + + // 调用this.onEvent + $.isFunction( this[ name ] ) && + this[ name ].apply( this, args ) === false || + + // 广播所有uploader的事件。 + Mediator.trigger.apply( Mediator, + [ this, type ].concat( args ) ) === false ) { + + return false; + } + + return true; + }, + + /** + * 销毁 webuploader 实例 + * @method destroy + * @grammar destroy() => undefined + */ + destroy: function() { + this.request( 'destroy', arguments ); + this.off(); + }, + + // widgets/widget.js将补充此方法的详细文档。 + request: Base.noop + }); + + /** + * 创建Uploader实例,等同于new Uploader( opts ); + * @method create + * @class Base + * @static + * @grammar Base.create( opts ) => Uploader + */ + Base.create = Uploader.create = function( opts ) { + return new Uploader( opts ); + }; + + // 暴露Uploader,可以通过它来扩展业务逻辑。 + Base.Uploader = Uploader; + + return Uploader; + }); + /** + * @fileOverview Runtime管理器,负责Runtime的选择, 连接 + */ + define('runtime/runtime',[ + 'base', + 'mediator' + ], function( Base, Mediator ) { + + var $ = Base.$, + factories = {}, + + // 获取对象的第一个key + getFirstKey = function( obj ) { + for ( var key in obj ) { + if ( obj.hasOwnProperty( key ) ) { + return key; + } + } + return null; + }; + + // 接口类。 + function Runtime( options ) { + this.options = $.extend({ + container: document.body + }, options ); + this.uid = Base.guid('rt_'); + } + + $.extend( Runtime.prototype, { + + getContainer: function() { + var opts = this.options, + parent, container; + + if ( this._container ) { + return this._container; + } + + parent = $( opts.container || document.body ); + container = $( document.createElement('div') ); + + container.attr( 'id', 'rt_' + this.uid ); + container.css({ + position: 'absolute', + top: '0px', + left: '0px', + width: '1px', + height: '1px', + overflow: 'hidden' + }); + + parent.append( container ); + parent.addClass('webuploader-container'); + this._container = container; + this._parent = parent; + return container; + }, + + init: Base.noop, + exec: Base.noop, + + destroy: function() { + this._container && this._container.remove(); + this._parent && this._parent.removeClass('webuploader-container'); + this.off(); + } + }); + + Runtime.orders = 'html5,flash'; + + + /** + * 添加Runtime实现。 + * @param {String} type 类型 + * @param {Runtime} factory 具体Runtime实现。 + */ + Runtime.addRuntime = function( type, factory ) { + factories[ type ] = factory; + }; + + Runtime.hasRuntime = function( type ) { + return !!(type ? factories[ type ] : getFirstKey( factories )); + }; + + Runtime.create = function( opts, orders ) { + var type, runtime; + + orders = orders || Runtime.orders; + $.each( orders.split( /\s*,\s*/g ), function() { + if ( factories[ this ] ) { + type = this; + return false; + } + }); + + type = type || getFirstKey( factories ); + + if ( !type ) { + throw new Error('Runtime Error'); + } + + runtime = new factories[ type ]( opts ); + return runtime; + }; + + Mediator.installTo( Runtime.prototype ); + return Runtime; + }); + + /** + * @fileOverview Runtime管理器,负责Runtime的选择, 连接 + */ + define('runtime/client',[ + 'base', + 'mediator', + 'runtime/runtime' + ], function( Base, Mediator, Runtime ) { + + var cache; + + cache = (function() { + var obj = {}; + + return { + add: function( runtime ) { + obj[ runtime.uid ] = runtime; + }, + + get: function( ruid, standalone ) { + var i; + + if ( ruid ) { + return obj[ ruid ]; + } + + for ( i in obj ) { + // 有些类型不能重用,比如filepicker. + if ( standalone && obj[ i ].__standalone ) { + continue; + } + + return obj[ i ]; + } + + return null; + }, + + remove: function( runtime ) { + delete obj[ runtime.uid ]; + } + }; + })(); + + function RuntimeClient( component, standalone ) { + var deferred = Base.Deferred(), + runtime; + + this.uid = Base.guid('client_'); + + // 允许runtime没有初始化之前,注册一些方法在初始化后执行。 + this.runtimeReady = function( cb ) { + return deferred.done( cb ); + }; + + this.connectRuntime = function( opts, cb ) { + + // already connected. + if ( runtime ) { + throw new Error('already connected!'); + } + + deferred.done( cb ); + + if ( typeof opts === 'string' && cache.get( opts ) ) { + runtime = cache.get( opts ); + } + + // 像filePicker只能独立存在,不能公用。 + runtime = runtime || cache.get( null, standalone ); + + // 需要创建 + if ( !runtime ) { + runtime = Runtime.create( opts, opts.runtimeOrder ); + runtime.__promise = deferred.promise(); + runtime.once( 'ready', deferred.resolve ); + runtime.init(); + cache.add( runtime ); + runtime.__client = 1; + } else { + // 来自cache + Base.$.extend( runtime.options, opts ); + runtime.__promise.then( deferred.resolve ); + runtime.__client++; + } + + standalone && (runtime.__standalone = standalone); + return runtime; + }; + + this.getRuntime = function() { + return runtime; + }; + + this.disconnectRuntime = function() { + if ( !runtime ) { + return; + } + + runtime.__client--; + + if ( runtime.__client <= 0 ) { + cache.remove( runtime ); + delete runtime.__promise; + runtime.destroy(); + } + + runtime = null; + }; + + this.exec = function() { + if ( !runtime ) { + return; + } + + var args = Base.slice( arguments ); + component && args.unshift( component ); + + return runtime.exec.apply( this, args ); + }; + + this.getRuid = function() { + return runtime && runtime.uid; + }; + + this.destroy = (function( destroy ) { + return function() { + destroy && destroy.apply( this, arguments ); + this.trigger('destroy'); + this.off(); + this.exec('destroy'); + this.disconnectRuntime(); + }; + })( this.destroy ); + } + + Mediator.installTo( RuntimeClient.prototype ); + return RuntimeClient; + }); + /** + * @fileOverview 错误信息 + */ + define('lib/dnd',[ + 'base', + 'mediator', + 'runtime/client' + ], function( Base, Mediator, RuntimeClent ) { + + var $ = Base.$; + + function DragAndDrop( opts ) { + opts = this.options = $.extend({}, DragAndDrop.options, opts ); + + opts.container = $( opts.container ); + + if ( !opts.container.length ) { + return; + } + + RuntimeClent.call( this, 'DragAndDrop' ); + } + + DragAndDrop.options = { + accept: null, + disableGlobalDnd: false + }; + + Base.inherits( RuntimeClent, { + constructor: DragAndDrop, + + init: function() { + var me = this; + + me.connectRuntime( me.options, function() { + me.exec('init'); + me.trigger('ready'); + }); + } + }); + + Mediator.installTo( DragAndDrop.prototype ); + + return DragAndDrop; + }); + /** + * @fileOverview 组件基类。 + */ + define('widgets/widget',[ + 'base', + 'uploader' + ], function( Base, Uploader ) { + + var $ = Base.$, + _init = Uploader.prototype._init, + _destroy = Uploader.prototype.destroy, + IGNORE = {}, + widgetClass = []; + + function isArrayLike( obj ) { + if ( !obj ) { + return false; + } + + var length = obj.length, + type = $.type( obj ); + + if ( obj.nodeType === 1 && length ) { + return true; + } + + return type === 'array' || type !== 'function' && type !== 'string' && + (length === 0 || typeof length === 'number' && length > 0 && + (length - 1) in obj); + } + + function Widget( uploader ) { + this.owner = uploader; + this.options = uploader.options; + } + + $.extend( Widget.prototype, { + + init: Base.noop, + + // 类Backbone的事件监听声明,监听uploader实例上的事件 + // widget直接无法监听事件,事件只能通过uploader来传递 + invoke: function( apiName, args ) { + + /* + { + 'make-thumb': 'makeThumb' + } + */ + var map = this.responseMap; + + // 如果无API响应声明则忽略 + if ( !map || !(apiName in map) || !(map[ apiName ] in this) || + !$.isFunction( this[ map[ apiName ] ] ) ) { + + return IGNORE; + } + + return this[ map[ apiName ] ].apply( this, args ); + + }, + + /** + * 发送命令。当传入`callback`或者`handler`中返回`promise`时。返回一个当所有`handler`中的promise都完成后完成的新`promise`。 + * @method request + * @grammar request( command, args ) => * | Promise + * @grammar request( command, args, callback ) => Promise + * @for Uploader + */ + request: function() { + return this.owner.request.apply( this.owner, arguments ); + } + }); + + // 扩展Uploader. + $.extend( Uploader.prototype, { + + /** + * @property {String | Array} [disableWidgets=undefined] + * @namespace options + * @for Uploader + * @description 默认所有 Uploader.register 了的 widget 都会被加载,如果禁用某一部分,请通过此 option 指定黑名单。 + */ + + // 覆写_init用来初始化widgets + _init: function() { + var me = this, + widgets = me._widgets = [], + deactives = me.options.disableWidgets || ''; + + $.each( widgetClass, function( _, klass ) { + (!deactives || !~deactives.indexOf( klass._name )) && + widgets.push( new klass( me ) ); + }); + + return _init.apply( me, arguments ); + }, + + request: function( apiName, args, callback ) { + var i = 0, + widgets = this._widgets, + len = widgets && widgets.length, + rlts = [], + dfds = [], + widget, rlt, promise, key; + + args = isArrayLike( args ) ? args : [ args ]; + + for ( ; i < len; i++ ) { + widget = widgets[ i ]; + rlt = widget.invoke( apiName, args ); + + if ( rlt !== IGNORE ) { + + // Deferred对象 + if ( Base.isPromise( rlt ) ) { + dfds.push( rlt ); + } else { + rlts.push( rlt ); + } + } + } + + // 如果有callback,则用异步方式。 + if ( callback || dfds.length ) { + promise = Base.when.apply( Base, dfds ); + key = promise.pipe ? 'pipe' : 'then'; + + // 很重要不能删除。删除了会死循环。 + // 保证执行顺序。让callback总是在下一个 tick 中执行。 + return promise[ key ](function() { + var deferred = Base.Deferred(), + args = arguments; + + if ( args.length === 1 ) { + args = args[ 0 ]; + } + + setTimeout(function() { + deferred.resolve( args ); + }, 1 ); + + return deferred.promise(); + })[ callback ? key : 'done' ]( callback || Base.noop ); + } else { + return rlts[ 0 ]; + } + }, + + destroy: function() { + _destroy.apply( this, arguments ); + this._widgets = null; + } + }); + + /** + * 添加组件 + * @grammar Uploader.register(proto); + * @grammar Uploader.register(map, proto); + * @param {object} responseMap API 名称与函数实现的映射 + * @param {object} proto 组件原型,构造函数通过 constructor 属性定义 + * @method Uploader.register + * @for Uploader + * @example + * Uploader.register({ + * 'make-thumb': 'makeThumb' + * }, { + * init: function( options ) {}, + * makeThumb: function() {} + * }); + * + * Uploader.register({ + * 'make-thumb': function() { + * + * } + * }); + */ + Uploader.register = Widget.register = function( responseMap, widgetProto ) { + var map = { init: 'init', destroy: 'destroy', name: 'anonymous' }, + klass; + + if ( arguments.length === 1 ) { + widgetProto = responseMap; + + // 自动生成 map 表。 + $.each(widgetProto, function(key) { + if ( key[0] === '_' || key === 'name' ) { + key === 'name' && (map.name = widgetProto.name); + return; + } + + map[key.replace(/[A-Z]/g, '-$&').toLowerCase()] = key; + }); + + } else { + map = $.extend( map, responseMap ); + } + + widgetProto.responseMap = map; + klass = Base.inherits( Widget, widgetProto ); + klass._name = map.name; + widgetClass.push( klass ); + + return klass; + }; + + /** + * 删除插件,只有在注册时指定了名字的才能被删除。 + * @grammar Uploader.unRegister(name); + * @param {string} name 组件名字 + * @method Uploader.unRegister + * @for Uploader + * @example + * + * Uploader.register({ + * name: 'custom', + * + * 'make-thumb': function() { + * + * } + * }); + * + * Uploader.unRegister('custom'); + */ + Uploader.unRegister = Widget.unRegister = function( name ) { + if ( !name || name === 'anonymous' ) { + return; + } + + // 删除指定的插件。 + for ( var i = widgetClass.length; i--; ) { + if ( widgetClass[i]._name === name ) { + widgetClass.splice(i, 1) + } + } + }; + + return Widget; + }); + /** + * @fileOverview DragAndDrop Widget。 + */ + define('widgets/filednd',[ + 'base', + 'uploader', + 'lib/dnd', + 'widgets/widget' + ], function( Base, Uploader, Dnd ) { + var $ = Base.$; + + Uploader.options.dnd = ''; + + /** + * @property {Selector} [dnd=undefined] 指定Drag And Drop拖拽的容器,如果不指定,则不启动。 + * @namespace options + * @for Uploader + */ + + /** + * @property {Selector} [disableGlobalDnd=false] 是否禁掉整个页面的拖拽功能,如果不禁用,图片拖进来的时候会默认被浏览器打开。 + * @namespace options + * @for Uploader + */ + + /** + * @event dndAccept + * @param {DataTransferItemList} items DataTransferItem + * @description 阻止此事件可以拒绝某些类型的文件拖入进来。目前只有 chrome 提供这样的 API,且只能通过 mime-type 验证。 + * @for Uploader + */ + return Uploader.register({ + name: 'dnd', + + init: function( opts ) { + + if ( !opts.dnd || + this.request('predict-runtime-type') !== 'html5' ) { + return; + } + + var me = this, + deferred = Base.Deferred(), + options = $.extend({}, { + disableGlobalDnd: opts.disableGlobalDnd, + container: opts.dnd, + accept: opts.accept + }), + dnd; + + this.dnd = dnd = new Dnd( options ); + + dnd.once( 'ready', deferred.resolve ); + dnd.on( 'drop', function( files ) { + me.request( 'add-file', [ files ]); + }); + + // 检测文件是否全部允许添加。 + dnd.on( 'accept', function( items ) { + return me.owner.trigger( 'dndAccept', items ); + }); + + dnd.init(); + + return deferred.promise(); + }, + + destroy: function() { + this.dnd && this.dnd.destroy(); + } + }); + }); + + /** + * @fileOverview 错误信息 + */ + define('lib/filepaste',[ + 'base', + 'mediator', + 'runtime/client' + ], function( Base, Mediator, RuntimeClent ) { + + var $ = Base.$; + + function FilePaste( opts ) { + opts = this.options = $.extend({}, opts ); + opts.container = $( opts.container || document.body ); + RuntimeClent.call( this, 'FilePaste' ); + } + + Base.inherits( RuntimeClent, { + constructor: FilePaste, + + init: function() { + var me = this; + + me.connectRuntime( me.options, function() { + me.exec('init'); + me.trigger('ready'); + }); + } + }); + + Mediator.installTo( FilePaste.prototype ); + + return FilePaste; + }); + /** + * @fileOverview 组件基类。 + */ + define('widgets/filepaste',[ + 'base', + 'uploader', + 'lib/filepaste', + 'widgets/widget' + ], function( Base, Uploader, FilePaste ) { + var $ = Base.$; + + /** + * @property {Selector} [paste=undefined] 指定监听paste事件的容器,如果不指定,不启用此功能。此功能为通过粘贴来添加截屏的图片。建议设置为`document.body`. + * @namespace options + * @for Uploader + */ + return Uploader.register({ + name: 'paste', + + init: function( opts ) { + + if ( !opts.paste || + this.request('predict-runtime-type') !== 'html5' ) { + return; + } + + var me = this, + deferred = Base.Deferred(), + options = $.extend({}, { + container: opts.paste, + accept: opts.accept + }), + paste; + + this.paste = paste = new FilePaste( options ); + + paste.once( 'ready', deferred.resolve ); + paste.on( 'paste', function( files ) { + me.owner.request( 'add-file', [ files ]); + }); + paste.init(); + + return deferred.promise(); + }, + + destroy: function() { + this.paste && this.paste.destroy(); + } + }); + }); + /** + * @fileOverview Blob + */ + define('lib/blob',[ + 'base', + 'runtime/client' + ], function( Base, RuntimeClient ) { + + function Blob( ruid, source ) { + var me = this; + + me.source = source; + me.ruid = ruid; + this.size = source.size || 0; + + // 如果没有指定 mimetype, 但是知道文件后缀。 + if ( !source.type && this.ext && + ~'jpg,jpeg,png,gif,bmp'.indexOf( this.ext ) ) { + this.type = 'image/' + (this.ext === 'jpg' ? 'jpeg' : this.ext); + } else { + this.type = source.type || 'application/octet-stream'; + } + + RuntimeClient.call( me, 'Blob' ); + this.uid = source.uid || this.uid; + + if ( ruid ) { + me.connectRuntime( ruid ); + } + } + + Base.inherits( RuntimeClient, { + constructor: Blob, + + slice: function( start, end ) { + return this.exec( 'slice', start, end ); + }, + + getSource: function() { + return this.source; + } + }); + + return Blob; + }); + /** + * 为了统一化Flash的File和HTML5的File而存在。 + * 以至于要调用Flash里面的File,也可以像调用HTML5版本的File一下。 + * @fileOverview File + */ + define('lib/file',[ + 'base', + 'lib/blob' + ], function( Base, Blob ) { + + var uid = 1, + rExt = /\.([^.]+)$/; + + function File( ruid, file ) { + var ext; + + this.name = file.name || ('untitled' + uid++); + ext = rExt.exec( file.name ) ? RegExp.$1.toLowerCase() : ''; + + // todo 支持其他类型文件的转换。 + // 如果有 mimetype, 但是文件名里面没有找出后缀规律 + if ( !ext && file.type ) { + ext = /\/(jpg|jpeg|png|gif|bmp)$/i.exec( file.type ) ? + RegExp.$1.toLowerCase() : ''; + this.name += '.' + ext; + } + + this.ext = ext; + this.lastModifiedDate = file.lastModifiedDate || + (new Date()).toLocaleString(); + + Blob.apply( this, arguments ); + } + + return Base.inherits( Blob, File ); + }); + + /** + * @fileOverview 错误信息 + */ + define('lib/filepicker',[ + 'base', + 'runtime/client', + 'lib/file' + ], function( Base, RuntimeClent, File ) { + + var $ = Base.$; + + function FilePicker( opts ) { + opts = this.options = $.extend({}, FilePicker.options, opts ); + + opts.container = $( opts.id ); + + if ( !opts.container.length ) { + throw new Error('按钮指定错误'); + } + + opts.innerHTML = opts.innerHTML || opts.label || + opts.container.html() || ''; + + opts.button = $( opts.button || document.createElement('div') ); + opts.button.html( opts.innerHTML ); + opts.container.html( opts.button ); + + RuntimeClent.call( this, 'FilePicker', true ); + } + + FilePicker.options = { + button: null, + container: null, + label: null, + innerHTML: null, + multiple: true, + accept: null, + name: 'file' + }; + + Base.inherits( RuntimeClent, { + constructor: FilePicker, + + init: function() { + var me = this, + opts = me.options, + button = opts.button; + + button.addClass('webuploader-pick'); + + me.on( 'all', function( type ) { + var files; + + switch ( type ) { + case 'mouseenter': + button.addClass('webuploader-pick-hover'); + break; + + case 'mouseleave': + button.removeClass('webuploader-pick-hover'); + break; + + case 'change': + files = me.exec('getFiles'); + me.trigger( 'select', $.map( files, function( file ) { + file = new File( me.getRuid(), file ); + + // 记录来源。 + file._refer = opts.container; + return file; + }), opts.container ); + break; + } + }); + + me.connectRuntime( opts, function() { + me.refresh(); + me.exec( 'init', opts ); + me.trigger('ready'); + }); + + this._resizeHandler = Base.bindFn( this.refresh, this ); + $( window ).on( 'resize', this._resizeHandler ); + }, + + refresh: function() { + var shimContainer = this.getRuntime().getContainer(), + button = this.options.button, + width = button.outerWidth ? + button.outerWidth() : button.width(), + + height = button.outerHeight ? + button.outerHeight() : button.height(), + + pos = button.offset(); + + width && height && shimContainer.css({ + bottom: 'auto', + right: 'auto', + width: width + 'px', + height: height + 'px' + }).offset( pos ); + }, + + enable: function() { + var btn = this.options.button; + + btn.removeClass('webuploader-pick-disable'); + this.refresh(); + }, + + disable: function() { + var btn = this.options.button; + + this.getRuntime().getContainer().css({ + top: '-99999px' + }); + + btn.addClass('webuploader-pick-disable'); + }, + + destroy: function() { + var btn = this.options.button; + $( window ).off( 'resize', this._resizeHandler ); + btn.removeClass('webuploader-pick-disable webuploader-pick-hover ' + + 'webuploader-pick'); + } + }); + + return FilePicker; + }); + + /** + * @fileOverview 文件选择相关 + */ + define('widgets/filepicker',[ + 'base', + 'uploader', + 'lib/filepicker', + 'widgets/widget' + ], function( Base, Uploader, FilePicker ) { + var $ = Base.$; + + $.extend( Uploader.options, { + + /** + * @property {Selector | Object} [pick=undefined] + * @namespace options + * @for Uploader + * @description 指定选择文件的按钮容器,不指定则不创建按钮。 + * + * * `id` {Seletor} 指定选择文件的按钮容器,不指定则不创建按钮。 + * * `label` {String} 请采用 `innerHTML` 代替 + * * `innerHTML` {String} 指定按钮文字。不指定时优先从指定的容器中看是否自带文字。 + * * `multiple` {Boolean} 是否开起同时选择多个文件能力。 + */ + pick: null, + + /** + * @property {Arroy} [accept=null] + * @namespace options + * @for Uploader + * @description 指定接受哪些类型的文件。 由于目前还有ext转mimeType表,所以这里需要分开指定。 + * + * * `title` {String} 文字描述 + * * `extensions` {String} 允许的文件后缀,不带点,多个用逗号分割。 + * * `mimeTypes` {String} 多个用逗号分割。 + * + * 如: + * + * ``` + * { + * title: 'Images', + * extensions: 'gif,jpg,jpeg,bmp,png', + * mimeTypes: 'image/*' + * } + * ``` + */ + accept: null/*{ + title: 'Images', + extensions: 'gif,jpg,jpeg,bmp,png', + mimeTypes: 'image/*' + }*/ + }); + + return Uploader.register({ + name: 'picker', + + init: function( opts ) { + this.pickers = []; + return opts.pick && this.addBtn( opts.pick ); + }, + + refresh: function() { + $.each( this.pickers, function() { + this.refresh(); + }); + }, + + /** + * @method addButton + * @for Uploader + * @grammar addButton( pick ) => Promise + * @description + * 添加文件选择按钮,如果一个按钮不够,需要调用此方法来添加。参数跟[options.pick](#WebUploader:Uploader:options)一致。 + * @example + * uploader.addButton({ + * id: '#btnContainer', + * innerHTML: '选择文件' + * }); + */ + addBtn: function( pick ) { + var me = this, + opts = me.options, + accept = opts.accept, + promises = []; + + if ( !pick ) { + return; + } + + $.isPlainObject( pick ) || (pick = { + id: pick + }); + + $( pick.id ).each(function() { + var options, picker, deferred; + + deferred = Base.Deferred(); + + options = $.extend({}, pick, { + accept: $.isPlainObject( accept ) ? [ accept ] : accept, + swf: opts.swf, + runtimeOrder: opts.runtimeOrder, + id: this + }); + + picker = new FilePicker( options ); + + picker.once( 'ready', deferred.resolve ); + picker.on( 'select', function( files ) { + me.owner.request( 'add-file', [ files ]); + }); + picker.init(); + + me.pickers.push( picker ); + + promises.push( deferred.promise() ); + }); + + return Base.when.apply( Base, promises ); + }, + + disable: function() { + $.each( this.pickers, function() { + this.disable(); + }); + }, + + enable: function() { + $.each( this.pickers, function() { + this.enable(); + }); + }, + + destroy: function() { + $.each( this.pickers, function() { + this.destroy(); + }); + this.pickers = null; + } + }); + }); + /** + * @fileOverview 文件属性封装 + */ + define('file',[ + 'base', + 'mediator' + ], function( Base, Mediator ) { + + var $ = Base.$, + idPrefix = 'WU_FILE_', + idSuffix = 0, + rExt = /\.([^.]+)$/, + statusMap = {}; + + function gid() { + return idPrefix + idSuffix++; + } + + /** + * 文件类 + * @class File + * @constructor 构造函数 + * @grammar new File( source ) => File + * @param {Lib.File} source [lib.File](#Lib.File)实例, 此source对象是带有Runtime信息的。 + */ + function WUFile( source ) { + + /** + * 文件名,包括扩展名(后缀) + * @property name + * @type {string} + */ + this.name = source.name || 'Untitled'; + + /** + * 文件体积(字节) + * @property size + * @type {uint} + * @default 0 + */ + this.size = source.size || 0; + + /** + * 文件MIMETYPE类型,与文件类型的对应关系请参考[http://t.cn/z8ZnFny](http://t.cn/z8ZnFny) + * @property type + * @type {string} + * @default 'application/octet-stream' + */ + this.type = source.type || 'application/octet-stream'; + + /** + * 文件最后修改日期 + * @property lastModifiedDate + * @type {int} + * @default 当前时间戳 + */ + this.lastModifiedDate = source.lastModifiedDate || (new Date() * 1); + + /** + * 文件ID,每个对象具有唯一ID,与文件名无关 + * @property id + * @type {string} + */ + this.id = gid(); + + /** + * 文件扩展名,通过文件名获取,例如test.png的扩展名为png + * @property ext + * @type {string} + */ + this.ext = rExt.exec( this.name ) ? RegExp.$1 : ''; + + + /** + * 状态文字说明。在不同的status语境下有不同的用途。 + * @property statusText + * @type {string} + */ + this.statusText = ''; + + // 存储文件状态,防止通过属性直接修改 + statusMap[ this.id ] = WUFile.Status.INITED; + + this.source = source; + this.loaded = 0; + + this.on( 'error', function( msg ) { + this.setStatus( WUFile.Status.ERROR, msg ); + }); + } + + $.extend( WUFile.prototype, { + + /** + * 设置状态,状态变化时会触发`change`事件。 + * @method setStatus + * @grammar setStatus( status[, statusText] ); + * @param {File.Status|String} status [文件状态值](#WebUploader:File:File.Status) + * @param {String} [statusText=''] 状态说明,常在error时使用,用http, abort,server等来标记是由于什么原因导致文件错误。 + */ + setStatus: function( status, text ) { + + var prevStatus = statusMap[ this.id ]; + + typeof text !== 'undefined' && (this.statusText = text); + + if ( status !== prevStatus ) { + statusMap[ this.id ] = status; + /** + * 文件状态变化 + * @event statuschange + */ + this.trigger( 'statuschange', status, prevStatus ); + } + + }, + + /** + * 获取文件状态 + * @return {File.Status} + * @example + 文件状态具体包括以下几种类型: + { + // 初始化 + INITED: 0, + // 已入队列 + QUEUED: 1, + // 正在上传 + PROGRESS: 2, + // 上传出错 + ERROR: 3, + // 上传成功 + COMPLETE: 4, + // 上传取消 + CANCELLED: 5 + } + */ + getStatus: function() { + return statusMap[ this.id ]; + }, + + /** + * 获取文件原始信息。 + * @return {*} + */ + getSource: function() { + return this.source; + }, + + destroy: function() { + this.off(); + delete statusMap[ this.id ]; + } + }); + + Mediator.installTo( WUFile.prototype ); + + /** + * 文件状态值,具体包括以下几种类型: + * * `inited` 初始状态 + * * `queued` 已经进入队列, 等待上传 + * * `progress` 上传中 + * * `complete` 上传完成。 + * * `error` 上传出错,可重试 + * * `interrupt` 上传中断,可续传。 + * * `invalid` 文件不合格,不能重试上传。会自动从队列中移除。 + * * `cancelled` 文件被移除。 + * @property {Object} Status + * @namespace File + * @class File + * @static + */ + WUFile.Status = { + INITED: 'inited', // 初始状态 + QUEUED: 'queued', // 已经进入队列, 等待上传 + PROGRESS: 'progress', // 上传中 + ERROR: 'error', // 上传出错,可重试 + COMPLETE: 'complete', // 上传完成。 + CANCELLED: 'cancelled', // 上传取消。 + INTERRUPT: 'interrupt', // 上传中断,可续传。 + INVALID: 'invalid' // 文件不合格,不能重试上传。 + }; + + return WUFile; + }); + + /** + * @fileOverview 文件队列 + */ + define('queue',[ + 'base', + 'mediator', + 'file' + ], function( Base, Mediator, WUFile ) { + + var $ = Base.$, + STATUS = WUFile.Status; + + /** + * 文件队列, 用来存储各个状态中的文件。 + * @class Queue + * @extends Mediator + */ + function Queue() { + + /** + * 统计文件数。 + * * `numOfQueue` 队列中的文件数。 + * * `numOfSuccess` 上传成功的文件数 + * * `numOfCancel` 被取消的文件数 + * * `numOfProgress` 正在上传中的文件数 + * * `numOfUploadFailed` 上传错误的文件数。 + * * `numOfInvalid` 无效的文件数。 + * * `numofDeleted` 被移除的文件数。 + * @property {Object} stats + */ + this.stats = { + numOfQueue: 0, + numOfSuccess: 0, + numOfCancel: 0, + numOfProgress: 0, + numOfUploadFailed: 0, + numOfInvalid: 0, + numofDeleted: 0, + numofInterrupt: 0, + }; + + // 上传队列,仅包括等待上传的文件 + this._queue = []; + + // 存储所有文件 + this._map = {}; + } + + $.extend( Queue.prototype, { + + /** + * 将新文件加入对队列尾部 + * + * @method append + * @param {File} file 文件对象 + */ + append: function( file ) { + this._queue.push( file ); + this._fileAdded( file ); + return this; + }, + + /** + * 将新文件加入对队列头部 + * + * @method prepend + * @param {File} file 文件对象 + */ + prepend: function( file ) { + this._queue.unshift( file ); + this._fileAdded( file ); + return this; + }, + + /** + * 获取文件对象 + * + * @method getFile + * @param {String} fileId 文件ID + * @return {File} + */ + getFile: function( fileId ) { + if ( typeof fileId !== 'string' ) { + return fileId; + } + return this._map[ fileId ]; + }, + + /** + * 从队列中取出一个指定状态的文件。 + * @grammar fetch( status ) => File + * @method fetch + * @param {String} status [文件状态值](#WebUploader:File:File.Status) + * @return {File} [File](#WebUploader:File) + */ + fetch: function( status ) { + var len = this._queue.length, + i, file; + + status = status || STATUS.QUEUED; + + for ( i = 0; i < len; i++ ) { + file = this._queue[ i ]; + + if ( status === file.getStatus() ) { + return file; + } + } + + return null; + }, + + /** + * 对队列进行排序,能够控制文件上传顺序。 + * @grammar sort( fn ) => undefined + * @method sort + * @param {Function} fn 排序方法 + */ + sort: function( fn ) { + if ( typeof fn === 'function' ) { + this._queue.sort( fn ); + } + }, + + /** + * 获取指定类型的文件列表, 列表中每一个成员为[File](#WebUploader:File)对象。 + * @grammar getFiles( [status1[, status2 ...]] ) => Array + * @method getFiles + * @param {String} [status] [文件状态值](#WebUploader:File:File.Status) + */ + getFiles: function() { + var sts = [].slice.call( arguments, 0 ), + ret = [], + i = 0, + len = this._queue.length, + file; + + for ( ; i < len; i++ ) { + file = this._queue[ i ]; + + if ( sts.length && !~$.inArray( file.getStatus(), sts ) ) { + continue; + } + + ret.push( file ); + } + + return ret; + }, + + /** + * 在队列中删除文件。 + * @grammar removeFile( file ) => Array + * @method removeFile + * @param {File} 文件对象。 + */ + removeFile: function( file ) { + var me = this, + existing = this._map[ file.id ]; + + if ( existing ) { + delete this._map[ file.id ]; + file.destroy(); + this.stats.numofDeleted++; + } + }, + + _fileAdded: function( file ) { + var me = this, + existing = this._map[ file.id ]; + + if ( !existing ) { + this._map[ file.id ] = file; + + file.on( 'statuschange', function( cur, pre ) { + me._onFileStatusChange( cur, pre ); + }); + } + }, + + _onFileStatusChange: function( curStatus, preStatus ) { + var stats = this.stats; + + switch ( preStatus ) { + case STATUS.PROGRESS: + stats.numOfProgress--; + break; + + case STATUS.QUEUED: + stats.numOfQueue --; + break; + + case STATUS.ERROR: + stats.numOfUploadFailed--; + break; + + case STATUS.INVALID: + stats.numOfInvalid--; + break; + + case STATUS.INTERRUPT: + stats.numofInterrupt--; + break; + } + + switch ( curStatus ) { + case STATUS.QUEUED: + stats.numOfQueue++; + break; + + case STATUS.PROGRESS: + stats.numOfProgress++; + break; + + case STATUS.ERROR: + stats.numOfUploadFailed++; + break; + + case STATUS.COMPLETE: + stats.numOfSuccess++; + break; + + case STATUS.CANCELLED: + stats.numOfCancel++; + break; + + + case STATUS.INVALID: + stats.numOfInvalid++; + break; + + case STATUS.INTERRUPT: + stats.numofInterrupt++; + break; + } + } + + }); + + Mediator.installTo( Queue.prototype ); + + return Queue; + }); + /** + * @fileOverview 队列 + */ + define('widgets/queue',[ + 'base', + 'uploader', + 'queue', + 'file', + 'lib/file', + 'runtime/client', + 'widgets/widget' + ], function( Base, Uploader, Queue, WUFile, File, RuntimeClient ) { + + var $ = Base.$, + rExt = /\.\w+$/, + Status = WUFile.Status; + + return Uploader.register({ + name: 'queue', + + init: function( opts ) { + var me = this, + deferred, len, i, item, arr, accept, runtime; + + if ( $.isPlainObject( opts.accept ) ) { + opts.accept = [ opts.accept ]; + } + + // accept中的中生成匹配正则。 + if ( opts.accept ) { + arr = []; + + for ( i = 0, len = opts.accept.length; i < len; i++ ) { + item = opts.accept[ i ].extensions; + item && arr.push( item ); + } + + if ( arr.length ) { + accept = '\\.' + arr.join(',') + .replace( /,/g, '$|\\.' ) + .replace( /\*/g, '.*' ) + '$'; + } + + me.accept = new RegExp( accept, 'i' ); + } + + me.queue = new Queue(); + me.stats = me.queue.stats; + + // 如果当前不是html5运行时,那就算了。 + // 不执行后续操作 + if ( this.request('predict-runtime-type') !== 'html5' ) { + return; + } + + // 创建一个 html5 运行时的 placeholder + // 以至于外部添加原生 File 对象的时候能正确包裹一下供 webuploader 使用。 + deferred = Base.Deferred(); + this.placeholder = runtime = new RuntimeClient('Placeholder'); + runtime.connectRuntime({ + runtimeOrder: 'html5' + }, function() { + me._ruid = runtime.getRuid(); + deferred.resolve(); + }); + return deferred.promise(); + }, + + + // 为了支持外部直接添加一个原生File对象。 + _wrapFile: function( file ) { + if ( !(file instanceof WUFile) ) { + + if ( !(file instanceof File) ) { + if ( !this._ruid ) { + throw new Error('Can\'t add external files.'); + } + file = new File( this._ruid, file ); + } + + file = new WUFile( file ); + } + + return file; + }, + + // 判断文件是否可以被加入队列 + acceptFile: function( file ) { + var invalid = !file || !file.size || this.accept && + + // 如果名字中有后缀,才做后缀白名单处理。 + rExt.exec( file.name ) && !this.accept.test( file.name ); + + return !invalid; + }, + + + /** + * @event beforeFileQueued + * @param {File} file File对象 + * @description 当文件被加入队列之前触发,此事件的handler返回值为`false`,则此文件不会被添加进入队列。 + * @for Uploader + */ + + /** + * @event fileQueued + * @param {File} file File对象 + * @description 当文件被加入队列以后触发。 + * @for Uploader + */ + + _addFile: function( file ) { + var me = this; + + file = me._wrapFile( file ); + + // 不过类型判断允许不允许,先派送 `beforeFileQueued` + if ( !me.owner.trigger( 'beforeFileQueued', file ) ) { + return; + } + + // 类型不匹配,则派送错误事件,并返回。 + if ( !me.acceptFile( file ) ) { + me.owner.trigger( 'error', 'Q_TYPE_DENIED', file ); + return; + } + + me.queue.append( file ); + me.owner.trigger( 'fileQueued', file ); + return file; + }, + + getFile: function( fileId ) { + return this.queue.getFile( fileId ); + }, + + /** + * @event filesQueued + * @param {File} files 数组,内容为原始File(lib/File)对象。 + * @description 当一批文件添加进队列以后触发。 + * @for Uploader + */ + + /** + * @property {Boolean} [auto=false] + * @namespace options + * @for Uploader + * @description 设置为 true 后,不需要手动调用上传,有文件选择即开始上传。 + * + */ + + /** + * @method addFiles + * @grammar addFiles( file ) => undefined + * @grammar addFiles( [file1, file2 ...] ) => undefined + * @param {Array of File or File} [files] Files 对象 数组 + * @description 添加文件到队列 + * @for Uploader + */ + addFile: function( files ) { + var me = this; + + if ( !files.length ) { + files = [ files ]; + } + + files = $.map( files, function( file ) { + return me._addFile( file ); + }); + + me.owner.trigger( 'filesQueued', files ); + + if ( me.options.auto ) { + setTimeout(function() { + me.request('start-upload'); + }, 20 ); + } + }, + + getStats: function() { + return this.stats; + }, + + /** + * @event fileDequeued + * @param {File} file File对象 + * @description 当文件被移除队列后触发。 + * @for Uploader + */ + + /** + * @method removeFile + * @grammar removeFile( file ) => undefined + * @grammar removeFile( id ) => undefined + * @grammar removeFile( file, true ) => undefined + * @grammar removeFile( id, true ) => undefined + * @param {File|id} file File对象或这File对象的id + * @description 移除某一文件, 默认只会标记文件状态为已取消,如果第二个参数为 `true` 则会从 queue 中移除。 + * @for Uploader + * @example + * + * $li.on('click', '.remove-this', function() { + * uploader.removeFile( file ); + * }) + */ + removeFile: function( file, remove ) { + var me = this; + + file = file.id ? file : me.queue.getFile( file ); + + this.request( 'cancel-file', file ); + + if ( remove ) { + this.queue.removeFile( file ); + } + }, + + /** + * @method getFiles + * @grammar getFiles() => Array + * @grammar getFiles( status1, status2, status... ) => Array + * @description 返回指定状态的文件集合,不传参数将返回所有状态的文件。 + * @for Uploader + * @example + * console.log( uploader.getFiles() ); // => all files + * console.log( uploader.getFiles('error') ) // => all error files. + */ + getFiles: function() { + return this.queue.getFiles.apply( this.queue, arguments ); + }, + + fetchFile: function() { + return this.queue.fetch.apply( this.queue, arguments ); + }, + + /** + * @method retry + * @grammar retry() => undefined + * @grammar retry( file ) => undefined + * @description 重试上传,重试指定文件,或者从出错的文件开始重新上传。 + * @for Uploader + * @example + * function retry() { + * uploader.retry(); + * } + */ + retry: function( file, noForceStart ) { + var me = this, + files, i, len; + + if ( file ) { + file = file.id ? file : me.queue.getFile( file ); + file.setStatus( Status.QUEUED ); + noForceStart || me.request('start-upload'); + return; + } + + files = me.queue.getFiles( Status.ERROR ); + i = 0; + len = files.length; + + for ( ; i < len; i++ ) { + file = files[ i ]; + file.setStatus( Status.QUEUED ); + } + + me.request('start-upload'); + }, + + /** + * @method sort + * @grammar sort( fn ) => undefined + * @description 排序队列中的文件,在上传之前调整可以控制上传顺序。 + * @for Uploader + */ + sortFiles: function() { + return this.queue.sort.apply( this.queue, arguments ); + }, + + /** + * @event reset + * @description 当 uploader 被重置的时候触发。 + * @for Uploader + */ + + /** + * @method reset + * @grammar reset() => undefined + * @description 重置uploader。目前只重置了队列。 + * @for Uploader + * @example + * uploader.reset(); + */ + reset: function() { + this.owner.trigger('reset'); + this.queue = new Queue(); + this.stats = this.queue.stats; + }, + + destroy: function() { + this.reset(); + this.placeholder && this.placeholder.destroy(); + } + }); + + }); + /** + * @fileOverview 添加获取Runtime相关信息的方法。 + */ + define('widgets/runtime',[ + 'uploader', + 'runtime/runtime', + 'widgets/widget' + ], function( Uploader, Runtime ) { + + Uploader.support = function() { + return Runtime.hasRuntime.apply( Runtime, arguments ); + }; + + return Uploader.register({ + name: 'runtime', + + init: function() { + if ( !this.predictRuntimeType() ) { + throw Error('Runtime Error'); + } + }, + + /** + * 预测Uploader将采用哪个`Runtime` + * @grammar predictRuntimeType() => String + * @method predictRuntimeType + * @for Uploader + */ + predictRuntimeType: function() { + var orders = this.options.runtimeOrder || Runtime.orders, + type = this.type, + i, len; + + if ( !type ) { + orders = orders.split( /\s*,\s*/g ); + + for ( i = 0, len = orders.length; i < len; i++ ) { + if ( Runtime.hasRuntime( orders[ i ] ) ) { + this.type = type = orders[ i ]; + break; + } + } + } + + return type; + } + }); + }); + /** + * @fileOverview Transport + */ + define('lib/transport',[ + 'base', + 'runtime/client', + 'mediator' + ], function( Base, RuntimeClient, Mediator ) { + + var $ = Base.$; + + function Transport( opts ) { + var me = this; + + opts = me.options = $.extend( true, {}, Transport.options, opts || {} ); + RuntimeClient.call( this, 'Transport' ); + + this._blob = null; + this._formData = opts.formData || {}; + this._headers = opts.headers || {}; + + this.on( 'progress', this._timeout ); + this.on( 'load error', function() { + me.trigger( 'progress', 1 ); + clearTimeout( me._timer ); + }); + } + + Transport.options = { + server: '', + method: 'POST', + + // 跨域时,是否允许携带cookie, 只有html5 runtime才有效 + withCredentials: false, + fileVal: 'file', + timeout: 2 * 60 * 1000, // 2分钟 + formData: {}, + headers: {}, + sendAsBinary: false + }; + + $.extend( Transport.prototype, { + + // 添加Blob, 只能添加一次,最后一次有效。 + appendBlob: function( key, blob, filename ) { + var me = this, + opts = me.options; + + if ( me.getRuid() ) { + me.disconnectRuntime(); + } + + // 连接到blob归属的同一个runtime. + me.connectRuntime( blob.ruid, function() { + me.exec('init'); + }); + + me._blob = blob; + opts.fileVal = key || opts.fileVal; + opts.filename = filename || opts.filename; + }, + + // 添加其他字段 + append: function( key, value ) { + if ( typeof key === 'object' ) { + $.extend( this._formData, key ); + } else { + this._formData[ key ] = value; + } + }, + + setRequestHeader: function( key, value ) { + if ( typeof key === 'object' ) { + $.extend( this._headers, key ); + } else { + this._headers[ key ] = value; + } + }, + + send: function( method ) { + this.exec( 'send', method ); + this._timeout(); + }, + + abort: function() { + clearTimeout( this._timer ); + return this.exec('abort'); + }, + + destroy: function() { + this.trigger('destroy'); + this.off(); + this.exec('destroy'); + this.disconnectRuntime(); + }, + + getResponse: function() { + return this.exec('getResponse'); + }, + + getResponseAsJson: function() { + return this.exec('getResponseAsJson'); + }, + + getStatus: function() { + return this.exec('getStatus'); + }, + + _timeout: function() { + var me = this, + duration = me.options.timeout; + + if ( !duration ) { + return; + } + + clearTimeout( me._timer ); + me._timer = setTimeout(function() { + me.abort(); + me.trigger( 'error', 'timeout' ); + }, duration ); + } + + }); + + // 让Transport具备事件功能。 + Mediator.installTo( Transport.prototype ); + + return Transport; + }); + /** + * @fileOverview 负责文件上传相关。 + */ + define('widgets/upload',[ + 'base', + 'uploader', + 'file', + 'lib/transport', + 'widgets/widget' + ], function( Base, Uploader, WUFile, Transport ) { + + var $ = Base.$, + isPromise = Base.isPromise, + Status = WUFile.Status; + + // 添加默认配置项 + $.extend( Uploader.options, { + + + /** + * @property {Boolean} [prepareNextFile=false] + * @namespace options + * @for Uploader + * @description 是否允许在文件传输时提前把下一个文件准备好。 + * 对于一个文件的准备工作比较耗时,比如图片压缩,md5序列化。 + * 如果能提前在当前文件传输期处理,可以节省总体耗时。 + */ + prepareNextFile: false, + + /** + * @property {Boolean} [chunked=false] + * @namespace options + * @for Uploader + * @description 是否要分片处理大文件上传。 + */ + chunked: false, + + /** + * @property {Boolean} [chunkSize=5242880] + * @namespace options + * @for Uploader + * @description 如果要分片,分多大一片? 默认大小为5M. + */ + chunkSize: 5 * 1024 * 1024, + + /** + * @property {Boolean} [chunkRetry=2] + * @namespace options + * @for Uploader + * @description 如果某个分片由于网络问题出错,允许自动重传多少次? + */ + chunkRetry: 2, + + /** + * @property {Boolean} [threads=3] + * @namespace options + * @for Uploader + * @description 上传并发数。允许同时最大上传进程数。 + */ + threads: 3, + + + /** + * @property {Object} [formData={}] + * @namespace options + * @for Uploader + * @description 文件上传请求的参数表,每次发送都会发送此对象中的参数。 + */ + formData: {} + + /** + * @property {Object} [fileVal='file'] + * @namespace options + * @for Uploader + * @description 设置文件上传域的name。 + */ + + /** + * @property {Object} [method='POST'] + * @namespace options + * @for Uploader + * @description 文件上传方式,`POST`或者`GET`。 + */ + + /** + * @property {Object} [sendAsBinary=false] + * @namespace options + * @for Uploader + * @description 是否已二进制的流的方式发送文件,这样整个上传内容`php://input`都为文件内容, + * 其他参数在$_GET数组中。 + */ + }); + + // 负责将文件切片。 + function CuteFile( file, chunkSize ) { + var pending = [], + blob = file.source, + total = blob.size, + chunks = chunkSize ? Math.ceil( total / chunkSize ) : 1, + start = 0, + index = 0, + len, api; + + api = { + file: file, + + has: function() { + return !!pending.length; + }, + + shift: function() { + return pending.shift(); + }, + + unshift: function( block ) { + pending.unshift( block ); + } + }; + + while ( index < chunks ) { + len = Math.min( chunkSize, total - start ); + + pending.push({ + file: file, + start: start, + end: chunkSize ? (start + len) : total, + total: total, + chunks: chunks, + chunk: index++, + cuted: api + }); + start += len; + } + + file.blocks = pending.concat(); + file.remaning = pending.length; + + return api; + } + + Uploader.register({ + name: 'upload', + + init: function() { + var owner = this.owner, + me = this; + + this.runing = false; + this.progress = false; + + owner + .on( 'startUpload', function() { + me.progress = true; + }) + .on( 'uploadFinished', function() { + me.progress = false; + }); + + // 记录当前正在传的数据,跟threads相关 + this.pool = []; + + // 缓存分好片的文件。 + this.stack = []; + + // 缓存即将上传的文件。 + this.pending = []; + + // 跟踪还有多少分片在上传中但是没有完成上传。 + this.remaning = 0; + this.__tick = Base.bindFn( this._tick, this ); + + owner.on( 'uploadComplete', function( file ) { + + // 把其他块取消了。 + file.blocks && $.each( file.blocks, function( _, v ) { + v.transport && (v.transport.abort(), v.transport.destroy()); + delete v.transport; + }); + + delete file.blocks; + delete file.remaning; + }); + }, + + reset: function() { + this.request( 'stop-upload', true ); + this.runing = false; + this.pool = []; + this.stack = []; + this.pending = []; + this.remaning = 0; + this._trigged = false; + this._promise = null; + }, + + /** + * @event startUpload + * @description 当开始上传流程时触发。 + * @for Uploader + */ + + /** + * 开始上传。此方法可以从初始状态调用开始上传流程,也可以从暂停状态调用,继续上传流程。 + * + * 可以指定开始某一个文件。 + * @grammar upload() => undefined + * @grammar upload( file | fileId) => undefined + * @method upload + * @for Uploader + */ + startUpload: function(file) { + var me = this; + + // 移出invalid的文件 + $.each( me.request( 'get-files', Status.INVALID ), function() { + me.request( 'remove-file', this ); + }); + + // 如果指定了开始某个文件,则只开始指定文件。 + if ( file ) { + file = file.id ? file : me.request( 'get-file', file ); + + if (file.getStatus() === Status.INTERRUPT) { + $.each( me.pool, function( _, v ) { + + // 之前暂停过。 + if (v.file !== file) { + return; + } + + v.transport && v.transport.send(); + }); + + file.setStatus( Status.QUEUED ); + } else if (file.getStatus() === Status.PROGRESS) { + return; + } else { + file.setStatus( Status.QUEUED ); + } + } else { + $.each( me.request( 'get-files', [ Status.INITED ] ), function() { + this.setStatus( Status.QUEUED ); + }); + } + + if ( me.runing ) { + return; + } + + me.runing = true; + + // 如果有暂停的,则续传 + $.each( me.pool, function( _, v ) { + var file = v.file; + + if ( file.getStatus() === Status.INTERRUPT ) { + file.setStatus( Status.PROGRESS ); + me._trigged = false; + v.transport && v.transport.send(); + } + }); + + file || $.each( me.request( 'get-files', + Status.INTERRUPT ), function() { + this.setStatus( Status.PROGRESS ); + }); + + me._trigged = false; + Base.nextTick( me.__tick ); + me.owner.trigger('startUpload'); + }, + + /** + * @event stopUpload + * @description 当开始上传流程暂停时触发。 + * @for Uploader + */ + + /** + * 暂停上传。第一个参数为是否中断上传当前正在上传的文件。 + * + * 如果第一个参数是文件,则只暂停指定文件。 + * @grammar stop() => undefined + * @grammar stop( true ) => undefined + * @grammar stop( file ) => undefined + * @method stop + * @for Uploader + */ + stopUpload: function( file, interrupt ) { + var me = this; + + if (file === true) { + interrupt = file; + file = null; + } + + if ( me.runing === false ) { + return; + } + + // 如果只是暂停某个文件。 + if ( file ) { + file = file.id ? file : me.request( 'get-file', file ); + + if ( file.getStatus() !== Status.PROGRESS && + file.getStatus() !== Status.QUEUED ) { + return; + } + + file.setStatus( Status.INTERRUPT ); + $.each( me.pool, function( _, v ) { + + // 只 abort 指定的文件。 + if (v.file !== file) { + return; + } + + v.transport && v.transport.abort(); + me._putback(v); + me._popBlock(v); + }); + + return Base.nextTick( me.__tick ); + } + + me.runing = false; + + if (this._promise && this._promise.file) { + this._promise.file.setStatus( Status.INTERRUPT ); + } + + interrupt && $.each( me.pool, function( _, v ) { + v.transport && v.transport.abort(); + v.file.setStatus( Status.INTERRUPT ); + }); + + me.owner.trigger('stopUpload'); + }, + + /** + * @method cancelFile + * @grammar cancelFile( file ) => undefined + * @grammar cancelFile( id ) => undefined + * @param {File|id} file File对象或这File对象的id + * @description 标记文件状态为已取消, 同时将中断文件传输。 + * @for Uploader + * @example + * + * $li.on('click', '.remove-this', function() { + * uploader.cancelFile( file ); + * }) + */ + cancelFile: function( file ) { + file = file.id ? file : this.request( 'get-file', file ); + + // 如果正在上传。 + file.blocks && $.each( file.blocks, function( _, v ) { + var _tr = v.transport; + + if ( _tr ) { + _tr.abort(); + _tr.destroy(); + delete v.transport; + } + }); + + file.setStatus( Status.CANCELLED ); + this.owner.trigger( 'fileDequeued', file ); + }, + + /** + * 判断`Uplaode`r是否正在上传中。 + * @grammar isInProgress() => Boolean + * @method isInProgress + * @for Uploader + */ + isInProgress: function() { + return !!this.progress; + }, + + _getStats: function() { + return this.request('get-stats'); + }, + + /** + * 掉过一个文件上传,直接标记指定文件为已上传状态。 + * @grammar skipFile( file ) => undefined + * @method skipFile + * @for Uploader + */ + skipFile: function( file, status ) { + file = file.id ? file : this.request( 'get-file', file ); + + file.setStatus( status || Status.COMPLETE ); + file.skipped = true; + + // 如果正在上传。 + file.blocks && $.each( file.blocks, function( _, v ) { + var _tr = v.transport; + + if ( _tr ) { + _tr.abort(); + _tr.destroy(); + delete v.transport; + } + }); + + this.owner.trigger( 'uploadSkip', file ); + }, + + /** + * @event uploadFinished + * @description 当所有文件上传结束时触发。 + * @for Uploader + */ + _tick: function() { + var me = this, + opts = me.options, + fn, val; + + // 上一个promise还没有结束,则等待完成后再执行。 + if ( me._promise ) { + return me._promise.always( me.__tick ); + } + + // 还有位置,且还有文件要处理的话。 + if ( me.pool.length < opts.threads && (val = me._nextBlock()) ) { + me._trigged = false; + + fn = function( val ) { + me._promise = null; + + // 有可能是reject过来的,所以要检测val的类型。 + val && val.file && me._startSend( val ); + Base.nextTick( me.__tick ); + }; + + me._promise = isPromise( val ) ? val.always( fn ) : fn( val ); + + // 没有要上传的了,且没有正在传输的了。 + } else if ( !me.remaning && !me._getStats().numOfQueue && + !me._getStats().numofInterrupt ) { + me.runing = false; + + me._trigged || Base.nextTick(function() { + me.owner.trigger('uploadFinished'); + }); + me._trigged = true; + } + }, + + _putback: function(block) { + var idx; + + block.cuted.unshift(block); + idx = this.stack.indexOf(block.cuted); + + if (!~idx) { + this.stack.unshift(block.cuted); + } + }, + + _getStack: function() { + var i = 0, + act; + + while ( (act = this.stack[ i++ ]) ) { + if ( act.has() && act.file.getStatus() === Status.PROGRESS ) { + return act; + } else if (!act.has() || + act.file.getStatus() !== Status.PROGRESS && + act.file.getStatus() !== Status.INTERRUPT ) { + + // 把已经处理完了的,或者,状态为非 progress(上传中)、 + // interupt(暂停中) 的移除。 + this.stack.splice( --i, 1 ); + } + } + + return null; + }, + + _nextBlock: function() { + var me = this, + opts = me.options, + act, next, done, preparing; + + // 如果当前文件还有没有需要传输的,则直接返回剩下的。 + if ( (act = this._getStack()) ) { + + // 是否提前准备下一个文件 + if ( opts.prepareNextFile && !me.pending.length ) { + me._prepareNextFile(); + } + + return act.shift(); + + // 否则,如果正在运行,则准备下一个文件,并等待完成后返回下个分片。 + } else if ( me.runing ) { + + // 如果缓存中有,则直接在缓存中取,没有则去queue中取。 + if ( !me.pending.length && me._getStats().numOfQueue ) { + me._prepareNextFile(); + } + + next = me.pending.shift(); + done = function( file ) { + if ( !file ) { + return null; + } + + act = CuteFile( file, opts.chunked ? opts.chunkSize : 0 ); + me.stack.push(act); + return act.shift(); + }; + + // 文件可能还在prepare中,也有可能已经完全准备好了。 + if ( isPromise( next) ) { + preparing = next.file; + next = next[ next.pipe ? 'pipe' : 'then' ]( done ); + next.file = preparing; + return next; + } + + return done( next ); + } + }, + + + /** + * @event uploadStart + * @param {File} file File对象 + * @description 某个文件开始上传前触发,一个文件只会触发一次。 + * @for Uploader + */ + _prepareNextFile: function() { + var me = this, + file = me.request('fetch-file'), + pending = me.pending, + promise; + + if ( file ) { + promise = me.request( 'before-send-file', file, function() { + + // 有可能文件被skip掉了。文件被skip掉后,状态坑定不是Queued. + if ( file.getStatus() === Status.PROGRESS || + file.getStatus() === Status.INTERRUPT ) { + return file; + } + + return me._finishFile( file ); + }); + + me.owner.trigger( 'uploadStart', file ); + file.setStatus( Status.PROGRESS ); + + promise.file = file; + + // 如果还在pending中,则替换成文件本身。 + promise.done(function() { + var idx = $.inArray( promise, pending ); + + ~idx && pending.splice( idx, 1, file ); + }); + + // befeore-send-file的钩子就有错误发生。 + promise.fail(function( reason ) { + file.setStatus( Status.ERROR, reason ); + me.owner.trigger( 'uploadError', file, reason ); + me.owner.trigger( 'uploadComplete', file ); + }); + + pending.push( promise ); + } + }, + + // 让出位置了,可以让其他分片开始上传 + _popBlock: function( block ) { + var idx = $.inArray( block, this.pool ); + + this.pool.splice( idx, 1 ); + block.file.remaning--; + this.remaning--; + }, + + // 开始上传,可以被掉过。如果promise被reject了,则表示跳过此分片。 + _startSend: function( block ) { + var me = this, + file = block.file, + promise; + + // 有可能在 before-send-file 的 promise 期间改变了文件状态。 + // 如:暂停,取消 + // 我们不能中断 promise, 但是可以在 promise 完后,不做上传操作。 + if ( file.getStatus() !== Status.PROGRESS ) { + + // 如果是中断,则还需要放回去。 + if (file.getStatus() === Status.INTERRUPT) { + me._putback(block); + } + + return; + } + + me.pool.push( block ); + me.remaning++; + + // 如果没有分片,则直接使用原始的。 + // 不会丢失content-type信息。 + block.blob = block.chunks === 1 ? file.source : + file.source.slice( block.start, block.end ); + + // hook, 每个分片发送之前可能要做些异步的事情。 + promise = me.request( 'before-send', block, function() { + + // 有可能文件已经上传出错了,所以不需要再传输了。 + if ( file.getStatus() === Status.PROGRESS ) { + me._doSend( block ); + } else { + me._popBlock( block ); + Base.nextTick( me.__tick ); + } + }); + + // 如果为fail了,则跳过此分片。 + promise.fail(function() { + if ( file.remaning === 1 ) { + me._finishFile( file ).always(function() { + block.percentage = 1; + me._popBlock( block ); + me.owner.trigger( 'uploadComplete', file ); + Base.nextTick( me.__tick ); + }); + } else { + block.percentage = 1; + me._popBlock( block ); + Base.nextTick( me.__tick ); + } + }); + }, + + + /** + * @event uploadBeforeSend + * @param {Object} object + * @param {Object} data 默认的上传参数,可以扩展此对象来控制上传参数。 + * @param {Object} headers 可以扩展此对象来控制上传头部。 + * @description 当某个文件的分块在发送前触发,主要用来询问是否要添加附带参数,大文件在开起分片上传的前提下此事件可能会触发多次。 + * @for Uploader + */ + + /** + * @event uploadAccept + * @param {Object} object + * @param {Object} ret 服务端的返回数据,json格式,如果服务端不是json格式,从ret._raw中取数据,自行解析。 + * @description 当某个文件上传到服务端响应后,会派送此事件来询问服务端响应是否有效。如果此事件handler返回值为`false`, 则此文件将派送`server`类型的`uploadError`事件。 + * @for Uploader + */ + + /** + * @event uploadProgress + * @param {File} file File对象 + * @param {Number} percentage 上传进度 + * @description 上传过程中触发,携带上传进度。 + * @for Uploader + */ + + + /** + * @event uploadError + * @param {File} file File对象 + * @param {String} reason 出错的code + * @description 当文件上传出错时触发。 + * @for Uploader + */ + + /** + * @event uploadSuccess + * @param {File} file File对象 + * @param {Object} response 服务端返回的数据 + * @description 当文件上传成功时触发。 + * @for Uploader + */ + + /** + * @event uploadComplete + * @param {File} [file] File对象 + * @description 不管成功或者失败,文件上传完成时触发。 + * @for Uploader + */ + + // 做上传操作。 + _doSend: function( block ) { + var me = this, + owner = me.owner, + opts = me.options, + file = block.file, + tr = new Transport( opts ), + data = $.extend({}, opts.formData ), + headers = $.extend({}, opts.headers ), + requestAccept, ret; + + block.transport = tr; + + tr.on( 'destroy', function() { + delete block.transport; + me._popBlock( block ); + Base.nextTick( me.__tick ); + }); + + // 广播上传进度。以文件为单位。 + tr.on( 'progress', function( percentage ) { + var totalPercent = 0, + uploaded = 0; + + // 可能没有abort掉,progress还是执行进来了。 + // if ( !file.blocks ) { + // return; + // } + + totalPercent = block.percentage = percentage; + + if ( block.chunks > 1 ) { // 计算文件的整体速度。 + $.each( file.blocks, function( _, v ) { + uploaded += (v.percentage || 0) * (v.end - v.start); + }); + + totalPercent = uploaded / file.size; + } + + owner.trigger( 'uploadProgress', file, totalPercent || 0 ); + }); + + // 用来询问,是否返回的结果是有错误的。 + requestAccept = function( reject ) { + var fn; + + ret = tr.getResponseAsJson() || {}; + ret._raw = tr.getResponse(); + fn = function( value ) { + reject = value; + }; + + // 服务端响应了,不代表成功了,询问是否响应正确。 + if ( !owner.trigger( 'uploadAccept', block, ret, fn ) ) { + reject = reject || 'server'; + } + + return reject; + }; + + // 尝试重试,然后广播文件上传出错。 + tr.on( 'error', function( type, flag ) { + block.retried = block.retried || 0; + + // 自动重试 + if ( block.chunks > 1 && ~'http,abort'.indexOf( type ) && + block.retried < opts.chunkRetry ) { + + block.retried++; + tr.send(); + + } else { + + // http status 500 ~ 600 + if ( !flag && type === 'server' ) { + type = requestAccept( type ); + } + + file.setStatus( Status.ERROR, type ); + owner.trigger( 'uploadError', file, type ); + owner.trigger( 'uploadComplete', file ); + } + }); + + // 上传成功 + tr.on( 'load', function() { + var reason; + + // 如果非预期,转向上传出错。 + if ( (reason = requestAccept()) ) { + tr.trigger( 'error', reason, true ); + return; + } + + // 全部上传完成。 + if ( file.remaning === 1 ) { + me._finishFile( file, ret ); + } else { + tr.destroy(); + } + }); + + // 配置默认的上传字段。 + data = $.extend( data, { + id: file.id, + name: file.name, + type: file.type, + lastModifiedDate: file.lastModifiedDate, + size: file.size + }); + + block.chunks > 1 && $.extend( data, { + chunks: block.chunks, + chunk: block.chunk + }); + + // 在发送之间可以添加字段什么的。。。 + // 如果默认的字段不够使用,可以通过监听此事件来扩展 + owner.trigger( 'uploadBeforeSend', block, data, headers ); + + // 开始发送。 + tr.appendBlob( opts.fileVal, block.blob, file.name ); + tr.append( data ); + tr.setRequestHeader( headers ); + tr.send(); + }, + + // 完成上传。 + _finishFile: function( file, ret, hds ) { + var owner = this.owner; + + return owner + .request( 'after-send-file', arguments, function() { + file.setStatus( Status.COMPLETE ); + owner.trigger( 'uploadSuccess', file, ret, hds ); + }) + .fail(function( reason ) { + + // 如果外部已经标记为invalid什么的,不再改状态。 + if ( file.getStatus() === Status.PROGRESS ) { + file.setStatus( Status.ERROR, reason ); + } + + owner.trigger( 'uploadError', file, reason ); + }) + .always(function() { + owner.trigger( 'uploadComplete', file ); + }); + } + + }); + }); + /** + * @fileOverview 各种验证,包括文件总大小是否超出、单文件是否超出和文件是否重复。 + */ + + define('widgets/validator',[ + 'base', + 'uploader', + 'file', + 'widgets/widget' + ], function( Base, Uploader, WUFile ) { + + var $ = Base.$, + validators = {}, + api; + + /** + * @event error + * @param {String} type 错误类型。 + * @description 当validate不通过时,会以派送错误事件的形式通知调用者。通过`upload.on('error', handler)`可以捕获到此类错误,目前有以下错误会在特定的情况下派送错来。 + * + * * `Q_EXCEED_NUM_LIMIT` 在设置了`fileNumLimit`且尝试给`uploader`添加的文件数量超出这个值时派送。 + * * `Q_EXCEED_SIZE_LIMIT` 在设置了`Q_EXCEED_SIZE_LIMIT`且尝试给`uploader`添加的文件总大小超出这个值时派送。 + * * `Q_TYPE_DENIED` 当文件类型不满足时触发。。 + * @for Uploader + */ + + // 暴露给外面的api + api = { + + // 添加验证器 + addValidator: function( type, cb ) { + validators[ type ] = cb; + }, + + // 移除验证器 + removeValidator: function( type ) { + delete validators[ type ]; + } + }; + + // 在Uploader初始化的时候启动Validators的初始化 + Uploader.register({ + name: 'validator', + + init: function() { + var me = this; + Base.nextTick(function() { + $.each( validators, function() { + this.call( me.owner ); + }); + }); + } + }); + + /** + * @property {int} [fileNumLimit=undefined] + * @namespace options + * @for Uploader + * @description 验证文件总数量, 超出则不允许加入队列。 + */ + api.addValidator( 'fileNumLimit', function() { + var uploader = this, + opts = uploader.options, + count = 0, + max = parseInt( opts.fileNumLimit, 10 ), + flag = true; + + if ( !max ) { + return; + } + + uploader.on( 'beforeFileQueued', function( file ) { + + if ( count >= max && flag ) { + flag = false; + this.trigger( 'error', 'Q_EXCEED_NUM_LIMIT', max, file ); + setTimeout(function() { + flag = true; + }, 1 ); + } + + return count >= max ? false : true; + }); + + uploader.on( 'fileQueued', function() { + count++; + }); + + uploader.on( 'fileDequeued', function() { + count--; + }); + + uploader.on( 'reset', function() { + count = 0; + }); + }); + + + /** + * @property {int} [fileSizeLimit=undefined] + * @namespace options + * @for Uploader + * @description 验证文件总大小是否超出限制, 超出则不允许加入队列。 + */ + api.addValidator( 'fileSizeLimit', function() { + var uploader = this, + opts = uploader.options, + count = 0, + max = parseInt( opts.fileSizeLimit, 10 ), + flag = true; + + if ( !max ) { + return; + } + + uploader.on( 'beforeFileQueued', function( file ) { + var invalid = count + file.size > max; + + if ( invalid && flag ) { + flag = false; + this.trigger( 'error', 'Q_EXCEED_SIZE_LIMIT', max, file ); + setTimeout(function() { + flag = true; + }, 1 ); + } + + return invalid ? false : true; + }); + + uploader.on( 'fileQueued', function( file ) { + count += file.size; + }); + + uploader.on( 'fileDequeued', function( file ) { + count -= file.size; + }); + + uploader.on( 'reset', function() { + count = 0; + }); + }); + + /** + * @property {int} [fileSingleSizeLimit=undefined] + * @namespace options + * @for Uploader + * @description 验证单个文件大小是否超出限制, 超出则不允许加入队列。 + */ + api.addValidator( 'fileSingleSizeLimit', function() { + var uploader = this, + opts = uploader.options, + max = opts.fileSingleSizeLimit; + + if ( !max ) { + return; + } + + uploader.on( 'beforeFileQueued', function( file ) { + + if ( file.size > max ) { + file.setStatus( WUFile.Status.INVALID, 'exceed_size' ); + this.trigger( 'error', 'F_EXCEED_SIZE', max, file ); + return false; + } + + }); + + }); + + /** + * @property {Boolean} [duplicate=undefined] + * @namespace options + * @for Uploader + * @description 去重, 根据文件名字、文件大小和最后修改时间来生成hash Key. + */ + api.addValidator( 'duplicate', function() { + var uploader = this, + opts = uploader.options, + mapping = {}; + + if ( opts.duplicate ) { + return; + } + + function hashString( str ) { + var hash = 0, + i = 0, + len = str.length, + _char; + + for ( ; i < len; i++ ) { + _char = str.charCodeAt( i ); + hash = _char + (hash << 6) + (hash << 16) - hash; + } + + return hash; + } + + uploader.on( 'beforeFileQueued', function( file ) { + var hash = file.__hash || (file.__hash = hashString( file.name + + file.size + file.lastModifiedDate )); + + // 已经重复了 + if ( mapping[ hash ] ) { + this.trigger( 'error', 'F_DUPLICATE', file ); + return false; + } + }); + + uploader.on( 'fileQueued', function( file ) { + var hash = file.__hash; + + hash && (mapping[ hash ] = true); + }); + + uploader.on( 'fileDequeued', function( file ) { + var hash = file.__hash; + + hash && (delete mapping[ hash ]); + }); + + uploader.on( 'reset', function() { + mapping = {}; + }); + }); + + return api; + }); + + /** + * @fileOverview Runtime管理器,负责Runtime的选择, 连接 + */ + define('runtime/compbase',[],function() { + + function CompBase( owner, runtime ) { + + this.owner = owner; + this.options = owner.options; + + this.getRuntime = function() { + return runtime; + }; + + this.getRuid = function() { + return runtime.uid; + }; + + this.trigger = function() { + return owner.trigger.apply( owner, arguments ); + }; + } + + return CompBase; + }); + /** + * @fileOverview Html5Runtime + */ + define('runtime/html5/runtime',[ + 'base', + 'runtime/runtime', + 'runtime/compbase' + ], function( Base, Runtime, CompBase ) { + + var type = 'html5', + components = {}; + + function Html5Runtime() { + var pool = {}, + me = this, + destroy = this.destroy; + + Runtime.apply( me, arguments ); + me.type = type; + + + // 这个方法的调用者,实际上是RuntimeClient + me.exec = function( comp, fn/*, args...*/) { + var client = this, + uid = client.uid, + args = Base.slice( arguments, 2 ), + instance; + + if ( components[ comp ] ) { + instance = pool[ uid ] = pool[ uid ] || + new components[ comp ]( client, me ); + + if ( instance[ fn ] ) { + return instance[ fn ].apply( instance, args ); + } + } + }; + + me.destroy = function() { + // @todo 删除池子中的所有实例 + return destroy && destroy.apply( this, arguments ); + }; + } + + Base.inherits( Runtime, { + constructor: Html5Runtime, + + // 不需要连接其他程序,直接执行callback + init: function() { + var me = this; + setTimeout(function() { + me.trigger('ready'); + }, 1 ); + } + + }); + + // 注册Components + Html5Runtime.register = function( name, component ) { + var klass = components[ name ] = Base.inherits( CompBase, component ); + return klass; + }; + + // 注册html5运行时。 + // 只有在支持的前提下注册。 + if ( window.Blob && window.FileReader && window.DataView ) { + Runtime.addRuntime( type, Html5Runtime ); + } + + return Html5Runtime; + }); + /** + * @fileOverview Blob Html实现 + */ + define('runtime/html5/blob',[ + 'runtime/html5/runtime', + 'lib/blob' + ], function( Html5Runtime, Blob ) { + + return Html5Runtime.register( 'Blob', { + slice: function( start, end ) { + var blob = this.owner.source, + slice = blob.slice || blob.webkitSlice || blob.mozSlice; + + blob = slice.call( blob, start, end ); + + return new Blob( this.getRuid(), blob ); + } + }); + }); + /** + * @fileOverview FilePaste + */ + define('runtime/html5/dnd',[ + 'base', + 'runtime/html5/runtime', + 'lib/file' + ], function( Base, Html5Runtime, File ) { + + var $ = Base.$, + prefix = 'webuploader-dnd-'; + + return Html5Runtime.register( 'DragAndDrop', { + init: function() { + var elem = this.elem = this.options.container; + + this.dragEnterHandler = Base.bindFn( this._dragEnterHandler, this ); + this.dragOverHandler = Base.bindFn( this._dragOverHandler, this ); + this.dragLeaveHandler = Base.bindFn( this._dragLeaveHandler, this ); + this.dropHandler = Base.bindFn( this._dropHandler, this ); + this.dndOver = false; + + elem.on( 'dragenter', this.dragEnterHandler ); + elem.on( 'dragover', this.dragOverHandler ); + elem.on( 'dragleave', this.dragLeaveHandler ); + elem.on( 'drop', this.dropHandler ); + + if ( this.options.disableGlobalDnd ) { + $( document ).on( 'dragover', this.dragOverHandler ); + $( document ).on( 'drop', this.dropHandler ); + } + }, + + _dragEnterHandler: function( e ) { + var me = this, + denied = me._denied || false, + items; + + e = e.originalEvent || e; + + if ( !me.dndOver ) { + me.dndOver = true; + + // 注意只有 chrome 支持。 + items = e.dataTransfer.items; + + if ( items && items.length ) { + me._denied = denied = !me.trigger( 'accept', items ); + } + + me.elem.addClass( prefix + 'over' ); + me.elem[ denied ? 'addClass' : + 'removeClass' ]( prefix + 'denied' ); + } + + e.dataTransfer.dropEffect = denied ? 'none' : 'copy'; + + return false; + }, + + _dragOverHandler: function( e ) { + // 只处理框内的。 + var parentElem = this.elem.parent().get( 0 ); + if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) { + return false; + } + + clearTimeout( this._leaveTimer ); + this._dragEnterHandler.call( this, e ); + + return false; + }, + + _dragLeaveHandler: function() { + var me = this, + handler; + + handler = function() { + me.dndOver = false; + me.elem.removeClass( prefix + 'over ' + prefix + 'denied' ); + }; + + clearTimeout( me._leaveTimer ); + me._leaveTimer = setTimeout( handler, 100 ); + return false; + }, + + _dropHandler: function( e ) { + var me = this, + ruid = me.getRuid(), + parentElem = me.elem.parent().get( 0 ), + dataTransfer, data; + + // 只处理框内的。 + if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) { + return false; + } + + e = e.originalEvent || e; + dataTransfer = e.dataTransfer; + + // 如果是页面内拖拽,还不能处理,不阻止事件。 + // 此处 ie11 下会报参数错误, + try { + data = dataTransfer.getData('text/html'); + } catch( err ) { + } + + if ( data ) { + return; + } + + me._getTansferFiles( dataTransfer, function( results ) { + me.trigger( 'drop', $.map( results, function( file ) { + return new File( ruid, file ); + }) ); + }); + + me.dndOver = false; + me.elem.removeClass( prefix + 'over' ); + return false; + }, + + // 如果传入 callback 则去查看文件夹,否则只管当前文件夹。 + _getTansferFiles: function( dataTransfer, callback ) { + var results = [], + promises = [], + items, files, file, item, i, len, canAccessFolder; + + items = dataTransfer.items; + files = dataTransfer.files; + + canAccessFolder = !!(items && items[ 0 ].webkitGetAsEntry); + + for ( i = 0, len = files.length; i < len; i++ ) { + file = files[ i ]; + item = items && items[ i ]; + + if ( canAccessFolder && item.webkitGetAsEntry().isDirectory ) { + + promises.push( this._traverseDirectoryTree( + item.webkitGetAsEntry(), results ) ); + } else { + results.push( file ); + } + } + + Base.when.apply( Base, promises ).done(function() { + + if ( !results.length ) { + return; + } + + callback( results ); + }); + }, + + _traverseDirectoryTree: function( entry, results ) { + var deferred = Base.Deferred(), + me = this; + + if ( entry.isFile ) { + entry.file(function( file ) { + results.push( file ); + deferred.resolve(); + }); + } else if ( entry.isDirectory ) { + entry.createReader().readEntries(function( entries ) { + var len = entries.length, + promises = [], + arr = [], // 为了保证顺序。 + i; + + for ( i = 0; i < len; i++ ) { + promises.push( me._traverseDirectoryTree( + entries[ i ], arr ) ); + } + + Base.when.apply( Base, promises ).then(function() { + results.push.apply( results, arr ); + deferred.resolve(); + }, deferred.reject ); + }); + } + + return deferred.promise(); + }, + + destroy: function() { + var elem = this.elem; + + // 还没 init 就调用 destroy + if (!elem) { + return; + } + + elem.off( 'dragenter', this.dragEnterHandler ); + elem.off( 'dragover', this.dragOverHandler ); + elem.off( 'dragleave', this.dragLeaveHandler ); + elem.off( 'drop', this.dropHandler ); + + if ( this.options.disableGlobalDnd ) { + $( document ).off( 'dragover', this.dragOverHandler ); + $( document ).off( 'drop', this.dropHandler ); + } + } + }); + }); + + /** + * @fileOverview FilePaste + */ + define('runtime/html5/filepaste',[ + 'base', + 'runtime/html5/runtime', + 'lib/file' + ], function( Base, Html5Runtime, File ) { + + return Html5Runtime.register( 'FilePaste', { + init: function() { + var opts = this.options, + elem = this.elem = opts.container, + accept = '.*', + arr, i, len, item; + + // accetp的mimeTypes中生成匹配正则。 + if ( opts.accept ) { + arr = []; + + for ( i = 0, len = opts.accept.length; i < len; i++ ) { + item = opts.accept[ i ].mimeTypes; + item && arr.push( item ); + } + + if ( arr.length ) { + accept = arr.join(','); + accept = accept.replace( /,/g, '|' ).replace( /\*/g, '.*' ); + } + } + this.accept = accept = new RegExp( accept, 'i' ); + this.hander = Base.bindFn( this._pasteHander, this ); + elem.on( 'paste', this.hander ); + }, + + _pasteHander: function( e ) { + var allowed = [], + ruid = this.getRuid(), + items, item, blob, i, len; + + e = e.originalEvent || e; + items = e.clipboardData.items; + + for ( i = 0, len = items.length; i < len; i++ ) { + item = items[ i ]; + + if ( item.kind !== 'file' || !(blob = item.getAsFile()) ) { + continue; + } + + allowed.push( new File( ruid, blob ) ); + } + + if ( allowed.length ) { + // 不阻止非文件粘贴(文字粘贴)的事件冒泡 + e.preventDefault(); + e.stopPropagation(); + this.trigger( 'paste', allowed ); + } + }, + + destroy: function() { + this.elem.off( 'paste', this.hander ); + } + }); + }); + + /** + * @fileOverview FilePicker + */ + define('runtime/html5/filepicker',[ + 'base', + 'runtime/html5/runtime' + ], function( Base, Html5Runtime ) { + + var $ = Base.$; + + return Html5Runtime.register( 'FilePicker', { + init: function() { + var container = this.getRuntime().getContainer(), + me = this, + owner = me.owner, + opts = me.options, + label = this.label = $( document.createElement('label') ), + input = this.input = $( document.createElement('input') ), + arr, i, len, mouseHandler; + + input.attr( 'type', 'file' ); + input.attr( 'name', opts.name ); + input.addClass('webuploader-element-invisible'); + + label.on( 'click', function() { + input.trigger('click'); + }); + + label.css({ + opacity: 0, + width: '100%', + height: '100%', + display: 'block', + cursor: 'pointer', + background: '#ffffff' + }); + + if ( opts.multiple ) { + input.attr( 'multiple', 'multiple' ); + } + + // @todo Firefox不支持单独指定后缀 + if ( opts.accept && opts.accept.length > 0 ) { + arr = []; + + for ( i = 0, len = opts.accept.length; i < len; i++ ) { + arr.push( opts.accept[ i ].mimeTypes ); + } + + input.attr( 'accept', arr.join(',') ); + } + + container.append( input ); + container.append( label ); + + mouseHandler = function( e ) { + owner.trigger( e.type ); + }; + + input.on( 'change', function( e ) { + var fn = arguments.callee, + clone; + + me.files = e.target.files; + + // reset input + clone = this.cloneNode( true ); + clone.value = null; + this.parentNode.replaceChild( clone, this ); + + input.off(); + input = $( clone ).on( 'change', fn ) + .on( 'mouseenter mouseleave', mouseHandler ); + + owner.trigger('change'); + }); + + label.on( 'mouseenter mouseleave', mouseHandler ); + + }, + + + getFiles: function() { + return this.files; + }, + + destroy: function() { + this.input.off(); + this.label.off(); + } + }); + }); + /** + * @fileOverview Transport + * @todo 支持chunked传输,优势: + * 可以将大文件分成小块,挨个传输,可以提高大文件成功率,当失败的时候,也只需要重传那小部分, + * 而不需要重头再传一次。另外断点续传也需要用chunked方式。 + */ + define('runtime/html5/transport',[ + 'base', + 'runtime/html5/runtime' + ], function( Base, Html5Runtime ) { + + var noop = Base.noop, + $ = Base.$; + + return Html5Runtime.register( 'Transport', { + init: function() { + this._status = 0; + this._response = null; + }, + + send: function() { + var owner = this.owner, + opts = this.options, + xhr = this._initAjax(), + blob = owner._blob, + server = opts.server, + formData, binary, fr; + + if ( opts.sendAsBinary ) { + server += (/\?/.test( server ) ? '&' : '?') + + $.param( owner._formData ); + + binary = blob.getSource(); + } else { + formData = new FormData(); + $.each( owner._formData, function( k, v ) { + formData.append( k, v ); + }); + + formData.append( opts.fileVal, blob.getSource(), + opts.filename || owner._formData.name || '' ); + } + + if ( opts.withCredentials && 'withCredentials' in xhr ) { + xhr.open( opts.method, server, true ); + xhr.withCredentials = true; + } else { + xhr.open( opts.method, server ); + } + + this._setRequestHeader( xhr, opts.headers ); + + if ( binary ) { + // 强制设置成 content-type 为文件流。 + xhr.overrideMimeType && + xhr.overrideMimeType('application/octet-stream'); + + // android直接发送blob会导致服务端接收到的是空文件。 + // bug详情。 + // https://code.google.com/p/android/issues/detail?id=39882 + // 所以先用fileReader读取出来再通过arraybuffer的方式发送。 + if ( Base.os.android ) { + fr = new FileReader(); + + fr.onload = function() { + xhr.send( this.result ); + fr = fr.onload = null; + }; + + fr.readAsArrayBuffer( binary ); + } else { + xhr.send( binary ); + } + } else { + xhr.send( formData ); + } + }, + + getResponse: function() { + return this._response; + }, + + getResponseAsJson: function() { + return this._parseJson( this._response ); + }, + + getStatus: function() { + return this._status; + }, + + abort: function() { + var xhr = this._xhr; + + if ( xhr ) { + xhr.upload.onprogress = noop; + xhr.onreadystatechange = noop; + xhr.abort(); + + this._xhr = xhr = null; + } + }, + + destroy: function() { + this.abort(); + }, + + _initAjax: function() { + var me = this, + xhr = new XMLHttpRequest(), + opts = this.options; + + if ( opts.withCredentials && !('withCredentials' in xhr) && + typeof XDomainRequest !== 'undefined' ) { + xhr = new XDomainRequest(); + } + + xhr.upload.onprogress = function( e ) { + var percentage = 0; + + if ( e.lengthComputable ) { + percentage = e.loaded / e.total; + } + + return me.trigger( 'progress', percentage ); + }; + + xhr.onreadystatechange = function() { + + if ( xhr.readyState !== 4 ) { + return; + } + + xhr.upload.onprogress = noop; + xhr.onreadystatechange = noop; + me._xhr = null; + me._status = xhr.status; + + if ( xhr.status >= 200 && xhr.status < 300 ) { + me._response = xhr.responseText; + return me.trigger('load'); + } else if ( xhr.status >= 500 && xhr.status < 600 ) { + me._response = xhr.responseText; + return me.trigger( 'error', 'server' ); + } + + + return me.trigger( 'error', me._status ? 'http' : 'abort' ); + }; + + me._xhr = xhr; + return xhr; + }, + + _setRequestHeader: function( xhr, headers ) { + $.each( headers, function( key, val ) { + xhr.setRequestHeader( key, val ); + }); + }, + + _parseJson: function( str ) { + var json; + + try { + json = JSON.parse( str ); + } catch ( ex ) { + json = {}; + } + + return json; + } + }); + }); + /** + * @fileOverview FlashRuntime + */ + define('runtime/flash/runtime',[ + 'base', + 'runtime/runtime', + 'runtime/compbase' + ], function( Base, Runtime, CompBase ) { + + var $ = Base.$, + type = 'flash', + components = {}; + + + function getFlashVersion() { + var version; + + try { + version = navigator.plugins[ 'Shockwave Flash' ]; + version = version.description; + } catch ( ex ) { + try { + version = new ActiveXObject('ShockwaveFlash.ShockwaveFlash') + .GetVariable('$version'); + } catch ( ex2 ) { + version = '0.0'; + } + } + version = version.match( /\d+/g ); + return parseFloat( version[ 0 ] + '.' + version[ 1 ], 10 ); + } + + function FlashRuntime() { + var pool = {}, + clients = {}, + destroy = this.destroy, + me = this, + jsreciver = Base.guid('webuploader_'); + + Runtime.apply( me, arguments ); + me.type = type; + + + // 这个方法的调用者,实际上是RuntimeClient + me.exec = function( comp, fn/*, args...*/ ) { + var client = this, + uid = client.uid, + args = Base.slice( arguments, 2 ), + instance; + + clients[ uid ] = client; + + if ( components[ comp ] ) { + if ( !pool[ uid ] ) { + pool[ uid ] = new components[ comp ]( client, me ); + } + + instance = pool[ uid ]; + + if ( instance[ fn ] ) { + return instance[ fn ].apply( instance, args ); + } + } + + return me.flashExec.apply( client, arguments ); + }; + + function handler( evt, obj ) { + var type = evt.type || evt, + parts, uid; + + parts = type.split('::'); + uid = parts[ 0 ]; + type = parts[ 1 ]; + + // console.log.apply( console, arguments ); + + if ( type === 'Ready' && uid === me.uid ) { + me.trigger('ready'); + } else if ( clients[ uid ] ) { + clients[ uid ].trigger( type.toLowerCase(), evt, obj ); + } + + // Base.log( evt, obj ); + } + + // flash的接受器。 + window[ jsreciver ] = function() { + var args = arguments; + + // 为了能捕获得到。 + setTimeout(function() { + handler.apply( null, args ); + }, 1 ); + }; + + this.jsreciver = jsreciver; + + this.destroy = function() { + // @todo 删除池子中的所有实例 + return destroy && destroy.apply( this, arguments ); + }; + + this.flashExec = function( comp, fn ) { + var flash = me.getFlash(), + args = Base.slice( arguments, 2 ); + + return flash.exec( this.uid, comp, fn, args ); + }; + + // @todo + } + + Base.inherits( Runtime, { + constructor: FlashRuntime, + + init: function() { + var container = this.getContainer(), + opts = this.options, + html; + + // if not the minimal height, shims are not initialized + // in older browsers (e.g FF3.6, IE6,7,8, Safari 4.0,5.0, etc) + container.css({ + position: 'absolute', + top: '-8px', + left: '-8px', + width: '9px', + height: '9px', + overflow: 'hidden' + }); + + // insert flash object + html = '' + + '' + + '' + + '' + + ''; + + container.html( html ); + }, + + getFlash: function() { + if ( this._flash ) { + return this._flash; + } + + this._flash = $( '#' + this.uid ).get( 0 ); + return this._flash; + } + + }); + + FlashRuntime.register = function( name, component ) { + component = components[ name ] = Base.inherits( CompBase, $.extend({ + + // @todo fix this later + flashExec: function() { + var owner = this.owner, + runtime = this.getRuntime(); + + return runtime.flashExec.apply( owner, arguments ); + } + }, component ) ); + + return component; + }; + + if ( getFlashVersion() >= 11.4 ) { + Runtime.addRuntime( type, FlashRuntime ); + } + + return FlashRuntime; + }); + /** + * @fileOverview FilePicker + */ + define('runtime/flash/filepicker',[ + 'base', + 'runtime/flash/runtime' + ], function( Base, FlashRuntime ) { + var $ = Base.$; + + return FlashRuntime.register( 'FilePicker', { + init: function( opts ) { + var copy = $.extend({}, opts ), + len, i; + + // 修复Flash再没有设置title的情况下无法弹出flash文件选择框的bug. + len = copy.accept && copy.accept.length; + for ( i = 0; i < len; i++ ) { + if ( !copy.accept[ i ].title ) { + copy.accept[ i ].title = 'Files'; + } + } + + delete copy.button; + delete copy.id; + delete copy.container; + + this.flashExec( 'FilePicker', 'init', copy ); + }, + + destroy: function() { + this.flashExec( 'FilePicker', 'destroy' ); + } + }); + }); + /** + * @fileOverview Transport flash实现 + */ + define('runtime/flash/transport',[ + 'base', + 'runtime/flash/runtime', + 'runtime/client' + ], function( Base, FlashRuntime, RuntimeClient ) { + var $ = Base.$; + + return FlashRuntime.register( 'Transport', { + init: function() { + this._status = 0; + this._response = null; + this._responseJson = null; + }, + + send: function() { + var owner = this.owner, + opts = this.options, + xhr = this._initAjax(), + blob = owner._blob, + server = opts.server, + binary; + + xhr.connectRuntime( blob.ruid ); + + if ( opts.sendAsBinary ) { + server += (/\?/.test( server ) ? '&' : '?') + + $.param( owner._formData ); + + binary = blob.uid; + } else { + $.each( owner._formData, function( k, v ) { + xhr.exec( 'append', k, v ); + }); + + xhr.exec( 'appendBlob', opts.fileVal, blob.uid, + opts.filename || owner._formData.name || '' ); + } + + this._setRequestHeader( xhr, opts.headers ); + xhr.exec( 'send', { + method: opts.method, + url: server, + forceURLStream: opts.forceURLStream, + mimeType: 'application/octet-stream' + }, binary ); + }, + + getStatus: function() { + return this._status; + }, + + getResponse: function() { + return this._response || ''; + }, + + getResponseAsJson: function() { + return this._responseJson; + }, + + abort: function() { + var xhr = this._xhr; + + if ( xhr ) { + xhr.exec('abort'); + xhr.destroy(); + this._xhr = xhr = null; + } + }, + + destroy: function() { + this.abort(); + }, + + _initAjax: function() { + var me = this, + xhr = new RuntimeClient('XMLHttpRequest'); + + xhr.on( 'uploadprogress progress', function( e ) { + var percent = e.loaded / e.total; + percent = Math.min( 1, Math.max( 0, percent ) ); + return me.trigger( 'progress', percent ); + }); + + xhr.on( 'load', function() { + var status = xhr.exec('getStatus'), + readBody = false, + err = '', + p; + + xhr.off(); + me._xhr = null; + + if ( status >= 200 && status < 300 ) { + readBody = true; + } else if ( status >= 500 && status < 600 ) { + readBody = true; + err = 'server'; + } else { + err = 'http'; + } + + if ( readBody ) { + me._response = xhr.exec('getResponse'); + me._response = decodeURIComponent( me._response ); + + // flash 处理可能存在 bug, 没辙只能靠 js 了 + // try { + // me._responseJson = xhr.exec('getResponseAsJson'); + // } catch ( error ) { + + p = window.JSON && window.JSON.parse || function( s ) { + try { + return new Function('return ' + s).call(); + } catch ( err ) { + return {}; + } + }; + me._responseJson = me._response ? p(me._response) : {}; + + // } + } + + xhr.destroy(); + xhr = null; + + return err ? me.trigger( 'error', err ) : me.trigger('load'); + }); + + xhr.on( 'error', function() { + xhr.off(); + me._xhr = null; + me.trigger( 'error', 'http' ); + }); + + me._xhr = xhr; + return xhr; + }, + + _setRequestHeader: function( xhr, headers ) { + $.each( headers, function( key, val ) { + xhr.exec( 'setRequestHeader', key, val ); + }); + } + }); + }); + /** + * @fileOverview 没有图像处理的版本。 + */ + define('preset/withoutimage',[ + 'base', + + // widgets + 'widgets/filednd', + 'widgets/filepaste', + 'widgets/filepicker', + 'widgets/queue', + 'widgets/runtime', + 'widgets/upload', + 'widgets/validator', + + // runtimes + // html5 + 'runtime/html5/blob', + 'runtime/html5/dnd', + 'runtime/html5/filepaste', + 'runtime/html5/filepicker', + 'runtime/html5/transport', + + // flash + 'runtime/flash/filepicker', + 'runtime/flash/transport' + ], function( Base ) { + return Base; + }); + define('webuploader',[ + 'preset/withoutimage' + ], function( preset ) { + return preset; + }); + return require('webuploader'); +}); diff --git a/static/plugins/webuploader/webuploader.withoutimage.min.js b/static/plugins/webuploader/webuploader.withoutimage.min.js new file mode 100644 index 00000000..84ba1987 --- /dev/null +++ b/static/plugins/webuploader/webuploader.withoutimage.min.js @@ -0,0 +1,2 @@ +/* WebUploader 0.1.5 */!function(a,b){var c,d={},e=function(a,b){var c,d,e;if("string"==typeof a)return h(a);for(c=[],d=a.length,e=0;d>e;e++)c.push(h(a[e]));return b.apply(null,c)},f=function(a,b,c){2===arguments.length&&(c=b,b=null),e(b||[],function(){g(a,c,arguments)})},g=function(a,b,c){var f,g={exports:b};"function"==typeof b&&(c.length||(c=[e,g.exports,g]),f=b.apply(null,c),void 0!==f&&(g.exports=f)),d[a]=g.exports},h=function(b){var c=d[b]||a[b];if(!c)throw new Error("`"+b+"` is undefined");return c},i=function(a){var b,c,e,f,g,h;h=function(a){return a&&a.charAt(0).toUpperCase()+a.substr(1)};for(b in d)if(c=a,d.hasOwnProperty(b)){for(e=b.split("/"),g=h(e.pop());f=h(e.shift());)c[f]=c[f]||{},c=c[f];c[g]=d[b]}return a},j=function(c){return a.__dollar=c,i(b(a,f,e))};"object"==typeof module&&"object"==typeof module.exports?module.exports=j():"function"==typeof define&&define.amd?define(["jquery"],j):(c=a.WebUploader,a.WebUploader=j(),a.WebUploader.noConflict=function(){a.WebUploader=c})}(window,function(a,b,c){return b("dollar-third",[],function(){var b=a.__dollar||a.jQuery||a.Zepto;if(!b)throw new Error("jQuery or Zepto not found!");return b}),b("dollar",["dollar-third"],function(a){return a}),b("promise-third",["dollar"],function(a){return{Deferred:a.Deferred,when:a.when,isPromise:function(a){return a&&"function"==typeof a.then}}}),b("promise",["promise-third"],function(a){return a}),b("base",["dollar","promise"],function(b,c){function d(a){return function(){return h.apply(a,arguments)}}function e(a,b){return function(){return a.apply(b,arguments)}}function f(a){var b;return Object.create?Object.create(a):(b=function(){},b.prototype=a,new b)}var g=function(){},h=Function.call;return{version:"0.1.5",$:b,Deferred:c.Deferred,isPromise:c.isPromise,when:c.when,browser:function(a){var b={},c=a.match(/WebKit\/([\d.]+)/),d=a.match(/Chrome\/([\d.]+)/)||a.match(/CriOS\/([\d.]+)/),e=a.match(/MSIE\s([\d\.]+)/)||a.match(/(?:trident)(?:.*rv:([\w.]+))?/i),f=a.match(/Firefox\/([\d.]+)/),g=a.match(/Safari\/([\d.]+)/),h=a.match(/OPR\/([\d.]+)/);return c&&(b.webkit=parseFloat(c[1])),d&&(b.chrome=parseFloat(d[1])),e&&(b.ie=parseFloat(e[1])),f&&(b.firefox=parseFloat(f[1])),g&&(b.safari=parseFloat(g[1])),h&&(b.opera=parseFloat(h[1])),b}(navigator.userAgent),os:function(a){var b={},c=a.match(/(?:Android);?[\s\/]+([\d.]+)?/),d=a.match(/(?:iPad|iPod|iPhone).*OS\s([\d_]+)/);return c&&(b.android=parseFloat(c[1])),d&&(b.ios=parseFloat(d[1].replace(/_/g,"."))),b}(navigator.userAgent),inherits:function(a,c,d){var e;return"function"==typeof c?(e=c,c=null):e=c&&c.hasOwnProperty("constructor")?c.constructor:function(){return a.apply(this,arguments)},b.extend(!0,e,a,d||{}),e.__super__=a.prototype,e.prototype=f(a.prototype),c&&b.extend(!0,e.prototype,c),e},noop:g,bindFn:e,log:function(){return a.console?e(console.log,console):g}(),nextTick:function(){return function(a){setTimeout(a,1)}}(),slice:d([].slice),guid:function(){var a=0;return function(b){for(var c=(+new Date).toString(32),d=0;5>d;d++)c+=Math.floor(65535*Math.random()).toString(32);return(b||"wu_")+c+(a++).toString(32)}}(),formatSize:function(a,b,c){var d;for(c=c||["B","K","M","G","TB"];(d=c.shift())&&a>1024;)a/=1024;return("B"===d?a:a.toFixed(b||2))+d}}}),b("mediator",["base"],function(a){function b(a,b,c,d){return f.grep(a,function(a){return!(!a||b&&a.e!==b||c&&a.cb!==c&&a.cb._cb!==c||d&&a.ctx!==d)})}function c(a,b,c){f.each((a||"").split(h),function(a,d){c(d,b)})}function d(a,b){for(var c,d=!1,e=-1,f=a.length;++e1?void(d.isPlainObject(b)&&d.isPlainObject(c[a])?d.extend(c[a],b):c[a]=b):a?c[a]:c},getStats:function(){var a=this.request("get-stats");return a?{successNum:a.numOfSuccess,progressNum:a.numOfProgress,cancelNum:a.numOfCancel,invalidNum:a.numOfInvalid,uploadFailNum:a.numOfUploadFailed,queueNum:a.numOfQueue,interruptNum:a.numofInterrupt}:{}},trigger:function(a){var c=[].slice.call(arguments,1),e=this.options,f="on"+a.substring(0,1).toUpperCase()+a.substring(1);return b.trigger.apply(this,arguments)===!1||d.isFunction(e[f])&&e[f].apply(this,c)===!1||d.isFunction(this[f])&&this[f].apply(this,c)===!1||b.trigger.apply(b,[this,a].concat(c))===!1?!1:!0},destroy:function(){this.request("destroy",arguments),this.off()},request:a.noop}),a.create=c.create=function(a){return new c(a)},a.Uploader=c,c}),b("runtime/runtime",["base","mediator"],function(a,b){function c(b){this.options=d.extend({container:document.body},b),this.uid=a.guid("rt_")}var d=a.$,e={},f=function(a){for(var b in a)if(a.hasOwnProperty(b))return b;return null};return d.extend(c.prototype,{getContainer:function(){var a,b,c=this.options;return this._container?this._container:(a=d(c.container||document.body),b=d(document.createElement("div")),b.attr("id","rt_"+this.uid),b.css({position:"absolute",top:"0px",left:"0px",width:"1px",height:"1px",overflow:"hidden"}),a.append(b),a.addClass("webuploader-container"),this._container=b,this._parent=a,b)},init:a.noop,exec:a.noop,destroy:function(){this._container&&this._container.remove(),this._parent&&this._parent.removeClass("webuploader-container"),this.off()}}),c.orders="html5,flash",c.addRuntime=function(a,b){e[a]=b},c.hasRuntime=function(a){return!!(a?e[a]:f(e))},c.create=function(a,b){var g,h;if(b=b||c.orders,d.each(b.split(/\s*,\s*/g),function(){return e[this]?(g=this,!1):void 0}),g=g||f(e),!g)throw new Error("Runtime Error");return h=new e[g](a)},b.installTo(c.prototype),c}),b("runtime/client",["base","mediator","runtime/runtime"],function(a,b,c){function d(b,d){var f,g=a.Deferred();this.uid=a.guid("client_"),this.runtimeReady=function(a){return g.done(a)},this.connectRuntime=function(b,h){if(f)throw new Error("already connected!");return g.done(h),"string"==typeof b&&e.get(b)&&(f=e.get(b)),f=f||e.get(null,d),f?(a.$.extend(f.options,b),f.__promise.then(g.resolve),f.__client++):(f=c.create(b,b.runtimeOrder),f.__promise=g.promise(),f.once("ready",g.resolve),f.init(),e.add(f),f.__client=1),d&&(f.__standalone=d),f},this.getRuntime=function(){return f},this.disconnectRuntime=function(){f&&(f.__client--,f.__client<=0&&(e.remove(f),delete f.__promise,f.destroy()),f=null)},this.exec=function(){if(f){var c=a.slice(arguments);return b&&c.unshift(b),f.exec.apply(this,c)}},this.getRuid=function(){return f&&f.uid},this.destroy=function(a){return function(){a&&a.apply(this,arguments),this.trigger("destroy"),this.off(),this.exec("destroy"),this.disconnectRuntime()}}(this.destroy)}var e;return e=function(){var a={};return{add:function(b){a[b.uid]=b},get:function(b,c){var d;if(b)return a[b];for(d in a)if(!c||!a[d].__standalone)return a[d];return null},remove:function(b){delete a[b.uid]}}}(),b.installTo(d.prototype),d}),b("lib/dnd",["base","mediator","runtime/client"],function(a,b,c){function d(a){a=this.options=e.extend({},d.options,a),a.container=e(a.container),a.container.length&&c.call(this,"DragAndDrop")}var e=a.$;return d.options={accept:null,disableGlobalDnd:!1},a.inherits(c,{constructor:d,init:function(){var a=this;a.connectRuntime(a.options,function(){a.exec("init"),a.trigger("ready")})}}),b.installTo(d.prototype),d}),b("widgets/widget",["base","uploader"],function(a,b){function c(a){if(!a)return!1;var b=a.length,c=e.type(a);return 1===a.nodeType&&b?!0:"array"===c||"function"!==c&&"string"!==c&&(0===b||"number"==typeof b&&b>0&&b-1 in a)}function d(a){this.owner=a,this.options=a.options}var e=a.$,f=b.prototype._init,g=b.prototype.destroy,h={},i=[];return e.extend(d.prototype,{init:a.noop,invoke:function(a,b){var c=this.responseMap;return c&&a in c&&c[a]in this&&e.isFunction(this[c[a]])?this[c[a]].apply(this,b):h},request:function(){return this.owner.request.apply(this.owner,arguments)}}),e.extend(b.prototype,{_init:function(){var a=this,b=a._widgets=[],c=a.options.disableWidgets||"";return e.each(i,function(d,e){(!c||!~c.indexOf(e._name))&&b.push(new e(a))}),f.apply(a,arguments)},request:function(b,d,e){var f,g,i,j,k=0,l=this._widgets,m=l&&l.length,n=[],o=[];for(d=c(d)?d:[d];m>k;k++)f=l[k],g=f.invoke(b,d),g!==h&&(a.isPromise(g)?o.push(g):n.push(g));return e||o.length?(i=a.when.apply(a,o),j=i.pipe?"pipe":"then",i[j](function(){var b=a.Deferred(),c=arguments;return 1===c.length&&(c=c[0]),setTimeout(function(){b.resolve(c)},1),b.promise()})[e?j:"done"](e||a.noop)):n[0]},destroy:function(){g.apply(this,arguments),this._widgets=null}}),b.register=d.register=function(b,c){var f,g={init:"init",destroy:"destroy",name:"anonymous"};return 1===arguments.length?(c=b,e.each(c,function(a){return"_"===a[0]||"name"===a?void("name"===a&&(g.name=c.name)):void(g[a.replace(/[A-Z]/g,"-$&").toLowerCase()]=a)})):g=e.extend(g,b),c.responseMap=g,f=a.inherits(d,c),f._name=g.name,i.push(f),f},b.unRegister=d.unRegister=function(a){if(a&&"anonymous"!==a)for(var b=i.length;b--;)i[b]._name===a&&i.splice(b,1)},d}),b("widgets/filednd",["base","uploader","lib/dnd","widgets/widget"],function(a,b,c){var d=a.$;return b.options.dnd="",b.register({name:"dnd",init:function(b){if(b.dnd&&"html5"===this.request("predict-runtime-type")){var e,f=this,g=a.Deferred(),h=d.extend({},{disableGlobalDnd:b.disableGlobalDnd,container:b.dnd,accept:b.accept});return this.dnd=e=new c(h),e.once("ready",g.resolve),e.on("drop",function(a){f.request("add-file",[a])}),e.on("accept",function(a){return f.owner.trigger("dndAccept",a)}),e.init(),g.promise()}},destroy:function(){this.dnd&&this.dnd.destroy()}})}),b("lib/filepaste",["base","mediator","runtime/client"],function(a,b,c){function d(a){a=this.options=e.extend({},a),a.container=e(a.container||document.body),c.call(this,"FilePaste")}var e=a.$;return a.inherits(c,{constructor:d,init:function(){var a=this;a.connectRuntime(a.options,function(){a.exec("init"),a.trigger("ready")})}}),b.installTo(d.prototype),d}),b("widgets/filepaste",["base","uploader","lib/filepaste","widgets/widget"],function(a,b,c){var d=a.$;return b.register({name:"paste",init:function(b){if(b.paste&&"html5"===this.request("predict-runtime-type")){var e,f=this,g=a.Deferred(),h=d.extend({},{container:b.paste,accept:b.accept});return this.paste=e=new c(h),e.once("ready",g.resolve),e.on("paste",function(a){f.owner.request("add-file",[a])}),e.init(),g.promise()}},destroy:function(){this.paste&&this.paste.destroy()}})}),b("lib/blob",["base","runtime/client"],function(a,b){function c(a,c){var d=this;d.source=c,d.ruid=a,this.size=c.size||0,this.type=!c.type&&this.ext&&~"jpg,jpeg,png,gif,bmp".indexOf(this.ext)?"image/"+("jpg"===this.ext?"jpeg":this.ext):c.type||"application/octet-stream",b.call(d,"Blob"),this.uid=c.uid||this.uid,a&&d.connectRuntime(a)}return a.inherits(b,{constructor:c,slice:function(a,b){return this.exec("slice",a,b)},getSource:function(){return this.source}}),c}),b("lib/file",["base","lib/blob"],function(a,b){function c(a,c){var f;this.name=c.name||"untitled"+d++,f=e.exec(c.name)?RegExp.$1.toLowerCase():"",!f&&c.type&&(f=/\/(jpg|jpeg|png|gif|bmp)$/i.exec(c.type)?RegExp.$1.toLowerCase():"",this.name+="."+f),this.ext=f,this.lastModifiedDate=c.lastModifiedDate||(new Date).toLocaleString(),b.apply(this,arguments)}var d=1,e=/\.([^.]+)$/;return a.inherits(b,c)}),b("lib/filepicker",["base","runtime/client","lib/file"],function(b,c,d){function e(a){if(a=this.options=f.extend({},e.options,a),a.container=f(a.id),!a.container.length)throw new Error("按钮指定错误");a.innerHTML=a.innerHTML||a.label||a.container.html()||"",a.button=f(a.button||document.createElement("div")),a.button.html(a.innerHTML),a.container.html(a.button),c.call(this,"FilePicker",!0)}var f=b.$;return e.options={button:null,container:null,label:null,innerHTML:null,multiple:!0,accept:null,name:"file"},b.inherits(c,{constructor:e,init:function(){var c=this,e=c.options,g=e.button;g.addClass("webuploader-pick"),c.on("all",function(a){var b;switch(a){case"mouseenter":g.addClass("webuploader-pick-hover");break;case"mouseleave":g.removeClass("webuploader-pick-hover");break;case"change":b=c.exec("getFiles"),c.trigger("select",f.map(b,function(a){return a=new d(c.getRuid(),a),a._refer=e.container,a}),e.container)}}),c.connectRuntime(e,function(){c.refresh(),c.exec("init",e),c.trigger("ready")}),this._resizeHandler=b.bindFn(this.refresh,this),f(a).on("resize",this._resizeHandler)},refresh:function(){var a=this.getRuntime().getContainer(),b=this.options.button,c=b.outerWidth?b.outerWidth():b.width(),d=b.outerHeight?b.outerHeight():b.height(),e=b.offset();c&&d&&a.css({bottom:"auto",right:"auto",width:c+"px",height:d+"px"}).offset(e)},enable:function(){var a=this.options.button;a.removeClass("webuploader-pick-disable"),this.refresh()},disable:function(){var a=this.options.button;this.getRuntime().getContainer().css({top:"-99999px"}),a.addClass("webuploader-pick-disable")},destroy:function(){var b=this.options.button;f(a).off("resize",this._resizeHandler),b.removeClass("webuploader-pick-disable webuploader-pick-hover webuploader-pick")}}),e}),b("widgets/filepicker",["base","uploader","lib/filepicker","widgets/widget"],function(a,b,c){var d=a.$;return d.extend(b.options,{pick:null,accept:null}),b.register({name:"picker",init:function(a){return this.pickers=[],a.pick&&this.addBtn(a.pick)},refresh:function(){d.each(this.pickers,function(){this.refresh()})},addBtn:function(b){var e=this,f=e.options,g=f.accept,h=[];if(b)return d.isPlainObject(b)||(b={id:b}),d(b.id).each(function(){var i,j,k;k=a.Deferred(),i=d.extend({},b,{accept:d.isPlainObject(g)?[g]:g,swf:f.swf,runtimeOrder:f.runtimeOrder,id:this}),j=new c(i),j.once("ready",k.resolve),j.on("select",function(a){e.owner.request("add-file",[a])}),j.init(),e.pickers.push(j),h.push(k.promise())}),a.when.apply(a,h)},disable:function(){d.each(this.pickers,function(){this.disable()})},enable:function(){d.each(this.pickers,function(){this.enable()})},destroy:function(){d.each(this.pickers,function(){this.destroy()}),this.pickers=null}})}),b("file",["base","mediator"],function(a,b){function c(){return f+g++}function d(a){this.name=a.name||"Untitled",this.size=a.size||0,this.type=a.type||"application/octet-stream",this.lastModifiedDate=a.lastModifiedDate||1*new Date,this.id=c(),this.ext=h.exec(this.name)?RegExp.$1:"",this.statusText="",i[this.id]=d.Status.INITED,this.source=a,this.loaded=0,this.on("error",function(a){this.setStatus(d.Status.ERROR,a)})}var e=a.$,f="WU_FILE_",g=0,h=/\.([^.]+)$/,i={};return e.extend(d.prototype,{setStatus:function(a,b){var c=i[this.id];"undefined"!=typeof b&&(this.statusText=b),a!==c&&(i[this.id]=a,this.trigger("statuschange",a,c))},getStatus:function(){return i[this.id]},getSource:function(){return this.source},destroy:function(){this.off(),delete i[this.id]}}),b.installTo(d.prototype),d.Status={INITED:"inited",QUEUED:"queued",PROGRESS:"progress",ERROR:"error",COMPLETE:"complete",CANCELLED:"cancelled",INTERRUPT:"interrupt",INVALID:"invalid"},d}),b("queue",["base","mediator","file"],function(a,b,c){function d(){this.stats={numOfQueue:0,numOfSuccess:0,numOfCancel:0,numOfProgress:0,numOfUploadFailed:0,numOfInvalid:0,numofDeleted:0,numofInterrupt:0},this._queue=[],this._map={}}var e=a.$,f=c.Status;return e.extend(d.prototype,{append:function(a){return this._queue.push(a),this._fileAdded(a),this},prepend:function(a){return this._queue.unshift(a),this._fileAdded(a),this},getFile:function(a){return"string"!=typeof a?a:this._map[a]},fetch:function(a){var b,c,d=this._queue.length;for(a=a||f.QUEUED,b=0;d>b;b++)if(c=this._queue[b],a===c.getStatus())return c;return null},sort:function(a){"function"==typeof a&&this._queue.sort(a)},getFiles:function(){for(var a,b=[].slice.call(arguments,0),c=[],d=0,f=this._queue.length;f>d;d++)a=this._queue[d],(!b.length||~e.inArray(a.getStatus(),b))&&c.push(a);return c},removeFile:function(a){var b=this._map[a.id];b&&(delete this._map[a.id],a.destroy(),this.stats.numofDeleted++)},_fileAdded:function(a){var b=this,c=this._map[a.id];c||(this._map[a.id]=a,a.on("statuschange",function(a,c){b._onFileStatusChange(a,c)}))},_onFileStatusChange:function(a,b){var c=this.stats;switch(b){case f.PROGRESS:c.numOfProgress--;break;case f.QUEUED:c.numOfQueue--;break;case f.ERROR:c.numOfUploadFailed--;break;case f.INVALID:c.numOfInvalid--;break;case f.INTERRUPT:c.numofInterrupt--}switch(a){case f.QUEUED:c.numOfQueue++;break;case f.PROGRESS:c.numOfProgress++;break;case f.ERROR:c.numOfUploadFailed++;break;case f.COMPLETE:c.numOfSuccess++;break;case f.CANCELLED:c.numOfCancel++;break;case f.INVALID:c.numOfInvalid++;break;case f.INTERRUPT:c.numofInterrupt++}}}),b.installTo(d.prototype),d}),b("widgets/queue",["base","uploader","queue","file","lib/file","runtime/client","widgets/widget"],function(a,b,c,d,e,f){var g=a.$,h=/\.\w+$/,i=d.Status;return b.register({name:"queue",init:function(b){var d,e,h,i,j,k,l,m=this;if(g.isPlainObject(b.accept)&&(b.accept=[b.accept]),b.accept){for(j=[],h=0,e=b.accept.length;e>h;h++)i=b.accept[h].extensions,i&&j.push(i);j.length&&(k="\\."+j.join(",").replace(/,/g,"$|\\.").replace(/\*/g,".*")+"$"),m.accept=new RegExp(k,"i")}return m.queue=new c,m.stats=m.queue.stats,"html5"===this.request("predict-runtime-type")?(d=a.Deferred(),this.placeholder=l=new f("Placeholder"),l.connectRuntime({runtimeOrder:"html5"},function(){m._ruid=l.getRuid(),d.resolve()}),d.promise()):void 0},_wrapFile:function(a){if(!(a instanceof d)){if(!(a instanceof e)){if(!this._ruid)throw new Error("Can't add external files.");a=new e(this._ruid,a)}a=new d(a)}return a},acceptFile:function(a){var b=!a||!a.size||this.accept&&h.exec(a.name)&&!this.accept.test(a.name);return!b},_addFile:function(a){var b=this;return a=b._wrapFile(a),b.owner.trigger("beforeFileQueued",a)?b.acceptFile(a)?(b.queue.append(a),b.owner.trigger("fileQueued",a),a):void b.owner.trigger("error","Q_TYPE_DENIED",a):void 0},getFile:function(a){return this.queue.getFile(a)},addFile:function(a){var b=this;a.length||(a=[a]),a=g.map(a,function(a){return b._addFile(a)}),b.owner.trigger("filesQueued",a),b.options.auto&&setTimeout(function(){b.request("start-upload")},20)},getStats:function(){return this.stats},removeFile:function(a,b){var c=this;a=a.id?a:c.queue.getFile(a),this.request("cancel-file",a),b&&this.queue.removeFile(a)},getFiles:function(){return this.queue.getFiles.apply(this.queue,arguments)},fetchFile:function(){return this.queue.fetch.apply(this.queue,arguments)},retry:function(a,b){var c,d,e,f=this;if(a)return a=a.id?a:f.queue.getFile(a),a.setStatus(i.QUEUED),void(b||f.request("start-upload"));for(c=f.queue.getFiles(i.ERROR),d=0,e=c.length;e>d;d++)a=c[d],a.setStatus(i.QUEUED);f.request("start-upload")},sortFiles:function(){return this.queue.sort.apply(this.queue,arguments)},reset:function(){this.owner.trigger("reset"),this.queue=new c,this.stats=this.queue.stats},destroy:function(){this.reset(),this.placeholder&&this.placeholder.destroy()}})}),b("widgets/runtime",["uploader","runtime/runtime","widgets/widget"],function(a,b){return a.support=function(){return b.hasRuntime.apply(b,arguments)},a.register({name:"runtime",init:function(){if(!this.predictRuntimeType())throw Error("Runtime Error")},predictRuntimeType:function(){var a,c,d=this.options.runtimeOrder||b.orders,e=this.type;if(!e)for(d=d.split(/\s*,\s*/g),a=0,c=d.length;c>a;a++)if(b.hasRuntime(d[a])){this.type=e=d[a];break}return e}})}),b("lib/transport",["base","runtime/client","mediator"],function(a,b,c){function d(a){var c=this;a=c.options=e.extend(!0,{},d.options,a||{}),b.call(this,"Transport"),this._blob=null,this._formData=a.formData||{},this._headers=a.headers||{},this.on("progress",this._timeout),this.on("load error",function(){c.trigger("progress",1),clearTimeout(c._timer)})}var e=a.$;return d.options={server:"",method:"POST",withCredentials:!1,fileVal:"file",timeout:12e4,formData:{},headers:{},sendAsBinary:!1},e.extend(d.prototype,{appendBlob:function(a,b,c){var d=this,e=d.options;d.getRuid()&&d.disconnectRuntime(),d.connectRuntime(b.ruid,function(){d.exec("init")}),d._blob=b,e.fileVal=a||e.fileVal,e.filename=c||e.filename},append:function(a,b){"object"==typeof a?e.extend(this._formData,a):this._formData[a]=b},setRequestHeader:function(a,b){"object"==typeof a?e.extend(this._headers,a):this._headers[a]=b},send:function(a){this.exec("send",a),this._timeout()},abort:function(){return clearTimeout(this._timer),this.exec("abort")},destroy:function(){this.trigger("destroy"),this.off(),this.exec("destroy"),this.disconnectRuntime()},getResponse:function(){return this.exec("getResponse")},getResponseAsJson:function(){return this.exec("getResponseAsJson")},getStatus:function(){return this.exec("getStatus")},_timeout:function(){var a=this,b=a.options.timeout;b&&(clearTimeout(a._timer),a._timer=setTimeout(function(){a.abort(),a.trigger("error","timeout")},b))}}),c.installTo(d.prototype),d}),b("widgets/upload",["base","uploader","file","lib/transport","widgets/widget"],function(a,b,c,d){function e(a,b){var c,d,e=[],f=a.source,g=f.size,h=b?Math.ceil(g/b):1,i=0,j=0;for(d={file:a,has:function(){return!!e.length},shift:function(){return e.shift()},unshift:function(a){e.unshift(a)}};h>j;)c=Math.min(b,g-i),e.push({file:a,start:i,end:b?i+c:g,total:g,chunks:h,chunk:j++,cuted:d}),i+=c;return a.blocks=e.concat(),a.remaning=e.length,d}var f=a.$,g=a.isPromise,h=c.Status;f.extend(b.options,{prepareNextFile:!1,chunked:!1,chunkSize:5242880,chunkRetry:2,threads:3,formData:{}}),b.register({name:"upload",init:function(){var b=this.owner,c=this;this.runing=!1,this.progress=!1,b.on("startUpload",function(){c.progress=!0}).on("uploadFinished",function(){c.progress=!1}),this.pool=[],this.stack=[],this.pending=[],this.remaning=0,this.__tick=a.bindFn(this._tick,this),b.on("uploadComplete",function(a){a.blocks&&f.each(a.blocks,function(a,b){b.transport&&(b.transport.abort(),b.transport.destroy()),delete b.transport}),delete a.blocks,delete a.remaning})},reset:function(){this.request("stop-upload",!0),this.runing=!1,this.pool=[],this.stack=[],this.pending=[],this.remaning=0,this._trigged=!1,this._promise=null},startUpload:function(b){var c=this;if(f.each(c.request("get-files",h.INVALID),function(){c.request("remove-file",this)}),b)if(b=b.id?b:c.request("get-file",b),b.getStatus()===h.INTERRUPT)f.each(c.pool,function(a,c){c.file===b&&c.transport&&c.transport.send()}),b.setStatus(h.QUEUED);else{if(b.getStatus()===h.PROGRESS)return;b.setStatus(h.QUEUED)}else f.each(c.request("get-files",[h.INITED]),function(){this.setStatus(h.QUEUED)});c.runing||(c.runing=!0,f.each(c.pool,function(a,b){var d=b.file;d.getStatus()===h.INTERRUPT&&(d.setStatus(h.PROGRESS),c._trigged=!1,b.transport&&b.transport.send())}),b||f.each(c.request("get-files",h.INTERRUPT),function(){this.setStatus(h.PROGRESS)}),c._trigged=!1,a.nextTick(c.__tick),c.owner.trigger("startUpload"))},stopUpload:function(b,c){var d=this;if(b===!0&&(c=b,b=null),d.runing!==!1){if(b){if(b=b.id?b:d.request("get-file",b),b.getStatus()!==h.PROGRESS&&b.getStatus()!==h.QUEUED)return;return b.setStatus(h.INTERRUPT),f.each(d.pool,function(a,c){c.file===b&&(c.transport&&c.transport.abort(),d._putback(c),d._popBlock(c))}),a.nextTick(d.__tick)}d.runing=!1,this._promise&&this._promise.file&&this._promise.file.setStatus(h.INTERRUPT),c&&f.each(d.pool,function(a,b){b.transport&&b.transport.abort(),b.file.setStatus(h.INTERRUPT)}),d.owner.trigger("stopUpload")}},cancelFile:function(a){a=a.id?a:this.request("get-file",a),a.blocks&&f.each(a.blocks,function(a,b){var c=b.transport;c&&(c.abort(),c.destroy(),delete b.transport)}),a.setStatus(h.CANCELLED),this.owner.trigger("fileDequeued",a)},isInProgress:function(){return!!this.progress},_getStats:function(){return this.request("get-stats")},skipFile:function(a,b){a=a.id?a:this.request("get-file",a),a.setStatus(b||h.COMPLETE),a.skipped=!0,a.blocks&&f.each(a.blocks,function(a,b){var c=b.transport;c&&(c.abort(),c.destroy(),delete b.transport)}),this.owner.trigger("uploadSkip",a)},_tick:function(){var b,c,d=this,e=d.options;return d._promise?d._promise.always(d.__tick):void(d.pool.length1&&(f.each(k.blocks,function(a,b){d+=(b.percentage||0)*(b.end-b.start)}),c=d/k.size),i.trigger("uploadProgress",k,c||0)}),c=function(a){var c;return e=l.getResponseAsJson()||{},e._raw=l.getResponse(),c=function(b){a=b},i.trigger("uploadAccept",b,e,c)||(a=a||"server"),a},l.on("error",function(a,d){b.retried=b.retried||0,b.chunks>1&&~"http,abort".indexOf(a)&&b.retried1&&f.extend(m,{chunks:b.chunks,chunk:b.chunk}),i.trigger("uploadBeforeSend",b,m,n),l.appendBlob(j.fileVal,b.blob,k.name),l.append(m),l.setRequestHeader(n),l.send()},_finishFile:function(a,b,c){var d=this.owner;return d.request("after-send-file",arguments,function(){a.setStatus(h.COMPLETE),d.trigger("uploadSuccess",a,b,c)}).fail(function(b){a.getStatus()===h.PROGRESS&&a.setStatus(h.ERROR,b),d.trigger("uploadError",a,b)}).always(function(){d.trigger("uploadComplete",a)})}})}),b("widgets/validator",["base","uploader","file","widgets/widget"],function(a,b,c){var d,e=a.$,f={};return d={addValidator:function(a,b){f[a]=b},removeValidator:function(a){delete f[a]}},b.register({name:"validator",init:function(){var b=this;a.nextTick(function(){e.each(f,function(){this.call(b.owner)})})}}),d.addValidator("fileNumLimit",function(){var a=this,b=a.options,c=0,d=parseInt(b.fileNumLimit,10),e=!0;d&&(a.on("beforeFileQueued",function(a){return c>=d&&e&&(e=!1,this.trigger("error","Q_EXCEED_NUM_LIMIT",d,a),setTimeout(function(){e=!0},1)),c>=d?!1:!0}),a.on("fileQueued",function(){c++}),a.on("fileDequeued",function(){c--}),a.on("reset",function(){c=0}))}),d.addValidator("fileSizeLimit",function(){var a=this,b=a.options,c=0,d=parseInt(b.fileSizeLimit,10),e=!0;d&&(a.on("beforeFileQueued",function(a){var b=c+a.size>d;return b&&e&&(e=!1,this.trigger("error","Q_EXCEED_SIZE_LIMIT",d,a),setTimeout(function(){e=!0},1)),b?!1:!0}),a.on("fileQueued",function(a){c+=a.size}),a.on("fileDequeued",function(a){c-=a.size}),a.on("reset",function(){c=0}))}),d.addValidator("fileSingleSizeLimit",function(){var a=this,b=a.options,d=b.fileSingleSizeLimit;d&&a.on("beforeFileQueued",function(a){return a.size>d?(a.setStatus(c.Status.INVALID,"exceed_size"),this.trigger("error","F_EXCEED_SIZE",d,a),!1):void 0})}),d.addValidator("duplicate",function(){function a(a){for(var b,c=0,d=0,e=a.length;e>d;d++)b=a.charCodeAt(d),c=b+(c<<6)+(c<<16)-c;return c}var b=this,c=b.options,d={};c.duplicate||(b.on("beforeFileQueued",function(b){var c=b.__hash||(b.__hash=a(b.name+b.size+b.lastModifiedDate));return d[c]?(this.trigger("error","F_DUPLICATE",b),!1):void 0}),b.on("fileQueued",function(a){var b=a.__hash;b&&(d[b]=!0)}),b.on("fileDequeued",function(a){var b=a.__hash;b&&delete d[b]}),b.on("reset",function(){d={}}))}),d}),b("runtime/compbase",[],function(){function a(a,b){this.owner=a,this.options=a.options,this.getRuntime=function(){return b},this.getRuid=function(){return b.uid},this.trigger=function(){return a.trigger.apply(a,arguments)}}return a}),b("runtime/html5/runtime",["base","runtime/runtime","runtime/compbase"],function(b,c,d){function e(){var a={},d=this,e=this.destroy;c.apply(d,arguments),d.type=f,d.exec=function(c,e){var f,h=this,i=h.uid,j=b.slice(arguments,2);return g[c]&&(f=a[i]=a[i]||new g[c](h,d),f[e])?f[e].apply(f,j):void 0},d.destroy=function(){return e&&e.apply(this,arguments)}}var f="html5",g={};return b.inherits(c,{constructor:e,init:function(){var a=this;setTimeout(function(){a.trigger("ready")},1)}}),e.register=function(a,c){var e=g[a]=b.inherits(d,c);return e},a.Blob&&a.FileReader&&a.DataView&&c.addRuntime(f,e),e}),b("runtime/html5/blob",["runtime/html5/runtime","lib/blob"],function(a,b){return a.register("Blob",{slice:function(a,c){var d=this.owner.source,e=d.slice||d.webkitSlice||d.mozSlice; +return d=e.call(d,a,c),new b(this.getRuid(),d)}})}),b("runtime/html5/dnd",["base","runtime/html5/runtime","lib/file"],function(a,b,c){var d=a.$,e="webuploader-dnd-";return b.register("DragAndDrop",{init:function(){var b=this.elem=this.options.container;this.dragEnterHandler=a.bindFn(this._dragEnterHandler,this),this.dragOverHandler=a.bindFn(this._dragOverHandler,this),this.dragLeaveHandler=a.bindFn(this._dragLeaveHandler,this),this.dropHandler=a.bindFn(this._dropHandler,this),this.dndOver=!1,b.on("dragenter",this.dragEnterHandler),b.on("dragover",this.dragOverHandler),b.on("dragleave",this.dragLeaveHandler),b.on("drop",this.dropHandler),this.options.disableGlobalDnd&&(d(document).on("dragover",this.dragOverHandler),d(document).on("drop",this.dropHandler))},_dragEnterHandler:function(a){var b,c=this,d=c._denied||!1;return a=a.originalEvent||a,c.dndOver||(c.dndOver=!0,b=a.dataTransfer.items,b&&b.length&&(c._denied=d=!c.trigger("accept",b)),c.elem.addClass(e+"over"),c.elem[d?"addClass":"removeClass"](e+"denied")),a.dataTransfer.dropEffect=d?"none":"copy",!1},_dragOverHandler:function(a){var b=this.elem.parent().get(0);return b&&!d.contains(b,a.currentTarget)?!1:(clearTimeout(this._leaveTimer),this._dragEnterHandler.call(this,a),!1)},_dragLeaveHandler:function(){var a,b=this;return a=function(){b.dndOver=!1,b.elem.removeClass(e+"over "+e+"denied")},clearTimeout(b._leaveTimer),b._leaveTimer=setTimeout(a,100),!1},_dropHandler:function(a){var b,f,g=this,h=g.getRuid(),i=g.elem.parent().get(0);if(i&&!d.contains(i,a.currentTarget))return!1;a=a.originalEvent||a,b=a.dataTransfer;try{f=b.getData("text/html")}catch(j){}return f?void 0:(g._getTansferFiles(b,function(a){g.trigger("drop",d.map(a,function(a){return new c(h,a)}))}),g.dndOver=!1,g.elem.removeClass(e+"over"),!1)},_getTansferFiles:function(b,c){var d,e,f,g,h,i,j,k=[],l=[];for(d=b.items,e=b.files,j=!(!d||!d[0].webkitGetAsEntry),h=0,i=e.length;i>h;h++)f=e[h],g=d&&d[h],j&&g.webkitGetAsEntry().isDirectory?l.push(this._traverseDirectoryTree(g.webkitGetAsEntry(),k)):k.push(f);a.when.apply(a,l).done(function(){k.length&&c(k)})},_traverseDirectoryTree:function(b,c){var d=a.Deferred(),e=this;return b.isFile?b.file(function(a){c.push(a),d.resolve()}):b.isDirectory&&b.createReader().readEntries(function(b){var f,g=b.length,h=[],i=[];for(f=0;g>f;f++)h.push(e._traverseDirectoryTree(b[f],i));a.when.apply(a,h).then(function(){c.push.apply(c,i),d.resolve()},d.reject)}),d.promise()},destroy:function(){var a=this.elem;a&&(a.off("dragenter",this.dragEnterHandler),a.off("dragover",this.dragOverHandler),a.off("dragleave",this.dragLeaveHandler),a.off("drop",this.dropHandler),this.options.disableGlobalDnd&&(d(document).off("dragover",this.dragOverHandler),d(document).off("drop",this.dropHandler)))}})}),b("runtime/html5/filepaste",["base","runtime/html5/runtime","lib/file"],function(a,b,c){return b.register("FilePaste",{init:function(){var b,c,d,e,f=this.options,g=this.elem=f.container,h=".*";if(f.accept){for(b=[],c=0,d=f.accept.length;d>c;c++)e=f.accept[c].mimeTypes,e&&b.push(e);b.length&&(h=b.join(","),h=h.replace(/,/g,"|").replace(/\*/g,".*"))}this.accept=h=new RegExp(h,"i"),this.hander=a.bindFn(this._pasteHander,this),g.on("paste",this.hander)},_pasteHander:function(a){var b,d,e,f,g,h=[],i=this.getRuid();for(a=a.originalEvent||a,b=a.clipboardData.items,f=0,g=b.length;g>f;f++)d=b[f],"file"===d.kind&&(e=d.getAsFile())&&h.push(new c(i,e));h.length&&(a.preventDefault(),a.stopPropagation(),this.trigger("paste",h))},destroy:function(){this.elem.off("paste",this.hander)}})}),b("runtime/html5/filepicker",["base","runtime/html5/runtime"],function(a,b){var c=a.$;return b.register("FilePicker",{init:function(){var a,b,d,e,f=this.getRuntime().getContainer(),g=this,h=g.owner,i=g.options,j=this.label=c(document.createElement("label")),k=this.input=c(document.createElement("input"));if(k.attr("type","file"),k.attr("name",i.name),k.addClass("webuploader-element-invisible"),j.on("click",function(){k.trigger("click")}),j.css({opacity:0,width:"100%",height:"100%",display:"block",cursor:"pointer",background:"#ffffff"}),i.multiple&&k.attr("multiple","multiple"),i.accept&&i.accept.length>0){for(a=[],b=0,d=i.accept.length;d>b;b++)a.push(i.accept[b].mimeTypes);k.attr("accept",a.join(","))}f.append(k),f.append(j),e=function(a){h.trigger(a.type)},k.on("change",function(a){var b,d=arguments.callee;g.files=a.target.files,b=this.cloneNode(!0),b.value=null,this.parentNode.replaceChild(b,this),k.off(),k=c(b).on("change",d).on("mouseenter mouseleave",e),h.trigger("change")}),j.on("mouseenter mouseleave",e)},getFiles:function(){return this.files},destroy:function(){this.input.off(),this.label.off()}})}),b("runtime/html5/transport",["base","runtime/html5/runtime"],function(a,b){var c=a.noop,d=a.$;return b.register("Transport",{init:function(){this._status=0,this._response=null},send:function(){var b,c,e,f=this.owner,g=this.options,h=this._initAjax(),i=f._blob,j=g.server;g.sendAsBinary?(j+=(/\?/.test(j)?"&":"?")+d.param(f._formData),c=i.getSource()):(b=new FormData,d.each(f._formData,function(a,c){b.append(a,c)}),b.append(g.fileVal,i.getSource(),g.filename||f._formData.name||"")),g.withCredentials&&"withCredentials"in h?(h.open(g.method,j,!0),h.withCredentials=!0):h.open(g.method,j),this._setRequestHeader(h,g.headers),c?(h.overrideMimeType&&h.overrideMimeType("application/octet-stream"),a.os.android?(e=new FileReader,e.onload=function(){h.send(this.result),e=e.onload=null},e.readAsArrayBuffer(c)):h.send(c)):h.send(b)},getResponse:function(){return this._response},getResponseAsJson:function(){return this._parseJson(this._response)},getStatus:function(){return this._status},abort:function(){var a=this._xhr;a&&(a.upload.onprogress=c,a.onreadystatechange=c,a.abort(),this._xhr=a=null)},destroy:function(){this.abort()},_initAjax:function(){var a=this,b=new XMLHttpRequest,d=this.options;return!d.withCredentials||"withCredentials"in b||"undefined"==typeof XDomainRequest||(b=new XDomainRequest),b.upload.onprogress=function(b){var c=0;return b.lengthComputable&&(c=b.loaded/b.total),a.trigger("progress",c)},b.onreadystatechange=function(){return 4===b.readyState?(b.upload.onprogress=c,b.onreadystatechange=c,a._xhr=null,a._status=b.status,b.status>=200&&b.status<300?(a._response=b.responseText,a.trigger("load")):b.status>=500&&b.status<600?(a._response=b.responseText,a.trigger("error","server")):a.trigger("error",a._status?"http":"abort")):void 0},a._xhr=b,b},_setRequestHeader:function(a,b){d.each(b,function(b,c){a.setRequestHeader(b,c)})},_parseJson:function(a){var b;try{b=JSON.parse(a)}catch(c){b={}}return b}})}),b("runtime/flash/runtime",["base","runtime/runtime","runtime/compbase"],function(b,c,d){function e(){var a;try{a=navigator.plugins["Shockwave Flash"],a=a.description}catch(b){try{a=new ActiveXObject("ShockwaveFlash.ShockwaveFlash").GetVariable("$version")}catch(c){a="0.0"}}return a=a.match(/\d+/g),parseFloat(a[0]+"."+a[1],10)}function f(){function d(a,b){var c,d,e=a.type||a;c=e.split("::"),d=c[0],e=c[1],"Ready"===e&&d===j.uid?j.trigger("ready"):f[d]&&f[d].trigger(e.toLowerCase(),a,b)}var e={},f={},g=this.destroy,j=this,k=b.guid("webuploader_");c.apply(j,arguments),j.type=h,j.exec=function(a,c){var d,g=this,h=g.uid,k=b.slice(arguments,2);return f[h]=g,i[a]&&(e[h]||(e[h]=new i[a](g,j)),d=e[h],d[c])?d[c].apply(d,k):j.flashExec.apply(g,arguments)},a[k]=function(){var a=arguments;setTimeout(function(){d.apply(null,a)},1)},this.jsreciver=k,this.destroy=function(){return g&&g.apply(this,arguments)},this.flashExec=function(a,c){var d=j.getFlash(),e=b.slice(arguments,2);return d.exec(this.uid,a,c,e)}}var g=b.$,h="flash",i={};return b.inherits(c,{constructor:f,init:function(){var a,c=this.getContainer(),d=this.options;c.css({position:"absolute",top:"-8px",left:"-8px",width:"9px",height:"9px",overflow:"hidden"}),a='',c.html(a)},getFlash:function(){return this._flash?this._flash:(this._flash=g("#"+this.uid).get(0),this._flash)}}),f.register=function(a,c){return c=i[a]=b.inherits(d,g.extend({flashExec:function(){var a=this.owner,b=this.getRuntime();return b.flashExec.apply(a,arguments)}},c))},e()>=11.4&&c.addRuntime(h,f),f}),b("runtime/flash/filepicker",["base","runtime/flash/runtime"],function(a,b){var c=a.$;return b.register("FilePicker",{init:function(a){var b,d,e=c.extend({},a);for(b=e.accept&&e.accept.length,d=0;b>d;d++)e.accept[d].title||(e.accept[d].title="Files");delete e.button,delete e.id,delete e.container,this.flashExec("FilePicker","init",e)},destroy:function(){this.flashExec("FilePicker","destroy")}})}),b("runtime/flash/transport",["base","runtime/flash/runtime","runtime/client"],function(b,c,d){var e=b.$;return c.register("Transport",{init:function(){this._status=0,this._response=null,this._responseJson=null},send:function(){var a,b=this.owner,c=this.options,d=this._initAjax(),f=b._blob,g=c.server;d.connectRuntime(f.ruid),c.sendAsBinary?(g+=(/\?/.test(g)?"&":"?")+e.param(b._formData),a=f.uid):(e.each(b._formData,function(a,b){d.exec("append",a,b)}),d.exec("appendBlob",c.fileVal,f.uid,c.filename||b._formData.name||"")),this._setRequestHeader(d,c.headers),d.exec("send",{method:c.method,url:g,forceURLStream:c.forceURLStream,mimeType:"application/octet-stream"},a)},getStatus:function(){return this._status},getResponse:function(){return this._response||""},getResponseAsJson:function(){return this._responseJson},abort:function(){var a=this._xhr;a&&(a.exec("abort"),a.destroy(),this._xhr=a=null)},destroy:function(){this.abort()},_initAjax:function(){var b=this,c=new d("XMLHttpRequest");return c.on("uploadprogress progress",function(a){var c=a.loaded/a.total;return c=Math.min(1,Math.max(0,c)),b.trigger("progress",c)}),c.on("load",function(){var d,e=c.exec("getStatus"),f=!1,g="";return c.off(),b._xhr=null,e>=200&&300>e?f=!0:e>=500&&600>e?(f=!0,g="server"):g="http",f&&(b._response=c.exec("getResponse"),b._response=decodeURIComponent(b._response),d=a.JSON&&a.JSON.parse||function(a){try{return new Function("return "+a).call()}catch(b){return{}}},b._responseJson=b._response?d(b._response):{}),c.destroy(),c=null,g?b.trigger("error",g):b.trigger("load")}),c.on("error",function(){c.off(),b._xhr=null,b.trigger("error","http")}),b._xhr=c,c},_setRequestHeader:function(a,b){e.each(b,function(b,c){a.exec("setRequestHeader",b,c)})}})}),b("preset/withoutimage",["base","widgets/filednd","widgets/filepaste","widgets/filepicker","widgets/queue","widgets/runtime","widgets/upload","widgets/validator","runtime/html5/blob","runtime/html5/dnd","runtime/html5/filepaste","runtime/html5/filepicker","runtime/html5/transport","runtime/flash/filepicker","runtime/flash/transport"],function(a){return a}),b("webuploader",["preset/withoutimage"],function(a){return a}),c("webuploader")}); \ No newline at end of file diff --git a/views/attachment/page.html b/views/attachment/page.html new file mode 100644 index 00000000..7509e8e4 --- /dev/null +++ b/views/attachment/page.html @@ -0,0 +1,42 @@ +
    +
    +
    +
    +
    +
    +
    + + {{/*

    或将文件拖到这里,单次最多可上传10个

    */}} +
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + +
    文件名131267.89 KB
    文件名Jacob31123 KB
    文件名1Larry123 KB
    +
    +
    +
    + +
    +
    +
    \ No newline at end of file diff --git a/views/layouts/attachment.html b/views/layouts/attachment.html new file mode 100644 index 00000000..26445f74 --- /dev/null +++ b/views/layouts/attachment.html @@ -0,0 +1,40 @@ + + + + + + + MM-Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +{{.LayoutContent}} + + \ No newline at end of file diff --git a/views/page/edit.html b/views/page/edit.html index d84bf5db..89dbc662 100644 --- a/views/page/edit.html +++ b/views/page/edit.html @@ -35,17 +35,19 @@ css : "/static/plugins/editor.md/lib/katex/katex.min" }; var editor = editormd("document_page_editor", { + placeholder: "欢迎使用 MM-Wiki, 支持 Markdown 语法", width: "100%", height: 500, path : '/static/plugins/editor.md/lib/', toolbarIcons : function() { // Using "||" set icons align right. return [ - "sidebar", "save", "|", "undo", "redo", "|", + "sidebar", "save", "|", + "undo", "redo", "|", "bold", "del", "italic", "quote", "|", "h1", "h2", "h3", "h4", "h5", "h6", "|", "list-ul", "list-ol", "hr", "|", - "link", "reference-link", "image", "code", "preformatted-text", "code-block", "table", "datetime", "html-entities", "pagebreak", "|", + "link", "reference-link", "image", "attachment", "code", "preformatted-text", "code-block", "table", "datetime", "html-entities", "pagebreak", "|", "goto-line", "watch", "preview", "search", "|", "help" ] @@ -53,7 +55,8 @@ // 增加自定义工具栏 toolbarCustomIcons : { sidebar : '', - save : "" + save : "", + attachment : '' }, theme : "default", previewTheme : "default", From cae1cc098b2927ae515ff965b8602570a471fc61 Mon Sep 17 00:00:00 2001 From: phachon Date: Tue, 20 Nov 2018 17:24:02 +0800 Subject: [PATCH 02/14] add bootstrap-fileinput --- static/css/common.css | 8 + static/js/modules/page.js | 9 +- .../bootstrap-fileinput/css/fileinput-rtl.css | 101 + .../css/fileinput-rtl.min.css | 12 + .../bootstrap-fileinput/css/fileinput.css | 520 ++ .../bootstrap-fileinput/css/fileinput.min.css | 12 + .../bootstrap-fileinput/examples/index.html | 237 + .../bootstrap-fileinput/img/loading-sm.gif | Bin 0 -> 2670 bytes .../bootstrap-fileinput/img/loading.gif | Bin 0 -> 847 bytes .../bootstrap-fileinput/js/fileinput.js | 4489 +++++++++ .../bootstrap-fileinput/js/fileinput.min.js | 11 + .../bootstrap-fileinput/js/locales/zh-TW.js | 102 + .../bootstrap-fileinput/js/locales/zh.js | 101 + .../bootstrap-fileinput/js/plugins/piexif.js | 2471 +++++ .../js/plugins/piexif.min.js | 1 + .../bootstrap-fileinput/js/plugins/purify.js | 1009 ++ .../js/plugins/purify.min.js | 1 + .../js/plugins/sortable.js | 1590 ++++ .../js/plugins/sortable.min.js | 1 + .../bootstrap-fileinput/nuget/Package.nuspec | 21 + .../bootstrap-fileinput/nuget/build.bat | 35 + .../themes/explorer-bck/theme.css | 156 + .../themes/explorer-bck/theme.js | 58 + .../themes/explorer-bck/theme.min.css | 12 + .../themes/explorer-bck/theme.min.js | 12 + .../themes/explorer-fa/theme.css | 157 + .../themes/explorer-fa/theme.js | 87 + .../themes/explorer-fa/theme.min.css | 13 + .../themes/explorer-fa/theme.min.js | 14 + .../themes/explorer-fas/theme.css | 157 + .../themes/explorer-fas/theme.js | 87 + .../themes/explorer-fas/theme.min.css | 13 + .../themes/explorer-fas/theme.min.js | 14 + .../themes/explorer/theme.css | 156 + .../themes/explorer/theme.js | 56 + .../themes/explorer/theme.min.css | 12 + .../themes/explorer/theme.min.js | 12 + .../bootstrap-fileinput/themes/fa/theme.js | 47 + .../themes/fa/theme.min.js | 12 + .../bootstrap-fileinput/themes/fas/theme.js | 47 + .../themes/fas/theme.min.js | 12 + .../bootstrap-fileinput/themes/gly/theme.js | 45 + .../themes/gly/theme.min.js | 12 + static/plugins/webuploader/README.md | 25 - static/plugins/webuploader/Uploader.swf | Bin 143099 -> 0 bytes static/plugins/webuploader/bg.png | Bin 2851 -> 0 bytes static/plugins/webuploader/icons.png | Bin 2678 -> 0 bytes static/plugins/webuploader/image.png | Bin 1672 -> 0 bytes static/plugins/webuploader/progress.png | Bin 1269 -> 0 bytes static/plugins/webuploader/success.png | Bin 1621 -> 0 bytes static/plugins/webuploader/upload.js | 569 -- static/plugins/webuploader/upload.style.css | 14 - static/plugins/webuploader/webuploader.css | 28 - .../plugins/webuploader/webuploader.custom.js | 6502 ------------- .../webuploader/webuploader.custom.min.js | 2 - static/plugins/webuploader/webuploader.fis.js | 8083 ---------------- .../webuploader/webuploader.flashonly.js | 4622 ---------- .../webuploader/webuploader.flashonly.min.js | 2 - .../webuploader/webuploader.html5only.js | 6030 ------------ .../webuploader/webuploader.html5only.min.js | 2 - static/plugins/webuploader/webuploader.js | 8106 ----------------- static/plugins/webuploader/webuploader.min.js | 3 - .../webuploader/webuploader.noimage.js | 5026 ---------- .../webuploader/webuploader.noimage.min.js | 2 - .../plugins/webuploader/webuploader.nolog.js | 8012 ---------------- .../webuploader/webuploader.nolog.min.js | 3 - .../webuploader/webuploader.withoutimage.js | 4993 ---------- .../webuploader.withoutimage.min.js | 2 - views/attachment/page.html | 117 +- views/layouts/attachment.html | 21 +- 70 files changed, 12014 insertions(+), 52072 deletions(-) create mode 100644 static/plugins/bootstrap-fileinput/css/fileinput-rtl.css create mode 100644 static/plugins/bootstrap-fileinput/css/fileinput-rtl.min.css create mode 100644 static/plugins/bootstrap-fileinput/css/fileinput.css create mode 100644 static/plugins/bootstrap-fileinput/css/fileinput.min.css create mode 100644 static/plugins/bootstrap-fileinput/examples/index.html create mode 100644 static/plugins/bootstrap-fileinput/img/loading-sm.gif create mode 100644 static/plugins/bootstrap-fileinput/img/loading.gif create mode 100644 static/plugins/bootstrap-fileinput/js/fileinput.js create mode 100644 static/plugins/bootstrap-fileinput/js/fileinput.min.js create mode 100644 static/plugins/bootstrap-fileinput/js/locales/zh-TW.js create mode 100644 static/plugins/bootstrap-fileinput/js/locales/zh.js create mode 100644 static/plugins/bootstrap-fileinput/js/plugins/piexif.js create mode 100644 static/plugins/bootstrap-fileinput/js/plugins/piexif.min.js create mode 100644 static/plugins/bootstrap-fileinput/js/plugins/purify.js create mode 100644 static/plugins/bootstrap-fileinput/js/plugins/purify.min.js create mode 100644 static/plugins/bootstrap-fileinput/js/plugins/sortable.js create mode 100644 static/plugins/bootstrap-fileinput/js/plugins/sortable.min.js create mode 100644 static/plugins/bootstrap-fileinput/nuget/Package.nuspec create mode 100644 static/plugins/bootstrap-fileinput/nuget/build.bat create mode 100644 static/plugins/bootstrap-fileinput/themes/explorer-bck/theme.css create mode 100644 static/plugins/bootstrap-fileinput/themes/explorer-bck/theme.js create mode 100644 static/plugins/bootstrap-fileinput/themes/explorer-bck/theme.min.css create mode 100644 static/plugins/bootstrap-fileinput/themes/explorer-bck/theme.min.js create mode 100644 static/plugins/bootstrap-fileinput/themes/explorer-fa/theme.css create mode 100644 static/plugins/bootstrap-fileinput/themes/explorer-fa/theme.js create mode 100644 static/plugins/bootstrap-fileinput/themes/explorer-fa/theme.min.css create mode 100644 static/plugins/bootstrap-fileinput/themes/explorer-fa/theme.min.js create mode 100644 static/plugins/bootstrap-fileinput/themes/explorer-fas/theme.css create mode 100644 static/plugins/bootstrap-fileinput/themes/explorer-fas/theme.js create mode 100644 static/plugins/bootstrap-fileinput/themes/explorer-fas/theme.min.css create mode 100644 static/plugins/bootstrap-fileinput/themes/explorer-fas/theme.min.js create mode 100644 static/plugins/bootstrap-fileinput/themes/explorer/theme.css create mode 100644 static/plugins/bootstrap-fileinput/themes/explorer/theme.js create mode 100644 static/plugins/bootstrap-fileinput/themes/explorer/theme.min.css create mode 100644 static/plugins/bootstrap-fileinput/themes/explorer/theme.min.js create mode 100644 static/plugins/bootstrap-fileinput/themes/fa/theme.js create mode 100644 static/plugins/bootstrap-fileinput/themes/fa/theme.min.js create mode 100644 static/plugins/bootstrap-fileinput/themes/fas/theme.js create mode 100644 static/plugins/bootstrap-fileinput/themes/fas/theme.min.js create mode 100644 static/plugins/bootstrap-fileinput/themes/gly/theme.js create mode 100644 static/plugins/bootstrap-fileinput/themes/gly/theme.min.js delete mode 100644 static/plugins/webuploader/README.md delete mode 100644 static/plugins/webuploader/Uploader.swf delete mode 100644 static/plugins/webuploader/bg.png delete mode 100644 static/plugins/webuploader/icons.png delete mode 100644 static/plugins/webuploader/image.png delete mode 100644 static/plugins/webuploader/progress.png delete mode 100644 static/plugins/webuploader/success.png delete mode 100644 static/plugins/webuploader/upload.js delete mode 100644 static/plugins/webuploader/upload.style.css delete mode 100644 static/plugins/webuploader/webuploader.css delete mode 100644 static/plugins/webuploader/webuploader.custom.js delete mode 100644 static/plugins/webuploader/webuploader.custom.min.js delete mode 100644 static/plugins/webuploader/webuploader.fis.js delete mode 100644 static/plugins/webuploader/webuploader.flashonly.js delete mode 100644 static/plugins/webuploader/webuploader.flashonly.min.js delete mode 100644 static/plugins/webuploader/webuploader.html5only.js delete mode 100644 static/plugins/webuploader/webuploader.html5only.min.js delete mode 100644 static/plugins/webuploader/webuploader.js delete mode 100644 static/plugins/webuploader/webuploader.min.js delete mode 100644 static/plugins/webuploader/webuploader.noimage.js delete mode 100644 static/plugins/webuploader/webuploader.noimage.min.js delete mode 100644 static/plugins/webuploader/webuploader.nolog.js delete mode 100644 static/plugins/webuploader/webuploader.nolog.min.js delete mode 100644 static/plugins/webuploader/webuploader.withoutimage.js delete mode 100644 static/plugins/webuploader/webuploader.withoutimage.min.js diff --git a/static/css/common.css b/static/css/common.css index c35c6efd..2afe2ad5 100644 --- a/static/css/common.css +++ b/static/css/common.css @@ -446,3 +446,11 @@ ul.countdown li p { .view-page-time { font-size: 12px; } + +#attach_row .file-drop-zone { + margin: 3px; +} + +#attach_row .file-other-icon { + font-size: 1.4em; +} \ No newline at end of file diff --git a/static/js/modules/page.js b/static/js/modules/page.js index 90022c74..a1c025d5 100644 --- a/static/js/modules/page.js +++ b/static/js/modules/page.js @@ -164,9 +164,16 @@ var Page = { shade : 0.1, resize: false, maxmin: false, - area: ["600px", "300px"], + area: ["800px", "500px"], content: "/attachment/page", padding:"10px" }); + }, + + /** + * uploader + */ + attachUpload: function () { + } }; \ No newline at end of file diff --git a/static/plugins/bootstrap-fileinput/css/fileinput-rtl.css b/static/plugins/bootstrap-fileinput/css/fileinput-rtl.css new file mode 100644 index 00000000..93395a74 --- /dev/null +++ b/static/plugins/bootstrap-fileinput/css/fileinput-rtl.css @@ -0,0 +1,101 @@ +/*! + * bootstrap-fileinput v4.5.1 + * http://plugins.krajee.com/file-input + * + * Krajee RTL (Right To Left) default styling for bootstrap-fileinput. + * + * Author: Kartik Visweswaran + * Copyright: 2014 - 2018, Kartik Visweswaran, Krajee.com + * + * Licensed under the BSD 3-Clause + * https://github.com/kartik-v/bootstrap-fileinput/blob/master/LICENSE.md + */ +.kv-rtl .close, .kv-rtl .krajee-default .file-actions, .kv-rtl .krajee-default .file-other-error { + float: left; +} + +.kv-rtl .krajee-default.file-preview-frame, .kv-rtl .krajee-default .file-drag-handle, .kv-rtl .krajee-default .file-upload-indicator { + float: right; +} + +.kv-rtl .file-zoom-dialog, .kv-rtl .file-error-message pre, .kv-rtl .file-error-message ul { + text-align: right; +} + +.kv-rtl { + direction: rtl; +} + +.kv-rtl .floating-buttons { + left: 10px; + right: auto; +} + +.kv-rtl .floating-buttons .btn-kv { + margin-left: 0; + margin-right: 3px; +} + +.kv-rtl .file-caption-icon { + left: auto; + right: 8px; +} + +.kv-rtl .file-drop-zone { + margin: 12px 12px 12px 15px; +} + +.kv-rtl .btn-prev { + right: 1px; + left: auto; +} + +.kv-rtl .btn-next { + left: 1px; + right: auto; +} + +.kv-rtl .pull-right, .kv-rtl .float-right { + float: left !important; +} + +.kv-rtl .pull-left, .kv-rtl .float-left { + float: right !important; +} + +.kv-rtl .kv-zoom-title { + direction: ltr; +} + +.kv-rtl .krajee-default.file-preview-frame { + box-shadow: -1px 1px 5px 0 #a2958a; +} + +.kv-rtl .krajee-default.file-preview-frame:not(.file-preview-error):hover { + box-shadow: -3px 3px 5px 0 #333; +} + +.kv-rtl .kv-zoom-actions .btn-kv { + margin-left: 0; + margin-right: 3px; +} + +.kv-rtl .file-caption.icon-visible .file-caption-name { + padding-left: 0; + padding-right: 15px; +} + +.kv-rtl .input-group-btn > .btn:last-child { + border-radius: 4px 0 0 4px; +} + +.kv-rtl .input-group .form-control:first-child { + border-radius: 0 4px 4px 0; +} + +.kv-rtl .btn-file input[type=file] { + left: auto; + right: 0; + text-align: left; + background: none repeat scroll 100% 0 transparent; +} \ No newline at end of file diff --git a/static/plugins/bootstrap-fileinput/css/fileinput-rtl.min.css b/static/plugins/bootstrap-fileinput/css/fileinput-rtl.min.css new file mode 100644 index 00000000..6211f2b2 --- /dev/null +++ b/static/plugins/bootstrap-fileinput/css/fileinput-rtl.min.css @@ -0,0 +1,12 @@ +/*! + * bootstrap-fileinput v4.5.1 + * http://plugins.krajee.com/file-input + * + * Krajee RTL (Right To Left) default styling for bootstrap-fileinput. + * + * Author: Kartik Visweswaran + * Copyright: 2014 - 2018, Kartik Visweswaran, Krajee.com + * + * Licensed under the BSD 3-Clause + * https://github.com/kartik-v/bootstrap-fileinput/blob/master/LICENSE.md + */.kv-rtl .close,.kv-rtl .krajee-default .file-actions,.kv-rtl .krajee-default .file-other-error{float:left}.kv-rtl .krajee-default .file-drag-handle,.kv-rtl .krajee-default .file-upload-indicator,.kv-rtl .krajee-default.file-preview-frame{float:right}.kv-rtl .file-error-message pre,.kv-rtl .file-error-message ul,.kv-rtl .file-zoom-dialog{text-align:right}.kv-rtl{direction:rtl}.kv-rtl .floating-buttons{left:10px;right:auto}.kv-rtl .floating-buttons .btn-kv{margin-left:0;margin-right:3px}.kv-rtl .file-caption-icon{left:auto;right:8px}.kv-rtl .file-drop-zone{margin:12px 12px 12px 15px}.kv-rtl .btn-prev{right:1px;left:auto}.kv-rtl .btn-next{left:1px;right:auto}.kv-rtl .float-right,.kv-rtl .pull-right{float:left!important}.kv-rtl .float-left,.kv-rtl .pull-left{float:right!important}.kv-rtl .kv-zoom-title{direction:ltr}.kv-rtl .krajee-default.file-preview-frame{box-shadow:-1px 1px 5px 0 #a2958a}.kv-rtl .krajee-default.file-preview-frame:not(.file-preview-error):hover{box-shadow:-3px 3px 5px 0 #333}.kv-rtl .kv-zoom-actions .btn-kv{margin-left:0;margin-right:3px}.kv-rtl .file-caption.icon-visible .file-caption-name{padding-left:0;padding-right:15px}.kv-rtl .input-group-btn>.btn:last-child{border-radius:4px 0 0 4px}.kv-rtl .input-group .form-control:first-child{border-radius:0 4px 4px 0}.kv-rtl .btn-file input[type=file]{left:auto;right:0;text-align:left;background:100% 0 none} \ No newline at end of file diff --git a/static/plugins/bootstrap-fileinput/css/fileinput.css b/static/plugins/bootstrap-fileinput/css/fileinput.css new file mode 100644 index 00000000..79b41353 --- /dev/null +++ b/static/plugins/bootstrap-fileinput/css/fileinput.css @@ -0,0 +1,520 @@ +/*! + * bootstrap-fileinput v4.5.1 + * http://plugins.krajee.com/file-input + * + * Krajee default styling for bootstrap-fileinput. + * + * Author: Kartik Visweswaran + * Copyright: 2014 - 2018, Kartik Visweswaran, Krajee.com + * + * Licensed under the BSD 3-Clause + * https://github.com/kartik-v/bootstrap-fileinput/blob/master/LICENSE.md + */ +.file-loading input[type=file], input[type=file].file-loading { + width: 0; + height: 0; +} + +.file-no-browse { + position: absolute; + left: 50%; + bottom: 20%; + width: 1px; + height: 1px; + font-size: 0; + opacity: 0; + border: none; + background: none; + outline: none; + box-shadow: none; +} + +.kv-hidden, .file-caption-icon, .file-zoom-dialog .modal-header:before, .file-zoom-dialog .modal-header:after, .file-input-new .file-preview, .file-input-new .close, .file-input-new .glyphicon-file, .file-input-new .fileinput-remove-button, .file-input-new .fileinput-upload-button, .file-input-new .no-browse .input-group-btn, .file-input-ajax-new .fileinput-remove-button, .file-input-ajax-new .fileinput-upload-button, .file-input-ajax-new .no-browse .input-group-btn, .hide-content .kv-file-content { + display: none; +} + +.btn-file input[type=file], .file-caption-icon, .file-preview .fileinput-remove, .krajee-default .file-thumb-progress, .file-zoom-dialog .btn-navigate, .file-zoom-dialog .floating-buttons { + position: absolute; +} + +.file-input, .file-loading:before, .btn-file, .file-caption, .file-preview, .krajee-default.file-preview-frame, .krajee-default .file-thumbnail-footer, .file-zoom-dialog .modal-dialog { + position: relative; +} + +.file-error-message pre, .file-error-message ul, .krajee-default .file-actions, .krajee-default .file-other-error { + text-align: left; +} + +.file-error-message pre, .file-error-message ul { + margin: 0; +} + +.krajee-default .file-drag-handle, .krajee-default .file-upload-indicator { + float: left; + margin: 5px 0 -5px; + width: 16px; + height: 16px; +} + +.krajee-default .file-thumb-progress .progress, .krajee-default .file-thumb-progress .progress-bar { + height: 11px; + font-family: Verdana, Helvetica, sans-serif; + font-size: 9px; +} + +.krajee-default .file-caption-info, .krajee-default .file-size-info { + display: block; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + width: 160px; + height: 15px; + margin: auto; +} + +.file-zoom-content > .file-object.type-video, .file-zoom-content > .file-object.type-flash, .file-zoom-content > .file-object.type-image { + max-width: 100%; + max-height: 100%; + width: auto; +} + +.file-zoom-content > .file-object.type-video, .file-zoom-content > .file-object.type-flash { + height: 100%; +} + +.file-zoom-content > .file-object.type-pdf, .file-zoom-content > .file-object.type-html, .file-zoom-content > .file-object.type-text, .file-zoom-content > .file-object.type-default { + width: 100%; +} + +.file-loading:before { + content: " Loading..."; + display: inline-block; + padding-left: 20px; + line-height: 16px; + font-size: 13px; + font-variant: small-caps; + color: #999; + background: transparent url(../img/loading.gif) top left no-repeat; +} + +.file-object { + margin: 0 0 -5px 0; + padding: 0; +} + +.btn-file { + overflow: hidden; +} + +.btn-file input[type=file] { + top: 0; + left: 0; + min-width: 100%; + min-height: 100%; + text-align: right; + opacity: 0; + background: none repeat scroll 0 0 transparent; + cursor: inherit; + display: block; +} + +.btn-file ::-ms-browse { + font-size: 10000px; + width: 100%; + height: 100%; +} + +.file-caption .file-caption-name { + width: 100%; + margin: 0; + padding: 0; + box-shadow: none; + border: none; + background: none; + outline: none; +} + +.file-caption.icon-visible .file-caption-icon { + display: inline-block; +} + +.file-caption.icon-visible .file-caption-name { + padding-left: 15px; +} + +.file-caption-icon { + left: 8px; +} + +.file-error-message { + color: #a94442; + background-color: #f2dede; + margin: 5px; + border: 1px solid #ebccd1; + border-radius: 4px; + padding: 15px; +} + +.file-error-message pre { + margin: 5px 0; +} + +.file-caption-disabled { + background-color: #eee; + cursor: not-allowed; + opacity: 1; +} + +.file-preview { + border-radius: 5px; + border: 1px solid #ddd; + padding: 8px; + width: 100%; + margin-bottom: 5px; +} + +.file-preview .btn-xs { + padding: 1px 5px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} + +.file-preview .fileinput-remove { + top: 1px; + right: 1px; + line-height: 10px; +} + +.file-preview .clickable { + cursor: pointer; +} + +.file-preview-image { + font: 40px Impact, Charcoal, sans-serif; + color: #008000; +} + +.krajee-default.file-preview-frame { + margin: 8px; + border: 1px solid rgba(0,0,0,0.2); + box-shadow: 0 0 10px 0 rgba(0,0,0,0.2); + padding: 6px; + float: left; + text-align: center; +} + +.krajee-default.file-preview-frame .kv-file-content { + width: 213px; + height: 160px; +} + +.krajee-default.file-preview-frame .kv-file-content.kv-pdf-rendered { + width: 400px; +} + +.krajee-default.file-preview-frame .file-thumbnail-footer { + height: 70px; +} + +.krajee-default.file-preview-frame:not(.file-preview-error):hover { + border: 1px solid rgba(0,0,0,0.3); + box-shadow: 0 0 10px 0 rgba(0,0,0,0.4); +} + +.krajee-default .file-preview-text { + display: block; + color: #428bca; + border: 1px solid #ddd; + font-family: Menlo, Monaco, Consolas, "Courier New", monospace; + outline: none; + padding: 8px; + resize: none; +} + +.krajee-default .file-preview-html { + border: 1px solid #ddd; + padding: 8px; + overflow: auto; +} + +.krajee-default .file-other-icon { + font-size: 6em; +} + +.krajee-default .file-footer-buttons { + float: right; +} + +.krajee-default .file-footer-caption { + display: block; + text-align: center; + padding-top: 4px; + font-size: 11px; + color: #777; + margin-bottom: 15px; +} + +.krajee-default .file-preview-error { + opacity: 0.65; + box-shadow: none; +} + +.krajee-default .file-thumb-progress { + height: 11px; + top: 37px; + left: 0; + right: 0; +} + +.krajee-default.kvsortable-ghost { + background: #e1edf7; + border: 2px solid #a1abff; +} + +.krajee-default .file-preview-other:hover { + opacity: 0.8; +} + +.krajee-default .file-preview-frame:not(.file-preview-error) .file-footer-caption:hover { + color: #000; +} + +.kv-upload-progress .progress { + height: 20px; + margin: 10px 0; + overflow: hidden; +} + +.kv-upload-progress .progress-bar { + height: 20px; + font-family: Verdana, Helvetica, sans-serif; +} + +/*noinspection CssOverwrittenProperties*/ +.file-zoom-dialog .file-other-icon { + font-size: 22em; + font-size: 50vmin; +} + +.file-zoom-dialog .modal-dialog { + width: auto; +} + +.file-zoom-dialog .modal-header { + display: flex; + align-items: center; + justify-content: space-between; +} + +.file-zoom-dialog .btn-navigate { + padding: 0; + margin: 0; + background: transparent; + text-decoration: none; + outline: none; + opacity: 0.7; + top: 45%; + font-size: 4em; + color: #1c94c4; +} + +.file-zoom-dialog .btn-navigate:not([disabled]):hover { + outline: none; + box-shadow: none; + opacity: 0.6; +} + +.file-zoom-dialog .floating-buttons { + top: 5px; + right: 10px; +} + +.file-zoom-dialog .btn-navigate[disabled] { + opacity: 0.3; +} + +.file-zoom-dialog .btn-prev { + left: 1px; +} + +.file-zoom-dialog .btn-next { + right: 1px; +} + +.file-zoom-dialog .kv-zoom-title { + font-weight: 300; + color: #999; + max-width: 50%; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +.file-input-new .no-browse .form-control { + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; +} + +.file-input-ajax-new .no-browse .form-control { + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; +} + +.file-caption-main { + width: 100%; +} + +.file-thumb-loading { + background: transparent url(../img/loading.gif) no-repeat scroll center center content-box !important; +} + +.file-drop-zone { + border: 1px dashed #aaa; + border-radius: 4px; + height: 100%; + text-align: center; + vertical-align: middle; + margin: 12px 15px 12px 12px; + padding: 5px; +} + +.file-drop-zone.clickable:hover { + border: 2px dashed #999; +} + +.file-drop-zone.clickable:focus { + border: 2px solid #5acde2; +} + +.file-drop-zone .file-preview-thumbnails { + cursor: default; +} + +.file-drop-zone-title { + color: #aaa; + font-size: 1.6em; + padding: 85px 10px; + cursor: default; +} + +.file-highlighted { + border: 2px dashed #999 !important; + background-color: #eee; +} + +.file-uploading { + background: url(../img/loading-sm.gif) no-repeat center bottom 10px; + opacity: 0.65; +} + +.file-zoom-fullscreen .modal-dialog { + min-width: 100%; + margin: 0; +} + +.file-zoom-fullscreen .modal-content { + border-radius: 0; + box-shadow: none; + min-height: 100vh; +} + +.file-zoom-fullscreen .modal-body { + overflow-y: auto; +} + +.floating-buttons { + z-index: 3000; +} + +.floating-buttons .btn-kv { + margin-left: 3px; + z-index: 3000; +} + +.file-zoom-content { + height: 480px; + text-align: center; +} + +.file-zoom-content .file-preview-image { + max-height: 100%; +} + +.file-zoom-content .file-preview-video { + max-height: 100%; +} + +.file-zoom-content > .file-object.type-image { + height: auto; + min-height: inherit; +} + +.file-zoom-content > .file-object.type-audio { + width: auto; + height: 30px; +} + +@media (min-width: 576px) { + .file-zoom-dialog .modal-dialog { + max-width: 500px; + } +} + +@media (min-width: 992px) { + .file-zoom-dialog .modal-lg { + max-width: 800px; + } +} + +@media (max-width: 767px) { + .file-preview-thumbnails { + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + } + + .file-zoom-dialog .modal-header { + flex-direction: column; + } +} + +@media (max-width: 350px) { + .krajee-default.file-preview-frame .kv-file-content { + width: 160px; + } +} + +@media (max-width: 420px) { + .krajee-default.file-preview-frame .kv-file-content.kv-pdf-rendered { + width: 100%; + } +} + +.file-loading[dir=rtl]:before { + background: transparent url(../img/loading.gif) top right no-repeat; + padding-left: 0; + padding-right: 20px; +} + +.file-sortable .file-drag-handle { + cursor: move; + opacity: 1; +} + +.file-sortable .file-drag-handle:hover { + opacity: 0.7; +} + +.clickable .file-drop-zone-title { + cursor: pointer; +} + +.kv-zoom-actions .btn-kv { + margin-left: 3px; +} + +.file-preview-initial.sortable-chosen { + background-color: #d9edf7; +} diff --git a/static/plugins/bootstrap-fileinput/css/fileinput.min.css b/static/plugins/bootstrap-fileinput/css/fileinput.min.css new file mode 100644 index 00000000..95eb90a0 --- /dev/null +++ b/static/plugins/bootstrap-fileinput/css/fileinput.min.css @@ -0,0 +1,12 @@ +/*! + * bootstrap-fileinput v4.5.1 + * http://plugins.krajee.com/file-input + * + * Krajee default styling for bootstrap-fileinput. + * + * Author: Kartik Visweswaran + * Copyright: 2014 - 2018, Kartik Visweswaran, Krajee.com + * + * Licensed under the BSD 3-Clause + * https://github.com/kartik-v/bootstrap-fileinput/blob/master/LICENSE.md + */.btn-file input[type=file],.file-caption-icon,.file-no-browse,.file-preview .fileinput-remove,.file-zoom-dialog .btn-navigate,.file-zoom-dialog .floating-buttons,.krajee-default .file-thumb-progress{position:absolute}.file-loading input[type=file],input[type=file].file-loading{width:0;height:0}.file-no-browse{left:50%;bottom:20%;width:1px;height:1px;font-size:0;opacity:0;border:none;background:0 0;outline:0;box-shadow:none}.file-caption-icon,.file-input-ajax-new .fileinput-remove-button,.file-input-ajax-new .fileinput-upload-button,.file-input-ajax-new .no-browse .input-group-btn,.file-input-new .close,.file-input-new .file-preview,.file-input-new .fileinput-remove-button,.file-input-new .fileinput-upload-button,.file-input-new .glyphicon-file,.file-input-new .no-browse .input-group-btn,.file-zoom-dialog .modal-header:after,.file-zoom-dialog .modal-header:before,.hide-content .kv-file-content,.kv-hidden{display:none}.btn-file,.file-caption,.file-input,.file-loading:before,.file-preview,.file-zoom-dialog .modal-dialog,.krajee-default .file-thumbnail-footer,.krajee-default.file-preview-frame{position:relative}.file-error-message pre,.file-error-message ul,.krajee-default .file-actions,.krajee-default .file-other-error{text-align:left}.file-error-message pre,.file-error-message ul{margin:0}.krajee-default .file-drag-handle,.krajee-default .file-upload-indicator{float:left;margin:5px 0 -5px;width:16px;height:16px}.krajee-default .file-thumb-progress .progress,.krajee-default .file-thumb-progress .progress-bar{height:11px;font-family:Verdana,Helvetica,sans-serif;font-size:9px}.krajee-default .file-caption-info,.krajee-default .file-size-info{display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;width:160px;height:15px;margin:auto}.file-zoom-content>.file-object.type-flash,.file-zoom-content>.file-object.type-image,.file-zoom-content>.file-object.type-video{max-width:100%;max-height:100%;width:auto}.file-zoom-content>.file-object.type-flash,.file-zoom-content>.file-object.type-video{height:100%}.file-zoom-content>.file-object.type-default,.file-zoom-content>.file-object.type-html,.file-zoom-content>.file-object.type-pdf,.file-zoom-content>.file-object.type-text{width:100%}.file-loading:before{content:" Loading...";display:inline-block;padding-left:20px;line-height:16px;font-size:13px;font-variant:small-caps;color:#999;background:url(../img/loading.gif) top left no-repeat}.file-object{margin:0 0 -5px;padding:0}.btn-file{overflow:hidden}.btn-file input[type=file]{top:0;left:0;min-width:100%;min-height:100%;text-align:right;opacity:0;background:none;cursor:inherit;display:block}.btn-file ::-ms-browse{font-size:10000px;width:100%;height:100%}.file-caption .file-caption-name{width:100%;margin:0;padding:0;box-shadow:none;border:none;background:0 0;outline:0}.file-caption.icon-visible .file-caption-icon{display:inline-block}.file-caption.icon-visible .file-caption-name{padding-left:15px}.file-caption-icon{left:8px}.file-error-message{color:#a94442;background-color:#f2dede;margin:5px;border:1px solid #ebccd1;border-radius:4px;padding:15px}.file-error-message pre{margin:5px 0}.file-caption-disabled{background-color:#eee;cursor:not-allowed;opacity:1}.file-preview{border-radius:5px;border:1px solid #ddd;padding:8px;width:100%;margin-bottom:5px}.file-preview .btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.file-preview .fileinput-remove{top:1px;right:1px;line-height:10px}.file-preview .clickable{cursor:pointer}.file-preview-image{font:40px Impact,Charcoal,sans-serif;color:green}.krajee-default.file-preview-frame{margin:8px;border:1px solid rgba(0,0,0,.2);box-shadow:0 0 10px 0 rgba(0,0,0,.2);padding:6px;float:left;text-align:center}.krajee-default.file-preview-frame .kv-file-content{width:213px;height:160px}.krajee-default.file-preview-frame .kv-file-content.kv-pdf-rendered{width:400px}.krajee-default.file-preview-frame .file-thumbnail-footer{height:70px}.krajee-default.file-preview-frame:not(.file-preview-error):hover{border:1px solid rgba(0,0,0,.3);box-shadow:0 0 10px 0 rgba(0,0,0,.4)}.krajee-default .file-preview-text{display:block;color:#428bca;border:1px solid #ddd;font-family:Menlo,Monaco,Consolas,"Courier New",monospace;outline:0;padding:8px;resize:none}.krajee-default .file-preview-html{border:1px solid #ddd;padding:8px;overflow:auto}.krajee-default .file-other-icon{font-size:6em}.krajee-default .file-footer-buttons{float:right}.krajee-default .file-footer-caption{display:block;text-align:center;padding-top:4px;font-size:11px;color:#777;margin-bottom:15px}.krajee-default .file-preview-error{opacity:.65;box-shadow:none}.krajee-default .file-thumb-progress{height:11px;top:37px;left:0;right:0}.krajee-default.kvsortable-ghost{background:#e1edf7;border:2px solid #a1abff}.krajee-default .file-preview-other:hover{opacity:.8}.krajee-default .file-preview-frame:not(.file-preview-error) .file-footer-caption:hover{color:#000}.kv-upload-progress .progress{height:20px;margin:10px 0;overflow:hidden}.kv-upload-progress .progress-bar{height:20px;font-family:Verdana,Helvetica,sans-serif}.file-zoom-dialog .file-other-icon{font-size:22em;font-size:50vmin}.file-zoom-dialog .modal-dialog{width:auto}.file-zoom-dialog .modal-header{display:flex;align-items:center;justify-content:space-between}.file-zoom-dialog .btn-navigate{padding:0;margin:0;background:0 0;text-decoration:none;outline:0;opacity:.7;top:45%;font-size:4em;color:#1c94c4}.file-zoom-dialog .btn-navigate:not([disabled]):hover{outline:0;box-shadow:none;opacity:.6}.file-zoom-dialog .floating-buttons{top:5px;right:10px}.file-zoom-dialog .btn-navigate[disabled]{opacity:.3}.file-zoom-dialog .btn-prev{left:1px}.file-zoom-dialog .btn-next{right:1px}.file-zoom-dialog .kv-zoom-title{font-weight:300;color:#999;max-width:50%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.file-input-ajax-new .no-browse .form-control,.file-input-new .no-browse .form-control{border-top-right-radius:4px;border-bottom-right-radius:4px}.file-caption-main{width:100%}.file-thumb-loading{background:url(../img/loading.gif) center center no-repeat content-box!important}.file-drop-zone{border:1px dashed #aaa;border-radius:4px;height:100%;text-align:center;vertical-align:middle;margin:12px 15px 12px 12px;padding:5px}.file-drop-zone.clickable:hover{border:2px dashed #999}.file-drop-zone.clickable:focus{border:2px solid #5acde2}.file-drop-zone .file-preview-thumbnails{cursor:default}.file-drop-zone-title{color:#aaa;font-size:1.6em;padding:85px 10px;cursor:default}.file-highlighted{border:2px dashed #999!important;background-color:#eee}.file-uploading{background:url(../img/loading-sm.gif) center bottom 10px no-repeat;opacity:.65}.file-zoom-fullscreen .modal-dialog{min-width:100%;margin:0}.file-zoom-fullscreen .modal-content{border-radius:0;box-shadow:none;min-height:100vh}.file-zoom-fullscreen .modal-body{overflow-y:auto}.floating-buttons{z-index:3000}.floating-buttons .btn-kv{margin-left:3px;z-index:3000}.file-zoom-content{height:480px;text-align:center}.file-zoom-content .file-preview-image,.file-zoom-content .file-preview-video{max-height:100%}.file-zoom-content>.file-object.type-image{height:auto;min-height:inherit}.file-zoom-content>.file-object.type-audio{width:auto;height:30px}@media (min-width:576px){.file-zoom-dialog .modal-dialog{max-width:500px}}@media (min-width:992px){.file-zoom-dialog .modal-lg{max-width:800px}}@media (max-width:767px){.file-preview-thumbnails{display:flex;justify-content:center;align-items:center;flex-direction:column}.file-zoom-dialog .modal-header{flex-direction:column}}@media (max-width:350px){.krajee-default.file-preview-frame .kv-file-content{width:160px}}@media (max-width:420px){.krajee-default.file-preview-frame .kv-file-content.kv-pdf-rendered{width:100%}}.file-loading[dir=rtl]:before{background:url(../img/loading.gif) top right no-repeat;padding-left:0;padding-right:20px}.file-sortable .file-drag-handle{cursor:move;opacity:1}.file-sortable .file-drag-handle:hover{opacity:.7}.clickable .file-drop-zone-title{cursor:pointer}.kv-zoom-actions .btn-kv{margin-left:3px}.file-preview-initial.sortable-chosen{background-color:#d9edf7} diff --git a/static/plugins/bootstrap-fileinput/examples/index.html b/static/plugins/bootstrap-fileinput/examples/index.html new file mode 100644 index 00000000..351ea119 --- /dev/null +++ b/static/plugins/bootstrap-fileinput/examples/index.html @@ -0,0 +1,237 @@ + + + + + + + Krajee JQuery Plugins - © Kartik + + + + + + + + + + + + + + + +
    + +
    +
    + +
    +
    +
    + +
    +
    + + +
    +
    +
    + +
    + +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + + +
    +
    +
    +
    +
    + +
    +
    +
    +
    + + + + +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    + +
    +
    +
    + + +
    +

    Multi Language Inputs

    +
    + +
    + +
    +
    + +
    + +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/static/plugins/bootstrap-fileinput/img/loading-sm.gif b/static/plugins/bootstrap-fileinput/img/loading-sm.gif new file mode 100644 index 0000000000000000000000000000000000000000..44e3b7a0f702aa1d301468b1d6c1d74d45dfdfa4 GIT binary patch literal 2670 zcmb`Je@v5i9>LSr1(@@0hFth{;0&ijQR3npb})ArbBQ}PvxUrk9==i>-je&{`bYn0 zn)LJfykDR9({*{d^3^65#=>kEmYR~<(cUp}W8&ez9`;@Cv+TEYecW~T-rd+qu-ktq^cI?{N#Y-2bznz}@Wlou^?0COp;`YSDe?RQ|Tc54YcCq*3v~POu z=Q)qt<8AOhdGZ9Ce@UMNZ%`E#<|wqPl%!-T1wNGi6v$*^VoXMUfgA&)8H|&=avY{@ zJeN-^xjCUe&M)59EweXtoViM;a-1?pz|8ANLRhI=H=wp@r|+lJt@60`I1L>ikf9d z0~~r>SYAh`8ncKP#Y`em&991Ki+9$D<+Ah`h8ABLvp^+Tq&fmry95!A4*~{!r(W{r|twO<9E6hwX}% z)F~4zBCfrDD2L!G=moq=Ocm!kf(>!j#MWki`kE{G-BYoHbydNkr`YYfzkHJ}sdz&Z z8KyFYGObizP&mFNfL9$Pkl^BMW@H7Mj~&{~Y}&^@Em8az7i38?{{@hom-`@R{}EIl zuKr(+p!%I^SP+EozXRAQx&zv)Q;+jLkH^n1bF~-lxqi~zs6M>$2b`E?bDR1(oHROR zaB7GXUJ{zi)G5043^#qed;40J-ClCQog6(Hud5xphOP%&4#R>m7Bck%Y7iX-)s;GnT=AX8I6;x z*Ol?b-#BAm+c8m@;Zg6;uE=_2%jzUWr*x2ox8%paAn8qLY^bd(b;)ctlkU^fu=Vc3 zz;SN~k7AeP8RH2m;z z2CX1gkPsDhP~gugYVl&ptMm6Li~=Mu93%mmkyQ%qyHLgm8|ug3#{dSLkO}}pC4+2m z`Abd|+`DA@$4K%kR+rDRtj=pVseWU_SosF`XJ_(Mv(56PPTS5aAL87zteO7ON2zXW zXJVy1^(Mx0!G&0`_T@yc7N3_Iz0+VN*C^fuY4rdK8WyJhb56^W6%| zhv|@e@GJq}V9C%h00$|+q%bv<3=9s$WESlms9VW!FOl?MEbIgN3J@Vy2p1xL+O3%* z4%2yvY0~i3-n5pgHjU@SvC&L^4Rsa4mb2FHx-acsLminpiEGcb4^avWjB6bs?yxHv zFP>OoZm&@)|E$XG{cHFN;rjRP&k2G>fr0d>;EjE|0c(GfTT-1_oMa2pu0v=?C ziWR6(s{%i;LARh*1pqiF)TclTcn}}b?&(F5kHB?rd-LMljI3nO?V@)3zQ#dO-513t ztjcsEGqLW@n}vqVK-aY$rTULTyjJV(hyiVw^9O}%O%TO%-BuBoT7PVy_KKNUTkL36 z?~x1XsVjwnVnJz^Up>+9s|T|{G?2yf>OloQZy$sL`TErZnkDwpwn;{pv6^-mtgcw9 z_E@9A+pHJ039kOw(VR1!{Fzhc9qk-wO9+x3Qyrg@1UN60rpBE1jKy*kvzjY8GMB%rN zWSO@Pl7v*3SO+Q5wzobgsdpIf?Qr=duPeH-rgHWhu znosfxTsJ3QpXcCF1>~qGXs@ZJ zyw~IM73y!A^G>VZ8R)gNG&BU|KuYo)y+(NV^DdAFW5LeVJBBCMNfRtuR+y^ER&<(X zE7%B&e5#Z^`o7-@nEg_P~LI*N!rA^h{|;%@7gdIe0+9 zL6FPmK*NXRK=)=Qr!7yn1(?oIuvujqni9B8`-RfJ#pzQFR*0=zw6NbXTj0F1BLlZh zfVly?4JS*30beAWg%&SoyRsrS%M*Y;RQ^0c<0TpwHaFY zz6r{%O%_#7fe{VKyK@(naSJIeRCsx4aYAE48*ixFGRdMll6*@GUTc0%``>`o8j#V4 zg9*c-O~)LSJSTE&n6)s0!!ECt)zd(wL!wvC+k6wRhYtI}PTi7bt)^yIDG`AshjlL| qtmsHM!1PnVu#-b#!*qv!K4\n' + + '\n' + + '\n' + + '\n' + + '\n' + + '\n', + DEFAULT_PREVIEW: '
    \n' + + '{previewFileIcon}\n' + + '
    ', + MODAL_ID: 'kvFileinputModal', + MODAL_EVENTS: ['show', 'shown', 'hide', 'hidden', 'loaded'], + objUrl: window.URL || window.webkitURL, + compare: function (input, str, exact) { + return input !== undefined && (exact ? input === str : input.match(str)); + }, + isIE: function (ver) { + // check for IE versions < 11 + if (navigator.appName !== 'Microsoft Internet Explorer') { + return false; + } + if (ver === 10) { + return new RegExp('msie\\s' + ver, 'i').test(navigator.userAgent); + } + var div = document.createElement("div"), status; + div.innerHTML = ""; + status = div.getElementsByTagName("i").length; + document.body.appendChild(div); + div.parentNode.removeChild(div); + return status; + }, + canAssignFilesToInput: function () { + var input = document.createElement('input'); + try { + input.type = "file"; + input.files = null; + return true; + } catch (err) { + return false; + } + }, + getDragDropFolders: function (items) { + var i, item, len = items.length, folders = 0; + if (len > 0 && items[0].webkitGetAsEntry()) { + for (i = 0; i < len; i++) { + item = items[i].webkitGetAsEntry(); + if (item && item.isDirectory) { + folders++; + } + } + } + return folders; + }, + initModal: function ($modal) { + var $body = $('body'); + if ($body.length) { + $modal.appendTo($body); + } + }, + isEmpty: function (value, trim) { + return value === undefined || value === null || value.length === 0 || (trim && $.trim(value) === ''); + }, + isArray: function (a) { + return Array.isArray(a) || Object.prototype.toString.call(a) === '[object Array]'; + }, + ifSet: function (needle, haystack, def) { + def = def || ''; + return (haystack && typeof haystack === 'object' && needle in haystack) ? haystack[needle] : def; + }, + cleanArray: function (arr) { + if (!(arr instanceof Array)) { + arr = []; + } + return arr.filter(function (e) { + return (e !== undefined && e !== null); + }); + }, + spliceArray: function (arr, index, reverseOrder) { + var i, j = 0, out = [], newArr; + if (!(arr instanceof Array)) { + return []; + } + newArr = $.extend(true, [], arr); + if (reverseOrder) { + newArr.reverse(); + } + for (i = 0; i < newArr.length; i++) { + if (i !== index) { + out[j] = newArr[i]; + j++; + } + } + if (reverseOrder) { + out.reverse(); + } + return out; + }, + getNum: function (num, def) { + def = def || 0; + if (typeof num === "number") { + return num; + } + if (typeof num === "string") { + num = parseFloat(num); + } + return isNaN(num) ? def : num; + }, + hasFileAPISupport: function () { + return !!(window.File && window.FileReader); + }, + hasDragDropSupport: function () { + var div = document.createElement('div'); + /** @namespace div.draggable */ + /** @namespace div.ondragstart */ + /** @namespace div.ondrop */ + return !$h.isIE(9) && + (div.draggable !== undefined || (div.ondragstart !== undefined && div.ondrop !== undefined)); + }, + hasFileUploadSupport: function () { + return $h.hasFileAPISupport() && window.FormData; + }, + hasBlobSupport: function () { + try { + return !!window.Blob && Boolean(new Blob()); + } catch (e) { + return false; + } + }, + hasArrayBufferViewSupport: function () { + try { + return new Blob([new Uint8Array(100)]).size === 100; + } catch (e) { + return false; + } + }, + dataURI2Blob: function (dataURI) { + //noinspection JSUnresolvedVariable + var BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || + window.MSBlobBuilder, canBlob = $h.hasBlobSupport(), byteStr, arrayBuffer, intArray, i, mimeStr, bb, + canProceed = (canBlob || BlobBuilder) && window.atob && window.ArrayBuffer && window.Uint8Array; + if (!canProceed) { + return null; + } + if (dataURI.split(',')[0].indexOf('base64') >= 0) { + byteStr = atob(dataURI.split(',')[1]); + } else { + byteStr = decodeURIComponent(dataURI.split(',')[1]); + } + arrayBuffer = new ArrayBuffer(byteStr.length); + intArray = new Uint8Array(arrayBuffer); + for (i = 0; i < byteStr.length; i += 1) { + intArray[i] = byteStr.charCodeAt(i); + } + mimeStr = dataURI.split(',')[0].split(':')[1].split(';')[0]; + if (canBlob) { + return new Blob([$h.hasArrayBufferViewSupport() ? intArray : arrayBuffer], {type: mimeStr}); + } + bb = new BlobBuilder(); + bb.append(arrayBuffer); + return bb.getBlob(mimeStr); + }, + arrayBuffer2String: function (buffer) { + //noinspection JSUnresolvedVariable + if (window.TextDecoder) { + // noinspection JSUnresolvedFunction + return new TextDecoder("utf-8").decode(buffer); + } + var array = Array.prototype.slice.apply(new Uint8Array(buffer)), out = '', i = 0, len, c, char2, char3; + len = array.length; + while (i < len) { + c = array[i++]; + switch (c >> 4) { // jshint ignore:line + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + // 0xxxxxxx + out += String.fromCharCode(c); + break; + case 12: + case 13: + // 110x xxxx 10xx xxxx + char2 = array[i++]; + out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F)); // jshint ignore:line + break; + case 14: + // 1110 xxxx 10xx xxxx 10xx xxxx + char2 = array[i++]; + char3 = array[i++]; + out += String.fromCharCode(((c & 0x0F) << 12) | // jshint ignore:line + ((char2 & 0x3F) << 6) | // jshint ignore:line + ((char3 & 0x3F) << 0)); // jshint ignore:line + break; + } + } + return out; + }, + isHtml: function (str) { + var a = document.createElement('div'); + a.innerHTML = str; + for (var c = a.childNodes, i = c.length; i--;) { + if (c[i].nodeType === 1) { + return true; + } + } + return false; + }, + isSvg: function (str) { + return str.match(/^\s*<\?xml/i) && (str.match(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + }, + replaceTags: function (str, tags) { + var out = str; + if (!tags) { + return out; + } + $.each(tags, function (key, value) { + if (typeof value === "function") { + value = value(); + } + out = out.split(key).join(value); + }); + return out; + }, + cleanMemory: function ($thumb) { + var data = $thumb.is('img') ? $thumb.attr('src') : $thumb.find('source').attr('src'); + /** @namespace $h.objUrl.revokeObjectURL */ + $h.objUrl.revokeObjectURL(data); + }, + findFileName: function (filePath) { + var sepIndex = filePath.lastIndexOf('/'); + if (sepIndex === -1) { + sepIndex = filePath.lastIndexOf('\\'); + } + return filePath.split(filePath.substring(sepIndex, sepIndex + 1)).pop(); + }, + checkFullScreen: function () { + //noinspection JSUnresolvedVariable + return document.fullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement || + document.msFullscreenElement; + }, + toggleFullScreen: function (maximize) { + var doc = document, de = doc.documentElement; + if (de && maximize && !$h.checkFullScreen()) { + /** @namespace document.requestFullscreen */ + /** @namespace document.msRequestFullscreen */ + /** @namespace document.mozRequestFullScreen */ + /** @namespace document.webkitRequestFullscreen */ + /** @namespace Element.ALLOW_KEYBOARD_INPUT */ + if (de.requestFullscreen) { + de.requestFullscreen(); + } else if (de.msRequestFullscreen) { + de.msRequestFullscreen(); + } else if (de.mozRequestFullScreen) { + de.mozRequestFullScreen(); + } else if (de.webkitRequestFullscreen) { + de.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT); + } + } else { + /** @namespace document.exitFullscreen */ + /** @namespace document.msExitFullscreen */ + /** @namespace document.mozCancelFullScreen */ + /** @namespace document.webkitExitFullscreen */ + if (doc.exitFullscreen) { + doc.exitFullscreen(); + } else if (doc.msExitFullscreen) { + doc.msExitFullscreen(); + } else if (doc.mozCancelFullScreen) { + doc.mozCancelFullScreen(); + } else if (doc.webkitExitFullscreen) { + doc.webkitExitFullscreen(); + } + } + }, + moveArray: function (arr, oldIndex, newIndex, reverseOrder) { + var newArr = $.extend(true, [], arr); + if (reverseOrder) { + newArr.reverse(); + } + if (newIndex >= newArr.length) { + var k = newIndex - newArr.length; + while ((k--) + 1) { + newArr.push(undefined); + } + } + newArr.splice(newIndex, 0, newArr.splice(oldIndex, 1)[0]); + if (reverseOrder) { + newArr.reverse(); + } + return newArr; + }, + cleanZoomCache: function ($el) { + var $cache = $el.closest('.kv-zoom-cache-theme'); + if (!$cache.length) { + $cache = $el.closest('.kv-zoom-cache'); + } + $cache.remove(); + }, + closeButton: function (css) { + css = css ? 'close ' + css : 'close'; + return ''; + }, + getRotation: function (value) { + switch (value) { + case 2: + return 'rotateY(180deg)'; + case 3: + return 'rotate(180deg)'; + case 4: + return 'rotate(180deg) rotateY(180deg)'; + case 5: + return 'rotate(270deg) rotateY(180deg)'; + case 6: + return 'rotate(90deg)'; + case 7: + return 'rotate(90deg) rotateY(180deg)'; + case 8: + return 'rotate(270deg)'; + default: + return ''; + } + }, + setTransform: function (el, val) { + if (!el) { + return; + } + el.style.transform = val; + el.style.webkitTransform = val; + el.style['-moz-transform'] = val; + el.style['-ms-transform'] = val; + el.style['-o-transform'] = val; + }, + setImageOrientation: function ($img, $zoomImg, value) { + if (!$img || !$img.length) { + return; + } + var ev = 'load.fileinputimageorient'; + $img.off(ev).on(ev, function () { + var img = $img.get(0), zoomImg = $zoomImg && $zoomImg.length ? $zoomImg.get(0) : null, + h = img.offsetHeight, w = img.offsetWidth, r = $h.getRotation(value); + $img.data('orientation', value); + if (zoomImg) { + $zoomImg.data('orientation', value); + } + if (value < 5) { + $h.setTransform(img, r); + $h.setTransform(zoomImg, r); + return; + } + var offsetAngle = Math.atan(w / h), origFactor = Math.sqrt(Math.pow(h, 2) + Math.pow(w, 2)), + scale = !origFactor ? 1 : (h / Math.cos(Math.PI / 2 + offsetAngle)) / origFactor, + s = ' scale(' + Math.abs(scale) + ')'; + $h.setTransform(img, r + s); + $h.setTransform(zoomImg, r + s); + }); + } + }; + FileInput = function (element, options) { + var self = this; + self.$element = $(element); + self.$parent = self.$element.parent(); + if (!self._validate()) { + return; + } + self.isPreviewable = $h.hasFileAPISupport(); + self.isIE9 = $h.isIE(9); + self.isIE10 = $h.isIE(10); + if (self.isPreviewable || self.isIE9) { + self._init(options); + self._listen(); + } + self.$element.removeClass('file-loading'); + }; + //noinspection JSUnusedGlobalSymbols + FileInput.prototype = { + constructor: FileInput, + _cleanup: function () { + var self = this; + self.reader = null; + self.formdata = {}; + self.uploadCount = 0; + self.uploadStatus = {}; + self.uploadLog = []; + self.uploadAsyncCount = 0; + self.loadedImages = []; + self.totalImagesCount = 0; + self.ajaxRequests = []; + self.clearStack(); + self.fileBatchCompleted = true; + if (!self.isPreviewable) { + self.showPreview = false; + } + self.isError = false; + self.ajaxAborted = false; + self.cancelling = false; + }, + _init: function (options, refreshMode) { + var self = this, f, $el = self.$element, $cont, t, tmp; + self.options = options; + $.each(options, function (key, value) { + switch (key) { + case 'minFileCount': + case 'maxFileCount': + case 'minFileSize': + case 'maxFileSize': + case 'maxFilePreviewSize': + case 'resizeImageQuality': + case 'resizeIfSizeMoreThan': + case 'progressUploadThreshold': + case 'initialPreviewCount': + case 'zoomModalHeight': + case 'minImageHeight': + case 'maxImageHeight': + case 'minImageWidth': + case 'maxImageWidth': + self[key] = $h.getNum(value); + break; + default: + self[key] = value; + break; + } + }); + if (self.rtl) { // swap buttons for rtl + tmp = self.previewZoomButtonIcons.prev; + self.previewZoomButtonIcons.prev = self.previewZoomButtonIcons.next; + self.previewZoomButtonIcons.next = tmp; + } + if (!refreshMode) { + self._cleanup(); + } + self.$form = $el.closest('form'); + self._initTemplateDefaults(); + self.uploadFileAttr = !$h.isEmpty($el.attr('name')) ? $el.attr('name') : 'file_data'; + t = self._getLayoutTemplate('progress'); + self.progressTemplate = t.replace('{class}', self.progressClass); + self.progressCompleteTemplate = t.replace('{class}', self.progressCompleteClass); + self.progressErrorTemplate = t.replace('{class}', self.progressErrorClass); + self.isDisabled = $el.attr('disabled') || $el.attr('readonly'); + if (self.isDisabled) { + $el.attr('disabled', true); + } + self.isClickable = self.browseOnZoneClick && self.showPreview && + (self.dropZoneEnabled || !$h.isEmpty(self.defaultPreviewContent)); + self.isAjaxUpload = $h.hasFileUploadSupport() && !$h.isEmpty(self.uploadUrl); + self.dropZoneEnabled = $h.hasDragDropSupport() && self.dropZoneEnabled; + if (!self.isAjaxUpload) { + self.dropZoneEnabled = self.dropZoneEnabled && $h.canAssignFilesToInput(); + } + self.slug = typeof options.slugCallback === "function" ? options.slugCallback : self._slugDefault; + self.mainTemplate = self.showCaption ? self._getLayoutTemplate('main1') : self._getLayoutTemplate('main2'); + self.captionTemplate = self._getLayoutTemplate('caption'); + self.previewGenericTemplate = self._getPreviewTemplate('generic'); + if (!self.imageCanvas && self.resizeImage && (self.maxImageWidth || self.maxImageHeight)) { + self.imageCanvas = document.createElement('canvas'); + self.imageCanvasContext = self.imageCanvas.getContext('2d'); + } + if ($h.isEmpty($el.attr('id'))) { + $el.attr('id', $h.uniqId()); + } + self.namespace = '.fileinput_' + $el.attr('id').replace(/-/g, '_'); + if (self.$container === undefined) { + self.$container = self._createContainer(); + } else { + self._refreshContainer(); + } + $cont = self.$container; + self.$dropZone = $cont.find('.file-drop-zone'); + self.$progress = $cont.find('.kv-upload-progress'); + self.$btnUpload = $cont.find('.fileinput-upload'); + self.$captionContainer = $h.getElement(options, 'elCaptionContainer', $cont.find('.file-caption')); + self.$caption = $h.getElement(options, 'elCaptionText', $cont.find('.file-caption-name')); + if (!$h.isEmpty(self.msgPlaceholder)) { + f = $el.attr('multiple') ? self.filePlural : self.fileSingle; + self.$caption.attr('placeholder', self.msgPlaceholder.replace('{files}', f)); + } + self.$captionIcon = self.$captionContainer.find('.file-caption-icon'); + self.$previewContainer = $h.getElement(options, 'elPreviewContainer', $cont.find('.file-preview')); + self.$preview = $h.getElement(options, 'elPreviewImage', $cont.find('.file-preview-thumbnails')); + self.$previewStatus = $h.getElement(options, 'elPreviewStatus', $cont.find('.file-preview-status')); + self.$errorContainer = $h.getElement(options, 'elErrorContainer', self.$previewContainer.find('.kv-fileinput-error')); + self._validateDisabled(); + if (!$h.isEmpty(self.msgErrorClass)) { + $h.addCss(self.$errorContainer, self.msgErrorClass); + } + if (!refreshMode) { + self.$errorContainer.hide(); + self.previewInitId = "preview-" + $h.uniqId(); + self._initPreviewCache(); + self._initPreview(true); + self._initPreviewActions(); + if (self.$parent.hasClass('file-loading')) { + self.$container.insertBefore(self.$parent); + self.$parent.remove(); + } + } else { + if (!self._errorsExist()) { + self.$errorContainer.hide(); + } + } + self._setFileDropZoneTitle(); + if ($el.attr('disabled')) { + self.disable(); + } + self._initZoom(); + if (self.hideThumbnailContent) { + $h.addCss(self.$preview, 'hide-content'); + } + }, + _initTemplateDefaults: function () { + var self = this, tMain1, tMain2, tPreview, tFileIcon, tClose, tCaption, tBtnDefault, tBtnLink, tBtnBrowse, + tModalMain, tModal, tProgress, tSize, tFooter, tActions, tActionDelete, tActionUpload, tActionDownload, + tActionZoom, tActionDrag, tIndicator, tTagBef, tTagBef1, tTagBef2, tTagAft, tGeneric, tHtml, tImage, + tText, tOffice, tGdocs, tVideo, tAudio, tFlash, tObject, tPdf, tOther, tStyle, tZoomCache, vDefaultDim; + tMain1 = '{preview}\n' + + '
    \n' + + '
    \n' + + ' {caption}\n' + + '
    \n' + + ' {remove}\n' + + ' {cancel}\n' + + ' {upload}\n' + + ' {browse}\n' + + '
    \n' + + '
    '; + tMain2 = '{preview}\n
    \n
    \n{remove}\n{cancel}\n{upload}\n{browse}\n'; + tPreview = '
    \n' + + ' {close}' + + '
    \n' + + '
    \n' + + '
    \n' + + '
    ' + + '
    \n' + + '
    \n' + + '
    \n' + + '
    '; + tClose = $h.closeButton('fileinput-remove'); + tFileIcon = ''; + // noinspection HtmlUnknownAttribute + tCaption = '
    \n' + + ' \n' + + ' \n' + + '
    '; + //noinspection HtmlUnknownAttribute + tBtnDefault = ''; + //noinspection HtmlUnknownAttribute + tBtnLink = '{icon} {label}'; + //noinspection HtmlUnknownAttribute + tBtnBrowse = '
    {icon} {label}
    '; + tModalMain = ''; + tModal = '\n'; + tProgress = '
    \n' + + '
    \n' + + ' {status}\n' + + '
    \n' + + '
    '; + tSize = ' ({sizeText})'; + tFooter = ''; + tActions = '
    \n' + + ' \n' + + '
    \n' + + '{drag}\n' + + '
    '; + //noinspection HtmlUnknownAttribute + tActionDelete = '\n'; + tActionUpload = ''; + tActionDownload = '{downloadIcon}'; + tActionZoom = ''; + tActionDrag = '{dragIcon}'; + tIndicator = '
    {indicator}
    '; + tTagBef = '
    \n'; + tTagBef2 = tTagBef + ' title="{caption}">
    \n'; + tTagAft = '
    {footer}\n
    \n'; + tGeneric = '{content}\n'; + tStyle = ' {style}'; + tHtml = '
    {data}
    \n'; + tImage = '\n'; + tText = '\n'; + tOffice = ''; + tGdocs = ''; + tVideo = '\n'; + tAudio = '\n'; + tFlash = '\n'; + tPdf = '\n'; + tObject = '\n' + '\n' + + $h.OBJECT_PARAMS + ' ' + $h.DEFAULT_PREVIEW + '\n\n'; + tOther = '
    \n' + $h.DEFAULT_PREVIEW + '\n
    \n'; + tZoomCache = ''; + vDefaultDim = {width: "100%", height: "100%", 'min-height': "480px"}; + if (self._isPdfRendered()) { + tPdf = self.pdfRendererTemplate.replace('{renderer}', self.pdfRendererUrl); + } + self.defaults = { + layoutTemplates: { + main1: tMain1, + main2: tMain2, + preview: tPreview, + close: tClose, + fileIcon: tFileIcon, + caption: tCaption, + modalMain: tModalMain, + modal: tModal, + progress: tProgress, + size: tSize, + footer: tFooter, + indicator: tIndicator, + actions: tActions, + actionDelete: tActionDelete, + actionUpload: tActionUpload, + actionDownload: tActionDownload, + actionZoom: tActionZoom, + actionDrag: tActionDrag, + btnDefault: tBtnDefault, + btnLink: tBtnLink, + btnBrowse: tBtnBrowse, + zoomCache: tZoomCache + }, + previewMarkupTags: { + tagBefore1: tTagBef1, + tagBefore2: tTagBef2, + tagAfter: tTagAft + }, + previewContentTemplates: { + generic: tGeneric, + html: tHtml, + image: tImage, + text: tText, + office: tOffice, + gdocs: tGdocs, + video: tVideo, + audio: tAudio, + flash: tFlash, + object: tObject, + pdf: tPdf, + other: tOther + }, + allowedPreviewTypes: ['image', 'html', 'text', 'video', 'audio', 'flash', 'pdf', 'object'], + previewTemplates: {}, + previewSettings: { + image: {width: "auto", height: "auto", 'max-width': "100%", 'max-height': "100%"}, + html: {width: "213px", height: "160px"}, + text: {width: "213px", height: "160px"}, + office: {width: "213px", height: "160px"}, + gdocs: {width: "213px", height: "160px"}, + video: {width: "213px", height: "160px"}, + audio: {width: "100%", height: "30px"}, + flash: {width: "213px", height: "160px"}, + object: {width: "213px", height: "160px"}, + pdf: {width: "100%", height: "160px"}, + other: {width: "213px", height: "160px"} + }, + previewSettingsSmall: { + image: {width: "auto", height: "auto", 'max-width': "100%", 'max-height': "100%"}, + html: {width: "100%", height: "160px"}, + text: {width: "100%", height: "160px"}, + office: {width: "100%", height: "160px"}, + gdocs: {width: "100%", height: "160px"}, + video: {width: "100%", height: "auto"}, + audio: {width: "100%", height: "30px"}, + flash: {width: "100%", height: "auto"}, + object: {width: "100%", height: "auto"}, + pdf: {width: "100%", height: "160px"}, + other: {width: "100%", height: "160px"} + }, + previewZoomSettings: { + image: {width: "auto", height: "auto", 'max-width': "100%", 'max-height': "100%"}, + html: vDefaultDim, + text: vDefaultDim, + office: {width: "100%", height: "100%", 'max-width': "100%", 'min-height': "480px"}, + gdocs: {width: "100%", height: "100%", 'max-width': "100%", 'min-height': "480px"}, + video: {width: "auto", height: "100%", 'max-width': "100%"}, + audio: {width: "100%", height: "30px"}, + flash: {width: "auto", height: "480px"}, + object: {width: "auto", height: "100%", 'max-width': "100%", 'min-height': "480px"}, + pdf: vDefaultDim, + other: {width: "auto", height: "100%", 'min-height': "480px"} + }, + fileTypeSettings: { + image: function (vType, vName) { + return ($h.compare(vType, 'image.*') && !$h.compare(vType, /(tiff?|wmf)$/i) || + $h.compare(vName, /\.(gif|png|jpe?g)$/i)); + }, + html: function (vType, vName) { + return $h.compare(vType, 'text/html') || $h.compare(vName, /\.(htm|html)$/i); + }, + office: function (vType, vName) { + return $h.compare(vType, /(word|excel|powerpoint|office)$/i) || + $h.compare(vName, /\.(docx?|xlsx?|pptx?|pps|potx?)$/i); + }, + gdocs: function (vType, vName) { + return $h.compare(vType, /(word|excel|powerpoint|office|iwork-pages|tiff?)$/i) || + $h.compare(vName, /\.(docx?|xlsx?|pptx?|pps|potx?|rtf|ods|odt|pages|ai|dxf|ttf|tiff?|wmf|e?ps)$/i); + }, + text: function (vType, vName) { + return $h.compare(vType, 'text.*') || $h.compare(vName, /\.(xml|javascript)$/i) || + $h.compare(vName, /\.(txt|md|csv|nfo|ini|json|php|js|css)$/i); + }, + video: function (vType, vName) { + return $h.compare(vType, 'video.*') && ($h.compare(vType, /(ogg|mp4|mp?g|mov|webm|3gp)$/i) || + $h.compare(vName, /\.(og?|mp4|webm|mp?g|mov|3gp)$/i)); + }, + audio: function (vType, vName) { + return $h.compare(vType, 'audio.*') && ($h.compare(vName, /(ogg|mp3|mp?g|wav)$/i) || + $h.compare(vName, /\.(og?|mp3|mp?g|wav)$/i)); + }, + flash: function (vType, vName) { + return $h.compare(vType, 'application/x-shockwave-flash', true) || $h.compare(vName, /\.(swf)$/i); + }, + pdf: function (vType, vName) { + return $h.compare(vType, 'application/pdf', true) || $h.compare(vName, /\.(pdf)$/i); + }, + object: function () { + return true; + }, + other: function () { + return true; + } + }, + fileActionSettings: { + showRemove: true, + showUpload: true, + showDownload: true, + showZoom: true, + showDrag: true, + removeIcon: '', + removeClass: 'btn btn-sm btn-kv btn-default btn-outline-secondary', + removeErrorClass: 'btn btn-sm btn-kv btn-danger', + removeTitle: 'Remove file', + uploadIcon: '', + uploadClass: 'btn btn-sm btn-kv btn-default btn-outline-secondary', + uploadTitle: 'Upload file', + uploadRetryIcon: '', + uploadRetryTitle: 'Retry upload', + downloadIcon: '', + downloadClass: 'btn btn-sm btn-kv btn-default btn-outline-secondary', + downloadTitle: 'Download file', + zoomIcon: '', + zoomClass: 'btn btn-sm btn-kv btn-default btn-outline-secondary', + zoomTitle: 'View Details', + dragIcon: '', + dragClass: 'text-info', + dragTitle: 'Move / Rearrange', + dragSettings: {}, + indicatorNew: '', + indicatorSuccess: '', + indicatorError: '', + indicatorLoading: '', + indicatorNewTitle: 'Not uploaded yet', + indicatorSuccessTitle: 'Uploaded', + indicatorErrorTitle: 'Upload Error', + indicatorLoadingTitle: 'Uploading ...' + } + }; + $.each(self.defaults, function (key, setting) { + if (key === 'allowedPreviewTypes') { + if (self.allowedPreviewTypes === undefined) { + self.allowedPreviewTypes = setting; + } + return; + } + self[key] = $.extend(true, {}, setting, self[key]); + }); + self._initPreviewTemplates(); + }, + _initPreviewTemplates: function () { + var self = this, tags = self.previewMarkupTags, tagBef, tagAft = tags.tagAfter; + $.each(self.previewContentTemplates, function (key, value) { + if ($h.isEmpty(self.previewTemplates[key])) { + tagBef = tags.tagBefore2; + if (key === 'generic' || key === 'image' || key === 'html' || key === 'text') { + tagBef = tags.tagBefore1; + } + if (self._isPdfRendered() && key === 'pdf') { + tagBef = tagBef.replace('kv-file-content', 'kv-file-content kv-pdf-rendered'); + } + self.previewTemplates[key] = tagBef + value + tagAft; + } + }); + }, + _initPreviewCache: function () { + var self = this; + self.previewCache = { + data: {}, + init: function () { + var content = self.initialPreview; + if (content.length > 0 && !$h.isArray(content)) { + content = content.split(self.initialPreviewDelimiter); + } + self.previewCache.data = { + content: content, + config: self.initialPreviewConfig, + tags: self.initialPreviewThumbTags + }; + }, + count: function () { + return !!self.previewCache.data && !!self.previewCache.data.content ? + self.previewCache.data.content.length : 0; + }, + get: function (i, isDisabled) { + var ind = 'init_' + i, data = self.previewCache.data, config = data.config[i], + content = data.content[i], previewId = self.previewInitId + '-' + ind, out, $tmp, cat, ftr, + fname, ftype, frameClass, asData = $h.ifSet('previewAsData', config, self.initialPreviewAsData), + parseTemplate = function (cat, dat, fn, ft, id, ftr, ind, fc, t) { + fc = ' file-preview-initial ' + $h.SORT_CSS + (fc ? ' ' + fc : ''); + return self._generatePreviewTemplate(cat, dat, fn, ft, id, false, null, fc, ftr, ind, t); + }; + if (!content) { + return ''; + } + isDisabled = isDisabled === undefined ? true : isDisabled; + cat = $h.ifSet('type', config, self.initialPreviewFileType || 'generic'); + fname = $h.ifSet('filename', config, $h.ifSet('caption', config)); + ftype = $h.ifSet('filetype', config, cat); + ftr = self.previewCache.footer(i, isDisabled, (config && config.size || null)); + frameClass = $h.ifSet('frameClass', config); + if (asData) { + out = parseTemplate(cat, content, fname, ftype, previewId, ftr, ind, frameClass); + } else { + out = parseTemplate('generic', content, fname, ftype, previewId, ftr, ind, frameClass, cat) + .setTokens({'content': data.content[i]}); + } + if (data.tags.length && data.tags[i]) { + out = $h.replaceTags(out, data.tags[i]); + } + /** @namespace config.frameAttr */ + if (!$h.isEmpty(config) && !$h.isEmpty(config.frameAttr)) { + $tmp = $(document.createElement('div')).html(out); + $tmp.find('.file-preview-initial').attr(config.frameAttr); + out = $tmp.html(); + $tmp.remove(); + } + return out; + }, + add: function (content, config, tags, append) { + var data = self.previewCache.data, index; + if (!$h.isArray(content)) { + content = content.split(self.initialPreviewDelimiter); + } + if (append) { + index = data.content.push(content) - 1; + data.config[index] = config; + data.tags[index] = tags; + } else { + index = content.length - 1; + data.content = content; + data.config = config; + data.tags = tags; + } + self.previewCache.data = data; + return index; + }, + set: function (content, config, tags, append) { + var data = self.previewCache.data, i, chk; + if (!content || !content.length) { + return; + } + if (!$h.isArray(content)) { + content = content.split(self.initialPreviewDelimiter); + } + chk = content.filter(function (n) { + return n !== null; + }); + if (!chk.length) { + return; + } + if (data.content === undefined) { + data.content = []; + } + if (data.config === undefined) { + data.config = []; + } + if (data.tags === undefined) { + data.tags = []; + } + if (append) { + for (i = 0; i < content.length; i++) { + if (content[i]) { + data.content.push(content[i]); + } + } + for (i = 0; i < config.length; i++) { + if (config[i]) { + data.config.push(config[i]); + } + } + for (i = 0; i < tags.length; i++) { + if (tags[i]) { + data.tags.push(tags[i]); + } + } + } else { + data.content = content; + data.config = config; + data.tags = tags; + } + self.previewCache.data = data; + }, + unset: function (index) { + var chk = self.previewCache.count(), rev = self.reversePreviewOrder; + if (!chk) { + return; + } + if (chk === 1) { + self.previewCache.data.content = []; + self.previewCache.data.config = []; + self.previewCache.data.tags = []; + self.initialPreview = []; + self.initialPreviewConfig = []; + self.initialPreviewThumbTags = []; + return; + } + self.previewCache.data.content = $h.spliceArray(self.previewCache.data.content, index, rev); + self.previewCache.data.config = $h.spliceArray(self.previewCache.data.config, index, rev); + self.previewCache.data.tags = $h.spliceArray(self.previewCache.data.tags, index, rev); + }, + out: function () { + var html = '', caption, len = self.previewCache.count(), i, content; + if (len === 0) { + return {content: '', caption: ''}; + } + for (i = 0; i < len; i++) { + content = self.previewCache.get(i); + html = self.reversePreviewOrder ? (content + html) : (html + content); + } + caption = self._getMsgSelected(len); + return {content: html, caption: caption}; + }, + footer: function (i, isDisabled, size) { + var data = self.previewCache.data || {}; + if ($h.isEmpty(data.content)) { + return ''; + } + if ($h.isEmpty(data.config) || $h.isEmpty(data.config[i])) { + data.config[i] = {}; + } + isDisabled = isDisabled === undefined ? true : isDisabled; + var config = data.config[i], caption = $h.ifSet('caption', config), a, + width = $h.ifSet('width', config, 'auto'), url = $h.ifSet('url', config, false), + key = $h.ifSet('key', config, null), fs = self.fileActionSettings, + initPreviewShowDel = self.initialPreviewShowDelete || false, + dUrl = config.downloadUrl || self.initialPreviewDownloadUrl || '', + dFil = config.filename || config.caption || '', + initPreviewShowDwl = !!(dUrl), + sDel = $h.ifSet('showRemove', config, $h.ifSet('showRemove', fs, initPreviewShowDel)), + sDwl = $h.ifSet('showDownload', config, $h.ifSet('showDownload', fs, initPreviewShowDwl)), + sZm = $h.ifSet('showZoom', config, $h.ifSet('showZoom', fs, true)), + sDrg = $h.ifSet('showDrag', config, $h.ifSet('showDrag', fs, true)), + dis = (url === false) && isDisabled; + sDwl = sDwl && config.downloadUrl !== false && !!dUrl; + a = self._renderFileActions(false, sDwl, sDel, sZm, sDrg, dis, url, key, true, dUrl, dFil); + return self._getLayoutTemplate('footer').setTokens({ + 'progress': self._renderThumbProgress(), + 'actions': a, + 'caption': caption, + 'size': self._getSize(size), + 'width': width, + 'indicator': '' + }); + } + }; + self.previewCache.init(); + }, + _isPdfRendered: function () { + var self = this, useLib = self.usePdfRenderer, + flag = typeof useLib === "function" ? useLib() : !!useLib; + return flag && self.pdfRendererUrl; + }, + _handler: function ($el, event, callback) { + var self = this, ns = self.namespace, ev = event.split(' ').join(ns + ' ') + ns; + if (!$el || !$el.length) { + return; + } + $el.off(ev).on(ev, callback); + }, + _log: function (msg) { + var self = this, id = self.$element.attr('id'); + if (id) { + msg = '"' + id + '": ' + msg; + } + msg = 'bootstrap-fileinput: ' + msg; + if (typeof window.console.log !== "undefined") { + window.console.log(msg); + } else { + window.alert(msg); + } + }, + _validate: function () { + var self = this, status = self.$element.attr('type') === 'file'; + if (!status) { + self._log('The input "type" must be set to "file" for initializing the "bootstrap-fileinput" plugin.'); + } + return status; + }, + _errorsExist: function () { + var self = this, $err, $errList = self.$errorContainer.find('li'); + if ($errList.length) { + return true; + } + $err = $(document.createElement('div')).html(self.$errorContainer.html()); + $err.find('.kv-error-close').remove(); + $err.find('ul').remove(); + return !!$.trim($err.text()).length; + }, + _errorHandler: function (evt, caption) { + var self = this, err = evt.target.error, showError = function (msg) { + self._showError(msg.replace('{name}', caption)); + }; + /** @namespace err.NOT_FOUND_ERR */ + /** @namespace err.SECURITY_ERR */ + /** @namespace err.NOT_READABLE_ERR */ + if (err.code === err.NOT_FOUND_ERR) { + showError(self.msgFileNotFound); + } else if (err.code === err.SECURITY_ERR) { + showError(self.msgFileSecured); + } else if (err.code === err.NOT_READABLE_ERR) { + showError(self.msgFileNotReadable); + } else if (err.code === err.ABORT_ERR) { + showError(self.msgFilePreviewAborted); + } else { + showError(self.msgFilePreviewError); + } + }, + _addError: function (msg) { + var self = this, $error = self.$errorContainer; + if (msg && $error.length) { + $error.html(self.errorCloseButton + msg); + self._handler($error.find('.kv-error-close'), 'click', function () { + setTimeout(function () { + if (self.showPreview && !self.getFrames().length) { + self.clear(); + } + $error.fadeOut('slow'); + }, 10); + }); + } + }, + _setValidationError: function (css) { + var self = this; + css = (css ? css + ' ' : '') + 'has-error'; + self.$container.removeClass(css).addClass('has-error'); + $h.addCss(self.$captionContainer, 'is-invalid'); + }, + _resetErrors: function (fade) { + var self = this, $error = self.$errorContainer; + self.isError = false; + self.$container.removeClass('has-error'); + self.$captionContainer.removeClass('is-invalid'); + $error.html(''); + if (fade) { + $error.fadeOut('slow'); + } else { + $error.hide(); + } + }, + _showFolderError: function (folders) { + var self = this, $error = self.$errorContainer, msg; + if (!folders) { + return; + } + if (!self.isAjaxUpload) { + self._clearFileInput(); + } + msg = self.msgFoldersNotAllowed.replace('{n}', folders); + self._addError(msg); + self._setValidationError(); + $error.fadeIn(800); + self._raise('filefoldererror', [folders, msg]); + }, + _showUploadError: function (msg, params, event) { + var self = this, $error = self.$errorContainer, ev = event || 'fileuploaderror', e = params && params.id ? + '
  • ' + msg + '
  • ' : '
  • ' + msg + '
  • '; + if ($error.find('ul').length === 0) { + self._addError('
      ' + e + '
    '); + } else { + $error.find('ul').append(e); + } + $error.fadeIn(800); + self._raise(ev, [params, msg]); + self._setValidationError('file-input-new'); + return true; + }, + _showError: function (msg, params, event) { + var self = this, $error = self.$errorContainer, ev = event || 'fileerror'; + params = params || {}; + params.reader = self.reader; + self._addError(msg); + $error.fadeIn(800); + self._raise(ev, [params, msg]); + if (!self.isAjaxUpload) { + self._clearFileInput(); + } + self._setValidationError('file-input-new'); + self.$btnUpload.attr('disabled', true); + return true; + }, + _noFilesError: function (params) { + var self = this, label = self.minFileCount > 1 ? self.filePlural : self.fileSingle, + msg = self.msgFilesTooLess.replace('{n}', self.minFileCount).replace('{files}', label), + $error = self.$errorContainer; + self._addError(msg); + self.isError = true; + self._updateFileDetails(0); + $error.fadeIn(800); + self._raise('fileerror', [params, msg]); + self._clearFileInput(); + self._setValidationError(); + }, + _parseError: function (operation, jqXHR, errorThrown, fileName) { + /** @namespace jqXHR.responseJSON */ + var self = this, errMsg = $.trim(errorThrown + ''), textPre, + text = jqXHR.responseJSON !== undefined && jqXHR.responseJSON.error !== undefined ? + jqXHR.responseJSON.error : jqXHR.responseText; + if (self.cancelling && self.msgUploadAborted) { + errMsg = self.msgUploadAborted; + } + if (self.showAjaxErrorDetails && text) { + text = $.trim(text.replace(/\n\s*\n/g, '\n')); + textPre = text.length ? '
    ' + text + '
    ' : ''; + errMsg += errMsg ? textPre : text; + } + if (!errMsg) { + errMsg = self.msgAjaxError.replace('{operation}', operation); + } + self.cancelling = false; + return fileName ? '' + fileName + ': ' + errMsg : errMsg; + }, + _parseFileType: function (type, name) { + var self = this, isValid, vType, cat, i, types = self.allowedPreviewTypes || []; + if (type === 'application/text-plain') { + return 'text'; + } + for (i = 0; i < types.length; i++) { + cat = types[i]; + isValid = self.fileTypeSettings[cat]; + vType = isValid(type, name) ? cat : ''; + if (!$h.isEmpty(vType)) { + return vType; + } + } + return 'other'; + }, + _getPreviewIcon: function (fname) { + var self = this, ext, out = null; + if (fname && fname.indexOf('.') > -1) { + ext = fname.split('.').pop(); + if (self.previewFileIconSettings) { + out = self.previewFileIconSettings[ext] || self.previewFileIconSettings[ext.toLowerCase()] || null; + } + if (self.previewFileExtSettings) { + $.each(self.previewFileExtSettings, function (key, func) { + if (self.previewFileIconSettings[key] && func(ext)) { + out = self.previewFileIconSettings[key]; + //noinspection UnnecessaryReturnStatementJS + return; + } + }); + } + } + return out; + }, + _parseFilePreviewIcon: function (content, fname) { + var self = this, icn = self._getPreviewIcon(fname) || self.previewFileIcon, out = content; + if (out.indexOf('{previewFileIcon}') > -1) { + out = out.setTokens({'previewFileIconClass': self.previewFileIconClass, 'previewFileIcon': icn}); + } + return out; + }, + _raise: function (event, params) { + var self = this, e = $.Event(event); + if (params !== undefined) { + self.$element.trigger(e, params); + } else { + self.$element.trigger(e); + } + if (e.isDefaultPrevented() || e.result === false) { + return false; + } + switch (event) { + // ignore these events + case 'filebatchuploadcomplete': + case 'filebatchuploadsuccess': + case 'fileuploaded': + case 'fileclear': + case 'filecleared': + case 'filereset': + case 'fileerror': + case 'filefoldererror': + case 'fileuploaderror': + case 'filebatchuploaderror': + case 'filedeleteerror': + case 'filecustomerror': + case 'filesuccessremove': + break; + // receive data response via `filecustomerror` event` + default: + if (!self.ajaxAborted) { + self.ajaxAborted = e.result; + } + break; + } + return true; + }, + _listenFullScreen: function (isFullScreen) { + var self = this, $modal = self.$modal, $btnFull, $btnBord; + if (!$modal || !$modal.length) { + return; + } + $btnFull = $modal && $modal.find('.btn-fullscreen'); + $btnBord = $modal && $modal.find('.btn-borderless'); + if (!$btnFull.length || !$btnBord.length) { + return; + } + $btnFull.removeClass('active').attr('aria-pressed', 'false'); + $btnBord.removeClass('active').attr('aria-pressed', 'false'); + if (isFullScreen) { + $btnFull.addClass('active').attr('aria-pressed', 'true'); + } else { + $btnBord.addClass('active').attr('aria-pressed', 'true'); + } + if ($modal.hasClass('file-zoom-fullscreen')) { + self._maximizeZoomDialog(); + } else { + if (isFullScreen) { + self._maximizeZoomDialog(); + } else { + $btnBord.removeClass('active').attr('aria-pressed', 'false'); + } + } + }, + _listen: function () { + var self = this, $el = self.$element, $form = self.$form, $cont = self.$container, fullScreenEvents; + self._handler($el, 'click', function (e) { + if ($el.hasClass('file-no-browse')) { + if ($el.data('zoneClicked')) { + $el.data('zoneClicked', false); + } else { + e.preventDefault(); + } + } + }); + self._handler($el, 'change', $.proxy(self._change, self)); + if (self.showBrowse) { + self._handler(self.$btnFile, 'click', $.proxy(self._browse, self)); + } + self._handler($cont.find('.fileinput-remove:not([disabled])'), 'click', $.proxy(self.clear, self)); + self._handler($cont.find('.fileinput-cancel'), 'click', $.proxy(self.cancel, self)); + self._initDragDrop(); + self._handler($form, 'reset', $.proxy(self.clear, self)); + if (!self.isAjaxUpload) { + self._handler($form, 'submit', $.proxy(self._submitForm, self)); + } + self._handler(self.$container.find('.fileinput-upload'), 'click', $.proxy(self._uploadClick, self)); + self._handler($(window), 'resize', function () { + self._listenFullScreen(screen.width === window.innerWidth && screen.height === window.innerHeight); + }); + fullScreenEvents = 'webkitfullscreenchange mozfullscreenchange fullscreenchange MSFullscreenChange'; + self._handler($(document), fullScreenEvents, function () { + self._listenFullScreen($h.checkFullScreen()); + }); + self._autoFitContent(); + self._initClickable(); + self._refreshPreview(); + }, + _autoFitContent: function () { + var width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth, + self = this, config = width < 400 ? (self.previewSettingsSmall || self.defaults.previewSettingsSmall) : + (self.previewSettings || self.defaults.previewSettings), sel; + $.each(config, function (cat, settings) { + sel = '.file-preview-frame .file-preview-' + cat; + self.$preview.find(sel + '.kv-preview-data,' + sel + ' .kv-preview-data').css(settings); + }); + }, + _scanDroppedItems: function (item, files, path) { + path = path || ""; + var self = this, i, dirReader, readDir, errorHandler = function (e) { + self._log('Error scanning dropped files!'); + self._log(e); + }; + if (item.isFile) { + item.file(function (file) { + files.push(file); + }, errorHandler); + } else { + if (item.isDirectory) { + dirReader = item.createReader(); + readDir = function () { + dirReader.readEntries(function (entries) { + if (entries && entries.length > 0) { + for (i = 0; i < entries.length; i++) { + self._scanDroppedItems(entries[i], files, path + item.name + "/"); + } + // recursively call readDir() again, since browser can only handle first 100 entries. + readDir(); + } + return null; + }, errorHandler); + }; + readDir(); + } + } + + }, + _initDragDrop: function () { + var self = this, $zone = self.$dropZone; + if (self.dropZoneEnabled && self.showPreview) { + self._handler($zone, 'dragenter dragover', $.proxy(self._zoneDragEnter, self)); + self._handler($zone, 'dragleave', $.proxy(self._zoneDragLeave, self)); + self._handler($zone, 'drop', $.proxy(self._zoneDrop, self)); + self._handler($(document), 'dragenter dragover drop', self._zoneDragDropInit); + } + }, + _zoneDragDropInit: function (e) { + e.stopPropagation(); + e.preventDefault(); + }, + _zoneDragEnter: function (e) { + var self = this, hasFiles = $.inArray('Files', e.originalEvent.dataTransfer.types) > -1; + self._zoneDragDropInit(e); + if (self.isDisabled || !hasFiles) { + e.originalEvent.dataTransfer.effectAllowed = 'none'; + e.originalEvent.dataTransfer.dropEffect = 'none'; + return; + } + $h.addCss(self.$dropZone, 'file-highlighted'); + }, + _zoneDragLeave: function (e) { + var self = this; + self._zoneDragDropInit(e); + if (self.isDisabled) { + return; + } + self.$dropZone.removeClass('file-highlighted'); + }, + _zoneDrop: function (e) { + /** @namespace e.originalEvent.dataTransfer */ + var self = this, i, $el = self.$element, dataTransfer = e.originalEvent.dataTransfer, + files = dataTransfer.files, items = dataTransfer.items, folders = $h.getDragDropFolders(items), + processFiles = function () { + if (!self.isAjaxUpload) { + self.changeTriggered = true; + $el.get(0).files = files; + setTimeout(function () { + self.changeTriggered = false; + $el.trigger('change' + self.namespace); + }, 10); + } else { + self._change(e, files); + } + self.$dropZone.removeClass('file-highlighted'); + }; + e.preventDefault(); + if (self.isDisabled || $h.isEmpty(files)) { + return; + } + if (folders > 0) { + if (!self.isAjaxUpload) { + self._showFolderError(folders); + return; + } + files = []; + for (i = 0; i < items.length; i++) { + var item = items[i].webkitGetAsEntry(); + if (item) { + self._scanDroppedItems(item, files); + } + } + setTimeout(function () { + processFiles(); + }, 500); + } else { + processFiles(); + } + }, + _uploadClick: function (e) { + var self = this, $btn = self.$container.find('.fileinput-upload'), $form, + isEnabled = !$btn.hasClass('disabled') && $h.isEmpty($btn.attr('disabled')); + if (e && e.isDefaultPrevented()) { + return; + } + if (!self.isAjaxUpload) { + if (isEnabled && $btn.attr('type') !== 'submit') { + $form = $btn.closest('form'); + // downgrade to normal form submit if possible + if ($form.length) { + $form.trigger('submit'); + } + e.preventDefault(); + } + return; + } + e.preventDefault(); + if (isEnabled) { + self.upload(); + } + }, + _submitForm: function () { + var self = this; + return self._isFileSelectionValid() && !self._abort({}); + }, + _clearPreview: function () { + var self = this, $p = self.$preview, + $thumbs = self.showUploadedThumbs ? self.getFrames(':not(.file-preview-success)') : self.getFrames(); + $thumbs.each(function () { + var $thumb = $(this); + $thumb.remove(); + $h.cleanZoomCache($p.find('#zoom-' + $thumb.attr('id'))); + }); + if (!self.getFrames().length || !self.showPreview) { + self._resetUpload(); + } + self._validateDefaultPreview(); + }, + _initSortable: function () { + var self = this, $el = self.$preview, settings, selector = '.' + $h.SORT_CSS, + rev = self.reversePreviewOrder; + if (!window.KvSortable || $el.find(selector).length === 0) { + return; + } + //noinspection JSUnusedGlobalSymbols + settings = { + handle: '.drag-handle-init', + dataIdAttr: 'data-preview-id', + scroll: false, + draggable: selector, + onSort: function (e) { + var oldIndex = e.oldIndex, newIndex = e.newIndex, i = 0; + self.initialPreview = $h.moveArray(self.initialPreview, oldIndex, newIndex, rev); + self.initialPreviewConfig = $h.moveArray(self.initialPreviewConfig, oldIndex, newIndex, rev); + self.previewCache.init(); + self.getFrames('.file-preview-initial').each(function () { + $(this).attr('data-fileindex', 'init_' + i); + i++; + }); + self._raise('filesorted', { + previewId: $(e.item).attr('id'), + 'oldIndex': oldIndex, + 'newIndex': newIndex, + stack: self.initialPreviewConfig + }); + } + }; + if ($el.data('kvsortable')) { + $el.kvsortable('destroy'); + } + $.extend(true, settings, self.fileActionSettings.dragSettings); + $el.kvsortable(settings); + }, + _setPreviewContent: function (content) { + var self = this; + self.$preview.html(content); + self._autoFitContent(); + }, + _initPreview: function (isInit) { + var self = this, cap = self.initialCaption || '', out; + if (!self.previewCache.count()) { + self._clearPreview(); + if (isInit) { + self._setCaption(cap); + } else { + self._initCaption(); + } + return; + } + out = self.previewCache.out(); + cap = isInit && self.initialCaption ? self.initialCaption : out.caption; + self._setPreviewContent(out.content); + self._setInitThumbAttr(); + self._setCaption(cap); + self._initSortable(); + if (!$h.isEmpty(out.content)) { + self.$container.removeClass('file-input-new'); + } + }, + _getZoomButton: function (type) { + var self = this, label = self.previewZoomButtonIcons[type], css = self.previewZoomButtonClasses[type], + title = ' title="' + (self.previewZoomButtonTitles[type] || '') + '" ', + params = title + (type === 'close' ? ' data-dismiss="modal" aria-hidden="true"' : ''); + if (type === 'fullscreen' || type === 'borderless' || type === 'toggleheader') { + params += ' data-toggle="button" aria-pressed="false" autocomplete="off"'; + } + return ''; + }, + _getModalContent: function () { + var self = this; + return self._getLayoutTemplate('modal').setTokens({ + 'rtl': self.rtl ? ' kv-rtl' : '', + 'zoomFrameClass': self.frameClass, + 'heading': self.msgZoomModalHeading, + 'prev': self._getZoomButton('prev'), + 'next': self._getZoomButton('next'), + 'toggleheader': self._getZoomButton('toggleheader'), + 'fullscreen': self._getZoomButton('fullscreen'), + 'borderless': self._getZoomButton('borderless'), + 'close': self._getZoomButton('close') + }); + }, + _listenModalEvent: function (event) { + var self = this, $modal = self.$modal, getParams = function (e) { + return { + sourceEvent: e, + previewId: $modal.data('previewId'), + modal: $modal + }; + }; + $modal.on(event + '.bs.modal', function (e) { + var $btnFull = $modal.find('.btn-fullscreen'), $btnBord = $modal.find('.btn-borderless'); + self._raise('filezoom' + event, getParams(e)); + if (event === 'shown') { + $btnBord.removeClass('active').attr('aria-pressed', 'false'); + $btnFull.removeClass('active').attr('aria-pressed', 'false'); + if ($modal.hasClass('file-zoom-fullscreen')) { + self._maximizeZoomDialog(); + if ($h.checkFullScreen()) { + $btnFull.addClass('active').attr('aria-pressed', 'true'); + } else { + $btnBord.addClass('active').attr('aria-pressed', 'true'); + } + } + } + }); + }, + _initZoom: function () { + var self = this, $dialog, modalMain = self._getLayoutTemplate('modalMain'), modalId = '#' + $h.MODAL_ID; + if (!self.showPreview) { + return; + } + self.$modal = $(modalId); + if (!self.$modal || !self.$modal.length) { + $dialog = $(document.createElement('div')).html(modalMain).insertAfter(self.$container); + self.$modal = $(modalId).insertBefore($dialog); + $dialog.remove(); + } + $h.initModal(self.$modal); + self.$modal.html(self._getModalContent()); + $.each($h.MODAL_EVENTS, function (key, event) { + self._listenModalEvent(event); + }); + }, + _initZoomButtons: function () { + var self = this, previewId = self.$modal.data('previewId') || '', $first, $last, + thumbs = self.getFrames().toArray(), len = thumbs.length, $prev = self.$modal.find('.btn-prev'), + $next = self.$modal.find('.btn-next'); + if (thumbs.length < 2) { + $prev.hide(); + $next.hide(); + return; + } else { + $prev.show(); + $next.show(); + } + if (!len) { + return; + } + $first = $(thumbs[0]); + $last = $(thumbs[len - 1]); + $prev.removeAttr('disabled'); + $next.removeAttr('disabled'); + if ($first.length && $first.attr('id') === previewId) { + $prev.attr('disabled', true); + } + if ($last.length && $last.attr('id') === previewId) { + $next.attr('disabled', true); + } + }, + _maximizeZoomDialog: function () { + var self = this, $modal = self.$modal, $head = $modal.find('.modal-header:visible'), + $foot = $modal.find('.modal-footer:visible'), $body = $modal.find('.modal-body'), + h = $(window).height(), diff = 0; + $modal.addClass('file-zoom-fullscreen'); + if ($head && $head.length) { + h -= $head.outerHeight(true); + } + if ($foot && $foot.length) { + h -= $foot.outerHeight(true); + } + if ($body && $body.length) { + diff = $body.outerHeight(true) - $body.height(); + h -= diff; + } + $modal.find('.kv-zoom-body').height(h); + }, + _resizeZoomDialog: function (fullScreen) { + var self = this, $modal = self.$modal, $btnFull = $modal.find('.btn-fullscreen'), + $btnBord = $modal.find('.btn-borderless'); + if ($modal.hasClass('file-zoom-fullscreen')) { + $h.toggleFullScreen(false); + if (!fullScreen) { + if (!$btnFull.hasClass('active')) { + $modal.removeClass('file-zoom-fullscreen'); + self.$modal.find('.kv-zoom-body').css('height', self.zoomModalHeight); + } else { + $btnFull.removeClass('active').attr('aria-pressed', 'false'); + } + } else { + if (!$btnFull.hasClass('active')) { + $modal.removeClass('file-zoom-fullscreen'); + self._resizeZoomDialog(true); + if ($btnBord.hasClass('active')) { + $btnBord.removeClass('active').attr('aria-pressed', 'false'); + } + } + } + } else { + if (!fullScreen) { + self._maximizeZoomDialog(); + return; + } + $h.toggleFullScreen(true); + } + $modal.focus(); + }, + _setZoomContent: function ($frame, animate) { + var self = this, $content, tmplt, body, title, $body, $dataEl, config, pid = $frame.attr('id'), + $modal = self.$modal, $prev = $modal.find('.btn-prev'), $next = $modal.find('.btn-next'), $tmp, + $btnFull = $modal.find('.btn-fullscreen'), $btnBord = $modal.find('.btn-borderless'), cap, size, + $btnTogh = $modal.find('.btn-toggleheader'), $zoomPreview = self.$preview.find('#zoom-' + pid); + tmplt = $zoomPreview.attr('data-template') || 'generic'; + $content = $zoomPreview.find('.kv-file-content'); + body = $content.length ? $content.html() : ''; + cap = $frame.data('caption') || ''; + size = $frame.data('size') || ''; + title = cap + ' ' + size; + $modal.find('.kv-zoom-title').attr('title', $('
    ').html(title).text()).html(title); + $body = $modal.find('.kv-zoom-body'); + $modal.removeClass('kv-single-content'); + if (animate) { + $tmp = $body.addClass('file-thumb-loading').clone().insertAfter($body); + $body.html(body).hide(); + $tmp.fadeOut('fast', function () { + $body.fadeIn('fast', function () { + $body.removeClass('file-thumb-loading'); + }); + $tmp.remove(); + }); + } else { + $body.html(body); + } + config = self.previewZoomSettings[tmplt]; + if (config) { + $dataEl = $body.find('.kv-preview-data'); + $h.addCss($dataEl, 'file-zoom-detail'); + $.each(config, function (key, value) { + $dataEl.css(key, value); + if (($dataEl.attr('width') && key === 'width') || ($dataEl.attr('height') && key === 'height')) { + $dataEl.removeAttr(key); + } + }); + } + $modal.data('previewId', pid); + self._handler($prev, 'click', function () { + self._zoomSlideShow('prev', pid); + }); + self._handler($next, 'click', function () { + self._zoomSlideShow('next', pid); + }); + self._handler($btnFull, 'click', function () { + self._resizeZoomDialog(true); + }); + self._handler($btnBord, 'click', function () { + self._resizeZoomDialog(false); + }); + self._handler($btnTogh, 'click', function () { + var $header = $modal.find('.modal-header'), $floatBar = $modal.find('.modal-body .floating-buttons'), + ht, $actions = $header.find('.kv-zoom-actions'), resize = function (height) { + var $body = self.$modal.find('.kv-zoom-body'), h = self.zoomModalHeight; + if ($modal.hasClass('file-zoom-fullscreen')) { + h = $body.outerHeight(true); + if (!height) { + h = h - $header.outerHeight(true); + } + } + $body.css('height', height ? h + height : h); + }; + if ($header.is(':visible')) { + ht = $header.outerHeight(true); + $header.slideUp('slow', function () { + $actions.find('.btn').appendTo($floatBar); + resize(ht); + }); + } else { + $floatBar.find('.btn').appendTo($actions); + $header.slideDown('slow', function () { + resize(); + }); + } + $modal.focus(); + }); + self._handler($modal, 'keydown', function (e) { + var key = e.which || e.keyCode; + if (key === 37 && !$prev.attr('disabled')) { + self._zoomSlideShow('prev', pid); + } + if (key === 39 && !$next.attr('disabled')) { + self._zoomSlideShow('next', pid); + } + }); + }, + _zoomPreview: function ($btn) { + var self = this, $frame, $modal = self.$modal; + if (!$btn.length) { + throw 'Cannot zoom to detailed preview!'; + } + $h.initModal($modal); + $modal.html(self._getModalContent()); + $frame = $btn.closest($h.FRAMES); + self._setZoomContent($frame); + $modal.modal('show'); + self._initZoomButtons(); + }, + _zoomSlideShow: function (dir, previewId) { + var self = this, $btn = self.$modal.find('.kv-zoom-actions .btn-' + dir), $targFrame, i, + thumbs = self.getFrames().toArray(), len = thumbs.length, out; + if ($btn.attr('disabled')) { + return; + } + for (i = 0; i < len; i++) { + if ($(thumbs[i]).attr('id') === previewId) { + out = dir === 'prev' ? i - 1 : i + 1; + break; + } + } + if (out < 0 || out >= len || !thumbs[out]) { + return; + } + $targFrame = $(thumbs[out]); + if ($targFrame.length) { + self._setZoomContent($targFrame, true); + } + self._initZoomButtons(); + self._raise('filezoom' + dir, {'previewId': previewId, modal: self.$modal}); + }, + _initZoomButton: function () { + var self = this; + self.$preview.find('.kv-file-zoom').each(function () { + var $el = $(this); + self._handler($el, 'click', function () { + self._zoomPreview($el); + }); + }); + }, + _inputFileCount: function () { + return this.$element.get(0).files.length; + }, + _refreshPreview: function () { + var self = this, files; + if (!self._inputFileCount() || !self.showPreview || !self.isPreviewable) { + return; + } + if (self.isAjaxUpload) { + files = self.getFileStack(); + self.filestack = []; + if (files.length) { + self._clearFileInput(); + } else { + files = self.$element.get(0).files; + } + } else { + files = self.$element.get(0).files; + } + if (files && files.length) { + self.readFiles(files); + self._setFileDropZoneTitle(); + } + }, + _clearObjects: function ($el) { + $el.find('video audio').each(function () { + this.pause(); + $(this).remove(); + }); + $el.find('img object div').each(function () { + $(this).remove(); + }); + }, + _clearFileInput: function () { + var self = this, $el = self.$element, $srcFrm, $tmpFrm, $tmpEl; + if (!self._inputFileCount()) { + return; + } + $srcFrm = $el.closest('form'); + $tmpFrm = $(document.createElement('form')); + $tmpEl = $(document.createElement('div')); + $el.before($tmpEl); + if ($srcFrm.length) { + $srcFrm.after($tmpFrm); + } else { + $tmpEl.after($tmpFrm); + } + $tmpFrm.append($el).trigger('reset'); + $tmpEl.before($el).remove(); + $tmpFrm.remove(); + }, + _resetUpload: function () { + var self = this; + self.uploadCache = {content: [], config: [], tags: [], append: true}; + self.uploadCount = 0; + self.uploadStatus = {}; + self.uploadLog = []; + self.uploadAsyncCount = 0; + self.loadedImages = []; + self.totalImagesCount = 0; + self.$btnUpload.removeAttr('disabled'); + self._setProgress(0); + self.$progress.hide(); + self._resetErrors(false); + self.ajaxAborted = false; + self.ajaxRequests = []; + self._resetCanvas(); + self.cacheInitialPreview = {}; + if (self.overwriteInitial) { + self.initialPreview = []; + self.initialPreviewConfig = []; + self.initialPreviewThumbTags = []; + self.previewCache.data = { + content: [], + config: [], + tags: [] + }; + } + }, + _resetCanvas: function () { + var self = this; + if (self.canvas && self.imageCanvasContext) { + self.imageCanvasContext.clearRect(0, 0, self.canvas.width, self.canvas.height); + } + }, + _hasInitialPreview: function () { + var self = this; + return !self.overwriteInitial && self.previewCache.count(); + }, + _resetPreview: function () { + var self = this, out, cap; + if (self.previewCache.count()) { + out = self.previewCache.out(); + self._setPreviewContent(out.content); + self._setInitThumbAttr(); + cap = self.initialCaption ? self.initialCaption : out.caption; + self._setCaption(cap); + } else { + self._clearPreview(); + self._initCaption(); + } + if (self.showPreview) { + self._initZoom(); + self._initSortable(); + } + }, + _clearDefaultPreview: function () { + var self = this; + self.$preview.find('.file-default-preview').remove(); + }, + _validateDefaultPreview: function () { + var self = this; + if (!self.showPreview || $h.isEmpty(self.defaultPreviewContent)) { + return; + } + self._setPreviewContent('
    ' + self.defaultPreviewContent + '
    '); + self.$container.removeClass('file-input-new'); + self._initClickable(); + }, + _resetPreviewThumbs: function (isAjax) { + var self = this, out; + if (isAjax) { + self._clearPreview(); + self.clearStack(); + return; + } + if (self._hasInitialPreview()) { + out = self.previewCache.out(); + self._setPreviewContent(out.content); + self._setInitThumbAttr(); + self._setCaption(out.caption); + self._initPreviewActions(); + } else { + self._clearPreview(); + } + }, + _getLayoutTemplate: function (t) { + var self = this, template = self.layoutTemplates[t]; + if ($h.isEmpty(self.customLayoutTags)) { + return template; + } + return $h.replaceTags(template, self.customLayoutTags); + }, + _getPreviewTemplate: function (t) { + var self = this, template = self.previewTemplates[t]; + if ($h.isEmpty(self.customPreviewTags)) { + return template; + } + return $h.replaceTags(template, self.customPreviewTags); + }, + _getOutData: function (jqXHR, responseData, filesData) { + var self = this; + jqXHR = jqXHR || {}; + responseData = responseData || {}; + filesData = filesData || self.filestack.slice(0) || {}; + return { + form: self.formdata, + files: filesData, + filenames: self.filenames, + filescount: self.getFilesCount(), + extra: self._getExtraData(), + response: responseData, + reader: self.reader, + jqXHR: jqXHR + }; + }, + _getMsgSelected: function (n) { + var self = this, strFiles = n === 1 ? self.fileSingle : self.filePlural; + return n > 0 ? self.msgSelected.replace('{n}', n).replace('{files}', strFiles) : self.msgNoFilesSelected; + }, + _getFrame: function (id) { + var self = this, $frame = $('#' + id); + if (!$frame.length) { + self._log('Invalid thumb frame with id: "' + id + '".'); + return null; + } + return $frame; + }, + _getThumbs: function (css) { + css = css || ''; + return this.getFrames(':not(.file-preview-initial)' + css); + }, + _getExtraData: function (previewId, index) { + var self = this, data = self.uploadExtraData; + if (typeof self.uploadExtraData === "function") { + data = self.uploadExtraData(previewId, index); + } + return data; + }, + _initXhr: function (xhrobj, previewId, fileCount) { + var self = this; + if (xhrobj.upload) { + xhrobj.upload.addEventListener('progress', function (event) { + var pct = 0, total = event.total, position = event.loaded || event.position; + /** @namespace event.lengthComputable */ + if (event.lengthComputable) { + pct = Math.floor(position / total * 100); + } + if (previewId) { + self._setAsyncUploadStatus(previewId, pct, fileCount); + } else { + self._setProgress(pct); + } + }, false); + } + return xhrobj; + }, + _initAjaxSettings: function () { + var self = this; + self._ajaxSettings = $.extend(true, {}, self.ajaxSettings); + self._ajaxDeleteSettings = $.extend(true, {}, self.ajaxDeleteSettings); + }, + _mergeAjaxCallback: function (funcName, srcFunc, type) { + var self = this, settings = self._ajaxSettings, flag = self.mergeAjaxCallbacks, targFunc; + if (type === 'delete') { + settings = self._ajaxDeleteSettings; + flag = self.mergeAjaxDeleteCallbacks; + } + targFunc = settings[funcName]; + if (flag && typeof targFunc === "function") { + if (flag === 'before') { + settings[funcName] = function () { + targFunc.apply(this, arguments); + srcFunc.apply(this, arguments); + }; + } else { + settings[funcName] = function () { + srcFunc.apply(this, arguments); + targFunc.apply(this, arguments); + }; + } + } else { + settings[funcName] = srcFunc; + } + }, + _ajaxSubmit: function (fnBefore, fnSuccess, fnComplete, fnError, previewId, index) { + var self = this, settings; + if (!self._raise('filepreajax', [previewId, index])) { + return; + } + self._uploadExtra(previewId, index); + self._initAjaxSettings(); + self._mergeAjaxCallback('beforeSend', fnBefore); + self._mergeAjaxCallback('success', fnSuccess); + self._mergeAjaxCallback('complete', fnComplete); + self._mergeAjaxCallback('error', fnError); + settings = $.extend(true, {}, { + xhr: function () { + var xhrobj = $.ajaxSettings.xhr(); + return self._initXhr(xhrobj, previewId, self.getFileStack().length); + }, + url: index && self.uploadUrlThumb ? self.uploadUrlThumb : self.uploadUrl, + type: 'POST', + dataType: 'json', + data: self.formdata, + cache: false, + processData: false, + contentType: false + }, self._ajaxSettings); + self.ajaxRequests.push($.ajax(settings)); + }, + _mergeArray: function (prop, content) { + var self = this, arr1 = $h.cleanArray(self[prop]), arr2 = $h.cleanArray(content); + self[prop] = arr1.concat(arr2); + }, + _initUploadSuccess: function (out, $thumb, allFiles) { + var self = this, append, data, index, $div, $newCache, content, config, tags, i; + if (!self.showPreview || typeof out !== 'object' || $.isEmptyObject(out)) { + return; + } + if (out.initialPreview !== undefined && out.initialPreview.length > 0) { + self.hasInitData = true; + content = out.initialPreview || []; + config = out.initialPreviewConfig || []; + tags = out.initialPreviewThumbTags || []; + append = out.append === undefined || out.append; + if (content.length > 0 && !$h.isArray(content)) { + content = content.split(self.initialPreviewDelimiter); + } + self._mergeArray('initialPreview', content); + self._mergeArray('initialPreviewConfig', config); + self._mergeArray('initialPreviewThumbTags', tags); + if ($thumb !== undefined) { + if (!allFiles) { + index = self.previewCache.add(content, config[0], tags[0], append); + data = self.previewCache.get(index, false); + $div = $(document.createElement('div')).html(data).hide().insertAfter($thumb); + $newCache = $div.find('.kv-zoom-cache'); + if ($newCache && $newCache.length) { + $newCache.insertAfter($thumb); + } + $thumb.fadeOut('slow', function () { + var $newThumb = $div.find('.file-preview-frame'); + if ($newThumb && $newThumb.length) { + $newThumb.insertBefore($thumb).fadeIn('slow').css('display:inline-block'); + } + self._initPreviewActions(); + self._clearFileInput(); + $h.cleanZoomCache(self.$preview.find('#zoom-' + $thumb.attr('id'))); + $thumb.remove(); + $div.remove(); + self._initSortable(); + }); + } else { + i = $thumb.attr('data-fileindex'); + self.uploadCache.content[i] = content[0]; + self.uploadCache.config[i] = config[0] || []; + self.uploadCache.tags[i] = tags[0] || []; + self.uploadCache.append = append; + } + } else { + self.previewCache.set(content, config, tags, append); + self._initPreview(); + self._initPreviewActions(); + } + } + }, + _initSuccessThumbs: function () { + var self = this; + if (!self.showPreview) { + return; + } + self._getThumbs($h.FRAMES + '.file-preview-success').each(function () { + var $thumb = $(this), $preview = self.$preview, $remove = $thumb.find('.kv-file-remove'); + $remove.removeAttr('disabled'); + self._handler($remove, 'click', function () { + var id = $thumb.attr('id'), + out = self._raise('filesuccessremove', [id, $thumb.attr('data-fileindex')]); + $h.cleanMemory($thumb); + if (out === false) { + return; + } + $thumb.fadeOut('slow', function () { + $h.cleanZoomCache($preview.find('#zoom-' + id)); + $thumb.remove(); + if (!self.getFrames().length) { + self.reset(); + } + }); + }); + }); + }, + _checkAsyncComplete: function () { + var self = this, previewId, i; + for (i = 0; i < self.filestack.length; i++) { + if (self.filestack[i]) { + previewId = self.previewInitId + "-" + i; + if ($.inArray(previewId, self.uploadLog) === -1) { + return false; + } + } + } + return (self.uploadAsyncCount === self.uploadLog.length); + }, + _uploadExtra: function (previewId, index) { + var self = this, data = self._getExtraData(previewId, index); + if (data.length === 0) { + return; + } + $.each(data, function (key, value) { + self.formdata.append(key, value); + }); + }, + _uploadSingle: function (i, isBatch) { + var self = this, total = self.getFileStack().length, formdata = new FormData(), outData, + previewId = self.previewInitId + "-" + i, $thumb, chkComplete, $btnUpload, $btnDelete, + hasPostData = self.filestack.length > 0 || !$.isEmptyObject(self.uploadExtraData), uploadFailed, + $prog = $('#' + previewId).find('.file-thumb-progress'), fnBefore, fnSuccess, fnComplete, fnError, + updateUploadLog, params = {id: previewId, index: i}; + self.formdata = formdata; + if (self.showPreview) { + $thumb = $('#' + previewId + ':not(.file-preview-initial)'); + $btnUpload = $thumb.find('.kv-file-upload'); + $btnDelete = $thumb.find('.kv-file-remove'); + $prog.show(); + } + if (total === 0 || !hasPostData || ($btnUpload && $btnUpload.hasClass('disabled')) || self._abort(params)) { + return; + } + updateUploadLog = function (i, previewId) { + if (!uploadFailed) { + self.updateStack(i, undefined); + } + self.uploadLog.push(previewId); + if (self._checkAsyncComplete()) { + self.fileBatchCompleted = true; + } + }; + chkComplete = function () { + var u = self.uploadCache, $initThumbs, i, j, len = 0, data = self.cacheInitialPreview; + if (!self.fileBatchCompleted) { + return; + } + if (data && data.content) { + len = data.content.length; + } + setTimeout(function () { + var triggerReset = self.getFileStack(true).length === 0; + if (self.showPreview) { + self.previewCache.set(u.content, u.config, u.tags, u.append); + if (len) { + for (i = 0; i < u.content.length; i++) { + j = i + len; + data.content[j] = u.content[i]; + //noinspection JSUnresolvedVariable + if (data.config.length) { + data.config[j] = u.config[i]; + } + if (data.tags.length) { + data.tags[j] = u.tags[i]; + } + } + self.initialPreview = $h.cleanArray(data.content); + self.initialPreviewConfig = $h.cleanArray(data.config); + self.initialPreviewThumbTags = $h.cleanArray(data.tags); + } else { + self.initialPreview = u.content; + self.initialPreviewConfig = u.config; + self.initialPreviewThumbTags = u.tags; + } + self.cacheInitialPreview = {}; + if (self.hasInitData) { + self._initPreview(); + self._initPreviewActions(); + } + } + self.unlock(triggerReset); + if (triggerReset) { + self._clearFileInput(); + } + $initThumbs = self.$preview.find('.file-preview-initial'); + if (self.uploadAsync && $initThumbs.length) { + $h.addCss($initThumbs, $h.SORT_CSS); + self._initSortable(); + } + self._raise('filebatchuploadcomplete', [self.filestack, self._getExtraData()]); + self.uploadCount = 0; + self.uploadStatus = {}; + self.uploadLog = []; + self._setProgress(101); + self.ajaxAborted = false; + }, 100); + }; + fnBefore = function (jqXHR) { + outData = self._getOutData(jqXHR); + self.fileBatchCompleted = false; + if (!isBatch) { + self.ajaxAborted = false; + } + if (self.showPreview) { + if (!$thumb.hasClass('file-preview-success')) { + self._setThumbStatus($thumb, 'Loading'); + $h.addCss($thumb, 'file-uploading'); + } + $btnUpload.attr('disabled', true); + $btnDelete.attr('disabled', true); + } + if (!isBatch) { + self.lock(); + } + self._raise('filepreupload', [outData, previewId, i]); + $.extend(true, params, outData); + if (self._abort(params)) { + jqXHR.abort(); + if (!isBatch) { + self._setThumbStatus($thumb, 'New'); + $thumb.removeClass('file-uploading'); + $btnUpload.removeAttr('disabled'); + $btnDelete.removeAttr('disabled'); + self.unlock(); + } + self._setProgressCancelled(); + } + }; + fnSuccess = function (data, textStatus, jqXHR) { + var pid = self.showPreview && $thumb.attr('id') ? $thumb.attr('id') : previewId; + outData = self._getOutData(jqXHR, data); + $.extend(true, params, outData); + setTimeout(function () { + if ($h.isEmpty(data) || $h.isEmpty(data.error)) { + if (self.showPreview) { + self._setThumbStatus($thumb, 'Success'); + $btnUpload.hide(); + self._initUploadSuccess(data, $thumb, isBatch); + self._setProgress(101, $prog); + } + self._raise('fileuploaded', [outData, pid, i]); + if (!isBatch) { + self.updateStack(i, undefined); + } else { + updateUploadLog(i, pid); + } + } else { + uploadFailed = true; + self._showUploadError(data.error, params); + self._setPreviewError($thumb, i, self.filestack[i], self.retryErrorUploads); + if (!self.retryErrorUploads) { + $btnUpload.hide(); + } + if (isBatch) { + updateUploadLog(i, pid); + } + self._setProgress(101, $('#' + pid).find('.file-thumb-progress'), self.msgUploadError); + } + }, 100); + }; + fnComplete = function () { + setTimeout(function () { + if (self.showPreview) { + $btnUpload.removeAttr('disabled'); + $btnDelete.removeAttr('disabled'); + $thumb.removeClass('file-uploading'); + } + if (!isBatch) { + self.unlock(false); + self._clearFileInput(); + } else { + chkComplete(); + } + self._initSuccessThumbs(); + }, 100); + }; + fnError = function (jqXHR, textStatus, errorThrown) { + var op = self.ajaxOperations.uploadThumb, + errMsg = self._parseError(op, jqXHR, errorThrown, (isBatch && self.filestack[i].name ? self.filestack[i].name : null)); + uploadFailed = true; + setTimeout(function () { + if (isBatch) { + updateUploadLog(i, previewId); + } + self.uploadStatus[previewId] = 100; + self._setPreviewError($thumb, i, self.filestack[i], self.retryErrorUploads); + if (!self.retryErrorUploads) { + $btnUpload.hide(); + } + $.extend(true, params, self._getOutData(jqXHR)); + self._setProgress(101, $prog, self.msgAjaxProgressError.replace('{operation}', op)); + self._setProgress(101, $('#' + previewId).find('.file-thumb-progress'), self.msgUploadError); + self._showUploadError(errMsg, params); + }, 100); + }; + formdata.append(self.uploadFileAttr, self.filestack[i], self.filenames[i]); + formdata.append('file_id', i); + self._ajaxSubmit(fnBefore, fnSuccess, fnComplete, fnError, previewId, i); + }, + _uploadBatch: function () { + var self = this, files = self.filestack, total = files.length, params = {}, fnBefore, fnSuccess, fnError, + fnComplete, hasPostData = self.filestack.length > 0 || !$.isEmptyObject(self.uploadExtraData), + setAllUploaded; + self.formdata = new FormData(); + if (total === 0 || !hasPostData || self._abort(params)) { + return; + } + setAllUploaded = function () { + $.each(files, function (key) { + self.updateStack(key, undefined); + }); + self._clearFileInput(); + }; + fnBefore = function (jqXHR) { + self.lock(); + var outData = self._getOutData(jqXHR); + self.ajaxAborted = false; + if (self.showPreview) { + self._getThumbs().each(function () { + var $thumb = $(this), $btnUpload = $thumb.find('.kv-file-upload'), + $btnDelete = $thumb.find('.kv-file-remove'); + if (!$thumb.hasClass('file-preview-success')) { + self._setThumbStatus($thumb, 'Loading'); + $h.addCss($thumb, 'file-uploading'); + } + $btnUpload.attr('disabled', true); + $btnDelete.attr('disabled', true); + }); + } + self._raise('filebatchpreupload', [outData]); + if (self._abort(outData)) { + jqXHR.abort(); + self._getThumbs().each(function () { + var $thumb = $(this), $btnUpload = $thumb.find('.kv-file-upload'), + $btnDelete = $thumb.find('.kv-file-remove'); + if ($thumb.hasClass('file-preview-loading')) { + self._setThumbStatus($thumb, 'New'); + $thumb.removeClass('file-uploading'); + } + $btnUpload.removeAttr('disabled'); + $btnDelete.removeAttr('disabled'); + }); + self._setProgressCancelled(); + } + }; + fnSuccess = function (data, textStatus, jqXHR) { + /** @namespace data.errorkeys */ + var outData = self._getOutData(jqXHR, data), key = 0, + $thumbs = self._getThumbs(':not(.file-preview-success)'), + keys = $h.isEmpty(data) || $h.isEmpty(data.errorkeys) ? [] : data.errorkeys; + + if ($h.isEmpty(data) || $h.isEmpty(data.error)) { + self._raise('filebatchuploadsuccess', [outData]); + setAllUploaded(); + if (self.showPreview) { + $thumbs.each(function () { + var $thumb = $(this); + self._setThumbStatus($thumb, 'Success'); + $thumb.removeClass('file-uploading'); + $thumb.find('.kv-file-upload').hide().removeAttr('disabled'); + }); + self._initUploadSuccess(data); + } else { + self.reset(); + } + self._setProgress(101); + } else { + if (self.showPreview) { + $thumbs.each(function () { + var $thumb = $(this), i = $thumb.attr('data-fileindex'); + $thumb.removeClass('file-uploading'); + $thumb.find('.kv-file-upload').removeAttr('disabled'); + $thumb.find('.kv-file-remove').removeAttr('disabled'); + if (keys.length === 0 || $.inArray(key, keys) !== -1) { + self._setPreviewError($thumb, i, self.filestack[i], self.retryErrorUploads); + if (!self.retryErrorUploads) { + $thumb.find('.kv-file-upload').hide(); + self.updateStack(i, undefined); + } + } else { + $thumb.find('.kv-file-upload').hide(); + self._setThumbStatus($thumb, 'Success'); + self.updateStack(i, undefined); + } + if (!$thumb.hasClass('file-preview-error') || self.retryErrorUploads) { + key++; + } + }); + self._initUploadSuccess(data); + } + self._showUploadError(data.error, outData, 'filebatchuploaderror'); + self._setProgress(101, self.$progress, self.msgUploadError); + } + }; + fnComplete = function () { + self.unlock(); + self._initSuccessThumbs(); + self._clearFileInput(); + self._raise('filebatchuploadcomplete', [self.filestack, self._getExtraData()]); + }; + fnError = function (jqXHR, textStatus, errorThrown) { + var outData = self._getOutData(jqXHR), op = self.ajaxOperations.uploadBatch, + errMsg = self._parseError(op, jqXHR, errorThrown); + self._showUploadError(errMsg, outData, 'filebatchuploaderror'); + self.uploadFileCount = total - 1; + if (!self.showPreview) { + return; + } + self._getThumbs().each(function () { + var $thumb = $(this), key = $thumb.attr('data-fileindex'); + $thumb.removeClass('file-uploading'); + if (self.filestack[key] !== undefined) { + self._setPreviewError($thumb); + } + }); + self._getThumbs().removeClass('file-uploading'); + self._getThumbs(' .kv-file-upload').removeAttr('disabled'); + self._getThumbs(' .kv-file-delete').removeAttr('disabled'); + self._setProgress(101, self.$progress, self.msgAjaxProgressError.replace('{operation}', op)); + }; + $.each(files, function (key, data) { + if (!$h.isEmpty(files[key])) { + self.formdata.append(self.uploadFileAttr, data, self.filenames[key]); + } + }); + self._ajaxSubmit(fnBefore, fnSuccess, fnComplete, fnError); + }, + _uploadExtraOnly: function () { + var self = this, params = {}, fnBefore, fnSuccess, fnComplete, fnError; + self.formdata = new FormData(); + if (self._abort(params)) { + return; + } + fnBefore = function (jqXHR) { + self.lock(); + var outData = self._getOutData(jqXHR); + self._raise('filebatchpreupload', [outData]); + self._setProgress(50); + params.data = outData; + params.xhr = jqXHR; + if (self._abort(params)) { + jqXHR.abort(); + self._setProgressCancelled(); + } + }; + fnSuccess = function (data, textStatus, jqXHR) { + var outData = self._getOutData(jqXHR, data); + if ($h.isEmpty(data) || $h.isEmpty(data.error)) { + self._raise('filebatchuploadsuccess', [outData]); + self._clearFileInput(); + self._initUploadSuccess(data); + self._setProgress(101); + } else { + self._showUploadError(data.error, outData, 'filebatchuploaderror'); + } + }; + fnComplete = function () { + self.unlock(); + self._clearFileInput(); + self._raise('filebatchuploadcomplete', [self.filestack, self._getExtraData()]); + }; + fnError = function (jqXHR, textStatus, errorThrown) { + var outData = self._getOutData(jqXHR), op = self.ajaxOperations.uploadExtra, + errMsg = self._parseError(op, jqXHR, errorThrown); + params.data = outData; + self._showUploadError(errMsg, outData, 'filebatchuploaderror'); + self._setProgress(101, self.$progress, self.msgAjaxProgressError.replace('{operation}', op)); + }; + self._ajaxSubmit(fnBefore, fnSuccess, fnComplete, fnError); + }, + _deleteFileIndex: function ($frame) { + var self = this, ind = $frame.attr('data-fileindex'), rev = self.reversePreviewOrder; + if (ind.substring(0, 5) === 'init_') { + ind = parseInt(ind.replace('init_', '')); + self.initialPreview = $h.spliceArray(self.initialPreview, ind, rev); + self.initialPreviewConfig = $h.spliceArray(self.initialPreviewConfig, ind, rev); + self.initialPreviewThumbTags = $h.spliceArray(self.initialPreviewThumbTags, ind, rev); + self.getFrames().each(function () { + var $nFrame = $(this), nInd = $nFrame.attr('data-fileindex'); + if (nInd.substring(0, 5) === 'init_') { + nInd = parseInt(nInd.replace('init_', '')); + if (nInd > ind) { + nInd--; + $nFrame.attr('data-fileindex', 'init_' + nInd); + } + } + }); + if (self.uploadAsync) { + self.cacheInitialPreview = self.getPreview(); + } + } + }, + _initFileActions: function () { + var self = this, $preview = self.$preview; + if (!self.showPreview) { + return; + } + self._initZoomButton(); + self.getFrames(' .kv-file-remove').each(function () { + var $el = $(this), $frame = $el.closest($h.FRAMES), hasError, id = $frame.attr('id'), + ind = $frame.attr('data-fileindex'), n, cap, status; + self._handler($el, 'click', function () { + status = self._raise('filepreremove', [id, ind]); + if (status === false || !self._validateMinCount()) { + return false; + } + hasError = $frame.hasClass('file-preview-error'); + $h.cleanMemory($frame); + $frame.fadeOut('slow', function () { + $h.cleanZoomCache($preview.find('#zoom-' + id)); + self.updateStack(ind, undefined); + self._clearObjects($frame); + $frame.remove(); + if (id && hasError) { + self.$errorContainer.find('li[data-file-id="' + id + '"]').fadeOut('fast', function () { + $(this).remove(); + if (!self._errorsExist()) { + self._resetErrors(); + } + }); + } + self._clearFileInput(); + var filestack = self.getFileStack(true), chk = self.previewCache.count(), + len = filestack.length, hasThumb = self.showPreview && self.getFrames().length; + if (len === 0 && chk === 0 && !hasThumb) { + self.reset(); + } else { + n = chk + len; + cap = n > 1 ? self._getMsgSelected(n) : (filestack[0] ? self._getFileNames()[0] : ''); + self._setCaption(cap); + } + self._raise('fileremoved', [id, ind]); + }); + }); + }); + self.getFrames(' .kv-file-upload').each(function () { + var $el = $(this); + self._handler($el, 'click', function () { + var $frame = $el.closest($h.FRAMES), ind = $frame.attr('data-fileindex'); + self.$progress.hide(); + if ($frame.hasClass('file-preview-error') && !self.retryErrorUploads) { + return; + } + self._uploadSingle(ind, false); + }); + }); + }, + _initPreviewActions: function () { + var self = this, $preview = self.$preview, deleteExtraData = self.deleteExtraData || {}, + btnRemove = $h.FRAMES + ' .kv-file-remove', settings = self.fileActionSettings, + origClass = settings.removeClass, errClass = settings.removeErrorClass, + resetProgress = function () { + var hasFiles = self.isAjaxUpload ? self.previewCache.count() : self._inputFileCount(); + if (!$preview.find($h.FRAMES).length && !hasFiles) { + self._setCaption(''); + self.reset(); + self.initialCaption = ''; + } + }; + self._initZoomButton(); + $preview.find(btnRemove).each(function () { + var $el = $(this), vUrl = $el.data('url') || self.deleteUrl, vKey = $el.data('key'), + fnBefore, fnSuccess, fnError; + if ($h.isEmpty(vUrl) || vKey === undefined) { + return; + } + var $frame = $el.closest($h.FRAMES), cache = self.previewCache.data, + settings, params, index = $frame.attr('data-fileindex'), config, extraData; + index = parseInt(index.replace('init_', '')); + config = $h.isEmpty(cache.config) && $h.isEmpty(cache.config[index]) ? null : cache.config[index]; + extraData = $h.isEmpty(config) || $h.isEmpty(config.extra) ? deleteExtraData : config.extra; + if (typeof extraData === "function") { + extraData = extraData(); + } + params = {id: $el.attr('id'), key: vKey, extra: extraData}; + fnBefore = function (jqXHR) { + self.ajaxAborted = false; + self._raise('filepredelete', [vKey, jqXHR, extraData]); + if (self._abort()) { + jqXHR.abort(); + } else { + $el.removeClass(errClass); + $h.addCss($frame, 'file-uploading'); + $h.addCss($el, 'disabled ' + origClass); + } + }; + fnSuccess = function (data, textStatus, jqXHR) { + var n, cap; + if (!$h.isEmpty(data) && !$h.isEmpty(data.error)) { + params.jqXHR = jqXHR; + params.response = data; + self._showError(data.error, params, 'filedeleteerror'); + $frame.removeClass('file-uploading'); + $el.removeClass('disabled ' + origClass).addClass(errClass); + resetProgress(); + return; + } + $frame.removeClass('file-uploading').addClass('file-deleted'); + $frame.fadeOut('slow', function () { + index = parseInt(($frame.attr('data-fileindex')).replace('init_', '')); + self.previewCache.unset(index); + self._deleteFileIndex($frame); + n = self.previewCache.count(); + cap = n > 0 ? self._getMsgSelected(n) : ''; + self._setCaption(cap); + self._raise('filedeleted', [vKey, jqXHR, extraData]); + $h.cleanZoomCache($preview.find('#zoom-' + $frame.attr('id'))); + self._clearObjects($frame); + $frame.remove(); + resetProgress(); + }); + }; + fnError = function (jqXHR, textStatus, errorThrown) { + var op = self.ajaxOperations.deleteThumb, errMsg = self._parseError(op, jqXHR, errorThrown); + params.jqXHR = jqXHR; + params.response = {}; + self._showError(errMsg, params, 'filedeleteerror'); + $frame.removeClass('file-uploading'); + $el.removeClass('disabled ' + origClass).addClass(errClass); + resetProgress(); + }; + self._initAjaxSettings(); + self._mergeAjaxCallback('beforeSend', fnBefore, 'delete'); + self._mergeAjaxCallback('success', fnSuccess, 'delete'); + self._mergeAjaxCallback('error', fnError, 'delete'); + settings = $.extend(true, {}, { + url: vUrl, + type: 'POST', + dataType: 'json', + data: $.extend(true, {}, {key: vKey}, extraData) + }, self._ajaxDeleteSettings); + self._handler($el, 'click', function () { + if (!self._validateMinCount()) { + return false; + } + self.ajaxAborted = false; + self._raise('filebeforedelete', [vKey, extraData]); + //noinspection JSUnresolvedVariable,JSHint + if (self.ajaxAborted instanceof Promise) { + self.ajaxAborted.then(function (result) { + if (!result) { + $.ajax(settings); + } + }); + } else { + if (!self.ajaxAborted) { + $.ajax(settings); + } + } + }); + }); + }, + _hideFileIcon: function () { + var self = this; + if (self.overwriteInitial) { + self.$captionContainer.removeClass('icon-visible'); + } + }, + _showFileIcon: function () { + var self = this; + $h.addCss(self.$captionContainer, 'icon-visible'); + }, + _getSize: function (bytes) { + var self = this, size = parseFloat(bytes), i, func = self.fileSizeGetter, sizes, out; + if (!$.isNumeric(bytes) || !$.isNumeric(size)) { + return ''; + } + if (typeof func === 'function') { + out = func(size); + } else { + if (size === 0) { + out = '0.00 B'; + } else { + i = Math.floor(Math.log(size) / Math.log(1024)); + sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; + out = (size / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + sizes[i]; + } + } + return self._getLayoutTemplate('size').replace('{sizeText}', out); + }, + _generatePreviewTemplate: function (cat, data, fname, ftype, previewId, isError, size, frameClass, foot, ind, templ) { + var self = this, caption = self.slug(fname), prevContent, zoomContent = '', styleAttribs = '', + screenW = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth, + config = screenW < 400 ? (self.previewSettingsSmall[cat] || self.defaults.previewSettingsSmall[cat]) : + (self.previewSettings[cat] || self.defaults.previewSettings[cat]), + footer = foot || self._renderFileFooter(caption, size, 'auto', isError), + hasIconSetting = self._getPreviewIcon(fname), typeCss = 'type-default', + forcePrevIcon = hasIconSetting && self.preferIconicPreview, + forceZoomIcon = hasIconSetting && self.preferIconicZoomPreview, getContent; + if (config) { + $.each(config, function (key, val) { + styleAttribs += key + ':' + val + ';'; + }); + } + getContent = function (c, d, zoom, frameCss) { + var id = zoom ? 'zoom-' + previewId : previewId, tmplt = self._getPreviewTemplate(c), + css = (frameClass || '') + ' ' + frameCss; + if (self.frameClass) { + css = self.frameClass + ' ' + css; + } + if (zoom) { + css = css.replace(' ' + $h.SORT_CSS, ''); + } + tmplt = self._parseFilePreviewIcon(tmplt, fname); + if (c === 'text') { + d = $h.htmlEncode(d); + } + if (cat === 'object' && !ftype) { + $.each(self.defaults.fileTypeSettings, function (key, func) { + if (key === 'object' || key === 'other') { + return; + } + if (func(fname, ftype)) { + typeCss = 'type-' + key; + } + }); + } + return tmplt.setTokens({ + 'previewId': id, + 'caption': caption, + 'frameClass': css, + 'type': ftype, + 'fileindex': ind, + 'typeCss': typeCss, + 'footer': footer, + 'data': d, + 'template': templ || cat, + 'style': styleAttribs ? 'style="' + styleAttribs + '"' : '' + }); + }; + ind = ind || previewId.slice(previewId.lastIndexOf('-') + 1); + if (self.fileActionSettings.showZoom) { + zoomContent = getContent((forceZoomIcon ? 'other' : cat), data, true, 'kv-zoom-thumb'); + } + zoomContent = '\n' + self._getLayoutTemplate('zoomCache').replace('{zoomContent}', zoomContent); + prevContent = getContent((forcePrevIcon ? 'other' : cat), data, false, 'kv-preview-thumb'); + return prevContent + zoomContent; + }, + _addToPreview: function ($preview, content) { + var self = this; + return self.reversePreviewOrder ? $preview.prepend(content) : $preview.append(content); + }, + _previewDefault: function (file, previewId, isDisabled) { + var self = this, $preview = self.$preview; + if (!self.showPreview) { + return; + } + var fname = file ? file.name : '', ftype = file ? file.type : '', content, size = file.size || 0, + caption = self.slug(fname), isError = isDisabled === true && !self.isAjaxUpload, + data = $h.objUrl.createObjectURL(file); + self._clearDefaultPreview(); + content = self._generatePreviewTemplate('other', data, fname, ftype, previewId, isError, size); + self._addToPreview($preview, content); + self._setThumbAttr(previewId, caption, size); + if (isDisabled === true && self.isAjaxUpload) { + self._setThumbStatus($('#' + previewId), 'Error'); + } + }, + _previewFile: function (i, file, theFile, previewId, data, fileInfo) { + if (!this.showPreview) { + return; + } + var self = this, fname = file ? file.name : '', ftype = fileInfo.type, caption = fileInfo.name, + cat = self._parseFileType(ftype, fname), types = self.allowedPreviewTypes, content, + mimes = self.allowedPreviewMimeTypes, $preview = self.$preview, fsize = file.size || 0, + chkTypes = types && types.indexOf(cat) >= 0, chkMimes = mimes && mimes.indexOf(ftype) !== -1, + iData = (cat === 'text' || cat === 'html' || cat === 'image') ? theFile.target.result : data; + /** @namespace window.DOMPurify */ + if (cat === 'html' && self.purifyHtml && window.DOMPurify) { + iData = window.DOMPurify.sanitize(iData); + } + if (chkTypes || chkMimes) { + content = self._generatePreviewTemplate(cat, iData, fname, ftype, previewId, false, fsize); + self._clearDefaultPreview(); + self._addToPreview($preview, content); + var $img = $preview.find('#' + previewId + ' img'); + self._validateImageOrientation($img, file, previewId, caption, ftype, fsize, iData); + } else { + self._previewDefault(file, previewId); + } + self._setThumbAttr(previewId, caption, fsize); + self._initSortable(); + }, + _setThumbAttr: function (id, caption, size) { + var self = this, $frame = $('#' + id); + if ($frame.length) { + size = size && size > 0 ? self._getSize(size) : ''; + $frame.data({'caption': caption, 'size': size}); + } + }, + _setInitThumbAttr: function () { + var self = this, data = self.previewCache.data, len = self.previewCache.count(), config, + caption, size, previewId; + if (len === 0) { + return; + } + for (var i = 0; i < len; i++) { + config = data.config[i]; + previewId = self.previewInitId + '-' + 'init_' + i; + caption = $h.ifSet('caption', config, $h.ifSet('filename', config)); + size = $h.ifSet('size', config); + self._setThumbAttr(previewId, caption, size); + } + }, + _slugDefault: function (text) { + // noinspection RegExpRedundantEscape + return $h.isEmpty(text) ? '' : String(text).replace(/[\[\]\/\{}:;#%=\(\)\*\+\?\\\^\$\|<>&"']/g, '_'); + }, + _updateFileDetails: function (numFiles) { + var self = this, $el = self.$element, fileStack = self.getFileStack(), + name = ($h.isIE(9) && $h.findFileName($el.val())) || + ($el[0].files[0] && $el[0].files[0].name) || (fileStack.length && fileStack[0].name) || '', + label = self.slug(name), n = self.isAjaxUpload ? fileStack.length : numFiles, + nFiles = self.previewCache.count() + n, log = n === 1 ? label : self._getMsgSelected(nFiles); + if (self.isError) { + self.$previewContainer.removeClass('file-thumb-loading'); + self.$previewStatus.html(''); + self.$captionContainer.removeClass('icon-visible'); + } else { + self._showFileIcon(); + } + self._setCaption(log, self.isError); + self.$container.removeClass('file-input-new file-input-ajax-new'); + if (arguments.length === 1) { + self._raise('fileselect', [numFiles, label]); + } + if (self.previewCache.count()) { + self._initPreviewActions(); + } + }, + _setThumbStatus: function ($thumb, status) { + var self = this; + if (!self.showPreview) { + return; + } + var icon = 'indicator' + status, msg = icon + 'Title', + css = 'file-preview-' + status.toLowerCase(), + $indicator = $thumb.find('.file-upload-indicator'), + config = self.fileActionSettings; + $thumb.removeClass('file-preview-success file-preview-error file-preview-loading'); + if (status === 'Success') { + $thumb.find('.file-drag-handle').remove(); + } + $indicator.html(config[icon]); + $indicator.attr('title', config[msg]); + $thumb.addClass(css); + if (status === 'Error' && !self.retryErrorUploads) { + $thumb.find('.kv-file-upload').attr('disabled', true); + } + }, + _setProgressCancelled: function () { + var self = this; + self._setProgress(101, self.$progress, self.msgCancelled); + }, + _setProgress: function (p, $el, error) { + var self = this, pct = Math.min(p, 100), out, pctLimit = self.progressUploadThreshold, + t = p <= 100 ? self.progressTemplate : self.progressCompleteTemplate, + template = pct < 100 ? self.progressTemplate : (error ? self.progressErrorTemplate : t); + $el = $el || self.$progress; + if (!$h.isEmpty(template)) { + if (pctLimit && pct > pctLimit && p <= 100) { + out = template.setTokens({'percent': pctLimit, 'status': self.msgUploadThreshold}); + } else { + out = template.setTokens({'percent': pct, 'status': (p > 100 ? self.msgUploadEnd : pct + '%')}); + } + $el.html(out); + if (error) { + $el.find('[role="progressbar"]').html(error); + } + } + }, + _setFileDropZoneTitle: function () { + var self = this, $zone = self.$container.find('.file-drop-zone'), title = self.dropZoneTitle, strFiles; + if (self.isClickable) { + strFiles = $h.isEmpty(self.$element.attr('multiple')) ? self.fileSingle : self.filePlural; + title += self.dropZoneClickTitle.replace('{files}', strFiles); + } + $zone.find('.' + self.dropZoneTitleClass).remove(); + if (!self.showPreview || $zone.length === 0 || self.getFileStack().length > 0 || !self.dropZoneEnabled || + (!self.isAjaxUpload && self.$element.files)) { + return; + } + if ($zone.find($h.FRAMES).length === 0 && $h.isEmpty(self.defaultPreviewContent)) { + $zone.prepend('
    ' + title + '
    '); + } + self.$container.removeClass('file-input-new'); + $h.addCss(self.$container, 'file-input-ajax-new'); + }, + _setAsyncUploadStatus: function (previewId, pct, total) { + var self = this, sum = 0; + self._setProgress(pct, $('#' + previewId).find('.file-thumb-progress')); + self.uploadStatus[previewId] = pct; + $.each(self.uploadStatus, function (key, value) { + sum += value; + }); + self._setProgress(Math.floor(sum / total)); + }, + _validateMinCount: function () { + var self = this, len = self.isAjaxUpload ? self.getFileStack().length : self._inputFileCount(); + if (self.validateInitialCount && self.minFileCount > 0 && self._getFileCount(len - 1) < self.minFileCount) { + self._noFilesError({}); + return false; + } + return true; + }, + _getFileCount: function (fileCount) { + var self = this, addCount = 0; + if (self.validateInitialCount && !self.overwriteInitial) { + addCount = self.previewCache.count(); + fileCount += addCount; + } + return fileCount; + }, + _getFileId: function (file) { + var self = this, custom = self.generateFileId, relativePath; + if (typeof custom === 'function') { + return custom(file, event); + } + if (!file) { + return null; + } + /** @namespace file.webkitRelativePath */ + /** @namespace file.fileName */ + relativePath = String(file.webkitRelativePath || file.fileName || file.name || null); + if (!relativePath) { + return null; + } + return (file.size + '-' + relativePath.replace(/[^0-9a-zA-Z_-]/img, '')); + }, + _getFileName: function (file) { + return file && file.name ? this.slug(file.name) : undefined; + }, + _getFileIds: function (skipNull) { + var self = this; + return self.fileids.filter(function (n) { + return (skipNull ? n !== undefined : n !== undefined && n !== null); + }); + }, + _getFileNames: function (skipNull) { + var self = this; + return self.filenames.filter(function (n) { + return (skipNull ? n !== undefined : n !== undefined && n !== null); + }); + }, + _setPreviewError: function ($thumb, i, val, repeat) { + var self = this; + if (i !== undefined) { + self.updateStack(i, val); + } + if (!self.showPreview) { + return; + } + if (self.removeFromPreviewOnError && !repeat) { + $thumb.remove(); + return; + } else { + self._setThumbStatus($thumb, 'Error'); + } + self._refreshUploadButton($thumb, repeat); + }, + _refreshUploadButton: function ($thumb, repeat) { + var self = this, $btn = $thumb.find('.kv-file-upload'), cfg = self.fileActionSettings, + icon = cfg.uploadIcon, title = cfg.uploadTitle; + if (!$btn.length) { + return; + } + if (repeat) { + icon = cfg.uploadRetryIcon; + title = cfg.uploadRetryTitle; + } + $btn.attr('title', title).html(icon); + }, + _checkDimensions: function (i, chk, $img, $thumb, fname, type, params) { + var self = this, msg, dim, tag = chk === 'Small' ? 'min' : 'max', limit = self[tag + 'Image' + type], + $imgEl, isValid; + if ($h.isEmpty(limit) || !$img.length) { + return; + } + $imgEl = $img[0]; + dim = (type === 'Width') ? $imgEl.naturalWidth || $imgEl.width : $imgEl.naturalHeight || $imgEl.height; + isValid = chk === 'Small' ? dim >= limit : dim <= limit; + if (isValid) { + return; + } + msg = self['msgImage' + type + chk].setTokens({'name': fname, 'size': limit}); + self._showUploadError(msg, params); + self._setPreviewError($thumb, i, null); + }, + _getExifObj: function (iData) { + var self = this, exifObj = null; + try { + exifObj = window.piexif ? window.piexif.load(iData) : null; + } catch (err) { + exifObj = null; + } + if (!exifObj) { + self._log('Error loading the piexif.js library.'); + } + return exifObj; + }, + _validateImageOrientation: function ($img, file, previewId, caption, ftype, fsize, iData) { + var self = this, exifObj, value; + exifObj = $img.length && self.autoOrientImage ? self._getExifObj(iData) : null; + value = exifObj ? exifObj["0th"][piexif.ImageIFD.Orientation] : null; // jshint ignore:line + if (!value) { + self._validateImage(previewId, caption, ftype, fsize, iData, exifObj); + return; + } + $h.setImageOrientation($img, self.$preview.find('#zoom-' + previewId + ' img'), value); + self._raise('fileimageoriented', {'$img': $img, 'file': file}); + self._validateImage(previewId, caption, ftype, fsize, iData, exifObj); + }, + _validateImage: function (previewId, fname, ftype, fsize, iData, exifObj) { + var self = this, $preview = self.$preview, params, w1, w2, $thumb = $preview.find("#" + previewId), + i = $thumb.attr('data-fileindex'), $img = $thumb.find('img'); + fname = fname || 'Untitled'; + $img.one('load', function () { + w1 = $thumb.width(); + w2 = $preview.width(); + if (w1 > w2) { + $img.css('width', '100%'); + } + params = {ind: i, id: previewId}; + self._checkDimensions(i, 'Small', $img, $thumb, fname, 'Width', params); + self._checkDimensions(i, 'Small', $img, $thumb, fname, 'Height', params); + if (!self.resizeImage) { + self._checkDimensions(i, 'Large', $img, $thumb, fname, 'Width', params); + self._checkDimensions(i, 'Large', $img, $thumb, fname, 'Height', params); + } + self._raise('fileimageloaded', [previewId]); + self.loadedImages.push({ + ind: i, + img: $img, + thumb: $thumb, + pid: previewId, + typ: ftype, + siz: fsize, + validated: false, + imgData: iData, + exifObj: exifObj + }); + $thumb.data('exif', exifObj); + self._validateAllImages(); + }).one('error', function () { + self._raise('fileimageloaderror', [previewId]); + }).each(function () { + if (this.complete) { + $(this).trigger('load'); + } else { + if (this.error) { + $(this).trigger('error'); + } + } + }); + }, + _validateAllImages: function () { + var self = this, i, counter = {val: 0}, numImgs = self.loadedImages.length, config, + fsize, minSize = self.resizeIfSizeMoreThan; + if (numImgs !== self.totalImagesCount) { + return; + } + self._raise('fileimagesloaded'); + if (!self.resizeImage) { + return; + } + for (i = 0; i < self.loadedImages.length; i++) { + config = self.loadedImages[i]; + if (config.validated) { + continue; + } + fsize = config.siz; + if (fsize && fsize > minSize * 1000) { + self._getResizedImage(config, counter, numImgs); + } + self.loadedImages[i].validated = true; + } + }, + _getResizedImage: function (config, counter, numImgs) { + var self = this, img = $(config.img)[0], width = img.naturalWidth, height = img.naturalHeight, blob, + ratio = 1, maxWidth = self.maxImageWidth || width, maxHeight = self.maxImageHeight || height, + isValidImage = !!(width && height), chkWidth, chkHeight, canvas = self.imageCanvas, dataURI, + context = self.imageCanvasContext, type = config.typ, pid = config.pid, ind = config.ind, + $thumb = config.thumb, throwError, msg, exifObj = config.exifObj, exifStr; + throwError = function (msg, params, ev) { + if (self.isAjaxUpload) { + self._showUploadError(msg, params, ev); + } else { + self._showError(msg, params, ev); + } + self._setPreviewError($thumb, ind); + }; + if (!self.filestack[ind] || !isValidImage || (width <= maxWidth && height <= maxHeight)) { + if (isValidImage && self.filestack[ind]) { + self._raise('fileimageresized', [pid, ind]); + } + counter.val++; + if (counter.val === numImgs) { + self._raise('fileimagesresized'); + } + if (!isValidImage) { + throwError(self.msgImageResizeError, {id: pid, 'index': ind}, 'fileimageresizeerror'); + return; + } + } + type = type || self.resizeDefaultImageType; + chkWidth = width > maxWidth; + chkHeight = height > maxHeight; + if (self.resizePreference === 'width') { + ratio = chkWidth ? maxWidth / width : (chkHeight ? maxHeight / height : 1); + } else { + ratio = chkHeight ? maxHeight / height : (chkWidth ? maxWidth / width : 1); + } + self._resetCanvas(); + width *= ratio; + height *= ratio; + canvas.width = width; + canvas.height = height; + try { + context.drawImage(img, 0, 0, width, height); + dataURI = canvas.toDataURL(type, self.resizeQuality); + if (exifObj) { + exifStr = window.piexif.dump(exifObj); + dataURI = window.piexif.insert(exifStr, dataURI); + } + blob = $h.dataURI2Blob(dataURI); + self.filestack[ind] = blob; + self._raise('fileimageresized', [pid, ind]); + counter.val++; + if (counter.val === numImgs) { + self._raise('fileimagesresized', [undefined, undefined]); + } + if (!(blob instanceof Blob)) { + throwError(self.msgImageResizeError, {id: pid, 'index': ind}, 'fileimageresizeerror'); + } + } + catch (err) { + counter.val++; + if (counter.val === numImgs) { + self._raise('fileimagesresized', [undefined, undefined]); + } + msg = self.msgImageResizeException.replace('{errors}', err.message); + throwError(msg, {id: pid, 'index': ind}, 'fileimageresizeexception'); + } + }, + _initBrowse: function ($container) { + var self = this, $el = self.$element; + if (self.showBrowse) { + self.$btnFile = $container.find('.btn-file').append($el); + } else { + $el.appendTo($container).attr('tabindex', -1); + $h.addCss($el, 'file-no-browse'); + } + }, + _initClickable: function () { + var self = this, $zone, $tmpZone; + if (!self.isClickable) { + return; + } + $zone = self.$dropZone; + if (!self.isAjaxUpload) { + $tmpZone = self.$preview.find('.file-default-preview'); + if ($tmpZone.length) { + $zone = $tmpZone; + } + } + + $h.addCss($zone, 'clickable'); + $zone.attr('tabindex', -1); + self._handler($zone, 'click', function (e) { + var $tar = $(e.target); + if (!$(self.elErrorContainer + ':visible').length && + (!$tar.parents('.file-preview-thumbnails').length || $tar.parents('.file-default-preview').length)) { + self.$element.data('zoneClicked', true).trigger('click'); + $zone.blur(); + } + }); + }, + _initCaption: function () { + var self = this, cap = self.initialCaption || ''; + if (self.overwriteInitial || $h.isEmpty(cap)) { + self.$caption.val(''); + return false; + } + self._setCaption(cap); + return true; + }, + _setCaption: function (content, isError) { + var self = this, title, out, icon, n, cap, stack = self.getFileStack(); + if (!self.$caption.length) { + return; + } + self.$captionContainer.removeClass('icon-visible'); + if (isError) { + title = $('
    ' + self.msgValidationError + '
    ').text(); + n = stack.length; + if (n) { + cap = n === 1 && stack[0] ? self._getFileNames()[0] : self._getMsgSelected(n); + } else { + cap = self._getMsgSelected(self.msgNo); + } + out = $h.isEmpty(content) ? cap : content; + icon = '' + self.msgValidationErrorIcon + ''; + } else { + if ($h.isEmpty(content)) { + return; + } + title = $('
    ' + content + '
    ').text(); + out = title; + icon = self._getLayoutTemplate('fileIcon'); + } + self.$captionContainer.addClass('icon-visible'); + self.$caption.attr('title', title).val(out); + self.$captionIcon.html(icon); + }, + _createContainer: function () { + var self = this, attribs = {"class": 'file-input file-input-new' + (self.rtl ? ' kv-rtl' : '')}, + $container = $(document.createElement("div")).attr(attribs).html(self._renderMain()); + $container.insertBefore(self.$element); + self._initBrowse($container); + if (self.theme) { + $container.addClass('theme-' + self.theme); + } + return $container; + }, + _refreshContainer: function () { + var self = this, $container = self.$container, $el = self.$element; + $el.insertAfter($container); + $container.html(self._renderMain()); + self._initBrowse($container); + self._validateDisabled(); + }, + _validateDisabled: function () { + var self = this; + self.$caption.attr({readonly: self.isDisabled}); + }, + _renderMain: function () { + var self = this, + dropCss = self.dropZoneEnabled ? ' file-drop-zone' : 'file-drop-disabled', + close = !self.showClose ? '' : self._getLayoutTemplate('close'), + preview = !self.showPreview ? '' : self._getLayoutTemplate('preview') + .setTokens({'class': self.previewClass, 'dropClass': dropCss}), + css = self.isDisabled ? self.captionClass + ' file-caption-disabled' : self.captionClass, + caption = self.captionTemplate.setTokens({'class': css + ' kv-fileinput-caption'}); + return self.mainTemplate.setTokens({ + 'class': self.mainClass + (!self.showBrowse && self.showCaption ? ' no-browse' : ''), + 'preview': preview, + 'close': close, + 'caption': caption, + 'upload': self._renderButton('upload'), + 'remove': self._renderButton('remove'), + 'cancel': self._renderButton('cancel'), + 'browse': self._renderButton('browse') + }); + + }, + _renderButton: function (type) { + var self = this, tmplt = self._getLayoutTemplate('btnDefault'), css = self[type + 'Class'], + title = self[type + 'Title'], icon = self[type + 'Icon'], label = self[type + 'Label'], + status = self.isDisabled ? ' disabled' : '', btnType = 'button'; + switch (type) { + case 'remove': + if (!self.showRemove) { + return ''; + } + break; + case 'cancel': + if (!self.showCancel) { + return ''; + } + css += ' kv-hidden'; + break; + case 'upload': + if (!self.showUpload) { + return ''; + } + if (self.isAjaxUpload && !self.isDisabled) { + tmplt = self._getLayoutTemplate('btnLink').replace('{href}', self.uploadUrl); + } else { + btnType = 'submit'; + } + break; + case 'browse': + if (!self.showBrowse) { + return ''; + } + tmplt = self._getLayoutTemplate('btnBrowse'); + break; + default: + return ''; + } + + css += type === 'browse' ? ' btn-file' : ' fileinput-' + type + ' fileinput-' + type + '-button'; + if (!$h.isEmpty(label)) { + label = ' ' + label + ''; + } + return tmplt.setTokens({ + 'type': btnType, 'css': css, 'title': title, 'status': status, 'icon': icon, 'label': label + }); + }, + _renderThumbProgress: function () { + var self = this; + return '
    ' + + self.progressTemplate.setTokens({'percent': '0', 'status': self.msgUploadBegin}) + + '
    '; + }, + _renderFileFooter: function (caption, size, width, isError) { + var self = this, config = self.fileActionSettings, rem = config.showRemove, drg = config.showDrag, + upl = config.showUpload, zoom = config.showZoom, out, + template = self._getLayoutTemplate('footer'), tInd = self._getLayoutTemplate('indicator'), + ind = isError ? config.indicatorError : config.indicatorNew, + title = isError ? config.indicatorErrorTitle : config.indicatorNewTitle, + indicator = tInd.setTokens({'indicator': ind, 'indicatorTitle': title}); + size = self._getSize(size); + if (self.isAjaxUpload) { + out = template.setTokens({ + 'actions': self._renderFileActions(upl, false, rem, zoom, drg, false, false, false), + 'caption': caption, + 'size': size, + 'width': width, + 'progress': self._renderThumbProgress(), + 'indicator': indicator + }); + } else { + out = template.setTokens({ + 'actions': self._renderFileActions(false, false, false, zoom, drg, false, false, false), + 'caption': caption, + 'size': size, + 'width': width, + 'progress': '', + 'indicator': indicator + }); + } + out = $h.replaceTags(out, self.previewThumbTags); + return out; + }, + _renderFileActions: function (showUpl, showDwn, showDel, showZoom, showDrag, disabled, url, key, isInit, dUrl, dFile) { + if (!showUpl && !showDwn && !showDel && !showZoom && !showDrag) { + return ''; + } + var self = this, vUrl = url === false ? '' : ' data-url="' + url + '"', + vKey = key === false ? '' : ' data-key="' + key + '"', btnDelete = '', btnUpload = '', btnDownload = '', + btnZoom = '', btnDrag = '', css, template = self._getLayoutTemplate('actions'), + config = self.fileActionSettings, + otherButtons = self.otherActionButtons.setTokens({'dataKey': vKey, 'key': key}), + removeClass = disabled ? config.removeClass + ' disabled' : config.removeClass; + if (showDel) { + btnDelete = self._getLayoutTemplate('actionDelete').setTokens({ + 'removeClass': removeClass, + 'removeIcon': config.removeIcon, + 'removeTitle': config.removeTitle, + 'dataUrl': vUrl, + 'dataKey': vKey, + 'key': key + }); + } + if (showUpl) { + btnUpload = self._getLayoutTemplate('actionUpload').setTokens({ + 'uploadClass': config.uploadClass, + 'uploadIcon': config.uploadIcon, + 'uploadTitle': config.uploadTitle + }); + } + if (showDwn) { + btnDownload = self._getLayoutTemplate('actionDownload').setTokens({ + 'downloadClass': config.downloadClass, + 'downloadIcon': config.downloadIcon, + 'downloadTitle': config.downloadTitle, + 'downloadUrl': dUrl || self.initialPreviewDownloadUrl + }); + btnDownload = btnDownload.setTokens({'filename': dFile, 'key': key}); + } + if (showZoom) { + btnZoom = self._getLayoutTemplate('actionZoom').setTokens({ + 'zoomClass': config.zoomClass, + 'zoomIcon': config.zoomIcon, + 'zoomTitle': config.zoomTitle + }); + } + if (showDrag && isInit) { + css = 'drag-handle-init ' + config.dragClass; + btnDrag = self._getLayoutTemplate('actionDrag').setTokens({ + 'dragClass': css, + 'dragTitle': config.dragTitle, + 'dragIcon': config.dragIcon + }); + } + return template.setTokens({ + 'delete': btnDelete, + 'upload': btnUpload, + 'download': btnDownload, + 'zoom': btnZoom, + 'drag': btnDrag, + 'other': otherButtons + }); + }, + _browse: function (e) { + var self = this; + if (e && e.isDefaultPrevented() || !self._raise('filebrowse')) { + return; + } + if (self.isError && !self.isAjaxUpload) { + self.clear(); + } + self.$captionContainer.focus(); + }, + _filterDuplicate: function (file, files, fileIds) { + var self = this, fileId = self._getFileId(file); + + if (fileId && fileIds && fileIds.indexOf(fileId) > -1) { + return; + } + if (!fileIds) { + fileIds = []; + } + files.push(file); + fileIds.push(fileId); + }, + _change: function (e) { + var self = this; + if (self.changeTriggered) { + return; + } + var $el = self.$element, isDragDrop = arguments.length > 1, isAjaxUpload = self.isAjaxUpload, + tfiles = [], files = isDragDrop ? arguments[1] : $el.get(0).files, total, + maxCount = !isAjaxUpload && $h.isEmpty($el.attr('multiple')) ? 1 : self.maxFileCount, + len, ctr = self.filestack.length, isSingleUpload = $h.isEmpty($el.attr('multiple')), + flagSingle = (isSingleUpload && ctr > 0), fileIds = self._getFileIds(), + throwError = function (mesg, file, previewId, index) { + var p1 = $.extend(true, {}, self._getOutData({}, {}, files), {id: previewId, index: index}), + p2 = {id: previewId, index: index, file: file, files: files}; + return isAjaxUpload ? self._showUploadError(mesg, p1) : self._showError(mesg, p2); + }, + maxCountCheck = function (n, m) { + var msg = self.msgFilesTooMany.replace('{m}', m).replace('{n}', n); + self.isError = throwError(msg, null, null, null); + self.$captionContainer.removeClass('icon-visible'); + self._setCaption('', true); + self.$container.removeClass('file-input-new file-input-ajax-new'); + }; + self.reader = null; + self._resetUpload(); + self._hideFileIcon(); + if (self.dropZoneEnabled) { + self.$container.find('.file-drop-zone .' + self.dropZoneTitleClass).remove(); + } + if (isAjaxUpload) { + $.each(files, function (vKey, vFile) { + self._filterDuplicate(vFile, tfiles, fileIds); + }); + } else { + if (e.target && e.target.files === undefined) { + files = e.target.value ? [{name: e.target.value.replace(/^.+\\/, '')}] : []; + } else { + files = e.target.files || {}; + } + tfiles = files; + } + if ($h.isEmpty(tfiles) || tfiles.length === 0) { + if (!isAjaxUpload) { + self.clear(); + } + self._raise('fileselectnone'); + return; + } + self._resetErrors(); + len = tfiles.length; + total = self._getFileCount(isAjaxUpload ? (self.getFileStack().length + len) : len); + if (maxCount > 0 && total > maxCount) { + if (!self.autoReplace || len > maxCount) { + maxCountCheck((self.autoReplace && len > maxCount ? len : total), maxCount); + return; + } + if (total > maxCount) { + self._resetPreviewThumbs(isAjaxUpload); + } + } else { + if (!isAjaxUpload || flagSingle) { + self._resetPreviewThumbs(false); + if (flagSingle) { + self.clearStack(); + } + } else { + if (isAjaxUpload && ctr === 0 && (!self.previewCache.count() || self.overwriteInitial)) { + self._resetPreviewThumbs(true); + } + } + } + if (self.isPreviewable) { + self.readFiles(tfiles); + } else { + self._updateFileDetails(1); + } + }, + _abort: function (params) { + var self = this, data; + if (self.ajaxAborted && typeof self.ajaxAborted === "object" && self.ajaxAborted.message !== undefined) { + data = $.extend(true, {}, self._getOutData(), params); + data.abortData = self.ajaxAborted.data || {}; + data.abortMessage = self.ajaxAborted.message; + self._setProgress(101, self.$progress, self.msgCancelled); + self._showUploadError(self.ajaxAborted.message, data, 'filecustomerror'); + self.cancel(); + return true; + } + return !!self.ajaxAborted; + }, + _resetFileStack: function () { + var self = this, i = 0, newstack = [], newnames = [], newids = []; + self._getThumbs().each(function () { + var $thumb = $(this), ind = $thumb.attr('data-fileindex'), file = self.filestack[ind], + pid = $thumb.attr('id'); + if (ind === '-1' || ind === -1) { + return; + } + if (file !== undefined) { + newstack[i] = file; + newnames[i] = self._getFileName(file); + newids[i] = self._getFileId(file); + $thumb.attr({'id': self.previewInitId + '-' + i, 'data-fileindex': i}); + i++; + } else { + $thumb.attr({'id': 'uploaded-' + $h.uniqId(), 'data-fileindex': '-1'}); + } + self.$preview.find('#zoom-' + pid).attr({ + 'id': 'zoom-' + $thumb.attr('id'), + 'data-fileindex': $thumb.attr('data-fileindex') + }); + }); + self.filestack = newstack; + self.filenames = newnames; + self.fileids = newids; + }, + _isFileSelectionValid: function (cnt) { + var self = this; + cnt = cnt || 0; + if (self.required && !self.getFilesCount()) { + self.$errorContainer.html(''); + self._showUploadError(self.msgFileRequired); + return false; + } + if (self.minFileCount > 0 && self._getFileCount(cnt) < self.minFileCount) { + self._noFilesError({}); + return false; + } + return true; + }, + clearStack: function () { + var self = this; + self.filestack = []; + self.filenames = []; + self.fileids = []; + return self.$element; + }, + updateStack: function (i, file) { + var self = this; + self.filestack[i] = file; + self.filenames[i] = self._getFileName(file); + self.fileids[i] = file && self._getFileId(file) || null; + return self.$element; + }, + addToStack: function (file) { + var self = this; + self.filestack.push(file); + self.filenames.push(self._getFileName(file)); + self.fileids.push(self._getFileId(file)); + return self.$element; + }, + getFileStack: function (skipNull) { + var self = this; + return self.filestack.filter(function (n) { + return (skipNull ? n !== undefined : n !== undefined && n !== null); + }); + }, + getFilesCount: function () { + var self = this, len = self.isAjaxUpload ? self.getFileStack().length : self._inputFileCount(); + return self._getFileCount(len); + }, + readFiles: function (files) { + this.reader = new FileReader(); + var self = this, $el = self.$element, $preview = self.$preview, reader = self.reader, + $container = self.$previewContainer, $status = self.$previewStatus, msgLoading = self.msgLoading, + msgProgress = self.msgProgress, previewInitId = self.previewInitId, numFiles = files.length, + settings = self.fileTypeSettings, ctr = self.filestack.length, readFile, + fileTypes = self.allowedFileTypes, typLen = fileTypes ? fileTypes.length : 0, + fileExt = self.allowedFileExtensions, strExt = $h.isEmpty(fileExt) ? '' : fileExt.join(', '), + maxPreviewSize = self.maxFilePreviewSize && parseFloat(self.maxFilePreviewSize), + canPreview = $preview.length && (!maxPreviewSize || isNaN(maxPreviewSize)), + throwError = function (msg, file, previewId, index) { + var p1 = $.extend(true, {}, self._getOutData({}, {}, files), {id: previewId, index: index}), + p2 = {id: previewId, index: index, file: file, files: files}, $thumb; + self._previewDefault(file, previewId, true); + if (self.isAjaxUpload) { + self.addToStack(undefined); + setTimeout(function () { + readFile(index + 1); + }, 100); + } else { + numFiles = 0; + } + self._initFileActions(); + $thumb = $('#' + previewId); + $thumb.find('.kv-file-upload').hide(); + if (self.removeFromPreviewOnError) { + $thumb.remove(); + } + self.isError = self.isAjaxUpload ? self._showUploadError(msg, p1) : self._showError(msg, p2); + self._updateFileDetails(numFiles); + }; + + self.loadedImages = []; + self.totalImagesCount = 0; + + $.each(files, function (key, file) { + var func = self.fileTypeSettings.image; + if (func && func(file.type)) { + self.totalImagesCount++; + } + }); + readFile = function (i) { + if ($h.isEmpty($el.attr('multiple'))) { + numFiles = 1; + } + if (i >= numFiles) { + if (self.isAjaxUpload && self.filestack.length > 0) { + self._raise('filebatchselected', [self.getFileStack()]); + } else { + self._raise('filebatchselected', [files]); + } + $container.removeClass('file-thumb-loading'); + $status.html(''); + return; + } + var node = ctr + i, previewId = previewInitId + "-" + node, file = files[i], fSizeKB, j, msg, + fnText = settings.text, fnImage = settings.image, fnHtml = settings.html, typ, chk, typ1, typ2, + caption = file && file.name ? self.slug(file.name) : '', fileSize = (file && file.size || 0) / 1000, + fileExtExpr = '', previewData = file ? $h.objUrl.createObjectURL(file) : null, fileCount = 0, + strTypes = '', + func, knownTypes = 0, isText, isHtml, isImage, txtFlag, processFileLoaded = function () { + var msg = msgProgress.setTokens({ + 'index': i + 1, + 'files': numFiles, + 'percent': 50, + 'name': caption + }); + setTimeout(function () { + $status.html(msg); + self._updateFileDetails(numFiles); + readFile(i + 1); + }, 100); + self._raise('fileloaded', [file, previewId, i, reader]); + }; + if (!file) { + return; + } + if (typLen > 0) { + for (j = 0; j < typLen; j++) { + typ1 = fileTypes[j]; + typ2 = self.msgFileTypes[typ1] || typ1; + strTypes += j === 0 ? typ2 : ', ' + typ2; + } + } + if (caption === false) { + readFile(i + 1); + return; + } + if (caption.length === 0) { + msg = self.msgInvalidFileName.replace('{name}', $h.htmlEncode(file.name, '[unknown]')); + throwError(msg, file, previewId, i); + return; + } + if (!$h.isEmpty(fileExt)) { + fileExtExpr = new RegExp('\\.(' + fileExt.join('|') + ')$', 'i'); + } + fSizeKB = fileSize.toFixed(2); + if (self.maxFileSize > 0 && fileSize > self.maxFileSize) { + msg = self.msgSizeTooLarge.setTokens({ + 'name': caption, + 'size': fSizeKB, + 'maxSize': self.maxFileSize + }); + throwError(msg, file, previewId, i); + return; + } + if (self.minFileSize !== null && fileSize <= $h.getNum(self.minFileSize)) { + msg = self.msgSizeTooSmall.setTokens({ + 'name': caption, + 'size': fSizeKB, + 'minSize': self.minFileSize + }); + throwError(msg, file, previewId, i); + return; + } + if (!$h.isEmpty(fileTypes) && $h.isArray(fileTypes)) { + for (j = 0; j < fileTypes.length; j += 1) { + typ = fileTypes[j]; + func = settings[typ]; + fileCount += !func || (typeof func !== 'function') ? 0 : (func(file.type, file.name) ? 1 : 0); + } + if (fileCount === 0) { + msg = self.msgInvalidFileType.setTokens({'name': caption, 'types': strTypes}); + throwError(msg, file, previewId, i); + return; + } + } + if (fileCount === 0 && !$h.isEmpty(fileExt) && $h.isArray(fileExt) && !$h.isEmpty(fileExtExpr)) { + chk = $h.compare(caption, fileExtExpr); + fileCount += $h.isEmpty(chk) ? 0 : chk.length; + if (fileCount === 0) { + msg = self.msgInvalidFileExtension.setTokens({'name': caption, 'extensions': strExt}); + throwError(msg, file, previewId, i); + return; + } + } + if (!self.showPreview) { + if (self.isAjaxUpload) { + self.addToStack(file); + } + setTimeout(function () { + readFile(i + 1); + self._updateFileDetails(numFiles); + }, 100); + self._raise('fileloaded', [file, previewId, i, reader]); + return; + } + if (!canPreview && fileSize > maxPreviewSize) { + self.addToStack(file); + $container.addClass('file-thumb-loading'); + self._previewDefault(file, previewId); + self._initFileActions(); + self._updateFileDetails(numFiles); + readFile(i + 1); + return; + } + if ($preview.length && FileReader !== undefined) { + isText = fnText(file.type, caption); + isHtml = fnHtml(file.type, caption); + isImage = fnImage(file.type, caption); + $status.html(msgLoading.replace('{index}', i + 1).replace('{files}', numFiles)); + $container.addClass('file-thumb-loading'); + reader.onerror = function (evt) { + self._errorHandler(evt, caption); + }; + reader.onload = function (theFile) { + var hex, fileInfo, uint, byte, bytes = [], contents, mime, readTextImage = function (textFlag) { + var newReader = new FileReader(); + newReader.onerror = function (theFileNew) { + self._errorHandler(theFileNew, caption); + }; + newReader.onload = function (theFileNew) { + self._previewFile(i, file, theFileNew, previewId, previewData, fileInfo); + self._initFileActions(); + processFileLoaded(); + }; + if (textFlag) { + newReader.readAsText(file, self.textEncoding); + } else { + newReader.readAsDataURL(file); + } + }; + fileInfo = {'name': caption, 'type': file.type}; + $.each(settings, function (key, func) { + if (key !== 'object' && key !== 'other' && func(file.type, caption)) { + knownTypes++; + } + }); + if (knownTypes === 0) {// auto detect mime types from content if no known file types detected + uint = new Uint8Array(theFile.target.result); + for (j = 0; j < uint.length; j++) { + byte = uint[j].toString(16); + bytes.push(byte); + } + hex = bytes.join('').toLowerCase().substring(0, 8); + mime = $h.getMimeType(hex, '', ''); + if ($h.isEmpty(mime)) { // look for ascii text content + contents = $h.arrayBuffer2String(reader.result); + mime = $h.isSvg(contents) ? 'image/svg+xml' : $h.getMimeType(hex, contents, file.type); + } + fileInfo = {'name': caption, 'type': mime}; + isText = fnText(mime, ''); + isHtml = fnHtml(mime, ''); + isImage = fnImage(mime, ''); + txtFlag = isText || isHtml; + if (txtFlag || isImage) { + readTextImage(txtFlag); + return; + } + } + self._previewFile(i, file, theFile, previewId, previewData, fileInfo); + self._initFileActions(); + processFileLoaded(); + }; + reader.onprogress = function (data) { + if (data.lengthComputable) { + var fact = (data.loaded / data.total) * 100, progress = Math.ceil(fact); + msg = msgProgress.setTokens({ + 'index': i + 1, + 'files': numFiles, + 'percent': progress, + 'name': caption + }); + setTimeout(function () { + $status.html(msg); + }, 100); + } + }; + + if (isText || isHtml) { + reader.readAsText(file, self.textEncoding); + } else { + if (isImage) { + reader.readAsDataURL(file); + } else { + reader.readAsArrayBuffer(file); + } + } + } else { + self._previewDefault(file, previewId); + setTimeout(function () { + readFile(i + 1); + self._updateFileDetails(numFiles); + }, 100); + self._raise('fileloaded', [file, previewId, i, reader]); + } + self.addToStack(file); + }; + + readFile(0); + self._updateFileDetails(numFiles, false); + }, + lock: function () { + var self = this; + self._resetErrors(); + self.disable(); + if (self.showRemove) { + self.$container.find('.fileinput-remove').hide(); + } + if (self.showCancel) { + self.$container.find('.fileinput-cancel').show(); + } + self._raise('filelock', [self.filestack, self._getExtraData()]); + return self.$element; + }, + unlock: function (reset) { + var self = this; + if (reset === undefined) { + reset = true; + } + self.enable(); + if (self.showCancel) { + self.$container.find('.fileinput-cancel').hide(); + } + if (self.showRemove) { + self.$container.find('.fileinput-remove').show(); + } + if (reset) { + self._resetFileStack(); + } + self._raise('fileunlock', [self.filestack, self._getExtraData()]); + return self.$element; + }, + cancel: function () { + var self = this, xhr = self.ajaxRequests, len = xhr.length, i; + if (len > 0) { + for (i = 0; i < len; i += 1) { + self.cancelling = true; + xhr[i].abort(); + } + } + self._setProgressCancelled(); + self._getThumbs().each(function () { + var $thumb = $(this), ind = $thumb.attr('data-fileindex'); + $thumb.removeClass('file-uploading'); + if (self.filestack[ind] !== undefined) { + $thumb.find('.kv-file-upload').removeClass('disabled').removeAttr('disabled'); + $thumb.find('.kv-file-remove').removeClass('disabled').removeAttr('disabled'); + } + self.unlock(); + }); + return self.$element; + }, + clear: function () { + var self = this, cap; + if (!self._raise('fileclear')) { + return; + } + self.$btnUpload.removeAttr('disabled'); + self._getThumbs().find('video,audio,img').each(function () { + $h.cleanMemory($(this)); + }); + self._clearFileInput(); + self._resetUpload(); + self.clearStack(); + self._resetErrors(true); + if (self._hasInitialPreview()) { + self._showFileIcon(); + self._resetPreview(); + self._initPreviewActions(); + self.$container.removeClass('file-input-new'); + } else { + self._getThumbs().each(function () { + self._clearObjects($(this)); + }); + if (self.isAjaxUpload) { + self.previewCache.data = {}; + } + self.$preview.html(''); + cap = (!self.overwriteInitial && self.initialCaption.length > 0) ? self.initialCaption : ''; + self.$caption.attr('title', '').val(cap); + $h.addCss(self.$container, 'file-input-new'); + self._validateDefaultPreview(); + } + if (self.$container.find($h.FRAMES).length === 0) { + if (!self._initCaption()) { + self.$captionContainer.removeClass('icon-visible'); + } + } + self._hideFileIcon(); + self._raise('filecleared'); + self.$captionContainer.focus(); + self._setFileDropZoneTitle(); + return self.$element; + }, + reset: function () { + var self = this; + if (!self._raise('filereset')) { + return; + } + self._resetPreview(); + self.$container.find('.fileinput-filename').text(''); + $h.addCss(self.$container, 'file-input-new'); + if (self.getFrames().length || self.dropZoneEnabled) { + self.$container.removeClass('file-input-new'); + } + self.clearStack(); + self.formdata = {}; + self._setFileDropZoneTitle(); + return self.$element; + }, + disable: function () { + var self = this; + self.isDisabled = true; + self._raise('filedisabled'); + self.$element.attr('disabled', 'disabled'); + self.$container.find(".kv-fileinput-caption").addClass("file-caption-disabled"); + self.$container.find(".fileinput-remove, .fileinput-upload, .file-preview-frame button") + .attr("disabled", true); + $h.addCss(self.$container.find('.btn-file'), 'disabled'); + self._initDragDrop(); + return self.$element; + }, + enable: function () { + var self = this; + self.isDisabled = false; + self._raise('fileenabled'); + self.$element.removeAttr('disabled'); + self.$container.find(".kv-fileinput-caption").removeClass("file-caption-disabled"); + self.$container.find(".fileinput-remove, .fileinput-upload, .file-preview-frame button") + .removeAttr("disabled"); + self.$container.find('.btn-file').removeClass('disabled'); + self._initDragDrop(); + return self.$element; + }, + upload: function () { + var self = this, totLen = self.getFileStack().length, i, outData, len, + hasExtraData = !$.isEmptyObject(self._getExtraData()); + if (!self.isAjaxUpload || self.isDisabled || !self._isFileSelectionValid(totLen)) { + return; + } + self._resetUpload(); + if (totLen === 0 && !hasExtraData) { + self._showUploadError(self.msgUploadEmpty); + return; + } + self.$progress.show(); + self.uploadCount = 0; + self.uploadStatus = {}; + self.uploadLog = []; + self.lock(); + self._setProgress(2); + if (totLen === 0 && hasExtraData) { + self._uploadExtraOnly(); + return; + } + len = self.filestack.length; + self.hasInitData = false; + if (self.uploadAsync) { + outData = self._getOutData(); + self._raise('filebatchpreupload', [outData]); + self.fileBatchCompleted = false; + self.uploadCache = {content: [], config: [], tags: [], append: true}; + self.uploadAsyncCount = self.getFileStack().length; + for (i = 0; i < len; i++) { + self.uploadCache.content[i] = null; + self.uploadCache.config[i] = null; + self.uploadCache.tags[i] = null; + } + self.$preview.find('.file-preview-initial').removeClass($h.SORT_CSS); + self._initSortable(); + self.cacheInitialPreview = self.getPreview(); + + for (i = 0; i < len; i++) { + if (self.filestack[i]) { + self._uploadSingle(i, true); + } + } + return; + } + self._uploadBatch(); + return self.$element; + }, + destroy: function () { + var self = this, $form = self.$form, $cont = self.$container, $el = self.$element, ns = self.namespace; + $(document).off(ns); + $(window).off(ns); + if ($form && $form.length) { + $form.off(ns); + } + if (self.isAjaxUpload) { + self._clearFileInput(); + } + self._cleanup(); + self._initPreviewCache(); + $el.insertBefore($cont).off(ns).removeData(); + $cont.off().remove(); + return $el; + }, + refresh: function (options) { + var self = this, $el = self.$element; + if (typeof options !== 'object' || $h.isEmpty(options)) { + options = self.options; + } else { + options = $.extend(true, {}, self.options, options); + } + self._init(options, true); + self._listen(); + return $el; + }, + zoom: function (frameId) { + var self = this, $frame = self._getFrame(frameId), $modal = self.$modal; + if (!$frame) { + return; + } + $h.initModal($modal); + $modal.html(self._getModalContent()); + self._setZoomContent($frame); + $modal.modal('show'); + self._initZoomButtons(); + }, + getExif: function (frameId) { + var self = this, $frame = self._getFrame(frameId); + return $frame && $frame.data('exif') || null; + }, + getFrames: function (cssFilter) { + var self = this, $frames; + cssFilter = cssFilter || ''; + $frames = self.$preview.find($h.FRAMES + cssFilter); + if (self.reversePreviewOrder) { + $frames = $($frames.get().reverse()); + } + return $frames; + }, + getPreview: function () { + var self = this; + return { + content: self.initialPreview, + config: self.initialPreviewConfig, + tags: self.initialPreviewThumbTags + }; + } + }; + + $.fn.fileinput = function (option) { + if (!$h.hasFileAPISupport() && !$h.isIE(9)) { + return; + } + var args = Array.apply(null, arguments), retvals = []; + args.shift(); + this.each(function () { + var self = $(this), data = self.data('fileinput'), options = typeof option === 'object' && option, + theme = options.theme || self.data('theme'), l = {}, t = {}, + lang = options.language || self.data('language') || $.fn.fileinput.defaults.language || 'en', opt; + if (!data) { + if (theme) { + t = $.fn.fileinputThemes[theme] || {}; + } + if (lang !== 'en' && !$h.isEmpty($.fn.fileinputLocales[lang])) { + l = $.fn.fileinputLocales[lang] || {}; + } + opt = $.extend(true, {}, $.fn.fileinput.defaults, t, $.fn.fileinputLocales.en, l, options, self.data()); + data = new FileInput(this, opt); + self.data('fileinput', data); + } + + if (typeof option === 'string') { + retvals.push(data[option].apply(data, args)); + } + }); + switch (retvals.length) { + case 0: + return this; + case 1: + return retvals[0]; + default: + return retvals; + } + }; + + $.fn.fileinput.defaults = { + language: 'en', + showCaption: true, + showBrowse: true, + showPreview: true, + showRemove: true, + showUpload: true, + showCancel: true, + showClose: true, + showUploadedThumbs: true, + browseOnZoneClick: false, + autoReplace: false, + autoOrientImage: false, // if `true` applicable for JPEG images only + required: false, + rtl: false, + hideThumbnailContent: false, + generateFileId: null, + previewClass: '', + captionClass: '', + frameClass: 'krajee-default', + mainClass: 'file-caption-main', + mainTemplate: null, + purifyHtml: true, + fileSizeGetter: null, + initialCaption: '', + initialPreview: [], + initialPreviewDelimiter: '*$$*', + initialPreviewAsData: false, + initialPreviewFileType: 'image', + initialPreviewConfig: [], + initialPreviewThumbTags: [], + previewThumbTags: {}, + initialPreviewShowDelete: true, + initialPreviewDownloadUrl: '', + removeFromPreviewOnError: false, + deleteUrl: '', + deleteExtraData: {}, + overwriteInitial: true, + previewZoomButtonIcons: { + prev: '', + next: '', + toggleheader: '', + fullscreen: '', + borderless: '', + close: '' + }, + previewZoomButtonClasses: { + prev: 'btn btn-navigate', + next: 'btn btn-navigate', + toggleheader: 'btn btn-sm btn-kv btn-default btn-outline-secondary', + fullscreen: 'btn btn-sm btn-kv btn-default btn-outline-secondary', + borderless: 'btn btn-sm btn-kv btn-default btn-outline-secondary', + close: 'btn btn-sm btn-kv btn-default btn-outline-secondary' + }, + previewTemplates: {}, + previewContentTemplates: {}, + preferIconicPreview: false, + preferIconicZoomPreview: false, + allowedPreviewTypes: undefined, + allowedPreviewMimeTypes: null, + allowedFileTypes: null, + allowedFileExtensions: null, + defaultPreviewContent: null, + customLayoutTags: {}, + customPreviewTags: {}, + previewFileIcon: '', + previewFileIconClass: 'file-other-icon', + previewFileIconSettings: {}, + previewFileExtSettings: {}, + buttonLabelClass: 'hidden-xs', + browseIcon: ' ', + browseClass: 'btn btn-primary', + removeIcon: '', + removeClass: 'btn btn-default btn-secondary', + cancelIcon: '', + cancelClass: 'btn btn-default btn-secondary', + uploadIcon: '', + uploadClass: 'btn btn-default btn-secondary', + uploadUrl: null, + uploadUrlThumb: null, + uploadAsync: true, + uploadExtraData: {}, + zoomModalHeight: 480, + minImageWidth: null, + minImageHeight: null, + maxImageWidth: null, + maxImageHeight: null, + resizeImage: false, + resizePreference: 'width', + resizeQuality: 0.92, + resizeDefaultImageType: 'image/jpeg', + resizeIfSizeMoreThan: 0, // in KB + minFileSize: 0, + maxFileSize: 0, + maxFilePreviewSize: 25600, // 25 MB + minFileCount: 0, + maxFileCount: 0, + validateInitialCount: false, + msgValidationErrorClass: 'text-danger', + msgValidationErrorIcon: ' ', + msgErrorClass: 'file-error-message', + progressThumbClass: "progress-bar bg-success progress-bar-success progress-bar-striped active", + progressClass: "progress-bar bg-success progress-bar-success progress-bar-striped active", + progressCompleteClass: "progress-bar bg-success progress-bar-success", + progressErrorClass: "progress-bar bg-danger progress-bar-danger", + progressUploadThreshold: 99, + previewFileType: 'image', + elCaptionContainer: null, + elCaptionText: null, + elPreviewContainer: null, + elPreviewImage: null, + elPreviewStatus: null, + elErrorContainer: null, + errorCloseButton: $h.closeButton('kv-error-close'), + slugCallback: null, + dropZoneEnabled: true, + dropZoneTitleClass: 'file-drop-zone-title', + fileActionSettings: {}, + otherActionButtons: '', + textEncoding: 'UTF-8', + ajaxSettings: {}, + ajaxDeleteSettings: {}, + showAjaxErrorDetails: true, + mergeAjaxCallbacks: false, + mergeAjaxDeleteCallbacks: false, + retryErrorUploads: true, + reversePreviewOrder: false + }; + + // noinspection HtmlUnknownAttribute + $.fn.fileinputLocales.en = { + fileSingle: 'file', + filePlural: 'files', + browseLabel: 'Browse …', + removeLabel: 'Remove', + removeTitle: 'Clear selected files', + cancelLabel: 'Cancel', + cancelTitle: 'Abort ongoing upload', + uploadLabel: 'Upload', + uploadTitle: 'Upload selected files', + msgNo: 'No', + msgNoFilesSelected: 'No files selected', + msgCancelled: 'Cancelled', + msgPlaceholder: 'Select {files}...', + msgZoomModalHeading: 'Detailed Preview', + msgFileRequired: 'You must select a file to upload.', + msgSizeTooSmall: 'File "{name}" ({size} KB) is too small and must be larger than {minSize} KB.', + msgSizeTooLarge: 'File "{name}" ({size} KB) exceeds maximum allowed upload size of {maxSize} KB.', + msgFilesTooLess: 'You must select at least {n} {files} to upload.', + msgFilesTooMany: 'Number of files selected for upload ({n}) exceeds maximum allowed limit of {m}.', + msgFileNotFound: 'File "{name}" not found!', + msgFileSecured: 'Security restrictions prevent reading the file "{name}".', + msgFileNotReadable: 'File "{name}" is not readable.', + msgFilePreviewAborted: 'File preview aborted for "{name}".', + msgFilePreviewError: 'An error occurred while reading the file "{name}".', + msgInvalidFileName: 'Invalid or unsupported characters in file name "{name}".', + msgInvalidFileType: 'Invalid type for file "{name}". Only "{types}" files are supported.', + msgInvalidFileExtension: 'Invalid extension for file "{name}". Only "{extensions}" files are supported.', + msgFileTypes: { + 'image': 'image', + 'html': 'HTML', + 'text': 'text', + 'video': 'video', + 'audio': 'audio', + 'flash': 'flash', + 'pdf': 'PDF', + 'object': 'object' + }, + msgUploadAborted: 'The file upload was aborted', + msgUploadThreshold: 'Processing...', + msgUploadBegin: 'Initializing...', + msgUploadEnd: 'Done', + msgUploadEmpty: 'No valid data available for upload.', + msgUploadError: 'Error', + msgValidationError: 'Validation Error', + msgLoading: 'Loading file {index} of {files} …', + msgProgress: 'Loading file {index} of {files} - {name} - {percent}% completed.', + msgSelected: '{n} {files} selected', + msgFoldersNotAllowed: 'Drag & drop files only! {n} folder(s) dropped were skipped.', + msgImageWidthSmall: 'Width of image file "{name}" must be at least {size} px.', + msgImageHeightSmall: 'Height of image file "{name}" must be at least {size} px.', + msgImageWidthLarge: 'Width of image file "{name}" cannot exceed {size} px.', + msgImageHeightLarge: 'Height of image file "{name}" cannot exceed {size} px.', + msgImageResizeError: 'Could not get the image dimensions to resize.', + msgImageResizeException: 'Error while resizing the image.
    {errors}
    ', + msgAjaxError: 'Something went wrong with the {operation} operation. Please try again later!', + msgAjaxProgressError: '{operation} failed', + ajaxOperations: { + deleteThumb: 'file delete', + uploadThumb: 'file upload', + uploadBatch: 'batch file upload', + uploadExtra: 'form data upload' + }, + dropZoneTitle: 'Drag & drop files here …', + dropZoneClickTitle: '
    (or click to select {files})', + previewZoomButtonTitles: { + prev: 'View previous file', + next: 'View next file', + toggleheader: 'Toggle header', + fullscreen: 'Toggle full screen', + borderless: 'Toggle borderless mode', + close: 'Close detailed preview' + }, + usePdfRenderer: function () { + return !!navigator.userAgent.match(/(iPod|iPhone|iPad|Android)/i); + }, + pdfRendererUrl: '', + pdfRendererTemplate: '' + }; + + $.fn.fileinput.Constructor = FileInput; + + /** + * Convert automatically file inputs with class 'file' into a bootstrap fileinput control. + */ + $(document).ready(function () { + var $input = $('input.file[type=file]'); + if ($input.length) { + $input.fileinput(); + } + }); +})); \ No newline at end of file diff --git a/static/plugins/bootstrap-fileinput/js/fileinput.min.js b/static/plugins/bootstrap-fileinput/js/fileinput.min.js new file mode 100644 index 00000000..6632e562 --- /dev/null +++ b/static/plugins/bootstrap-fileinput/js/fileinput.min.js @@ -0,0 +1,11 @@ +/*! + * bootstrap-fileinput v4.5.1 + * http://plugins.krajee.com/file-input + * + * Author: Kartik Visweswaran + * Copyright: 2014 - 2018, Kartik Visweswaran, Krajee.com + * + * Licensed under the BSD 3-Clause + * https://github.com/kartik-v/bootstrap-fileinput/blob/master/LICENSE.md + */ +!function(e){"use strict";"function"==typeof define&&define.amd?define(["jquery"],e):"object"==typeof module&&module.exports?module.exports=e(require("jquery")):e(window.jQuery)}(function(e){"use strict";var t,i;e.fn.fileinputLocales={},e.fn.fileinputThemes={},String.prototype.setTokens=function(e){var t,i,a=this.toString();for(t in e)e.hasOwnProperty(t)&&(i=new RegExp("{"+t+"}","g"),a=a.replace(i,e[t]));return a},t={FRAMES:".kv-preview-thumb",SORT_CSS:"file-sortable",OBJECT_PARAMS:'\n\n\n\n\n\n',DEFAULT_PREVIEW:'
    \n{previewFileIcon}\n
    ',MODAL_ID:"kvFileinputModal",MODAL_EVENTS:["show","shown","hide","hidden","loaded"],objUrl:window.URL||window.webkitURL,compare:function(e,t,i){return void 0!==e&&(i?e===t:e.match(t))},isIE:function(e){if("Microsoft Internet Explorer"!==navigator.appName)return!1;if(10===e)return new RegExp("msie\\s"+e,"i").test(navigator.userAgent);var t,i=document.createElement("div");return i.innerHTML="\x3c!--[if IE "+e+"]> 0&&e[0].webkitGetAsEntry())for(t=0;t=0?atob(e.split(",")[1]):decodeURIComponent(e.split(",")[1]),a=new ArrayBuffer(i.length),r=new Uint8Array(a),n=0;n>4){case 0:case 1:case 2:case 3:case 4:case 5:case 6:case 7:s+=String.fromCharCode(i);break;case 12:case 13:a=n[o++],s+=String.fromCharCode((31&i)<<6|63&a);break;case 14:a=n[o++],r=n[o++],s+=String.fromCharCode((15&i)<<12|(63&a)<<6|(63&r)<<0)}return s},isHtml:function(e){var t=document.createElement("div");t.innerHTML=e;for(var i=t.childNodes,a=i.length;a--;)if(1===i[a].nodeType)return!0;return!1},isSvg:function(e){return e.match(/^\s*<\?xml/i)&&(e.match(//g,">").replace(/"/g,""").replace(/'/g,"'")},replaceTags:function(t,i){var a=t;return i?(e.each(i,function(e,t){"function"==typeof t&&(t=t()),a=a.split(e).join(t)}),a):a},cleanMemory:function(e){var i=e.is("img")?e.attr("src"):e.find("source").attr("src");t.objUrl.revokeObjectURL(i)},findFileName:function(e){var t=e.lastIndexOf("/");return-1===t&&(t=e.lastIndexOf("\\")),e.split(e.substring(t,t+1)).pop()},checkFullScreen:function(){return document.fullscreenElement||document.mozFullScreenElement||document.webkitFullscreenElement||document.msFullscreenElement},toggleFullScreen:function(e){var i=document,a=i.documentElement;a&&e&&!t.checkFullScreen()?a.requestFullscreen?a.requestFullscreen():a.msRequestFullscreen?a.msRequestFullscreen():a.mozRequestFullScreen?a.mozRequestFullScreen():a.webkitRequestFullscreen&&a.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT):i.exitFullscreen?i.exitFullscreen():i.msExitFullscreen?i.msExitFullscreen():i.mozCancelFullScreen?i.mozCancelFullScreen():i.webkitExitFullscreen&&i.webkitExitFullscreen()},moveArray:function(t,i,a,r){var n=e.extend(!0,[],t);if(r&&n.reverse(),a>=n.length)for(var s=a-n.length;1+s--;)n.push(void 0);return n.splice(a,0,n.splice(i,1)[0]),r&&n.reverse(),n},cleanZoomCache:function(e){var t=e.closest(".kv-zoom-cache-theme");t.length||(t=e.closest(".kv-zoom-cache")),t.remove()},closeButton:function(e){return''},getRotation:function(e){switch(e){case 2:return"rotateY(180deg)";case 3:return"rotate(180deg)";case 4:return"rotate(180deg) rotateY(180deg)";case 5:return"rotate(270deg) rotateY(180deg)";case 6:return"rotate(90deg)";case 7:return"rotate(90deg) rotateY(180deg)";case 8:return"rotate(270deg)";default:return""}},setTransform:function(e,t){e&&(e.style.transform=t,e.style.webkitTransform=t,e.style["-moz-transform"]=t,e.style["-ms-transform"]=t,e.style["-o-transform"]=t)},setImageOrientation:function(e,i,a){if(e&&e.length){var r="load.fileinputimageorient";e.off(r).on(r,function(){var r=e.get(0),n=i&&i.length?i.get(0):null,s=r.offsetHeight,o=r.offsetWidth,l=t.getRotation(a);if(e.data("orientation",a),n&&i.data("orientation",a),a<5)return t.setTransform(r,l),void t.setTransform(n,l);var d=Math.atan(o/s),c=Math.sqrt(Math.pow(s,2)+Math.pow(o,2)),h=c?s/Math.cos(Math.PI/2+d)/c:1,p=" scale("+Math.abs(h)+")";t.setTransform(r,l+p),t.setTransform(n,l+p)})}}},(i=function(i,a){this.$element=e(i),this.$parent=this.$element.parent(),this._validate()&&(this.isPreviewable=t.hasFileAPISupport(),this.isIE9=t.isIE(9),this.isIE10=t.isIE(10),(this.isPreviewable||this.isIE9)&&(this._init(a),this._listen()),this.$element.removeClass("file-loading"))}).prototype={constructor:i,_cleanup:function(){this.reader=null,this.formdata={},this.uploadCount=0,this.uploadStatus={},this.uploadLog=[],this.uploadAsyncCount=0,this.loadedImages=[],this.totalImagesCount=0,this.ajaxRequests=[],this.clearStack(),this.fileBatchCompleted=!0,this.isPreviewable||(this.showPreview=!1),this.isError=!1,this.ajaxAborted=!1,this.cancelling=!1},_init:function(i,a){var r,n,s,o,l=this,d=l.$element;l.options=i,e.each(i,function(e,i){switch(e){case"minFileCount":case"maxFileCount":case"minFileSize":case"maxFileSize":case"maxFilePreviewSize":case"resizeImageQuality":case"resizeIfSizeMoreThan":case"progressUploadThreshold":case"initialPreviewCount":case"zoomModalHeight":case"minImageHeight":case"maxImageHeight":case"minImageWidth":case"maxImageWidth":l[e]=t.getNum(i);break;default:l[e]=i}}),l.rtl&&(o=l.previewZoomButtonIcons.prev,l.previewZoomButtonIcons.prev=l.previewZoomButtonIcons.next,l.previewZoomButtonIcons.next=o),a||l._cleanup(),l.$form=d.closest("form"),l._initTemplateDefaults(),l.uploadFileAttr=t.isEmpty(d.attr("name"))?"file_data":d.attr("name"),s=l._getLayoutTemplate("progress"),l.progressTemplate=s.replace("{class}",l.progressClass),l.progressCompleteTemplate=s.replace("{class}",l.progressCompleteClass),l.progressErrorTemplate=s.replace("{class}",l.progressErrorClass),l.isDisabled=d.attr("disabled")||d.attr("readonly"),l.isDisabled&&d.attr("disabled",!0),l.isClickable=l.browseOnZoneClick&&l.showPreview&&(l.dropZoneEnabled||!t.isEmpty(l.defaultPreviewContent)),l.isAjaxUpload=t.hasFileUploadSupport()&&!t.isEmpty(l.uploadUrl),l.dropZoneEnabled=t.hasDragDropSupport()&&l.dropZoneEnabled,l.isAjaxUpload||(l.dropZoneEnabled=l.dropZoneEnabled&&t.canAssignFilesToInput()),l.slug="function"==typeof i.slugCallback?i.slugCallback:l._slugDefault,l.mainTemplate=l.showCaption?l._getLayoutTemplate("main1"):l._getLayoutTemplate("main2"),l.captionTemplate=l._getLayoutTemplate("caption"),l.previewGenericTemplate=l._getPreviewTemplate("generic"),!l.imageCanvas&&l.resizeImage&&(l.maxImageWidth||l.maxImageHeight)&&(l.imageCanvas=document.createElement("canvas"),l.imageCanvasContext=l.imageCanvas.getContext("2d")),t.isEmpty(d.attr("id"))&&d.attr("id",t.uniqId()),l.namespace=".fileinput_"+d.attr("id").replace(/-/g,"_"),void 0===l.$container?l.$container=l._createContainer():l._refreshContainer(),n=l.$container,l.$dropZone=n.find(".file-drop-zone"),l.$progress=n.find(".kv-upload-progress"),l.$btnUpload=n.find(".fileinput-upload"),l.$captionContainer=t.getElement(i,"elCaptionContainer",n.find(".file-caption")),l.$caption=t.getElement(i,"elCaptionText",n.find(".file-caption-name")),t.isEmpty(l.msgPlaceholder)||(r=d.attr("multiple")?l.filePlural:l.fileSingle,l.$caption.attr("placeholder",l.msgPlaceholder.replace("{files}",r))),l.$captionIcon=l.$captionContainer.find(".file-caption-icon"),l.$previewContainer=t.getElement(i,"elPreviewContainer",n.find(".file-preview")),l.$preview=t.getElement(i,"elPreviewImage",n.find(".file-preview-thumbnails")),l.$previewStatus=t.getElement(i,"elPreviewStatus",n.find(".file-preview-status")),l.$errorContainer=t.getElement(i,"elErrorContainer",l.$previewContainer.find(".kv-fileinput-error")),l._validateDisabled(),t.isEmpty(l.msgErrorClass)||t.addCss(l.$errorContainer,l.msgErrorClass),a?l._errorsExist()||l.$errorContainer.hide():(l.$errorContainer.hide(),l.previewInitId="preview-"+t.uniqId(),l._initPreviewCache(),l._initPreview(!0),l._initPreviewActions(),l.$parent.hasClass("file-loading")&&(l.$container.insertBefore(l.$parent),l.$parent.remove())),l._setFileDropZoneTitle(),d.attr("disabled")&&l.disable(),l._initZoom(),l.hideThumbnailContent&&t.addCss(l.$preview,"hide-content")},_initTemplateDefaults:function(){var i,a,r,n,s,o,l,d,c,h=this;i=t.closeButton("fileinput-remove"),a='',r='
    \n",s='\x3c!--suppress ALL --\x3e\n",l='\n',o='\n\n'+t.OBJECT_PARAMS+" "+t.DEFAULT_PREVIEW+"\n\n",d='
    \n'+t.DEFAULT_PREVIEW+"\n
    \n",c={width:"100%",height:"100%","min-height":"480px"},h._isPdfRendered()&&(l=h.pdfRendererTemplate.replace("{renderer}",h.pdfRendererUrl)),h.defaults={layoutTemplates:{main1:'{preview}\n
    \n
    \n {caption}\n
    \n {remove}\n {cancel}\n {upload}\n {browse}\n
    \n
    ',main2:'{preview}\n
    \n
    \n{remove}\n{cancel}\n{upload}\n{browse}\n',preview:'
    \n {close}
    \n
    \n
    \n
    \n
    \n
    \n
    ',close:i,fileIcon:'',caption:'
    \n \n \n
    ',modalMain:a,modal:'\n',progress:'
    \n
    \n {status}\n
    \n
    ',size:" ({sizeText})",footer:'',indicator:'
    {indicator}
    ',actions:'
    \n \n
    \n{drag}\n
    ',actionDelete:'\n',actionUpload:'',actionDownload:'{downloadIcon}',actionZoom:'',actionDrag:'{dragIcon}',btnDefault:'',btnLink:'{icon} {label}',btnBrowse:'
    {icon} {label}
    ',zoomCache:''},previewMarkupTags:{tagBefore1:'
    \n',tagBefore2:'
    \n',tagAfter:"
    {footer}\n
    \n"},previewContentTemplates:{generic:"{content}\n",html:'
    {data}
    \n',image:'{caption}\n',text:'\n',office:'',gdocs:'',video:n,audio:s,flash:'\n',object:o,pdf:l,other:d},allowedPreviewTypes:["image","html","text","video","audio","flash","pdf","object"],previewTemplates:{},previewSettings:{image:{width:"auto",height:"auto","max-width":"100%","max-height":"100%"},html:{width:"213px",height:"160px"},text:{width:"213px",height:"160px"},office:{width:"213px",height:"160px"},gdocs:{width:"213px",height:"160px"},video:{width:"213px",height:"160px"},audio:{width:"100%",height:"30px"},flash:{width:"213px",height:"160px"},object:{width:"213px",height:"160px"},pdf:{width:"100%",height:"160px"},other:{width:"213px",height:"160px"}},previewSettingsSmall:{image:{width:"auto",height:"auto","max-width":"100%","max-height":"100%"},html:{width:"100%",height:"160px"},text:{width:"100%",height:"160px"},office:{width:"100%",height:"160px"},gdocs:{width:"100%",height:"160px"},video:{width:"100%",height:"auto"},audio:{width:"100%",height:"30px"},flash:{width:"100%",height:"auto"},object:{width:"100%",height:"auto"},pdf:{width:"100%",height:"160px"},other:{width:"100%",height:"160px"}},previewZoomSettings:{image:{width:"auto",height:"auto","max-width":"100%","max-height":"100%"},html:c,text:c,office:{width:"100%",height:"100%","max-width":"100%","min-height":"480px"},gdocs:{width:"100%",height:"100%","max-width":"100%","min-height":"480px"},video:{width:"auto",height:"100%","max-width":"100%"},audio:{width:"100%",height:"30px"},flash:{width:"auto",height:"480px"},object:{width:"auto",height:"100%","max-width":"100%","min-height":"480px"},pdf:c,other:{width:"auto",height:"100%","min-height":"480px"}},fileTypeSettings:{image:function(e,i){return t.compare(e,"image.*")&&!t.compare(e,/(tiff?|wmf)$/i)||t.compare(i,/\.(gif|png|jpe?g)$/i)},html:function(e,i){return t.compare(e,"text/html")||t.compare(i,/\.(htm|html)$/i)},office:function(e,i){return t.compare(e,/(word|excel|powerpoint|office)$/i)||t.compare(i,/\.(docx?|xlsx?|pptx?|pps|potx?)$/i)},gdocs:function(e,i){return t.compare(e,/(word|excel|powerpoint|office|iwork-pages|tiff?)$/i)||t.compare(i,/\.(docx?|xlsx?|pptx?|pps|potx?|rtf|ods|odt|pages|ai|dxf|ttf|tiff?|wmf|e?ps)$/i)},text:function(e,i){return t.compare(e,"text.*")||t.compare(i,/\.(xml|javascript)$/i)||t.compare(i,/\.(txt|md|csv|nfo|ini|json|php|js|css)$/i)},video:function(e,i){return t.compare(e,"video.*")&&(t.compare(e,/(ogg|mp4|mp?g|mov|webm|3gp)$/i)||t.compare(i,/\.(og?|mp4|webm|mp?g|mov|3gp)$/i))},audio:function(e,i){return t.compare(e,"audio.*")&&(t.compare(i,/(ogg|mp3|mp?g|wav)$/i)||t.compare(i,/\.(og?|mp3|mp?g|wav)$/i))},flash:function(e,i){return t.compare(e,"application/x-shockwave-flash",!0)||t.compare(i,/\.(swf)$/i)},pdf:function(e,i){return t.compare(e,"application/pdf",!0)||t.compare(i,/\.(pdf)$/i)},object:function(){return!0},other:function(){return!0}},fileActionSettings:{showRemove:!0,showUpload:!0,showDownload:!0,showZoom:!0,showDrag:!0,removeIcon:'',removeClass:"btn btn-sm btn-kv btn-default btn-outline-secondary",removeErrorClass:"btn btn-sm btn-kv btn-danger",removeTitle:"Remove file",uploadIcon:'',uploadClass:"btn btn-sm btn-kv btn-default btn-outline-secondary",uploadTitle:"Upload file",uploadRetryIcon:'',uploadRetryTitle:"Retry upload",downloadIcon:'',downloadClass:"btn btn-sm btn-kv btn-default btn-outline-secondary",downloadTitle:"Download file",zoomIcon:'',zoomClass:"btn btn-sm btn-kv btn-default btn-outline-secondary",zoomTitle:"View Details",dragIcon:'',dragClass:"text-info",dragTitle:"Move / Rearrange",dragSettings:{},indicatorNew:'',indicatorSuccess:'',indicatorError:'',indicatorLoading:'',indicatorNewTitle:"Not uploaded yet",indicatorSuccessTitle:"Uploaded",indicatorErrorTitle:"Upload Error",indicatorLoadingTitle:"Uploading ..."}},e.each(h.defaults,function(t,i){"allowedPreviewTypes"!==t?h[t]=e.extend(!0,{},i,h[t]):void 0===h.allowedPreviewTypes&&(h.allowedPreviewTypes=i)}),h._initPreviewTemplates()},_initPreviewTemplates:function(){var i,a=this,r=a.previewMarkupTags,n=r.tagAfter;e.each(a.previewContentTemplates,function(e,s){t.isEmpty(a.previewTemplates[e])&&(i=r.tagBefore2,"generic"!==e&&"image"!==e&&"html"!==e&&"text"!==e||(i=r.tagBefore1),a._isPdfRendered()&&"pdf"===e&&(i=i.replace("kv-file-content","kv-file-content kv-pdf-rendered")),a.previewTemplates[e]=i+s+n)})},_initPreviewCache:function(){var i=this;i.previewCache={data:{},init:function(){var e=i.initialPreview;e.length>0&&!t.isArray(e)&&(e=e.split(i.initialPreviewDelimiter)),i.previewCache.data={content:e,config:i.initialPreviewConfig,tags:i.initialPreviewThumbTags}},count:function(){return i.previewCache.data&&i.previewCache.data.content?i.previewCache.data.content.length:0},get:function(a,r){var n,s,o,l,d,c,h,p="init_"+a,u=i.previewCache.data,f=u.config[a],m=u.content[a],g=i.previewInitId+"-"+p,v=t.ifSet("previewAsData",f,i.initialPreviewAsData),w=function(e,a,r,n,s,o,l,d,c){return d=" file-preview-initial "+t.SORT_CSS+(d?" "+d:""),i._generatePreviewTemplate(e,a,r,n,s,!1,null,d,o,l,c)};return m?(r=void 0===r||r,o=t.ifSet("type",f,i.initialPreviewFileType||"generic"),d=t.ifSet("filename",f,t.ifSet("caption",f)),c=t.ifSet("filetype",f,o),l=i.previewCache.footer(a,r,f&&f.size||null),h=t.ifSet("frameClass",f),n=v?w(o,m,d,c,g,l,p,h):w("generic",m,d,c,g,l,p,h,o).setTokens({content:u.content[a]}),u.tags.length&&u.tags[a]&&(n=t.replaceTags(n,u.tags[a])),t.isEmpty(f)||t.isEmpty(f.frameAttr)||((s=e(document.createElement("div")).html(n)).find(".file-preview-initial").attr(f.frameAttr),n=s.html(),s.remove()),n):""},add:function(e,a,r,n){var s,o=i.previewCache.data;return t.isArray(e)||(e=e.split(i.initialPreviewDelimiter)),n?(s=o.content.push(e)-1,o.config[s]=a,o.tags[s]=r):(s=e.length-1,o.content=e,o.config=a,o.tags=r),i.previewCache.data=o,s},set:function(e,a,r,n){var s,o=i.previewCache.data;if(e&&e.length&&(t.isArray(e)||(e=e.split(i.initialPreviewDelimiter)),e.filter(function(e){return null!==e}).length)){if(void 0===o.content&&(o.content=[]),void 0===o.config&&(o.config=[]),void 0===o.tags&&(o.tags=[]),n){for(s=0;s'+e+"":"
  • "+e+"
  • ";return 0===a.find("ul").length?this._addError("
      "+n+"
    "):a.find("ul").append(n),a.fadeIn(800),this._raise(r,[t,e]),this._setValidationError("file-input-new"),!0},_showError:function(e,t,i){var a=this.$errorContainer,r=i||"fileerror";return(t=t||{}).reader=this.reader,this._addError(e),a.fadeIn(800),this._raise(r,[t,e]),this.isAjaxUpload||this._clearFileInput(),this._setValidationError("file-input-new"),this.$btnUpload.attr("disabled",!0),!0},_noFilesError:function(e){var t=this.minFileCount>1?this.filePlural:this.fileSingle,i=this.msgFilesTooLess.replace("{n}",this.minFileCount).replace("{files}",t),a=this.$errorContainer;this._addError(i),this.isError=!0,this._updateFileDetails(0),a.fadeIn(800),this._raise("fileerror",[e,i]),this._clearFileInput(),this._setValidationError()},_parseError:function(t,i,a,r){var n,s=e.trim(a+""),o=void 0!==i.responseJSON&&void 0!==i.responseJSON.error?i.responseJSON.error:i.responseText;return this.cancelling&&this.msgUploadAborted&&(s=this.msgUploadAborted),this.showAjaxErrorDetails&&o&&(n=(o=e.trim(o.replace(/\n\s*\n/g,"\n"))).length?"
    "+o+"
    ":"",s+=s?n:o),s||(s=this.msgAjaxError.replace("{operation}",t)),this.cancelling=!1,r?""+r+": "+s:s},_parseFileType:function(e,i){var a,r,n,s=this.allowedPreviewTypes||[];if("application/text-plain"===e)return"text";for(n=0;n-1&&(i=t.split(".").pop(),a.previewFileIconSettings&&(r=a.previewFileIconSettings[i]||a.previewFileIconSettings[i.toLowerCase()]||null),a.previewFileExtSettings&&e.each(a.previewFileExtSettings,function(e,t){a.previewFileIconSettings[e]&&t(i)&&(r=a.previewFileIconSettings[e])})),r},_parseFilePreviewIcon:function(e,t){var i=this._getPreviewIcon(t)||this.previewFileIcon,a=e;return a.indexOf("{previewFileIcon}")>-1&&(a=a.setTokens({previewFileIconClass:this.previewFileIconClass,previewFileIcon:i})),a},_raise:function(t,i){var a=e.Event(t);if(void 0!==i?this.$element.trigger(a,i):this.$element.trigger(a),a.isDefaultPrevented()||!1===a.result)return!1;switch(t){case"filebatchuploadcomplete":case"filebatchuploadsuccess":case"fileuploaded":case"fileclear":case"filecleared":case"filereset":case"fileerror":case"filefoldererror":case"fileuploaderror":case"filebatchuploaderror":case"filedeleteerror":case"filecustomerror":case"filesuccessremove":break;default:this.ajaxAborted||(this.ajaxAborted=a.result)}return!0},_listenFullScreen:function(e){var t,i,a=this.$modal;a&&a.length&&(t=a&&a.find(".btn-fullscreen"),i=a&&a.find(".btn-borderless"),t.length&&i.length&&(t.removeClass("active").attr("aria-pressed","false"),i.removeClass("active").attr("aria-pressed","false"),e?t.addClass("active").attr("aria-pressed","true"):i.addClass("active").attr("aria-pressed","true"),a.hasClass("file-zoom-fullscreen")?this._maximizeZoomDialog():e?this._maximizeZoomDialog():i.removeClass("active").attr("aria-pressed","false")))},_listen:function(){var i=this,a=i.$element,r=i.$form,n=i.$container;i._handler(a,"click",function(e){a.hasClass("file-no-browse")&&(a.data("zoneClicked")?a.data("zoneClicked",!1):e.preventDefault())}),i._handler(a,"change",e.proxy(i._change,i)),i.showBrowse&&i._handler(i.$btnFile,"click",e.proxy(i._browse,i)),i._handler(n.find(".fileinput-remove:not([disabled])"),"click",e.proxy(i.clear,i)),i._handler(n.find(".fileinput-cancel"),"click",e.proxy(i.cancel,i)),i._initDragDrop(),i._handler(r,"reset",e.proxy(i.clear,i)),i.isAjaxUpload||i._handler(r,"submit",e.proxy(i._submitForm,i)),i._handler(i.$container.find(".fileinput-upload"),"click",e.proxy(i._uploadClick,i)),i._handler(e(window),"resize",function(){i._listenFullScreen(screen.width===window.innerWidth&&screen.height===window.innerHeight)}),i._handler(e(document),"webkitfullscreenchange mozfullscreenchange fullscreenchange MSFullscreenChange",function(){i._listenFullScreen(t.checkFullScreen())}),i._autoFitContent(),i._initClickable(),i._refreshPreview()},_autoFitContent:function(){var t,i=window.innerWidth||document.documentElement.clientWidth||document.body.clientWidth,a=this,r=i<400?a.previewSettingsSmall||a.defaults.previewSettingsSmall:a.previewSettings||a.defaults.previewSettings;e.each(r,function(e,i){t=".file-preview-frame .file-preview-"+e,a.$preview.find(t+".kv-preview-data,"+t+" .kv-preview-data").css(i)})},_scanDroppedItems:function(e,t,i){i=i||"";var a,r,n,s=this,o=function(e){s._log("Error scanning dropped files!"),s._log(e)};e.isFile?e.file(function(e){t.push(e)},o):e.isDirectory&&(r=e.createReader(),(n=function(){r.readEntries(function(r){if(r&&r.length>0){for(a=0;a-1;if(this._zoneDragDropInit(i),this.isDisabled||!a)return i.originalEvent.dataTransfer.effectAllowed="none",void(i.originalEvent.dataTransfer.dropEffect="none");t.addCss(this.$dropZone,"file-highlighted")},_zoneDragLeave:function(e){this._zoneDragDropInit(e),this.isDisabled||this.$dropZone.removeClass("file-highlighted")},_zoneDrop:function(e){var i,a=this,r=a.$element,n=e.originalEvent.dataTransfer,s=n.files,o=n.items,l=t.getDragDropFolders(o),d=function(){a.isAjaxUpload?a._change(e,s):(a.changeTriggered=!0,r.get(0).files=s,setTimeout(function(){a.changeTriggered=!1,r.trigger("change"+a.namespace)},10)),a.$dropZone.removeClass("file-highlighted")};if(e.preventDefault(),!a.isDisabled&&!t.isEmpty(s))if(l>0){if(!a.isAjaxUpload)return void a._showFolderError(l);for(s=[],i=0;i
    ").html(o).text()).html(o),l=f.find(".kv-zoom-body"),f.removeClass("kv-single-content"),a?(h=l.addClass("file-thumb-loading").clone().insertAfter(l),l.html(s).hide(),h.fadeOut("fast",function(){l.fadeIn("fast",function(){l.removeClass("file-thumb-loading")}),h.remove()})):l.html(s),(c=p.previewZoomSettings[n])&&(d=l.find(".kv-preview-data"),t.addCss(d,"file-zoom-detail"),e.each(c,function(e,t){d.css(e,t),(d.attr("width")&&"width"===e||d.attr("height")&&"height"===e)&&d.removeAttr(e)})),f.data("previewId",u),p._handler(m,"click",function(){p._zoomSlideShow("prev",u)}),p._handler(g,"click",function(){p._zoomSlideShow("next",u)}),p._handler(v,"click",function(){p._resizeZoomDialog(!0)}),p._handler(w,"click",function(){p._resizeZoomDialog(!1)}),p._handler(_,"click",function(){var e,t=f.find(".modal-header"),i=f.find(".modal-body .floating-buttons"),a=t.find(".kv-zoom-actions"),r=function(e){var i=p.$modal.find(".kv-zoom-body"),a=p.zoomModalHeight;f.hasClass("file-zoom-fullscreen")&&(a=i.outerHeight(!0),e||(a-=t.outerHeight(!0))),i.css("height",e?a+e:a)};t.is(":visible")?(e=t.outerHeight(!0),t.slideUp("slow",function(){a.find(".btn").appendTo(i),r(e)})):(i.find(".btn").appendTo(a),t.slideDown("slow",function(){r()})),f.focus()}),p._handler(f,"keydown",function(e){var t=e.which||e.keyCode;37!==t||m.attr("disabled")||p._zoomSlideShow("prev",u),39!==t||g.attr("disabled")||p._zoomSlideShow("next",u)})},_zoomPreview:function(e){var i,a=this.$modal;if(!e.length)throw"Cannot zoom to detailed preview!";t.initModal(a),a.html(this._getModalContent()),i=e.closest(t.FRAMES),this._setZoomContent(i),a.modal("show"),this._initZoomButtons()},_zoomSlideShow:function(t,i){var a,r,n,s=this.$modal.find(".kv-zoom-actions .btn-"+t),o=this.getFrames().toArray(),l=o.length;if(!s.attr("disabled")){for(r=0;r=l||!o[n]||((a=e(o[n])).length&&this._setZoomContent(a,!0),this._initZoomButtons(),this._raise("filezoom"+t,{previewId:i,modal:this.$modal}))}},_initZoomButton:function(){var t=this;t.$preview.find(".kv-file-zoom").each(function(){var i=e(this);t._handler(i,"click",function(){t._zoomPreview(i)})})},_inputFileCount:function(){return this.$element.get(0).files.length},_refreshPreview:function(){var e;this._inputFileCount()&&this.showPreview&&this.isPreviewable&&(this.isAjaxUpload?(e=this.getFileStack(),this.filestack=[],e.length?this._clearFileInput():e=this.$element.get(0).files):e=this.$element.get(0).files,e&&e.length&&(this.readFiles(e),this._setFileDropZoneTitle()))},_clearObjects:function(t){t.find("video audio").each(function(){this.pause(),e(this).remove()}),t.find("img object div").each(function(){e(this).remove()})},_clearFileInput:function(){var t,i,a,r=this.$element;this._inputFileCount()&&(t=r.closest("form"),i=e(document.createElement("form")),a=e(document.createElement("div")),r.before(a),t.length?t.after(i):a.after(i),i.append(r).trigger("reset"),a.before(r).remove(),i.remove())},_resetUpload:function(){this.uploadCache={content:[],config:[],tags:[],append:!0},this.uploadCount=0,this.uploadStatus={},this.uploadLog=[],this.uploadAsyncCount=0,this.loadedImages=[],this.totalImagesCount=0,this.$btnUpload.removeAttr("disabled"),this._setProgress(0),this.$progress.hide(),this._resetErrors(!1),this.ajaxAborted=!1,this.ajaxRequests=[],this._resetCanvas(),this.cacheInitialPreview={},this.overwriteInitial&&(this.initialPreview=[],this.initialPreviewConfig=[],this.initialPreviewThumbTags=[],this.previewCache.data={content:[],config:[],tags:[]})},_resetCanvas:function(){this.canvas&&this.imageCanvasContext&&this.imageCanvasContext.clearRect(0,0,this.canvas.width,this.canvas.height)},_hasInitialPreview:function(){return!this.overwriteInitial&&this.previewCache.count()},_resetPreview:function(){var e,t;this.previewCache.count()?(e=this.previewCache.out(),this._setPreviewContent(e.content),this._setInitThumbAttr(),t=this.initialCaption?this.initialCaption:e.caption,this._setCaption(t)):(this._clearPreview(),this._initCaption()),this.showPreview&&(this._initZoom(),this._initSortable())},_clearDefaultPreview:function(){this.$preview.find(".file-default-preview").remove()},_validateDefaultPreview:function(){this.showPreview&&!t.isEmpty(this.defaultPreviewContent)&&(this._setPreviewContent('
    '+this.defaultPreviewContent+"
    "),this.$container.removeClass("file-input-new"),this._initClickable())},_resetPreviewThumbs:function(e){var t;if(e)return this._clearPreview(),void this.clearStack();this._hasInitialPreview()?(t=this.previewCache.out(),this._setPreviewContent(t.content),this._setInitThumbAttr(),this._setCaption(t.caption),this._initPreviewActions()):this._clearPreview()},_getLayoutTemplate:function(e){var i=this.layoutTemplates[e];return t.isEmpty(this.customLayoutTags)?i:t.replaceTags(i,this.customLayoutTags)},_getPreviewTemplate:function(e){var i=this.previewTemplates[e];return t.isEmpty(this.customPreviewTags)?i:t.replaceTags(i,this.customPreviewTags)},_getOutData:function(e,t,i){return e=e||{},t=t||{},i=i||this.filestack.slice(0)||{},{form:this.formdata,files:i,filenames:this.filenames,filescount:this.getFilesCount(),extra:this._getExtraData(),response:t,reader:this.reader,jqXHR:e}},_getMsgSelected:function(e){var t=1===e?this.fileSingle:this.filePlural;return e>0?this.msgSelected.replace("{n}",e).replace("{files}",t):this.msgNoFilesSelected},_getFrame:function(t){var i=e("#"+t);return i.length?i:(this._log('Invalid thumb frame with id: "'+t+'".'),null)},_getThumbs:function(e){return e=e||"",this.getFrames(":not(.file-preview-initial)"+e)},_getExtraData:function(e,t){var i=this.uploadExtraData;return"function"==typeof this.uploadExtraData&&(i=this.uploadExtraData(e,t)),i},_initXhr:function(e,t,i){var a=this;return e.upload&&e.upload.addEventListener("progress",function(e){var r=0,n=e.total,s=e.loaded||e.position;e.lengthComputable&&(r=Math.floor(s/n*100)),t?a._setAsyncUploadStatus(t,r,i):a._setProgress(r)},!1),e},_initAjaxSettings:function(){this._ajaxSettings=e.extend(!0,{},this.ajaxSettings),this._ajaxDeleteSettings=e.extend(!0,{},this.ajaxDeleteSettings)},_mergeAjaxCallback:function(e,t,i){var a,r=this._ajaxSettings,n=this.mergeAjaxCallbacks;"delete"===i&&(r=this._ajaxDeleteSettings,n=this.mergeAjaxDeleteCallbacks),a=r[e],r[e]=n&&"function"==typeof a?"before"===n?function(){a.apply(this,arguments),t.apply(this,arguments)}:function(){t.apply(this,arguments),a.apply(this,arguments)}:t},_ajaxSubmit:function(t,i,a,r,n,s){var o,l=this;l._raise("filepreajax",[n,s])&&(l._uploadExtra(n,s),l._initAjaxSettings(),l._mergeAjaxCallback("beforeSend",t),l._mergeAjaxCallback("success",i),l._mergeAjaxCallback("complete",a),l._mergeAjaxCallback("error",r),o=e.extend(!0,{},{xhr:function(){var t=e.ajaxSettings.xhr();return l._initXhr(t,n,l.getFileStack().length)},url:s&&l.uploadUrlThumb?l.uploadUrlThumb:l.uploadUrl,type:"POST",dataType:"json",data:l.formdata,cache:!1,processData:!1,contentType:!1},l._ajaxSettings),l.ajaxRequests.push(e.ajax(o)))},_mergeArray:function(e,i){var a=t.cleanArray(this[e]),r=t.cleanArray(i);this[e]=a.concat(r)},_initUploadSuccess:function(i,a,r){var n,s,o,l,d,c,h,p,u,f=this;f.showPreview&&"object"==typeof i&&!e.isEmptyObject(i)&&void 0!==i.initialPreview&&i.initialPreview.length>0&&(f.hasInitData=!0,c=i.initialPreview||[],h=i.initialPreviewConfig||[],p=i.initialPreviewThumbTags||[],n=void 0===i.append||i.append,c.length>0&&!t.isArray(c)&&(c=c.split(f.initialPreviewDelimiter)),f._mergeArray("initialPreview",c),f._mergeArray("initialPreviewConfig",h),f._mergeArray("initialPreviewThumbTags",p),void 0!==a?r?(u=a.attr("data-fileindex"),f.uploadCache.content[u]=c[0],f.uploadCache.config[u]=h[0]||[],f.uploadCache.tags[u]=p[0]||[],f.uploadCache.append=n):(o=f.previewCache.add(c,h[0],p[0],n),s=f.previewCache.get(o,!1),(d=(l=e(document.createElement("div")).html(s).hide().insertAfter(a)).find(".kv-zoom-cache"))&&d.length&&d.insertAfter(a),a.fadeOut("slow",function(){var e=l.find(".file-preview-frame");e&&e.length&&e.insertBefore(a).fadeIn("slow").css("display:inline-block"),f._initPreviewActions(),f._clearFileInput(),t.cleanZoomCache(f.$preview.find("#zoom-"+a.attr("id"))),a.remove(),l.remove(),f._initSortable()})):(f.previewCache.set(c,h,p,n),f._initPreview(),f._initPreviewActions()))},_initSuccessThumbs:function(){var i=this;i.showPreview&&i._getThumbs(t.FRAMES+".file-preview-success").each(function(){var a=e(this),r=i.$preview,n=a.find(".kv-file-remove");n.removeAttr("disabled"),i._handler(n,"click",function(){var e=a.attr("id"),n=i._raise("filesuccessremove",[e,a.attr("data-fileindex")]);t.cleanMemory(a),!1!==n&&a.fadeOut("slow",function(){t.cleanZoomCache(r.find("#zoom-"+e)),a.remove(),i.getFrames().length||i.reset()})})})},_checkAsyncComplete:function(){var t,i;for(i=0;i0||!e.isEmptyObject(m.uploadExtraData),b=e("#"+w).find(".file-thumb-progress"),C={id:w,index:i};m.formdata=v,m.showPreview&&(n=e("#"+w+":not(.file-preview-initial)"),o=n.find(".kv-file-upload"),l=n.find(".kv-file-remove"),b.show()),0===g||!_||o&&o.hasClass("disabled")||m._abort(C)||(f=function(e,t){d||m.updateStack(e,void 0),m.uploadLog.push(t),m._checkAsyncComplete()&&(m.fileBatchCompleted=!0)},s=function(){var e,i,a,r=m.uploadCache,n=0,s=m.cacheInitialPreview;m.fileBatchCompleted&&(s&&s.content&&(n=s.content.length),setTimeout(function(){var o=0===m.getFileStack(!0).length;if(m.showPreview){if(m.previewCache.set(r.content,r.config,r.tags,r.append),n){for(i=0;i0||!e.isEmptyObject(o.uploadExtraData);o.formdata=new FormData,0!==d&&c&&!o._abort({})&&(s=function(){e.each(l,function(e){o.updateStack(e,void 0)}),o._clearFileInput()},i=function(i){o.lock();var a=o._getOutData(i);o.ajaxAborted=!1,o.showPreview&&o._getThumbs().each(function(){var i=e(this),a=i.find(".kv-file-upload"),r=i.find(".kv-file-remove");i.hasClass("file-preview-success")||(o._setThumbStatus(i,"Loading"),t.addCss(i,"file-uploading")),a.attr("disabled",!0),r.attr("disabled",!0)}),o._raise("filebatchpreupload",[a]),o._abort(a)&&(i.abort(),o._getThumbs().each(function(){var t=e(this),i=t.find(".kv-file-upload"),a=t.find(".kv-file-remove");t.hasClass("file-preview-loading")&&(o._setThumbStatus(t,"New"),t.removeClass("file-uploading")),i.removeAttr("disabled"),a.removeAttr("disabled")}),o._setProgressCancelled())},a=function(i,a,r){var n=o._getOutData(r,i),l=0,d=o._getThumbs(":not(.file-preview-success)"),c=t.isEmpty(i)||t.isEmpty(i.errorkeys)?[]:i.errorkeys;t.isEmpty(i)||t.isEmpty(i.error)?(o._raise("filebatchuploadsuccess",[n]),s(),o.showPreview?(d.each(function(){var t=e(this);o._setThumbStatus(t,"Success"),t.removeClass("file-uploading"),t.find(".kv-file-upload").hide().removeAttr("disabled")}),o._initUploadSuccess(i)):o.reset(),o._setProgress(101)):(o.showPreview&&(d.each(function(){var t=e(this),i=t.attr("data-fileindex");t.removeClass("file-uploading"),t.find(".kv-file-upload").removeAttr("disabled"),t.find(".kv-file-remove").removeAttr("disabled"),0===c.length||-1!==e.inArray(l,c)?(o._setPreviewError(t,i,o.filestack[i],o.retryErrorUploads),o.retryErrorUploads||(t.find(".kv-file-upload").hide(),o.updateStack(i,void 0))):(t.find(".kv-file-upload").hide(),o._setThumbStatus(t,"Success"),o.updateStack(i,void 0)),t.hasClass("file-preview-error")&&!o.retryErrorUploads||l++}),o._initUploadSuccess(i)),o._showUploadError(i.error,n,"filebatchuploaderror"),o._setProgress(101,o.$progress,o.msgUploadError))},n=function(){o.unlock(),o._initSuccessThumbs(),o._clearFileInput(),o._raise("filebatchuploadcomplete",[o.filestack,o._getExtraData()])},r=function(t,i,a){var r=o._getOutData(t),n=o.ajaxOperations.uploadBatch,s=o._parseError(n,t,a);o._showUploadError(s,r,"filebatchuploaderror"),o.uploadFileCount=d-1,o.showPreview&&(o._getThumbs().each(function(){var t=e(this),i=t.attr("data-fileindex");t.removeClass("file-uploading"),void 0!==o.filestack[i]&&o._setPreviewError(t)}),o._getThumbs().removeClass("file-uploading"),o._getThumbs(" .kv-file-upload").removeAttr("disabled"),o._getThumbs(" .kv-file-delete").removeAttr("disabled"),o._setProgress(101,o.$progress,o.msgAjaxProgressError.replace("{operation}",n)))},e.each(l,function(e,i){t.isEmpty(l[e])||o.formdata.append(o.uploadFileAttr,i,o.filenames[e])}),o._ajaxSubmit(i,a,n,r))},_uploadExtraOnly:function(){var e,i,a,r,n=this,s={};n.formdata=new FormData,n._abort(s)||(e=function(e){n.lock();var t=n._getOutData(e);n._raise("filebatchpreupload",[t]),n._setProgress(50),s.data=t,s.xhr=e,n._abort(s)&&(e.abort(),n._setProgressCancelled())},i=function(e,i,a){var r=n._getOutData(a,e);t.isEmpty(e)||t.isEmpty(e.error)?(n._raise("filebatchuploadsuccess",[r]),n._clearFileInput(),n._initUploadSuccess(e),n._setProgress(101)):n._showUploadError(e.error,r,"filebatchuploaderror")},a=function(){n.unlock(),n._clearFileInput(),n._raise("filebatchuploadcomplete",[n.filestack,n._getExtraData()])},r=function(e,t,i){var a=n._getOutData(e),r=n.ajaxOperations.uploadExtra,o=n._parseError(r,e,i);s.data=a,n._showUploadError(o,a,"filebatchuploaderror"),n._setProgress(101,n.$progress,n.msgAjaxProgressError.replace("{operation}",r))},n._ajaxSubmit(e,i,a,r))},_deleteFileIndex:function(i){var a=i.attr("data-fileindex"),r=this.reversePreviewOrder;"init_"===a.substring(0,5)&&(a=parseInt(a.replace("init_","")),this.initialPreview=t.spliceArray(this.initialPreview,a,r),this.initialPreviewConfig=t.spliceArray(this.initialPreviewConfig,a,r),this.initialPreviewThumbTags=t.spliceArray(this.initialPreviewThumbTags,a,r),this.getFrames().each(function(){var t=e(this),i=t.attr("data-fileindex");"init_"===i.substring(0,5)&&(i=parseInt(i.replace("init_","")))>a&&(i--,t.attr("data-fileindex","init_"+i))}),this.uploadAsync&&(this.cacheInitialPreview=this.getPreview()))},_initFileActions:function(){var i=this,a=i.$preview;i.showPreview&&(i._initZoomButton(),i.getFrames(" .kv-file-remove").each(function(){var r,n,s,o=e(this),l=o.closest(t.FRAMES),d=l.attr("id"),c=l.attr("data-fileindex");i._handler(o,"click",function(){if(!1===i._raise("filepreremove",[d,c])||!i._validateMinCount())return!1;r=l.hasClass("file-preview-error"),t.cleanMemory(l),l.fadeOut("slow",function(){t.cleanZoomCache(a.find("#zoom-"+d)),i.updateStack(c,void 0),i._clearObjects(l),l.remove(),d&&r&&i.$errorContainer.find('li[data-file-id="'+d+'"]').fadeOut("fast",function(){e(this).remove(),i._errorsExist()||i._resetErrors()}),i._clearFileInput();var o=i.getFileStack(!0),h=i.previewCache.count(),p=o.length,u=i.showPreview&&i.getFrames().length;0!==p||0!==h||u?(s=(n=h+p)>1?i._getMsgSelected(n):o[0]?i._getFileNames()[0]:"",i._setCaption(s)):i.reset(),i._raise("fileremoved",[d,c])})})}),i.getFrames(" .kv-file-upload").each(function(){var a=e(this);i._handler(a,"click",function(){var e=a.closest(t.FRAMES),r=e.attr("data-fileindex");i.$progress.hide(),e.hasClass("file-preview-error")&&!i.retryErrorUploads||i._uploadSingle(r,!1)})}))},_initPreviewActions:function(){var i=this,a=i.$preview,r=i.deleteExtraData||{},n=t.FRAMES+" .kv-file-remove",s=i.fileActionSettings,o=s.removeClass,l=s.removeErrorClass,d=function(){var e=i.isAjaxUpload?i.previewCache.count():i._inputFileCount();a.find(t.FRAMES).length||e||(i._setCaption(""),i.reset(),i.initialCaption="")};i._initZoomButton(),a.find(n).each(function(){var n,s,c,h=e(this),p=h.data("url")||i.deleteUrl,u=h.data("key");if(!t.isEmpty(p)&&void 0!==u){var f,m,g,v,w=h.closest(t.FRAMES),_=i.previewCache.data,b=w.attr("data-fileindex");b=parseInt(b.replace("init_","")),g=t.isEmpty(_.config)&&t.isEmpty(_.config[b])?null:_.config[b],"function"==typeof(v=t.isEmpty(g)||t.isEmpty(g.extra)?r:g.extra)&&(v=v()),m={id:h.attr("id"),key:u,extra:v},n=function(e){i.ajaxAborted=!1,i._raise("filepredelete",[u,e,v]),i._abort()?e.abort():(h.removeClass(l),t.addCss(w,"file-uploading"),t.addCss(h,"disabled "+o))},s=function(e,r,n){var s,c;if(!t.isEmpty(e)&&!t.isEmpty(e.error))return m.jqXHR=n,m.response=e,i._showError(e.error,m,"filedeleteerror"),w.removeClass("file-uploading"),h.removeClass("disabled "+o).addClass(l),void d();w.removeClass("file-uploading").addClass("file-deleted"),w.fadeOut("slow",function(){b=parseInt(w.attr("data-fileindex").replace("init_","")),i.previewCache.unset(b),i._deleteFileIndex(w),s=i.previewCache.count(),c=s>0?i._getMsgSelected(s):"",i._setCaption(c),i._raise("filedeleted",[u,n,v]),t.cleanZoomCache(a.find("#zoom-"+w.attr("id"))),i._clearObjects(w),w.remove(),d()})},c=function(e,t,a){var r=i.ajaxOperations.deleteThumb,n=i._parseError(r,e,a);m.jqXHR=e,m.response={},i._showError(n,m,"filedeleteerror"),w.removeClass("file-uploading"),h.removeClass("disabled "+o).addClass(l),d()},i._initAjaxSettings(),i._mergeAjaxCallback("beforeSend",n,"delete"),i._mergeAjaxCallback("success",s,"delete"),i._mergeAjaxCallback("error",c,"delete"),f=e.extend(!0,{},{url:p,type:"POST",dataType:"json",data:e.extend(!0,{},{key:u},v)},i._ajaxDeleteSettings),i._handler(h,"click",function(){if(!i._validateMinCount())return!1;i.ajaxAborted=!1,i._raise("filebeforedelete",[u,v]),i.ajaxAborted instanceof Promise?i.ajaxAborted.then(function(t){t||e.ajax(f)}):i.ajaxAborted||e.ajax(f)})}})},_hideFileIcon:function(){this.overwriteInitial&&this.$captionContainer.removeClass("icon-visible")},_showFileIcon:function(){t.addCss(this.$captionContainer,"icon-visible")},_getSize:function(t){var i,a,r,n=parseFloat(t),s=this.fileSizeGetter;return e.isNumeric(t)&&e.isNumeric(n)?("function"==typeof s?r=s(n):0===n?r="0.00 B":(i=Math.floor(Math.log(n)/Math.log(1024)),a=["B","KB","MB","GB","TB","PB","EB","ZB","YB"],r=1*(n/Math.pow(1024,i)).toFixed(2)+" "+a[i]),this._getLayoutTemplate("size").replace("{sizeText}",r)):""},_generatePreviewTemplate:function(i,a,r,n,s,o,l,d,c,h,p){var u,f=this,m=f.slug(r),g="",v="",w=(window.innerWidth||document.documentElement.clientWidth||document.body.clientWidth)<400?f.previewSettingsSmall[i]||f.defaults.previewSettingsSmall[i]:f.previewSettings[i]||f.defaults.previewSettings[i],_=c||f._renderFileFooter(m,l,"auto",o),b=f._getPreviewIcon(r),C="type-default",y=b&&f.preferIconicPreview,x=b&&f.preferIconicZoomPreview;return w&&e.each(w,function(e,t){v+=e+":"+t+";"}),u=function(a,o,l,c){var u=l?"zoom-"+s:s,g=f._getPreviewTemplate(a),w=(d||"")+" "+c;return f.frameClass&&(w=f.frameClass+" "+w),l&&(w=w.replace(" "+t.SORT_CSS,"")),g=f._parseFilePreviewIcon(g,r),"text"===a&&(o=t.htmlEncode(o)),"object"!==i||n||e.each(f.defaults.fileTypeSettings,function(e,t){"object"!==e&&"other"!==e&&t(r,n)&&(C="type-"+e)}),g.setTokens({previewId:u,caption:m,frameClass:w,type:n,fileindex:h,typeCss:C,footer:_,data:o,template:p||i,style:v?'style="'+v+'"':""})},h=h||s.slice(s.lastIndexOf("-")+1),f.fileActionSettings.showZoom&&(g=u(x?"other":i,a,!0,"kv-zoom-thumb")),g="\n"+f._getLayoutTemplate("zoomCache").replace("{zoomContent}",g),u(y?"other":i,a,!1,"kv-preview-thumb")+g},_addToPreview:function(e,t){return this.reversePreviewOrder?e.prepend(t):e.append(t)},_previewDefault:function(i,a,r){var n=this.$preview;if(this.showPreview){var s,o=i?i.name:"",l=i?i.type:"",d=i.size||0,c=this.slug(o),h=!0===r&&!this.isAjaxUpload,p=t.objUrl.createObjectURL(i);this._clearDefaultPreview(),s=this._generatePreviewTemplate("other",p,o,l,a,h,d),this._addToPreview(n,s),this._setThumbAttr(a,c,d),!0===r&&this.isAjaxUpload&&this._setThumbStatus(e("#"+a),"Error")}},_previewFile:function(e,t,i,a,r,n){if(this.showPreview){var s,o=t?t.name:"",l=n.type,d=n.name,c=this._parseFileType(l,o),h=this.allowedPreviewTypes,p=this.allowedPreviewMimeTypes,u=this.$preview,f=t.size||0,m=h&&h.indexOf(c)>=0,g=p&&-1!==p.indexOf(l),v="text"===c||"html"===c||"image"===c?i.target.result:r;if("html"===c&&this.purifyHtml&&window.DOMPurify&&(v=window.DOMPurify.sanitize(v)),m||g){s=this._generatePreviewTemplate(c,v,o,l,a,!1,f),this._clearDefaultPreview(),this._addToPreview(u,s);var w=u.find("#"+a+" img");this._validateImageOrientation(w,t,a,d,l,f,v)}else this._previewDefault(t,a);this._setThumbAttr(a,d,f),this._initSortable()}},_setThumbAttr:function(t,i,a){var r=e("#"+t);r.length&&(a=a&&a>0?this._getSize(a):"",r.data({caption:i,size:a}))},_setInitThumbAttr:function(){var e,i,a,r,n=this.previewCache.data,s=this.previewCache.count();if(0!==s)for(var o=0;o&"']/g,"_")},_updateFileDetails:function(e){var i=this.$element,a=this.getFileStack(),r=t.isIE(9)&&t.findFileName(i.val())||i[0].files[0]&&i[0].files[0].name||a.length&&a[0].name||"",n=this.slug(r),s=this.isAjaxUpload?a.length:e,o=this.previewCache.count()+s,l=1===s?n:this._getMsgSelected(o);this.isError?(this.$previewContainer.removeClass("file-thumb-loading"),this.$previewStatus.html(""),this.$captionContainer.removeClass("icon-visible")):this._showFileIcon(),this._setCaption(l,this.isError),this.$container.removeClass("file-input-new file-input-ajax-new"),1===arguments.length&&this._raise("fileselect",[e,n]),this.previewCache.count()&&this._initPreviewActions()},_setThumbStatus:function(e,t){if(this.showPreview){var i="indicator"+t,a=i+"Title",r="file-preview-"+t.toLowerCase(),n=e.find(".file-upload-indicator"),s=this.fileActionSettings;e.removeClass("file-preview-success file-preview-error file-preview-loading"),"Success"===t&&e.find(".file-drag-handle").remove(),n.html(s[i]),n.attr("title",s[a]),e.addClass(r),"Error"!==t||this.retryErrorUploads||e.find(".kv-file-upload").attr("disabled",!0)}},_setProgressCancelled:function(){this._setProgress(101,this.$progress,this.msgCancelled)},_setProgress:function(e,i,a){var r,n=Math.min(e,100),s=this.progressUploadThreshold,o=e<=100?this.progressTemplate:this.progressCompleteTemplate,l=n<100?this.progressTemplate:a?this.progressErrorTemplate:o;i=i||this.$progress,t.isEmpty(l)||(r=s&&n>s&&e<=100?l.setTokens({percent:s,status:this.msgUploadThreshold}):l.setTokens({percent:n,status:e>100?this.msgUploadEnd:n+"%"}),i.html(r),a&&i.find('[role="progressbar"]').html(a))},_setFileDropZoneTitle:function(){var e,i=this.$container.find(".file-drop-zone"),a=this.dropZoneTitle;this.isClickable&&(e=t.isEmpty(this.$element.attr("multiple"))?this.fileSingle:this.filePlural,a+=this.dropZoneClickTitle.replace("{files}",e)),i.find("."+this.dropZoneTitleClass).remove(),!this.showPreview||0===i.length||this.getFileStack().length>0||!this.dropZoneEnabled||!this.isAjaxUpload&&this.$element.files||(0===i.find(t.FRAMES).length&&t.isEmpty(this.defaultPreviewContent)&&i.prepend('
    '+a+"
    "),this.$container.removeClass("file-input-new"),t.addCss(this.$container,"file-input-ajax-new"))},_setAsyncUploadStatus:function(t,i,a){var r=0;this._setProgress(i,e("#"+t).find(".file-thumb-progress")),this.uploadStatus[t]=i,e.each(this.uploadStatus,function(e,t){r+=t}),this._setProgress(Math.floor(r/a))},_validateMinCount:function(){var e=this.isAjaxUpload?this.getFileStack().length:this._inputFileCount();return!(this.validateInitialCount&&this.minFileCount>0&&this._getFileCount(e-1)=h:d<=h)||(l=this["msgImage"+s+i].setTokens({name:n,size:h}),this._showUploadError(l,o),this._setPreviewError(r,e,null)))},_getExifObj:function(e){var t=null;try{t=window.piexif?window.piexif.load(e):null}catch(e){t=null}return t||this._log("Error loading the piexif.js library."),t},_validateImageOrientation:function(e,i,a,r,n,s,o){var l,d;(d=(l=e.length&&this.autoOrientImage?this._getExifObj(o):null)?l["0th"][piexif.ImageIFD.Orientation]:null)?(t.setImageOrientation(e,this.$preview.find("#zoom-"+a+" img"),d),this._raise("fileimageoriented",{$img:e,file:i}),this._validateImage(a,r,n,s,o,l)):this._validateImage(a,r,n,s,o,l)},_validateImage:function(t,i,a,r,n,s){var o,l,d,c=this,h=c.$preview,p=h.find("#"+t),u=p.attr("data-fileindex"),f=p.find("img");i=i||"Untitled",f.one("load",function(){l=p.width(),d=h.width(),l>d&&f.css("width","100%"),o={ind:u,id:t},c._checkDimensions(u,"Small",f,p,i,"Width",o),c._checkDimensions(u,"Small",f,p,i,"Height",o),c.resizeImage||(c._checkDimensions(u,"Large",f,p,i,"Width",o),c._checkDimensions(u,"Large",f,p,i,"Height",o)),c._raise("fileimageloaded",[t]),c.loadedImages.push({ind:u,img:f,thumb:p,pid:t,typ:a,siz:r,validated:!1,imgData:n,exifObj:s}),p.data("exif",s),c._validateAllImages()}).one("error",function(){c._raise("fileimageloaderror",[t])}).each(function(){this.complete?e(this).trigger("load"):this.error&&e(this).trigger("error")})},_validateAllImages:function(){var e,t,i,a={val:0},r=this.loadedImages.length,n=this.resizeIfSizeMoreThan;if(r===this.totalImagesCount&&(this._raise("fileimagesloaded"),this.resizeImage))for(e=0;e1e3*n&&this._getResizedImage(t,a,r),this.loadedImages[e].validated=!0)},_getResizedImage:function(i,a,r){var n,s,o,l,d,c,h=this,p=e(i.img)[0],u=p.naturalWidth,f=p.naturalHeight,m=1,g=h.maxImageWidth||u,v=h.maxImageHeight||f,w=!(!u||!f),_=h.imageCanvas,b=h.imageCanvasContext,C=i.typ,y=i.pid,x=i.ind,T=i.thumb,E=i.exifObj;if(d=function(e,t,i){h.isAjaxUpload?h._showUploadError(e,t,i):h._showError(e,t,i),h._setPreviewError(T,x)},h.filestack[x]&&w&&!(u<=g&&f<=v)||(w&&h.filestack[x]&&h._raise("fileimageresized",[y,x]),a.val++,a.val===r&&h._raise("fileimagesresized"),w)){C=C||h.resizeDefaultImageType,s=u>g,o=f>v,m="width"===h.resizePreference?s?g/u:o?v/f:1:o?v/f:s?g/u:1,h._resetCanvas(),u*=m,f*=m,_.width=u,_.height=f;try{b.drawImage(p,0,0,u,f),l=_.toDataURL(C,h.resizeQuality),E&&(c=window.piexif.dump(E),l=window.piexif.insert(c,l)),n=t.dataURI2Blob(l),h.filestack[x]=n,h._raise("fileimageresized",[y,x]),a.val++,a.val===r&&h._raise("fileimagesresized",[void 0,void 0]),n instanceof Blob||d(h.msgImageResizeError,{id:y,index:x},"fileimageresizeerror")}catch(e){a.val++,a.val===r&&h._raise("fileimagesresized",[void 0,void 0]),d(h.msgImageResizeException.replace("{errors}",e.message),{id:y,index:x},"fileimageresizeexception")}}else d(h.msgImageResizeError,{id:y,index:x},"fileimageresizeerror")},_initBrowse:function(e){var i=this.$element;this.showBrowse?this.$btnFile=e.find(".btn-file").append(i):(i.appendTo(e).attr("tabindex",-1),t.addCss(i,"file-no-browse"))},_initClickable:function(){var i,a,r=this;r.isClickable&&(i=r.$dropZone,r.isAjaxUpload||(a=r.$preview.find(".file-default-preview")).length&&(i=a),t.addCss(i,"clickable"),i.attr("tabindex",-1),r._handler(i,"click",function(t){var a=e(t.target);e(r.elErrorContainer+":visible").length||a.parents(".file-preview-thumbnails").length&&!a.parents(".file-default-preview").length||(r.$element.data("zoneClicked",!0).trigger("click"),i.blur())}))},_initCaption:function(){var e=this.initialCaption||"";return this.overwriteInitial||t.isEmpty(e)?(this.$caption.val(""),!1):(this._setCaption(e),!0)},_setCaption:function(i,a){var r,n,s,o,l,d=this.getFileStack();if(this.$caption.length){if(this.$captionContainer.removeClass("icon-visible"),a)r=e("
    "+this.msgValidationError+"
    ").text(),l=(o=d.length)?1===o&&d[0]?this._getFileNames()[0]:this._getMsgSelected(o):this._getMsgSelected(this.msgNo),n=t.isEmpty(i)?l:i,s=''+this.msgValidationErrorIcon+"";else{if(t.isEmpty(i))return;n=r=e("
    "+i+"
    ").text(),s=this._getLayoutTemplate("fileIcon")}this.$captionContainer.addClass("icon-visible"),this.$caption.attr("title",r).val(n),this.$captionIcon.html(s)}},_createContainer:function(){var t={class:"file-input file-input-new"+(this.rtl?" kv-rtl":"")},i=e(document.createElement("div")).attr(t).html(this._renderMain());return i.insertBefore(this.$element),this._initBrowse(i),this.theme&&i.addClass("theme-"+this.theme),i},_refreshContainer:function(){var e=this.$container;this.$element.insertAfter(e),e.html(this._renderMain()),this._initBrowse(e),this._validateDisabled()},_validateDisabled:function(){this.$caption.attr({readonly:this.isDisabled})},_renderMain:function(){var e=this.dropZoneEnabled?" file-drop-zone":"file-drop-disabled",t=this.showClose?this._getLayoutTemplate("close"):"",i=this.showPreview?this._getLayoutTemplate("preview").setTokens({class:this.previewClass,dropClass:e}):"",a=this.isDisabled?this.captionClass+" file-caption-disabled":this.captionClass,r=this.captionTemplate.setTokens({class:a+" kv-fileinput-caption"});return this.mainTemplate.setTokens({class:this.mainClass+(!this.showBrowse&&this.showCaption?" no-browse":""),preview:i,close:t,caption:r,upload:this._renderButton("upload"),remove:this._renderButton("remove"),cancel:this._renderButton("cancel"),browse:this._renderButton("browse")})},_renderButton:function(e){var i=this._getLayoutTemplate("btnDefault"),a=this[e+"Class"],r=this[e+"Title"],n=this[e+"Icon"],s=this[e+"Label"],o=this.isDisabled?" disabled":"",l="button";switch(e){case"remove":if(!this.showRemove)return"";break;case"cancel":if(!this.showCancel)return"";a+=" kv-hidden";break;case"upload":if(!this.showUpload)return"";this.isAjaxUpload&&!this.isDisabled?i=this._getLayoutTemplate("btnLink").replace("{href}",this.uploadUrl):l="submit";break;case"browse":if(!this.showBrowse)return"";i=this._getLayoutTemplate("btnBrowse");break;default:return""}return a+="browse"===e?" btn-file":" fileinput-"+e+" fileinput-"+e+"-button",t.isEmpty(s)||(s=' '+s+""),i.setTokens({type:l,css:a,title:r,status:o,icon:n,label:s})},_renderThumbProgress:function(){return'
    '+this.progressTemplate.setTokens({percent:"0",status:this.msgUploadBegin})+"
    "},_renderFileFooter:function(e,i,a,r){var n,s=this.fileActionSettings,o=s.showRemove,l=s.showDrag,d=s.showUpload,c=s.showZoom,h=this._getLayoutTemplate("footer"),p=this._getLayoutTemplate("indicator"),u=r?s.indicatorError:s.indicatorNew,f=r?s.indicatorErrorTitle:s.indicatorNewTitle,m=p.setTokens({indicator:u,indicatorTitle:f});return i=this._getSize(i),n=this.isAjaxUpload?h.setTokens({actions:this._renderFileActions(d,!1,o,c,l,!1,!1,!1),caption:e,size:i,width:a,progress:this._renderThumbProgress(),indicator:m}):h.setTokens({actions:this._renderFileActions(!1,!1,!1,c,l,!1,!1,!1),caption:e,size:i,width:a,progress:"",indicator:m}),n=t.replaceTags(n,this.previewThumbTags)},_renderFileActions:function(e,t,i,a,r,n,s,o,l,d,c){if(!(e||t||i||a||r))return"";var h,p=!1===s?"":' data-url="'+s+'"',u=!1===o?"":' data-key="'+o+'"',f="",m="",g="",v="",w="",_=this._getLayoutTemplate("actions"),b=this.fileActionSettings,C=this.otherActionButtons.setTokens({dataKey:u,key:o}),y=n?b.removeClass+" disabled":b.removeClass;return i&&(f=this._getLayoutTemplate("actionDelete").setTokens({removeClass:y,removeIcon:b.removeIcon,removeTitle:b.removeTitle,dataUrl:p,dataKey:u,key:o})),e&&(m=this._getLayoutTemplate("actionUpload").setTokens({uploadClass:b.uploadClass,uploadIcon:b.uploadIcon,uploadTitle:b.uploadTitle})),t&&(g=(g=this._getLayoutTemplate("actionDownload").setTokens({downloadClass:b.downloadClass,downloadIcon:b.downloadIcon,downloadTitle:b.downloadTitle,downloadUrl:d||this.initialPreviewDownloadUrl})).setTokens({filename:c,key:o})),a&&(v=this._getLayoutTemplate("actionZoom").setTokens({zoomClass:b.zoomClass,zoomIcon:b.zoomIcon,zoomTitle:b.zoomTitle})),r&&l&&(h="drag-handle-init "+b.dragClass,w=this._getLayoutTemplate("actionDrag").setTokens({dragClass:h,dragTitle:b.dragTitle,dragIcon:b.dragIcon})),_.setTokens({delete:f,upload:m,download:g,zoom:v,drag:w,other:C})},_browse:function(e){e&&e.isDefaultPrevented()||!this._raise("filebrowse")||(this.isError&&!this.isAjaxUpload&&this.clear(),this.$captionContainer.focus())},_filterDuplicate:function(e,t,i){var a=this._getFileId(e);a&&i&&i.indexOf(a)>-1||(i||(i=[]),t.push(e),i.push(a))},_change:function(i){var a=this;if(!a.changeTriggered){var r,n,s,o,l,d,c,h,p,u,f,m=a.$element,g=arguments.length>1,v=a.isAjaxUpload,w=[],_=g?arguments[1]:m.get(0).files,b=!v&&t.isEmpty(m.attr("multiple"))?1:a.maxFileCount,C=a.filestack.length,y=t.isEmpty(m.attr("multiple"))&&C>0,x=a._getFileIds();if(a.reader=null,a._resetUpload(),a._hideFileIcon(),a.dropZoneEnabled&&a.$container.find(".file-drop-zone ."+a.dropZoneTitleClass).remove(),v?e.each(_,function(e,t){a._filterDuplicate(t,w,x)}):(_=i.target&&void 0===i.target.files?i.target.value?[{name:i.target.value.replace(/^.+\\/,"")}]:[]:i.target.files||{},w=_),t.isEmpty(w)||0===w.length)return v||a.clear(),void a._raise("fileselectnone");if(a._resetErrors(),n=w.length,r=a._getFileCount(v?a.getFileStack().length+n:n),b>0&&r>b){if(!a.autoReplace||n>b)return s=a.autoReplace&&n>b?n:r,o=b,f=a.msgFilesTooMany.replace("{m}",o).replace("{n}",s),a.isError=(l=f,d=null,c=null,h=null,p=e.extend(!0,{},a._getOutData({},{},_),{id:c,index:h}),u={id:c,index:h,file:d,files:_},v?a._showUploadError(l,p):a._showError(l,u)),a.$captionContainer.removeClass("icon-visible"),a._setCaption("",!0),void a.$container.removeClass("file-input-new file-input-ajax-new");r>b&&a._resetPreviewThumbs(v)}else!v||y?(a._resetPreviewThumbs(!1),y&&a.clearStack()):!v||0!==C||a.previewCache.count()&&!a.overwriteInitial||a._resetPreviewThumbs(!0);a.isPreviewable?a.readFiles(w):a._updateFileDetails(1)}},_abort:function(t){var i;return this.ajaxAborted&&"object"==typeof this.ajaxAborted&&void 0!==this.ajaxAborted.message?((i=e.extend(!0,{},this._getOutData(),t)).abortData=this.ajaxAborted.data||{},i.abortMessage=this.ajaxAborted.message,this._setProgress(101,this.$progress,this.msgCancelled),this._showUploadError(this.ajaxAborted.message,i,"filecustomerror"),this.cancel(),!0):!!this.ajaxAborted},_resetFileStack:function(){var i=this,a=0,r=[],n=[],s=[];i._getThumbs().each(function(){var o=e(this),l=o.attr("data-fileindex"),d=i.filestack[l],c=o.attr("id");"-1"!==l&&-1!==l&&(void 0!==d?(r[a]=d,n[a]=i._getFileName(d),s[a]=i._getFileId(d),o.attr({id:i.previewInitId+"-"+a,"data-fileindex":a}),a++):o.attr({id:"uploaded-"+t.uniqId(),"data-fileindex":"-1"}),i.$preview.find("#zoom-"+c).attr({id:"zoom-"+o.attr("id"),"data-fileindex":o.attr("data-fileindex")}))}),i.filestack=r,i.filenames=n,i.fileids=s},_isFileSelectionValid:function(e){return e=e||0,this.required&&!this.getFilesCount()?(this.$errorContainer.html(""),this._showUploadError(this.msgFileRequired),!1):!(this.minFileCount>0&&this._getFileCount(e)=u)return r.isAjaxUpload&&r.filestack.length>0?r._raise("filebatchselected",[r.getFileStack()]):r._raise("filebatchselected",[i]),l.removeClass("file-thumb-loading"),void d.html("");var T,E,S,k,F,P,I,A,D,z,$,j,U=p+"-"+(m+x),B=i[x],R=f.text,O=f.image,L=f.html,M=B&&B.name?r.slug(B.name):"",Z=(B&&B.size||0)/1e3,N="",H=B?t.objUrl.createObjectURL(B):null,W=0,q="",V=0,K=function(){var e=h.setTokens({index:x+1,files:u,percent:50,name:M});setTimeout(function(){d.html(e),r._updateFileDetails(u),a(x+1)},100),r._raise("fileloaded",[B,U,x,o])};if(B){if(v>0)for(E=0;E0&&Z>r.maxFileSize)return S=r.msgSizeTooLarge.setTokens({name:M,size:T,maxSize:r.maxFileSize}),void y(S,B,U,x);if(null!==r.minFileSize&&Z<=t.getNum(r.minFileSize))return S=r.msgSizeTooSmall.setTokens({name:M,size:T,minSize:r.minFileSize}),void y(S,B,U,x);if(!t.isEmpty(g)&&t.isArray(g)){for(E=0;Eb)return r.addToStack(B),l.addClass("file-thumb-loading"),r._previewDefault(B,U),r._initFileActions(),r._updateFileDetails(u),void a(x+1);s.length&&void 0!==FileReader?(D=R(B.type,M),z=L(B.type,M),$=O(B.type,M),d.html(c.replace("{index}",x+1).replace("{files}",u)),l.addClass("file-thumb-loading"),o.onerror=function(e){r._errorHandler(e,M)},o.onload=function(i){var a,n,s,l,d,c,h,p,u=[];if(n={name:M,type:B.type},e.each(f,function(e,t){"object"!==e&&"other"!==e&&t(B.type,M)&&V++}),0===V){for(s=new Uint8Array(i.target.result),E=0;E0)for(t=0;t0?a.initialCaption:"",a.$caption.attr("title","").val(i),t.addCss(a.$container,"file-input-new"),a._validateDefaultPreview()),0===a.$container.find(t.FRAMES).length&&(a._initCaption()||a.$captionContainer.removeClass("icon-visible")),a._hideFileIcon(),a._raise("filecleared"),a.$captionContainer.focus(),a._setFileDropZoneTitle(),a.$element},reset:function(){if(this._raise("filereset"))return this._resetPreview(),this.$container.find(".fileinput-filename").text(""),t.addCss(this.$container,"file-input-new"),(this.getFrames().length||this.dropZoneEnabled)&&this.$container.removeClass("file-input-new"),this.clearStack(),this.formdata={},this._setFileDropZoneTitle(),this.$element},disable:function(){return this.isDisabled=!0,this._raise("filedisabled"),this.$element.attr("disabled","disabled"),this.$container.find(".kv-fileinput-caption").addClass("file-caption-disabled"),this.$container.find(".fileinput-remove, .fileinput-upload, .file-preview-frame button").attr("disabled",!0),t.addCss(this.$container.find(".btn-file"),"disabled"),this._initDragDrop(),this.$element},enable:function(){return this.isDisabled=!1,this._raise("fileenabled"),this.$element.removeAttr("disabled"),this.$container.find(".kv-fileinput-caption").removeClass("file-caption-disabled"),this.$container.find(".fileinput-remove, .fileinput-upload, .file-preview-frame button").removeAttr("disabled"),this.$container.find(".btn-file").removeClass("disabled"),this._initDragDrop(),this.$element},upload:function(){var i,a,r,n=this.getFileStack().length,s=!e.isEmptyObject(this._getExtraData());if(this.isAjaxUpload&&!this.isDisabled&&this._isFileSelectionValid(n))if(this._resetUpload(),0!==n||s)if(this.$progress.show(),this.uploadCount=0,this.uploadStatus={},this.uploadLog=[],this.lock(),this._setProgress(2),0===n&&s)this._uploadExtraOnly();else{if(r=this.filestack.length,this.hasInitData=!1,!this.uploadAsync)return this._uploadBatch(),this.$element;for(a=this._getOutData(),this._raise("filebatchpreupload",[a]),this.fileBatchCompleted=!1,this.uploadCache={content:[],config:[],tags:[],append:!0},this.uploadAsyncCount=this.getFileStack().length,i=0;i
    ',next:'',toggleheader:'',fullscreen:'',borderless:'',close:''},previewZoomButtonClasses:{prev:"btn btn-navigate",next:"btn btn-navigate",toggleheader:"btn btn-sm btn-kv btn-default btn-outline-secondary",fullscreen:"btn btn-sm btn-kv btn-default btn-outline-secondary",borderless:"btn btn-sm btn-kv btn-default btn-outline-secondary",close:"btn btn-sm btn-kv btn-default btn-outline-secondary"},previewTemplates:{},previewContentTemplates:{},preferIconicPreview:!1,preferIconicZoomPreview:!1,allowedPreviewTypes:void 0,allowedPreviewMimeTypes:null,allowedFileTypes:null,allowedFileExtensions:null,defaultPreviewContent:null,customLayoutTags:{},customPreviewTags:{},previewFileIcon:'',previewFileIconClass:"file-other-icon",previewFileIconSettings:{},previewFileExtSettings:{},buttonLabelClass:"hidden-xs",browseIcon:' ',browseClass:"btn btn-primary",removeIcon:'',removeClass:"btn btn-default btn-secondary",cancelIcon:'',cancelClass:"btn btn-default btn-secondary",uploadIcon:'',uploadClass:"btn btn-default btn-secondary",uploadUrl:null,uploadUrlThumb:null,uploadAsync:!0,uploadExtraData:{},zoomModalHeight:480,minImageWidth:null,minImageHeight:null,maxImageWidth:null,maxImageHeight:null,resizeImage:!1,resizePreference:"width",resizeQuality:.92,resizeDefaultImageType:"image/jpeg",resizeIfSizeMoreThan:0,minFileSize:0,maxFileSize:0,maxFilePreviewSize:25600,minFileCount:0,maxFileCount:0,validateInitialCount:!1,msgValidationErrorClass:"text-danger",msgValidationErrorIcon:' ',msgErrorClass:"file-error-message",progressThumbClass:"progress-bar bg-success progress-bar-success progress-bar-striped active",progressClass:"progress-bar bg-success progress-bar-success progress-bar-striped active",progressCompleteClass:"progress-bar bg-success progress-bar-success",progressErrorClass:"progress-bar bg-danger progress-bar-danger",progressUploadThreshold:99,previewFileType:"image",elCaptionContainer:null,elCaptionText:null,elPreviewContainer:null,elPreviewImage:null,elPreviewStatus:null,elErrorContainer:null,errorCloseButton:t.closeButton("kv-error-close"),slugCallback:null,dropZoneEnabled:!0,dropZoneTitleClass:"file-drop-zone-title",fileActionSettings:{},otherActionButtons:"",textEncoding:"UTF-8",ajaxSettings:{},ajaxDeleteSettings:{},showAjaxErrorDetails:!0,mergeAjaxCallbacks:!1,mergeAjaxDeleteCallbacks:!1,retryErrorUploads:!0,reversePreviewOrder:!1},e.fn.fileinputLocales.en={fileSingle:"file",filePlural:"files",browseLabel:"Browse …",removeLabel:"Remove",removeTitle:"Clear selected files",cancelLabel:"Cancel",cancelTitle:"Abort ongoing upload",uploadLabel:"Upload",uploadTitle:"Upload selected files",msgNo:"No",msgNoFilesSelected:"No files selected",msgCancelled:"Cancelled",msgPlaceholder:"Select {files}...",msgZoomModalHeading:"Detailed Preview",msgFileRequired:"You must select a file to upload.",msgSizeTooSmall:'File "{name}" ({size} KB) is too small and must be larger than {minSize} KB.',msgSizeTooLarge:'File "{name}" ({size} KB) exceeds maximum allowed upload size of {maxSize} KB.',msgFilesTooLess:"You must select at least {n} {files} to upload.",msgFilesTooMany:"Number of files selected for upload ({n}) exceeds maximum allowed limit of {m}.",msgFileNotFound:'File "{name}" not found!',msgFileSecured:'Security restrictions prevent reading the file "{name}".',msgFileNotReadable:'File "{name}" is not readable.',msgFilePreviewAborted:'File preview aborted for "{name}".',msgFilePreviewError:'An error occurred while reading the file "{name}".',msgInvalidFileName:'Invalid or unsupported characters in file name "{name}".',msgInvalidFileType:'Invalid type for file "{name}". Only "{types}" files are supported.',msgInvalidFileExtension:'Invalid extension for file "{name}". Only "{extensions}" files are supported.',msgFileTypes:{image:"image",html:"HTML",text:"text",video:"video",audio:"audio",flash:"flash",pdf:"PDF",object:"object"},msgUploadAborted:"The file upload was aborted",msgUploadThreshold:"Processing...",msgUploadBegin:"Initializing...",msgUploadEnd:"Done",msgUploadEmpty:"No valid data available for upload.",msgUploadError:"Error",msgValidationError:"Validation Error",msgLoading:"Loading file {index} of {files} …",msgProgress:"Loading file {index} of {files} - {name} - {percent}% completed.",msgSelected:"{n} {files} selected",msgFoldersNotAllowed:"Drag & drop files only! {n} folder(s) dropped were skipped.",msgImageWidthSmall:'Width of image file "{name}" must be at least {size} px.',msgImageHeightSmall:'Height of image file "{name}" must be at least {size} px.',msgImageWidthLarge:'Width of image file "{name}" cannot exceed {size} px.',msgImageHeightLarge:'Height of image file "{name}" cannot exceed {size} px.',msgImageResizeError:"Could not get the image dimensions to resize.",msgImageResizeException:"Error while resizing the image.
    {errors}
    ",msgAjaxError:"Something went wrong with the {operation} operation. Please try again later!",msgAjaxProgressError:"{operation} failed",ajaxOperations:{deleteThumb:"file delete",uploadThumb:"file upload",uploadBatch:"batch file upload",uploadExtra:"form data upload"},dropZoneTitle:"Drag & drop files here …",dropZoneClickTitle:"
    (or click to select {files})",previewZoomButtonTitles:{prev:"View previous file",next:"View next file",toggleheader:"Toggle header",fullscreen:"Toggle full screen",borderless:"Toggle borderless mode",close:"Close detailed preview"},usePdfRenderer:function(){return!!navigator.userAgent.match(/(iPod|iPhone|iPad|Android)/i)},pdfRendererUrl:"",pdfRendererTemplate:''},e.fn.fileinput.Constructor=i,e(document).ready(function(){var t=e("input.file[type=file]");t.length&&t.fileinput()})}); \ No newline at end of file diff --git a/static/plugins/bootstrap-fileinput/js/locales/zh-TW.js b/static/plugins/bootstrap-fileinput/js/locales/zh-TW.js new file mode 100644 index 00000000..49f7710f --- /dev/null +++ b/static/plugins/bootstrap-fileinput/js/locales/zh-TW.js @@ -0,0 +1,102 @@ +/*! + * FileInput Chinese Traditional Translations + * + * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or + * any HTML markup tags in the messages must not be converted or translated. + * + * @see http://github.com/kartik-v/bootstrap-fileinput + * @author kangqf + * + * NOTE: this file must be saved in UTF-8 encoding. + */ +(function ($) { + "use strict"; + + $.fn.fileinputLocales['zh-TW'] = { + fileSingle: '單一檔案', + filePlural: '複選檔案', + browseLabel: '瀏覽 …', + removeLabel: '移除', + removeTitle: '清除選取檔案', + cancelLabel: '取消', + cancelTitle: '取消上傳中檔案', + uploadLabel: '上傳', + uploadTitle: '上傳選取檔案', + msgNo: '沒有', + msgNoFilesSelected: '未選擇檔案', + msgCancelled: '取消', + zoomTitle: '詳細資料', + msgPlaceholder: '選擇 {files}...', + msgZoomModalHeading: '內容預覽', + msgFileRequired: '必須選擇壹個文件上傳.', + msgSizeTooSmall: '檔案 "{name}" ({size} KB) 必須大於限定大小 {minSize} KB.', + msgSizeTooLarge: '檔案 "{name}" ({size} KB) 大小超過上限 {maxSize} KB.', + msgFilesTooLess: '最少必須選擇 {n} {files} 來上傳. ', + msgFilesTooMany: '上傳的檔案數量 ({n}) 超過最大檔案上傳限制 {m}.', + msgFileNotFound: '檔案 "{name}" 未發現!', + msgFileSecured: '安全限制,禁止讀取檔案 "{name}".', + msgFileNotReadable: '文件 "{name}" 不可讀取.', + msgFilePreviewAborted: '檔案 "{name}" 預覽中止.', + msgFilePreviewError: '讀取 "{name}" 發生錯誤.', + msgInvalidFileName: '附檔名 "{name}" 包含非法字符.', + msgInvalidFileType: '檔案類型錯誤 "{name}". 只能使用 "{types}" 類型的檔案.', + msgInvalidFileExtension: '附檔名錯誤 "{name}". 只能使用 "{extensions}" 的檔案.', + msgFileTypes: { + 'image': 'image', + 'html': 'HTML', + 'text': 'text', + 'video': 'video', + 'audio': 'audio', + 'flash': 'flash', + 'pdf': 'PDF', + 'object': 'object' + }, + msgUploadAborted: '該文件上傳被中止', + msgUploadThreshold: '處理中...', + msgUploadBegin: '正在初始化...', + msgUploadEnd: '完成', + msgUploadEmpty: '無效的文件上傳.', + msgUploadError: '上傳錯誤', + msgValidationError: '驗證錯誤', + msgLoading: '載入第 {index} 個檔案,共 {files} …', + msgProgress: '載入第 {index} 個檔案,共 {files} - {name} - {percent}% 成功.', + msgSelected: '{n} {files} 選取', + msgFoldersNotAllowed: '只支援單檔拖曳! 無法使用 {n} 拖拽的資料夹.', + msgImageWidthSmall: '圖檔寬度"{name}"必須至少為{size}像素(px).', + msgImageHeightSmall: '圖檔高度"{name}"必須至少為{size}像素(px).', + msgImageWidthLarge: '圖檔寬度"{name}"不能超過{size}像素(px).', + msgImageHeightLarge: '圖檔高度"{name}"不能超過{size}像素(px).', + msgImageResizeError: '無法獲取的圖像尺寸調整。', + msgImageResizeException: '錯誤而調整圖像大小。
    {errors}
    ', + msgAjaxError: '{operation} 發生錯誤. 請重試!', + msgAjaxProgressError: '{operation} 失敗', + ajaxOperations: { + deleteThumb: 'file delete', + uploadThumb: 'file upload', + uploadBatch: 'batch file upload', + uploadExtra: 'form data upload' + }, + dropZoneTitle: '拖曳檔案至此 …', + dropZoneClickTitle: '
    (或點擊{files}按鈕選擇文件)', + fileActionSettings: { + removeTitle: '刪除檔案', + uploadTitle: '上傳檔案', + uploadRetryTitle: '重試', + downloadTitle: '下載檔案', + zoomTitle: '詳細資料', + dragTitle: '移動 / 重置', + indicatorNewTitle: '尚未上傳', + indicatorSuccessTitle: '上傳成功', + indicatorErrorTitle: '上傳失敗', + indicatorLoadingTitle: '上傳中 ...' + }, + previewZoomButtonTitles: { + prev: '預覽上壹個文件', + next: '預覽下壹個文件', + toggleheader: '縮放', + fullscreen: '全屏', + borderless: '無邊界模式', + close: '關閉當前預覽' + } + }; +})(window.jQuery); diff --git a/static/plugins/bootstrap-fileinput/js/locales/zh.js b/static/plugins/bootstrap-fileinput/js/locales/zh.js new file mode 100644 index 00000000..469fa380 --- /dev/null +++ b/static/plugins/bootstrap-fileinput/js/locales/zh.js @@ -0,0 +1,101 @@ +/*! + * FileInput Chinese Translations + * + * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or + * any HTML markup tags in the messages must not be converted or translated. + * + * @see http://github.com/kartik-v/bootstrap-fileinput + * @author kangqf + * + * NOTE: this file must be saved in UTF-8 encoding. + */ +(function ($) { + "use strict"; + + $.fn.fileinputLocales['zh'] = { + fileSingle: '文件', + filePlural: '个文件', + browseLabel: '选择 …', + removeLabel: '移除', + removeTitle: '清除选中文件', + cancelLabel: '取消', + cancelTitle: '取消进行中的上传', + uploadLabel: '上传', + uploadTitle: '上传选中文件', + msgNo: '没有', + msgNoFilesSelected: '未选择文件', + msgCancelled: '取消', + msgPlaceholder: '选择 {files}...', + msgZoomModalHeading: '详细预览', + msgFileRequired: '必须选择一个文件上传.', + msgSizeTooSmall: '文件 "{name}" ({size} KB) 必须大于限定大小 {minSize} KB.', + msgSizeTooLarge: '文件 "{name}" ({size} KB) 超过了允许大小 {maxSize} KB.', + msgFilesTooLess: '你必须选择最少 {n} {files} 来上传. ', + msgFilesTooMany: '选择的上传文件个数 ({n}) 超出最大文件的限制个数 {m}.', + msgFileNotFound: '文件 "{name}" 未找到!', + msgFileSecured: '安全限制,为了防止读取文件 "{name}".', + msgFileNotReadable: '文件 "{name}" 不可读.', + msgFilePreviewAborted: '取消 "{name}" 的预览.', + msgFilePreviewError: '读取 "{name}" 时出现了一个错误.', + msgInvalidFileName: '文件名 "{name}" 包含非法字符.', + msgInvalidFileType: '不正确的类型 "{name}". 只支持 "{types}" 类型的文件.', + msgInvalidFileExtension: '不正确的文件扩展名 "{name}". 只支持 "{extensions}" 的文件扩展名.', + msgFileTypes: { + 'image': 'image', + 'html': 'HTML', + 'text': 'text', + 'video': 'video', + 'audio': 'audio', + 'flash': 'flash', + 'pdf': 'PDF', + 'object': 'object' + }, + msgUploadAborted: '该文件上传被中止', + msgUploadThreshold: '处理中...', + msgUploadBegin: '正在初始化...', + msgUploadEnd: '完成', + msgUploadEmpty: '无效的文件上传.', + msgUploadError: '上传出错', + msgValidationError: '验证错误', + msgLoading: '加载第 {index} 文件 共 {files} …', + msgProgress: '加载第 {index} 文件 共 {files} - {name} - {percent}% 完成.', + msgSelected: '{n} {files} 选中', + msgFoldersNotAllowed: '只支持拖拽文件! 跳过 {n} 拖拽的文件夹.', + msgImageWidthSmall: '图像文件的"{name}"的宽度必须是至少{size}像素.', + msgImageHeightSmall: '图像文件的"{name}"的高度必须至少为{size}像素.', + msgImageWidthLarge: '图像文件"{name}"的宽度不能超过{size}像素.', + msgImageHeightLarge: '图像文件"{name}"的高度不能超过{size}像素.', + msgImageResizeError: '无法获取的图像尺寸调整。', + msgImageResizeException: '调整图像大小时发生错误。
    {errors}
    ', + msgAjaxError: '{operation} 发生错误. 请重试!', + msgAjaxProgressError: '{operation} 失败', + ajaxOperations: { + deleteThumb: '删除文件', + uploadThumb: '上传文件', + uploadBatch: '批量上传', + uploadExtra: '表单数据上传' + }, + dropZoneTitle: '拖拽文件到这里 …
    支持多文件同时上传', + dropZoneClickTitle: '
    (或点击{files}按钮选择文件)', + fileActionSettings: { + removeTitle: '删除文件', + uploadTitle: '上传文件', + downloadTitle: '下载文件', + uploadRetryTitle: '重试', + zoomTitle: '查看详情', + dragTitle: '移动 / 重置', + indicatorNewTitle: '没有上传', + indicatorSuccessTitle: '上传', + indicatorErrorTitle: '上传错误', + indicatorLoadingTitle: '上传 ...' + }, + previewZoomButtonTitles: { + prev: '预览上一个文件', + next: '预览下一个文件', + toggleheader: '缩放', + fullscreen: '全屏', + borderless: '无边界模式', + close: '关闭当前预览' + } + }; +})(window.jQuery); diff --git a/static/plugins/bootstrap-fileinput/js/plugins/piexif.js b/static/plugins/bootstrap-fileinput/js/plugins/piexif.js new file mode 100644 index 00000000..343bf391 --- /dev/null +++ b/static/plugins/bootstrap-fileinput/js/plugins/piexif.js @@ -0,0 +1,2471 @@ +/* piexifjs + +The MIT License (MIT) + +Copyright (c) 2014, 2015 hMatoba(https://github.com/hMatoba) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +(function () { + "use strict"; + var that = {}; + that.version = "1.03"; + + that.remove = function (jpeg) { + var b64 = false; + if (jpeg.slice(0, 2) == "\xff\xd8") { + } else if (jpeg.slice(0, 23) == "data:image/jpeg;base64," || jpeg.slice(0, 22) == "data:image/jpg;base64,") { + jpeg = atob(jpeg.split(",")[1]); + b64 = true; + } else { + throw ("Given data is not jpeg."); + } + + var segments = splitIntoSegments(jpeg); + if (segments[1].slice(0, 2) == "\xff\xe1" && + segments[1].slice(4, 10) == "Exif\x00\x00") { + segments = [segments[0]].concat(segments.slice(2)); + } else if (segments[2].slice(0, 2) == "\xff\xe1" && + segments[2].slice(4, 10) == "Exif\x00\x00") { + segments = segments.slice(0, 2).concat(segments.slice(3)); + } else { + throw("Exif not found."); + } + + var new_data = segments.join(""); + if (b64) { + new_data = "data:image/jpeg;base64," + btoa(new_data); + } + + return new_data; + }; + + + that.insert = function (exif, jpeg) { + var b64 = false; + if (exif.slice(0, 6) != "\x45\x78\x69\x66\x00\x00") { + throw ("Given data is not exif."); + } + if (jpeg.slice(0, 2) == "\xff\xd8") { + } else if (jpeg.slice(0, 23) == "data:image/jpeg;base64," || jpeg.slice(0, 22) == "data:image/jpg;base64,") { + jpeg = atob(jpeg.split(",")[1]); + b64 = true; + } else { + throw ("Given data is not jpeg."); + } + + var exifStr = "\xff\xe1" + pack(">H", [exif.length + 2]) + exif; + var segments = splitIntoSegments(jpeg); + var new_data = mergeSegments(segments, exifStr); + if (b64) { + new_data = "data:image/jpeg;base64," + btoa(new_data); + } + + return new_data; + }; + + + that.load = function (data) { + var input_data; + if (typeof (data) == "string") { + if (data.slice(0, 2) == "\xff\xd8") { + input_data = data; + } else if (data.slice(0, 23) == "data:image/jpeg;base64," || data.slice(0, 22) == "data:image/jpg;base64,") { + input_data = atob(data.split(",")[1]); + } else if (data.slice(0, 4) == "Exif") { + input_data = data.slice(6); + } else { + throw ("'load' gots invalid file data."); + } + } else { + throw ("'load' gots invalid type argument."); + } + + var exifDict = {}; + var exif_dict = { + "0th": {}, + "Exif": {}, + "GPS": {}, + "Interop": {}, + "1st": {}, + "thumbnail": null + }; + var exifReader = new ExifReader(input_data); + if (exifReader.tiftag === null) { + return exif_dict; + } + + if (exifReader.tiftag.slice(0, 2) == "\x49\x49") { + exifReader.endian_mark = "<"; + } else { + exifReader.endian_mark = ">"; + } + + var pointer = unpack(exifReader.endian_mark + "L", + exifReader.tiftag.slice(4, 8))[0]; + exif_dict["0th"] = exifReader.get_ifd(pointer, "0th"); + + var first_ifd_pointer = exif_dict["0th"]["first_ifd_pointer"]; + delete exif_dict["0th"]["first_ifd_pointer"]; + + if (34665 in exif_dict["0th"]) { + pointer = exif_dict["0th"][34665]; + exif_dict["Exif"] = exifReader.get_ifd(pointer, "Exif"); + } + if (34853 in exif_dict["0th"]) { + pointer = exif_dict["0th"][34853]; + exif_dict["GPS"] = exifReader.get_ifd(pointer, "GPS"); + } + if (40965 in exif_dict["Exif"]) { + pointer = exif_dict["Exif"][40965]; + exif_dict["Interop"] = exifReader.get_ifd(pointer, "Interop"); + } + if (first_ifd_pointer != "\x00\x00\x00\x00") { + pointer = unpack(exifReader.endian_mark + "L", + first_ifd_pointer)[0]; + exif_dict["1st"] = exifReader.get_ifd(pointer, "1st"); + if ((513 in exif_dict["1st"]) && (514 in exif_dict["1st"])) { + var end = exif_dict["1st"][513] + exif_dict["1st"][514]; + var thumb = exifReader.tiftag.slice(exif_dict["1st"][513], end); + exif_dict["thumbnail"] = thumb; + } + } + + return exif_dict; + }; + + + that.dump = function (exif_dict_original) { + var TIFF_HEADER_LENGTH = 8; + + var exif_dict = copy(exif_dict_original); + var header = "Exif\x00\x00\x4d\x4d\x00\x2a\x00\x00\x00\x08"; + var exif_is = false; + var gps_is = false; + var interop_is = false; + var first_is = false; + + var zeroth_ifd, + exif_ifd, + interop_ifd, + gps_ifd, + first_ifd; + + if ("0th" in exif_dict) { + zeroth_ifd = exif_dict["0th"]; + } else { + zeroth_ifd = {}; + } + + if ((("Exif" in exif_dict) && (Object.keys(exif_dict["Exif"]).length)) || + (("Interop" in exif_dict) && (Object.keys(exif_dict["Interop"]).length))) { + zeroth_ifd[34665] = 1; + exif_is = true; + exif_ifd = exif_dict["Exif"]; + if (("Interop" in exif_dict) && Object.keys(exif_dict["Interop"]).length) { + exif_ifd[40965] = 1; + interop_is = true; + interop_ifd = exif_dict["Interop"]; + } else if (Object.keys(exif_ifd).indexOf(that.ExifIFD.InteroperabilityTag.toString()) > -1) { + delete exif_ifd[40965]; + } + } else if (Object.keys(zeroth_ifd).indexOf(that.ImageIFD.ExifTag.toString()) > -1) { + delete zeroth_ifd[34665]; + } + + if (("GPS" in exif_dict) && (Object.keys(exif_dict["GPS"]).length)) { + zeroth_ifd[that.ImageIFD.GPSTag] = 1; + gps_is = true; + gps_ifd = exif_dict["GPS"]; + } else if (Object.keys(zeroth_ifd).indexOf(that.ImageIFD.GPSTag.toString()) > -1) { + delete zeroth_ifd[that.ImageIFD.GPSTag]; + } + + if (("1st" in exif_dict) && + ("thumbnail" in exif_dict) && + (exif_dict["thumbnail"] != null)) { + first_is = true; + exif_dict["1st"][513] = 1; + exif_dict["1st"][514] = 1; + first_ifd = exif_dict["1st"]; + } + + var zeroth_set = _dict_to_bytes(zeroth_ifd, "0th", 0); + var zeroth_length = (zeroth_set[0].length + exif_is * 12 + gps_is * 12 + 4 + + zeroth_set[1].length); + + var exif_set, + exif_bytes = "", + exif_length = 0, + gps_set, + gps_bytes = "", + gps_length = 0, + interop_set, + interop_bytes = "", + interop_length = 0, + first_set, + first_bytes = "", + thumbnail; + if (exif_is) { + exif_set = _dict_to_bytes(exif_ifd, "Exif", zeroth_length); + exif_length = exif_set[0].length + interop_is * 12 + exif_set[1].length; + } + if (gps_is) { + gps_set = _dict_to_bytes(gps_ifd, "GPS", zeroth_length + exif_length); + gps_bytes = gps_set.join(""); + gps_length = gps_bytes.length; + } + if (interop_is) { + var offset = zeroth_length + exif_length + gps_length; + interop_set = _dict_to_bytes(interop_ifd, "Interop", offset); + interop_bytes = interop_set.join(""); + interop_length = interop_bytes.length; + } + if (first_is) { + var offset = zeroth_length + exif_length + gps_length + interop_length; + first_set = _dict_to_bytes(first_ifd, "1st", offset); + thumbnail = _get_thumbnail(exif_dict["thumbnail"]); + if (thumbnail.length > 64000) { + throw ("Given thumbnail is too large. max 64kB"); + } + } + + var exif_pointer = "", + gps_pointer = "", + interop_pointer = "", + first_ifd_pointer = "\x00\x00\x00\x00"; + if (exif_is) { + var pointer_value = TIFF_HEADER_LENGTH + zeroth_length; + var pointer_str = pack(">L", [pointer_value]); + var key = 34665; + var key_str = pack(">H", [key]); + var type_str = pack(">H", [TYPES["Long"]]); + var length_str = pack(">L", [1]); + exif_pointer = key_str + type_str + length_str + pointer_str; + } + if (gps_is) { + var pointer_value = TIFF_HEADER_LENGTH + zeroth_length + exif_length; + var pointer_str = pack(">L", [pointer_value]); + var key = 34853; + var key_str = pack(">H", [key]); + var type_str = pack(">H", [TYPES["Long"]]); + var length_str = pack(">L", [1]); + gps_pointer = key_str + type_str + length_str + pointer_str; + } + if (interop_is) { + var pointer_value = (TIFF_HEADER_LENGTH + + zeroth_length + exif_length + gps_length); + var pointer_str = pack(">L", [pointer_value]); + var key = 40965; + var key_str = pack(">H", [key]); + var type_str = pack(">H", [TYPES["Long"]]); + var length_str = pack(">L", [1]); + interop_pointer = key_str + type_str + length_str + pointer_str; + } + if (first_is) { + var pointer_value = (TIFF_HEADER_LENGTH + zeroth_length + + exif_length + gps_length + interop_length); + first_ifd_pointer = pack(">L", [pointer_value]); + var thumbnail_pointer = (pointer_value + first_set[0].length + 24 + + 4 + first_set[1].length); + var thumbnail_p_bytes = ("\x02\x01\x00\x04\x00\x00\x00\x01" + + pack(">L", [thumbnail_pointer])); + var thumbnail_length_bytes = ("\x02\x02\x00\x04\x00\x00\x00\x01" + + pack(">L", [thumbnail.length])); + first_bytes = (first_set[0] + thumbnail_p_bytes + + thumbnail_length_bytes + "\x00\x00\x00\x00" + + first_set[1] + thumbnail); + } + + var zeroth_bytes = (zeroth_set[0] + exif_pointer + gps_pointer + + first_ifd_pointer + zeroth_set[1]); + if (exif_is) { + exif_bytes = exif_set[0] + interop_pointer + exif_set[1]; + } + + return (header + zeroth_bytes + exif_bytes + gps_bytes + + interop_bytes + first_bytes); + }; + + + function copy(obj) { + return JSON.parse(JSON.stringify(obj)); + } + + + function _get_thumbnail(jpeg) { + var segments = splitIntoSegments(jpeg); + while (("\xff\xe0" <= segments[1].slice(0, 2)) && (segments[1].slice(0, 2) <= "\xff\xef")) { + segments = [segments[0]].concat(segments.slice(2)); + } + return segments.join(""); + } + + + function _pack_byte(array) { + return pack(">" + nStr("B", array.length), array); + } + + + function _pack_short(array) { + return pack(">" + nStr("H", array.length), array); + } + + + function _pack_long(array) { + return pack(">" + nStr("L", array.length), array); + } + + + function _value_to_bytes(raw_value, value_type, offset) { + var four_bytes_over = ""; + var value_str = ""; + var length, + new_value, + num, + den; + + if (value_type == "Byte") { + length = raw_value.length; + if (length <= 4) { + value_str = (_pack_byte(raw_value) + + nStr("\x00", 4 - length)); + } else { + value_str = pack(">L", [offset]); + four_bytes_over = _pack_byte(raw_value); + } + } else if (value_type == "Short") { + length = raw_value.length; + if (length <= 2) { + value_str = (_pack_short(raw_value) + + nStr("\x00\x00", 2 - length)); + } else { + value_str = pack(">L", [offset]); + four_bytes_over = _pack_short(raw_value); + } + } else if (value_type == "Long") { + length = raw_value.length; + if (length <= 1) { + value_str = _pack_long(raw_value); + } else { + value_str = pack(">L", [offset]); + four_bytes_over = _pack_long(raw_value); + } + } else if (value_type == "Ascii") { + new_value = raw_value + "\x00"; + length = new_value.length; + if (length > 4) { + value_str = pack(">L", [offset]); + four_bytes_over = new_value; + } else { + value_str = new_value + nStr("\x00", 4 - length); + } + } else if (value_type == "Rational") { + if (typeof (raw_value[0]) == "number") { + length = 1; + num = raw_value[0]; + den = raw_value[1]; + new_value = pack(">L", [num]) + pack(">L", [den]); + } else { + length = raw_value.length; + new_value = ""; + for (var n = 0; n < length; n++) { + num = raw_value[n][0]; + den = raw_value[n][1]; + new_value += (pack(">L", [num]) + + pack(">L", [den])); + } + } + value_str = pack(">L", [offset]); + four_bytes_over = new_value; + } else if (value_type == "SRational") { + if (typeof (raw_value[0]) == "number") { + length = 1; + num = raw_value[0]; + den = raw_value[1]; + new_value = pack(">l", [num]) + pack(">l", [den]); + } else { + length = raw_value.length; + new_value = ""; + for (var n = 0; n < length; n++) { + num = raw_value[n][0]; + den = raw_value[n][1]; + new_value += (pack(">l", [num]) + + pack(">l", [den])); + } + } + value_str = pack(">L", [offset]); + four_bytes_over = new_value; + } else if (value_type == "Undefined") { + length = raw_value.length; + if (length > 4) { + value_str = pack(">L", [offset]); + four_bytes_over = raw_value; + } else { + value_str = raw_value + nStr("\x00", 4 - length); + } + } + + var length_str = pack(">L", [length]); + + return [length_str, value_str, four_bytes_over]; + } + + function _dict_to_bytes(ifd_dict, ifd, ifd_offset) { + var TIFF_HEADER_LENGTH = 8; + var tag_count = Object.keys(ifd_dict).length; + var entry_header = pack(">H", [tag_count]); + var entries_length; + if (["0th", "1st"].indexOf(ifd) > -1) { + entries_length = 2 + tag_count * 12 + 4; + } else { + entries_length = 2 + tag_count * 12; + } + var entries = ""; + var values = ""; + var key; + + for (var key in ifd_dict) { + if (typeof (key) == "string") { + key = parseInt(key); + } + if ((ifd == "0th") && ([34665, 34853].indexOf(key) > -1)) { + continue; + } else if ((ifd == "Exif") && (key == 40965)) { + continue; + } else if ((ifd == "1st") && ([513, 514].indexOf(key) > -1)) { + continue; + } + + var raw_value = ifd_dict[key]; + var key_str = pack(">H", [key]); + var value_type = TAGS[ifd][key]["type"]; + var type_str = pack(">H", [TYPES[value_type]]); + + if (typeof (raw_value) == "number") { + raw_value = [raw_value]; + } + var offset = TIFF_HEADER_LENGTH + entries_length + ifd_offset + values.length; + var b = _value_to_bytes(raw_value, value_type, offset); + var length_str = b[0]; + var value_str = b[1]; + var four_bytes_over = b[2]; + + entries += key_str + type_str + length_str + value_str; + values += four_bytes_over; + } + + return [entry_header + entries, values]; + } + + + + function ExifReader(data) { + var segments, + app1; + if (data.slice(0, 2) == "\xff\xd8") { // JPEG + segments = splitIntoSegments(data); + app1 = getExifSeg(segments); + if (app1) { + this.tiftag = app1.slice(10); + } else { + this.tiftag = null; + } + } else if (["\x49\x49", "\x4d\x4d"].indexOf(data.slice(0, 2)) > -1) { // TIFF + this.tiftag = data; + } else if (data.slice(0, 4) == "Exif") { // Exif + this.tiftag = data.slice(6); + } else { + throw ("Given file is neither JPEG nor TIFF."); + } + } + + ExifReader.prototype = { + get_ifd: function (pointer, ifd_name) { + var ifd_dict = {}; + var tag_count = unpack(this.endian_mark + "H", + this.tiftag.slice(pointer, pointer + 2))[0]; + var offset = pointer + 2; + var t; + if (["0th", "1st"].indexOf(ifd_name) > -1) { + t = "Image"; + } else { + t = ifd_name; + } + + for (var x = 0; x < tag_count; x++) { + pointer = offset + 12 * x; + var tag = unpack(this.endian_mark + "H", + this.tiftag.slice(pointer, pointer + 2))[0]; + var value_type = unpack(this.endian_mark + "H", + this.tiftag.slice(pointer + 2, pointer + 4))[0]; + var value_num = unpack(this.endian_mark + "L", + this.tiftag.slice(pointer + 4, pointer + 8))[0]; + var value = this.tiftag.slice(pointer + 8, pointer + 12); + + var v_set = [value_type, value_num, value]; + if (tag in TAGS[t]) { + ifd_dict[tag] = this.convert_value(v_set); + } + } + + if (ifd_name == "0th") { + pointer = offset + 12 * tag_count; + ifd_dict["first_ifd_pointer"] = this.tiftag.slice(pointer, pointer + 4); + } + + return ifd_dict; + }, + + convert_value: function (val) { + var data = null; + var t = val[0]; + var length = val[1]; + var value = val[2]; + var pointer; + + if (t == 1) { // BYTE + if (length > 4) { + pointer = unpack(this.endian_mark + "L", value)[0]; + data = unpack(this.endian_mark + nStr("B", length), + this.tiftag.slice(pointer, pointer + length)); + } else { + data = unpack(this.endian_mark + nStr("B", length), value.slice(0, length)); + } + } else if (t == 2) { // ASCII + if (length > 4) { + pointer = unpack(this.endian_mark + "L", value)[0]; + data = this.tiftag.slice(pointer, pointer + length - 1); + } else { + data = value.slice(0, length - 1); + } + } else if (t == 3) { // SHORT + if (length > 2) { + pointer = unpack(this.endian_mark + "L", value)[0]; + data = unpack(this.endian_mark + nStr("H", length), + this.tiftag.slice(pointer, pointer + length * 2)); + } else { + data = unpack(this.endian_mark + nStr("H", length), + value.slice(0, length * 2)); + } + } else if (t == 4) { // LONG + if (length > 1) { + pointer = unpack(this.endian_mark + "L", value)[0]; + data = unpack(this.endian_mark + nStr("L", length), + this.tiftag.slice(pointer, pointer + length * 4)); + } else { + data = unpack(this.endian_mark + nStr("L", length), + value); + } + } else if (t == 5) { // RATIONAL + pointer = unpack(this.endian_mark + "L", value)[0]; + if (length > 1) { + data = []; + for (var x = 0; x < length; x++) { + data.push([unpack(this.endian_mark + "L", + this.tiftag.slice(pointer + x * 8, pointer + 4 + x * 8))[0], + unpack(this.endian_mark + "L", + this.tiftag.slice(pointer + 4 + x * 8, pointer + 8 + x * 8))[0] + ]); + } + } else { + data = [unpack(this.endian_mark + "L", + this.tiftag.slice(pointer, pointer + 4))[0], + unpack(this.endian_mark + "L", + this.tiftag.slice(pointer + 4, pointer + 8))[0] + ]; + } + } else if (t == 7) { // UNDEFINED BYTES + if (length > 4) { + pointer = unpack(this.endian_mark + "L", value)[0]; + data = this.tiftag.slice(pointer, pointer + length); + } else { + data = value.slice(0, length); + } + } else if (t == 10) { // SRATIONAL + pointer = unpack(this.endian_mark + "L", value)[0]; + if (length > 1) { + data = []; + for (var x = 0; x < length; x++) { + data.push([unpack(this.endian_mark + "l", + this.tiftag.slice(pointer + x * 8, pointer + 4 + x * 8))[0], + unpack(this.endian_mark + "l", + this.tiftag.slice(pointer + 4 + x * 8, pointer + 8 + x * 8))[0] + ]); + } + } else { + data = [unpack(this.endian_mark + "l", + this.tiftag.slice(pointer, pointer + 4))[0], + unpack(this.endian_mark + "l", + this.tiftag.slice(pointer + 4, pointer + 8))[0] + ]; + } + } else { + throw ("Exif might be wrong. Got incorrect value " + + "type to decode. type:" + t); + } + + if ((data instanceof Array) && (data.length == 1)) { + return data[0]; + } else { + return data; + } + }, + }; + + + if (typeof window !== "undefined" && typeof window.btoa === "function") { + var btoa = window.btoa; + } + if (typeof btoa === "undefined") { + var btoa = function (input) { var output = ""; + var chr1, chr2, chr3, enc1, enc2, enc3, enc4; + var i = 0; + var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; + + while (i < input.length) { + + chr1 = input.charCodeAt(i++); + chr2 = input.charCodeAt(i++); + chr3 = input.charCodeAt(i++); + + enc1 = chr1 >> 2; + enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); + enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); + enc4 = chr3 & 63; + + if (isNaN(chr2)) { + enc3 = enc4 = 64; + } else if (isNaN(chr3)) { + enc4 = 64; + } + + output = output + + keyStr.charAt(enc1) + keyStr.charAt(enc2) + + keyStr.charAt(enc3) + keyStr.charAt(enc4); + + } + + return output; + }; + } + + + if (typeof window !== "undefined" && typeof window.atob === "function") { + var atob = window.atob; + } + if (typeof atob === "undefined") { + var atob = function (input) { + var output = ""; + var chr1, chr2, chr3; + var enc1, enc2, enc3, enc4; + var i = 0; + var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; + + input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); + + while (i < input.length) { + + enc1 = keyStr.indexOf(input.charAt(i++)); + enc2 = keyStr.indexOf(input.charAt(i++)); + enc3 = keyStr.indexOf(input.charAt(i++)); + enc4 = keyStr.indexOf(input.charAt(i++)); + + chr1 = (enc1 << 2) | (enc2 >> 4); + chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); + chr3 = ((enc3 & 3) << 6) | enc4; + + output = output + String.fromCharCode(chr1); + + if (enc3 != 64) { + output = output + String.fromCharCode(chr2); + } + if (enc4 != 64) { + output = output + String.fromCharCode(chr3); + } + + } + + return output; + }; + } + + + function getImageSize(imageArray) { + var segments = slice2Segments(imageArray); + var seg, + width, + height, + SOF = [192, 193, 194, 195, 197, 198, 199, 201, 202, 203, 205, 206, 207]; + + for (var x = 0; x < segments.length; x++) { + seg = segments[x]; + if (SOF.indexOf(seg[1]) >= 0) { + height = seg[5] * 256 + seg[6]; + width = seg[7] * 256 + seg[8]; + break; + } + } + return [width, height]; + } + + + function pack(mark, array) { + if (!(array instanceof Array)) { + throw ("'pack' error. Got invalid type argument."); + } + if ((mark.length - 1) != array.length) { + throw ("'pack' error. " + (mark.length - 1) + " marks, " + array.length + " elements."); + } + + var littleEndian; + if (mark[0] == "<") { + littleEndian = true; + } else if (mark[0] == ">") { + littleEndian = false; + } else { + throw (""); + } + var packed = ""; + var p = 1; + var val = null; + var c = null; + var valStr = null; + + while (c = mark[p]) { + if (c.toLowerCase() == "b") { + val = array[p - 1]; + if ((c == "b") && (val < 0)) { + val += 0x100; + } + if ((val > 0xff) || (val < 0)) { + throw ("'pack' error."); + } else { + valStr = String.fromCharCode(val); + } + } else if (c == "H") { + val = array[p - 1]; + if ((val > 0xffff) || (val < 0)) { + throw ("'pack' error."); + } else { + valStr = String.fromCharCode(Math.floor((val % 0x10000) / 0x100)) + + String.fromCharCode(val % 0x100); + if (littleEndian) { + valStr = valStr.split("").reverse().join(""); + } + } + } else if (c.toLowerCase() == "l") { + val = array[p - 1]; + if ((c == "l") && (val < 0)) { + val += 0x100000000; + } + if ((val > 0xffffffff) || (val < 0)) { + throw ("'pack' error."); + } else { + valStr = String.fromCharCode(Math.floor(val / 0x1000000)) + + String.fromCharCode(Math.floor((val % 0x1000000) / 0x10000)) + + String.fromCharCode(Math.floor((val % 0x10000) / 0x100)) + + String.fromCharCode(val % 0x100); + if (littleEndian) { + valStr = valStr.split("").reverse().join(""); + } + } + } else { + throw ("'pack' error."); + } + + packed += valStr; + p += 1; + } + + return packed; + } + + function unpack(mark, str) { + if (typeof (str) != "string") { + throw ("'unpack' error. Got invalid type argument."); + } + var l = 0; + for (var markPointer = 1; markPointer < mark.length; markPointer++) { + if (mark[markPointer].toLowerCase() == "b") { + l += 1; + } else if (mark[markPointer].toLowerCase() == "h") { + l += 2; + } else if (mark[markPointer].toLowerCase() == "l") { + l += 4; + } else { + throw ("'unpack' error. Got invalid mark."); + } + } + + if (l != str.length) { + throw ("'unpack' error. Mismatch between symbol and string length. " + l + ":" + str.length); + } + + var littleEndian; + if (mark[0] == "<") { + littleEndian = true; + } else if (mark[0] == ">") { + littleEndian = false; + } else { + throw ("'unpack' error."); + } + var unpacked = []; + var strPointer = 0; + var p = 1; + var val = null; + var c = null; + var length = null; + var sliced = ""; + + while (c = mark[p]) { + if (c.toLowerCase() == "b") { + length = 1; + sliced = str.slice(strPointer, strPointer + length); + val = sliced.charCodeAt(0); + if ((c == "b") && (val >= 0x80)) { + val -= 0x100; + } + } else if (c == "H") { + length = 2; + sliced = str.slice(strPointer, strPointer + length); + if (littleEndian) { + sliced = sliced.split("").reverse().join(""); + } + val = sliced.charCodeAt(0) * 0x100 + + sliced.charCodeAt(1); + } else if (c.toLowerCase() == "l") { + length = 4; + sliced = str.slice(strPointer, strPointer + length); + if (littleEndian) { + sliced = sliced.split("").reverse().join(""); + } + val = sliced.charCodeAt(0) * 0x1000000 + + sliced.charCodeAt(1) * 0x10000 + + sliced.charCodeAt(2) * 0x100 + + sliced.charCodeAt(3); + if ((c == "l") && (val >= 0x80000000)) { + val -= 0x100000000; + } + } else { + throw ("'unpack' error. " + c); + } + + unpacked.push(val); + strPointer += length; + p += 1; + } + + return unpacked; + } + + function nStr(ch, num) { + var str = ""; + for (var i = 0; i < num; i++) { + str += ch; + } + return str; + } + + function splitIntoSegments(data) { + if (data.slice(0, 2) != "\xff\xd8") { + throw ("Given data isn't JPEG."); + } + + var head = 2; + var segments = ["\xff\xd8"]; + while (true) { + if (data.slice(head, head + 2) == "\xff\xda") { + segments.push(data.slice(head)); + break; + } else { + var length = unpack(">H", data.slice(head + 2, head + 4))[0]; + var endPoint = head + length + 2; + segments.push(data.slice(head, endPoint)); + head = endPoint; + } + + if (head >= data.length) { + throw ("Wrong JPEG data."); + } + } + return segments; + } + + + function getExifSeg(segments) { + var seg; + for (var i = 0; i < segments.length; i++) { + seg = segments[i]; + if (seg.slice(0, 2) == "\xff\xe1" && + seg.slice(4, 10) == "Exif\x00\x00") { + return seg; + } + } + return null; + } + + + function mergeSegments(segments, exif) { + + if (segments[1].slice(0, 2) == "\xff\xe0" && + (segments[2].slice(0, 2) == "\xff\xe1" && + segments[2].slice(4, 10) == "Exif\x00\x00")) { + if (exif) { + segments[2] = exif; + segments = ["\xff\xd8"].concat(segments.slice(2)); + } else if (exif == null) { + segments = segments.slice(0, 2).concat(segments.slice(3)); + } else { + segments = segments.slice(0).concat(segments.slice(2)); + } + } else if (segments[1].slice(0, 2) == "\xff\xe0") { + if (exif) { + segments[1] = exif; + } + } else if (segments[1].slice(0, 2) == "\xff\xe1" && + segments[1].slice(4, 10) == "Exif\x00\x00") { + if (exif) { + segments[1] = exif; + } else if (exif == null) { + segments = segments.slice(0).concat(segments.slice(2)); + } + } else { + if (exif) { + segments = [segments[0], exif].concat(segments.slice(1)); + } + } + + return segments.join(""); + } + + + function toHex(str) { + var hexStr = ""; + for (var i = 0; i < str.length; i++) { + var h = str.charCodeAt(i); + var hex = ((h < 10) ? "0" : "") + h.toString(16); + hexStr += hex + " "; + } + return hexStr; + } + + + var TYPES = { + "Byte": 1, + "Ascii": 2, + "Short": 3, + "Long": 4, + "Rational": 5, + "Undefined": 7, + "SLong": 9, + "SRational": 10 + }; + + + var TAGS = { + 'Image': { + 11: { + 'name': 'ProcessingSoftware', + 'type': 'Ascii' + }, + 254: { + 'name': 'NewSubfileType', + 'type': 'Long' + }, + 255: { + 'name': 'SubfileType', + 'type': 'Short' + }, + 256: { + 'name': 'ImageWidth', + 'type': 'Long' + }, + 257: { + 'name': 'ImageLength', + 'type': 'Long' + }, + 258: { + 'name': 'BitsPerSample', + 'type': 'Short' + }, + 259: { + 'name': 'Compression', + 'type': 'Short' + }, + 262: { + 'name': 'PhotometricInterpretation', + 'type': 'Short' + }, + 263: { + 'name': 'Threshholding', + 'type': 'Short' + }, + 264: { + 'name': 'CellWidth', + 'type': 'Short' + }, + 265: { + 'name': 'CellLength', + 'type': 'Short' + }, + 266: { + 'name': 'FillOrder', + 'type': 'Short' + }, + 269: { + 'name': 'DocumentName', + 'type': 'Ascii' + }, + 270: { + 'name': 'ImageDescription', + 'type': 'Ascii' + }, + 271: { + 'name': 'Make', + 'type': 'Ascii' + }, + 272: { + 'name': 'Model', + 'type': 'Ascii' + }, + 273: { + 'name': 'StripOffsets', + 'type': 'Long' + }, + 274: { + 'name': 'Orientation', + 'type': 'Short' + }, + 277: { + 'name': 'SamplesPerPixel', + 'type': 'Short' + }, + 278: { + 'name': 'RowsPerStrip', + 'type': 'Long' + }, + 279: { + 'name': 'StripByteCounts', + 'type': 'Long' + }, + 282: { + 'name': 'XResolution', + 'type': 'Rational' + }, + 283: { + 'name': 'YResolution', + 'type': 'Rational' + }, + 284: { + 'name': 'PlanarConfiguration', + 'type': 'Short' + }, + 290: { + 'name': 'GrayResponseUnit', + 'type': 'Short' + }, + 291: { + 'name': 'GrayResponseCurve', + 'type': 'Short' + }, + 292: { + 'name': 'T4Options', + 'type': 'Long' + }, + 293: { + 'name': 'T6Options', + 'type': 'Long' + }, + 296: { + 'name': 'ResolutionUnit', + 'type': 'Short' + }, + 301: { + 'name': 'TransferFunction', + 'type': 'Short' + }, + 305: { + 'name': 'Software', + 'type': 'Ascii' + }, + 306: { + 'name': 'DateTime', + 'type': 'Ascii' + }, + 315: { + 'name': 'Artist', + 'type': 'Ascii' + }, + 316: { + 'name': 'HostComputer', + 'type': 'Ascii' + }, + 317: { + 'name': 'Predictor', + 'type': 'Short' + }, + 318: { + 'name': 'WhitePoint', + 'type': 'Rational' + }, + 319: { + 'name': 'PrimaryChromaticities', + 'type': 'Rational' + }, + 320: { + 'name': 'ColorMap', + 'type': 'Short' + }, + 321: { + 'name': 'HalftoneHints', + 'type': 'Short' + }, + 322: { + 'name': 'TileWidth', + 'type': 'Short' + }, + 323: { + 'name': 'TileLength', + 'type': 'Short' + }, + 324: { + 'name': 'TileOffsets', + 'type': 'Short' + }, + 325: { + 'name': 'TileByteCounts', + 'type': 'Short' + }, + 330: { + 'name': 'SubIFDs', + 'type': 'Long' + }, + 332: { + 'name': 'InkSet', + 'type': 'Short' + }, + 333: { + 'name': 'InkNames', + 'type': 'Ascii' + }, + 334: { + 'name': 'NumberOfInks', + 'type': 'Short' + }, + 336: { + 'name': 'DotRange', + 'type': 'Byte' + }, + 337: { + 'name': 'TargetPrinter', + 'type': 'Ascii' + }, + 338: { + 'name': 'ExtraSamples', + 'type': 'Short' + }, + 339: { + 'name': 'SampleFormat', + 'type': 'Short' + }, + 340: { + 'name': 'SMinSampleValue', + 'type': 'Short' + }, + 341: { + 'name': 'SMaxSampleValue', + 'type': 'Short' + }, + 342: { + 'name': 'TransferRange', + 'type': 'Short' + }, + 343: { + 'name': 'ClipPath', + 'type': 'Byte' + }, + 344: { + 'name': 'XClipPathUnits', + 'type': 'Long' + }, + 345: { + 'name': 'YClipPathUnits', + 'type': 'Long' + }, + 346: { + 'name': 'Indexed', + 'type': 'Short' + }, + 347: { + 'name': 'JPEGTables', + 'type': 'Undefined' + }, + 351: { + 'name': 'OPIProxy', + 'type': 'Short' + }, + 512: { + 'name': 'JPEGProc', + 'type': 'Long' + }, + 513: { + 'name': 'JPEGInterchangeFormat', + 'type': 'Long' + }, + 514: { + 'name': 'JPEGInterchangeFormatLength', + 'type': 'Long' + }, + 515: { + 'name': 'JPEGRestartInterval', + 'type': 'Short' + }, + 517: { + 'name': 'JPEGLosslessPredictors', + 'type': 'Short' + }, + 518: { + 'name': 'JPEGPointTransforms', + 'type': 'Short' + }, + 519: { + 'name': 'JPEGQTables', + 'type': 'Long' + }, + 520: { + 'name': 'JPEGDCTables', + 'type': 'Long' + }, + 521: { + 'name': 'JPEGACTables', + 'type': 'Long' + }, + 529: { + 'name': 'YCbCrCoefficients', + 'type': 'Rational' + }, + 530: { + 'name': 'YCbCrSubSampling', + 'type': 'Short' + }, + 531: { + 'name': 'YCbCrPositioning', + 'type': 'Short' + }, + 532: { + 'name': 'ReferenceBlackWhite', + 'type': 'Rational' + }, + 700: { + 'name': 'XMLPacket', + 'type': 'Byte' + }, + 18246: { + 'name': 'Rating', + 'type': 'Short' + }, + 18249: { + 'name': 'RatingPercent', + 'type': 'Short' + }, + 32781: { + 'name': 'ImageID', + 'type': 'Ascii' + }, + 33421: { + 'name': 'CFARepeatPatternDim', + 'type': 'Short' + }, + 33422: { + 'name': 'CFAPattern', + 'type': 'Byte' + }, + 33423: { + 'name': 'BatteryLevel', + 'type': 'Rational' + }, + 33432: { + 'name': 'Copyright', + 'type': 'Ascii' + }, + 33434: { + 'name': 'ExposureTime', + 'type': 'Rational' + }, + 34377: { + 'name': 'ImageResources', + 'type': 'Byte' + }, + 34665: { + 'name': 'ExifTag', + 'type': 'Long' + }, + 34675: { + 'name': 'InterColorProfile', + 'type': 'Undefined' + }, + 34853: { + 'name': 'GPSTag', + 'type': 'Long' + }, + 34857: { + 'name': 'Interlace', + 'type': 'Short' + }, + 34858: { + 'name': 'TimeZoneOffset', + 'type': 'Long' + }, + 34859: { + 'name': 'SelfTimerMode', + 'type': 'Short' + }, + 37387: { + 'name': 'FlashEnergy', + 'type': 'Rational' + }, + 37388: { + 'name': 'SpatialFrequencyResponse', + 'type': 'Undefined' + }, + 37389: { + 'name': 'Noise', + 'type': 'Undefined' + }, + 37390: { + 'name': 'FocalPlaneXResolution', + 'type': 'Rational' + }, + 37391: { + 'name': 'FocalPlaneYResolution', + 'type': 'Rational' + }, + 37392: { + 'name': 'FocalPlaneResolutionUnit', + 'type': 'Short' + }, + 37393: { + 'name': 'ImageNumber', + 'type': 'Long' + }, + 37394: { + 'name': 'SecurityClassification', + 'type': 'Ascii' + }, + 37395: { + 'name': 'ImageHistory', + 'type': 'Ascii' + }, + 37397: { + 'name': 'ExposureIndex', + 'type': 'Rational' + }, + 37398: { + 'name': 'TIFFEPStandardID', + 'type': 'Byte' + }, + 37399: { + 'name': 'SensingMethod', + 'type': 'Short' + }, + 40091: { + 'name': 'XPTitle', + 'type': 'Byte' + }, + 40092: { + 'name': 'XPComment', + 'type': 'Byte' + }, + 40093: { + 'name': 'XPAuthor', + 'type': 'Byte' + }, + 40094: { + 'name': 'XPKeywords', + 'type': 'Byte' + }, + 40095: { + 'name': 'XPSubject', + 'type': 'Byte' + }, + 50341: { + 'name': 'PrintImageMatching', + 'type': 'Undefined' + }, + 50706: { + 'name': 'DNGVersion', + 'type': 'Byte' + }, + 50707: { + 'name': 'DNGBackwardVersion', + 'type': 'Byte' + }, + 50708: { + 'name': 'UniqueCameraModel', + 'type': 'Ascii' + }, + 50709: { + 'name': 'LocalizedCameraModel', + 'type': 'Byte' + }, + 50710: { + 'name': 'CFAPlaneColor', + 'type': 'Byte' + }, + 50711: { + 'name': 'CFALayout', + 'type': 'Short' + }, + 50712: { + 'name': 'LinearizationTable', + 'type': 'Short' + }, + 50713: { + 'name': 'BlackLevelRepeatDim', + 'type': 'Short' + }, + 50714: { + 'name': 'BlackLevel', + 'type': 'Rational' + }, + 50715: { + 'name': 'BlackLevelDeltaH', + 'type': 'SRational' + }, + 50716: { + 'name': 'BlackLevelDeltaV', + 'type': 'SRational' + }, + 50717: { + 'name': 'WhiteLevel', + 'type': 'Short' + }, + 50718: { + 'name': 'DefaultScale', + 'type': 'Rational' + }, + 50719: { + 'name': 'DefaultCropOrigin', + 'type': 'Short' + }, + 50720: { + 'name': 'DefaultCropSize', + 'type': 'Short' + }, + 50721: { + 'name': 'ColorMatrix1', + 'type': 'SRational' + }, + 50722: { + 'name': 'ColorMatrix2', + 'type': 'SRational' + }, + 50723: { + 'name': 'CameraCalibration1', + 'type': 'SRational' + }, + 50724: { + 'name': 'CameraCalibration2', + 'type': 'SRational' + }, + 50725: { + 'name': 'ReductionMatrix1', + 'type': 'SRational' + }, + 50726: { + 'name': 'ReductionMatrix2', + 'type': 'SRational' + }, + 50727: { + 'name': 'AnalogBalance', + 'type': 'Rational' + }, + 50728: { + 'name': 'AsShotNeutral', + 'type': 'Short' + }, + 50729: { + 'name': 'AsShotWhiteXY', + 'type': 'Rational' + }, + 50730: { + 'name': 'BaselineExposure', + 'type': 'SRational' + }, + 50731: { + 'name': 'BaselineNoise', + 'type': 'Rational' + }, + 50732: { + 'name': 'BaselineSharpness', + 'type': 'Rational' + }, + 50733: { + 'name': 'BayerGreenSplit', + 'type': 'Long' + }, + 50734: { + 'name': 'LinearResponseLimit', + 'type': 'Rational' + }, + 50735: { + 'name': 'CameraSerialNumber', + 'type': 'Ascii' + }, + 50736: { + 'name': 'LensInfo', + 'type': 'Rational' + }, + 50737: { + 'name': 'ChromaBlurRadius', + 'type': 'Rational' + }, + 50738: { + 'name': 'AntiAliasStrength', + 'type': 'Rational' + }, + 50739: { + 'name': 'ShadowScale', + 'type': 'SRational' + }, + 50740: { + 'name': 'DNGPrivateData', + 'type': 'Byte' + }, + 50741: { + 'name': 'MakerNoteSafety', + 'type': 'Short' + }, + 50778: { + 'name': 'CalibrationIlluminant1', + 'type': 'Short' + }, + 50779: { + 'name': 'CalibrationIlluminant2', + 'type': 'Short' + }, + 50780: { + 'name': 'BestQualityScale', + 'type': 'Rational' + }, + 50781: { + 'name': 'RawDataUniqueID', + 'type': 'Byte' + }, + 50827: { + 'name': 'OriginalRawFileName', + 'type': 'Byte' + }, + 50828: { + 'name': 'OriginalRawFileData', + 'type': 'Undefined' + }, + 50829: { + 'name': 'ActiveArea', + 'type': 'Short' + }, + 50830: { + 'name': 'MaskedAreas', + 'type': 'Short' + }, + 50831: { + 'name': 'AsShotICCProfile', + 'type': 'Undefined' + }, + 50832: { + 'name': 'AsShotPreProfileMatrix', + 'type': 'SRational' + }, + 50833: { + 'name': 'CurrentICCProfile', + 'type': 'Undefined' + }, + 50834: { + 'name': 'CurrentPreProfileMatrix', + 'type': 'SRational' + }, + 50879: { + 'name': 'ColorimetricReference', + 'type': 'Short' + }, + 50931: { + 'name': 'CameraCalibrationSignature', + 'type': 'Byte' + }, + 50932: { + 'name': 'ProfileCalibrationSignature', + 'type': 'Byte' + }, + 50934: { + 'name': 'AsShotProfileName', + 'type': 'Byte' + }, + 50935: { + 'name': 'NoiseReductionApplied', + 'type': 'Rational' + }, + 50936: { + 'name': 'ProfileName', + 'type': 'Byte' + }, + 50937: { + 'name': 'ProfileHueSatMapDims', + 'type': 'Long' + }, + 50938: { + 'name': 'ProfileHueSatMapData1', + 'type': 'Float' + }, + 50939: { + 'name': 'ProfileHueSatMapData2', + 'type': 'Float' + }, + 50940: { + 'name': 'ProfileToneCurve', + 'type': 'Float' + }, + 50941: { + 'name': 'ProfileEmbedPolicy', + 'type': 'Long' + }, + 50942: { + 'name': 'ProfileCopyright', + 'type': 'Byte' + }, + 50964: { + 'name': 'ForwardMatrix1', + 'type': 'SRational' + }, + 50965: { + 'name': 'ForwardMatrix2', + 'type': 'SRational' + }, + 50966: { + 'name': 'PreviewApplicationName', + 'type': 'Byte' + }, + 50967: { + 'name': 'PreviewApplicationVersion', + 'type': 'Byte' + }, + 50968: { + 'name': 'PreviewSettingsName', + 'type': 'Byte' + }, + 50969: { + 'name': 'PreviewSettingsDigest', + 'type': 'Byte' + }, + 50970: { + 'name': 'PreviewColorSpace', + 'type': 'Long' + }, + 50971: { + 'name': 'PreviewDateTime', + 'type': 'Ascii' + }, + 50972: { + 'name': 'RawImageDigest', + 'type': 'Undefined' + }, + 50973: { + 'name': 'OriginalRawFileDigest', + 'type': 'Undefined' + }, + 50974: { + 'name': 'SubTileBlockSize', + 'type': 'Long' + }, + 50975: { + 'name': 'RowInterleaveFactor', + 'type': 'Long' + }, + 50981: { + 'name': 'ProfileLookTableDims', + 'type': 'Long' + }, + 50982: { + 'name': 'ProfileLookTableData', + 'type': 'Float' + }, + 51008: { + 'name': 'OpcodeList1', + 'type': 'Undefined' + }, + 51009: { + 'name': 'OpcodeList2', + 'type': 'Undefined' + }, + 51022: { + 'name': 'OpcodeList3', + 'type': 'Undefined' + } + }, + 'Exif': { + 33434: { + 'name': 'ExposureTime', + 'type': 'Rational' + }, + 33437: { + 'name': 'FNumber', + 'type': 'Rational' + }, + 34850: { + 'name': 'ExposureProgram', + 'type': 'Short' + }, + 34852: { + 'name': 'SpectralSensitivity', + 'type': 'Ascii' + }, + 34855: { + 'name': 'ISOSpeedRatings', + 'type': 'Short' + }, + 34856: { + 'name': 'OECF', + 'type': 'Undefined' + }, + 34864: { + 'name': 'SensitivityType', + 'type': 'Short' + }, + 34865: { + 'name': 'StandardOutputSensitivity', + 'type': 'Long' + }, + 34866: { + 'name': 'RecommendedExposureIndex', + 'type': 'Long' + }, + 34867: { + 'name': 'ISOSpeed', + 'type': 'Long' + }, + 34868: { + 'name': 'ISOSpeedLatitudeyyy', + 'type': 'Long' + }, + 34869: { + 'name': 'ISOSpeedLatitudezzz', + 'type': 'Long' + }, + 36864: { + 'name': 'ExifVersion', + 'type': 'Undefined' + }, + 36867: { + 'name': 'DateTimeOriginal', + 'type': 'Ascii' + }, + 36868: { + 'name': 'DateTimeDigitized', + 'type': 'Ascii' + }, + 37121: { + 'name': 'ComponentsConfiguration', + 'type': 'Undefined' + }, + 37122: { + 'name': 'CompressedBitsPerPixel', + 'type': 'Rational' + }, + 37377: { + 'name': 'ShutterSpeedValue', + 'type': 'SRational' + }, + 37378: { + 'name': 'ApertureValue', + 'type': 'Rational' + }, + 37379: { + 'name': 'BrightnessValue', + 'type': 'SRational' + }, + 37380: { + 'name': 'ExposureBiasValue', + 'type': 'SRational' + }, + 37381: { + 'name': 'MaxApertureValue', + 'type': 'Rational' + }, + 37382: { + 'name': 'SubjectDistance', + 'type': 'Rational' + }, + 37383: { + 'name': 'MeteringMode', + 'type': 'Short' + }, + 37384: { + 'name': 'LightSource', + 'type': 'Short' + }, + 37385: { + 'name': 'Flash', + 'type': 'Short' + }, + 37386: { + 'name': 'FocalLength', + 'type': 'Rational' + }, + 37396: { + 'name': 'SubjectArea', + 'type': 'Short' + }, + 37500: { + 'name': 'MakerNote', + 'type': 'Undefined' + }, + 37510: { + 'name': 'UserComment', + 'type': 'Ascii' + }, + 37520: { + 'name': 'SubSecTime', + 'type': 'Ascii' + }, + 37521: { + 'name': 'SubSecTimeOriginal', + 'type': 'Ascii' + }, + 37522: { + 'name': 'SubSecTimeDigitized', + 'type': 'Ascii' + }, + 40960: { + 'name': 'FlashpixVersion', + 'type': 'Undefined' + }, + 40961: { + 'name': 'ColorSpace', + 'type': 'Short' + }, + 40962: { + 'name': 'PixelXDimension', + 'type': 'Long' + }, + 40963: { + 'name': 'PixelYDimension', + 'type': 'Long' + }, + 40964: { + 'name': 'RelatedSoundFile', + 'type': 'Ascii' + }, + 40965: { + 'name': 'InteroperabilityTag', + 'type': 'Long' + }, + 41483: { + 'name': 'FlashEnergy', + 'type': 'Rational' + }, + 41484: { + 'name': 'SpatialFrequencyResponse', + 'type': 'Undefined' + }, + 41486: { + 'name': 'FocalPlaneXResolution', + 'type': 'Rational' + }, + 41487: { + 'name': 'FocalPlaneYResolution', + 'type': 'Rational' + }, + 41488: { + 'name': 'FocalPlaneResolutionUnit', + 'type': 'Short' + }, + 41492: { + 'name': 'SubjectLocation', + 'type': 'Short' + }, + 41493: { + 'name': 'ExposureIndex', + 'type': 'Rational' + }, + 41495: { + 'name': 'SensingMethod', + 'type': 'Short' + }, + 41728: { + 'name': 'FileSource', + 'type': 'Undefined' + }, + 41729: { + 'name': 'SceneType', + 'type': 'Undefined' + }, + 41730: { + 'name': 'CFAPattern', + 'type': 'Undefined' + }, + 41985: { + 'name': 'CustomRendered', + 'type': 'Short' + }, + 41986: { + 'name': 'ExposureMode', + 'type': 'Short' + }, + 41987: { + 'name': 'WhiteBalance', + 'type': 'Short' + }, + 41988: { + 'name': 'DigitalZoomRatio', + 'type': 'Rational' + }, + 41989: { + 'name': 'FocalLengthIn35mmFilm', + 'type': 'Short' + }, + 41990: { + 'name': 'SceneCaptureType', + 'type': 'Short' + }, + 41991: { + 'name': 'GainControl', + 'type': 'Short' + }, + 41992: { + 'name': 'Contrast', + 'type': 'Short' + }, + 41993: { + 'name': 'Saturation', + 'type': 'Short' + }, + 41994: { + 'name': 'Sharpness', + 'type': 'Short' + }, + 41995: { + 'name': 'DeviceSettingDescription', + 'type': 'Undefined' + }, + 41996: { + 'name': 'SubjectDistanceRange', + 'type': 'Short' + }, + 42016: { + 'name': 'ImageUniqueID', + 'type': 'Ascii' + }, + 42032: { + 'name': 'CameraOwnerName', + 'type': 'Ascii' + }, + 42033: { + 'name': 'BodySerialNumber', + 'type': 'Ascii' + }, + 42034: { + 'name': 'LensSpecification', + 'type': 'Rational' + }, + 42035: { + 'name': 'LensMake', + 'type': 'Ascii' + }, + 42036: { + 'name': 'LensModel', + 'type': 'Ascii' + }, + 42037: { + 'name': 'LensSerialNumber', + 'type': 'Ascii' + }, + 42240: { + 'name': 'Gamma', + 'type': 'Rational' + } + }, + 'GPS': { + 0: { + 'name': 'GPSVersionID', + 'type': 'Byte' + }, + 1: { + 'name': 'GPSLatitudeRef', + 'type': 'Ascii' + }, + 2: { + 'name': 'GPSLatitude', + 'type': 'Rational' + }, + 3: { + 'name': 'GPSLongitudeRef', + 'type': 'Ascii' + }, + 4: { + 'name': 'GPSLongitude', + 'type': 'Rational' + }, + 5: { + 'name': 'GPSAltitudeRef', + 'type': 'Byte' + }, + 6: { + 'name': 'GPSAltitude', + 'type': 'Rational' + }, + 7: { + 'name': 'GPSTimeStamp', + 'type': 'Rational' + }, + 8: { + 'name': 'GPSSatellites', + 'type': 'Ascii' + }, + 9: { + 'name': 'GPSStatus', + 'type': 'Ascii' + }, + 10: { + 'name': 'GPSMeasureMode', + 'type': 'Ascii' + }, + 11: { + 'name': 'GPSDOP', + 'type': 'Rational' + }, + 12: { + 'name': 'GPSSpeedRef', + 'type': 'Ascii' + }, + 13: { + 'name': 'GPSSpeed', + 'type': 'Rational' + }, + 14: { + 'name': 'GPSTrackRef', + 'type': 'Ascii' + }, + 15: { + 'name': 'GPSTrack', + 'type': 'Rational' + }, + 16: { + 'name': 'GPSImgDirectionRef', + 'type': 'Ascii' + }, + 17: { + 'name': 'GPSImgDirection', + 'type': 'Rational' + }, + 18: { + 'name': 'GPSMapDatum', + 'type': 'Ascii' + }, + 19: { + 'name': 'GPSDestLatitudeRef', + 'type': 'Ascii' + }, + 20: { + 'name': 'GPSDestLatitude', + 'type': 'Rational' + }, + 21: { + 'name': 'GPSDestLongitudeRef', + 'type': 'Ascii' + }, + 22: { + 'name': 'GPSDestLongitude', + 'type': 'Rational' + }, + 23: { + 'name': 'GPSDestBearingRef', + 'type': 'Ascii' + }, + 24: { + 'name': 'GPSDestBearing', + 'type': 'Rational' + }, + 25: { + 'name': 'GPSDestDistanceRef', + 'type': 'Ascii' + }, + 26: { + 'name': 'GPSDestDistance', + 'type': 'Rational' + }, + 27: { + 'name': 'GPSProcessingMethod', + 'type': 'Undefined' + }, + 28: { + 'name': 'GPSAreaInformation', + 'type': 'Undefined' + }, + 29: { + 'name': 'GPSDateStamp', + 'type': 'Ascii' + }, + 30: { + 'name': 'GPSDifferential', + 'type': 'Short' + }, + 31: { + 'name': 'GPSHPositioningError', + 'type': 'Rational' + } + }, + 'Interop': { + 1: { + 'name': 'InteroperabilityIndex', + 'type': 'Ascii' + } + }, + }; + TAGS["0th"] = TAGS["Image"]; + TAGS["1st"] = TAGS["Image"]; + that.TAGS = TAGS; + + + that.ImageIFD = { + ProcessingSoftware:11, + NewSubfileType:254, + SubfileType:255, + ImageWidth:256, + ImageLength:257, + BitsPerSample:258, + Compression:259, + PhotometricInterpretation:262, + Threshholding:263, + CellWidth:264, + CellLength:265, + FillOrder:266, + DocumentName:269, + ImageDescription:270, + Make:271, + Model:272, + StripOffsets:273, + Orientation:274, + SamplesPerPixel:277, + RowsPerStrip:278, + StripByteCounts:279, + XResolution:282, + YResolution:283, + PlanarConfiguration:284, + GrayResponseUnit:290, + GrayResponseCurve:291, + T4Options:292, + T6Options:293, + ResolutionUnit:296, + TransferFunction:301, + Software:305, + DateTime:306, + Artist:315, + HostComputer:316, + Predictor:317, + WhitePoint:318, + PrimaryChromaticities:319, + ColorMap:320, + HalftoneHints:321, + TileWidth:322, + TileLength:323, + TileOffsets:324, + TileByteCounts:325, + SubIFDs:330, + InkSet:332, + InkNames:333, + NumberOfInks:334, + DotRange:336, + TargetPrinter:337, + ExtraSamples:338, + SampleFormat:339, + SMinSampleValue:340, + SMaxSampleValue:341, + TransferRange:342, + ClipPath:343, + XClipPathUnits:344, + YClipPathUnits:345, + Indexed:346, + JPEGTables:347, + OPIProxy:351, + JPEGProc:512, + JPEGInterchangeFormat:513, + JPEGInterchangeFormatLength:514, + JPEGRestartInterval:515, + JPEGLosslessPredictors:517, + JPEGPointTransforms:518, + JPEGQTables:519, + JPEGDCTables:520, + JPEGACTables:521, + YCbCrCoefficients:529, + YCbCrSubSampling:530, + YCbCrPositioning:531, + ReferenceBlackWhite:532, + XMLPacket:700, + Rating:18246, + RatingPercent:18249, + ImageID:32781, + CFARepeatPatternDim:33421, + CFAPattern:33422, + BatteryLevel:33423, + Copyright:33432, + ExposureTime:33434, + ImageResources:34377, + ExifTag:34665, + InterColorProfile:34675, + GPSTag:34853, + Interlace:34857, + TimeZoneOffset:34858, + SelfTimerMode:34859, + FlashEnergy:37387, + SpatialFrequencyResponse:37388, + Noise:37389, + FocalPlaneXResolution:37390, + FocalPlaneYResolution:37391, + FocalPlaneResolutionUnit:37392, + ImageNumber:37393, + SecurityClassification:37394, + ImageHistory:37395, + ExposureIndex:37397, + TIFFEPStandardID:37398, + SensingMethod:37399, + XPTitle:40091, + XPComment:40092, + XPAuthor:40093, + XPKeywords:40094, + XPSubject:40095, + PrintImageMatching:50341, + DNGVersion:50706, + DNGBackwardVersion:50707, + UniqueCameraModel:50708, + LocalizedCameraModel:50709, + CFAPlaneColor:50710, + CFALayout:50711, + LinearizationTable:50712, + BlackLevelRepeatDim:50713, + BlackLevel:50714, + BlackLevelDeltaH:50715, + BlackLevelDeltaV:50716, + WhiteLevel:50717, + DefaultScale:50718, + DefaultCropOrigin:50719, + DefaultCropSize:50720, + ColorMatrix1:50721, + ColorMatrix2:50722, + CameraCalibration1:50723, + CameraCalibration2:50724, + ReductionMatrix1:50725, + ReductionMatrix2:50726, + AnalogBalance:50727, + AsShotNeutral:50728, + AsShotWhiteXY:50729, + BaselineExposure:50730, + BaselineNoise:50731, + BaselineSharpness:50732, + BayerGreenSplit:50733, + LinearResponseLimit:50734, + CameraSerialNumber:50735, + LensInfo:50736, + ChromaBlurRadius:50737, + AntiAliasStrength:50738, + ShadowScale:50739, + DNGPrivateData:50740, + MakerNoteSafety:50741, + CalibrationIlluminant1:50778, + CalibrationIlluminant2:50779, + BestQualityScale:50780, + RawDataUniqueID:50781, + OriginalRawFileName:50827, + OriginalRawFileData:50828, + ActiveArea:50829, + MaskedAreas:50830, + AsShotICCProfile:50831, + AsShotPreProfileMatrix:50832, + CurrentICCProfile:50833, + CurrentPreProfileMatrix:50834, + ColorimetricReference:50879, + CameraCalibrationSignature:50931, + ProfileCalibrationSignature:50932, + AsShotProfileName:50934, + NoiseReductionApplied:50935, + ProfileName:50936, + ProfileHueSatMapDims:50937, + ProfileHueSatMapData1:50938, + ProfileHueSatMapData2:50939, + ProfileToneCurve:50940, + ProfileEmbedPolicy:50941, + ProfileCopyright:50942, + ForwardMatrix1:50964, + ForwardMatrix2:50965, + PreviewApplicationName:50966, + PreviewApplicationVersion:50967, + PreviewSettingsName:50968, + PreviewSettingsDigest:50969, + PreviewColorSpace:50970, + PreviewDateTime:50971, + RawImageDigest:50972, + OriginalRawFileDigest:50973, + SubTileBlockSize:50974, + RowInterleaveFactor:50975, + ProfileLookTableDims:50981, + ProfileLookTableData:50982, + OpcodeList1:51008, + OpcodeList2:51009, + OpcodeList3:51022, + NoiseProfile:51041, + }; + + + that.ExifIFD = { + ExposureTime:33434, + FNumber:33437, + ExposureProgram:34850, + SpectralSensitivity:34852, + ISOSpeedRatings:34855, + OECF:34856, + SensitivityType:34864, + StandardOutputSensitivity:34865, + RecommendedExposureIndex:34866, + ISOSpeed:34867, + ISOSpeedLatitudeyyy:34868, + ISOSpeedLatitudezzz:34869, + ExifVersion:36864, + DateTimeOriginal:36867, + DateTimeDigitized:36868, + ComponentsConfiguration:37121, + CompressedBitsPerPixel:37122, + ShutterSpeedValue:37377, + ApertureValue:37378, + BrightnessValue:37379, + ExposureBiasValue:37380, + MaxApertureValue:37381, + SubjectDistance:37382, + MeteringMode:37383, + LightSource:37384, + Flash:37385, + FocalLength:37386, + SubjectArea:37396, + MakerNote:37500, + UserComment:37510, + SubSecTime:37520, + SubSecTimeOriginal:37521, + SubSecTimeDigitized:37522, + FlashpixVersion:40960, + ColorSpace:40961, + PixelXDimension:40962, + PixelYDimension:40963, + RelatedSoundFile:40964, + InteroperabilityTag:40965, + FlashEnergy:41483, + SpatialFrequencyResponse:41484, + FocalPlaneXResolution:41486, + FocalPlaneYResolution:41487, + FocalPlaneResolutionUnit:41488, + SubjectLocation:41492, + ExposureIndex:41493, + SensingMethod:41495, + FileSource:41728, + SceneType:41729, + CFAPattern:41730, + CustomRendered:41985, + ExposureMode:41986, + WhiteBalance:41987, + DigitalZoomRatio:41988, + FocalLengthIn35mmFilm:41989, + SceneCaptureType:41990, + GainControl:41991, + Contrast:41992, + Saturation:41993, + Sharpness:41994, + DeviceSettingDescription:41995, + SubjectDistanceRange:41996, + ImageUniqueID:42016, + CameraOwnerName:42032, + BodySerialNumber:42033, + LensSpecification:42034, + LensMake:42035, + LensModel:42036, + LensSerialNumber:42037, + Gamma:42240, + }; + + + that.GPSIFD = { + GPSVersionID:0, + GPSLatitudeRef:1, + GPSLatitude:2, + GPSLongitudeRef:3, + GPSLongitude:4, + GPSAltitudeRef:5, + GPSAltitude:6, + GPSTimeStamp:7, + GPSSatellites:8, + GPSStatus:9, + GPSMeasureMode:10, + GPSDOP:11, + GPSSpeedRef:12, + GPSSpeed:13, + GPSTrackRef:14, + GPSTrack:15, + GPSImgDirectionRef:16, + GPSImgDirection:17, + GPSMapDatum:18, + GPSDestLatitudeRef:19, + GPSDestLatitude:20, + GPSDestLongitudeRef:21, + GPSDestLongitude:22, + GPSDestBearingRef:23, + GPSDestBearing:24, + GPSDestDistanceRef:25, + GPSDestDistance:26, + GPSProcessingMethod:27, + GPSAreaInformation:28, + GPSDateStamp:29, + GPSDifferential:30, + GPSHPositioningError:31, + }; + + + that.InteropIFD = { + InteroperabilityIndex:1, + }; + + that.GPSHelper = { + degToDmsRational:function (degFloat) { + var minFloat = degFloat % 1 * 60; + var secFloat = minFloat % 1 * 60; + var deg = Math.floor(degFloat); + var min = Math.floor(minFloat); + var sec = Math.round(secFloat * 100); + + return [[deg, 1], [min, 1], [sec, 100]]; + } + }; + + + if (typeof exports !== 'undefined') { + if (typeof module !== 'undefined' && module.exports) { + exports = module.exports = that; + } + exports.piexif = that; + } else { + window.piexif = that; + } + +})(); diff --git a/static/plugins/bootstrap-fileinput/js/plugins/piexif.min.js b/static/plugins/bootstrap-fileinput/js/plugins/piexif.min.js new file mode 100644 index 00000000..10d61245 --- /dev/null +++ b/static/plugins/bootstrap-fileinput/js/plugins/piexif.min.js @@ -0,0 +1 @@ +!function(){"use strict";function e(e){return JSON.parse(JSON.stringify(e))}function t(e){for(var t=y(e);"ÿà"<=t[1].slice(0,2)&&t[1].slice(0,2)<="ÿï";)t=[t[0]].concat(t.slice(2));return t.join("")}function a(e){return s(">"+p("B",e.length),e)}function i(e){return s(">"+p("H",e.length),e)}function n(e){return s(">"+p("L",e.length),e)}function r(e,t,r){var o,l,m,y,c="",S="";if("Byte"==t)o=e.length,4>=o?S=a(e)+p("\x00",4-o):(S=s(">L",[r]),c=a(e));else if("Short"==t)o=e.length,2>=o?S=i(e)+p("\x00\x00",2-o):(S=s(">L",[r]),c=i(e));else if("Long"==t)o=e.length,1>=o?S=n(e):(S=s(">L",[r]),c=n(e));else if("Ascii"==t)l=e+"\x00",o=l.length,o>4?(S=s(">L",[r]),c=l):S=l+p("\x00",4-o);else if("Rational"==t){if("number"==typeof e[0])o=1,m=e[0],y=e[1],l=s(">L",[m])+s(">L",[y]);else{o=e.length,l="";for(var f=0;o>f;f++)m=e[f][0],y=e[f][1],l+=s(">L",[m])+s(">L",[y])}S=s(">L",[r]),c=l}else if("SRational"==t){if("number"==typeof e[0])o=1,m=e[0],y=e[1],l=s(">l",[m])+s(">l",[y]);else{o=e.length,l="";for(var f=0;o>f;f++)m=e[f][0],y=e[f][1],l+=s(">l",[m])+s(">l",[y])}S=s(">L",[r]),c=l}else"Undefined"==t&&(o=e.length,o>4?(S=s(">L",[r]),c=e):S=e+p("\x00",4-o));var h=s(">L",[o]);return[h,S,c]}function o(e,t,a){var i,n=8,o=Object.keys(e).length,l=s(">H",[o]);i=["0th","1st"].indexOf(t)>-1?2+12*o+4:2+12*o;var m,p="",y="";for(var m in e)if("string"==typeof m&&(m=parseInt(m)),!("0th"==t&&[34665,34853].indexOf(m)>-1||"Exif"==t&&40965==m||"1st"==t&&[513,514].indexOf(m)>-1)){var c=e[m],S=s(">H",[m]),f=u[t][m].type,h=s(">H",[g[f]]);"number"==typeof c&&(c=[c]);var d=n+i+a+y.length,P=r(c,f,d),C=P[0],R=P[1],L=P[2];p+=S+h+C+R,y+=L}return[l+p,y]}function l(e){var t,a;if("ÿØ"==e.slice(0,2))t=y(e),a=c(t),a?this.tiftag=a.slice(10):this.tiftag=null;else if(["II","MM"].indexOf(e.slice(0,2))>-1)this.tiftag=e;else{if("Exif"!=e.slice(0,4))throw"Given file is neither JPEG nor TIFF.";this.tiftag=e.slice(6)}}function s(e,t){if(!(t instanceof Array))throw"'pack' error. Got invalid type argument.";if(e.length-1!=t.length)throw"'pack' error. "+(e.length-1)+" marks, "+t.length+" elements.";var a;if("<"==e[0])a=!0;else{if(">"!=e[0])throw"";a=!1}for(var i="",n=1,r=null,o=null,l=null;o=e[n];){if("b"==o.toLowerCase()){if(r=t[n-1],"b"==o&&0>r&&(r+=256),r>255||0>r)throw"'pack' error.";l=String.fromCharCode(r)}else if("H"==o){if(r=t[n-1],r>65535||0>r)throw"'pack' error.";l=String.fromCharCode(Math.floor(r%65536/256))+String.fromCharCode(r%256),a&&(l=l.split("").reverse().join(""))}else{if("l"!=o.toLowerCase())throw"'pack' error.";if(r=t[n-1],"l"==o&&0>r&&(r+=4294967296),r>4294967295||0>r)throw"'pack' error.";l=String.fromCharCode(Math.floor(r/16777216))+String.fromCharCode(Math.floor(r%16777216/65536))+String.fromCharCode(Math.floor(r%65536/256))+String.fromCharCode(r%256),a&&(l=l.split("").reverse().join(""))}i+=l,n+=1}return i}function m(e,t){if("string"!=typeof t)throw"'unpack' error. Got invalid type argument.";for(var a=0,i=1;i"!=e[0])throw"'unpack' error.";n=!1}for(var r=[],o=0,l=1,s=null,m=null,p=null,y="";m=e[l];){if("b"==m.toLowerCase())p=1,y=t.slice(o,o+p),s=y.charCodeAt(0),"b"==m&&s>=128&&(s-=256);else if("H"==m)p=2,y=t.slice(o,o+p),n&&(y=y.split("").reverse().join("")),s=256*y.charCodeAt(0)+y.charCodeAt(1);else{if("l"!=m.toLowerCase())throw"'unpack' error. "+m;p=4,y=t.slice(o,o+p),n&&(y=y.split("").reverse().join("")),s=16777216*y.charCodeAt(0)+65536*y.charCodeAt(1)+256*y.charCodeAt(2)+y.charCodeAt(3),"l"==m&&s>=2147483648&&(s-=4294967296)}r.push(s),o+=p,l+=1}return r}function p(e,t){for(var a="",i=0;t>i;i++)a+=e;return a}function y(e){if("ÿØ"!=e.slice(0,2))throw"Given data isn't JPEG.";for(var t=2,a=["ÿØ"];;){if("ÿÚ"==e.slice(t,t+2)){a.push(e.slice(t));break}var i=m(">H",e.slice(t+2,t+4))[0],n=t+i+2;if(a.push(e.slice(t,n)),t=n,t>=e.length)throw"Wrong JPEG data."}return a}function c(e){for(var t,a=0;aH",[e.length+2])+e,n=y(t),r=S(n,i);return a&&(r="data:image/jpeg;base64,"+h(r)),r},f.load=function(e){var t;if("string"!=typeof e)throw"'load' gots invalid type argument.";if("ÿØ"==e.slice(0,2))t=e;else if("data:image/jpeg;base64,"==e.slice(0,23)||"data:image/jpg;base64,"==e.slice(0,22))t=d(e.split(",")[1]);else{if("Exif"!=e.slice(0,4))throw"'load' gots invalid file data.";t=e.slice(6)}var a={"0th":{},Exif:{},GPS:{},Interop:{},"1st":{},thumbnail:null},i=new l(t);if(null===i.tiftag)return a;"II"==i.tiftag.slice(0,2)?i.endian_mark="<":i.endian_mark=">";var n=m(i.endian_mark+"L",i.tiftag.slice(4,8))[0];a["0th"]=i.get_ifd(n,"0th");var r=a["0th"].first_ifd_pointer;if(delete a["0th"].first_ifd_pointer,34665 in a["0th"]&&(n=a["0th"][34665],a.Exif=i.get_ifd(n,"Exif")),34853 in a["0th"]&&(n=a["0th"][34853],a.GPS=i.get_ifd(n,"GPS")),40965 in a.Exif&&(n=a.Exif[40965],a.Interop=i.get_ifd(n,"Interop")),"\x00\x00\x00\x00"!=r&&(n=m(i.endian_mark+"L",r)[0],a["1st"]=i.get_ifd(n,"1st"),513 in a["1st"]&&514 in a["1st"])){var o=a["1st"][513]+a["1st"][514],s=i.tiftag.slice(a["1st"][513],o);a.thumbnail=s}return a},f.dump=function(a){var i,n,r,l,m,p=8,y=e(a),c="Exif\x00\x00MM\x00*\x00\x00\x00\b",S=!1,h=!1,d=!1,u=!1;i="0th"in y?y["0th"]:{},"Exif"in y&&Object.keys(y.Exif).length||"Interop"in y&&Object.keys(y.Interop).length?(i[34665]=1,S=!0,n=y.Exif,"Interop"in y&&Object.keys(y.Interop).length?(n[40965]=1,d=!0,r=y.Interop):Object.keys(n).indexOf(f.ExifIFD.InteroperabilityTag.toString())>-1&&delete n[40965]):Object.keys(i).indexOf(f.ImageIFD.ExifTag.toString())>-1&&delete i[34665],"GPS"in y&&Object.keys(y.GPS).length?(i[f.ImageIFD.GPSTag]=1,h=!0,l=y.GPS):Object.keys(i).indexOf(f.ImageIFD.GPSTag.toString())>-1&&delete i[f.ImageIFD.GPSTag],"1st"in y&&"thumbnail"in y&&null!=y.thumbnail&&(u=!0,y["1st"][513]=1,y["1st"][514]=1,m=y["1st"]);var P,C,R,L,x,I=o(i,"0th",0),D=I[0].length+12*S+12*h+4+I[1].length,G="",A=0,v="",b=0,T="",k=0,w="";if(S&&(P=o(n,"Exif",D),A=P[0].length+12*d+P[1].length),h&&(C=o(l,"GPS",D+A),v=C.join(""),b=v.length),d){var F=D+A+b;R=o(r,"Interop",F),T=R.join(""),k=T.length}if(u){var F=D+A+b+k;if(L=o(m,"1st",F),x=t(y.thumbnail),x.length>64e3)throw"Given thumbnail is too large. max 64kB"}var B="",E="",M="",O="\x00\x00\x00\x00";if(S){var N=p+D,U=s(">L",[N]),_=34665,H=s(">H",[_]),j=s(">H",[g.Long]),V=s(">L",[1]);B=H+j+V+U}if(h){var N=p+D+A,U=s(">L",[N]),_=34853,H=s(">H",[_]),j=s(">H",[g.Long]),V=s(">L",[1]);E=H+j+V+U}if(d){var N=p+D+A+b,U=s(">L",[N]),_=40965,H=s(">H",[_]),j=s(">H",[g.Long]),V=s(">L",[1]);M=H+j+V+U}if(u){var N=p+D+A+b+k;O=s(">L",[N]);var J=N+L[0].length+24+4+L[1].length,X="\x00\x00\x00\x00"+s(">L",[J]),z="\x00\x00\x00\x00"+s(">L",[x.length]);w=L[0]+X+z+"\x00\x00\x00\x00"+L[1]+x}var Y=I[0]+B+E+O+I[1];return S&&(G=P[0]+M+P[1]),c+Y+G+v+T+w},l.prototype={get_ifd:function(e,t){var a,i={},n=m(this.endian_mark+"H",this.tiftag.slice(e,e+2))[0],r=e+2;a=["0th","1st"].indexOf(t)>-1?"Image":t;for(var o=0;n>o;o++){e=r+12*o;var l=m(this.endian_mark+"H",this.tiftag.slice(e,e+2))[0],s=m(this.endian_mark+"H",this.tiftag.slice(e+2,e+4))[0],p=m(this.endian_mark+"L",this.tiftag.slice(e+4,e+8))[0],y=this.tiftag.slice(e+8,e+12),c=[s,p,y];l in u[a]&&(i[l]=this.convert_value(c))}return"0th"==t&&(e=r+12*n,i.first_ifd_pointer=this.tiftag.slice(e,e+4)),i},convert_value:function(e){var t,a=null,i=e[0],n=e[1],r=e[2];if(1==i)n>4?(t=m(this.endian_mark+"L",r)[0],a=m(this.endian_mark+p("B",n),this.tiftag.slice(t,t+n))):a=m(this.endian_mark+p("B",n),r.slice(0,n));else if(2==i)n>4?(t=m(this.endian_mark+"L",r)[0],a=this.tiftag.slice(t,t+n-1)):a=r.slice(0,n-1);else if(3==i)n>2?(t=m(this.endian_mark+"L",r)[0],a=m(this.endian_mark+p("H",n),this.tiftag.slice(t,t+2*n))):a=m(this.endian_mark+p("H",n),r.slice(0,2*n));else if(4==i)n>1?(t=m(this.endian_mark+"L",r)[0],a=m(this.endian_mark+p("L",n),this.tiftag.slice(t,t+4*n))):a=m(this.endian_mark+p("L",n),r);else if(5==i)if(t=m(this.endian_mark+"L",r)[0],n>1){a=[];for(var o=0;n>o;o++)a.push([m(this.endian_mark+"L",this.tiftag.slice(t+8*o,t+4+8*o))[0],m(this.endian_mark+"L",this.tiftag.slice(t+4+8*o,t+8+8*o))[0]])}else a=[m(this.endian_mark+"L",this.tiftag.slice(t,t+4))[0],m(this.endian_mark+"L",this.tiftag.slice(t+4,t+8))[0]];else if(7==i)n>4?(t=m(this.endian_mark+"L",r)[0],a=this.tiftag.slice(t,t+n)):a=r.slice(0,n);else{if(10!=i)throw"Exif might be wrong. Got incorrect value type to decode. type:"+i;if(t=m(this.endian_mark+"L",r)[0],n>1){a=[];for(var o=0;n>o;o++)a.push([m(this.endian_mark+"l",this.tiftag.slice(t+8*o,t+4+8*o))[0],m(this.endian_mark+"l",this.tiftag.slice(t+4+8*o,t+8+8*o))[0]])}else a=[m(this.endian_mark+"l",this.tiftag.slice(t,t+4))[0],m(this.endian_mark+"l",this.tiftag.slice(t+4,t+8))[0]]}return a instanceof Array&&1==a.length?a[0]:a}},"undefined"!=typeof window&&"function"==typeof window.btoa)var h=window.btoa;if("undefined"==typeof h)var h=function(e){for(var t,a,i,n,r,o,l,s="",m=0,p="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";m>2,r=(3&t)<<4|a>>4,o=(15&a)<<2|i>>6,l=63&i,isNaN(a)?o=l=64:isNaN(i)&&(l=64),s=s+p.charAt(n)+p.charAt(r)+p.charAt(o)+p.charAt(l);return s};if("undefined"!=typeof window&&"function"==typeof window.atob)var d=window.atob;if("undefined"==typeof d)var d=function(e){var t,a,i,n,r,o,l,s="",m=0,p="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";for(e=e.replace(/[^A-Za-z0-9\+\/\=]/g,"");m>4,a=(15&r)<<4|o>>2,i=(3&o)<<6|l,s+=String.fromCharCode(t),64!=o&&(s+=String.fromCharCode(a)),64!=l&&(s+=String.fromCharCode(i));return s};var g={Byte:1,Ascii:2,Short:3,Long:4,Rational:5,Undefined:7,SLong:9,SRational:10},u={Image:{11:{name:"ProcessingSoftware",type:"Ascii"},254:{name:"NewSubfileType",type:"Long"},255:{name:"SubfileType",type:"Short"},256:{name:"ImageWidth",type:"Long"},257:{name:"ImageLength",type:"Long"},258:{name:"BitsPerSample",type:"Short"},259:{name:"Compression",type:"Short"},262:{name:"PhotometricInterpretation",type:"Short"},263:{name:"Threshholding",type:"Short"},264:{name:"CellWidth",type:"Short"},265:{name:"CellLength",type:"Short"},266:{name:"FillOrder",type:"Short"},269:{name:"DocumentName",type:"Ascii"},270:{name:"ImageDescription",type:"Ascii"},271:{name:"Make",type:"Ascii"},272:{name:"Model",type:"Ascii"},273:{name:"StripOffsets",type:"Long"},274:{name:"Orientation",type:"Short"},277:{name:"SamplesPerPixel",type:"Short"},278:{name:"RowsPerStrip",type:"Long"},279:{name:"StripByteCounts",type:"Long"},282:{name:"XResolution",type:"Rational"},283:{name:"YResolution",type:"Rational"},284:{name:"PlanarConfiguration",type:"Short"},290:{name:"GrayResponseUnit",type:"Short"},291:{name:"GrayResponseCurve",type:"Short"},292:{name:"T4Options",type:"Long"},293:{name:"T6Options",type:"Long"},296:{name:"ResolutionUnit",type:"Short"},301:{name:"TransferFunction",type:"Short"},305:{name:"Software",type:"Ascii"},306:{name:"DateTime",type:"Ascii"},315:{name:"Artist",type:"Ascii"},316:{name:"HostComputer",type:"Ascii"},317:{name:"Predictor",type:"Short"},318:{name:"WhitePoint",type:"Rational"},319:{name:"PrimaryChromaticities",type:"Rational"},320:{name:"ColorMap",type:"Short"},321:{name:"HalftoneHints",type:"Short"},322:{name:"TileWidth",type:"Short"},323:{name:"TileLength",type:"Short"},324:{name:"TileOffsets",type:"Short"},325:{name:"TileByteCounts",type:"Short"},330:{name:"SubIFDs",type:"Long"},332:{name:"InkSet",type:"Short"},333:{name:"InkNames",type:"Ascii"},334:{name:"NumberOfInks",type:"Short"},336:{name:"DotRange",type:"Byte"},337:{name:"TargetPrinter",type:"Ascii"},338:{name:"ExtraSamples",type:"Short"},339:{name:"SampleFormat",type:"Short"},340:{name:"SMinSampleValue",type:"Short"},341:{name:"SMaxSampleValue",type:"Short"},342:{name:"TransferRange",type:"Short"},343:{name:"ClipPath",type:"Byte"},344:{name:"XClipPathUnits",type:"Long"},345:{name:"YClipPathUnits",type:"Long"},346:{name:"Indexed",type:"Short"},347:{name:"JPEGTables",type:"Undefined"},351:{name:"OPIProxy",type:"Short"},512:{name:"JPEGProc",type:"Long"},513:{name:"JPEGInterchangeFormat",type:"Long"},514:{name:"JPEGInterchangeFormatLength",type:"Long"},515:{name:"JPEGRestartInterval",type:"Short"},517:{name:"JPEGLosslessPredictors",type:"Short"},518:{name:"JPEGPointTransforms",type:"Short"},519:{name:"JPEGQTables",type:"Long"},520:{name:"JPEGDCTables",type:"Long"},521:{name:"JPEGACTables",type:"Long"},529:{name:"YCbCrCoefficients",type:"Rational"},530:{name:"YCbCrSubSampling",type:"Short"},531:{name:"YCbCrPositioning",type:"Short"},532:{name:"ReferenceBlackWhite",type:"Rational"},700:{name:"XMLPacket",type:"Byte"},18246:{name:"Rating",type:"Short"},18249:{name:"RatingPercent",type:"Short"},32781:{name:"ImageID",type:"Ascii"},33421:{name:"CFARepeatPatternDim",type:"Short"},33422:{name:"CFAPattern",type:"Byte"},33423:{name:"BatteryLevel",type:"Rational"},33432:{name:"Copyright",type:"Ascii"},33434:{name:"ExposureTime",type:"Rational"},34377:{name:"ImageResources",type:"Byte"},34665:{name:"ExifTag",type:"Long"},34675:{name:"InterColorProfile",type:"Undefined"},34853:{name:"GPSTag",type:"Long"},34857:{name:"Interlace",type:"Short"},34858:{name:"TimeZoneOffset",type:"Long"},34859:{name:"SelfTimerMode",type:"Short"},37387:{name:"FlashEnergy",type:"Rational"},37388:{name:"SpatialFrequencyResponse",type:"Undefined"},37389:{name:"Noise",type:"Undefined"},37390:{name:"FocalPlaneXResolution",type:"Rational"},37391:{name:"FocalPlaneYResolution",type:"Rational"},37392:{name:"FocalPlaneResolutionUnit",type:"Short"},37393:{name:"ImageNumber",type:"Long"},37394:{name:"SecurityClassification",type:"Ascii"},37395:{name:"ImageHistory",type:"Ascii"},37397:{name:"ExposureIndex",type:"Rational"},37398:{name:"TIFFEPStandardID",type:"Byte"},37399:{name:"SensingMethod",type:"Short"},40091:{name:"XPTitle",type:"Byte"},40092:{name:"XPComment",type:"Byte"},40093:{name:"XPAuthor",type:"Byte"},40094:{name:"XPKeywords",type:"Byte"},40095:{name:"XPSubject",type:"Byte"},50341:{name:"PrintImageMatching",type:"Undefined"},50706:{name:"DNGVersion",type:"Byte"},50707:{name:"DNGBackwardVersion",type:"Byte"},50708:{name:"UniqueCameraModel",type:"Ascii"},50709:{name:"LocalizedCameraModel",type:"Byte"},50710:{name:"CFAPlaneColor",type:"Byte"},50711:{name:"CFALayout",type:"Short"},50712:{name:"LinearizationTable",type:"Short"},50713:{name:"BlackLevelRepeatDim",type:"Short"},50714:{name:"BlackLevel",type:"Rational"},50715:{name:"BlackLevelDeltaH",type:"SRational"},50716:{name:"BlackLevelDeltaV",type:"SRational"},50717:{name:"WhiteLevel",type:"Short"},50718:{name:"DefaultScale",type:"Rational"},50719:{name:"DefaultCropOrigin",type:"Short"},50720:{name:"DefaultCropSize",type:"Short"},50721:{name:"ColorMatrix1",type:"SRational"},50722:{name:"ColorMatrix2",type:"SRational"},50723:{name:"CameraCalibration1",type:"SRational"},50724:{name:"CameraCalibration2",type:"SRational"},50725:{name:"ReductionMatrix1",type:"SRational"},50726:{name:"ReductionMatrix2",type:"SRational"},50727:{name:"AnalogBalance",type:"Rational"},50728:{name:"AsShotNeutral",type:"Short"},50729:{name:"AsShotWhiteXY",type:"Rational"},50730:{name:"BaselineExposure",type:"SRational"},50731:{name:"BaselineNoise",type:"Rational"},50732:{name:"BaselineSharpness",type:"Rational"},50733:{name:"BayerGreenSplit",type:"Long"},50734:{name:"LinearResponseLimit",type:"Rational"},50735:{name:"CameraSerialNumber",type:"Ascii"},50736:{name:"LensInfo",type:"Rational"},50737:{name:"ChromaBlurRadius",type:"Rational"},50738:{name:"AntiAliasStrength",type:"Rational"},50739:{name:"ShadowScale",type:"SRational"},50740:{name:"DNGPrivateData",type:"Byte"},50741:{name:"MakerNoteSafety",type:"Short"},50778:{name:"CalibrationIlluminant1",type:"Short"},50779:{name:"CalibrationIlluminant2",type:"Short"},50780:{name:"BestQualityScale",type:"Rational"},50781:{name:"RawDataUniqueID",type:"Byte"},50827:{name:"OriginalRawFileName",type:"Byte"},50828:{name:"OriginalRawFileData",type:"Undefined"},50829:{name:"ActiveArea",type:"Short"},50830:{name:"MaskedAreas",type:"Short"},50831:{name:"AsShotICCProfile",type:"Undefined"},50832:{name:"AsShotPreProfileMatrix",type:"SRational"},50833:{name:"CurrentICCProfile",type:"Undefined"},50834:{name:"CurrentPreProfileMatrix",type:"SRational"},50879:{name:"ColorimetricReference",type:"Short"},50931:{name:"CameraCalibrationSignature",type:"Byte"},50932:{name:"ProfileCalibrationSignature",type:"Byte"},50934:{name:"AsShotProfileName",type:"Byte"},50935:{name:"NoiseReductionApplied",type:"Rational"},50936:{name:"ProfileName",type:"Byte"},50937:{name:"ProfileHueSatMapDims",type:"Long"},50938:{name:"ProfileHueSatMapData1",type:"Float"},50939:{name:"ProfileHueSatMapData2",type:"Float"},50940:{name:"ProfileToneCurve",type:"Float"},50941:{name:"ProfileEmbedPolicy",type:"Long"},50942:{name:"ProfileCopyright",type:"Byte"},50964:{name:"ForwardMatrix1",type:"SRational"},50965:{name:"ForwardMatrix2",type:"SRational"},50966:{name:"PreviewApplicationName",type:"Byte"},50967:{name:"PreviewApplicationVersion",type:"Byte"},50968:{name:"PreviewSettingsName",type:"Byte"},50969:{name:"PreviewSettingsDigest",type:"Byte"},50970:{name:"PreviewColorSpace",type:"Long"},50971:{name:"PreviewDateTime",type:"Ascii"},50972:{name:"RawImageDigest",type:"Undefined"},50973:{name:"OriginalRawFileDigest",type:"Undefined"},50974:{name:"SubTileBlockSize",type:"Long"},50975:{name:"RowInterleaveFactor",type:"Long"},50981:{name:"ProfileLookTableDims",type:"Long"},50982:{name:"ProfileLookTableData",type:"Float"},51008:{name:"OpcodeList1",type:"Undefined"},51009:{name:"OpcodeList2",type:"Undefined"},51022:{name:"OpcodeList3",type:"Undefined"}},Exif:{33434:{name:"ExposureTime",type:"Rational"},33437:{name:"FNumber",type:"Rational"},34850:{name:"ExposureProgram",type:"Short"},34852:{name:"SpectralSensitivity",type:"Ascii"},34855:{name:"ISOSpeedRatings",type:"Short"},34856:{name:"OECF",type:"Undefined"},34864:{name:"SensitivityType",type:"Short"},34865:{name:"StandardOutputSensitivity",type:"Long"},34866:{name:"RecommendedExposureIndex",type:"Long"},34867:{name:"ISOSpeed",type:"Long"},34868:{name:"ISOSpeedLatitudeyyy",type:"Long"},34869:{name:"ISOSpeedLatitudezzz",type:"Long"},36864:{name:"ExifVersion",type:"Undefined"},36867:{name:"DateTimeOriginal",type:"Ascii"},36868:{name:"DateTimeDigitized",type:"Ascii"},37121:{name:"ComponentsConfiguration",type:"Undefined"},37122:{name:"CompressedBitsPerPixel",type:"Rational"},37377:{name:"ShutterSpeedValue",type:"SRational"},37378:{name:"ApertureValue",type:"Rational"},37379:{name:"BrightnessValue",type:"SRational"},37380:{name:"ExposureBiasValue",type:"SRational"},37381:{name:"MaxApertureValue",type:"Rational"},37382:{name:"SubjectDistance",type:"Rational"},37383:{name:"MeteringMode",type:"Short"},37384:{name:"LightSource",type:"Short"},37385:{name:"Flash",type:"Short"},37386:{name:"FocalLength",type:"Rational"},37396:{name:"SubjectArea",type:"Short"},37500:{name:"MakerNote",type:"Undefined"},37510:{name:"UserComment",type:"Ascii"},37520:{name:"SubSecTime",type:"Ascii"},37521:{name:"SubSecTimeOriginal",type:"Ascii"},37522:{name:"SubSecTimeDigitized",type:"Ascii"},40960:{name:"FlashpixVersion",type:"Undefined"},40961:{name:"ColorSpace",type:"Short"},40962:{name:"PixelXDimension",type:"Long"},40963:{name:"PixelYDimension",type:"Long"},40964:{name:"RelatedSoundFile",type:"Ascii"},40965:{name:"InteroperabilityTag",type:"Long"},41483:{name:"FlashEnergy",type:"Rational"},41484:{name:"SpatialFrequencyResponse",type:"Undefined"},41486:{name:"FocalPlaneXResolution",type:"Rational"},41487:{name:"FocalPlaneYResolution",type:"Rational"},41488:{name:"FocalPlaneResolutionUnit",type:"Short"},41492:{name:"SubjectLocation",type:"Short"},41493:{name:"ExposureIndex",type:"Rational"},41495:{name:"SensingMethod",type:"Short"},41728:{name:"FileSource",type:"Undefined"},41729:{name:"SceneType",type:"Undefined"},41730:{name:"CFAPattern",type:"Undefined"},41985:{name:"CustomRendered",type:"Short"},41986:{name:"ExposureMode",type:"Short"},41987:{name:"WhiteBalance",type:"Short"},41988:{name:"DigitalZoomRatio",type:"Rational"},41989:{name:"FocalLengthIn35mmFilm",type:"Short"},41990:{name:"SceneCaptureType",type:"Short"},41991:{name:"GainControl",type:"Short"},41992:{name:"Contrast",type:"Short"},41993:{name:"Saturation",type:"Short"},41994:{name:"Sharpness",type:"Short"},41995:{name:"DeviceSettingDescription",type:"Undefined"},41996:{name:"SubjectDistanceRange",type:"Short"},42016:{name:"ImageUniqueID",type:"Ascii"},42032:{name:"CameraOwnerName",type:"Ascii"},42033:{name:"BodySerialNumber",type:"Ascii"},42034:{name:"LensSpecification",type:"Rational"},42035:{name:"LensMake",type:"Ascii"},42036:{name:"LensModel",type:"Ascii"},42037:{name:"LensSerialNumber",type:"Ascii"},42240:{name:"Gamma",type:"Rational"}},GPS:{0:{name:"GPSVersionID",type:"Byte"},1:{name:"GPSLatitudeRef",type:"Ascii"},2:{name:"GPSLatitude",type:"Rational"},3:{name:"GPSLongitudeRef",type:"Ascii"},4:{name:"GPSLongitude",type:"Rational"},5:{name:"GPSAltitudeRef",type:"Byte"},6:{name:"GPSAltitude",type:"Rational"},7:{name:"GPSTimeStamp",type:"Rational"},8:{name:"GPSSatellites",type:"Ascii"},9:{name:"GPSStatus",type:"Ascii"},10:{name:"GPSMeasureMode",type:"Ascii"},11:{name:"GPSDOP",type:"Rational"},12:{name:"GPSSpeedRef",type:"Ascii"},13:{name:"GPSSpeed",type:"Rational"},14:{name:"GPSTrackRef",type:"Ascii"},15:{name:"GPSTrack",type:"Rational"},16:{name:"GPSImgDirectionRef",type:"Ascii"},17:{name:"GPSImgDirection",type:"Rational"},18:{name:"GPSMapDatum",type:"Ascii"},19:{name:"GPSDestLatitudeRef",type:"Ascii"},20:{name:"GPSDestLatitude",type:"Rational"},21:{name:"GPSDestLongitudeRef",type:"Ascii"},22:{name:"GPSDestLongitude",type:"Rational"},23:{name:"GPSDestBearingRef",type:"Ascii"},24:{name:"GPSDestBearing",type:"Rational"},25:{name:"GPSDestDistanceRef",type:"Ascii"},26:{name:"GPSDestDistance",type:"Rational"},27:{name:"GPSProcessingMethod",type:"Undefined"},28:{name:"GPSAreaInformation",type:"Undefined"},29:{name:"GPSDateStamp",type:"Ascii"},30:{name:"GPSDifferential",type:"Short"},31:{name:"GPSHPositioningError",type:"Rational"}},Interop:{1:{name:"InteroperabilityIndex",type:"Ascii"}}};u["0th"]=u.Image,u["1st"]=u.Image,f.TAGS=u,f.ImageIFD={ProcessingSoftware:11,NewSubfileType:254,SubfileType:255,ImageWidth:256,ImageLength:257,BitsPerSample:258,Compression:259,PhotometricInterpretation:262,Threshholding:263,CellWidth:264,CellLength:265,FillOrder:266,DocumentName:269,ImageDescription:270,Make:271,Model:272,StripOffsets:273,Orientation:274,SamplesPerPixel:277,RowsPerStrip:278,StripByteCounts:279,XResolution:282,YResolution:283,PlanarConfiguration:284,GrayResponseUnit:290,GrayResponseCurve:291,T4Options:292,T6Options:293,ResolutionUnit:296,TransferFunction:301,Software:305,DateTime:306,Artist:315,HostComputer:316,Predictor:317,WhitePoint:318,PrimaryChromaticities:319,ColorMap:320,HalftoneHints:321,TileWidth:322,TileLength:323,TileOffsets:324,TileByteCounts:325,SubIFDs:330,InkSet:332,InkNames:333,NumberOfInks:334,DotRange:336,TargetPrinter:337,ExtraSamples:338,SampleFormat:339,SMinSampleValue:340,SMaxSampleValue:341,TransferRange:342,ClipPath:343,XClipPathUnits:344,YClipPathUnits:345,Indexed:346,JPEGTables:347,OPIProxy:351,JPEGProc:512,JPEGInterchangeFormat:513,JPEGInterchangeFormatLength:514,JPEGRestartInterval:515,JPEGLosslessPredictors:517,JPEGPointTransforms:518,JPEGQTables:519,JPEGDCTables:520,JPEGACTables:521,YCbCrCoefficients:529,YCbCrSubSampling:530,YCbCrPositioning:531,ReferenceBlackWhite:532,XMLPacket:700,Rating:18246,RatingPercent:18249,ImageID:32781,CFARepeatPatternDim:33421,CFAPattern:33422,BatteryLevel:33423,Copyright:33432,ExposureTime:33434,ImageResources:34377,ExifTag:34665,InterColorProfile:34675,GPSTag:34853,Interlace:34857,TimeZoneOffset:34858,SelfTimerMode:34859,FlashEnergy:37387,SpatialFrequencyResponse:37388,Noise:37389,FocalPlaneXResolution:37390,FocalPlaneYResolution:37391,FocalPlaneResolutionUnit:37392,ImageNumber:37393,SecurityClassification:37394,ImageHistory:37395,ExposureIndex:37397,TIFFEPStandardID:37398,SensingMethod:37399,XPTitle:40091,XPComment:40092,XPAuthor:40093,XPKeywords:40094,XPSubject:40095,PrintImageMatching:50341,DNGVersion:50706,DNGBackwardVersion:50707,UniqueCameraModel:50708,LocalizedCameraModel:50709,CFAPlaneColor:50710,CFALayout:50711,LinearizationTable:50712,BlackLevelRepeatDim:50713,BlackLevel:50714,BlackLevelDeltaH:50715,BlackLevelDeltaV:50716,WhiteLevel:50717,DefaultScale:50718,DefaultCropOrigin:50719,DefaultCropSize:50720,ColorMatrix1:50721,ColorMatrix2:50722,CameraCalibration1:50723,CameraCalibration2:50724,ReductionMatrix1:50725,ReductionMatrix2:50726,AnalogBalance:50727,AsShotNeutral:50728,AsShotWhiteXY:50729,BaselineExposure:50730,BaselineNoise:50731,BaselineSharpness:50732,BayerGreenSplit:50733,LinearResponseLimit:50734,CameraSerialNumber:50735,LensInfo:50736,ChromaBlurRadius:50737,AntiAliasStrength:50738,ShadowScale:50739,DNGPrivateData:50740,MakerNoteSafety:50741,CalibrationIlluminant1:50778,CalibrationIlluminant2:50779,BestQualityScale:50780,RawDataUniqueID:50781,OriginalRawFileName:50827,OriginalRawFileData:50828,ActiveArea:50829,MaskedAreas:50830,AsShotICCProfile:50831,AsShotPreProfileMatrix:50832,CurrentICCProfile:50833,CurrentPreProfileMatrix:50834,ColorimetricReference:50879,CameraCalibrationSignature:50931,ProfileCalibrationSignature:50932,AsShotProfileName:50934,NoiseReductionApplied:50935,ProfileName:50936,ProfileHueSatMapDims:50937,ProfileHueSatMapData1:50938,ProfileHueSatMapData2:50939,ProfileToneCurve:50940,ProfileEmbedPolicy:50941,ProfileCopyright:50942,ForwardMatrix1:50964,ForwardMatrix2:50965,PreviewApplicationName:50966,PreviewApplicationVersion:50967,PreviewSettingsName:50968,PreviewSettingsDigest:50969,PreviewColorSpace:50970,PreviewDateTime:50971,RawImageDigest:50972,OriginalRawFileDigest:50973,SubTileBlockSize:50974,RowInterleaveFactor:50975,ProfileLookTableDims:50981,ProfileLookTableData:50982,OpcodeList1:51008,OpcodeList2:51009,OpcodeList3:51022,NoiseProfile:51041},f.ExifIFD={ExposureTime:33434,FNumber:33437,ExposureProgram:34850,SpectralSensitivity:34852,ISOSpeedRatings:34855,OECF:34856,SensitivityType:34864,StandardOutputSensitivity:34865,RecommendedExposureIndex:34866,ISOSpeed:34867,ISOSpeedLatitudeyyy:34868,ISOSpeedLatitudezzz:34869,ExifVersion:36864,DateTimeOriginal:36867,DateTimeDigitized:36868,ComponentsConfiguration:37121,CompressedBitsPerPixel:37122,ShutterSpeedValue:37377,ApertureValue:37378,BrightnessValue:37379,ExposureBiasValue:37380,MaxApertureValue:37381,SubjectDistance:37382,MeteringMode:37383,LightSource:37384,Flash:37385,FocalLength:37386,SubjectArea:37396,MakerNote:37500,UserComment:37510,SubSecTime:37520,SubSecTimeOriginal:37521,SubSecTimeDigitized:37522,FlashpixVersion:40960,ColorSpace:40961,PixelXDimension:40962,PixelYDimension:40963,RelatedSoundFile:40964,InteroperabilityTag:40965,FlashEnergy:41483,SpatialFrequencyResponse:41484,FocalPlaneXResolution:41486,FocalPlaneYResolution:41487,FocalPlaneResolutionUnit:41488,SubjectLocation:41492,ExposureIndex:41493,SensingMethod:41495,FileSource:41728,SceneType:41729,CFAPattern:41730,CustomRendered:41985,ExposureMode:41986,WhiteBalance:41987,DigitalZoomRatio:41988,FocalLengthIn35mmFilm:41989,SceneCaptureType:41990,GainControl:41991,Contrast:41992,Saturation:41993,Sharpness:41994,DeviceSettingDescription:41995,SubjectDistanceRange:41996,ImageUniqueID:42016,CameraOwnerName:42032,BodySerialNumber:42033,LensSpecification:42034,LensMake:42035,LensModel:42036,LensSerialNumber:42037,Gamma:42240},f.GPSIFD={GPSVersionID:0,GPSLatitudeRef:1,GPSLatitude:2,GPSLongitudeRef:3,GPSLongitude:4,GPSAltitudeRef:5,GPSAltitude:6,GPSTimeStamp:7,GPSSatellites:8,GPSStatus:9,GPSMeasureMode:10,GPSDOP:11,GPSSpeedRef:12,GPSSpeed:13,GPSTrackRef:14,GPSTrack:15,GPSImgDirectionRef:16,GPSImgDirection:17,GPSMapDatum:18,GPSDestLatitudeRef:19,GPSDestLatitude:20,GPSDestLongitudeRef:21,GPSDestLongitude:22,GPSDestBearingRef:23,GPSDestBearing:24,GPSDestDistanceRef:25,GPSDestDistance:26,GPSProcessingMethod:27,GPSAreaInformation:28,GPSDateStamp:29,GPSDifferential:30,GPSHPositioningError:31},f.InteropIFD={InteroperabilityIndex:1},f.GPSHelper={degToDmsRational:function(e){var t=e%1*60,a=t%1*60,i=Math.floor(e),n=Math.floor(t),r=Math.round(100*a);return[[i,1],[n,1],[r,100]]}},"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=f),exports.piexif=f):window.piexif=f}(); \ No newline at end of file diff --git a/static/plugins/bootstrap-fileinput/js/plugins/purify.js b/static/plugins/bootstrap-fileinput/js/plugins/purify.js new file mode 100644 index 00000000..1426c190 --- /dev/null +++ b/static/plugins/bootstrap-fileinput/js/plugins/purify.js @@ -0,0 +1,1009 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global.DOMPurify = factory()); +}(this, (function () { 'use strict'; + +var html = ['a', 'abbr', 'acronym', 'address', 'area', 'article', 'aside', 'audio', 'b', 'bdi', 'bdo', 'big', 'blink', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'content', 'data', 'datalist', 'dd', 'decorator', 'del', 'details', 'dfn', 'dir', 'div', 'dl', 'dt', 'element', 'em', 'fieldset', 'figcaption', 'figure', 'font', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'i', 'img', 'input', 'ins', 'kbd', 'label', 'legend', 'li', 'main', 'map', 'mark', 'marquee', 'menu', 'menuitem', 'meter', 'nav', 'nobr', 'ol', 'optgroup', 'option', 'output', 'p', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'section', 'select', 'shadow', 'small', 'source', 'spacer', 'span', 'strike', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot', 'th', 'thead', 'time', 'tr', 'track', 'tt', 'u', 'ul', 'var', 'video', 'wbr']; + +// SVG +var svg = ['svg', 'a', 'altglyph', 'altglyphdef', 'altglyphitem', 'animatecolor', 'animatemotion', 'animatetransform', 'audio', 'canvas', 'circle', 'clippath', 'defs', 'desc', 'ellipse', 'filter', 'font', 'g', 'glyph', 'glyphref', 'hkern', 'image', 'line', 'lineargradient', 'marker', 'mask', 'metadata', 'mpath', 'path', 'pattern', 'polygon', 'polyline', 'radialgradient', 'rect', 'stop', 'style', 'switch', 'symbol', 'text', 'textpath', 'title', 'tref', 'tspan', 'video', 'view', 'vkern']; + +var svgFilters = ['feBlend', 'feColorMatrix', 'feComponentTransfer', 'feComposite', 'feConvolveMatrix', 'feDiffuseLighting', 'feDisplacementMap', 'feDistantLight', 'feFlood', 'feFuncA', 'feFuncB', 'feFuncG', 'feFuncR', 'feGaussianBlur', 'feMerge', 'feMergeNode', 'feMorphology', 'feOffset', 'fePointLight', 'feSpecularLighting', 'feSpotLight', 'feTile', 'feTurbulence']; + +var mathMl = ['math', 'menclose', 'merror', 'mfenced', 'mfrac', 'mglyph', 'mi', 'mlabeledtr', 'mmuliscripts', 'mn', 'mo', 'mover', 'mpadded', 'mphantom', 'mroot', 'mrow', 'ms', 'mpspace', 'msqrt', 'mystyle', 'msub', 'msup', 'msubsup', 'mtable', 'mtd', 'mtext', 'mtr', 'munder', 'munderover']; + +var text = ['#text']; + +var html$1 = ['accept', 'action', 'align', 'alt', 'autocomplete', 'background', 'bgcolor', 'border', 'cellpadding', 'cellspacing', 'checked', 'cite', 'class', 'clear', 'color', 'cols', 'colspan', 'coords', 'crossorigin', 'datetime', 'default', 'dir', 'disabled', 'download', 'enctype', 'face', 'for', 'headers', 'height', 'hidden', 'high', 'href', 'hreflang', 'id', 'integrity', 'ismap', 'label', 'lang', 'list', 'loop', 'low', 'max', 'maxlength', 'media', 'method', 'min', 'multiple', 'name', 'noshade', 'novalidate', 'nowrap', 'open', 'optimum', 'pattern', 'placeholder', 'poster', 'preload', 'pubdate', 'radiogroup', 'readonly', 'rel', 'required', 'rev', 'reversed', 'role', 'rows', 'rowspan', 'spellcheck', 'scope', 'selected', 'shape', 'size', 'sizes', 'span', 'srclang', 'start', 'src', 'srcset', 'step', 'style', 'summary', 'tabindex', 'title', 'type', 'usemap', 'valign', 'value', 'width', 'xmlns']; + +var svg$1 = ['accent-height', 'accumulate', 'additivive', 'alignment-baseline', 'ascent', 'attributename', 'attributetype', 'azimuth', 'basefrequency', 'baseline-shift', 'begin', 'bias', 'by', 'class', 'clip', 'clip-path', 'clip-rule', 'color', 'color-interpolation', 'color-interpolation-filters', 'color-profile', 'color-rendering', 'cx', 'cy', 'd', 'dx', 'dy', 'diffuseconstant', 'direction', 'display', 'divisor', 'dur', 'edgemode', 'elevation', 'end', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'flood-color', 'flood-opacity', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'fx', 'fy', 'g1', 'g2', 'glyph-name', 'glyphref', 'gradientunits', 'gradienttransform', 'height', 'href', 'id', 'image-rendering', 'in', 'in2', 'k', 'k1', 'k2', 'k3', 'k4', 'kerning', 'keypoints', 'keysplines', 'keytimes', 'lang', 'lengthadjust', 'letter-spacing', 'kernelmatrix', 'kernelunitlength', 'lighting-color', 'local', 'marker-end', 'marker-mid', 'marker-start', 'markerheight', 'markerunits', 'markerwidth', 'maskcontentunits', 'maskunits', 'max', 'mask', 'media', 'method', 'mode', 'min', 'name', 'numoctaves', 'offset', 'operator', 'opacity', 'order', 'orient', 'orientation', 'origin', 'overflow', 'paint-order', 'path', 'pathlength', 'patterncontentunits', 'patterntransform', 'patternunits', 'points', 'preservealpha', 'preserveaspectratio', 'r', 'rx', 'ry', 'radius', 'refx', 'refy', 'repeatcount', 'repeatdur', 'restart', 'result', 'rotate', 'scale', 'seed', 'shape-rendering', 'specularconstant', 'specularexponent', 'spreadmethod', 'stddeviation', 'stitchtiles', 'stop-color', 'stop-opacity', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke', 'stroke-width', 'style', 'surfacescale', 'tabindex', 'targetx', 'targety', 'transform', 'text-anchor', 'text-decoration', 'text-rendering', 'textlength', 'type', 'u1', 'u2', 'unicode', 'values', 'viewbox', 'visibility', 'vert-adv-y', 'vert-origin-x', 'vert-origin-y', 'width', 'word-spacing', 'wrap', 'writing-mode', 'xchannelselector', 'ychannelselector', 'x', 'x1', 'x2', 'xmlns', 'y', 'y1', 'y2', 'z', 'zoomandpan']; + +var mathMl$1 = ['accent', 'accentunder', 'align', 'bevelled', 'close', 'columnsalign', 'columnlines', 'columnspan', 'denomalign', 'depth', 'dir', 'display', 'displaystyle', 'fence', 'frame', 'height', 'href', 'id', 'largeop', 'length', 'linethickness', 'lspace', 'lquote', 'mathbackground', 'mathcolor', 'mathsize', 'mathvariant', 'maxsize', 'minsize', 'movablelimits', 'notation', 'numalign', 'open', 'rowalign', 'rowlines', 'rowspacing', 'rowspan', 'rspace', 'rquote', 'scriptlevel', 'scriptminsize', 'scriptsizemultiplier', 'selection', 'separator', 'separators', 'stretchy', 'subscriptshift', 'supscriptshift', 'symmetric', 'voffset', 'width', 'xmlns']; + +var xml = ['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']; + +/* Add properties to a lookup table */ +function addToSet(set, array) { + var l = array.length; + while (l--) { + if (typeof array[l] === 'string') { + array[l] = array[l].toLowerCase(); + } + set[array[l]] = true; + } + return set; +} + +/* Shallow clone an object */ +function clone(object) { + var newObject = {}; + var property = void 0; + for (property in object) { + if (Object.prototype.hasOwnProperty.call(object, property)) { + newObject[property] = object[property]; + } + } + return newObject; +} + +var MUSTACHE_EXPR = /\{\{[\s\S]*|[\s\S]*\}\}/gm; // Specify template detection regex for SAFE_FOR_TEMPLATES mode +var ERB_EXPR = /<%[\s\S]*|[\s\S]*%>/gm; +var DATA_ATTR = /^data-[\-\w.\u00B7-\uFFFF]/; // eslint-disable-line no-useless-escape +var ARIA_ATTR = /^aria-[\-\w]+$/; // eslint-disable-line no-useless-escape +var IS_ALLOWED_URI = /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i; // eslint-disable-line no-useless-escape +var IS_SCRIPT_OR_DATA = /^(?:\w+script|data):/i; +var ATTR_WHITESPACE = /[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205f\u3000]/g; // eslint-disable-line no-control-regex + +var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; + +function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } + +var getGlobal = function getGlobal() { + return typeof window === 'undefined' ? null : window; +}; + +function createDOMPurify() { + var window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal(); + + var DOMPurify = function DOMPurify(root) { + return createDOMPurify(root); + }; + + /** + * Version label, exposed for easier checks + * if DOMPurify is up to date or not + */ + DOMPurify.version = '1.0.7'; + + /** + * Array of elements that DOMPurify removed during sanitation. + * Empty if nothing was removed. + */ + DOMPurify.removed = []; + + if (!window || !window.document || window.document.nodeType !== 9) { + // Not running in a browser, provide a factory function + // so that you can pass your own Window + DOMPurify.isSupported = false; + + return DOMPurify; + } + + var originalDocument = window.document; + var useDOMParser = false; // See comment below + var removeTitle = false; // See comment below + + var document = window.document; + var DocumentFragment = window.DocumentFragment, + HTMLTemplateElement = window.HTMLTemplateElement, + Node = window.Node, + NodeFilter = window.NodeFilter, + _window$NamedNodeMap = window.NamedNodeMap, + NamedNodeMap = _window$NamedNodeMap === undefined ? window.NamedNodeMap || window.MozNamedAttrMap : _window$NamedNodeMap, + Text = window.Text, + Comment = window.Comment, + DOMParser = window.DOMParser; + + // As per issue #47, the web-components registry is inherited by a + // new document created via createHTMLDocument. As per the spec + // (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries) + // a new empty registry is used when creating a template contents owner + // document, so we use that as our parent document to ensure nothing + // is inherited. + + if (typeof HTMLTemplateElement === 'function') { + var template = document.createElement('template'); + if (template.content && template.content.ownerDocument) { + document = template.content.ownerDocument; + } + } + + var _document = document, + implementation = _document.implementation, + createNodeIterator = _document.createNodeIterator, + getElementsByTagName = _document.getElementsByTagName, + createDocumentFragment = _document.createDocumentFragment; + var importNode = originalDocument.importNode; + + + var hooks = {}; + + /** + * Expose whether this browser supports running the full DOMPurify. + */ + DOMPurify.isSupported = implementation && typeof implementation.createHTMLDocument !== 'undefined' && document.documentMode !== 9; + + var MUSTACHE_EXPR$$1 = MUSTACHE_EXPR, + ERB_EXPR$$1 = ERB_EXPR, + DATA_ATTR$$1 = DATA_ATTR, + ARIA_ATTR$$1 = ARIA_ATTR, + IS_SCRIPT_OR_DATA$$1 = IS_SCRIPT_OR_DATA, + ATTR_WHITESPACE$$1 = ATTR_WHITESPACE; + var IS_ALLOWED_URI$$1 = IS_ALLOWED_URI; + /** + * We consider the elements and attributes below to be safe. Ideally + * don't add any new ones but feel free to remove unwanted ones. + */ + + /* allowed element names */ + + var ALLOWED_TAGS = null; + var DEFAULT_ALLOWED_TAGS = addToSet({}, [].concat(_toConsumableArray(html), _toConsumableArray(svg), _toConsumableArray(svgFilters), _toConsumableArray(mathMl), _toConsumableArray(text))); + + /* Allowed attribute names */ + var ALLOWED_ATTR = null; + var DEFAULT_ALLOWED_ATTR = addToSet({}, [].concat(_toConsumableArray(html$1), _toConsumableArray(svg$1), _toConsumableArray(mathMl$1), _toConsumableArray(xml))); + + /* Explicitly forbidden tags (overrides ALLOWED_TAGS/ADD_TAGS) */ + var FORBID_TAGS = null; + + /* Explicitly forbidden attributes (overrides ALLOWED_ATTR/ADD_ATTR) */ + var FORBID_ATTR = null; + + /* Decide if ARIA attributes are okay */ + var ALLOW_ARIA_ATTR = true; + + /* Decide if custom data attributes are okay */ + var ALLOW_DATA_ATTR = true; + + /* Decide if unknown protocols are okay */ + var ALLOW_UNKNOWN_PROTOCOLS = false; + + /* Output should be safe for jQuery's $() factory? */ + var SAFE_FOR_JQUERY = false; + + /* Output should be safe for common template engines. + * This means, DOMPurify removes data attributes, mustaches and ERB + */ + var SAFE_FOR_TEMPLATES = false; + + /* Decide if document with ... should be returned */ + var WHOLE_DOCUMENT = false; + + /* Track whether config is already set on this instance of DOMPurify. */ + var SET_CONFIG = false; + + /* Decide if all elements (e.g. style, script) must be children of + * document.body. By default, browsers might move them to document.head */ + var FORCE_BODY = false; + + /* Decide if a DOM `HTMLBodyElement` should be returned, instead of a html string. + * If `WHOLE_DOCUMENT` is enabled a `HTMLHtmlElement` will be returned instead + */ + var RETURN_DOM = false; + + /* Decide if a DOM `DocumentFragment` should be returned, instead of a html string */ + var RETURN_DOM_FRAGMENT = false; + + /* If `RETURN_DOM` or `RETURN_DOM_FRAGMENT` is enabled, decide if the returned DOM + * `Node` is imported into the current `Document`. If this flag is not enabled the + * `Node` will belong (its ownerDocument) to a fresh `HTMLDocument`, created by + * DOMPurify. */ + var RETURN_DOM_IMPORT = false; + + /* Output should be free from DOM clobbering attacks? */ + var SANITIZE_DOM = true; + + /* Keep element content when removing element? */ + var KEEP_CONTENT = true; + + /* If a `Node` is passed to sanitize(), then performs sanitization in-place instead + * of importing it into a new Document and returning a sanitized copy */ + var IN_PLACE = false; + + /* Allow usage of profiles like html, svg and mathMl */ + var USE_PROFILES = {}; + + /* Tags to ignore content of when KEEP_CONTENT is true */ + var FORBID_CONTENTS = addToSet({}, ['audio', 'head', 'math', 'script', 'style', 'template', 'svg', 'video']); + + /* Tags that are safe for data: URIs */ + var DATA_URI_TAGS = addToSet({}, ['audio', 'video', 'img', 'source', 'image']); + + /* Attributes safe for values like "javascript:" */ + var URI_SAFE_ATTRIBUTES = addToSet({}, ['alt', 'class', 'for', 'id', 'label', 'name', 'pattern', 'placeholder', 'summary', 'title', 'value', 'style', 'xmlns']); + + /* Keep a reference to config to pass to hooks */ + var CONFIG = null; + + /* Ideally, do not touch anything below this line */ + /* ______________________________________________ */ + + var formElement = document.createElement('form'); + + /** + * _parseConfig + * + * @param {Object} cfg optional config literal + */ + // eslint-disable-next-line complexity + var _parseConfig = function _parseConfig(cfg) { + /* Shield configuration object from tampering */ + if ((typeof cfg === 'undefined' ? 'undefined' : _typeof(cfg)) !== 'object') { + cfg = {}; + } + /* Set configuration parameters */ + ALLOWED_TAGS = 'ALLOWED_TAGS' in cfg ? addToSet({}, cfg.ALLOWED_TAGS) : DEFAULT_ALLOWED_TAGS; + ALLOWED_ATTR = 'ALLOWED_ATTR' in cfg ? addToSet({}, cfg.ALLOWED_ATTR) : DEFAULT_ALLOWED_ATTR; + FORBID_TAGS = 'FORBID_TAGS' in cfg ? addToSet({}, cfg.FORBID_TAGS) : {}; + FORBID_ATTR = 'FORBID_ATTR' in cfg ? addToSet({}, cfg.FORBID_ATTR) : {}; + USE_PROFILES = 'USE_PROFILES' in cfg ? cfg.USE_PROFILES : false; + ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false; // Default true + ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false; // Default true + ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false; // Default false + SAFE_FOR_JQUERY = cfg.SAFE_FOR_JQUERY || false; // Default false + SAFE_FOR_TEMPLATES = cfg.SAFE_FOR_TEMPLATES || false; // Default false + WHOLE_DOCUMENT = cfg.WHOLE_DOCUMENT || false; // Default false + RETURN_DOM = cfg.RETURN_DOM || false; // Default false + RETURN_DOM_FRAGMENT = cfg.RETURN_DOM_FRAGMENT || false; // Default false + RETURN_DOM_IMPORT = cfg.RETURN_DOM_IMPORT || false; // Default false + FORCE_BODY = cfg.FORCE_BODY || false; // Default false + SANITIZE_DOM = cfg.SANITIZE_DOM !== false; // Default true + KEEP_CONTENT = cfg.KEEP_CONTENT !== false; // Default true + IN_PLACE = cfg.IN_PLACE || false; // Default false + + IS_ALLOWED_URI$$1 = cfg.ALLOWED_URI_REGEXP || IS_ALLOWED_URI$$1; + + if (SAFE_FOR_TEMPLATES) { + ALLOW_DATA_ATTR = false; + } + + if (RETURN_DOM_FRAGMENT) { + RETURN_DOM = true; + } + + /* Parse profile info */ + if (USE_PROFILES) { + ALLOWED_TAGS = addToSet({}, [].concat(_toConsumableArray(text))); + ALLOWED_ATTR = []; + if (USE_PROFILES.html === true) { + addToSet(ALLOWED_TAGS, html); + addToSet(ALLOWED_ATTR, html$1); + } + if (USE_PROFILES.svg === true) { + addToSet(ALLOWED_TAGS, svg); + addToSet(ALLOWED_ATTR, svg$1); + addToSet(ALLOWED_ATTR, xml); + } + if (USE_PROFILES.svgFilters === true) { + addToSet(ALLOWED_TAGS, svgFilters); + addToSet(ALLOWED_ATTR, svg$1); + addToSet(ALLOWED_ATTR, xml); + } + if (USE_PROFILES.mathMl === true) { + addToSet(ALLOWED_TAGS, mathMl); + addToSet(ALLOWED_ATTR, mathMl$1); + addToSet(ALLOWED_ATTR, xml); + } + } + + /* Merge configuration parameters */ + if (cfg.ADD_TAGS) { + if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) { + ALLOWED_TAGS = clone(ALLOWED_TAGS); + } + addToSet(ALLOWED_TAGS, cfg.ADD_TAGS); + } + if (cfg.ADD_ATTR) { + if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) { + ALLOWED_ATTR = clone(ALLOWED_ATTR); + } + addToSet(ALLOWED_ATTR, cfg.ADD_ATTR); + } + if (cfg.ADD_URI_SAFE_ATTR) { + addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR); + } + + /* Add #text in case KEEP_CONTENT is set to true */ + if (KEEP_CONTENT) { + ALLOWED_TAGS['#text'] = true; + } + + /* Add html, head and body to ALLOWED_TAGS in case WHOLE_DOCUMENT is true */ + if (WHOLE_DOCUMENT) { + addToSet(ALLOWED_TAGS, ['html', 'head', 'body']); + } + + /* Add tbody to ALLOWED_TAGS in case tables are permitted, see #286 */ + if (ALLOWED_TAGS.table) { + addToSet(ALLOWED_TAGS, ['tbody']); + } + + // Prevent further manipulation of configuration. + // Not available in IE8, Safari 5, etc. + if (Object && 'freeze' in Object) { + Object.freeze(cfg); + } + + CONFIG = cfg; + }; + + /** + * _forceRemove + * + * @param {Node} node a DOM node + */ + var _forceRemove = function _forceRemove(node) { + DOMPurify.removed.push({ element: node }); + try { + node.parentNode.removeChild(node); + } catch (err) { + node.outerHTML = ''; + } + }; + + /** + * _removeAttribute + * + * @param {String} name an Attribute name + * @param {Node} node a DOM node + */ + var _removeAttribute = function _removeAttribute(name, node) { + try { + DOMPurify.removed.push({ + attribute: node.getAttributeNode(name), + from: node + }); + } catch (err) { + DOMPurify.removed.push({ + attribute: null, + from: node + }); + } + node.removeAttribute(name); + }; + + /** + * _initDocument + * + * @param {String} dirty a string of dirty markup + * @return {Document} a DOM, filled with the dirty markup + */ + var _initDocument = function _initDocument(dirty) { + /* Create a HTML document */ + var doc = void 0; + + if (FORCE_BODY) { + dirty = '' + dirty; + } + + /* Use DOMParser to workaround Firefox bug (see comment below) */ + if (useDOMParser) { + try { + doc = new DOMParser().parseFromString(dirty, 'text/html'); + } catch (err) {} + } + + /* Remove title to fix an mXSS bug in older MS Edge */ + if (removeTitle) { + addToSet(FORBID_TAGS, ['title']); + } + + /* Otherwise use createHTMLDocument, because DOMParser is unsafe in + Safari (see comment below) */ + if (!doc || !doc.documentElement) { + doc = implementation.createHTMLDocument(''); + var _doc = doc, + body = _doc.body; + + body.parentNode.removeChild(body.parentNode.firstElementChild); + body.outerHTML = dirty; + } + + /* Work on whole document or just its body */ + return getElementsByTagName.call(doc, WHOLE_DOCUMENT ? 'html' : 'body')[0]; + }; + + // Firefox uses a different parser for innerHTML rather than + // DOMParser (see https://bugzilla.mozilla.org/show_bug.cgi?id=1205631) + // which means that you *must* use DOMParser, otherwise the output may + // not be safe if used in a document.write context later. + // + // So we feature detect the Firefox bug and use the DOMParser if necessary. + // + // MS Edge, in older versions, is affected by an mXSS behavior. The second + // check tests for the behavior and fixes it if necessary. + if (DOMPurify.isSupported) { + (function () { + try { + var doc = _initDocument('

    '); + if (doc.querySelector('svg img')) { + useDOMParser = true; + } + } catch (err) {} + })(); + (function () { + try { + var doc = _initDocument('</title><img>'); + if (doc.querySelector('title').textContent.match(/<\/title/)) { + removeTitle = true; + } + } catch (err) {} + })(); + } + + /** + * _createIterator + * + * @param {Document} root document/fragment to create iterator for + * @return {Iterator} iterator instance + */ + var _createIterator = function _createIterator(root) { + return createNodeIterator.call(root.ownerDocument || root, root, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT, function () { + return NodeFilter.FILTER_ACCEPT; + }, false); + }; + + /** + * _isClobbered + * + * @param {Node} elm element to check for clobbering attacks + * @return {Boolean} true if clobbered, false if safe + */ + var _isClobbered = function _isClobbered(elm) { + if (elm instanceof Text || elm instanceof Comment) { + return false; + } + if (typeof elm.nodeName !== 'string' || typeof elm.textContent !== 'string' || typeof elm.removeChild !== 'function' || !(elm.attributes instanceof NamedNodeMap) || typeof elm.removeAttribute !== 'function' || typeof elm.setAttribute !== 'function') { + return true; + } + return false; + }; + + /** + * _isNode + * + * @param {Node} obj object to check whether it's a DOM node + * @return {Boolean} true is object is a DOM node + */ + var _isNode = function _isNode(obj) { + return (typeof Node === 'undefined' ? 'undefined' : _typeof(Node)) === 'object' ? obj instanceof Node : obj && (typeof obj === 'undefined' ? 'undefined' : _typeof(obj)) === 'object' && typeof obj.nodeType === 'number' && typeof obj.nodeName === 'string'; + }; + + /** + * _executeHook + * Execute user configurable hooks + * + * @param {String} entryPoint Name of the hook's entry point + * @param {Node} currentNode node to work on with the hook + * @param {Object} data additional hook parameters + */ + var _executeHook = function _executeHook(entryPoint, currentNode, data) { + if (!hooks[entryPoint]) { + return; + } + + hooks[entryPoint].forEach(function (hook) { + hook.call(DOMPurify, currentNode, data, CONFIG); + }); + }; + + /** + * _sanitizeElements + * + * @protect nodeName + * @protect textContent + * @protect removeChild + * + * @param {Node} currentNode to check for permission to exist + * @return {Boolean} true if node was killed, false if left alive + */ + var _sanitizeElements = function _sanitizeElements(currentNode) { + var content = void 0; + + /* Execute a hook if present */ + _executeHook('beforeSanitizeElements', currentNode, null); + + /* Check if element is clobbered or can clobber */ + if (_isClobbered(currentNode)) { + _forceRemove(currentNode); + return true; + } + + /* Now let's check the element's type and name */ + var tagName = currentNode.nodeName.toLowerCase(); + + /* Execute a hook if present */ + _executeHook('uponSanitizeElement', currentNode, { + tagName: tagName, + allowedTags: ALLOWED_TAGS + }); + + /* Remove element if anything forbids its presence */ + if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) { + /* Keep content except for black-listed elements */ + if (KEEP_CONTENT && !FORBID_CONTENTS[tagName] && typeof currentNode.insertAdjacentHTML === 'function') { + try { + currentNode.insertAdjacentHTML('AfterEnd', currentNode.innerHTML); + } catch (err) {} + } + _forceRemove(currentNode); + return true; + } + + /* Convert markup to cover jQuery behavior */ + if (SAFE_FOR_JQUERY && !currentNode.firstElementChild && (!currentNode.content || !currentNode.content.firstElementChild) && /</g.test(currentNode.textContent)) { + DOMPurify.removed.push({ element: currentNode.cloneNode() }); + if (currentNode.innerHTML) { + currentNode.innerHTML = currentNode.innerHTML.replace(/</g, '<'); + } else { + currentNode.innerHTML = currentNode.textContent.replace(/</g, '<'); + } + } + + /* Sanitize element content to be template-safe */ + if (SAFE_FOR_TEMPLATES && currentNode.nodeType === 3) { + /* Get the element's text content */ + content = currentNode.textContent; + content = content.replace(MUSTACHE_EXPR$$1, ' '); + content = content.replace(ERB_EXPR$$1, ' '); + if (currentNode.textContent !== content) { + DOMPurify.removed.push({ element: currentNode.cloneNode() }); + currentNode.textContent = content; + } + } + + /* Execute a hook if present */ + _executeHook('afterSanitizeElements', currentNode, null); + + return false; + }; + + /** + * _isValidAttribute + * + * @param {string} lcTag Lowercase tag name of containing element. + * @param {string} lcName Lowercase attribute name. + * @param {string} value Attribute value. + * @return {Boolean} Returns true if `value` is valid, otherwise false. + */ + var _isValidAttribute = function _isValidAttribute(lcTag, lcName, value) { + /* Make sure attribute cannot clobber */ + if (SANITIZE_DOM && (lcName === 'id' || lcName === 'name') && (value in document || value in formElement)) { + return false; + } + + /* Sanitize attribute content to be template-safe */ + if (SAFE_FOR_TEMPLATES) { + value = value.replace(MUSTACHE_EXPR$$1, ' '); + value = value.replace(ERB_EXPR$$1, ' '); + } + + /* Allow valid data-* attributes: At least one character after "-" + (https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes) + XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804) + We don't need to check the value; it's always URI safe. */ + if (ALLOW_DATA_ATTR && DATA_ATTR$$1.test(lcName)) { + // This attribute is safe + } else if (ALLOW_ARIA_ATTR && ARIA_ATTR$$1.test(lcName)) { + // This attribute is safe + /* Otherwise, check the name is permitted */ + } else if (!ALLOWED_ATTR[lcName] || FORBID_ATTR[lcName]) { + return false; + + /* Check value is safe. First, is attr inert? If so, is safe */ + } else if (URI_SAFE_ATTRIBUTES[lcName]) { + // This attribute is safe + /* Check no script, data or unknown possibly unsafe URI + unless we know URI values are safe for that attribute */ + } else if (IS_ALLOWED_URI$$1.test(value.replace(ATTR_WHITESPACE$$1, ''))) { + // This attribute is safe + /* Keep image data URIs alive if src/xlink:href is allowed */ + } else if ((lcName === 'src' || lcName === 'xlink:href') && value.indexOf('data:') === 0 && DATA_URI_TAGS[lcTag]) { + // This attribute is safe + /* Allow unknown protocols: This provides support for links that + are handled by protocol handlers which may be unknown ahead of + time, e.g. fb:, spotify: */ + } else if (ALLOW_UNKNOWN_PROTOCOLS && !IS_SCRIPT_OR_DATA$$1.test(value.replace(ATTR_WHITESPACE$$1, ''))) { + // This attribute is safe + /* Check for binary attributes */ + // eslint-disable-next-line no-negated-condition + } else if (!value) { + // Binary attributes are safe at this point + /* Anything else, presume unsafe, do not add it back */ + } else { + return false; + } + return true; + }; + + /** + * _sanitizeAttributes + * + * @protect attributes + * @protect nodeName + * @protect removeAttribute + * @protect setAttribute + * + * @param {Node} node to sanitize + */ + // eslint-disable-next-line complexity + var _sanitizeAttributes = function _sanitizeAttributes(currentNode) { + var attr = void 0; + var value = void 0; + var lcName = void 0; + var idAttr = void 0; + var l = void 0; + /* Execute a hook if present */ + _executeHook('beforeSanitizeAttributes', currentNode, null); + + var attributes = currentNode.attributes; + + /* Check if we have attributes; if not we might have a text node */ + + if (!attributes) { + return; + } + + var hookEvent = { + attrName: '', + attrValue: '', + keepAttr: true, + allowedAttributes: ALLOWED_ATTR + }; + l = attributes.length; + + /* Go backwards over all attributes; safely remove bad ones */ + while (l--) { + attr = attributes[l]; + var _attr = attr, + name = _attr.name; + + value = attr.value.trim(); + lcName = name.toLowerCase(); + + /* Execute a hook if present */ + hookEvent.attrName = lcName; + hookEvent.attrValue = value; + hookEvent.keepAttr = true; + _executeHook('uponSanitizeAttribute', currentNode, hookEvent); + value = hookEvent.attrValue; + + /* Remove attribute */ + // Safari (iOS + Mac), last tested v8.0.5, crashes if you try to + // remove a "name" attribute from an <img> tag that has an "id" + // attribute at the time. + if (lcName === 'name' && currentNode.nodeName === 'IMG' && attributes.id) { + idAttr = attributes.id; + attributes = Array.prototype.slice.apply(attributes); + _removeAttribute('id', currentNode); + _removeAttribute(name, currentNode); + if (attributes.indexOf(idAttr) > l) { + currentNode.setAttribute('id', idAttr.value); + } + } else if ( + // This works around a bug in Safari, where input[type=file] + // cannot be dynamically set after type has been removed + currentNode.nodeName === 'INPUT' && lcName === 'type' && value === 'file' && (ALLOWED_ATTR[lcName] || !FORBID_ATTR[lcName])) { + continue; + } else { + // This avoids a crash in Safari v9.0 with double-ids. + // The trick is to first set the id to be empty and then to + // remove the attribute + if (name === 'id') { + currentNode.setAttribute(name, ''); + } + _removeAttribute(name, currentNode); + } + + /* Did the hooks approve of the attribute? */ + if (!hookEvent.keepAttr) { + continue; + } + + /* Is `value` valid for this attribute? */ + var lcTag = currentNode.nodeName.toLowerCase(); + if (!_isValidAttribute(lcTag, lcName, value)) { + continue; + } + + /* Handle invalid data-* attribute set by try-catching it */ + try { + currentNode.setAttribute(name, value); + DOMPurify.removed.pop(); + } catch (err) {} + } + + /* Execute a hook if present */ + _executeHook('afterSanitizeAttributes', currentNode, null); + }; + + /** + * _sanitizeShadowDOM + * + * @param {DocumentFragment} fragment to iterate over recursively + */ + var _sanitizeShadowDOM = function _sanitizeShadowDOM(fragment) { + var shadowNode = void 0; + var shadowIterator = _createIterator(fragment); + + /* Execute a hook if present */ + _executeHook('beforeSanitizeShadowDOM', fragment, null); + + while (shadowNode = shadowIterator.nextNode()) { + /* Execute a hook if present */ + _executeHook('uponSanitizeShadowNode', shadowNode, null); + + /* Sanitize tags and elements */ + if (_sanitizeElements(shadowNode)) { + continue; + } + + /* Deep shadow DOM detected */ + if (shadowNode.content instanceof DocumentFragment) { + _sanitizeShadowDOM(shadowNode.content); + } + + /* Check attributes, sanitize if necessary */ + _sanitizeAttributes(shadowNode); + } + + /* Execute a hook if present */ + _executeHook('afterSanitizeShadowDOM', fragment, null); + }; + + /** + * Sanitize + * Public method providing core sanitation functionality + * + * @param {String|Node} dirty string or DOM node + * @param {Object} configuration object + */ + // eslint-disable-next-line complexity + DOMPurify.sanitize = function (dirty, cfg) { + var body = void 0; + var importedNode = void 0; + var currentNode = void 0; + var oldNode = void 0; + var returnNode = void 0; + /* Make sure we have a string to sanitize. + DO NOT return early, as this will return the wrong type if + the user has requested a DOM object rather than a string */ + if (!dirty) { + dirty = '<!-->'; + } + + /* Stringify, in case dirty is an object */ + if (typeof dirty !== 'string' && !_isNode(dirty)) { + // eslint-disable-next-line no-negated-condition + if (typeof dirty.toString !== 'function') { + throw new TypeError('toString is not a function'); + } else { + dirty = dirty.toString(); + if (typeof dirty !== 'string') { + throw new TypeError('dirty is not a string, aborting'); + } + } + } + + /* Check we can run. Otherwise fall back or ignore */ + if (!DOMPurify.isSupported) { + if (_typeof(window.toStaticHTML) === 'object' || typeof window.toStaticHTML === 'function') { + if (typeof dirty === 'string') { + return window.toStaticHTML(dirty); + } + if (_isNode(dirty)) { + return window.toStaticHTML(dirty.outerHTML); + } + } + return dirty; + } + + /* Assign config vars */ + if (!SET_CONFIG) { + _parseConfig(cfg); + } + + /* Clean up removed elements */ + DOMPurify.removed = []; + + if (IN_PLACE) { + /* No special handling necessary for in-place sanitization */ + } else if (dirty instanceof Node) { + /* If dirty is a DOM element, append to an empty document to avoid + elements being stripped by the parser */ + body = _initDocument('<!-->'); + importedNode = body.ownerDocument.importNode(dirty, true); + if (importedNode.nodeType === 1 && importedNode.nodeName === 'BODY') { + /* Node is already a body, use as is */ + body = importedNode; + } else { + body.appendChild(importedNode); + } + } else { + /* Exit directly if we have nothing to do */ + if (!RETURN_DOM && !WHOLE_DOCUMENT && dirty.indexOf('<') === -1) { + return dirty; + } + + /* Initialize the document to work on */ + body = _initDocument(dirty); + + /* Check we have a DOM node from the data */ + if (!body) { + return RETURN_DOM ? null : ''; + } + } + + /* Remove first element node (ours) if FORCE_BODY is set */ + if (body && FORCE_BODY) { + _forceRemove(body.firstChild); + } + + /* Get node iterator */ + var nodeIterator = _createIterator(IN_PLACE ? dirty : body); + + /* Now start iterating over the created document */ + while (currentNode = nodeIterator.nextNode()) { + /* Fix IE's strange behavior with manipulated textNodes #89 */ + if (currentNode.nodeType === 3 && currentNode === oldNode) { + continue; + } + + /* Sanitize tags and elements */ + if (_sanitizeElements(currentNode)) { + continue; + } + + /* Shadow DOM detected, sanitize it */ + if (currentNode.content instanceof DocumentFragment) { + _sanitizeShadowDOM(currentNode.content); + } + + /* Check attributes, sanitize if necessary */ + _sanitizeAttributes(currentNode); + + oldNode = currentNode; + } + + /* If we sanitized `dirty` in-place, return it. */ + if (IN_PLACE) { + return dirty; + } + + /* Return sanitized string or DOM */ + if (RETURN_DOM) { + if (RETURN_DOM_FRAGMENT) { + returnNode = createDocumentFragment.call(body.ownerDocument); + + while (body.firstChild) { + returnNode.appendChild(body.firstChild); + } + } else { + returnNode = body; + } + + if (RETURN_DOM_IMPORT) { + /* AdoptNode() is not used because internal state is not reset + (e.g. the past names map of a HTMLFormElement), this is safe + in theory but we would rather not risk another attack vector. + The state that is cloned by importNode() is explicitly defined + by the specs. */ + returnNode = importNode.call(originalDocument, returnNode, true); + } + + return returnNode; + } + + return WHOLE_DOCUMENT ? body.outerHTML : body.innerHTML; + }; + + /** + * Public method to set the configuration once + * setConfig + * + * @param {Object} cfg configuration object + */ + DOMPurify.setConfig = function (cfg) { + _parseConfig(cfg); + SET_CONFIG = true; + }; + + /** + * Public method to remove the configuration + * clearConfig + * + */ + DOMPurify.clearConfig = function () { + CONFIG = null; + SET_CONFIG = false; + }; + + /** + * Public method to check if an attribute value is valid. + * Uses last set config, if any. Otherwise, uses config defaults. + * isValidAttribute + * + * @param {string} tag Tag name of containing element. + * @param {string} attr Attribute name. + * @param {string} value Attribute value. + * @return {Boolean} Returns true if `value` is valid. Otherwise, returns false. + */ + DOMPurify.isValidAttribute = function (tag, attr, value) { + /* Initialize shared config vars if necessary. */ + if (!CONFIG) { + _parseConfig({}); + } + var lcTag = tag.toLowerCase(); + var lcName = attr.toLowerCase(); + return _isValidAttribute(lcTag, lcName, value); + }; + + /** + * AddHook + * Public method to add DOMPurify hooks + * + * @param {String} entryPoint entry point for the hook to add + * @param {Function} hookFunction function to execute + */ + DOMPurify.addHook = function (entryPoint, hookFunction) { + if (typeof hookFunction !== 'function') { + return; + } + hooks[entryPoint] = hooks[entryPoint] || []; + hooks[entryPoint].push(hookFunction); + }; + + /** + * RemoveHook + * Public method to remove a DOMPurify hook at a given entryPoint + * (pops it from the stack of hooks if more are present) + * + * @param {String} entryPoint entry point for the hook to remove + */ + DOMPurify.removeHook = function (entryPoint) { + if (hooks[entryPoint]) { + hooks[entryPoint].pop(); + } + }; + + /** + * RemoveHooks + * Public method to remove all DOMPurify hooks at a given entryPoint + * + * @param {String} entryPoint entry point for the hooks to remove + */ + DOMPurify.removeHooks = function (entryPoint) { + if (hooks[entryPoint]) { + hooks[entryPoint] = []; + } + }; + + /** + * RemoveAllHooks + * Public method to remove all DOMPurify hooks + * + */ + DOMPurify.removeAllHooks = function () { + hooks = {}; + }; + + return DOMPurify; +} + +var purify = createDOMPurify(); + +return purify; + +}))); \ No newline at end of file diff --git a/static/plugins/bootstrap-fileinput/js/plugins/purify.min.js b/static/plugins/bootstrap-fileinput/js/plugins/purify.min.js new file mode 100644 index 00000000..dfd90a3e --- /dev/null +++ b/static/plugins/bootstrap-fileinput/js/plugins/purify.min.js @@ -0,0 +1 @@ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.DOMPurify=t()}(this,function(){"use strict";function e(e,t){for(var n=t.length;n--;)"string"==typeof t[n]&&(t[n]=t[n].toLowerCase()),e[t[n]]=!0;return e}function t(e){var t={},n=void 0;for(n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n]);return t}function n(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t<e.length;t++)n[t]=e[t];return n}return Array.from(e)}function r(){var x=arguments.length>0&&void 0!==arguments[0]?arguments[0]:A(),S=function(e){return r(e)};if(S.version="1.0.7",S.removed=[],!x||!x.document||9!==x.document.nodeType)return S.isSupported=!1,S;var k=x.document,w=!1,L=!1,E=x.document,O=x.DocumentFragment,M=x.HTMLTemplateElement,N=x.Node,_=x.NodeFilter,D=x.NamedNodeMap,C=void 0===D?x.NamedNodeMap||x.MozNamedAttrMap:D,R=x.Text,F=x.Comment,z=x.DOMParser;if("function"==typeof M){var H=E.createElement("template");H.content&&H.content.ownerDocument&&(E=H.content.ownerDocument)}var I=E,j=I.implementation,P=I.createNodeIterator,W=I.getElementsByTagName,U=I.createDocumentFragment,B=k.importNode,G={};S.isSupported=j&&void 0!==j.createHTMLDocument&&9!==E.documentMode;var q=m,V=p,Y=h,K=g,X=v,$=b,J=y,Q=null,Z=e({},[].concat(n(o),n(i),n(a),n(l),n(s))),ee=null,te=e({},[].concat(n(c),n(d),n(u),n(f))),ne=null,re=null,oe=!0,ie=!0,ae=!1,le=!1,se=!1,ce=!1,de=!1,ue=!1,fe=!1,me=!1,pe=!1,he=!0,ge=!0,ye=!1,ve={},be=e({},["audio","head","math","script","style","template","svg","video"]),Te=e({},["audio","video","img","source","image"]),Ae=e({},["alt","class","for","id","label","name","pattern","placeholder","summary","title","value","style","xmlns"]),xe=null,Se=E.createElement("form"),ke=function(r){"object"!==(void 0===r?"undefined":T(r))&&(r={}),Q="ALLOWED_TAGS"in r?e({},r.ALLOWED_TAGS):Z,ee="ALLOWED_ATTR"in r?e({},r.ALLOWED_ATTR):te,ne="FORBID_TAGS"in r?e({},r.FORBID_TAGS):{},re="FORBID_ATTR"in r?e({},r.FORBID_ATTR):{},ve="USE_PROFILES"in r&&r.USE_PROFILES,oe=!1!==r.ALLOW_ARIA_ATTR,ie=!1!==r.ALLOW_DATA_ATTR,ae=r.ALLOW_UNKNOWN_PROTOCOLS||!1,le=r.SAFE_FOR_JQUERY||!1,se=r.SAFE_FOR_TEMPLATES||!1,ce=r.WHOLE_DOCUMENT||!1,fe=r.RETURN_DOM||!1,me=r.RETURN_DOM_FRAGMENT||!1,pe=r.RETURN_DOM_IMPORT||!1,ue=r.FORCE_BODY||!1,he=!1!==r.SANITIZE_DOM,ge=!1!==r.KEEP_CONTENT,ye=r.IN_PLACE||!1,J=r.ALLOWED_URI_REGEXP||J,se&&(ie=!1),me&&(fe=!0),ve&&(Q=e({},[].concat(n(s))),ee=[],!0===ve.html&&(e(Q,o),e(ee,c)),!0===ve.svg&&(e(Q,i),e(ee,d),e(ee,f)),!0===ve.svgFilters&&(e(Q,a),e(ee,d),e(ee,f)),!0===ve.mathMl&&(e(Q,l),e(ee,u),e(ee,f))),r.ADD_TAGS&&(Q===Z&&(Q=t(Q)),e(Q,r.ADD_TAGS)),r.ADD_ATTR&&(ee===te&&(ee=t(ee)),e(ee,r.ADD_ATTR)),r.ADD_URI_SAFE_ATTR&&e(Ae,r.ADD_URI_SAFE_ATTR),ge&&(Q["#text"]=!0),ce&&e(Q,["html","head","body"]),Q.table&&e(Q,["tbody"]),Object&&"freeze"in Object&&Object.freeze(r),xe=r},we=function(e){S.removed.push({element:e});try{e.parentNode.removeChild(e)}catch(t){e.outerHTML=""}},Le=function(e,t){try{S.removed.push({attribute:t.getAttributeNode(e),from:t})}catch(e){S.removed.push({attribute:null,from:t})}t.removeAttribute(e)},Ee=function(t){var n=void 0;if(ue&&(t="<remove></remove>"+t),w)try{n=(new z).parseFromString(t,"text/html")}catch(e){}if(L&&e(ne,["title"]),!n||!n.documentElement){var r=(n=j.createHTMLDocument("")).body;r.parentNode.removeChild(r.parentNode.firstElementChild),r.outerHTML=t}return W.call(n,ce?"html":"body")[0]};S.isSupported&&(function(){try{Ee('<svg><p><style><img src="</style><img src=x onerror=alert(1)//">').querySelector("svg img")&&(w=!0)}catch(e){}}(),function(){try{Ee("<x/><title></title><img>").querySelector("title").textContent.match(/<\/title/)&&(L=!0)}catch(e){}}());var Oe=function(e){return P.call(e.ownerDocument||e,e,_.SHOW_ELEMENT|_.SHOW_COMMENT|_.SHOW_TEXT,function(){return _.FILTER_ACCEPT},!1)},Me=function(e){return!(e instanceof R||e instanceof F)&&!("string"==typeof e.nodeName&&"string"==typeof e.textContent&&"function"==typeof e.removeChild&&e.attributes instanceof C&&"function"==typeof e.removeAttribute&&"function"==typeof e.setAttribute)},Ne=function(e){return"object"===(void 0===N?"undefined":T(N))?e instanceof N:e&&"object"===(void 0===e?"undefined":T(e))&&"number"==typeof e.nodeType&&"string"==typeof e.nodeName},_e=function(e,t,n){G[e]&&G[e].forEach(function(e){e.call(S,t,n,xe)})},De=function(e){var t=void 0;if(_e("beforeSanitizeElements",e,null),Me(e))return we(e),!0;var n=e.nodeName.toLowerCase();if(_e("uponSanitizeElement",e,{tagName:n,allowedTags:Q}),!Q[n]||ne[n]){if(ge&&!be[n]&&"function"==typeof e.insertAdjacentHTML)try{e.insertAdjacentHTML("AfterEnd",e.innerHTML)}catch(e){}return we(e),!0}return!le||e.firstElementChild||e.content&&e.content.firstElementChild||!/</g.test(e.textContent)||(S.removed.push({element:e.cloneNode()}),e.innerHTML?e.innerHTML=e.innerHTML.replace(/</g,"<"):e.innerHTML=e.textContent.replace(/</g,"<")),se&&3===e.nodeType&&(t=(t=(t=e.textContent).replace(q," ")).replace(V," "),e.textContent!==t&&(S.removed.push({element:e.cloneNode()}),e.textContent=t)),_e("afterSanitizeElements",e,null),!1},Ce=function(e,t,n){if(he&&("id"===t||"name"===t)&&(n in E||n in Se))return!1;if(se&&(n=(n=n.replace(q," ")).replace(V," ")),ie&&Y.test(t));else if(oe&&K.test(t));else{if(!ee[t]||re[t])return!1;if(Ae[t]);else if(J.test(n.replace($,"")));else if("src"!==t&&"xlink:href"!==t||0!==n.indexOf("data:")||!Te[e]){if(ae&&!X.test(n.replace($,"")));else if(n)return!1}else;}return!0},Re=function(e){var t=void 0,n=void 0,r=void 0,o=void 0,i=void 0;_e("beforeSanitizeAttributes",e,null);var a=e.attributes;if(a){var l={attrName:"",attrValue:"",keepAttr:!0,allowedAttributes:ee};for(i=a.length;i--;){var s=(t=a[i]).name;if(n=t.value.trim(),r=s.toLowerCase(),l.attrName=r,l.attrValue=n,l.keepAttr=!0,_e("uponSanitizeAttribute",e,l),n=l.attrValue,"name"===r&&"IMG"===e.nodeName&&a.id)o=a.id,a=Array.prototype.slice.apply(a),Le("id",e),Le(s,e),a.indexOf(o)>i&&e.setAttribute("id",o.value);else{if("INPUT"===e.nodeName&&"type"===r&&"file"===n&&(ee[r]||!re[r]))continue;"id"===s&&e.setAttribute(s,""),Le(s,e)}if(l.keepAttr){var c=e.nodeName.toLowerCase();if(Ce(c,r,n))try{e.setAttribute(s,n),S.removed.pop()}catch(e){}}}_e("afterSanitizeAttributes",e,null)}},Fe=function e(t){var n=void 0,r=Oe(t);for(_e("beforeSanitizeShadowDOM",t,null);n=r.nextNode();)_e("uponSanitizeShadowNode",n,null),De(n)||(n.content instanceof O&&e(n.content),Re(n));_e("afterSanitizeShadowDOM",t,null)};return S.sanitize=function(e,t){var n=void 0,r=void 0,o=void 0,i=void 0,a=void 0;if(e||(e="\x3c!--\x3e"),"string"!=typeof e&&!Ne(e)){if("function"!=typeof e.toString)throw new TypeError("toString is not a function");if("string"!=typeof(e=e.toString()))throw new TypeError("dirty is not a string, aborting")}if(!S.isSupported){if("object"===T(x.toStaticHTML)||"function"==typeof x.toStaticHTML){if("string"==typeof e)return x.toStaticHTML(e);if(Ne(e))return x.toStaticHTML(e.outerHTML)}return e}if(de||ke(t),S.removed=[],ye);else if(e instanceof N)1===(r=(n=Ee("\x3c!--\x3e")).ownerDocument.importNode(e,!0)).nodeType&&"BODY"===r.nodeName?n=r:n.appendChild(r);else{if(!fe&&!ce&&-1===e.indexOf("<"))return e;if(!(n=Ee(e)))return fe?null:""}n&&ue&&we(n.firstChild);for(var l=Oe(ye?e:n);o=l.nextNode();)3===o.nodeType&&o===i||De(o)||(o.content instanceof O&&Fe(o.content),Re(o),i=o);if(ye)return e;if(fe){if(me)for(a=U.call(n.ownerDocument);n.firstChild;)a.appendChild(n.firstChild);else a=n;return pe&&(a=B.call(k,a,!0)),a}return ce?n.outerHTML:n.innerHTML},S.setConfig=function(e){ke(e),de=!0},S.clearConfig=function(){xe=null,de=!1},S.isValidAttribute=function(e,t,n){xe||ke({});var r=e.toLowerCase(),o=t.toLowerCase();return Ce(r,o,n)},S.addHook=function(e,t){"function"==typeof t&&(G[e]=G[e]||[],G[e].push(t))},S.removeHook=function(e){G[e]&&G[e].pop()},S.removeHooks=function(e){G[e]&&(G[e]=[])},S.removeAllHooks=function(){G={}},S}var o=["a","abbr","acronym","address","area","article","aside","audio","b","bdi","bdo","big","blink","blockquote","body","br","button","canvas","caption","center","cite","code","col","colgroup","content","data","datalist","dd","decorator","del","details","dfn","dir","div","dl","dt","element","em","fieldset","figcaption","figure","font","footer","form","h1","h2","h3","h4","h5","h6","head","header","hgroup","hr","html","i","img","input","ins","kbd","label","legend","li","main","map","mark","marquee","menu","menuitem","meter","nav","nobr","ol","optgroup","option","output","p","pre","progress","q","rp","rt","ruby","s","samp","section","select","shadow","small","source","spacer","span","strike","strong","style","sub","summary","sup","table","tbody","td","template","textarea","tfoot","th","thead","time","tr","track","tt","u","ul","var","video","wbr"],i=["svg","a","altglyph","altglyphdef","altglyphitem","animatecolor","animatemotion","animatetransform","audio","canvas","circle","clippath","defs","desc","ellipse","filter","font","g","glyph","glyphref","hkern","image","line","lineargradient","marker","mask","metadata","mpath","path","pattern","polygon","polyline","radialgradient","rect","stop","style","switch","symbol","text","textpath","title","tref","tspan","video","view","vkern"],a=["feBlend","feColorMatrix","feComponentTransfer","feComposite","feConvolveMatrix","feDiffuseLighting","feDisplacementMap","feDistantLight","feFlood","feFuncA","feFuncB","feFuncG","feFuncR","feGaussianBlur","feMerge","feMergeNode","feMorphology","feOffset","fePointLight","feSpecularLighting","feSpotLight","feTile","feTurbulence"],l=["math","menclose","merror","mfenced","mfrac","mglyph","mi","mlabeledtr","mmuliscripts","mn","mo","mover","mpadded","mphantom","mroot","mrow","ms","mpspace","msqrt","mystyle","msub","msup","msubsup","mtable","mtd","mtext","mtr","munder","munderover"],s=["#text"],c=["accept","action","align","alt","autocomplete","background","bgcolor","border","cellpadding","cellspacing","checked","cite","class","clear","color","cols","colspan","coords","crossorigin","datetime","default","dir","disabled","download","enctype","face","for","headers","height","hidden","high","href","hreflang","id","integrity","ismap","label","lang","list","loop","low","max","maxlength","media","method","min","multiple","name","noshade","novalidate","nowrap","open","optimum","pattern","placeholder","poster","preload","pubdate","radiogroup","readonly","rel","required","rev","reversed","role","rows","rowspan","spellcheck","scope","selected","shape","size","sizes","span","srclang","start","src","srcset","step","style","summary","tabindex","title","type","usemap","valign","value","width","xmlns"],d=["accent-height","accumulate","additivive","alignment-baseline","ascent","attributename","attributetype","azimuth","basefrequency","baseline-shift","begin","bias","by","class","clip","clip-path","clip-rule","color","color-interpolation","color-interpolation-filters","color-profile","color-rendering","cx","cy","d","dx","dy","diffuseconstant","direction","display","divisor","dur","edgemode","elevation","end","fill","fill-opacity","fill-rule","filter","flood-color","flood-opacity","font-family","font-size","font-size-adjust","font-stretch","font-style","font-variant","font-weight","fx","fy","g1","g2","glyph-name","glyphref","gradientunits","gradienttransform","height","href","id","image-rendering","in","in2","k","k1","k2","k3","k4","kerning","keypoints","keysplines","keytimes","lang","lengthadjust","letter-spacing","kernelmatrix","kernelunitlength","lighting-color","local","marker-end","marker-mid","marker-start","markerheight","markerunits","markerwidth","maskcontentunits","maskunits","max","mask","media","method","mode","min","name","numoctaves","offset","operator","opacity","order","orient","orientation","origin","overflow","paint-order","path","pathlength","patterncontentunits","patterntransform","patternunits","points","preservealpha","preserveaspectratio","r","rx","ry","radius","refx","refy","repeatcount","repeatdur","restart","result","rotate","scale","seed","shape-rendering","specularconstant","specularexponent","spreadmethod","stddeviation","stitchtiles","stop-color","stop-opacity","stroke-dasharray","stroke-dashoffset","stroke-linecap","stroke-linejoin","stroke-miterlimit","stroke-opacity","stroke","stroke-width","style","surfacescale","tabindex","targetx","targety","transform","text-anchor","text-decoration","text-rendering","textlength","type","u1","u2","unicode","values","viewbox","visibility","vert-adv-y","vert-origin-x","vert-origin-y","width","word-spacing","wrap","writing-mode","xchannelselector","ychannelselector","x","x1","x2","xmlns","y","y1","y2","z","zoomandpan"],u=["accent","accentunder","align","bevelled","close","columnsalign","columnlines","columnspan","denomalign","depth","dir","display","displaystyle","fence","frame","height","href","id","largeop","length","linethickness","lspace","lquote","mathbackground","mathcolor","mathsize","mathvariant","maxsize","minsize","movablelimits","notation","numalign","open","rowalign","rowlines","rowspacing","rowspan","rspace","rquote","scriptlevel","scriptminsize","scriptsizemultiplier","selection","separator","separators","stretchy","subscriptshift","supscriptshift","symmetric","voffset","width","xmlns"],f=["xlink:href","xml:id","xlink:title","xml:space","xmlns:xlink"],m=/\{\{[\s\S]*|[\s\S]*\}\}/gm,p=/<%[\s\S]*|[\s\S]*%>/gm,h=/^data-[\-\w.\u00B7-\uFFFF]/,g=/^aria-[\-\w]+$/,y=/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i,v=/^(?:\w+script|data):/i,b=/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205f\u3000]/g,T="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},A=function(){return"undefined"==typeof window?null:window};return r()}); \ No newline at end of file diff --git a/static/plugins/bootstrap-fileinput/js/plugins/sortable.js b/static/plugins/bootstrap-fileinput/js/plugins/sortable.js new file mode 100644 index 00000000..9fc23b8c --- /dev/null +++ b/static/plugins/bootstrap-fileinput/js/plugins/sortable.js @@ -0,0 +1,1590 @@ +/**! + * KvSortable + * @author RubaXa <trash@rubaxa.org> + * @license MIT + * + * Changed kvsortable plugin naming to prevent conflict with JQuery UI Sortable + * @author Kartik Visweswaran + */ + +(function kvsortableModule(factory) { + "use strict"; + + if (typeof define === "function" && define.amd) { + define(factory); + } + else if (typeof module != "undefined" && typeof module.exports != "undefined") { + module.exports = factory(); + } + else { + /* jshint sub:true */ + window["KvSortable"] = factory(); + } +})(function kvsortableFactory() { + "use strict"; + + if (typeof window === "undefined" || !window.document) { + return function kvsortableError() { + throw new Error("KvSortable.js requires a window with a document"); + }; + } + + var dragEl, + parentEl, + ghostEl, + cloneEl, + rootEl, + nextEl, + lastDownEl, + + scrollEl, + scrollParentEl, + scrollCustomFn, + + lastEl, + lastCSS, + lastParentCSS, + + oldIndex, + newIndex, + + activeGroup, + putKvSortable, + + autoScroll = {}, + + tapEvt, + touchEvt, + + moved, + + /** @const */ + R_SPACE = /\s+/g, + R_FLOAT = /left|right|inline/, + + expando = 'KvSortable' + (new Date).getTime(), + + win = window, + document = win.document, + parseInt = win.parseInt, + setTimeout = win.setTimeout, + + $ = win.jQuery || win.Zepto, + Polymer = win.Polymer, + + captureMode = false, + passiveMode = false, + + supportDraggable = ('draggable' in document.createElement('div')), + supportCssPointerEvents = (function (el) { + // false when IE11 + if (!!navigator.userAgent.match(/(?:Trident.*rv[ :]?11\.|msie)/i)) { + return false; + } + el = document.createElement('x'); + el.style.cssText = 'pointer-events:auto'; + return el.style.pointerEvents === 'auto'; + })(), + + _silent = false, + + abs = Math.abs, + min = Math.min, + + savedInputChecked = [], + touchDragOverListeners = [], + + _autoScroll = _throttle(function (/**Event*/evt, /**Object*/options, /**HTMLElement*/rootEl) { + // Bug: https://bugzilla.mozilla.org/show_bug.cgi?id=505521 + if (rootEl && options.scroll) { + var _this = rootEl[expando], + el, + rect, + sens = options.scrollSensitivity, + speed = options.scrollSpeed, + + x = evt.clientX, + y = evt.clientY, + + winWidth = window.innerWidth, + winHeight = window.innerHeight, + + vx, + vy, + + scrollOffsetX, + scrollOffsetY + ; + + // Delect scrollEl + if (scrollParentEl !== rootEl) { + scrollEl = options.scroll; + scrollParentEl = rootEl; + scrollCustomFn = options.scrollFn; + + if (scrollEl === true) { + scrollEl = rootEl; + + do { + if ((scrollEl.offsetWidth < scrollEl.scrollWidth) || + (scrollEl.offsetHeight < scrollEl.scrollHeight) + ) { + break; + } + /* jshint boss:true */ + } while (scrollEl = scrollEl.parentNode); + } + } + + if (scrollEl) { + el = scrollEl; + rect = scrollEl.getBoundingClientRect(); + vx = (abs(rect.right - x) <= sens) - (abs(rect.left - x) <= sens); + vy = (abs(rect.bottom - y) <= sens) - (abs(rect.top - y) <= sens); + } + + + if (!(vx || vy)) { + vx = (winWidth - x <= sens) - (x <= sens); + vy = (winHeight - y <= sens) - (y <= sens); + + /* jshint expr:true */ + (vx || vy) && (el = win); + } + + + if (autoScroll.vx !== vx || autoScroll.vy !== vy || autoScroll.el !== el) { + autoScroll.el = el; + autoScroll.vx = vx; + autoScroll.vy = vy; + + clearInterval(autoScroll.pid); + + if (el) { + autoScroll.pid = setInterval(function () { + scrollOffsetY = vy ? vy * speed : 0; + scrollOffsetX = vx ? vx * speed : 0; + + if ('function' === typeof(scrollCustomFn)) { + return scrollCustomFn.call(_this, scrollOffsetX, scrollOffsetY, evt); + } + + if (el === win) { + win.scrollTo(win.pageXOffset + scrollOffsetX, win.pageYOffset + scrollOffsetY); + } else { + el.scrollTop += scrollOffsetY; + el.scrollLeft += scrollOffsetX; + } + }, 24); + } + } + } + }, 30), + + _prepareGroup = function (options) { + function toFn(value, pull) { + if (value === void 0 || value === true) { + value = group.name; + } + + if (typeof value === 'function') { + return value; + } else { + return function (to, from) { + var fromGroup = from.options.group.name; + + return pull + ? value + : value && (value.join + ? value.indexOf(fromGroup) > -1 + : (fromGroup == value) + ); + }; + } + } + + var group = {}; + var originalGroup = options.group; + + if (!originalGroup || typeof originalGroup != 'object') { + originalGroup = {name: originalGroup}; + } + + group.name = originalGroup.name; + group.checkPull = toFn(originalGroup.pull, true); + group.checkPut = toFn(originalGroup.put); + group.revertClone = originalGroup.revertClone; + + options.group = group; + } + ; + + // Detect support a passive mode + try { + window.addEventListener('test', null, Object.defineProperty({}, 'passive', { + get: function () { + // `false`, because everything starts to work incorrectly and instead of d'n'd, + // begins the page has scrolled. + passiveMode = false; + captureMode = { + capture: false, + passive: passiveMode + }; + } + })); + } catch (err) {} + + /** + * @class KvSortable + * @param {HTMLElement} el + * @param {Object} [options] + */ + function KvSortable(el, options) { + if (!(el && el.nodeType && el.nodeType === 1)) { + throw 'KvSortable: `el` must be HTMLElement, and not ' + {}.toString.call(el); + } + + this.el = el; // root element + this.options = options = _extend({}, options); + + + // Export instance + el[expando] = this; + + // Default options + var defaults = { + group: Math.random(), + sort: true, + disabled: false, + store: null, + handle: null, + scroll: true, + scrollSensitivity: 30, + scrollSpeed: 10, + draggable: /[uo]l/i.test(el.nodeName) ? 'li' : '>*', + ghostClass: 'kvsortable-ghost', + chosenClass: 'kvsortable-chosen', + dragClass: 'kvsortable-drag', + ignore: 'a, img', + filter: null, + preventOnFilter: true, + animation: 0, + setData: function (dataTransfer, dragEl) { + dataTransfer.setData('Text', dragEl.textContent); + }, + dropBubble: false, + dragoverBubble: false, + dataIdAttr: 'data-id', + delay: 0, + forceFallback: false, + fallbackClass: 'kvsortable-fallback', + fallbackOnBody: false, + fallbackTolerance: 0, + fallbackOffset: {x: 0, y: 0}, + supportPointer: KvSortable.supportPointer !== false + }; + + + // Set default options + for (var name in defaults) { + !(name in options) && (options[name] = defaults[name]); + } + + _prepareGroup(options); + + // Bind all private methods + for (var fn in this) { + if (fn.charAt(0) === '_' && typeof this[fn] === 'function') { + this[fn] = this[fn].bind(this); + } + } + + // Setup drag mode + this.nativeDraggable = options.forceFallback ? false : supportDraggable; + + // Bind events + _on(el, 'mousedown', this._onTapStart); + _on(el, 'touchstart', this._onTapStart); + options.supportPointer && _on(el, 'pointerdown', this._onTapStart); + + if (this.nativeDraggable) { + _on(el, 'dragover', this); + _on(el, 'dragenter', this); + } + + touchDragOverListeners.push(this._onDragOver); + + // Restore sorting + options.store && this.sort(options.store.get(this)); + } + + + KvSortable.prototype = /** @lends KvSortable.prototype */ { + constructor: KvSortable, + + _onTapStart: function (/** Event|TouchEvent */evt) { + var _this = this, + el = this.el, + options = this.options, + preventOnFilter = options.preventOnFilter, + type = evt.type, + touch = evt.touches && evt.touches[0], + target = (touch || evt).target, + originalTarget = evt.target.shadowRoot && (evt.path && evt.path[0]) || target, + filter = options.filter, + startIndex; + + _saveInputCheckedState(el); + + + // Don't trigger start event when an element is been dragged, otherwise the evt.oldindex always wrong when set option.group. + if (dragEl) { + return; + } + + if (/mousedown|pointerdown/.test(type) && evt.button !== 0 || options.disabled) { + return; // only left button or enabled + } + + // cancel dnd if original target is content editable + if (originalTarget.isContentEditable) { + return; + } + + target = _closest(target, options.draggable, el); + + if (!target) { + return; + } + + if (lastDownEl === target) { + // Ignoring duplicate `down` + return; + } + + // Get the index of the dragged element within its parent + startIndex = _index(target, options.draggable); + + // Check filter + if (typeof filter === 'function') { + if (filter.call(this, evt, target, this)) { + _dispatchEvent(_this, originalTarget, 'filter', target, el, el, startIndex); + preventOnFilter && evt.preventDefault(); + return; // cancel dnd + } + } + else if (filter) { + filter = filter.split(',').some(function (criteria) { + criteria = _closest(originalTarget, criteria.trim(), el); + + if (criteria) { + _dispatchEvent(_this, criteria, 'filter', target, el, el, startIndex); + return true; + } + }); + + if (filter) { + preventOnFilter && evt.preventDefault(); + return; // cancel dnd + } + } + + if (options.handle && !_closest(originalTarget, options.handle, el)) { + return; + } + + // Prepare `dragstart` + this._prepareDragStart(evt, touch, target, startIndex); + }, + + _prepareDragStart: function (/** Event */evt, /** Touch */touch, /** HTMLElement */target, /** Number */startIndex) { + var _this = this, + el = _this.el, + options = _this.options, + ownerDocument = el.ownerDocument, + dragStartFn; + + if (target && !dragEl && (target.parentNode === el)) { + tapEvt = evt; + + rootEl = el; + dragEl = target; + parentEl = dragEl.parentNode; + nextEl = dragEl.nextSibling; + lastDownEl = target; + activeGroup = options.group; + oldIndex = startIndex; + + this._lastX = (touch || evt).clientX; + this._lastY = (touch || evt).clientY; + + dragEl.style['will-change'] = 'all'; + + dragStartFn = function () { + // Delayed drag has been triggered + // we can re-enable the events: touchmove/mousemove + _this._disableDelayedDrag(); + + // Make the element draggable + dragEl.draggable = _this.nativeDraggable; + + // Chosen item + _toggleClass(dragEl, options.chosenClass, true); + + // Bind the events: dragstart/dragend + _this._triggerDragStart(evt, touch); + + // Drag start event + _dispatchEvent(_this, rootEl, 'choose', dragEl, rootEl, rootEl, oldIndex); + }; + + // Disable "draggable" + options.ignore.split(',').forEach(function (criteria) { + _find(dragEl, criteria.trim(), _disableDraggable); + }); + + _on(ownerDocument, 'mouseup', _this._onDrop); + _on(ownerDocument, 'touchend', _this._onDrop); + _on(ownerDocument, 'touchcancel', _this._onDrop); + _on(ownerDocument, 'selectstart', _this); + options.supportPointer && _on(ownerDocument, 'pointercancel', _this._onDrop); + + if (options.delay) { + // If the user moves the pointer or let go the click or touch + // before the delay has been reached: + // disable the delayed drag + _on(ownerDocument, 'mouseup', _this._disableDelayedDrag); + _on(ownerDocument, 'touchend', _this._disableDelayedDrag); + _on(ownerDocument, 'touchcancel', _this._disableDelayedDrag); + _on(ownerDocument, 'mousemove', _this._disableDelayedDrag); + _on(ownerDocument, 'touchmove', _this._disableDelayedDrag); + options.supportPointer && _on(ownerDocument, 'pointermove', _this._disableDelayedDrag); + + _this._dragStartTimer = setTimeout(dragStartFn, options.delay); + } else { + dragStartFn(); + } + + + } + }, + + _disableDelayedDrag: function () { + var ownerDocument = this.el.ownerDocument; + + clearTimeout(this._dragStartTimer); + _off(ownerDocument, 'mouseup', this._disableDelayedDrag); + _off(ownerDocument, 'touchend', this._disableDelayedDrag); + _off(ownerDocument, 'touchcancel', this._disableDelayedDrag); + _off(ownerDocument, 'mousemove', this._disableDelayedDrag); + _off(ownerDocument, 'touchmove', this._disableDelayedDrag); + _off(ownerDocument, 'pointermove', this._disableDelayedDrag); + }, + + _triggerDragStart: function (/** Event */evt, /** Touch */touch) { + touch = touch || (evt.pointerType == 'touch' ? evt : null); + + if (touch) { + // Touch device support + tapEvt = { + target: dragEl, + clientX: touch.clientX, + clientY: touch.clientY + }; + + this._onDragStart(tapEvt, 'touch'); + } + else if (!this.nativeDraggable) { + this._onDragStart(tapEvt, true); + } + else { + _on(dragEl, 'dragend', this); + _on(rootEl, 'dragstart', this._onDragStart); + } + + try { + if (document.selection) { + // Timeout neccessary for IE9 + _nextTick(function () { + document.selection.empty(); + }); + } else { + window.getSelection().removeAllRanges(); + } + } catch (err) { + } + }, + + _dragStarted: function () { + if (rootEl && dragEl) { + var options = this.options; + + // Apply effect + _toggleClass(dragEl, options.ghostClass, true); + _toggleClass(dragEl, options.dragClass, false); + + KvSortable.active = this; + + // Drag start event + _dispatchEvent(this, rootEl, 'start', dragEl, rootEl, rootEl, oldIndex); + } else { + this._nulling(); + } + }, + + _emulateDragOver: function () { + if (touchEvt) { + if (this._lastX === touchEvt.clientX && this._lastY === touchEvt.clientY) { + return; + } + + this._lastX = touchEvt.clientX; + this._lastY = touchEvt.clientY; + + if (!supportCssPointerEvents) { + _css(ghostEl, 'display', 'none'); + } + + var target = document.elementFromPoint(touchEvt.clientX, touchEvt.clientY); + var parent = target; + var i = touchDragOverListeners.length; + + if (target && target.shadowRoot) { + target = target.shadowRoot.elementFromPoint(touchEvt.clientX, touchEvt.clientY); + parent = target; + } + + if (parent) { + do { + if (parent[expando]) { + while (i--) { + touchDragOverListeners[i]({ + clientX: touchEvt.clientX, + clientY: touchEvt.clientY, + target: target, + rootEl: parent + }); + } + + break; + } + + target = parent; // store last element + } + /* jshint boss:true */ + while (parent = parent.parentNode); + } + + if (!supportCssPointerEvents) { + _css(ghostEl, 'display', ''); + } + } + }, + + + _onTouchMove: function (/**TouchEvent*/evt) { + if (tapEvt) { + var options = this.options, + fallbackTolerance = options.fallbackTolerance, + fallbackOffset = options.fallbackOffset, + touch = evt.touches ? evt.touches[0] : evt, + dx = (touch.clientX - tapEvt.clientX) + fallbackOffset.x, + dy = (touch.clientY - tapEvt.clientY) + fallbackOffset.y, + translate3d = evt.touches ? 'translate3d(' + dx + 'px,' + dy + 'px,0)' : 'translate(' + dx + 'px,' + dy + 'px)'; + + // only set the status to dragging, when we are actually dragging + if (!KvSortable.active) { + if (fallbackTolerance && + min(abs(touch.clientX - this._lastX), abs(touch.clientY - this._lastY)) < fallbackTolerance + ) { + return; + } + + this._dragStarted(); + } + + // as well as creating the ghost element on the document body + this._appendGhost(); + + moved = true; + touchEvt = touch; + + _css(ghostEl, 'webkitTransform', translate3d); + _css(ghostEl, 'mozTransform', translate3d); + _css(ghostEl, 'msTransform', translate3d); + _css(ghostEl, 'transform', translate3d); + + evt.preventDefault(); + } + }, + + _appendGhost: function () { + if (!ghostEl) { + var rect = dragEl.getBoundingClientRect(), + css = _css(dragEl), + options = this.options, + ghostRect; + + ghostEl = dragEl.cloneNode(true); + + _toggleClass(ghostEl, options.ghostClass, false); + _toggleClass(ghostEl, options.fallbackClass, true); + _toggleClass(ghostEl, options.dragClass, true); + + _css(ghostEl, 'top', rect.top - parseInt(css.marginTop, 10)); + _css(ghostEl, 'left', rect.left - parseInt(css.marginLeft, 10)); + _css(ghostEl, 'width', rect.width); + _css(ghostEl, 'height', rect.height); + _css(ghostEl, 'opacity', '0.8'); + _css(ghostEl, 'position', 'fixed'); + _css(ghostEl, 'zIndex', '100000'); + _css(ghostEl, 'pointerEvents', 'none'); + + options.fallbackOnBody && document.body.appendChild(ghostEl) || rootEl.appendChild(ghostEl); + + // Fixing dimensions. + ghostRect = ghostEl.getBoundingClientRect(); + _css(ghostEl, 'width', rect.width * 2 - ghostRect.width); + _css(ghostEl, 'height', rect.height * 2 - ghostRect.height); + } + }, + + _onDragStart: function (/**Event*/evt, /**boolean*/useFallback) { + var _this = this; + var dataTransfer = evt.dataTransfer; + var options = _this.options; + + _this._offUpEvents(); + + if (activeGroup.checkPull(_this, _this, dragEl, evt)) { + cloneEl = _clone(dragEl); + + cloneEl.draggable = false; + cloneEl.style['will-change'] = ''; + + _css(cloneEl, 'display', 'none'); + _toggleClass(cloneEl, _this.options.chosenClass, false); + + // #1143: IFrame support workaround + _this._cloneId = _nextTick(function () { + rootEl.insertBefore(cloneEl, dragEl); + _dispatchEvent(_this, rootEl, 'clone', dragEl); + }); + } + + _toggleClass(dragEl, options.dragClass, true); + + if (useFallback) { + if (useFallback === 'touch') { + // Bind touch events + _on(document, 'touchmove', _this._onTouchMove); + _on(document, 'touchend', _this._onDrop); + _on(document, 'touchcancel', _this._onDrop); + + if (options.supportPointer) { + _on(document, 'pointermove', _this._onTouchMove); + _on(document, 'pointerup', _this._onDrop); + } + } else { + // Old brwoser + _on(document, 'mousemove', _this._onTouchMove); + _on(document, 'mouseup', _this._onDrop); + } + + _this._loopId = setInterval(_this._emulateDragOver, 50); + } + else { + if (dataTransfer) { + dataTransfer.effectAllowed = 'move'; + options.setData && options.setData.call(_this, dataTransfer, dragEl); + } + + _on(document, 'drop', _this); + + // #1143: Бывает элемент с IFrame внутри блокирует `drop`, + // поэтому если вызвался `mouseover`, значит надо отменять весь d'n'd. + // Breaking Chrome 62+ + // _on(document, 'mouseover', _this); + + _this._dragStartId = _nextTick(_this._dragStarted); + } + }, + + _onDragOver: function (/**Event*/evt) { + var el = this.el, + target, + dragRect, + targetRect, + revert, + options = this.options, + group = options.group, + activeKvSortable = KvSortable.active, + isOwner = (activeGroup === group), + isMovingBetweenKvSortable = false, + canSort = options.sort; + + if (evt.preventDefault !== void 0) { + evt.preventDefault(); + !options.dragoverBubble && evt.stopPropagation(); + } + + if (dragEl.animated) { + return; + } + + moved = true; + + if (activeKvSortable && !options.disabled && + (isOwner + ? canSort || (revert = !rootEl.contains(dragEl)) // Reverting item into the original list + : ( + putKvSortable === this || + ( + (activeKvSortable.lastPullMode = activeGroup.checkPull(this, activeKvSortable, dragEl, evt)) && + group.checkPut(this, activeKvSortable, dragEl, evt) + ) + ) + ) && + (evt.rootEl === void 0 || evt.rootEl === this.el) // touch fallback + ) { + // Smart auto-scrolling + _autoScroll(evt, options, this.el); + + if (_silent) { + return; + } + + target = _closest(evt.target, options.draggable, el); + dragRect = dragEl.getBoundingClientRect(); + + if (putKvSortable !== this) { + putKvSortable = this; + isMovingBetweenKvSortable = true; + } + + if (revert) { + _cloneHide(activeKvSortable, true); + parentEl = rootEl; // actualization + + if (cloneEl || nextEl) { + rootEl.insertBefore(dragEl, cloneEl || nextEl); + } + else if (!canSort) { + rootEl.appendChild(dragEl); + } + + return; + } + + + if ((el.children.length === 0) || (el.children[0] === ghostEl) || + (el === evt.target) && (_ghostIsLast(el, evt)) + ) { + //assign target only if condition is true + if (el.children.length !== 0 && el.children[0] !== ghostEl && el === evt.target) { + target = el.lastElementChild; + } + + if (target) { + if (target.animated) { + return; + } + + targetRect = target.getBoundingClientRect(); + } + + _cloneHide(activeKvSortable, isOwner); + + if (_onMove(rootEl, el, dragEl, dragRect, target, targetRect, evt) !== false) { + if (!dragEl.contains(el)) { + el.appendChild(dragEl); + parentEl = el; // actualization + } + + this._animate(dragRect, dragEl); + target && this._animate(targetRect, target); + } + } + else if (target && !target.animated && target !== dragEl && (target.parentNode[expando] !== void 0)) { + if (lastEl !== target) { + lastEl = target; + lastCSS = _css(target); + lastParentCSS = _css(target.parentNode); + } + + targetRect = target.getBoundingClientRect(); + + var width = targetRect.right - targetRect.left, + height = targetRect.bottom - targetRect.top, + floating = R_FLOAT.test(lastCSS.cssFloat + lastCSS.display) + || (lastParentCSS.display == 'flex' && lastParentCSS['flex-direction'].indexOf('row') === 0), + isWide = (target.offsetWidth > dragEl.offsetWidth), + isLong = (target.offsetHeight > dragEl.offsetHeight), + halfway = (floating ? (evt.clientX - targetRect.left) / width : (evt.clientY - targetRect.top) / height) > 0.5, + nextSibling = target.nextElementSibling, + after = false + ; + + if (floating) { + var elTop = dragEl.offsetTop, + tgTop = target.offsetTop; + + if (elTop === tgTop) { + after = (target.previousElementSibling === dragEl) && !isWide || halfway && isWide; + } + else if (target.previousElementSibling === dragEl || dragEl.previousElementSibling === target) { + after = (evt.clientY - targetRect.top) / height > 0.5; + } else { + after = tgTop > elTop; + } + } else if (!isMovingBetweenKvSortable) { + after = (nextSibling !== dragEl) && !isLong || halfway && isLong; + } + + var moveVector = _onMove(rootEl, el, dragEl, dragRect, target, targetRect, evt, after); + + if (moveVector !== false) { + if (moveVector === 1 || moveVector === -1) { + after = (moveVector === 1); + } + + _silent = true; + setTimeout(_unsilent, 30); + + _cloneHide(activeKvSortable, isOwner); + + if (!dragEl.contains(el)) { + if (after && !nextSibling) { + el.appendChild(dragEl); + } else { + target.parentNode.insertBefore(dragEl, after ? nextSibling : target); + } + } + + parentEl = dragEl.parentNode; // actualization + + this._animate(dragRect, dragEl); + this._animate(targetRect, target); + } + } + } + }, + + _animate: function (prevRect, target) { + var ms = this.options.animation; + + if (ms) { + var currentRect = target.getBoundingClientRect(); + + if (prevRect.nodeType === 1) { + prevRect = prevRect.getBoundingClientRect(); + } + + _css(target, 'transition', 'none'); + _css(target, 'transform', 'translate3d(' + + (prevRect.left - currentRect.left) + 'px,' + + (prevRect.top - currentRect.top) + 'px,0)' + ); + + target.offsetWidth; // repaint + + _css(target, 'transition', 'all ' + ms + 'ms'); + _css(target, 'transform', 'translate3d(0,0,0)'); + + clearTimeout(target.animated); + target.animated = setTimeout(function () { + _css(target, 'transition', ''); + _css(target, 'transform', ''); + target.animated = false; + }, ms); + } + }, + + _offUpEvents: function () { + var ownerDocument = this.el.ownerDocument; + + _off(document, 'touchmove', this._onTouchMove); + _off(document, 'pointermove', this._onTouchMove); + _off(ownerDocument, 'mouseup', this._onDrop); + _off(ownerDocument, 'touchend', this._onDrop); + _off(ownerDocument, 'pointerup', this._onDrop); + _off(ownerDocument, 'touchcancel', this._onDrop); + _off(ownerDocument, 'pointercancel', this._onDrop); + _off(ownerDocument, 'selectstart', this); + }, + + _onDrop: function (/**Event*/evt) { + var el = this.el, + options = this.options; + + clearInterval(this._loopId); + clearInterval(autoScroll.pid); + clearTimeout(this._dragStartTimer); + + _cancelNextTick(this._cloneId); + _cancelNextTick(this._dragStartId); + + // Unbind events + _off(document, 'mouseover', this); + _off(document, 'mousemove', this._onTouchMove); + + if (this.nativeDraggable) { + _off(document, 'drop', this); + _off(el, 'dragstart', this._onDragStart); + } + + this._offUpEvents(); + + if (evt) { + if (moved) { + evt.preventDefault(); + !options.dropBubble && evt.stopPropagation(); + } + + ghostEl && ghostEl.parentNode && ghostEl.parentNode.removeChild(ghostEl); + + if (rootEl === parentEl || KvSortable.active.lastPullMode !== 'clone') { + // Remove clone + cloneEl && cloneEl.parentNode && cloneEl.parentNode.removeChild(cloneEl); + } + + if (dragEl) { + if (this.nativeDraggable) { + _off(dragEl, 'dragend', this); + } + + _disableDraggable(dragEl); + dragEl.style['will-change'] = ''; + + // Remove class's + _toggleClass(dragEl, this.options.ghostClass, false); + _toggleClass(dragEl, this.options.chosenClass, false); + + // Drag stop event + _dispatchEvent(this, rootEl, 'unchoose', dragEl, parentEl, rootEl, oldIndex); + + if (rootEl !== parentEl) { + newIndex = _index(dragEl, options.draggable); + + if (newIndex >= 0) { + // Add event + _dispatchEvent(null, parentEl, 'add', dragEl, parentEl, rootEl, oldIndex, newIndex); + + // Remove event + _dispatchEvent(this, rootEl, 'remove', dragEl, parentEl, rootEl, oldIndex, newIndex); + + // drag from one list and drop into another + _dispatchEvent(null, parentEl, 'sort', dragEl, parentEl, rootEl, oldIndex, newIndex); + _dispatchEvent(this, rootEl, 'sort', dragEl, parentEl, rootEl, oldIndex, newIndex); + } + } + else { + if (dragEl.nextSibling !== nextEl) { + // Get the index of the dragged element within its parent + newIndex = _index(dragEl, options.draggable); + + if (newIndex >= 0) { + // drag & drop within the same list + _dispatchEvent(this, rootEl, 'update', dragEl, parentEl, rootEl, oldIndex, newIndex); + _dispatchEvent(this, rootEl, 'sort', dragEl, parentEl, rootEl, oldIndex, newIndex); + } + } + } + + if (KvSortable.active) { + /* jshint eqnull:true */ + if (newIndex == null || newIndex === -1) { + newIndex = oldIndex; + } + + _dispatchEvent(this, rootEl, 'end', dragEl, parentEl, rootEl, oldIndex, newIndex); + + // Save sorting + this.save(); + } + } + + } + + this._nulling(); + }, + + _nulling: function() { + rootEl = + dragEl = + parentEl = + ghostEl = + nextEl = + cloneEl = + lastDownEl = + + scrollEl = + scrollParentEl = + + tapEvt = + touchEvt = + + moved = + newIndex = + + lastEl = + lastCSS = + + putKvSortable = + activeGroup = + KvSortable.active = null; + + savedInputChecked.forEach(function (el) { + el.checked = true; + }); + savedInputChecked.length = 0; + }, + + handleEvent: function (/**Event*/evt) { + switch (evt.type) { + case 'drop': + case 'dragend': + this._onDrop(evt); + break; + + case 'dragover': + case 'dragenter': + if (dragEl) { + this._onDragOver(evt); + _globalDragOver(evt); + } + break; + + case 'mouseover': + this._onDrop(evt); + break; + + case 'selectstart': + evt.preventDefault(); + break; + } + }, + + + /** + * Serializes the item into an array of string. + * @returns {String[]} + */ + toArray: function () { + var order = [], + el, + children = this.el.children, + i = 0, + n = children.length, + options = this.options; + + for (; i < n; i++) { + el = children[i]; + if (_closest(el, options.draggable, this.el)) { + order.push(el.getAttribute(options.dataIdAttr) || _generateId(el)); + } + } + + return order; + }, + + + /** + * Sorts the elements according to the array. + * @param {String[]} order order of the items + */ + sort: function (order) { + var items = {}, rootEl = this.el; + + this.toArray().forEach(function (id, i) { + var el = rootEl.children[i]; + + if (_closest(el, this.options.draggable, rootEl)) { + items[id] = el; + } + }, this); + + order.forEach(function (id) { + if (items[id]) { + rootEl.removeChild(items[id]); + rootEl.appendChild(items[id]); + } + }); + }, + + + /** + * Save the current sorting + */ + save: function () { + var store = this.options.store; + store && store.set(this); + }, + + + /** + * For each element in the set, get the first element that matches the selector by testing the element itself and traversing up through its ancestors in the DOM tree. + * @param {HTMLElement} el + * @param {String} [selector] default: `options.draggable` + * @returns {HTMLElement|null} + */ + closest: function (el, selector) { + return _closest(el, selector || this.options.draggable, this.el); + }, + + + /** + * Set/get option + * @param {string} name + * @param {*} [value] + * @returns {*} + */ + option: function (name, value) { + var options = this.options; + + if (value === void 0) { + return options[name]; + } else { + options[name] = value; + + if (name === 'group') { + _prepareGroup(options); + } + } + }, + + + /** + * Destroy + */ + destroy: function () { + var el = this.el; + + el[expando] = null; + + _off(el, 'mousedown', this._onTapStart); + _off(el, 'touchstart', this._onTapStart); + _off(el, 'pointerdown', this._onTapStart); + + if (this.nativeDraggable) { + _off(el, 'dragover', this); + _off(el, 'dragenter', this); + } + + // Remove draggable attributes + Array.prototype.forEach.call(el.querySelectorAll('[draggable]'), function (el) { + el.removeAttribute('draggable'); + }); + + touchDragOverListeners.splice(touchDragOverListeners.indexOf(this._onDragOver), 1); + + this._onDrop(); + + this.el = el = null; + } + }; + + + function _cloneHide(kvsortable, state) { + if (kvsortable.lastPullMode !== 'clone') { + state = true; + } + + if (cloneEl && (cloneEl.state !== state)) { + _css(cloneEl, 'display', state ? 'none' : ''); + + if (!state) { + if (cloneEl.state) { + if (kvsortable.options.group.revertClone) { + rootEl.insertBefore(cloneEl, nextEl); + kvsortable._animate(dragEl, cloneEl); + } else { + rootEl.insertBefore(cloneEl, dragEl); + } + } + } + + cloneEl.state = state; + } + } + + + function _closest(/**HTMLElement*/el, /**String*/selector, /**HTMLElement*/ctx) { + if (el) { + ctx = ctx || document; + + do { + if ((selector === '>*' && el.parentNode === ctx) || _matches(el, selector)) { + return el; + } + /* jshint boss:true */ + } while (el = _getParentOrHost(el)); + } + + return null; + } + + + function _getParentOrHost(el) { + var parent = el.host; + + return (parent && parent.nodeType) ? parent : el.parentNode; + } + + + function _globalDragOver(/**Event*/evt) { + if (evt.dataTransfer) { + evt.dataTransfer.dropEffect = 'move'; + } + evt.preventDefault(); + } + + + function _on(el, event, fn) { + el.addEventListener(event, fn, captureMode); + } + + + function _off(el, event, fn) { + el.removeEventListener(event, fn, captureMode); + } + + + function _toggleClass(el, name, state) { + if (el) { + if (el.classList) { + el.classList[state ? 'add' : 'remove'](name); + } + else { + var className = (' ' + el.className + ' ').replace(R_SPACE, ' ').replace(' ' + name + ' ', ' '); + el.className = (className + (state ? ' ' + name : '')).replace(R_SPACE, ' '); + } + } + } + + + function _css(el, prop, val) { + var style = el && el.style; + + if (style) { + if (val === void 0) { + if (document.defaultView && document.defaultView.getComputedStyle) { + val = document.defaultView.getComputedStyle(el, ''); + } + else if (el.currentStyle) { + val = el.currentStyle; + } + + return prop === void 0 ? val : val[prop]; + } + else { + if (!(prop in style)) { + prop = '-webkit-' + prop; + } + + style[prop] = val + (typeof val === 'string' ? '' : 'px'); + } + } + } + + + function _find(ctx, tagName, iterator) { + if (ctx) { + var list = ctx.getElementsByTagName(tagName), i = 0, n = list.length; + + if (iterator) { + for (; i < n; i++) { + iterator(list[i], i); + } + } + + return list; + } + + return []; + } + + + + function _dispatchEvent(kvsortable, rootEl, name, targetEl, toEl, fromEl, startIndex, newIndex) { + kvsortable = (kvsortable || rootEl[expando]); + + var evt = document.createEvent('Event'), + options = kvsortable.options, + onName = 'on' + name.charAt(0).toUpperCase() + name.substr(1); + + evt.initEvent(name, true, true); + + evt.to = toEl || rootEl; + evt.from = fromEl || rootEl; + evt.item = targetEl || rootEl; + evt.clone = cloneEl; + + evt.oldIndex = startIndex; + evt.newIndex = newIndex; + + rootEl.dispatchEvent(evt); + + if (options[onName]) { + options[onName].call(kvsortable, evt); + } + } + + + function _onMove(fromEl, toEl, dragEl, dragRect, targetEl, targetRect, originalEvt, willInsertAfter) { + var evt, + kvsortable = fromEl[expando], + onMoveFn = kvsortable.options.onMove, + retVal; + + evt = document.createEvent('Event'); + evt.initEvent('move', true, true); + + evt.to = toEl; + evt.from = fromEl; + evt.dragged = dragEl; + evt.draggedRect = dragRect; + evt.related = targetEl || toEl; + evt.relatedRect = targetRect || toEl.getBoundingClientRect(); + evt.willInsertAfter = willInsertAfter; + + fromEl.dispatchEvent(evt); + + if (onMoveFn) { + retVal = onMoveFn.call(kvsortable, evt, originalEvt); + } + + return retVal; + } + + + function _disableDraggable(el) { + el.draggable = false; + } + + + function _unsilent() { + _silent = false; + } + + + /** @returns {HTMLElement|false} */ + function _ghostIsLast(el, evt) { + var lastEl = el.lastElementChild, + rect = lastEl.getBoundingClientRect(); + + // 5 — min delta + // abs — нельзя добавлять, а то глюки при наведении сверху + return (evt.clientY - (rect.top + rect.height) > 5) || + (evt.clientX - (rect.left + rect.width) > 5); + } + + + /** + * Generate id + * @param {HTMLElement} el + * @returns {String} + * @private + */ + function _generateId(el) { + var str = el.tagName + el.className + el.src + el.href + el.textContent, + i = str.length, + sum = 0; + + while (i--) { + sum += str.charCodeAt(i); + } + + return sum.toString(36); + } + + /** + * Returns the index of an element within its parent for a selected set of + * elements + * @param {HTMLElement} el + * @param {selector} selector + * @return {number} + */ + function _index(el, selector) { + var index = 0; + + if (!el || !el.parentNode) { + return -1; + } + + while (el && (el = el.previousElementSibling)) { + if ((el.nodeName.toUpperCase() !== 'TEMPLATE') && (selector === '>*' || _matches(el, selector))) { + index++; + } + } + + return index; + } + + function _matches(/**HTMLElement*/el, /**String*/selector) { + if (el) { + selector = selector.split('.'); + + var tag = selector.shift().toUpperCase(), + re = new RegExp('\\s(' + selector.join('|') + ')(?=\\s)', 'g'); + + return ( + (tag === '' || el.nodeName.toUpperCase() == tag) && + (!selector.length || ((' ' + el.className + ' ').match(re) || []).length == selector.length) + ); + } + + return false; + } + + function _throttle(callback, ms) { + var args, _this; + + return function () { + if (args === void 0) { + args = arguments; + _this = this; + + setTimeout(function () { + if (args.length === 1) { + callback.call(_this, args[0]); + } else { + callback.apply(_this, args); + } + + args = void 0; + }, ms); + } + }; + } + + function _extend(dst, src) { + if (dst && src) { + for (var key in src) { + if (src.hasOwnProperty(key)) { + dst[key] = src[key]; + } + } + } + + return dst; + } + + function _clone(el) { + if (Polymer && Polymer.dom) { + return Polymer.dom(el).cloneNode(true); + } + else if ($) { + return $(el).clone(true)[0]; + } + else { + return el.cloneNode(true); + } + } + + function _saveInputCheckedState(root) { + var inputs = root.getElementsByTagName('input'); + var idx = inputs.length; + + while (idx--) { + var el = inputs[idx]; + el.checked && savedInputChecked.push(el); + } + } + + function _nextTick(fn) { + return setTimeout(fn, 0); + } + + function _cancelNextTick(id) { + return clearTimeout(id); + } + + // Fixed #973: + _on(document, 'touchmove', function (evt) { + if (KvSortable.active) { + evt.preventDefault(); + } + }); + + // Export utils + KvSortable.utils = { + on: _on, + off: _off, + css: _css, + find: _find, + is: function (el, selector) { + return !!_closest(el, selector, el); + }, + extend: _extend, + throttle: _throttle, + closest: _closest, + toggleClass: _toggleClass, + clone: _clone, + index: _index, + nextTick: _nextTick, + cancelNextTick: _cancelNextTick + }; + + + /** + * Create kvsortable instance + * @param {HTMLElement} el + * @param {Object} [options] + */ + KvSortable.create = function (el, options) { + return new KvSortable(el, options); + }; + + + // Export + KvSortable.version = '1.7.0'; + return KvSortable; +}); +/** + * jQuery plugin for KvSortable + */ +(function (factory) { + "use strict"; + + if (typeof define === "function" && define.amd) { + define(["jquery"], factory); + } + else { + /* jshint sub:true */ + factory(jQuery); + } +})(function ($) { + "use strict"; + $.fn.kvsortable = function (options) { + var retVal, + args = arguments; + + this.each(function () { + var $el = $(this), kvsortable = $el.data('kvsortable'); + + if (!kvsortable && (options instanceof Object || !options)) { + kvsortable = new KvSortable(this, options); + $el.data('kvsortable', kvsortable); + } + + if (kvsortable) { + if (options === 'widget') { + retVal = kvsortable; + } + else if (options === 'destroy') { + kvsortable.destroy(); + $el.removeData('kvsortable'); + } + else if (typeof kvsortable[options] === 'function') { + retVal = kvsortable[options].apply(kvsortable, [].slice.call(args, 1)); + } + else if (options in kvsortable.options) { + retVal = kvsortable.option.apply(kvsortable, args); + } + } + }); + + return (retVal === void 0) ? this : retVal; + }; +}); \ No newline at end of file diff --git a/static/plugins/bootstrap-fileinput/js/plugins/sortable.min.js b/static/plugins/bootstrap-fileinput/js/plugins/sortable.min.js new file mode 100644 index 00000000..602876a0 --- /dev/null +++ b/static/plugins/bootstrap-fileinput/js/plugins/sortable.min.js @@ -0,0 +1 @@ +!function(t){"use strict";"function"==typeof define&&define.amd?define(t):"undefined"!=typeof module&&void 0!==module.exports?module.exports=t():window.KvSortable=t()}(function(){"use strict";function t(e,n){if(!e||!e.nodeType||1!==e.nodeType)throw"KvSortable: `el` must be HTMLElement, and not "+{}.toString.call(e);this.el=e,this.options=n=g({},n),e[U]=this;var i={group:Math.random(),sort:!0,disabled:!1,store:null,handle:null,scroll:!0,scrollSensitivity:30,scrollSpeed:10,draggable:/[uo]l/i.test(e.nodeName)?"li":">*",ghostClass:"kvsortable-ghost",chosenClass:"kvsortable-chosen",dragClass:"kvsortable-drag",ignore:"a, img",filter:null,preventOnFilter:!0,animation:0,setData:function(t,e){t.setData("Text",e.textContent)},dropBubble:!1,dragoverBubble:!1,dataIdAttr:"data-id",delay:0,forceFallback:!1,fallbackClass:"kvsortable-fallback",fallbackOnBody:!1,fallbackTolerance:0,fallbackOffset:{x:0,y:0},supportPointer:!1!==t.supportPointer};for(var r in i)!(r in n)&&(n[r]=i[r]);rt(n);for(var a in this)"_"===a.charAt(0)&&"function"==typeof this[a]&&(this[a]=this[a].bind(this));this.nativeDraggable=!n.forceFallback&&Z,o(e,"mousedown",this._onTapStart),o(e,"touchstart",this._onTapStart),n.supportPointer&&o(e,"pointerdown",this._onTapStart),this.nativeDraggable&&(o(e,"dragover",this),o(e,"dragenter",this)),ot.push(this._onDragOver),n.store&&this.sort(n.store.get(this))}function e(t,e){"clone"!==t.lastPullMode&&(e=!0),w&&w.state!==e&&(a(w,"display",e?"none":""),e||w.state&&(t.options.group.revertClone?(T.insertBefore(w,C),t._animate(_,w)):T.insertBefore(w,_)),w.state=e)}function n(t,e,n){if(t){n=n||W;do{if(">*"===e&&t.parentNode===n||f(t,e))return t}while(t=function(t){var e=t.host;return e&&e.nodeType?e:t.parentNode}(t))}return null}function o(t,e,n){t.addEventListener(e,n,G)}function i(t,e,n){t.removeEventListener(e,n,G)}function r(t,e,n){if(t)if(t.classList)t.classList[n?"add":"remove"](e);else{var o=(" "+t.className+" ").replace(L," ").replace(" "+e+" "," ");t.className=(o+(n?" "+e:"")).replace(L," ")}}function a(t,e,n){var o=t&&t.style;if(o){if(void 0===n)return W.defaultView&&W.defaultView.getComputedStyle?n=W.defaultView.getComputedStyle(t,""):t.currentStyle&&(n=t.currentStyle),void 0===e?n:n[e];e in o||(e="-webkit-"+e),o[e]=n+("string"==typeof n?"":"px")}}function s(t,e,n){if(t){var o=t.getElementsByTagName(e),i=0,r=o.length;if(n)for(;i<r;i++)n(o[i],i);return o}return[]}function l(t,e,n,o,i,r,a,s){t=t||e[U];var l=W.createEvent("Event"),c=t.options,d="on"+n.charAt(0).toUpperCase()+n.substr(1);l.initEvent(n,!0,!0),l.to=i||e,l.from=r||e,l.item=o||e,l.clone=w,l.oldIndex=a,l.newIndex=s,e.dispatchEvent(l),c[d]&&c[d].call(t,l)}function c(t,e,n,o,i,r,a,s){var l,c,d=t[U],h=d.options.onMove;return(l=W.createEvent("Event")).initEvent("move",!0,!0),l.to=e,l.from=t,l.dragged=n,l.draggedRect=o,l.related=i||e,l.relatedRect=r||e.getBoundingClientRect(),l.willInsertAfter=s,t.dispatchEvent(l),h&&(c=h.call(d,l,a)),c}function d(t){t.draggable=!1}function h(){$=!1}function u(t,e){var n=0;if(!t||!t.parentNode)return-1;for(;t&&(t=t.previousElementSibling);)"TEMPLATE"===t.nodeName.toUpperCase()||">*"!==e&&!f(t,e)||n++;return n}function f(t,e){if(t){var n=(e=e.split(".")).shift().toUpperCase(),o=new RegExp("\\s("+e.join("|")+")(?=\\s)","g");return!(""!==n&&t.nodeName.toUpperCase()!=n||e.length&&((" "+t.className+" ").match(o)||[]).length!=e.length)}return!1}function p(t,e){var n,o;return function(){void 0===n&&(n=arguments,o=this,q(function(){1===n.length?t.call(o,n[0]):t.apply(o,n),n=void 0},e))}}function g(t,e){if(t&&e)for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n]);return t}function v(t){return z&&z.dom?z.dom(t).cloneNode(!0):V?V(t).clone(!0)[0]:t.cloneNode(!0)}function m(t){return q(t,0)}function b(t){return clearTimeout(t)}if("undefined"==typeof window||!window.document)return function(){throw new Error("KvSortable.js requires a window with a document")};var _,D,y,w,T,C,S,E,k,x,N,B,P,Y,O,X,I,R,A,M,j={},L=/\s+/g,F=/left|right|inline/,U="KvSortable"+(new Date).getTime(),H=window,W=H.document,K=H.parseInt,q=H.setTimeout,V=H.jQuery||H.Zepto,z=H.Polymer,G=!1,Q=!1,Z="draggable"in W.createElement("div"),J=function(t){return!navigator.userAgent.match(/(?:Trident.*rv[ :]?11\.|msie)/i)&&(t=W.createElement("x"),t.style.cssText="pointer-events:auto","auto"===t.style.pointerEvents)}(),$=!1,tt=Math.abs,et=Math.min,nt=[],ot=[],it=p(function(t,e,n){if(n&&e.scroll){var o,i,r,a,s,l,c=n[U],d=e.scrollSensitivity,h=e.scrollSpeed,u=t.clientX,f=t.clientY,p=window.innerWidth,g=window.innerHeight;if(k!==n&&(E=e.scroll,k=n,x=e.scrollFn,!0===E)){E=n;do{if(E.offsetWidth<E.scrollWidth||E.offsetHeight<E.scrollHeight)break}while(E=E.parentNode)}E&&(o=E,i=E.getBoundingClientRect(),r=(tt(i.right-u)<=d)-(tt(i.left-u)<=d),a=(tt(i.bottom-f)<=d)-(tt(i.top-f)<=d)),r||a||(a=(g-f<=d)-(f<=d),((r=(p-u<=d)-(u<=d))||a)&&(o=H)),j.vx===r&&j.vy===a&&j.el===o||(j.el=o,j.vx=r,j.vy=a,clearInterval(j.pid),o&&(j.pid=setInterval(function(){if(l=a?a*h:0,s=r?r*h:0,"function"==typeof x)return x.call(c,s,l,t);o===H?H.scrollTo(H.pageXOffset+s,H.pageYOffset+l):(o.scrollTop+=l,o.scrollLeft+=s)},24)))}},30),rt=function(t){function e(t,e){return void 0!==t&&!0!==t||(t=n.name),"function"==typeof t?t:function(n,o){var i=o.options.group.name;return e?t:t&&(t.join?t.indexOf(i)>-1:i==t)}}var n={},o=t.group;o&&"object"==typeof o||(o={name:o}),n.name=o.name,n.checkPull=e(o.pull,!0),n.checkPut=e(o.put),n.revertClone=o.revertClone,t.group=n};try{window.addEventListener("test",null,Object.defineProperty({},"passive",{get:function(){G={capture:!1,passive:Q=!1}}}))}catch(t){}return t.prototype={constructor:t,_onTapStart:function(t){var e,o=this,i=this.el,r=this.options,a=r.preventOnFilter,s=t.type,c=t.touches&&t.touches[0],d=(c||t).target,h=t.target.shadowRoot&&t.path&&t.path[0]||d,f=r.filter;if(function(t){for(var e=t.getElementsByTagName("input"),n=e.length;n--;){var o=e[n];o.checked&&nt.push(o)}}(i),!_&&!(/mousedown|pointerdown/.test(s)&&0!==t.button||r.disabled)&&!h.isContentEditable&&(d=n(d,r.draggable,i))&&S!==d){if(e=u(d,r.draggable),"function"==typeof f){if(f.call(this,t,d,this))return l(o,h,"filter",d,i,i,e),void(a&&t.preventDefault())}else if(f&&(f=f.split(",").some(function(t){if(t=n(h,t.trim(),i))return l(o,t,"filter",d,i,i,e),!0})))return void(a&&t.preventDefault());r.handle&&!n(h,r.handle,i)||this._prepareDragStart(t,c,d,e)}},_prepareDragStart:function(t,e,n,i){var a,c=this,h=c.el,u=c.options,f=h.ownerDocument;n&&!_&&n.parentNode===h&&(R=t,T=h,D=(_=n).parentNode,C=_.nextSibling,S=n,X=u.group,Y=i,this._lastX=(e||t).clientX,this._lastY=(e||t).clientY,_.style["will-change"]="all",a=function(){c._disableDelayedDrag(),_.draggable=c.nativeDraggable,r(_,u.chosenClass,!0),c._triggerDragStart(t,e),l(c,T,"choose",_,T,T,Y)},u.ignore.split(",").forEach(function(t){s(_,t.trim(),d)}),o(f,"mouseup",c._onDrop),o(f,"touchend",c._onDrop),o(f,"touchcancel",c._onDrop),o(f,"selectstart",c),u.supportPointer&&o(f,"pointercancel",c._onDrop),u.delay?(o(f,"mouseup",c._disableDelayedDrag),o(f,"touchend",c._disableDelayedDrag),o(f,"touchcancel",c._disableDelayedDrag),o(f,"mousemove",c._disableDelayedDrag),o(f,"touchmove",c._disableDelayedDrag),u.supportPointer&&o(f,"pointermove",c._disableDelayedDrag),c._dragStartTimer=q(a,u.delay)):a())},_disableDelayedDrag:function(){var t=this.el.ownerDocument;clearTimeout(this._dragStartTimer),i(t,"mouseup",this._disableDelayedDrag),i(t,"touchend",this._disableDelayedDrag),i(t,"touchcancel",this._disableDelayedDrag),i(t,"mousemove",this._disableDelayedDrag),i(t,"touchmove",this._disableDelayedDrag),i(t,"pointermove",this._disableDelayedDrag)},_triggerDragStart:function(t,e){(e=e||("touch"==t.pointerType?t:null))?(R={target:_,clientX:e.clientX,clientY:e.clientY},this._onDragStart(R,"touch")):this.nativeDraggable?(o(_,"dragend",this),o(T,"dragstart",this._onDragStart)):this._onDragStart(R,!0);try{W.selection?m(function(){W.selection.empty()}):window.getSelection().removeAllRanges()}catch(t){}},_dragStarted:function(){if(T&&_){var e=this.options;r(_,e.ghostClass,!0),r(_,e.dragClass,!1),t.active=this,l(this,T,"start",_,T,T,Y)}else this._nulling()},_emulateDragOver:function(){if(A){if(this._lastX===A.clientX&&this._lastY===A.clientY)return;this._lastX=A.clientX,this._lastY=A.clientY,J||a(y,"display","none");var t=W.elementFromPoint(A.clientX,A.clientY),e=t,n=ot.length;if(t&&t.shadowRoot&&(e=t=t.shadowRoot.elementFromPoint(A.clientX,A.clientY)),e)do{if(e[U]){for(;n--;)ot[n]({clientX:A.clientX,clientY:A.clientY,target:t,rootEl:e});break}t=e}while(e=e.parentNode);J||a(y,"display","")}},_onTouchMove:function(e){if(R){var n=this.options,o=n.fallbackTolerance,i=n.fallbackOffset,r=e.touches?e.touches[0]:e,s=r.clientX-R.clientX+i.x,l=r.clientY-R.clientY+i.y,c=e.touches?"translate3d("+s+"px,"+l+"px,0)":"translate("+s+"px,"+l+"px)";if(!t.active){if(o&&et(tt(r.clientX-this._lastX),tt(r.clientY-this._lastY))<o)return;this._dragStarted()}this._appendGhost(),M=!0,A=r,a(y,"webkitTransform",c),a(y,"mozTransform",c),a(y,"msTransform",c),a(y,"transform",c),e.preventDefault()}},_appendGhost:function(){if(!y){var t,e=_.getBoundingClientRect(),n=a(_),o=this.options;r(y=_.cloneNode(!0),o.ghostClass,!1),r(y,o.fallbackClass,!0),r(y,o.dragClass,!0),a(y,"top",e.top-K(n.marginTop,10)),a(y,"left",e.left-K(n.marginLeft,10)),a(y,"width",e.width),a(y,"height",e.height),a(y,"opacity","0.8"),a(y,"position","fixed"),a(y,"zIndex","100000"),a(y,"pointerEvents","none"),o.fallbackOnBody&&W.body.appendChild(y)||T.appendChild(y),t=y.getBoundingClientRect(),a(y,"width",2*e.width-t.width),a(y,"height",2*e.height-t.height)}},_onDragStart:function(t,e){var n=this,i=t.dataTransfer,s=n.options;n._offUpEvents(),X.checkPull(n,n,_,t)&&((w=v(_)).draggable=!1,w.style["will-change"]="",a(w,"display","none"),r(w,n.options.chosenClass,!1),n._cloneId=m(function(){T.insertBefore(w,_),l(n,T,"clone",_)})),r(_,s.dragClass,!0),e?("touch"===e?(o(W,"touchmove",n._onTouchMove),o(W,"touchend",n._onDrop),o(W,"touchcancel",n._onDrop),s.supportPointer&&(o(W,"pointermove",n._onTouchMove),o(W,"pointerup",n._onDrop))):(o(W,"mousemove",n._onTouchMove),o(W,"mouseup",n._onDrop)),n._loopId=setInterval(n._emulateDragOver,50)):(i&&(i.effectAllowed="move",s.setData&&s.setData.call(n,i,_)),o(W,"drop",n),n._dragStartId=m(n._dragStarted))},_onDragOver:function(o){var i,r,s,l,d=this.el,u=this.options,f=u.group,p=t.active,g=X===f,v=!1,m=u.sort;if(void 0!==o.preventDefault&&(o.preventDefault(),!u.dragoverBubble&&o.stopPropagation()),!_.animated&&(M=!0,p&&!u.disabled&&(g?m||(l=!T.contains(_)):I===this||(p.lastPullMode=X.checkPull(this,p,_,o))&&f.checkPut(this,p,_,o))&&(void 0===o.rootEl||o.rootEl===this.el))){if(it(o,u,this.el),$)return;if(i=n(o.target,u.draggable,d),r=_.getBoundingClientRect(),I!==this&&(I=this,v=!0),l)return e(p,!0),D=T,void(w||C?T.insertBefore(_,w||C):m||T.appendChild(_));if(0===d.children.length||d.children[0]===y||d===o.target&&function(t,e){var n=t.lastElementChild.getBoundingClientRect();return e.clientY-(n.top+n.height)>5||e.clientX-(n.left+n.width)>5}(d,o)){if(0!==d.children.length&&d.children[0]!==y&&d===o.target&&(i=d.lastElementChild),i){if(i.animated)return;s=i.getBoundingClientRect()}e(p,g),!1!==c(T,d,_,r,i,s,o)&&(_.contains(d)||(d.appendChild(_),D=d),this._animate(r,_),i&&this._animate(s,i))}else if(i&&!i.animated&&i!==_&&void 0!==i.parentNode[U]){N!==i&&(N=i,B=a(i),P=a(i.parentNode));var b=(s=i.getBoundingClientRect()).right-s.left,S=s.bottom-s.top,E=F.test(B.cssFloat+B.display)||"flex"==P.display&&0===P["flex-direction"].indexOf("row"),k=i.offsetWidth>_.offsetWidth,x=i.offsetHeight>_.offsetHeight,Y=(E?(o.clientX-s.left)/b:(o.clientY-s.top)/S)>.5,O=i.nextElementSibling,R=!1;if(E){var A=_.offsetTop,j=i.offsetTop;R=A===j?i.previousElementSibling===_&&!k||Y&&k:i.previousElementSibling===_||_.previousElementSibling===i?(o.clientY-s.top)/S>.5:j>A}else v||(R=O!==_&&!x||Y&&x);var L=c(T,d,_,r,i,s,o,R);!1!==L&&(1!==L&&-1!==L||(R=1===L),$=!0,q(h,30),e(p,g),_.contains(d)||(R&&!O?d.appendChild(_):i.parentNode.insertBefore(_,R?O:i)),D=_.parentNode,this._animate(r,_),this._animate(s,i))}}},_animate:function(t,e){var n=this.options.animation;if(n){var o=e.getBoundingClientRect();1===t.nodeType&&(t=t.getBoundingClientRect()),a(e,"transition","none"),a(e,"transform","translate3d("+(t.left-o.left)+"px,"+(t.top-o.top)+"px,0)"),e.offsetWidth,a(e,"transition","all "+n+"ms"),a(e,"transform","translate3d(0,0,0)"),clearTimeout(e.animated),e.animated=q(function(){a(e,"transition",""),a(e,"transform",""),e.animated=!1},n)}},_offUpEvents:function(){var t=this.el.ownerDocument;i(W,"touchmove",this._onTouchMove),i(W,"pointermove",this._onTouchMove),i(t,"mouseup",this._onDrop),i(t,"touchend",this._onDrop),i(t,"pointerup",this._onDrop),i(t,"touchcancel",this._onDrop),i(t,"pointercancel",this._onDrop),i(t,"selectstart",this)},_onDrop:function(e){var n=this.el,o=this.options;clearInterval(this._loopId),clearInterval(j.pid),clearTimeout(this._dragStartTimer),b(this._cloneId),b(this._dragStartId),i(W,"mouseover",this),i(W,"mousemove",this._onTouchMove),this.nativeDraggable&&(i(W,"drop",this),i(n,"dragstart",this._onDragStart)),this._offUpEvents(),e&&(M&&(e.preventDefault(),!o.dropBubble&&e.stopPropagation()),y&&y.parentNode&&y.parentNode.removeChild(y),T!==D&&"clone"===t.active.lastPullMode||w&&w.parentNode&&w.parentNode.removeChild(w),_&&(this.nativeDraggable&&i(_,"dragend",this),d(_),_.style["will-change"]="",r(_,this.options.ghostClass,!1),r(_,this.options.chosenClass,!1),l(this,T,"unchoose",_,D,T,Y),T!==D?(O=u(_,o.draggable))>=0&&(l(null,D,"add",_,D,T,Y,O),l(this,T,"remove",_,D,T,Y,O),l(null,D,"sort",_,D,T,Y,O),l(this,T,"sort",_,D,T,Y,O)):_.nextSibling!==C&&(O=u(_,o.draggable))>=0&&(l(this,T,"update",_,D,T,Y,O),l(this,T,"sort",_,D,T,Y,O)),t.active&&(null!=O&&-1!==O||(O=Y),l(this,T,"end",_,D,T,Y,O),this.save()))),this._nulling()},_nulling:function(){T=_=D=y=C=w=S=E=k=R=A=M=O=N=B=I=X=t.active=null,nt.forEach(function(t){t.checked=!0}),nt.length=0},handleEvent:function(t){switch(t.type){case"drop":case"dragend":this._onDrop(t);break;case"dragover":case"dragenter":_&&(this._onDragOver(t),function(t){t.dataTransfer&&(t.dataTransfer.dropEffect="move"),t.preventDefault()}(t));break;case"mouseover":this._onDrop(t);break;case"selectstart":t.preventDefault()}},toArray:function(){for(var t,e=[],o=this.el.children,i=0,r=o.length,a=this.options;i<r;i++)n(t=o[i],a.draggable,this.el)&&e.push(t.getAttribute(a.dataIdAttr)||function(t){for(var e=t.tagName+t.className+t.src+t.href+t.textContent,n=e.length,o=0;n--;)o+=e.charCodeAt(n);return o.toString(36)}(t));return e},sort:function(t){var e={},o=this.el;this.toArray().forEach(function(t,i){var r=o.children[i];n(r,this.options.draggable,o)&&(e[t]=r)},this),t.forEach(function(t){e[t]&&(o.removeChild(e[t]),o.appendChild(e[t]))})},save:function(){var t=this.options.store;t&&t.set(this)},closest:function(t,e){return n(t,e||this.options.draggable,this.el)},option:function(t,e){var n=this.options;if(void 0===e)return n[t];n[t]=e,"group"===t&&rt(n)},destroy:function(){var t=this.el;t[U]=null,i(t,"mousedown",this._onTapStart),i(t,"touchstart",this._onTapStart),i(t,"pointerdown",this._onTapStart),this.nativeDraggable&&(i(t,"dragover",this),i(t,"dragenter",this)),Array.prototype.forEach.call(t.querySelectorAll("[draggable]"),function(t){t.removeAttribute("draggable")}),ot.splice(ot.indexOf(this._onDragOver),1),this._onDrop(),this.el=t=null}},o(W,"touchmove",function(e){t.active&&e.preventDefault()}),t.utils={on:o,off:i,css:a,find:s,is:function(t,e){return!!n(t,e,t)},extend:g,throttle:p,closest:n,toggleClass:r,clone:v,index:u,nextTick:m,cancelNextTick:b},t.create=function(e,n){return new t(e,n)},t.version="1.7.0",t}),function(t){"use strict";"function"==typeof define&&define.amd?define(["jquery"],t):t(jQuery)}(function(t){"use strict";t.fn.kvsortable=function(e){var n,o=arguments;return this.each(function(){var i=t(this),r=i.data("kvsortable");r||!(e instanceof Object)&&e||(r=new KvSortable(this,e),i.data("kvsortable",r)),r&&("widget"===e?n=r:"destroy"===e?(r.destroy(),i.removeData("kvsortable")):"function"==typeof r[e]?n=r[e].apply(r,[].slice.call(o,1)):e in r.options&&(n=r.option.apply(r,o)))}),void 0===n?this:n}}); diff --git a/static/plugins/bootstrap-fileinput/nuget/Package.nuspec b/static/plugins/bootstrap-fileinput/nuget/Package.nuspec new file mode 100644 index 00000000..f9cee388 --- /dev/null +++ b/static/plugins/bootstrap-fileinput/nuget/Package.nuspec @@ -0,0 +1,21 @@ +<?xml version="1.0"?> +<package > + <metadata> + <id>bootstrap-fileinput</id> + <title>bootstrap-fileinput + 4.5.1 + Kartik Visweswaran + Kartik Visweswaran + https://github.com/kartik-v/bootstrap-fileinput/blob/master/LICENSE.md + https://github.com/kartik-v/bootstrap-fileinput + http://getbootstrap.com/favicon.ico + false + An enhanced HTML 5 file input for Bootstrap 3.x with file preview for various files, offers multiple selection, and more. + https://github.com/kartik-v/bootstrap-fileinput/blob/master/CHANGE.md + Copyright 2014 - 2018 + bootstrap bootstrap-fileinput + + + + + \ No newline at end of file diff --git a/static/plugins/bootstrap-fileinput/nuget/build.bat b/static/plugins/bootstrap-fileinput/nuget/build.bat new file mode 100644 index 00000000..d002b9a9 --- /dev/null +++ b/static/plugins/bootstrap-fileinput/nuget/build.bat @@ -0,0 +1,35 @@ +@echo off + +NuGet Update -self +REM remove package content folder +rmdir /s /q content + +REM create new package content folder +mkdir content + +REM create sub folder for js files +mkdir content\Scripts + +REM create sub folders for css and img files +mkdir content\Content +mkdir content\Content\bootstrap-fileinput + +REM delete the previous package versions +REM del bootstrap-fileinput.* + +REM copy the content to the destination folders +xcopy ..\js content\Scripts /D /E /C /R /I /K /Y +xcopy ..\css content\Content\bootstrap-fileinput\css /D /E /C /R /I /K /Y +xcopy ..\img content\Content\bootstrap-fileinput\img /D /E /C /R /I /K /Y +xcopy ..\themes content\Content\bootstrap-fileinput\themes /D /E /C /R /I /K /Y +xcopy ..\scss content\Content\bootstrap-fileinput\scss /D /E /C /R /I /K /Y + +REM create a new package +NuGet Pack Package.nuspec -Exclude NuGet.exe;build.bat + +REM Upload the new package +REM for %%f in (content\Content\bootstrap-fileinput.*) do ( +REM NuGet Push %%f +REM rmdir /s /q content +REM del %%f +REM ) diff --git a/static/plugins/bootstrap-fileinput/themes/explorer-bck/theme.css b/static/plugins/bootstrap-fileinput/themes/explorer-bck/theme.css new file mode 100644 index 00000000..6f727247 --- /dev/null +++ b/static/plugins/bootstrap-fileinput/themes/explorer-bck/theme.css @@ -0,0 +1,156 @@ +/*! + * bootstrap-fileinput v4.5.1 + * http://plugins.krajee.com/file-input + * + * Krajee Explorer theme style for bootstrap-fileinput. Load this theme file after loading `fileinput.css`. + * + * Author: Kartik Visweswaran + * Copyright: 2014 - 2018, Kartik Visweswaran, Krajee.com + * + * Licensed under the BSD 3-Clause + * https://github.com/kartik-v/bootstrap-fileinput/blob/master/LICENSE.md + */ +.theme-explorer .file-upload-indicator, .theme-explorer .file-drag-handle, .theme-explorer .explorer-frame .kv-file-content, .theme-explorer .file-actions, .explorer-frame .file-preview-other { + text-align: center; +} + +.theme-explorer .file-thumb-progress .progress, .theme-explorer .file-thumb-progress .progress-bar { + height: 13px; + font-size: 11px; + line-height: 13px; +} + +.theme-explorer .file-upload-indicator, .theme-explorer .file-drag-handle { + position: absolute; + display: inline-block; + top: 0; + right: 3px; + width: 16px; + height: 16px; + font-size: 16px; +} + +.theme-explorer .file-thumb-progress .progress, .theme-explorer .explorer-caption { + display: block; +} + +.theme-explorer .explorer-frame td { + vertical-align: middle; + text-align: left; +} + +.theme-explorer .explorer-frame .kv-file-content { + width: 80px; + height: 80px; + padding: 5px; +} + +.theme-explorer .file-actions-cell { + position: relative; + width: 120px; + padding: 0; +} + +.theme-explorer .file-thumb-progress .progress { + margin-top: 5px; +} + +.theme-explorer .explorer-caption { + color: #777; +} + +.theme-explorer .kvsortable-ghost { + opacity: 0.6; + background: #e1edf7; + border: 2px solid #a1abff; +} + +.theme-explorer .file-preview .table { + margin: 0; +} + +.theme-explorer .file-error-message ul { + padding: 5px 0 0 20px; +} + +.explorer-frame .file-preview-text { + display: inline-block; + color: #428bca; + border: 1px solid #ddd; + font-family: Menlo, Monaco, Consolas, "Courier New", monospace; + outline: none; + padding: 8px; + resize: none; +} + +.explorer-frame .file-preview-html { + display: inline-block; + border: 1px solid #ddd; + padding: 8px; + overflow: auto; +} + +.explorer-frame .file-other-icon { + font-size: 2.6em; +} + +@media only screen and (max-width: 767px) { + .theme-explorer .table, .theme-explorer .table tbody, .theme-explorer .table tr, .theme-explorer .table td { + display: block; + width: 100% !important; + } + + .theme-explorer .table { + border: none; + } + + .theme-explorer .table tr { + margin-top: 5px; + } + + .theme-explorer .table tr:first-child { + margin-top: 0; + } + + .theme-explorer .table td { + text-align: center; + } + + .theme-explorer .table .kv-file-content { + border-bottom: none; + padding: 4px; + margin: 0; + } + + .theme-explorer .table .kv-file-content .file-preview-image { + max-width: 100%; + font-size: 20px; + } + + .theme-explorer .file-details-cell { + border-top: none; + border-bottom: none; + padding-top: 0; + margin: 0; + } + + .theme-explorer .file-actions-cell { + border-top: none; + padding-bottom: 4px; + } + + .theme-explorer .explorer-frame .explorer-caption { + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + left: 0; + right: 0; + margin: auto; + } +} + +/*noinspection CssOverwrittenProperties*/ +.file-zoom-dialog .explorer-frame .file-other-icon { + font-size: 22em; + font-size: 50vmin; +} diff --git a/static/plugins/bootstrap-fileinput/themes/explorer-bck/theme.js b/static/plugins/bootstrap-fileinput/themes/explorer-bck/theme.js new file mode 100644 index 00000000..1b9624d0 --- /dev/null +++ b/static/plugins/bootstrap-fileinput/themes/explorer-bck/theme.js @@ -0,0 +1,58 @@ +/*! + * bootstrap-fileinput v4.5.1 + * http://plugins.krajee.com/file-input + * + * Krajee Explorer theme configuration for bootstrap-fileinput. Load this theme file after loading `fileinput.js`. + * + * Author: Kartik Visweswaran + * Copyright: 2014 - 2018, Kartik Visweswaran, Krajee.com + * + * Licensed under the BSD 3-Clause + * https://github.com/kartik-v/bootstrap-fileinput/blob/master/LICENSE.md + */ +(function ($) { + "use strict"; + var teTagBef = '\n' + + ' {close}' + + '

    \n' + + ' \n' + + '
    \n' + + '
    ' + + '
    \n' + + '
    \n' + + '
    \n' + + '
    ', + footer: '
    {caption}
    ' + + '{size}{progress}{indicator} {actions}', + actions: '{drag}\n' + + '
    \n' + + ' \n' + + '
    ', + zoomCache: '' + + '{zoomContent}
    ' + }, + previewMarkupTags: { + tagBefore1: teTagBef + '>' + teContent, + tagBefore2: teTagBef + ' title="{caption}">' + teContent, + tagAfter: '\n{footer}\n' + }, + previewSettings: { + image: {height: "60px"}, + html: {width: "100px", height: "60px"}, + text: {width: "100px", height: "60px"}, + video: {width: "auto", height: "60px"}, + audio: {width: "auto", height: "60px"}, + flash: {width: "100%", height: "60px"}, + object: {width: "100%", height: "60px"}, + pdf: {width: "100px", height: "60px"}, + other: {width: "100%", height: "60px"} + }, + frameClass: 'explorer-frame' + }; +})(window.jQuery); diff --git a/static/plugins/bootstrap-fileinput/themes/explorer-bck/theme.min.css b/static/plugins/bootstrap-fileinput/themes/explorer-bck/theme.min.css new file mode 100644 index 00000000..8ff8b862 --- /dev/null +++ b/static/plugins/bootstrap-fileinput/themes/explorer-bck/theme.min.css @@ -0,0 +1,12 @@ +/*! + * bootstrap-fileinput v4.5.1 + * http://plugins.krajee.com/file-input + * + * Krajee Explorer theme style for bootstrap-fileinput. Load this theme file after loading `fileinput.css`. + * + * Author: Kartik Visweswaran + * Copyright: 2014 - 2018, Kartik Visweswaran, Krajee.com + * + * Licensed under the BSD 3-Clause + * https://github.com/kartik-v/bootstrap-fileinput/blob/master/LICENSE.md + */.explorer-frame .file-preview-other,.theme-explorer .explorer-frame .kv-file-content,.theme-explorer .file-actions,.theme-explorer .file-drag-handle,.theme-explorer .file-upload-indicator{text-align:center}.theme-explorer .file-thumb-progress .progress,.theme-explorer .file-thumb-progress .progress-bar{height:13px;font-size:11px;line-height:13px}.theme-explorer .file-drag-handle,.theme-explorer .file-upload-indicator{position:absolute;display:inline-block;top:0;right:3px;width:16px;height:16px;font-size:16px}.theme-explorer .explorer-caption,.theme-explorer .file-thumb-progress .progress{display:block}.theme-explorer .explorer-frame td{vertical-align:middle;text-align:left}.theme-explorer .explorer-frame .kv-file-content{width:80px;height:80px;padding:5px}.theme-explorer .file-actions-cell{position:relative;width:120px;padding:0}.theme-explorer .file-thumb-progress .progress{margin-top:5px}.theme-explorer .explorer-caption{color:#777}.theme-explorer .kvsortable-ghost{opacity:.6;background:#e1edf7;border:2px solid #a1abff}.theme-explorer .file-preview .table{margin:0}.theme-explorer .file-error-message ul{padding:5px 0 0 20px}.explorer-frame .file-preview-text{display:inline-block;color:#428bca;border:1px solid #ddd;font-family:Menlo,Monaco,Consolas,"Courier New",monospace;outline:0;padding:8px;resize:none}.explorer-frame .file-preview-html{display:inline-block;border:1px solid #ddd;padding:8px;overflow:auto}.explorer-frame .file-other-icon{font-size:2.6em}@media only screen and (max-width:767px){.theme-explorer .table,.theme-explorer .table tbody,.theme-explorer .table td,.theme-explorer .table tr{display:block;width:100%!important}.theme-explorer .table{border:none}.theme-explorer .table tr{margin-top:5px}.theme-explorer .table tr:first-child{margin-top:0}.theme-explorer .table td{text-align:center}.theme-explorer .table .kv-file-content{border-bottom:none;padding:4px;margin:0}.theme-explorer .table .kv-file-content .file-preview-image{max-width:100%;font-size:20px}.theme-explorer .file-details-cell{border-top:none;border-bottom:none;padding-top:0;margin:0}.theme-explorer .file-actions-cell{border-top:none;padding-bottom:4px}.theme-explorer .explorer-frame .explorer-caption{white-space:nowrap;text-overflow:ellipsis;overflow:hidden;left:0;right:0;margin:auto}}.file-zoom-dialog .explorer-frame .file-other-icon{font-size:22em;font-size:50vmin} \ No newline at end of file diff --git a/static/plugins/bootstrap-fileinput/themes/explorer-bck/theme.min.js b/static/plugins/bootstrap-fileinput/themes/explorer-bck/theme.min.js new file mode 100644 index 00000000..34d6540a --- /dev/null +++ b/static/plugins/bootstrap-fileinput/themes/explorer-bck/theme.min.js @@ -0,0 +1,12 @@ +/*! + * bootstrap-fileinput v4.5.1 + * http://plugins.krajee.com/file-input + * + * Krajee Explorer theme configuration for bootstrap-fileinput. Load this theme file after loading `fileinput.js`. + * + * Author: Kartik Visweswaran + * Copyright: 2014 - 2018, Kartik Visweswaran, Krajee.com + * + * Licensed under the BSD 3-Clause + * https://github.com/kartik-v/bootstrap-fileinput/blob/master/LICENSE.md + */!function(e){"use strict";var t='\n {close}
    \n \n
    \n
    \n
    \n
    \n
    ',footer:'
    {caption}
    {size}{progress}{indicator} {actions}',actions:'{drag}\n
    \n \n
    ',zoomCache:'{zoomContent}
    '},previewMarkupTags:{tagBefore1:t+">"+i,tagBefore2:t+' title="{caption}">'+i,tagAfter:"\n{footer}\n"},previewSettings:{image:{height:"60px"},html:{width:"100px",height:"60px"},text:{width:"100px",height:"60px"},video:{width:"auto",height:"60px"},audio:{width:"auto",height:"60px"},flash:{width:"100%",height:"60px"},object:{width:"100%",height:"60px"},pdf:{width:"100px",height:"60px"},other:{width:"100%",height:"60px"}},frameClass:"explorer-frame"}}(window.jQuery); diff --git a/static/plugins/bootstrap-fileinput/themes/explorer-fa/theme.css b/static/plugins/bootstrap-fileinput/themes/explorer-fa/theme.css new file mode 100644 index 00000000..b7011b35 --- /dev/null +++ b/static/plugins/bootstrap-fileinput/themes/explorer-fa/theme.css @@ -0,0 +1,157 @@ +/*! + * bootstrap-fileinput v4.5.1 + * http://plugins.krajee.com/file-input + * + * Krajee Explorer Font Awesome 4.x theme style for bootstrap-fileinput. Load this theme file after loading + * font awesome 4.x CSS and `fileinput.css`. + * + * Author: Kartik Visweswaran + * Copyright: 2014 - 2018, Kartik Visweswaran, Krajee.com + * + * Licensed under the BSD 3-Clause + * https://github.com/kartik-v/bootstrap-fileinput/blob/master/LICENSE.md + */ +.theme-explorer-fa .file-upload-indicator, .theme-explorer-fa .file-drag-handle, .theme-explorer-fa .explorer-frame .kv-file-content, .theme-explorer-fa .file-actions, .explorer-frame .file-preview-other { + text-align: center; +} + +.theme-explorer-fa .file-thumb-progress .progress, .theme-explorer-fa .file-thumb-progress .progress-bar { + height: 13px; + font-size: 11px; + line-height: 13px; +} + +.theme-explorer-fa .file-upload-indicator, .theme-explorer-fa .file-drag-handle { + position: absolute; + display: inline-block; + top: 0; + right: 3px; + width: 16px; + height: 16px; + font-size: 16px; +} + +.theme-explorer-fa .file-thumb-progress .progress, .theme-explorer-fa .explorer-caption { + display: block; +} + +.theme-explorer-fa .explorer-frame td { + vertical-align: middle; + text-align: left; +} + +.theme-explorer-fa .explorer-frame .kv-file-content { + width: 80px; + height: 80px; + padding: 5px; +} + +.theme-explorer-fa .file-actions-cell { + position: relative; + width: 120px; + padding: 0; +} + +.theme-explorer-fa .file-thumb-progress .progress { + margin-top: 5px; +} + +.theme-explorer-fa .explorer-caption { + color: #777; +} + +.theme-explorer-fa .kvsortable-ghost { + opacity: 0.6; + background: #e1edf7; + border: 2px solid #a1abff; +} + +.theme-explorer-fa .file-preview .table { + margin: 0; +} + +.theme-explorer-fa .file-error-message ul { + padding: 5px 0 0 20px; +} + +.explorer-frame .file-preview-text { + display: inline-block; + color: #428bca; + border: 1px solid #ddd; + font-family: Menlo, Monaco, Consolas, "Courier New", monospace; + outline: none; + padding: 8px; + resize: none; +} + +.explorer-frame .file-preview-html { + display: inline-block; + border: 1px solid #ddd; + padding: 8px; + overflow: auto; +} + +.explorer-frame .file-other-icon { + font-size: 2.6em; +} + +@media only screen and (max-width: 767px) { + .theme-explorer-fa .table, .theme-explorer-fa .table tbody, .theme-explorer-fa .table tr, .theme-explorer-fa .table td { + display: block; + width: 100% !important; + } + + .theme-explorer-fa .table { + border: none; + } + + .theme-explorer-fa .table tr { + margin-top: 5px; + } + + .theme-explorer-fa .table tr:first-child { + margin-top: 0; + } + + .theme-explorer-fa .table td { + text-align: center; + } + + .theme-explorer-fa .table .kv-file-content { + border-bottom: none; + padding: 4px; + margin: 0; + } + + .theme-explorer-fa .table .kv-file-content .file-preview-image { + max-width: 100%; + font-size: 20px; + } + + .theme-explorer-fa .file-details-cell { + border-top: none; + border-bottom: none; + padding-top: 0; + margin: 0; + } + + .theme-explorer-fa .file-actions-cell { + border-top: none; + padding-bottom: 4px; + } + + .theme-explorer-fa .explorer-frame .explorer-caption { + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + left: 0; + right: 0; + margin: auto; + } +} + +/*noinspection CssOverwrittenProperties*/ +.file-zoom-dialog .explorer-frame .file-other-icon { + font-size: 22em; + font-size: 50vmin; +} \ No newline at end of file diff --git a/static/plugins/bootstrap-fileinput/themes/explorer-fa/theme.js b/static/plugins/bootstrap-fileinput/themes/explorer-fa/theme.js new file mode 100644 index 00000000..25ec93b2 --- /dev/null +++ b/static/plugins/bootstrap-fileinput/themes/explorer-fa/theme.js @@ -0,0 +1,87 @@ +/*! + * bootstrap-fileinput v4.5.1 + * http://plugins.krajee.com/file-input + * + * Krajee Explorer Font Awesome theme configuration for bootstrap-fileinput. + * Load this theme file after loading `fileinput.js`. Ensure that + * font awesome assets and CSS are loaded on the page as well. + * + * Author: Kartik Visweswaran + * Copyright: 2014 - 2018, Kartik Visweswaran, Krajee.com + * + * Licensed under the BSD 3-Clause + * https://github.com/kartik-v/bootstrap-fileinput/blob/master/LICENSE.md + */ +(function ($) { + "use strict"; + var teTagBef = '\n' + + ' {close}' + + '
    \n' + + ' \n' + + '
    \n' + + '
    ' + + '
    \n' + + '
    \n' + + '
    \n' + + '
    ', + footer: '
    {caption}
    ' + + '{size}{progress}{indicator} {actions}', + actions: '{drag}\n' + + '
    \n' + + ' \n' + + '
    ', + zoomCache: '' + + '{zoomContent}
    ', + fileIcon: ' ' + }, + previewMarkupTags: { + tagBefore1: teTagBef + '>' + teContent, + tagBefore2: teTagBef + ' title="{caption}">' + teContent, + tagAfter: '\n{footer}\n' + }, + previewSettings: { + image: {height: "60px"}, + html: {width: "100px", height: "60px"}, + text: {width: "100px", height: "60px"}, + video: {width: "auto", height: "60px"}, + audio: {width: "auto", height: "60px"}, + flash: {width: "100%", height: "60px"}, + object: {width: "100%", height: "60px"}, + pdf: {width: "100px", height: "60px"}, + other: {width: "100%", height: "60px"} + }, + frameClass: 'explorer-frame', + fileActionSettings: { + removeIcon: '', + uploadIcon: '', + uploadRetryIcon: '', + downloadIcon: '', + zoomIcon: '', + dragIcon: '', + indicatorNew: '', + indicatorSuccess: '', + indicatorError: '', + indicatorLoading: '' + }, + previewZoomButtonIcons: { + prev: '', + next: '', + toggleheader: '', + fullscreen: '', + borderless: '', + close: '' + }, + previewFileIcon: '', + browseIcon: '', + removeIcon: '', + cancelIcon: '', + uploadIcon: '', + msgValidationErrorIcon: ' ' + }; +})(window.jQuery); \ No newline at end of file diff --git a/static/plugins/bootstrap-fileinput/themes/explorer-fa/theme.min.css b/static/plugins/bootstrap-fileinput/themes/explorer-fa/theme.min.css new file mode 100644 index 00000000..7e55ab4f --- /dev/null +++ b/static/plugins/bootstrap-fileinput/themes/explorer-fa/theme.min.css @@ -0,0 +1,13 @@ +/*! + * bootstrap-fileinput v4.5.1 + * http://plugins.krajee.com/file-input + * + * Krajee Explorer Font Awesome 4.x theme style for bootstrap-fileinput. Load this theme file after loading + * font awesome 4.x CSS and `fileinput.css`. + * + * Author: Kartik Visweswaran + * Copyright: 2014 - 2018, Kartik Visweswaran, Krajee.com + * + * Licensed under the BSD 3-Clause + * https://github.com/kartik-v/bootstrap-fileinput/blob/master/LICENSE.md + */.explorer-frame .file-preview-other,.theme-explorer-fa .explorer-frame .kv-file-content,.theme-explorer-fa .file-actions,.theme-explorer-fa .file-drag-handle,.theme-explorer-fa .file-upload-indicator{text-align:center}.theme-explorer-fa .file-thumb-progress .progress,.theme-explorer-fa .file-thumb-progress .progress-bar{height:13px;font-size:11px;line-height:13px}.theme-explorer-fa .file-drag-handle,.theme-explorer-fa .file-upload-indicator{position:absolute;display:inline-block;top:0;right:3px;width:16px;height:16px;font-size:16px}.theme-explorer-fa .explorer-caption,.theme-explorer-fa .file-thumb-progress .progress{display:block}.theme-explorer-fa .explorer-frame td{vertical-align:middle;text-align:left}.theme-explorer-fa .explorer-frame .kv-file-content{width:80px;height:80px;padding:5px}.theme-explorer-fa .file-actions-cell{position:relative;width:120px;padding:0}.theme-explorer-fa .file-thumb-progress .progress{margin-top:5px}.theme-explorer-fa .explorer-caption{color:#777}.theme-explorer-fa .kvsortable-ghost{opacity:.6;background:#e1edf7;border:2px solid #a1abff}.theme-explorer-fa .file-preview .table{margin:0}.theme-explorer-fa .file-error-message ul{padding:5px 0 0 20px}.explorer-frame .file-preview-text{display:inline-block;color:#428bca;border:1px solid #ddd;font-family:Menlo,Monaco,Consolas,"Courier New",monospace;outline:0;padding:8px;resize:none}.explorer-frame .file-preview-html{display:inline-block;border:1px solid #ddd;padding:8px;overflow:auto}.explorer-frame .file-other-icon{font-size:2.6em}@media only screen and (max-width:767px){.theme-explorer-fa .table,.theme-explorer-fa .table tbody,.theme-explorer-fa .table td,.theme-explorer-fa .table tr{display:block;width:100%!important}.theme-explorer-fa .table{border:none}.theme-explorer-fa .table tr{margin-top:5px}.theme-explorer-fa .table tr:first-child{margin-top:0}.theme-explorer-fa .table td{text-align:center}.theme-explorer-fa .table .kv-file-content{border-bottom:none;padding:4px;margin:0}.theme-explorer-fa .table .kv-file-content .file-preview-image{max-width:100%;font-size:20px}.theme-explorer-fa .file-details-cell{border-top:none;border-bottom:none;padding-top:0;margin:0}.theme-explorer-fa .file-actions-cell{border-top:none;padding-bottom:4px}.theme-explorer-fa .explorer-frame .explorer-caption{white-space:nowrap;text-overflow:ellipsis;overflow:hidden;left:0;right:0;margin:auto}}.file-zoom-dialog .explorer-frame .file-other-icon{font-size:22em;font-size:50vmin} \ No newline at end of file diff --git a/static/plugins/bootstrap-fileinput/themes/explorer-fa/theme.min.js b/static/plugins/bootstrap-fileinput/themes/explorer-fa/theme.min.js new file mode 100644 index 00000000..f8e7f523 --- /dev/null +++ b/static/plugins/bootstrap-fileinput/themes/explorer-fa/theme.min.js @@ -0,0 +1,14 @@ +/*! + * bootstrap-fileinput v4.5.1 + * http://plugins.krajee.com/file-input + * + * Krajee Explorer Font Awesome theme configuration for bootstrap-fileinput. + * Load this theme file after loading `fileinput.js`. Ensure that + * font awesome assets and CSS are loaded on the page as well. + * + * Author: Kartik Visweswaran + * Copyright: 2014 - 2018, Kartik Visweswaran, Krajee.com + * + * Licensed under the BSD 3-Clause + * https://github.com/kartik-v/bootstrap-fileinput/blob/master/LICENSE.md + */!function(a){"use strict";var e='\n {close}
    \n \n
    \n
    \n
    \n
    \n
    ',footer:'
    {caption}
    {size}{progress}{indicator} {actions}',actions:'{drag}\n
    \n \n
    ',zoomCache:'{zoomContent}
    ',fileIcon:' '},previewMarkupTags:{tagBefore1:e+">"+i,tagBefore2:e+' title="{caption}">'+i,tagAfter:"\n{footer}\n"},previewSettings:{image:{height:"60px"},html:{width:"100px",height:"60px"},text:{width:"100px",height:"60px"},video:{width:"auto",height:"60px"},audio:{width:"auto",height:"60px"},flash:{width:"100%",height:"60px"},object:{width:"100%",height:"60px"},pdf:{width:"100px",height:"60px"},other:{width:"100%",height:"60px"}},frameClass:"explorer-frame",fileActionSettings:{removeIcon:'',uploadIcon:'',uploadRetryIcon:'',downloadIcon:'',zoomIcon:'',dragIcon:'',indicatorNew:'',indicatorSuccess:'',indicatorError:'',indicatorLoading:''},previewZoomButtonIcons:{prev:'',next:'',toggleheader:'',fullscreen:'',borderless:'',close:''},previewFileIcon:'',browseIcon:'',removeIcon:'',cancelIcon:'',uploadIcon:'',msgValidationErrorIcon:' '}}(window.jQuery); \ No newline at end of file diff --git a/static/plugins/bootstrap-fileinput/themes/explorer-fas/theme.css b/static/plugins/bootstrap-fileinput/themes/explorer-fas/theme.css new file mode 100644 index 00000000..1d1b5cba --- /dev/null +++ b/static/plugins/bootstrap-fileinput/themes/explorer-fas/theme.css @@ -0,0 +1,157 @@ +/*! + * bootstrap-fileinput v4.5.1 + * http://plugins.krajee.com/file-input + * + * Krajee Explorer Font Awesome 5.x theme style for bootstrap-fileinput. Load this theme file after loading + * font awesome 5.x CSS and `fileinput.css`. + * + * Author: Kartik Visweswaran + * Copyright: 2014 - 2018, Kartik Visweswaran, Krajee.com + * + * Licensed under the BSD 3-Clause + * https://github.com/kartik-v/bootstrap-fileinput/blob/master/LICENSE.md + */ +.theme-explorer-fas .file-upload-indicator, .theme-explorer-fas .file-drag-handle, .theme-explorer-fas .explorer-frame .kv-file-content, .theme-explorer-fas .file-actions, .explorer-frame .file-preview-other { + text-align: center; +} + +.theme-explorer-fas .file-thumb-progress .progress, .theme-explorer-fas .file-thumb-progress .progress-bar { + height: 13px; + font-size: 11px; + line-height: 13px; +} + +.theme-explorer-fas .file-upload-indicator, .theme-explorer-fas .file-drag-handle { + position: absolute; + display: inline-block; + top: 0; + right: 3px; + width: 16px; + height: 16px; + font-size: 16px; +} + +.theme-explorer-fas .file-thumb-progress .progress, .theme-explorer-fas .explorer-caption { + display: block; +} + +.theme-explorer-fas .explorer-frame td { + vertical-align: middle; + text-align: left; +} + +.theme-explorer-fas .explorer-frame .kv-file-content { + width: 80px; + height: 80px; + padding: 5px; +} + +.theme-explorer-fas .file-actions-cell { + position: relative; + width: 120px; + padding: 0; +} + +.theme-explorer-fas .file-thumb-progress .progress { + margin-top: 5px; +} + +.theme-explorer-fas .explorer-caption { + color: #777; +} + +.theme-explorer-fas .kvsortable-ghost { + opacity: 0.6; + background: #e1edf7; + border: 2px solid #a1abff; +} + +.theme-explorer-fas .file-preview .table { + margin: 0; +} + +.theme-explorer-fas .file-error-message ul { + padding: 5px 0 0 20px; +} + +.explorer-frame .file-preview-text { + display: inline-block; + color: #428bca; + border: 1px solid #ddd; + font-family: Menlo, Monaco, Consolas, "Courier New", monospace; + outline: none; + padding: 8px; + resize: none; +} + +.explorer-frame .file-preview-html { + display: inline-block; + border: 1px solid #ddd; + padding: 8px; + overflow: auto; +} + +.explorer-frame .file-other-icon { + font-size: 2.6em; +} + +@media only screen and (max-width: 767px) { + .theme-explorer-fas .table, .theme-explorer-fas .table tbody, .theme-explorer-fas .table tr, .theme-explorer-fas .table td { + display: block; + width: 100% !important; + } + + .theme-explorer-fas .table { + border: none; + } + + .theme-explorer-fas .table tr { + margin-top: 5px; + } + + .theme-explorer-fas .table tr:first-child { + margin-top: 0; + } + + .theme-explorer-fas .table td { + text-align: center; + } + + .theme-explorer-fas .table .kv-file-content { + border-bottom: none; + padding: 4px; + margin: 0; + } + + .theme-explorer-fas .table .kv-file-content .file-preview-image { + max-width: 100%; + font-size: 20px; + } + + .theme-explorer-fas .file-details-cell { + border-top: none; + border-bottom: none; + padding-top: 0; + margin: 0; + } + + .theme-explorer-fas .file-actions-cell { + border-top: none; + padding-bottom: 4px; + } + + .theme-explorer-fas .explorer-frame .explorer-caption { + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + left: 0; + right: 0; + margin: auto; + } +} + +/*noinspection CssOverwrittenProperties*/ +.file-zoom-dialog .explorer-frame .file-other-icon { + font-size: 22em; + font-size: 50vmin; +} \ No newline at end of file diff --git a/static/plugins/bootstrap-fileinput/themes/explorer-fas/theme.js b/static/plugins/bootstrap-fileinput/themes/explorer-fas/theme.js new file mode 100644 index 00000000..48b0412b --- /dev/null +++ b/static/plugins/bootstrap-fileinput/themes/explorer-fas/theme.js @@ -0,0 +1,87 @@ +/*! + * bootstrap-fileinput v4.5.1 + * http://plugins.krajee.com/file-input + * + * Krajee Explorer Font Awesome theme configuration for bootstrap-fileinput. + * Load this theme file after loading `fileinput.js`. Ensure that + * font awesome assets and CSS are loaded on the page as well. + * + * Author: Kartik Visweswaran + * Copyright: 2014 - 2018, Kartik Visweswaran, Krajee.com + * + * Licensed under the BSD 3-Clause + * https://github.com/kartik-v/bootstrap-fileinput/blob/master/LICENSE.md + */ +(function ($) { + "use strict"; + var teTagBef = '\n' + + ' {close}' + + '
    \n' + + ' \n' + + '
    \n' + + '
    ' + + '
    \n' + + '
    \n' + + '
    \n' + + '
    ', + footer: '
    {caption}
    ' + + '{size}{progress}{indicator} {actions}', + actions: '{drag}\n' + + '
    \n' + + ' \n' + + '
    ', + zoomCache: '' + + '{zoomContent}
    ', + fileIcon: ' ' + }, + previewMarkupTags: { + tagBefore1: teTagBef + '>' + teContent, + tagBefore2: teTagBef + ' title="{caption}">' + teContent, + tagAfter: '\n{footer}\n' + }, + previewSettings: { + image: {height: "60px"}, + html: {width: "100px", height: "60px"}, + text: {width: "100px", height: "60px"}, + video: {width: "auto", height: "60px"}, + audio: {width: "auto", height: "60px"}, + flash: {width: "100%", height: "60px"}, + object: {width: "100%", height: "60px"}, + pdf: {width: "100px", height: "60px"}, + other: {width: "100%", height: "60px"} + }, + frameClass: 'explorer-frame', + fileActionSettings: { + removeIcon: '', + uploadIcon: '', + uploadRetryIcon: '', + downloadIcon: '', + zoomIcon: '', + dragIcon: '', + indicatorNew: '', + indicatorSuccess: '', + indicatorError: '', + indicatorLoading: '' + }, + previewZoomButtonIcons: { + prev: '', + next: '', + toggleheader: '', + fullscreen: '', + borderless: '', + close: '' + }, + previewFileIcon: '', + browseIcon: '', + removeIcon: '', + cancelIcon: '', + uploadIcon: '', + msgValidationErrorIcon: ' ' + }; +})(window.jQuery); \ No newline at end of file diff --git a/static/plugins/bootstrap-fileinput/themes/explorer-fas/theme.min.css b/static/plugins/bootstrap-fileinput/themes/explorer-fas/theme.min.css new file mode 100644 index 00000000..b39ffb26 --- /dev/null +++ b/static/plugins/bootstrap-fileinput/themes/explorer-fas/theme.min.css @@ -0,0 +1,13 @@ +/*! + * bootstrap-fileinput v4.5.1 + * http://plugins.krajee.com/file-input + * + * Krajee Explorer Font Awesome 5.x theme style for bootstrap-fileinput. Load this theme file after loading + * font awesome 5.x CSS and `fileinput.css`. + * + * Author: Kartik Visweswaran + * Copyright: 2014 - 2018, Kartik Visweswaran, Krajee.com + * + * Licensed under the BSD 3-Clause + * https://github.com/kartik-v/bootstrap-fileinput/blob/master/LICENSE.md + */.explorer-frame .file-preview-other,.theme-explorer-fas .explorer-frame .kv-file-content,.theme-explorer-fas .file-actions,.theme-explorer-fas .file-drag-handle,.theme-explorer-fas .file-upload-indicator{text-align:center}.theme-explorer-fas .file-thumb-progress .progress,.theme-explorer-fas .file-thumb-progress .progress-bar{height:13px;font-size:11px;line-height:13px}.theme-explorer-fas .file-drag-handle,.theme-explorer-fas .file-upload-indicator{position:absolute;display:inline-block;top:0;right:3px;width:16px;height:16px;font-size:16px}.theme-explorer-fas .explorer-caption,.theme-explorer-fas .file-thumb-progress .progress{display:block}.theme-explorer-fas .explorer-frame td{vertical-align:middle;text-align:left}.theme-explorer-fas .explorer-frame .kv-file-content{width:80px;height:80px;padding:5px}.theme-explorer-fas .file-actions-cell{position:relative;width:120px;padding:0}.theme-explorer-fas .file-thumb-progress .progress{margin-top:5px}.theme-explorer-fas .explorer-caption{color:#777}.theme-explorer-fas .kvsortable-ghost{opacity:.6;background:#e1edf7;border:2px solid #a1abff}.theme-explorer-fas .file-preview .table{margin:0}.theme-explorer-fas .file-error-message ul{padding:5px 0 0 20px}.explorer-frame .file-preview-text{display:inline-block;color:#428bca;border:1px solid #ddd;font-family:Menlo,Monaco,Consolas,"Courier New",monospace;outline:0;padding:8px;resize:none}.explorer-frame .file-preview-html{display:inline-block;border:1px solid #ddd;padding:8px;overflow:auto}.explorer-frame .file-other-icon{font-size:2.6em}@media only screen and (max-width:767px){.theme-explorer-fas .table,.theme-explorer-fas .table tbody,.theme-explorer-fas .table td,.theme-explorer-fas .table tr{display:block;width:100%!important}.theme-explorer-fas .table{border:none}.theme-explorer-fas .table tr{margin-top:5px}.theme-explorer-fas .table tr:first-child{margin-top:0}.theme-explorer-fas .table td{text-align:center}.theme-explorer-fas .table .kv-file-content{border-bottom:none;padding:4px;margin:0}.theme-explorer-fas .table .kv-file-content .file-preview-image{max-width:100%;font-size:20px}.theme-explorer-fas .file-details-cell{border-top:none;border-bottom:none;padding-top:0;margin:0}.theme-explorer-fas .file-actions-cell{border-top:none;padding-bottom:4px}.theme-explorer-fas .explorer-frame .explorer-caption{white-space:nowrap;text-overflow:ellipsis;overflow:hidden;left:0;right:0;margin:auto}}.file-zoom-dialog .explorer-frame .file-other-icon{font-size:22em;font-size:50vmin} \ No newline at end of file diff --git a/static/plugins/bootstrap-fileinput/themes/explorer-fas/theme.min.js b/static/plugins/bootstrap-fileinput/themes/explorer-fas/theme.min.js new file mode 100644 index 00000000..0134b722 --- /dev/null +++ b/static/plugins/bootstrap-fileinput/themes/explorer-fas/theme.min.js @@ -0,0 +1,14 @@ +/*! + * bootstrap-fileinput v4.5.1 + * http://plugins.krajee.com/file-input + * + * Krajee Explorer Font Awesome theme configuration for bootstrap-fileinput. + * Load this theme file after loading `fileinput.js`. Ensure that + * font awesome assets and CSS are loaded on the page as well. + * + * Author: Kartik Visweswaran + * Copyright: 2014 - 2018, Kartik Visweswaran, Krajee.com + * + * Licensed under the BSD 3-Clause + * https://github.com/kartik-v/bootstrap-fileinput/blob/master/LICENSE.md + */!function(a){"use strict";var e='\n {close}
    \n \n
    \n
    \n
    \n
    \n
    ',footer:'
    {caption}
    {size}{progress}{indicator} {actions}',actions:'{drag}\n
    \n \n
    ',zoomCache:'{zoomContent}
    ',fileIcon:' '},previewMarkupTags:{tagBefore1:e+">"+i,tagBefore2:e+' title="{caption}">'+i,tagAfter:"\n{footer}\n"},previewSettings:{image:{height:"60px"},html:{width:"100px",height:"60px"},text:{width:"100px",height:"60px"},video:{width:"auto",height:"60px"},audio:{width:"auto",height:"60px"},flash:{width:"100%",height:"60px"},object:{width:"100%",height:"60px"},pdf:{width:"100px",height:"60px"},other:{width:"100%",height:"60px"}},frameClass:"explorer-frame",fileActionSettings:{removeIcon:'',uploadIcon:'',uploadRetryIcon:'',downloadIcon:'',zoomIcon:'',dragIcon:'',indicatorNew:'',indicatorSuccess:'',indicatorError:'',indicatorLoading:''},previewZoomButtonIcons:{prev:'',next:'',toggleheader:'',fullscreen:'',borderless:'',close:''},previewFileIcon:'',browseIcon:'',removeIcon:'',cancelIcon:'',uploadIcon:'',msgValidationErrorIcon:' '}}(window.jQuery); \ No newline at end of file diff --git a/static/plugins/bootstrap-fileinput/themes/explorer/theme.css b/static/plugins/bootstrap-fileinput/themes/explorer/theme.css new file mode 100644 index 00000000..80789697 --- /dev/null +++ b/static/plugins/bootstrap-fileinput/themes/explorer/theme.css @@ -0,0 +1,156 @@ +/*! + * bootstrap-fileinput v4.5.1 + * http://plugins.krajee.com/file-input + * + * Krajee Explorer theme style for bootstrap-fileinput. Load this theme file after loading `fileinput.css`. + * + * Author: Kartik Visweswaran + * Copyright: 2014 - 2018, Kartik Visweswaran, Krajee.com + * + * Licensed under the BSD 3-Clause + * https://github.com/kartik-v/bootstrap-fileinput/blob/master/LICENSE.md + */ +.theme-explorer .file-upload-indicator, .theme-explorer .file-drag-handle, .theme-explorer .explorer-frame .kv-file-content, .theme-explorer .file-actions, .explorer-frame .file-preview-other { + text-align: center; +} + +.theme-explorer .file-thumb-progress .progress, .theme-explorer .file-thumb-progress .progress-bar { + height: 13px; + font-size: 11px; + line-height: 13px; +} + +.theme-explorer .file-upload-indicator, .theme-explorer .file-drag-handle { + position: absolute; + display: inline-block; + top: 0; + right: 3px; + width: 16px; + height: 16px; + font-size: 16px; +} + +.theme-explorer .file-thumb-progress .progress, .theme-explorer .explorer-caption { + display: block; +} + +.theme-explorer .explorer-frame td { + vertical-align: middle; + text-align: left; +} + +.theme-explorer .explorer-frame .kv-file-content { + width: 80px; + /*height: 80px;*/ + padding: 5px; +} + +.theme-explorer .file-actions-cell { + position: relative; + width: 120px; + padding: 0; +} + +.theme-explorer .file-thumb-progress .progress { + margin-top: 5px; +} + +.theme-explorer .explorer-caption { + color: #777; +} + +.theme-explorer .kvsortable-ghost { + opacity: 0.6; + background: #e1edf7; + border: 2px solid #a1abff; +} + +.theme-explorer .file-preview .table { + margin: 0; +} + +.theme-explorer .file-error-message ul { + padding: 5px 0 0 20px; +} + +.explorer-frame .file-preview-text { + display: inline-block; + color: #428bca; + border: 1px solid #ddd; + font-family: Menlo, Monaco, Consolas, "Courier New", monospace; + outline: none; + padding: 8px; + resize: none; +} + +.explorer-frame .file-preview-html { + display: inline-block; + border: 1px solid #ddd; + padding: 8px; + overflow: auto; +} + +.explorer-frame .file-other-icon { + font-size: 2.6em; +} + +@media only screen and (max-width: 767px) { + .theme-explorer .table, .theme-explorer .table tbody, .theme-explorer .table tr, .theme-explorer .table td { + display: block; + width: 100% !important; + } + + .theme-explorer .table { + border: none; + } + + .theme-explorer .table tr { + margin-top: 5px; + } + + .theme-explorer .table tr:first-child { + margin-top: 0; + } + + .theme-explorer .table td { + text-align: center; + } + + .theme-explorer .table .kv-file-content { + border-bottom: none; + padding: 4px; + margin: 0; + } + + .theme-explorer .table .kv-file-content .file-preview-image { + max-width: 100%; + font-size: 20px; + } + + .theme-explorer .file-details-cell { + border-top: none; + border-bottom: none; + padding-top: 0; + margin: 0; + } + + .theme-explorer .file-actions-cell { + border-top: none; + padding-bottom: 4px; + } + + .theme-explorer .explorer-frame .explorer-caption { + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + left: 0; + right: 0; + margin: auto; + } +} + +/*noinspection CssOverwrittenProperties*/ +.file-zoom-dialog .explorer-frame .file-other-icon { + font-size: 22em; + font-size: 50vmin; +} diff --git a/static/plugins/bootstrap-fileinput/themes/explorer/theme.js b/static/plugins/bootstrap-fileinput/themes/explorer/theme.js new file mode 100644 index 00000000..c341aa7c --- /dev/null +++ b/static/plugins/bootstrap-fileinput/themes/explorer/theme.js @@ -0,0 +1,56 @@ +/*! + * bootstrap-fileinput v4.5.1 + * http://plugins.krajee.com/file-input + * + * Krajee Explorer theme configuration for bootstrap-fileinput. Load this theme file after loading `fileinput.js`. + * + * Author: Kartik Visweswaran + * Copyright: 2014 - 2018, Kartik Visweswaran, Krajee.com + * + * Licensed under the BSD 3-Clause + * https://github.com/kartik-v/bootstrap-fileinput/blob/master/LICENSE.md + */ +(function ($) { + "use strict"; + var teTagBef = '\n' + + '
    \n' + + ' \n' + + '
    \n' + + '
    ' + + '
    \n' + + '
    \n' + + '
    \n' + + '', + footer: '
    {caption}
    ' + + '{size}{progress}{indicator} {actions}', + actions: '
    \n' + + ' \n' + + '
    ', + zoomCache: '' + + '{zoomContent}
    ' + }, + previewMarkupTags: { + tagBefore1: teTagBef + '>' + teContent, + tagBefore2: teTagBef + ' title="{caption}">' + teContent, + tagAfter: '\n{footer}\n' + }, + previewSettings: { + image: {height: "20px"}, + html: {width: "auto", height: "25px"}, + text: {width: "auto", height: "25px"}, + video: {width: "auto", height: "25px"}, + audio: {width: "auto", height: "25px"}, + flash: {width: "auto", height: "25px"}, + object: {width: "auto", height: "25px"}, + pdf: {width: "auto", height: "25px"}, + other: {width: "auto", height: "25px"} + }, + frameClass: 'explorer-frame' + }; +})(window.jQuery); diff --git a/static/plugins/bootstrap-fileinput/themes/explorer/theme.min.css b/static/plugins/bootstrap-fileinput/themes/explorer/theme.min.css new file mode 100644 index 00000000..8ff8b862 --- /dev/null +++ b/static/plugins/bootstrap-fileinput/themes/explorer/theme.min.css @@ -0,0 +1,12 @@ +/*! + * bootstrap-fileinput v4.5.1 + * http://plugins.krajee.com/file-input + * + * Krajee Explorer theme style for bootstrap-fileinput. Load this theme file after loading `fileinput.css`. + * + * Author: Kartik Visweswaran + * Copyright: 2014 - 2018, Kartik Visweswaran, Krajee.com + * + * Licensed under the BSD 3-Clause + * https://github.com/kartik-v/bootstrap-fileinput/blob/master/LICENSE.md + */.explorer-frame .file-preview-other,.theme-explorer .explorer-frame .kv-file-content,.theme-explorer .file-actions,.theme-explorer .file-drag-handle,.theme-explorer .file-upload-indicator{text-align:center}.theme-explorer .file-thumb-progress .progress,.theme-explorer .file-thumb-progress .progress-bar{height:13px;font-size:11px;line-height:13px}.theme-explorer .file-drag-handle,.theme-explorer .file-upload-indicator{position:absolute;display:inline-block;top:0;right:3px;width:16px;height:16px;font-size:16px}.theme-explorer .explorer-caption,.theme-explorer .file-thumb-progress .progress{display:block}.theme-explorer .explorer-frame td{vertical-align:middle;text-align:left}.theme-explorer .explorer-frame .kv-file-content{width:80px;height:80px;padding:5px}.theme-explorer .file-actions-cell{position:relative;width:120px;padding:0}.theme-explorer .file-thumb-progress .progress{margin-top:5px}.theme-explorer .explorer-caption{color:#777}.theme-explorer .kvsortable-ghost{opacity:.6;background:#e1edf7;border:2px solid #a1abff}.theme-explorer .file-preview .table{margin:0}.theme-explorer .file-error-message ul{padding:5px 0 0 20px}.explorer-frame .file-preview-text{display:inline-block;color:#428bca;border:1px solid #ddd;font-family:Menlo,Monaco,Consolas,"Courier New",monospace;outline:0;padding:8px;resize:none}.explorer-frame .file-preview-html{display:inline-block;border:1px solid #ddd;padding:8px;overflow:auto}.explorer-frame .file-other-icon{font-size:2.6em}@media only screen and (max-width:767px){.theme-explorer .table,.theme-explorer .table tbody,.theme-explorer .table td,.theme-explorer .table tr{display:block;width:100%!important}.theme-explorer .table{border:none}.theme-explorer .table tr{margin-top:5px}.theme-explorer .table tr:first-child{margin-top:0}.theme-explorer .table td{text-align:center}.theme-explorer .table .kv-file-content{border-bottom:none;padding:4px;margin:0}.theme-explorer .table .kv-file-content .file-preview-image{max-width:100%;font-size:20px}.theme-explorer .file-details-cell{border-top:none;border-bottom:none;padding-top:0;margin:0}.theme-explorer .file-actions-cell{border-top:none;padding-bottom:4px}.theme-explorer .explorer-frame .explorer-caption{white-space:nowrap;text-overflow:ellipsis;overflow:hidden;left:0;right:0;margin:auto}}.file-zoom-dialog .explorer-frame .file-other-icon{font-size:22em;font-size:50vmin} \ No newline at end of file diff --git a/static/plugins/bootstrap-fileinput/themes/explorer/theme.min.js b/static/plugins/bootstrap-fileinput/themes/explorer/theme.min.js new file mode 100644 index 00000000..34d6540a --- /dev/null +++ b/static/plugins/bootstrap-fileinput/themes/explorer/theme.min.js @@ -0,0 +1,12 @@ +/*! + * bootstrap-fileinput v4.5.1 + * http://plugins.krajee.com/file-input + * + * Krajee Explorer theme configuration for bootstrap-fileinput. Load this theme file after loading `fileinput.js`. + * + * Author: Kartik Visweswaran + * Copyright: 2014 - 2018, Kartik Visweswaran, Krajee.com + * + * Licensed under the BSD 3-Clause + * https://github.com/kartik-v/bootstrap-fileinput/blob/master/LICENSE.md + */!function(e){"use strict";var t='\n {close}
    \n \n
    \n
    \n
    \n
    \n',footer:'
    {caption}
    {size}{progress}{indicator} {actions}',actions:'{drag}\n
    \n \n
    ',zoomCache:'{zoomContent}
    '},previewMarkupTags:{tagBefore1:t+">"+i,tagBefore2:t+' title="{caption}">'+i,tagAfter:"\n{footer}\n"},previewSettings:{image:{height:"60px"},html:{width:"100px",height:"60px"},text:{width:"100px",height:"60px"},video:{width:"auto",height:"60px"},audio:{width:"auto",height:"60px"},flash:{width:"100%",height:"60px"},object:{width:"100%",height:"60px"},pdf:{width:"100px",height:"60px"},other:{width:"100%",height:"60px"}},frameClass:"explorer-frame"}}(window.jQuery); diff --git a/static/plugins/bootstrap-fileinput/themes/fa/theme.js b/static/plugins/bootstrap-fileinput/themes/fa/theme.js new file mode 100644 index 00000000..64610ac4 --- /dev/null +++ b/static/plugins/bootstrap-fileinput/themes/fa/theme.js @@ -0,0 +1,47 @@ +/*! + * bootstrap-fileinput v4.5.1 + * http://plugins.krajee.com/file-input + * + * Font Awesome icon theme configuration for bootstrap-fileinput. Requires font awesome assets to be loaded. + * + * Author: Kartik Visweswaran + * Copyright: 2014 - 2018, Kartik Visweswaran, Krajee.com + * + * Licensed under the BSD 3-Clause + * https://github.com/kartik-v/bootstrap-fileinput/blob/master/LICENSE.md + */ +(function ($) { + "use strict"; + + $.fn.fileinputThemes.fa = { + fileActionSettings: { + removeIcon: '', + uploadIcon: '', + uploadRetryIcon: '', + downloadIcon: '', + zoomIcon: '', + dragIcon: '', + indicatorNew: '', + indicatorSuccess: '', + indicatorError: '', + indicatorLoading: '' + }, + layoutTemplates: { + fileIcon: ' ' + }, + previewZoomButtonIcons: { + prev: '', + next: '', + toggleheader: '', + fullscreen: '', + borderless: '', + close: '' + }, + previewFileIcon: '', + browseIcon: '', + removeIcon: '', + cancelIcon: '', + uploadIcon: '', + msgValidationErrorIcon: ' ' + }; +})(window.jQuery); diff --git a/static/plugins/bootstrap-fileinput/themes/fa/theme.min.js b/static/plugins/bootstrap-fileinput/themes/fa/theme.min.js new file mode 100644 index 00000000..1c24c856 --- /dev/null +++ b/static/plugins/bootstrap-fileinput/themes/fa/theme.min.js @@ -0,0 +1,12 @@ +/*! + * bootstrap-fileinput v4.5.1 + * http://plugins.krajee.com/file-input + * + * Font Awesome icon theme configuration for bootstrap-fileinput. Requires font awesome assets to be loaded. + * + * Author: Kartik Visweswaran + * Copyright: 2014 - 2018, Kartik Visweswaran, Krajee.com + * + * Licensed under the BSD 3-Clause + * https://github.com/kartik-v/bootstrap-fileinput/blob/master/LICENSE.md + */!function(a){"use strict";a.fn.fileinputThemes.fa={fileActionSettings:{removeIcon:'',uploadIcon:'',uploadRetryIcon:'',downloadIcon:'',zoomIcon:'',dragIcon:'',indicatorNew:'',indicatorSuccess:'',indicatorError:'',indicatorLoading:''},layoutTemplates:{fileIcon:' '},previewZoomButtonIcons:{prev:'',next:'',toggleheader:'',fullscreen:'',borderless:'',close:''},previewFileIcon:'',browseIcon:'',removeIcon:'',cancelIcon:'',uploadIcon:'',msgValidationErrorIcon:' '}}(window.jQuery); \ No newline at end of file diff --git a/static/plugins/bootstrap-fileinput/themes/fas/theme.js b/static/plugins/bootstrap-fileinput/themes/fas/theme.js new file mode 100644 index 00000000..2ffa29a7 --- /dev/null +++ b/static/plugins/bootstrap-fileinput/themes/fas/theme.js @@ -0,0 +1,47 @@ +/*! + * bootstrap-fileinput v4.5.1 + * http://plugins.krajee.com/file-input + * + * Font Awesome 5 icon theme configuration for bootstrap-fileinput. Requires font awesome 5 assets to be loaded. + * + * Author: Kartik Visweswaran + * Copyright: 2014 - 2018, Kartik Visweswaran, Krajee.com + * + * Licensed under the BSD 3-Clause + * https://github.com/kartik-v/bootstrap-fileinput/blob/master/LICENSE.md + */ +(function ($) { + "use strict"; + + $.fn.fileinputThemes.fas = { + fileActionSettings: { + removeIcon: '', + uploadIcon: '', + uploadRetryIcon: '', + downloadIcon: '', + zoomIcon: '', + dragIcon: '', + indicatorNew: '', + indicatorSuccess: '', + indicatorError: '', + indicatorLoading: '' + }, + layoutTemplates: { + fileIcon: ' ' + }, + previewZoomButtonIcons: { + prev: '', + next: '', + toggleheader: '', + fullscreen: '', + borderless: '', + close: '' + }, + previewFileIcon: '', + browseIcon: '', + removeIcon: '', + cancelIcon: '', + uploadIcon: '', + msgValidationErrorIcon: ' ' + }; +})(window.jQuery); diff --git a/static/plugins/bootstrap-fileinput/themes/fas/theme.min.js b/static/plugins/bootstrap-fileinput/themes/fas/theme.min.js new file mode 100644 index 00000000..beb1b898 --- /dev/null +++ b/static/plugins/bootstrap-fileinput/themes/fas/theme.min.js @@ -0,0 +1,12 @@ +/*! + * bootstrap-fileinput v4.5.1 + * http://plugins.krajee.com/file-input + * + * Font Awesome 5 icon theme configuration for bootstrap-fileinput. Requires font awesome 5 assets to be loaded. + * + * Author: Kartik Visweswaran + * Copyright: 2014 - 2018, Kartik Visweswaran, Krajee.com + * + * Licensed under the BSD 3-Clause + * https://github.com/kartik-v/bootstrap-fileinput/blob/master/LICENSE.md + */!function(a){"use strict";a.fn.fileinputThemes.fas={fileActionSettings:{removeIcon:'',uploadIcon:'',uploadRetryIcon:'',downloadIcon:'',zoomIcon:'',dragIcon:'',indicatorNew:'',indicatorSuccess:'',indicatorError:'',indicatorLoading:''},layoutTemplates:{fileIcon:' '},previewZoomButtonIcons:{prev:'',next:'',toggleheader:'',fullscreen:'',borderless:'',close:''},previewFileIcon:'',browseIcon:'',removeIcon:'',cancelIcon:'',uploadIcon:'',msgValidationErrorIcon:' '}}(window.jQuery); diff --git a/static/plugins/bootstrap-fileinput/themes/gly/theme.js b/static/plugins/bootstrap-fileinput/themes/gly/theme.js new file mode 100644 index 00000000..7e18c88b --- /dev/null +++ b/static/plugins/bootstrap-fileinput/themes/gly/theme.js @@ -0,0 +1,45 @@ +/*! + * bootstrap-fileinput v4.5.1 + * http://plugins.krajee.com/file-input + * + * Glyphicon (default) theme configuration for bootstrap-fileinput. + * + * Author: Kartik Visweswaran + * Copyright: 2014 - 2018, Kartik Visweswaran, Krajee.com + * + * Licensed under the BSD 3-Clause + * https://github.com/kartik-v/bootstrap-fileinput/blob/master/LICENSE.md + */ +(function ($) { + "use strict"; + + $.fn.fileinputThemes.gly = { + fileActionSettings: { + removeIcon: '', + uploadIcon: '', + zoomIcon: '', + dragIcon: '', + indicatorNew: '', + indicatorSuccess: '', + indicatorError: '', + indicatorLoading: '' + }, + layoutTemplates: { + fileIcon: '' + }, + previewZoomButtonIcons: { + prev: '', + next: '', + toggleheader: '', + fullscreen: '', + borderless: '', + close: '' + }, + previewFileIcon: '', + browseIcon: ' ', + removeIcon: '', + cancelIcon: '', + uploadIcon: '', + msgValidationErrorIcon: ' ' + }; +})(window.jQuery); diff --git a/static/plugins/bootstrap-fileinput/themes/gly/theme.min.js b/static/plugins/bootstrap-fileinput/themes/gly/theme.min.js new file mode 100644 index 00000000..b04e7566 --- /dev/null +++ b/static/plugins/bootstrap-fileinput/themes/gly/theme.min.js @@ -0,0 +1,12 @@ +/*! + * bootstrap-fileinput v4.5.1 + * http://plugins.krajee.com/file-input + * + * Glyphicon (default) theme configuration for bootstrap-fileinput. + * + * Author: Kartik Visweswaran + * Copyright: 2014 - 2018, Kartik Visweswaran, Krajee.com + * + * Licensed under the BSD 3-Clause + * https://github.com/kartik-v/bootstrap-fileinput/blob/master/LICENSE.md + */!function(i){"use strict";i.fn.fileinputThemes.gly={fileActionSettings:{removeIcon:'',uploadIcon:'',zoomIcon:'',dragIcon:'',indicatorNew:'',indicatorSuccess:'',indicatorError:'',indicatorLoading:''},layoutTemplates:{fileIcon:''},previewZoomButtonIcons:{prev:'',next:'',toggleheader:'',fullscreen:'',borderless:'',close:''},previewFileIcon:'',browseIcon:' ',removeIcon:'',cancelIcon:'',uploadIcon:'',msgValidationErrorIcon:' '}}(window.jQuery); \ No newline at end of file diff --git a/static/plugins/webuploader/README.md b/static/plugins/webuploader/README.md deleted file mode 100644 index 93bceadc..00000000 --- a/static/plugins/webuploader/README.md +++ /dev/null @@ -1,25 +0,0 @@ -目录说明 -======================== - -```bash -├── Uploader.swf # SWF文件,当使用Flash运行时需要引入。 -├ -├── webuploader.js # 完全版本。 -├── webuploader.min.js # min版本 -├ -├── webuploader.flashonly.js # 只有Flash实现的版本。 -├── webuploader.flashonly.min.js # min版本 -├ -├── webuploader.html5only.js # 只有Html5实现的版本。 -├── webuploader.html5only.min.js # min版本 -├ -├── webuploader.noimage.js # 去除图片处理的版本,包括HTML5和FLASH. -├── webuploader.noimage.min.js # min版本 -├ -├── webuploader.custom.js # 自定义打包方案,请查看 Gruntfile.js,满足移动端使用。 -└── webuploader.custom.min.js # min版本 -``` - -## 示例 - -请把整个 Git 包下载下来放在 php 服务器下,因为默认提供的文件接受是用 php 编写的,打开 examples 页面便能查看示例效果。 \ No newline at end of file diff --git a/static/plugins/webuploader/Uploader.swf b/static/plugins/webuploader/Uploader.swf deleted file mode 100644 index bd75d6082fc0c3e977390a71dcca78932727147c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 143099 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,+E) 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 diff --git a/static/plugins/webuploader/bg.png b/static/plugins/webuploader/bg.png deleted file mode 100644 index 73d102db00caaa7c39241ad6bdf44f2f62deb4e4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2851 zcmaJ@dpMM78`svx*rZ4$VvKAeb1)1uF^VC!21S}^Y0SLC7;`W)j%ggCq?|T~l-1?i zT5>*qu|kR_rp(GB6hb7YaXyUU8*TM{-*tU`@Aba#^E~%;|9CT-hfG!1LBLe9IpnM{ z9*$>O0p3*G5H{cxV(&}}p^?yJhy@mG7KD)!&;c$H97OkJa43+iugMdG| zaB1d{zeN%7j$kV$8vq+akp?6<0s%&$pa>&l6bf+|YzRjf!r;;uWq>fk7^5(TCg9Hx zL>i4vreIFvY(B@5p3EWMTrLX(g9Qc#LIaUdCff^!K%>zc9EOGlQV#=8FoR1BGGK7D zzcAnc4v9@=aj8rOc!QDX$@J%%L!?UoPJzz)O3UDUE|auiuplA}hJeC1Qu^YE$Nzs< zI{m9RhkF|Mx8DCr%yAB80kG2mhw0BINgGGe-U!9QSg`>jm&tZ!GJU^P(b1d9Wpcck zEU=Xm3apDKlBkRg!=XmLdk5+#%N#|!{`*m=ohp(d#Fr@R&mJT*{FG?aK zBb#D}JLVkJJ^phmV-UXWgu?pch4s413FUysi9dmOouuT9$?!K%wQQaEc`CQ>kw+HR z)@tcJ0)arXo{*5hpO&mn1RTk%(+uPBYHMpXgid^q#rd9NEB)u4*uE8E<6%7k>ywh6 z=V9WaD33l5mRJmXLIS#hbOB!k^wT^)9f0wxUcr!09Vid|Gl@AfA;AaO*Tu-?uv5wB_d? ze_wc83@v7*Hy*D`0=ZCFQ_XScs>y`?2_m@JIcbUyR%l)uGF7P4)rzKmi zgE0HRc-@7l*)#m75MJoEG<$VK_J-Afj(V6@Z}uX)80PD>3wbI{F28)h`Dw$fab?HI ziOU}QR$uj}ZLKp}x?c}d;88{YZOSuR@_S=x4sY*|q{#N9jrj5P6Yqkq_5>W~36OY)|CtJu``tStB#IoQyj&KLzKr;{4(Z9Xz4)Z;X5f?!>1w>gUPz8&w|V{Sia4 z7Y2rA0Ov`A(5|bFPOw}PpXa7$-z6c1uG6)V4>3ghw;}y{n_Nnv2 zLG}8_#&zF?#H@^i=Bo|$wEmBBrnrdwil7l@@ReZStqq?4Mfjz(-g{4T{Z^qiB1 zz^-dlX?hlk7L4{qlcwtk_>b>rOkc6D?pi3Rb7EF*=Umh54ev>$% z_94$c2uP9eUmfjoCmVy55_~n*WsCCPOMEji9v58*4jFUluI#th8#TMCc)60U%Hqj0 z@6*Np!jXcWL;0v!JkDeD9+dHm+S89gR_ez@CP>^T({K@2?>vq^;&{#m!zWum84mGO!HVd83!PiwY zpAYzY(UdMZl8czj9i|7$THLMxw0kn(7B5d`I;6) zhmY{yi>B?RRhZO)rigLxWxf3>0fW+7yK3s^3$Vnvz10g>8do$gmMZXG$giW-9fqn8 zKYT0BcFBC(N^REOJ6tay-E3^o!xAcMbssmY+_y;1R9VF}Y(8FDoL|pdO0gSX@`#UK zRmod)!)9ku6=ky%X#`mF%*UPX5vdMMgeuQ9i2}V@=%Ig&|H9BG*dBIIRmY7tXFYiM z{M6V7ckToYzvZ~?~ z9dQN$txWTXP)ckZ@q@Sjmel5crc7bD;_0Jh`M2M7m^Cfu1#6hiy|}67Rz8-w=5*pJ4pG6HD8e*$A#CviD5Rs7^$Q_zfBU3TR8vs)&9)yhAY?2oDT2%*GNIAgZr z{e0~_ta?{xcJ6Ck^l>MUb%%w))1PQ@+NBFgjn%oBr0%M$AF!x@baizr=MHjT=hY@EY2&}t@Q z{ZnVnOz$hdeoP)y?GhIf>SC72hZNkGdQ0NYDf*SxrY5MZ^&FYM;dEp3;;!$u%;sO9*H6;am6P|>F0)n;;U$$_nB zN9y(a2IW=|yC}d+eB|P6a-ht@je|quCbehk#7CCZea9A2AAI}4Qy`UvYZNNw1F}>Y hD}v^2s$Q3a%g~P`+Sj%~6*jJ|cGmW|Ldy%0{{qKIBM1Nh diff --git a/static/plugins/webuploader/icons.png b/static/plugins/webuploader/icons.png deleted file mode 100644 index 12e4700163ac87fa38ae3d92a2c39d0fb4690fed..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2678 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 diff --git a/static/plugins/webuploader/progress.png b/static/plugins/webuploader/progress.png deleted file mode 100644 index 717c4865c90a959c6a0e9ad1af9c777d900a2e9c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1269 zcmeAS@N?(olHy`uVBq!ia0vp^f8U}fi7AzZCsS=07??9MLn2Bde0{8v^Kf6`()~Xj@TAnpKdC8`Lf!&sHg;q@=(~U%$M( zT(8_%FTW^V-_X+15@d#vkuFe$ZgFK^Nn(X=Ua>OF1ees}t-3#_`hBq$Z(46Le)Ln;eW^@CE2 z^Gl18f$@>14ATq@JNy=b6armi>cVAJd5X6R;MWawh(V&G(G=xXffXz1o@ zX=ZL{;B08&Z0-!x>zP+vl9-pA3bQv8XfIT+GhV$`&PAz-CHX}m`T04p6cCV+Uy@&( zkzb(T9Bihb;hUJ8nFkWk1Vs?Uzb>gonPsUdZbkXI3g8g7%EaOV0~10%hv-cqC)D(T zj?o7t52WM*69T3|5EGvGfgE`DNzDW1nId53*cQ_-&cMK^?CIhdQgN$ga=rIq1A(^5 zYKwg&`wo`WvF`u>{iFU|`!+wz>b#B?t8F4hxRm$~lz-tTH#6E8xZnKlv%`YRwvA{oKseqmK8(Gfx|> z#)dT+Zy!CGH{+89Q&m{rC!PyyIjq?Y9m+ziHPqr6qxfF`+2Qt=-KQ=fE8_j%1#Y2} z>NffN)P;AQIhrE)QQzeqbFS^A8(M1XGuQqTO<=fcH+M~2lzlL$Ao6teb6Mw<&;$U? C61Fb@ diff --git a/static/plugins/webuploader/success.png b/static/plugins/webuploader/success.png deleted file mode 100644 index 94f968dc8fd3c7ca8f6cb599d006ef3f23b62c7d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1621 zcmeAS@N?(olHy`uVBq!ia0vp^8X(NU1|)m_?Z^dEk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+m{l@EB1$5BeXNr6bM+EIYV;~{3xK*A7;Nk-3KEmEQ%e+* zQqwc@Y?a>c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxSU1_g&``n5OwZ87 z)XdCKN5ROz&`93^h|F{iO{`4Ktc=VRpg;*|TTx1yRgjAt)Gi>;Rw<*Tq`*pFzr4I$ zuiRKKzbIYb(9+TpWQLKEE>MMTab;dfVufyAu`f(~1RD^r68eAMwS&*t9 zlvO-#?>2=9ZF3nBND}m`vLFhHXsTY(OatnYqyQCInmZhe+73JqDfIV%MiQ6rPIL(9V zO~LIJBb<8mfsWA!MJ-ZP!-Rn82gHOYTp$OY^i%VI>AeV;u(vXbonl~MI_~M>7*cWT z%njcRVMh`65C6~Q2yI{K`NBQQh9heAinXE1(JfB=Ul%M;Ke#x6g%L8^f5l4Te zj&BcK1znDIyZS2~eC7F3K+}tVTa$=u*HTU0W4>0#`+Qf%|GkyBERBs#$Y}P@k7@U2 zd#*XsYr~WGeCA1uSK>2HTD)g9mP}Xrpg$+`K&8dx2JS~bjCx(hj5}1;-8qn2_>|?> zp{d+2q{I0hbQS!U-jOut`kSEi+IhQJGmBl4O%se*W?OGKZ0Mp?XmGnRgezL^8vCJ4 z#-ek9(R&%4!h+;oXA~Mtbyie=u#0PDiI#!iX`%2?^@kRL8ma*r6?+*}W4e|_8~J&& z+-+*Ub3{dDcAIF|!MTDz0#78qKYeaeVU$wDOOs3cR=e5FZ*O2e-6xc{Uu=$;!s>*M z>nZ|V>zUp8-oE6^wXjV+_UOvuE&SORr`;3hnBKu~e3rzlY@NRjD|5Ui2t>^@Ve7jy z)2vT>u1vesjJgG`f;l_n`FmbR~b^~{VzXB}7CdU_pt>lO9l=E;X#4<2bZu32r~J^e^%TSISQ_Qm$= z7d{rOo2%^RUGseJkpn9(MWszUQdl(i&RN5PBgZ0d#S7ZipShU3W`>Nsy6C#pCD*KW zyxHW%b?m|pr;dF;L$saa#oaeO`u=>jkXTjl9*cLEvOD!Jdr4ooKJW9gY7?D{e?uM^ z`_}E4`0yR0boA=9>3>WXzUH{&pDfpZ;_2xb=j*pKsk#WyD%J#5t{1#azeB z4SJq8H2*dJVX$xugZHVh{7U`u4@oM|quz5j-hRJi{qwh1k5uNqu`W>3HJG&Y^R}6R z$Gq-HPue$sd${oT*XKO8zN>Oe_%K^bZNm3^-vf7@5X#' ) - .appendTo( $wrap.find( '.queueList' ) ), - - // 状态栏,包括进度和控制按钮 - $statusBar = $wrap.find( '.statusBar' ), - - // 文件总体选择信息。 - $info = $statusBar.find( '.info' ), - - // 上传按钮 - $upload = $wrap.find( '.uploadBtn' ), - - // 没选择文件之前的内容。 - $placeHolder = $wrap.find( '.placeholder' ), - - $progress = $statusBar.find( '.progress' ).hide(), - - // 添加的文件数量 - fileCount = 0, - - // 添加的文件总大小 - fileSize = 0, - - // 优化retina, 在retina下这个值是2 - ratio = window.devicePixelRatio || 1, - - // 缩略图大小 - thumbnailWidth = 110 * ratio, - thumbnailHeight = 110 * ratio, - - // 可能有pedding, ready, uploading, confirm, done. - state = 'pedding', - - // 所有文件的进度信息,key为file id - percentages = {}, - // 判断浏览器是否支持图片的base64 - isSupportBase64 = ( function() { - var data = new Image(); - var support = true; - data.onload = data.onerror = function() { - if( this.width != 1 || this.height != 1 ) { - support = false; - } - } - data.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw=="; - return support; - } )(), - - // 检测是否已经安装flash,检测flash的版本 - flashVersion = ( function() { - var version; - - try { - version = navigator.plugins[ 'Shockwave Flash' ]; - version = version.description; - } catch ( ex ) { - try { - version = new ActiveXObject('ShockwaveFlash.ShockwaveFlash') - .GetVariable('$version'); - } catch ( ex2 ) { - version = '0.0'; - } - } - version = version.match( /\d+/g ); - return parseFloat( version[ 0 ] + '.' + version[ 1 ], 10 ); - } )(), - - supportTransition = (function(){ - var s = document.createElement('p').style, - r = 'transition' in s || - 'WebkitTransition' in s || - 'MozTransition' in s || - 'msTransition' in s || - 'OTransition' in s; - s = null; - return r; - })(), - - // WebUploader实例 - uploader; - - if ( !WebUploader.Uploader.support('flash') && WebUploader.browser.ie ) { - - // flash 安装了但是版本过低。 - if (flashVersion) { - (function(container) { - window['expressinstallcallback'] = function( state ) { - switch(state) { - case 'Download.Cancelled': - alert('您取消了更新!') - break; - - case 'Download.Failed': - alert('安装失败') - break; - - default: - alert('安装已成功,请刷新!'); - break; - } - delete window['expressinstallcallback']; - }; - - var swf = './expressInstall.swf'; - // insert flash object - var html = '' + - '' + - '' + - ''; - - container.html(html); - - })($wrap); - - // 压根就没有安转。 - } else { - $wrap.html('get flash player'); - } - - return; - } else if (!WebUploader.Uploader.support()) { - alert( 'Web Uploader 不支持您的浏览器!'); - return; - } - - // 实例化 - uploader = WebUploader.create({ - pick: { - id: '#filePicker', - label: '点击选择图片' - }, - formData: { - uid: 123 - }, - dnd: '#dndArea', - paste: '#uploader', - swf: '../../dist/Uploader.swf', - chunked: false, - chunkSize: 512 * 1024, - server: '../../server/fileupload.php', - // runtimeOrder: 'flash', - - // accept: { - // title: 'Images', - // extensions: 'gif,jpg,jpeg,bmp,png', - // mimeTypes: 'image/*' - // }, - - // 禁掉全局的拖拽功能。这样不会出现图片拖进页面的时候,把图片打开。 - disableGlobalDnd: true, - fileNumLimit: 300, - fileSizeLimit: 200 * 1024 * 1024, // 200 M - fileSingleSizeLimit: 50 * 1024 * 1024 // 50 M - }); - - // 拖拽时不接受 js, txt 文件。 - uploader.on( 'dndAccept', function( items ) { - var denied = false, - len = items.length, - i = 0, - // 修改js类型 - unAllowed = 'text/plain;application/javascript '; - - for ( ; i < len; i++ ) { - // 如果在列表里面 - if ( ~unAllowed.indexOf( items[ i ].type ) ) { - denied = true; - break; - } - } - - return !denied; - }); - - // uploader.on('filesQueued', function() { - // uploader.sort(function( a, b ) { - // if ( a.name < b.name ) - // return -1; - // if ( a.name > b.name ) - // return 1; - // return 0; - // }); - // }); - - // 添加“添加文件”的按钮, - uploader.addButton({ - id: '#filePicker2', - label: '继续添加' - }); - - uploader.on('ready', function() { - window.uploader = uploader; - }); - - // 当有文件添加进来时执行,负责view的创建 - function addFile( file ) { - var $li = $( '
  • ' + - '

    ' + file.name + '

    ' + - '

    '+ - '

    ' + - '
  • ' ), - - $btns = $('
    ' + - '删除' + - '向右旋转' + - '向左旋转
    ').appendTo( $li ), - $prgress = $li.find('p.progress span'), - $wrap = $li.find( 'p.imgWrap' ), - $info = $('

    '), - - showError = function( code ) { - switch( code ) { - case 'exceed_size': - text = '文件大小超出'; - break; - - case 'interrupt': - text = '上传暂停'; - break; - - default: - text = '上传失败,请重试'; - break; - } - - $info.text( text ).appendTo( $li ); - }; - - if ( file.getStatus() === 'invalid' ) { - showError( file.statusText ); - } else { - // @todo lazyload - $wrap.text( '预览中' ); - uploader.makeThumb( file, function( error, src ) { - var img; - - if ( error ) { - $wrap.text( '不能预览' ); - return; - } - - if( isSupportBase64 ) { - img = $(''); - $wrap.empty().append( img ); - } else { - $.ajax('../../server/preview.php', { - method: 'POST', - data: src, - dataType:'json' - }).done(function( response ) { - if (response.result) { - img = $(''); - $wrap.empty().append( img ); - } else { - $wrap.text("预览出错"); - } - }); - } - }, thumbnailWidth, thumbnailHeight ); - - percentages[ file.id ] = [ file.size, 0 ]; - file.rotation = 0; - } - - file.on('statuschange', function( cur, prev ) { - if ( prev === 'progress' ) { - $prgress.hide().width(0); - } else if ( prev === 'queued' ) { - $li.off( 'mouseenter mouseleave' ); - $btns.remove(); - } - - // 成功 - if ( cur === 'error' || cur === 'invalid' ) { - console.log( file.statusText ); - showError( file.statusText ); - percentages[ file.id ][ 1 ] = 1; - } else if ( cur === 'interrupt' ) { - showError( 'interrupt' ); - } else if ( cur === 'queued' ) { - percentages[ file.id ][ 1 ] = 0; - } else if ( cur === 'progress' ) { - $info.remove(); - $prgress.css('display', 'block'); - } else if ( cur === 'complete' ) { - $li.append( '' ); - } - - $li.removeClass( 'state-' + prev ).addClass( 'state-' + cur ); - }); - - $li.on( 'mouseenter', function() { - $btns.stop().animate({height: 30}); - }); - - $li.on( 'mouseleave', function() { - $btns.stop().animate({height: 0}); - }); - - $btns.on( 'click', 'span', function() { - var index = $(this).index(), - deg; - - switch ( index ) { - case 0: - uploader.removeFile( file ); - return; - - case 1: - file.rotation += 90; - break; - - case 2: - file.rotation -= 90; - break; - } - - if ( supportTransition ) { - deg = 'rotate(' + file.rotation + 'deg)'; - $wrap.css({ - '-webkit-transform': deg, - '-mos-transform': deg, - '-o-transform': deg, - 'transform': deg - }); - } else { - $wrap.css( 'filter', 'progid:DXImageTransform.Microsoft.BasicImage(rotation='+ (~~((file.rotation/90)%4 + 4)%4) +')'); - // use jquery animate to rotation - // $({ - // rotation: rotation - // }).animate({ - // rotation: file.rotation - // }, { - // easing: 'linear', - // step: function( now ) { - // now = now * Math.PI / 180; - - // var cos = Math.cos( now ), - // sin = Math.sin( now ); - - // $wrap.css( 'filter', "progid:DXImageTransform.Microsoft.Matrix(M11=" + cos + ",M12=" + (-sin) + ",M21=" + sin + ",M22=" + cos + ",SizingMethod='auto expand')"); - // } - // }); - } - - - }); - - $li.appendTo( $queue ); - } - - // 负责view的销毁 - function removeFile( file ) { - var $li = $('#'+file.id); - - delete percentages[ file.id ]; - updateTotalProgress(); - $li.off().find('.file-panel').off().end().remove(); - } - - function updateTotalProgress() { - var loaded = 0, - total = 0, - spans = $progress.children(), - percent; - - $.each( percentages, function( k, v ) { - total += v[ 0 ]; - loaded += v[ 0 ] * v[ 1 ]; - } ); - - percent = total ? loaded / total : 0; - - - spans.eq( 0 ).text( Math.round( percent * 100 ) + '%' ); - spans.eq( 1 ).css( 'width', Math.round( percent * 100 ) + '%' ); - updateStatus(); - } - - function updateStatus() { - var text = '', stats; - - if ( state === 'ready' ) { - text = '选中' + fileCount + '张图片,共' + - WebUploader.formatSize( fileSize ) + '。'; - } else if ( state === 'confirm' ) { - stats = uploader.getStats(); - if ( stats.uploadFailNum ) { - text = '已成功上传' + stats.successNum+ '张照片至XX相册,'+ - stats.uploadFailNum + '张照片上传失败,重新上传失败图片或忽略' - } - - } else { - stats = uploader.getStats(); - text = '共' + fileCount + '张(' + - WebUploader.formatSize( fileSize ) + - '),已上传' + stats.successNum + '张'; - - if ( stats.uploadFailNum ) { - text += ',失败' + stats.uploadFailNum + '张'; - } - } - - $info.html( text ); - } - - function setState( val ) { - var file, stats; - - if ( val === state ) { - return; - } - - $upload.removeClass( 'state-' + state ); - $upload.addClass( 'state-' + val ); - state = val; - - switch ( state ) { - case 'pedding': - $placeHolder.removeClass( 'element-invisible' ); - $queue.hide(); - $statusBar.addClass( 'element-invisible' ); - uploader.refresh(); - break; - - case 'ready': - $placeHolder.addClass( 'element-invisible' ); - $( '#filePicker2' ).removeClass( 'element-invisible'); - $queue.show(); - $statusBar.removeClass('element-invisible'); - uploader.refresh(); - break; - - case 'uploading': - $( '#filePicker2' ).addClass( 'element-invisible' ); - $progress.show(); - $upload.text( '暂停上传' ); - break; - - case 'paused': - $progress.show(); - $upload.text( '继续上传' ); - break; - - case 'confirm': - $progress.hide(); - $( '#filePicker2' ).removeClass( 'element-invisible' ); - $upload.text( '开始上传' ); - - stats = uploader.getStats(); - if ( stats.successNum && !stats.uploadFailNum ) { - setState( 'finish' ); - return; - } - break; - case 'finish': - stats = uploader.getStats(); - if ( stats.successNum ) { - alert( '上传成功' ); - } else { - // 没有成功的图片,重设 - state = 'done'; - location.reload(); - } - break; - } - - updateStatus(); - } - - uploader.onUploadProgress = function( file, percentage ) { - var $li = $('#'+file.id), - $percent = $li.find('.progress span'); - - $percent.css( 'width', percentage * 100 + '%' ); - percentages[ file.id ][ 1 ] = percentage; - updateTotalProgress(); - }; - - uploader.onFileQueued = function( file ) { - fileCount++; - fileSize += file.size; - - if ( fileCount === 1 ) { - $placeHolder.addClass( 'element-invisible' ); - $statusBar.show(); - } - - addFile( file ); - setState( 'ready' ); - updateTotalProgress(); - }; - - uploader.onFileDequeued = function( file ) { - fileCount--; - fileSize -= file.size; - - if ( !fileCount ) { - setState( 'pedding' ); - } - - removeFile( file ); - updateTotalProgress(); - - }; - - uploader.on( 'all', function( type ) { - var stats; - switch( type ) { - case 'uploadFinished': - setState( 'confirm' ); - break; - - case 'startUpload': - setState( 'uploading' ); - break; - - case 'stopUpload': - setState( 'paused' ); - break; - - } - }); - - uploader.onError = function( code ) { - alert( 'Eroor: ' + code ); - }; - - $upload.on('click', function() { - if ( $(this).hasClass( 'disabled' ) ) { - return false; - } - - if ( state === 'ready' ) { - uploader.upload(); - } else if ( state === 'paused' ) { - uploader.upload(); - } else if ( state === 'uploading' ) { - uploader.stop(); - } - }); - - $info.on( 'click', '.retry', function() { - uploader.retry(); - } ); - - $info.on( 'click', '.ignore', function() { - alert( 'todo' ); - } ); - - $upload.addClass( 'state-' + state ); - updateTotalProgress(); - }); - -})( jQuery ); \ No newline at end of file diff --git a/static/plugins/webuploader/upload.style.css b/static/plugins/webuploader/upload.style.css deleted file mode 100644 index 1c56dbc5..00000000 --- a/static/plugins/webuploader/upload.style.css +++ /dev/null @@ -1,14 +0,0 @@ -#uploader .queueList { - margin: 6px; -} - -#uploader .placeholder { - border: 3px dashed #e6e6e6; - min-height: 38px; - padding-top: 15px; - padding-bottom: 15px; - text-align: center; - color: #cccccc; - font-size: 18px; - position: relative; -} \ No newline at end of file diff --git a/static/plugins/webuploader/webuploader.css b/static/plugins/webuploader/webuploader.css deleted file mode 100644 index 12f451f8..00000000 --- a/static/plugins/webuploader/webuploader.css +++ /dev/null @@ -1,28 +0,0 @@ -.webuploader-container { - position: relative; -} -.webuploader-element-invisible { - position: absolute !important; - clip: rect(1px 1px 1px 1px); /* IE6, IE7 */ - clip: rect(1px,1px,1px,1px); -} -.webuploader-pick { - position: relative; - display: inline-block; - cursor: pointer; - background: #00b7ee; - padding: 10px 15px; - color: #fff; - text-align: center; - border-radius: 3px; - overflow: hidden; -} -.webuploader-pick-hover { - background: #00a2d4; -} - -.webuploader-pick-disable { - opacity: 0.6; - pointer-events:none; -} - diff --git a/static/plugins/webuploader/webuploader.custom.js b/static/plugins/webuploader/webuploader.custom.js deleted file mode 100644 index 2e242c6e..00000000 --- a/static/plugins/webuploader/webuploader.custom.js +++ /dev/null @@ -1,6502 +0,0 @@ -/*! WebUploader 0.1.5 */ - - -/** - * @fileOverview 让内部各个部件的代码可以用[amd](https://github.com/amdjs/amdjs-api/wiki/AMD)模块定义方式组织起来。 - * - * AMD API 内部的简单不完全实现,请忽略。只有当WebUploader被合并成一个文件的时候才会引入。 - */ -(function( root, factory ) { - var modules = {}, - - // 内部require, 简单不完全实现。 - // https://github.com/amdjs/amdjs-api/wiki/require - _require = function( deps, callback ) { - var args, len, i; - - // 如果deps不是数组,则直接返回指定module - if ( typeof deps === 'string' ) { - return getModule( deps ); - } else { - args = []; - for( len = deps.length, i = 0; i < len; i++ ) { - args.push( getModule( deps[ i ] ) ); - } - - return callback.apply( null, args ); - } - }, - - // 内部define,暂时不支持不指定id. - _define = function( id, deps, factory ) { - if ( arguments.length === 2 ) { - factory = deps; - deps = null; - } - - _require( deps || [], function() { - setModule( id, factory, arguments ); - }); - }, - - // 设置module, 兼容CommonJs写法。 - setModule = function( id, factory, args ) { - var module = { - exports: factory - }, - returned; - - if ( typeof factory === 'function' ) { - args.length || (args = [ _require, module.exports, module ]); - returned = factory.apply( null, args ); - returned !== undefined && (module.exports = returned); - } - - modules[ id ] = module.exports; - }, - - // 根据id获取module - getModule = function( id ) { - var module = modules[ id ] || root[ id ]; - - if ( !module ) { - throw new Error( '`' + id + '` is undefined' ); - } - - return module; - }, - - // 将所有modules,将路径ids装换成对象。 - exportsTo = function( obj ) { - var key, host, parts, part, last, ucFirst; - - // make the first character upper case. - ucFirst = function( str ) { - return str && (str.charAt( 0 ).toUpperCase() + str.substr( 1 )); - }; - - for ( key in modules ) { - host = obj; - - if ( !modules.hasOwnProperty( key ) ) { - continue; - } - - parts = key.split('/'); - last = ucFirst( parts.pop() ); - - while( (part = ucFirst( parts.shift() )) ) { - host[ part ] = host[ part ] || {}; - host = host[ part ]; - } - - host[ last ] = modules[ key ]; - } - - return obj; - }, - - makeExport = function( dollar ) { - root.__dollar = dollar; - - // exports every module. - return exportsTo( factory( root, _define, _require ) ); - }, - - origin; - - if ( typeof module === 'object' && typeof module.exports === 'object' ) { - - // For CommonJS and CommonJS-like environments where a proper window is present, - module.exports = makeExport(); - } else if ( typeof define === 'function' && define.amd ) { - - // Allow using this built library as an AMD module - // in another project. That other project will only - // see this AMD call, not the internal modules in - // the closure below. - define([ 'jquery' ], makeExport ); - } else { - - // Browser globals case. Just assign the - // result to a property on the global. - origin = root.WebUploader; - root.WebUploader = makeExport(); - root.WebUploader.noConflict = function() { - root.WebUploader = origin; - }; - } -})( window, function( window, define, require ) { - - - /** - * @fileOverview jQuery or Zepto - */ - define('dollar-third',[],function() { - var $ = window.__dollar || window.jQuery || window.Zepto; - - if ( !$ ) { - throw new Error('jQuery or Zepto not found!'); - } - - return $; - }); - /** - * @fileOverview Dom 操作相关 - */ - define('dollar',[ - 'dollar-third' - ], function( _ ) { - return _; - }); - /** - * 直接来源于jquery的代码。 - * @fileOverview Promise/A+ - * @beta - */ - define('promise-builtin',[ - 'dollar' - ], function( $ ) { - - var api; - - // 简单版Callbacks, 默认memory,可选once. - function Callbacks( once ) { - var list = [], - stack = !once && [], - fire = function( data ) { - memory = data; - fired = true; - firingIndex = firingStart || 0; - firingStart = 0; - firingLength = list.length; - firing = true; - - for ( ; list && firingIndex < firingLength; firingIndex++ ) { - list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ); - } - firing = false; - - if ( list ) { - if ( stack ) { - stack.length && fire( stack.shift() ); - } else { - list = []; - } - } - }, - self = { - add: function() { - if ( list ) { - var start = list.length; - (function add ( args ) { - $.each( args, function( _, arg ) { - var type = $.type( arg ); - if ( type === 'function' ) { - list.push( arg ); - } else if ( arg && arg.length && - type !== 'string' ) { - - add( arg ); - } - }); - })( arguments ); - - if ( firing ) { - firingLength = list.length; - } else if ( memory ) { - firingStart = start; - fire( memory ); - } - } - return this; - }, - - disable: function() { - list = stack = memory = undefined; - return this; - }, - - // Lock the list in its current state - lock: function() { - stack = undefined; - if ( !memory ) { - self.disable(); - } - return this; - }, - - fireWith: function( context, args ) { - if ( list && (!fired || stack) ) { - args = args || []; - args = [ context, args.slice ? args.slice() : args ]; - if ( firing ) { - stack.push( args ); - } else { - fire( args ); - } - } - return this; - }, - - fire: function() { - self.fireWith( this, arguments ); - return this; - } - }, - - fired, firing, firingStart, firingLength, firingIndex, memory; - - return self; - } - - function Deferred( func ) { - var tuples = [ - // action, add listener, listener list, final state - [ 'resolve', 'done', Callbacks( true ), 'resolved' ], - [ 'reject', 'fail', Callbacks( true ), 'rejected' ], - [ 'notify', 'progress', Callbacks() ] - ], - state = 'pending', - promise = { - state: function() { - return state; - }, - always: function() { - deferred.done( arguments ).fail( arguments ); - return this; - }, - then: function( /* fnDone, fnFail, fnProgress */ ) { - var fns = arguments; - return Deferred(function( newDefer ) { - $.each( tuples, function( i, tuple ) { - var action = tuple[ 0 ], - fn = $.isFunction( fns[ i ] ) && fns[ i ]; - - // deferred[ done | fail | progress ] for - // forwarding actions to newDefer - deferred[ tuple[ 1 ] ](function() { - var returned; - - returned = fn && fn.apply( this, arguments ); - - if ( returned && - $.isFunction( returned.promise ) ) { - - returned.promise() - .done( newDefer.resolve ) - .fail( newDefer.reject ) - .progress( newDefer.notify ); - } else { - newDefer[ action + 'With' ]( - this === promise ? - newDefer.promise() : - this, - fn ? [ returned ] : arguments ); - } - }); - }); - fns = null; - }).promise(); - }, - - // Get a promise for this deferred - // If obj is provided, the promise aspect is added to the object - promise: function( obj ) { - - return obj != null ? $.extend( obj, promise ) : promise; - } - }, - deferred = {}; - - // Keep pipe for back-compat - promise.pipe = promise.then; - - // Add list-specific methods - $.each( tuples, function( i, tuple ) { - var list = tuple[ 2 ], - stateString = tuple[ 3 ]; - - // promise[ done | fail | progress ] = list.add - promise[ tuple[ 1 ] ] = list.add; - - // Handle state - if ( stateString ) { - list.add(function() { - // state = [ resolved | rejected ] - state = stateString; - - // [ reject_list | resolve_list ].disable; progress_list.lock - }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock ); - } - - // deferred[ resolve | reject | notify ] - deferred[ tuple[ 0 ] ] = function() { - deferred[ tuple[ 0 ] + 'With' ]( this === deferred ? promise : - this, arguments ); - return this; - }; - deferred[ tuple[ 0 ] + 'With' ] = list.fireWith; - }); - - // Make the deferred a promise - promise.promise( deferred ); - - // Call given func if any - if ( func ) { - func.call( deferred, deferred ); - } - - // All done! - return deferred; - } - - api = { - /** - * 创建一个[Deferred](http://api.jquery.com/category/deferred-object/)对象。 - * 详细的Deferred用法说明,请参照jQuery的API文档。 - * - * Deferred对象在钩子回掉函数中经常要用到,用来处理需要等待的异步操作。 - * - * @for Base - * @method Deferred - * @grammar Base.Deferred() => Deferred - * @example - * // 在文件开始发送前做些异步操作。 - * // WebUploader会等待此异步操作完成后,开始发送文件。 - * Uploader.register({ - * 'before-send-file': 'doSomthingAsync' - * }, { - * - * doSomthingAsync: function() { - * var deferred = Base.Deferred(); - * - * // 模拟一次异步操作。 - * setTimeout(deferred.resolve, 2000); - * - * return deferred.promise(); - * } - * }); - */ - Deferred: Deferred, - - /** - * 判断传入的参数是否为一个promise对象。 - * @method isPromise - * @grammar Base.isPromise( anything ) => Boolean - * @param {*} anything 检测对象。 - * @return {Boolean} - * @for Base - * @example - * console.log( Base.isPromise() ); // => false - * console.log( Base.isPromise({ key: '123' }) ); // => false - * console.log( Base.isPromise( Base.Deferred().promise() ) ); // => true - * - * // Deferred也是一个Promise - * console.log( Base.isPromise( Base.Deferred() ) ); // => true - */ - isPromise: function( anything ) { - return anything && typeof anything.then === 'function'; - }, - - /** - * 返回一个promise,此promise在所有传入的promise都完成了后完成。 - * 详细请查看[这里](http://api.jquery.com/jQuery.when/)。 - * - * @method when - * @for Base - * @grammar Base.when( promise1[, promise2[, promise3...]] ) => Promise - */ - when: function( subordinate /* , ..., subordinateN */ ) { - var i = 0, - slice = [].slice, - resolveValues = slice.call( arguments ), - length = resolveValues.length, - - // the count of uncompleted subordinates - remaining = length !== 1 || (subordinate && - $.isFunction( subordinate.promise )) ? length : 0, - - // the master Deferred. If resolveValues consist of - // only a single Deferred, just use that. - deferred = remaining === 1 ? subordinate : Deferred(), - - // Update function for both resolve and progress values - updateFunc = function( i, contexts, values ) { - return function( value ) { - contexts[ i ] = this; - values[ i ] = arguments.length > 1 ? - slice.call( arguments ) : value; - - if ( values === progressValues ) { - deferred.notifyWith( contexts, values ); - } else if ( !(--remaining) ) { - deferred.resolveWith( contexts, values ); - } - }; - }, - - progressValues, progressContexts, resolveContexts; - - // add listeners to Deferred subordinates; treat others as resolved - if ( length > 1 ) { - progressValues = new Array( length ); - progressContexts = new Array( length ); - resolveContexts = new Array( length ); - for ( ; i < length; i++ ) { - if ( resolveValues[ i ] && - $.isFunction( resolveValues[ i ].promise ) ) { - - resolveValues[ i ].promise() - .done( updateFunc( i, resolveContexts, - resolveValues ) ) - .fail( deferred.reject ) - .progress( updateFunc( i, progressContexts, - progressValues ) ); - } else { - --remaining; - } - } - } - - // if we're not waiting on anything, resolve the master - if ( !remaining ) { - deferred.resolveWith( resolveContexts, resolveValues ); - } - - return deferred.promise(); - } - }; - - return api; - }); - define('promise',[ - 'promise-builtin' - ], function( $ ) { - return $; - }); - /** - * @fileOverview 基础类方法。 - */ - - /** - * Web Uploader内部类的详细说明,以下提及的功能类,都可以在`WebUploader`这个变量中访问到。 - * - * As you know, Web Uploader的每个文件都是用过[AMD](https://github.com/amdjs/amdjs-api/wiki/AMD)规范中的`define`组织起来的, 每个Module都会有个module id. - * 默认module id为该文件的路径,而此路径将会转化成名字空间存放在WebUploader中。如: - * - * * module `base`:WebUploader.Base - * * module `file`: WebUploader.File - * * module `lib/dnd`: WebUploader.Lib.Dnd - * * module `runtime/html5/dnd`: WebUploader.Runtime.Html5.Dnd - * - * - * 以下文档中对类的使用可能省略掉了`WebUploader`前缀。 - * @module WebUploader - * @title WebUploader API文档 - */ - define('base',[ - 'dollar', - 'promise' - ], function( $, promise ) { - - var noop = function() {}, - call = Function.call; - - // http://jsperf.com/uncurrythis - // 反科里化 - function uncurryThis( fn ) { - return function() { - return call.apply( fn, arguments ); - }; - } - - function bindFn( fn, context ) { - return function() { - return fn.apply( context, arguments ); - }; - } - - function createObject( proto ) { - var f; - - if ( Object.create ) { - return Object.create( proto ); - } else { - f = function() {}; - f.prototype = proto; - return new f(); - } - } - - - /** - * 基础类,提供一些简单常用的方法。 - * @class Base - */ - return { - - /** - * @property {String} version 当前版本号。 - */ - version: '0.1.5', - - /** - * @property {jQuery|Zepto} $ 引用依赖的jQuery或者Zepto对象。 - */ - $: $, - - Deferred: promise.Deferred, - - isPromise: promise.isPromise, - - when: promise.when, - - /** - * @description 简单的浏览器检查结果。 - * - * * `webkit` webkit版本号,如果浏览器为非webkit内核,此属性为`undefined`。 - * * `chrome` chrome浏览器版本号,如果浏览器为chrome,此属性为`undefined`。 - * * `ie` ie浏览器版本号,如果浏览器为非ie,此属性为`undefined`。**暂不支持ie10+** - * * `firefox` firefox浏览器版本号,如果浏览器为非firefox,此属性为`undefined`。 - * * `safari` safari浏览器版本号,如果浏览器为非safari,此属性为`undefined`。 - * * `opera` opera浏览器版本号,如果浏览器为非opera,此属性为`undefined`。 - * - * @property {Object} [browser] - */ - browser: (function( ua ) { - var ret = {}, - webkit = ua.match( /WebKit\/([\d.]+)/ ), - chrome = ua.match( /Chrome\/([\d.]+)/ ) || - ua.match( /CriOS\/([\d.]+)/ ), - - ie = ua.match( /MSIE\s([\d\.]+)/ ) || - ua.match( /(?:trident)(?:.*rv:([\w.]+))?/i ), - firefox = ua.match( /Firefox\/([\d.]+)/ ), - safari = ua.match( /Safari\/([\d.]+)/ ), - opera = ua.match( /OPR\/([\d.]+)/ ); - - webkit && (ret.webkit = parseFloat( webkit[ 1 ] )); - chrome && (ret.chrome = parseFloat( chrome[ 1 ] )); - ie && (ret.ie = parseFloat( ie[ 1 ] )); - firefox && (ret.firefox = parseFloat( firefox[ 1 ] )); - safari && (ret.safari = parseFloat( safari[ 1 ] )); - opera && (ret.opera = parseFloat( opera[ 1 ] )); - - return ret; - })( navigator.userAgent ), - - /** - * @description 操作系统检查结果。 - * - * * `android` 如果在android浏览器环境下,此值为对应的android版本号,否则为`undefined`。 - * * `ios` 如果在ios浏览器环境下,此值为对应的ios版本号,否则为`undefined`。 - * @property {Object} [os] - */ - os: (function( ua ) { - var ret = {}, - - // osx = !!ua.match( /\(Macintosh\; Intel / ), - android = ua.match( /(?:Android);?[\s\/]+([\d.]+)?/ ), - ios = ua.match( /(?:iPad|iPod|iPhone).*OS\s([\d_]+)/ ); - - // osx && (ret.osx = true); - android && (ret.android = parseFloat( android[ 1 ] )); - ios && (ret.ios = parseFloat( ios[ 1 ].replace( /_/g, '.' ) )); - - return ret; - })( navigator.userAgent ), - - /** - * 实现类与类之间的继承。 - * @method inherits - * @grammar Base.inherits( super ) => child - * @grammar Base.inherits( super, protos ) => child - * @grammar Base.inherits( super, protos, statics ) => child - * @param {Class} super 父类 - * @param {Object | Function} [protos] 子类或者对象。如果对象中包含constructor,子类将是用此属性值。 - * @param {Function} [protos.constructor] 子类构造器,不指定的话将创建个临时的直接执行父类构造器的方法。 - * @param {Object} [statics] 静态属性或方法。 - * @return {Class} 返回子类。 - * @example - * function Person() { - * console.log( 'Super' ); - * } - * Person.prototype.hello = function() { - * console.log( 'hello' ); - * }; - * - * var Manager = Base.inherits( Person, { - * world: function() { - * console.log( 'World' ); - * } - * }); - * - * // 因为没有指定构造器,父类的构造器将会执行。 - * var instance = new Manager(); // => Super - * - * // 继承子父类的方法 - * instance.hello(); // => hello - * instance.world(); // => World - * - * // 子类的__super__属性指向父类 - * console.log( Manager.__super__ === Person ); // => true - */ - inherits: function( Super, protos, staticProtos ) { - var child; - - if ( typeof protos === 'function' ) { - child = protos; - protos = null; - } else if ( protos && protos.hasOwnProperty('constructor') ) { - child = protos.constructor; - } else { - child = function() { - return Super.apply( this, arguments ); - }; - } - - // 复制静态方法 - $.extend( true, child, Super, staticProtos || {} ); - - /* jshint camelcase: false */ - - // 让子类的__super__属性指向父类。 - child.__super__ = Super.prototype; - - // 构建原型,添加原型方法或属性。 - // 暂时用Object.create实现。 - child.prototype = createObject( Super.prototype ); - protos && $.extend( true, child.prototype, protos ); - - return child; - }, - - /** - * 一个不做任何事情的方法。可以用来赋值给默认的callback. - * @method noop - */ - noop: noop, - - /** - * 返回一个新的方法,此方法将已指定的`context`来执行。 - * @grammar Base.bindFn( fn, context ) => Function - * @method bindFn - * @example - * var doSomething = function() { - * console.log( this.name ); - * }, - * obj = { - * name: 'Object Name' - * }, - * aliasFn = Base.bind( doSomething, obj ); - * - * aliasFn(); // => Object Name - * - */ - bindFn: bindFn, - - /** - * 引用Console.log如果存在的话,否则引用一个[空函数noop](#WebUploader:Base.noop)。 - * @grammar Base.log( args... ) => undefined - * @method log - */ - log: (function() { - if ( window.console ) { - return bindFn( console.log, console ); - } - return noop; - })(), - - nextTick: (function() { - - return function( cb ) { - setTimeout( cb, 1 ); - }; - - // @bug 当浏览器不在当前窗口时就停了。 - // var next = window.requestAnimationFrame || - // window.webkitRequestAnimationFrame || - // window.mozRequestAnimationFrame || - // function( cb ) { - // window.setTimeout( cb, 1000 / 60 ); - // }; - - // // fix: Uncaught TypeError: Illegal invocation - // return bindFn( next, window ); - })(), - - /** - * 被[uncurrythis](http://www.2ality.com/2011/11/uncurrying-this.html)的数组slice方法。 - * 将用来将非数组对象转化成数组对象。 - * @grammar Base.slice( target, start[, end] ) => Array - * @method slice - * @example - * function doSomthing() { - * var args = Base.slice( arguments, 1 ); - * console.log( args ); - * } - * - * doSomthing( 'ignored', 'arg2', 'arg3' ); // => Array ["arg2", "arg3"] - */ - slice: uncurryThis( [].slice ), - - /** - * 生成唯一的ID - * @method guid - * @grammar Base.guid() => String - * @grammar Base.guid( prefx ) => String - */ - guid: (function() { - var counter = 0; - - return function( prefix ) { - var guid = (+new Date()).toString( 32 ), - i = 0; - - for ( ; i < 5; i++ ) { - guid += Math.floor( Math.random() * 65535 ).toString( 32 ); - } - - return (prefix || 'wu_') + guid + (counter++).toString( 32 ); - }; - })(), - - /** - * 格式化文件大小, 输出成带单位的字符串 - * @method formatSize - * @grammar Base.formatSize( size ) => String - * @grammar Base.formatSize( size, pointLength ) => String - * @grammar Base.formatSize( size, pointLength, units ) => String - * @param {Number} size 文件大小 - * @param {Number} [pointLength=2] 精确到的小数点数。 - * @param {Array} [units=[ 'B', 'K', 'M', 'G', 'TB' ]] 单位数组。从字节,到千字节,一直往上指定。如果单位数组里面只指定了到了K(千字节),同时文件大小大于M, 此方法的输出将还是显示成多少K. - * @example - * console.log( Base.formatSize( 100 ) ); // => 100B - * console.log( Base.formatSize( 1024 ) ); // => 1.00K - * console.log( Base.formatSize( 1024, 0 ) ); // => 1K - * console.log( Base.formatSize( 1024 * 1024 ) ); // => 1.00M - * console.log( Base.formatSize( 1024 * 1024 * 1024 ) ); // => 1.00G - * console.log( Base.formatSize( 1024 * 1024 * 1024, 0, ['B', 'KB', 'MB'] ) ); // => 1024MB - */ - formatSize: function( size, pointLength, units ) { - var unit; - - units = units || [ 'B', 'K', 'M', 'G', 'TB' ]; - - while ( (unit = units.shift()) && size > 1024 ) { - size = size / 1024; - } - - return (unit === 'B' ? size : size.toFixed( pointLength || 2 )) + - unit; - } - }; - }); - /** - * 事件处理类,可以独立使用,也可以扩展给对象使用。 - * @fileOverview Mediator - */ - define('mediator',[ - 'base' - ], function( Base ) { - var $ = Base.$, - slice = [].slice, - separator = /\s+/, - protos; - - // 根据条件过滤出事件handlers. - function findHandlers( arr, name, callback, context ) { - return $.grep( arr, function( handler ) { - return handler && - (!name || handler.e === name) && - (!callback || handler.cb === callback || - handler.cb._cb === callback) && - (!context || handler.ctx === context); - }); - } - - function eachEvent( events, callback, iterator ) { - // 不支持对象,只支持多个event用空格隔开 - $.each( (events || '').split( separator ), function( _, key ) { - iterator( key, callback ); - }); - } - - function triggerHanders( events, args ) { - var stoped = false, - i = -1, - len = events.length, - handler; - - while ( ++i < len ) { - handler = events[ i ]; - - if ( handler.cb.apply( handler.ctx2, args ) === false ) { - stoped = true; - break; - } - } - - return !stoped; - } - - protos = { - - /** - * 绑定事件。 - * - * `callback`方法在执行时,arguments将会来源于trigger的时候携带的参数。如 - * ```javascript - * var obj = {}; - * - * // 使得obj有事件行为 - * Mediator.installTo( obj ); - * - * obj.on( 'testa', function( arg1, arg2 ) { - * console.log( arg1, arg2 ); // => 'arg1', 'arg2' - * }); - * - * obj.trigger( 'testa', 'arg1', 'arg2' ); - * ``` - * - * 如果`callback`中,某一个方法`return false`了,则后续的其他`callback`都不会被执行到。 - * 切会影响到`trigger`方法的返回值,为`false`。 - * - * `on`还可以用来添加一个特殊事件`all`, 这样所有的事件触发都会响应到。同时此类`callback`中的arguments有一个不同处, - * 就是第一个参数为`type`,记录当前是什么事件在触发。此类`callback`的优先级比脚低,会再正常`callback`执行完后触发。 - * ```javascript - * obj.on( 'all', function( type, arg1, arg2 ) { - * console.log( type, arg1, arg2 ); // => 'testa', 'arg1', 'arg2' - * }); - * ``` - * - * @method on - * @grammar on( name, callback[, context] ) => self - * @param {String} name 事件名,支持多个事件用空格隔开 - * @param {Function} callback 事件处理器 - * @param {Object} [context] 事件处理器的上下文。 - * @return {self} 返回自身,方便链式 - * @chainable - * @class Mediator - */ - on: function( name, callback, context ) { - var me = this, - set; - - if ( !callback ) { - return this; - } - - set = this._events || (this._events = []); - - eachEvent( name, callback, function( name, callback ) { - var handler = { e: name }; - - handler.cb = callback; - handler.ctx = context; - handler.ctx2 = context || me; - handler.id = set.length; - - set.push( handler ); - }); - - return this; - }, - - /** - * 绑定事件,且当handler执行完后,自动解除绑定。 - * @method once - * @grammar once( name, callback[, context] ) => self - * @param {String} name 事件名 - * @param {Function} callback 事件处理器 - * @param {Object} [context] 事件处理器的上下文。 - * @return {self} 返回自身,方便链式 - * @chainable - */ - once: function( name, callback, context ) { - var me = this; - - if ( !callback ) { - return me; - } - - eachEvent( name, callback, function( name, callback ) { - var once = function() { - me.off( name, once ); - return callback.apply( context || me, arguments ); - }; - - once._cb = callback; - me.on( name, once, context ); - }); - - return me; - }, - - /** - * 解除事件绑定 - * @method off - * @grammar off( [name[, callback[, context] ] ] ) => self - * @param {String} [name] 事件名 - * @param {Function} [callback] 事件处理器 - * @param {Object} [context] 事件处理器的上下文。 - * @return {self} 返回自身,方便链式 - * @chainable - */ - off: function( name, cb, ctx ) { - var events = this._events; - - if ( !events ) { - return this; - } - - if ( !name && !cb && !ctx ) { - this._events = []; - return this; - } - - eachEvent( name, cb, function( name, cb ) { - $.each( findHandlers( events, name, cb, ctx ), function() { - delete events[ this.id ]; - }); - }); - - return this; - }, - - /** - * 触发事件 - * @method trigger - * @grammar trigger( name[, args...] ) => self - * @param {String} type 事件名 - * @param {*} [...] 任意参数 - * @return {Boolean} 如果handler中return false了,则返回false, 否则返回true - */ - trigger: function( type ) { - var args, events, allEvents; - - if ( !this._events || !type ) { - return this; - } - - args = slice.call( arguments, 1 ); - events = findHandlers( this._events, type ); - allEvents = findHandlers( this._events, 'all' ); - - return triggerHanders( events, args ) && - triggerHanders( allEvents, arguments ); - } - }; - - /** - * 中介者,它本身是个单例,但可以通过[installTo](#WebUploader:Mediator:installTo)方法,使任何对象具备事件行为。 - * 主要目的是负责模块与模块之间的合作,降低耦合度。 - * - * @class Mediator - */ - return $.extend({ - - /** - * 可以通过这个接口,使任何对象具备事件功能。 - * @method installTo - * @param {Object} obj 需要具备事件行为的对象。 - * @return {Object} 返回obj. - */ - installTo: function( obj ) { - return $.extend( obj, protos ); - } - - }, protos ); - }); - /** - * @fileOverview Uploader上传类 - */ - define('uploader',[ - 'base', - 'mediator' - ], function( Base, Mediator ) { - - var $ = Base.$; - - /** - * 上传入口类。 - * @class Uploader - * @constructor - * @grammar new Uploader( opts ) => Uploader - * @example - * var uploader = WebUploader.Uploader({ - * swf: 'path_of_swf/Uploader.swf', - * - * // 开起分片上传。 - * chunked: true - * }); - */ - function Uploader( opts ) { - this.options = $.extend( true, {}, Uploader.options, opts ); - this._init( this.options ); - } - - // default Options - // widgets中有相应扩展 - Uploader.options = {}; - Mediator.installTo( Uploader.prototype ); - - // 批量添加纯命令式方法。 - $.each({ - upload: 'start-upload', - stop: 'stop-upload', - getFile: 'get-file', - getFiles: 'get-files', - addFile: 'add-file', - addFiles: 'add-file', - sort: 'sort-files', - removeFile: 'remove-file', - cancelFile: 'cancel-file', - skipFile: 'skip-file', - retry: 'retry', - isInProgress: 'is-in-progress', - makeThumb: 'make-thumb', - md5File: 'md5-file', - getDimension: 'get-dimension', - addButton: 'add-btn', - predictRuntimeType: 'predict-runtime-type', - refresh: 'refresh', - disable: 'disable', - enable: 'enable', - reset: 'reset' - }, function( fn, command ) { - Uploader.prototype[ fn ] = function() { - return this.request( command, arguments ); - }; - }); - - $.extend( Uploader.prototype, { - state: 'pending', - - _init: function( opts ) { - var me = this; - - me.request( 'init', opts, function() { - me.state = 'ready'; - me.trigger('ready'); - }); - }, - - /** - * 获取或者设置Uploader配置项。 - * @method option - * @grammar option( key ) => * - * @grammar option( key, val ) => self - * @example - * - * // 初始状态图片上传前不会压缩 - * var uploader = new WebUploader.Uploader({ - * compress: null; - * }); - * - * // 修改后图片上传前,尝试将图片压缩到1600 * 1600 - * uploader.option( 'compress', { - * width: 1600, - * height: 1600 - * }); - */ - option: function( key, val ) { - var opts = this.options; - - // setter - if ( arguments.length > 1 ) { - - if ( $.isPlainObject( val ) && - $.isPlainObject( opts[ key ] ) ) { - $.extend( opts[ key ], val ); - } else { - opts[ key ] = val; - } - - } else { // getter - return key ? opts[ key ] : opts; - } - }, - - /** - * 获取文件统计信息。返回一个包含一下信息的对象。 - * * `successNum` 上传成功的文件数 - * * `progressNum` 上传中的文件数 - * * `cancelNum` 被删除的文件数 - * * `invalidNum` 无效的文件数 - * * `uploadFailNum` 上传失败的文件数 - * * `queueNum` 还在队列中的文件数 - * * `interruptNum` 被暂停的文件数 - * @method getStats - * @grammar getStats() => Object - */ - getStats: function() { - // return this._mgr.getStats.apply( this._mgr, arguments ); - var stats = this.request('get-stats'); - - return stats ? { - successNum: stats.numOfSuccess, - progressNum: stats.numOfProgress, - - // who care? - // queueFailNum: 0, - cancelNum: stats.numOfCancel, - invalidNum: stats.numOfInvalid, - uploadFailNum: stats.numOfUploadFailed, - queueNum: stats.numOfQueue, - interruptNum: stats.numofInterrupt - } : {}; - }, - - // 需要重写此方法来来支持opts.onEvent和instance.onEvent的处理器 - trigger: function( type/*, args...*/ ) { - var args = [].slice.call( arguments, 1 ), - opts = this.options, - name = 'on' + type.substring( 0, 1 ).toUpperCase() + - type.substring( 1 ); - - if ( - // 调用通过on方法注册的handler. - Mediator.trigger.apply( this, arguments ) === false || - - // 调用opts.onEvent - $.isFunction( opts[ name ] ) && - opts[ name ].apply( this, args ) === false || - - // 调用this.onEvent - $.isFunction( this[ name ] ) && - this[ name ].apply( this, args ) === false || - - // 广播所有uploader的事件。 - Mediator.trigger.apply( Mediator, - [ this, type ].concat( args ) ) === false ) { - - return false; - } - - return true; - }, - - /** - * 销毁 webuploader 实例 - * @method destroy - * @grammar destroy() => undefined - */ - destroy: function() { - this.request( 'destroy', arguments ); - this.off(); - }, - - // widgets/widget.js将补充此方法的详细文档。 - request: Base.noop - }); - - /** - * 创建Uploader实例,等同于new Uploader( opts ); - * @method create - * @class Base - * @static - * @grammar Base.create( opts ) => Uploader - */ - Base.create = Uploader.create = function( opts ) { - return new Uploader( opts ); - }; - - // 暴露Uploader,可以通过它来扩展业务逻辑。 - Base.Uploader = Uploader; - - return Uploader; - }); - /** - * @fileOverview Runtime管理器,负责Runtime的选择, 连接 - */ - define('runtime/runtime',[ - 'base', - 'mediator' - ], function( Base, Mediator ) { - - var $ = Base.$, - factories = {}, - - // 获取对象的第一个key - getFirstKey = function( obj ) { - for ( var key in obj ) { - if ( obj.hasOwnProperty( key ) ) { - return key; - } - } - return null; - }; - - // 接口类。 - function Runtime( options ) { - this.options = $.extend({ - container: document.body - }, options ); - this.uid = Base.guid('rt_'); - } - - $.extend( Runtime.prototype, { - - getContainer: function() { - var opts = this.options, - parent, container; - - if ( this._container ) { - return this._container; - } - - parent = $( opts.container || document.body ); - container = $( document.createElement('div') ); - - container.attr( 'id', 'rt_' + this.uid ); - container.css({ - position: 'absolute', - top: '0px', - left: '0px', - width: '1px', - height: '1px', - overflow: 'hidden' - }); - - parent.append( container ); - parent.addClass('webuploader-container'); - this._container = container; - this._parent = parent; - return container; - }, - - init: Base.noop, - exec: Base.noop, - - destroy: function() { - this._container && this._container.remove(); - this._parent && this._parent.removeClass('webuploader-container'); - this.off(); - } - }); - - Runtime.orders = 'html5,flash'; - - - /** - * 添加Runtime实现。 - * @param {String} type 类型 - * @param {Runtime} factory 具体Runtime实现。 - */ - Runtime.addRuntime = function( type, factory ) { - factories[ type ] = factory; - }; - - Runtime.hasRuntime = function( type ) { - return !!(type ? factories[ type ] : getFirstKey( factories )); - }; - - Runtime.create = function( opts, orders ) { - var type, runtime; - - orders = orders || Runtime.orders; - $.each( orders.split( /\s*,\s*/g ), function() { - if ( factories[ this ] ) { - type = this; - return false; - } - }); - - type = type || getFirstKey( factories ); - - if ( !type ) { - throw new Error('Runtime Error'); - } - - runtime = new factories[ type ]( opts ); - return runtime; - }; - - Mediator.installTo( Runtime.prototype ); - return Runtime; - }); - - /** - * @fileOverview Runtime管理器,负责Runtime的选择, 连接 - */ - define('runtime/client',[ - 'base', - 'mediator', - 'runtime/runtime' - ], function( Base, Mediator, Runtime ) { - - var cache; - - cache = (function() { - var obj = {}; - - return { - add: function( runtime ) { - obj[ runtime.uid ] = runtime; - }, - - get: function( ruid, standalone ) { - var i; - - if ( ruid ) { - return obj[ ruid ]; - } - - for ( i in obj ) { - // 有些类型不能重用,比如filepicker. - if ( standalone && obj[ i ].__standalone ) { - continue; - } - - return obj[ i ]; - } - - return null; - }, - - remove: function( runtime ) { - delete obj[ runtime.uid ]; - } - }; - })(); - - function RuntimeClient( component, standalone ) { - var deferred = Base.Deferred(), - runtime; - - this.uid = Base.guid('client_'); - - // 允许runtime没有初始化之前,注册一些方法在初始化后执行。 - this.runtimeReady = function( cb ) { - return deferred.done( cb ); - }; - - this.connectRuntime = function( opts, cb ) { - - // already connected. - if ( runtime ) { - throw new Error('already connected!'); - } - - deferred.done( cb ); - - if ( typeof opts === 'string' && cache.get( opts ) ) { - runtime = cache.get( opts ); - } - - // 像filePicker只能独立存在,不能公用。 - runtime = runtime || cache.get( null, standalone ); - - // 需要创建 - if ( !runtime ) { - runtime = Runtime.create( opts, opts.runtimeOrder ); - runtime.__promise = deferred.promise(); - runtime.once( 'ready', deferred.resolve ); - runtime.init(); - cache.add( runtime ); - runtime.__client = 1; - } else { - // 来自cache - Base.$.extend( runtime.options, opts ); - runtime.__promise.then( deferred.resolve ); - runtime.__client++; - } - - standalone && (runtime.__standalone = standalone); - return runtime; - }; - - this.getRuntime = function() { - return runtime; - }; - - this.disconnectRuntime = function() { - if ( !runtime ) { - return; - } - - runtime.__client--; - - if ( runtime.__client <= 0 ) { - cache.remove( runtime ); - delete runtime.__promise; - runtime.destroy(); - } - - runtime = null; - }; - - this.exec = function() { - if ( !runtime ) { - return; - } - - var args = Base.slice( arguments ); - component && args.unshift( component ); - - return runtime.exec.apply( this, args ); - }; - - this.getRuid = function() { - return runtime && runtime.uid; - }; - - this.destroy = (function( destroy ) { - return function() { - destroy && destroy.apply( this, arguments ); - this.trigger('destroy'); - this.off(); - this.exec('destroy'); - this.disconnectRuntime(); - }; - })( this.destroy ); - } - - Mediator.installTo( RuntimeClient.prototype ); - return RuntimeClient; - }); - /** - * @fileOverview Blob - */ - define('lib/blob',[ - 'base', - 'runtime/client' - ], function( Base, RuntimeClient ) { - - function Blob( ruid, source ) { - var me = this; - - me.source = source; - me.ruid = ruid; - this.size = source.size || 0; - - // 如果没有指定 mimetype, 但是知道文件后缀。 - if ( !source.type && this.ext && - ~'jpg,jpeg,png,gif,bmp'.indexOf( this.ext ) ) { - this.type = 'image/' + (this.ext === 'jpg' ? 'jpeg' : this.ext); - } else { - this.type = source.type || 'application/octet-stream'; - } - - RuntimeClient.call( me, 'Blob' ); - this.uid = source.uid || this.uid; - - if ( ruid ) { - me.connectRuntime( ruid ); - } - } - - Base.inherits( RuntimeClient, { - constructor: Blob, - - slice: function( start, end ) { - return this.exec( 'slice', start, end ); - }, - - getSource: function() { - return this.source; - } - }); - - return Blob; - }); - /** - * 为了统一化Flash的File和HTML5的File而存在。 - * 以至于要调用Flash里面的File,也可以像调用HTML5版本的File一下。 - * @fileOverview File - */ - define('lib/file',[ - 'base', - 'lib/blob' - ], function( Base, Blob ) { - - var uid = 1, - rExt = /\.([^.]+)$/; - - function File( ruid, file ) { - var ext; - - this.name = file.name || ('untitled' + uid++); - ext = rExt.exec( file.name ) ? RegExp.$1.toLowerCase() : ''; - - // todo 支持其他类型文件的转换。 - // 如果有 mimetype, 但是文件名里面没有找出后缀规律 - if ( !ext && file.type ) { - ext = /\/(jpg|jpeg|png|gif|bmp)$/i.exec( file.type ) ? - RegExp.$1.toLowerCase() : ''; - this.name += '.' + ext; - } - - this.ext = ext; - this.lastModifiedDate = file.lastModifiedDate || - (new Date()).toLocaleString(); - - Blob.apply( this, arguments ); - } - - return Base.inherits( Blob, File ); - }); - - /** - * @fileOverview 错误信息 - */ - define('lib/filepicker',[ - 'base', - 'runtime/client', - 'lib/file' - ], function( Base, RuntimeClent, File ) { - - var $ = Base.$; - - function FilePicker( opts ) { - opts = this.options = $.extend({}, FilePicker.options, opts ); - - opts.container = $( opts.id ); - - if ( !opts.container.length ) { - throw new Error('按钮指定错误'); - } - - opts.innerHTML = opts.innerHTML || opts.label || - opts.container.html() || ''; - - opts.button = $( opts.button || document.createElement('div') ); - opts.button.html( opts.innerHTML ); - opts.container.html( opts.button ); - - RuntimeClent.call( this, 'FilePicker', true ); - } - - FilePicker.options = { - button: null, - container: null, - label: null, - innerHTML: null, - multiple: true, - accept: null, - name: 'file' - }; - - Base.inherits( RuntimeClent, { - constructor: FilePicker, - - init: function() { - var me = this, - opts = me.options, - button = opts.button; - - button.addClass('webuploader-pick'); - - me.on( 'all', function( type ) { - var files; - - switch ( type ) { - case 'mouseenter': - button.addClass('webuploader-pick-hover'); - break; - - case 'mouseleave': - button.removeClass('webuploader-pick-hover'); - break; - - case 'change': - files = me.exec('getFiles'); - me.trigger( 'select', $.map( files, function( file ) { - file = new File( me.getRuid(), file ); - - // 记录来源。 - file._refer = opts.container; - return file; - }), opts.container ); - break; - } - }); - - me.connectRuntime( opts, function() { - me.refresh(); - me.exec( 'init', opts ); - me.trigger('ready'); - }); - - this._resizeHandler = Base.bindFn( this.refresh, this ); - $( window ).on( 'resize', this._resizeHandler ); - }, - - refresh: function() { - var shimContainer = this.getRuntime().getContainer(), - button = this.options.button, - width = button.outerWidth ? - button.outerWidth() : button.width(), - - height = button.outerHeight ? - button.outerHeight() : button.height(), - - pos = button.offset(); - - width && height && shimContainer.css({ - bottom: 'auto', - right: 'auto', - width: width + 'px', - height: height + 'px' - }).offset( pos ); - }, - - enable: function() { - var btn = this.options.button; - - btn.removeClass('webuploader-pick-disable'); - this.refresh(); - }, - - disable: function() { - var btn = this.options.button; - - this.getRuntime().getContainer().css({ - top: '-99999px' - }); - - btn.addClass('webuploader-pick-disable'); - }, - - destroy: function() { - var btn = this.options.button; - $( window ).off( 'resize', this._resizeHandler ); - btn.removeClass('webuploader-pick-disable webuploader-pick-hover ' + - 'webuploader-pick'); - } - }); - - return FilePicker; - }); - - /** - * @fileOverview 组件基类。 - */ - define('widgets/widget',[ - 'base', - 'uploader' - ], function( Base, Uploader ) { - - var $ = Base.$, - _init = Uploader.prototype._init, - _destroy = Uploader.prototype.destroy, - IGNORE = {}, - widgetClass = []; - - function isArrayLike( obj ) { - if ( !obj ) { - return false; - } - - var length = obj.length, - type = $.type( obj ); - - if ( obj.nodeType === 1 && length ) { - return true; - } - - return type === 'array' || type !== 'function' && type !== 'string' && - (length === 0 || typeof length === 'number' && length > 0 && - (length - 1) in obj); - } - - function Widget( uploader ) { - this.owner = uploader; - this.options = uploader.options; - } - - $.extend( Widget.prototype, { - - init: Base.noop, - - // 类Backbone的事件监听声明,监听uploader实例上的事件 - // widget直接无法监听事件,事件只能通过uploader来传递 - invoke: function( apiName, args ) { - - /* - { - 'make-thumb': 'makeThumb' - } - */ - var map = this.responseMap; - - // 如果无API响应声明则忽略 - if ( !map || !(apiName in map) || !(map[ apiName ] in this) || - !$.isFunction( this[ map[ apiName ] ] ) ) { - - return IGNORE; - } - - return this[ map[ apiName ] ].apply( this, args ); - - }, - - /** - * 发送命令。当传入`callback`或者`handler`中返回`promise`时。返回一个当所有`handler`中的promise都完成后完成的新`promise`。 - * @method request - * @grammar request( command, args ) => * | Promise - * @grammar request( command, args, callback ) => Promise - * @for Uploader - */ - request: function() { - return this.owner.request.apply( this.owner, arguments ); - } - }); - - // 扩展Uploader. - $.extend( Uploader.prototype, { - - /** - * @property {String | Array} [disableWidgets=undefined] - * @namespace options - * @for Uploader - * @description 默认所有 Uploader.register 了的 widget 都会被加载,如果禁用某一部分,请通过此 option 指定黑名单。 - */ - - // 覆写_init用来初始化widgets - _init: function() { - var me = this, - widgets = me._widgets = [], - deactives = me.options.disableWidgets || ''; - - $.each( widgetClass, function( _, klass ) { - (!deactives || !~deactives.indexOf( klass._name )) && - widgets.push( new klass( me ) ); - }); - - return _init.apply( me, arguments ); - }, - - request: function( apiName, args, callback ) { - var i = 0, - widgets = this._widgets, - len = widgets && widgets.length, - rlts = [], - dfds = [], - widget, rlt, promise, key; - - args = isArrayLike( args ) ? args : [ args ]; - - for ( ; i < len; i++ ) { - widget = widgets[ i ]; - rlt = widget.invoke( apiName, args ); - - if ( rlt !== IGNORE ) { - - // Deferred对象 - if ( Base.isPromise( rlt ) ) { - dfds.push( rlt ); - } else { - rlts.push( rlt ); - } - } - } - - // 如果有callback,则用异步方式。 - if ( callback || dfds.length ) { - promise = Base.when.apply( Base, dfds ); - key = promise.pipe ? 'pipe' : 'then'; - - // 很重要不能删除。删除了会死循环。 - // 保证执行顺序。让callback总是在下一个 tick 中执行。 - return promise[ key ](function() { - var deferred = Base.Deferred(), - args = arguments; - - if ( args.length === 1 ) { - args = args[ 0 ]; - } - - setTimeout(function() { - deferred.resolve( args ); - }, 1 ); - - return deferred.promise(); - })[ callback ? key : 'done' ]( callback || Base.noop ); - } else { - return rlts[ 0 ]; - } - }, - - destroy: function() { - _destroy.apply( this, arguments ); - this._widgets = null; - } - }); - - /** - * 添加组件 - * @grammar Uploader.register(proto); - * @grammar Uploader.register(map, proto); - * @param {object} responseMap API 名称与函数实现的映射 - * @param {object} proto 组件原型,构造函数通过 constructor 属性定义 - * @method Uploader.register - * @for Uploader - * @example - * Uploader.register({ - * 'make-thumb': 'makeThumb' - * }, { - * init: function( options ) {}, - * makeThumb: function() {} - * }); - * - * Uploader.register({ - * 'make-thumb': function() { - * - * } - * }); - */ - Uploader.register = Widget.register = function( responseMap, widgetProto ) { - var map = { init: 'init', destroy: 'destroy', name: 'anonymous' }, - klass; - - if ( arguments.length === 1 ) { - widgetProto = responseMap; - - // 自动生成 map 表。 - $.each(widgetProto, function(key) { - if ( key[0] === '_' || key === 'name' ) { - key === 'name' && (map.name = widgetProto.name); - return; - } - - map[key.replace(/[A-Z]/g, '-$&').toLowerCase()] = key; - }); - - } else { - map = $.extend( map, responseMap ); - } - - widgetProto.responseMap = map; - klass = Base.inherits( Widget, widgetProto ); - klass._name = map.name; - widgetClass.push( klass ); - - return klass; - }; - - /** - * 删除插件,只有在注册时指定了名字的才能被删除。 - * @grammar Uploader.unRegister(name); - * @param {string} name 组件名字 - * @method Uploader.unRegister - * @for Uploader - * @example - * - * Uploader.register({ - * name: 'custom', - * - * 'make-thumb': function() { - * - * } - * }); - * - * Uploader.unRegister('custom'); - */ - Uploader.unRegister = Widget.unRegister = function( name ) { - if ( !name || name === 'anonymous' ) { - return; - } - - // 删除指定的插件。 - for ( var i = widgetClass.length; i--; ) { - if ( widgetClass[i]._name === name ) { - widgetClass.splice(i, 1) - } - } - }; - - return Widget; - }); - /** - * @fileOverview 文件选择相关 - */ - define('widgets/filepicker',[ - 'base', - 'uploader', - 'lib/filepicker', - 'widgets/widget' - ], function( Base, Uploader, FilePicker ) { - var $ = Base.$; - - $.extend( Uploader.options, { - - /** - * @property {Selector | Object} [pick=undefined] - * @namespace options - * @for Uploader - * @description 指定选择文件的按钮容器,不指定则不创建按钮。 - * - * * `id` {Seletor|dom} 指定选择文件的按钮容器,不指定则不创建按钮。**注意** 这里虽然写的是 id, 但是不是只支持 id, 还支持 class, 或者 dom 节点。 - * * `label` {String} 请采用 `innerHTML` 代替 - * * `innerHTML` {String} 指定按钮文字。不指定时优先从指定的容器中看是否自带文字。 - * * `multiple` {Boolean} 是否开起同时选择多个文件能力。 - */ - pick: null, - - /** - * @property {Arroy} [accept=null] - * @namespace options - * @for Uploader - * @description 指定接受哪些类型的文件。 由于目前还有ext转mimeType表,所以这里需要分开指定。 - * - * * `title` {String} 文字描述 - * * `extensions` {String} 允许的文件后缀,不带点,多个用逗号分割。 - * * `mimeTypes` {String} 多个用逗号分割。 - * - * 如: - * - * ``` - * { - * title: 'Images', - * extensions: 'gif,jpg,jpeg,bmp,png', - * mimeTypes: 'image/*' - * } - * ``` - */ - accept: null/*{ - title: 'Images', - extensions: 'gif,jpg,jpeg,bmp,png', - mimeTypes: 'image/*' - }*/ - }); - - return Uploader.register({ - name: 'picker', - - init: function( opts ) { - this.pickers = []; - return opts.pick && this.addBtn( opts.pick ); - }, - - refresh: function() { - $.each( this.pickers, function() { - this.refresh(); - }); - }, - - /** - * @method addButton - * @for Uploader - * @grammar addButton( pick ) => Promise - * @description - * 添加文件选择按钮,如果一个按钮不够,需要调用此方法来添加。参数跟[options.pick](#WebUploader:Uploader:options)一致。 - * @example - * uploader.addButton({ - * id: '#btnContainer', - * innerHTML: '选择文件' - * }); - */ - addBtn: function( pick ) { - var me = this, - opts = me.options, - accept = opts.accept, - promises = []; - - if ( !pick ) { - return; - } - - $.isPlainObject( pick ) || (pick = { - id: pick - }); - - $( pick.id ).each(function() { - var options, picker, deferred; - - deferred = Base.Deferred(); - - options = $.extend({}, pick, { - accept: $.isPlainObject( accept ) ? [ accept ] : accept, - swf: opts.swf, - runtimeOrder: opts.runtimeOrder, - id: this - }); - - picker = new FilePicker( options ); - - picker.once( 'ready', deferred.resolve ); - picker.on( 'select', function( files ) { - me.owner.request( 'add-file', [ files ]); - }); - picker.init(); - - me.pickers.push( picker ); - - promises.push( deferred.promise() ); - }); - - return Base.when.apply( Base, promises ); - }, - - disable: function() { - $.each( this.pickers, function() { - this.disable(); - }); - }, - - enable: function() { - $.each( this.pickers, function() { - this.enable(); - }); - }, - - destroy: function() { - $.each( this.pickers, function() { - this.destroy(); - }); - this.pickers = null; - } - }); - }); - /** - * @fileOverview Image - */ - define('lib/image',[ - 'base', - 'runtime/client', - 'lib/blob' - ], function( Base, RuntimeClient, Blob ) { - var $ = Base.$; - - // 构造器。 - function Image( opts ) { - this.options = $.extend({}, Image.options, opts ); - RuntimeClient.call( this, 'Image' ); - - this.on( 'load', function() { - this._info = this.exec('info'); - this._meta = this.exec('meta'); - }); - } - - // 默认选项。 - Image.options = { - - // 默认的图片处理质量 - quality: 90, - - // 是否裁剪 - crop: false, - - // 是否保留头部信息 - preserveHeaders: false, - - // 是否允许放大。 - allowMagnify: false - }; - - // 继承RuntimeClient. - Base.inherits( RuntimeClient, { - constructor: Image, - - info: function( val ) { - - // setter - if ( val ) { - this._info = val; - return this; - } - - // getter - return this._info; - }, - - meta: function( val ) { - - // setter - if ( val ) { - this._meta = val; - return this; - } - - // getter - return this._meta; - }, - - loadFromBlob: function( blob ) { - var me = this, - ruid = blob.getRuid(); - - this.connectRuntime( ruid, function() { - me.exec( 'init', me.options ); - me.exec( 'loadFromBlob', blob ); - }); - }, - - resize: function() { - var args = Base.slice( arguments ); - return this.exec.apply( this, [ 'resize' ].concat( args ) ); - }, - - crop: function() { - var args = Base.slice( arguments ); - return this.exec.apply( this, [ 'crop' ].concat( args ) ); - }, - - getAsDataUrl: function( type ) { - return this.exec( 'getAsDataUrl', type ); - }, - - getAsBlob: function( type ) { - var blob = this.exec( 'getAsBlob', type ); - - return new Blob( this.getRuid(), blob ); - } - }); - - return Image; - }); - /** - * @fileOverview 图片操作, 负责预览图片和上传前压缩图片 - */ - define('widgets/image',[ - 'base', - 'uploader', - 'lib/image', - 'widgets/widget' - ], function( Base, Uploader, Image ) { - - var $ = Base.$, - throttle; - - // 根据要处理的文件大小来节流,一次不能处理太多,会卡。 - throttle = (function( max ) { - var occupied = 0, - waiting = [], - tick = function() { - var item; - - while ( waiting.length && occupied < max ) { - item = waiting.shift(); - occupied += item[ 0 ]; - item[ 1 ](); - } - }; - - return function( emiter, size, cb ) { - waiting.push([ size, cb ]); - emiter.once( 'destroy', function() { - occupied -= size; - setTimeout( tick, 1 ); - }); - setTimeout( tick, 1 ); - }; - })( 5 * 1024 * 1024 ); - - $.extend( Uploader.options, { - - /** - * @property {Object} [thumb] - * @namespace options - * @for Uploader - * @description 配置生成缩略图的选项。 - * - * 默认为: - * - * ```javascript - * { - * width: 110, - * height: 110, - * - * // 图片质量,只有type为`image/jpeg`的时候才有效。 - * quality: 70, - * - * // 是否允许放大,如果想要生成小图的时候不失真,此选项应该设置为false. - * allowMagnify: true, - * - * // 是否允许裁剪。 - * crop: true, - * - * // 为空的话则保留原有图片格式。 - * // 否则强制转换成指定的类型。 - * type: 'image/jpeg' - * } - * ``` - */ - thumb: { - width: 110, - height: 110, - quality: 70, - allowMagnify: true, - crop: true, - preserveHeaders: false, - - // 为空的话则保留原有图片格式。 - // 否则强制转换成指定的类型。 - // IE 8下面 base64 大小不能超过 32K 否则预览失败,而非 jpeg 编码的图片很可 - // 能会超过 32k, 所以这里设置成预览的时候都是 image/jpeg - type: 'image/jpeg' - }, - - /** - * @property {Object} [compress] - * @namespace options - * @for Uploader - * @description 配置压缩的图片的选项。如果此选项为`false`, 则图片在上传前不进行压缩。 - * - * 默认为: - * - * ```javascript - * { - * width: 1600, - * height: 1600, - * - * // 图片质量,只有type为`image/jpeg`的时候才有效。 - * quality: 90, - * - * // 是否允许放大,如果想要生成小图的时候不失真,此选项应该设置为false. - * allowMagnify: false, - * - * // 是否允许裁剪。 - * crop: false, - * - * // 是否保留头部meta信息。 - * preserveHeaders: true, - * - * // 如果发现压缩后文件大小比原来还大,则使用原来图片 - * // 此属性可能会影响图片自动纠正功能 - * noCompressIfLarger: false, - * - * // 单位字节,如果图片大小小于此值,不会采用压缩。 - * compressSize: 0 - * } - * ``` - */ - compress: { - width: 1600, - height: 1600, - quality: 90, - allowMagnify: false, - crop: false, - preserveHeaders: true - } - }); - - return Uploader.register({ - - name: 'image', - - - /** - * 生成缩略图,此过程为异步,所以需要传入`callback`。 - * 通常情况在图片加入队里后调用此方法来生成预览图以增强交互效果。 - * - * 当 width 或者 height 的值介于 0 - 1 时,被当成百分比使用。 - * - * `callback`中可以接收到两个参数。 - * * 第一个为error,如果生成缩略图有错误,此error将为真。 - * * 第二个为ret, 缩略图的Data URL值。 - * - * **注意** - * Date URL在IE6/7中不支持,所以不用调用此方法了,直接显示一张暂不支持预览图片好了。 - * 也可以借助服务端,将 base64 数据传给服务端,生成一个临时文件供预览。 - * - * @method makeThumb - * @grammar makeThumb( file, callback ) => undefined - * @grammar makeThumb( file, callback, width, height ) => undefined - * @for Uploader - * @example - * - * uploader.on( 'fileQueued', function( file ) { - * var $li = ...; - * - * uploader.makeThumb( file, function( error, ret ) { - * if ( error ) { - * $li.text('预览错误'); - * } else { - * $li.append(''); - * } - * }); - * - * }); - */ - makeThumb: function( file, cb, width, height ) { - var opts, image; - - file = this.request( 'get-file', file ); - - // 只预览图片格式。 - if ( !file.type.match( /^image/ ) ) { - cb( true ); - return; - } - - opts = $.extend({}, this.options.thumb ); - - // 如果传入的是object. - if ( $.isPlainObject( width ) ) { - opts = $.extend( opts, width ); - width = null; - } - - width = width || opts.width; - height = height || opts.height; - - image = new Image( opts ); - - image.once( 'load', function() { - file._info = file._info || image.info(); - file._meta = file._meta || image.meta(); - - // 如果 width 的值介于 0 - 1 - // 说明设置的是百分比。 - if ( width <= 1 && width > 0 ) { - width = file._info.width * width; - } - - // 同样的规则应用于 height - if ( height <= 1 && height > 0 ) { - height = file._info.height * height; - } - - image.resize( width, height ); - }); - - // 当 resize 完后 - image.once( 'complete', function() { - cb( false, image.getAsDataUrl( opts.type ) ); - image.destroy(); - }); - - image.once( 'error', function( reason ) { - cb( reason || true ); - image.destroy(); - }); - - throttle( image, file.source.size, function() { - file._info && image.info( file._info ); - file._meta && image.meta( file._meta ); - image.loadFromBlob( file.source ); - }); - }, - - beforeSendFile: function( file ) { - var opts = this.options.compress || this.options.resize, - compressSize = opts && opts.compressSize || 0, - noCompressIfLarger = opts && opts.noCompressIfLarger || false, - image, deferred; - - file = this.request( 'get-file', file ); - - // 只压缩 jpeg 图片格式。 - // gif 可能会丢失针 - // bmp png 基本上尺寸都不大,且压缩比比较小。 - if ( !opts || !~'image/jpeg,image/jpg'.indexOf( file.type ) || - file.size < compressSize || - file._compressed ) { - return; - } - - opts = $.extend({}, opts ); - deferred = Base.Deferred(); - - image = new Image( opts ); - - deferred.always(function() { - image.destroy(); - image = null; - }); - image.once( 'error', deferred.reject ); - image.once( 'load', function() { - var width = opts.width, - height = opts.height; - - file._info = file._info || image.info(); - file._meta = file._meta || image.meta(); - - // 如果 width 的值介于 0 - 1 - // 说明设置的是百分比。 - if ( width <= 1 && width > 0 ) { - width = file._info.width * width; - } - - // 同样的规则应用于 height - if ( height <= 1 && height > 0 ) { - height = file._info.height * height; - } - - image.resize( width, height ); - }); - - image.once( 'complete', function() { - var blob, size; - - // 移动端 UC / qq 浏览器的无图模式下 - // ctx.getImageData 处理大图的时候会报 Exception - // INDEX_SIZE_ERR: DOM Exception 1 - try { - blob = image.getAsBlob( opts.type ); - - size = file.size; - - // 如果压缩后,比原来还大则不用压缩后的。 - if ( !noCompressIfLarger || blob.size < size ) { - // file.source.destroy && file.source.destroy(); - file.source = blob; - file.size = blob.size; - - file.trigger( 'resize', blob.size, size ); - } - - // 标记,避免重复压缩。 - file._compressed = true; - deferred.resolve(); - } catch ( e ) { - // 出错了直接继续,让其上传原始图片 - deferred.resolve(); - } - }); - - file._info && image.info( file._info ); - file._meta && image.meta( file._meta ); - - image.loadFromBlob( file.source ); - return deferred.promise(); - } - }); - }); - /** - * @fileOverview 文件属性封装 - */ - define('file',[ - 'base', - 'mediator' - ], function( Base, Mediator ) { - - var $ = Base.$, - idPrefix = 'WU_FILE_', - idSuffix = 0, - rExt = /\.([^.]+)$/, - statusMap = {}; - - function gid() { - return idPrefix + idSuffix++; - } - - /** - * 文件类 - * @class File - * @constructor 构造函数 - * @grammar new File( source ) => File - * @param {Lib.File} source [lib.File](#Lib.File)实例, 此source对象是带有Runtime信息的。 - */ - function WUFile( source ) { - - /** - * 文件名,包括扩展名(后缀) - * @property name - * @type {string} - */ - this.name = source.name || 'Untitled'; - - /** - * 文件体积(字节) - * @property size - * @type {uint} - * @default 0 - */ - this.size = source.size || 0; - - /** - * 文件MIMETYPE类型,与文件类型的对应关系请参考[http://t.cn/z8ZnFny](http://t.cn/z8ZnFny) - * @property type - * @type {string} - * @default 'application/octet-stream' - */ - this.type = source.type || 'application/octet-stream'; - - /** - * 文件最后修改日期 - * @property lastModifiedDate - * @type {int} - * @default 当前时间戳 - */ - this.lastModifiedDate = source.lastModifiedDate || (new Date() * 1); - - /** - * 文件ID,每个对象具有唯一ID,与文件名无关 - * @property id - * @type {string} - */ - this.id = gid(); - - /** - * 文件扩展名,通过文件名获取,例如test.png的扩展名为png - * @property ext - * @type {string} - */ - this.ext = rExt.exec( this.name ) ? RegExp.$1 : ''; - - - /** - * 状态文字说明。在不同的status语境下有不同的用途。 - * @property statusText - * @type {string} - */ - this.statusText = ''; - - // 存储文件状态,防止通过属性直接修改 - statusMap[ this.id ] = WUFile.Status.INITED; - - this.source = source; - this.loaded = 0; - - this.on( 'error', function( msg ) { - this.setStatus( WUFile.Status.ERROR, msg ); - }); - } - - $.extend( WUFile.prototype, { - - /** - * 设置状态,状态变化时会触发`change`事件。 - * @method setStatus - * @grammar setStatus( status[, statusText] ); - * @param {File.Status|String} status [文件状态值](#WebUploader:File:File.Status) - * @param {String} [statusText=''] 状态说明,常在error时使用,用http, abort,server等来标记是由于什么原因导致文件错误。 - */ - setStatus: function( status, text ) { - - var prevStatus = statusMap[ this.id ]; - - typeof text !== 'undefined' && (this.statusText = text); - - if ( status !== prevStatus ) { - statusMap[ this.id ] = status; - /** - * 文件状态变化 - * @event statuschange - */ - this.trigger( 'statuschange', status, prevStatus ); - } - - }, - - /** - * 获取文件状态 - * @return {File.Status} - * @example - 文件状态具体包括以下几种类型: - { - // 初始化 - INITED: 0, - // 已入队列 - QUEUED: 1, - // 正在上传 - PROGRESS: 2, - // 上传出错 - ERROR: 3, - // 上传成功 - COMPLETE: 4, - // 上传取消 - CANCELLED: 5 - } - */ - getStatus: function() { - return statusMap[ this.id ]; - }, - - /** - * 获取文件原始信息。 - * @return {*} - */ - getSource: function() { - return this.source; - }, - - destroy: function() { - this.off(); - delete statusMap[ this.id ]; - } - }); - - Mediator.installTo( WUFile.prototype ); - - /** - * 文件状态值,具体包括以下几种类型: - * * `inited` 初始状态 - * * `queued` 已经进入队列, 等待上传 - * * `progress` 上传中 - * * `complete` 上传完成。 - * * `error` 上传出错,可重试 - * * `interrupt` 上传中断,可续传。 - * * `invalid` 文件不合格,不能重试上传。会自动从队列中移除。 - * * `cancelled` 文件被移除。 - * @property {Object} Status - * @namespace File - * @class File - * @static - */ - WUFile.Status = { - INITED: 'inited', // 初始状态 - QUEUED: 'queued', // 已经进入队列, 等待上传 - PROGRESS: 'progress', // 上传中 - ERROR: 'error', // 上传出错,可重试 - COMPLETE: 'complete', // 上传完成。 - CANCELLED: 'cancelled', // 上传取消。 - INTERRUPT: 'interrupt', // 上传中断,可续传。 - INVALID: 'invalid' // 文件不合格,不能重试上传。 - }; - - return WUFile; - }); - - /** - * @fileOverview 文件队列 - */ - define('queue',[ - 'base', - 'mediator', - 'file' - ], function( Base, Mediator, WUFile ) { - - var $ = Base.$, - STATUS = WUFile.Status; - - /** - * 文件队列, 用来存储各个状态中的文件。 - * @class Queue - * @extends Mediator - */ - function Queue() { - - /** - * 统计文件数。 - * * `numOfQueue` 队列中的文件数。 - * * `numOfSuccess` 上传成功的文件数 - * * `numOfCancel` 被取消的文件数 - * * `numOfProgress` 正在上传中的文件数 - * * `numOfUploadFailed` 上传错误的文件数。 - * * `numOfInvalid` 无效的文件数。 - * * `numofDeleted` 被移除的文件数。 - * @property {Object} stats - */ - this.stats = { - numOfQueue: 0, - numOfSuccess: 0, - numOfCancel: 0, - numOfProgress: 0, - numOfUploadFailed: 0, - numOfInvalid: 0, - numofDeleted: 0, - numofInterrupt: 0 - }; - - // 上传队列,仅包括等待上传的文件 - this._queue = []; - - // 存储所有文件 - this._map = {}; - } - - $.extend( Queue.prototype, { - - /** - * 将新文件加入对队列尾部 - * - * @method append - * @param {File} file 文件对象 - */ - append: function( file ) { - this._queue.push( file ); - this._fileAdded( file ); - return this; - }, - - /** - * 将新文件加入对队列头部 - * - * @method prepend - * @param {File} file 文件对象 - */ - prepend: function( file ) { - this._queue.unshift( file ); - this._fileAdded( file ); - return this; - }, - - /** - * 获取文件对象 - * - * @method getFile - * @param {String} fileId 文件ID - * @return {File} - */ - getFile: function( fileId ) { - if ( typeof fileId !== 'string' ) { - return fileId; - } - return this._map[ fileId ]; - }, - - /** - * 从队列中取出一个指定状态的文件。 - * @grammar fetch( status ) => File - * @method fetch - * @param {String} status [文件状态值](#WebUploader:File:File.Status) - * @return {File} [File](#WebUploader:File) - */ - fetch: function( status ) { - var len = this._queue.length, - i, file; - - status = status || STATUS.QUEUED; - - for ( i = 0; i < len; i++ ) { - file = this._queue[ i ]; - - if ( status === file.getStatus() ) { - return file; - } - } - - return null; - }, - - /** - * 对队列进行排序,能够控制文件上传顺序。 - * @grammar sort( fn ) => undefined - * @method sort - * @param {Function} fn 排序方法 - */ - sort: function( fn ) { - if ( typeof fn === 'function' ) { - this._queue.sort( fn ); - } - }, - - /** - * 获取指定类型的文件列表, 列表中每一个成员为[File](#WebUploader:File)对象。 - * @grammar getFiles( [status1[, status2 ...]] ) => Array - * @method getFiles - * @param {String} [status] [文件状态值](#WebUploader:File:File.Status) - */ - getFiles: function() { - var sts = [].slice.call( arguments, 0 ), - ret = [], - i = 0, - len = this._queue.length, - file; - - for ( ; i < len; i++ ) { - file = this._queue[ i ]; - - if ( sts.length && !~$.inArray( file.getStatus(), sts ) ) { - continue; - } - - ret.push( file ); - } - - return ret; - }, - - /** - * 在队列中删除文件。 - * @grammar removeFile( file ) => Array - * @method removeFile - * @param {File} 文件对象。 - */ - removeFile: function( file ) { - var me = this, - existing = this._map[ file.id ]; - - if ( existing ) { - delete this._map[ file.id ]; - file.destroy(); - this.stats.numofDeleted++; - } - }, - - _fileAdded: function( file ) { - var me = this, - existing = this._map[ file.id ]; - - if ( !existing ) { - this._map[ file.id ] = file; - - file.on( 'statuschange', function( cur, pre ) { - me._onFileStatusChange( cur, pre ); - }); - } - }, - - _onFileStatusChange: function( curStatus, preStatus ) { - var stats = this.stats; - - switch ( preStatus ) { - case STATUS.PROGRESS: - stats.numOfProgress--; - break; - - case STATUS.QUEUED: - stats.numOfQueue --; - break; - - case STATUS.ERROR: - stats.numOfUploadFailed--; - break; - - case STATUS.INVALID: - stats.numOfInvalid--; - break; - - case STATUS.INTERRUPT: - stats.numofInterrupt--; - break; - } - - switch ( curStatus ) { - case STATUS.QUEUED: - stats.numOfQueue++; - break; - - case STATUS.PROGRESS: - stats.numOfProgress++; - break; - - case STATUS.ERROR: - stats.numOfUploadFailed++; - break; - - case STATUS.COMPLETE: - stats.numOfSuccess++; - break; - - case STATUS.CANCELLED: - stats.numOfCancel++; - break; - - - case STATUS.INVALID: - stats.numOfInvalid++; - break; - - case STATUS.INTERRUPT: - stats.numofInterrupt++; - break; - } - } - - }); - - Mediator.installTo( Queue.prototype ); - - return Queue; - }); - /** - * @fileOverview 队列 - */ - define('widgets/queue',[ - 'base', - 'uploader', - 'queue', - 'file', - 'lib/file', - 'runtime/client', - 'widgets/widget' - ], function( Base, Uploader, Queue, WUFile, File, RuntimeClient ) { - - var $ = Base.$, - rExt = /\.\w+$/, - Status = WUFile.Status; - - return Uploader.register({ - name: 'queue', - - init: function( opts ) { - var me = this, - deferred, len, i, item, arr, accept, runtime; - - if ( $.isPlainObject( opts.accept ) ) { - opts.accept = [ opts.accept ]; - } - - // accept中的中生成匹配正则。 - if ( opts.accept ) { - arr = []; - - for ( i = 0, len = opts.accept.length; i < len; i++ ) { - item = opts.accept[ i ].extensions; - item && arr.push( item ); - } - - if ( arr.length ) { - accept = '\\.' + arr.join(',') - .replace( /,/g, '$|\\.' ) - .replace( /\*/g, '.*' ) + '$'; - } - - me.accept = new RegExp( accept, 'i' ); - } - - me.queue = new Queue(); - me.stats = me.queue.stats; - - // 如果当前不是html5运行时,那就算了。 - // 不执行后续操作 - if ( this.request('predict-runtime-type') !== 'html5' ) { - return; - } - - // 创建一个 html5 运行时的 placeholder - // 以至于外部添加原生 File 对象的时候能正确包裹一下供 webuploader 使用。 - deferred = Base.Deferred(); - this.placeholder = runtime = new RuntimeClient('Placeholder'); - runtime.connectRuntime({ - runtimeOrder: 'html5' - }, function() { - me._ruid = runtime.getRuid(); - deferred.resolve(); - }); - return deferred.promise(); - }, - - - // 为了支持外部直接添加一个原生File对象。 - _wrapFile: function( file ) { - if ( !(file instanceof WUFile) ) { - - if ( !(file instanceof File) ) { - if ( !this._ruid ) { - throw new Error('Can\'t add external files.'); - } - file = new File( this._ruid, file ); - } - - file = new WUFile( file ); - } - - return file; - }, - - // 判断文件是否可以被加入队列 - acceptFile: function( file ) { - var invalid = !file || !file.size || this.accept && - - // 如果名字中有后缀,才做后缀白名单处理。 - rExt.exec( file.name ) && !this.accept.test( file.name ); - - return !invalid; - }, - - - /** - * @event beforeFileQueued - * @param {File} file File对象 - * @description 当文件被加入队列之前触发,此事件的handler返回值为`false`,则此文件不会被添加进入队列。 - * @for Uploader - */ - - /** - * @event fileQueued - * @param {File} file File对象 - * @description 当文件被加入队列以后触发。 - * @for Uploader - */ - - _addFile: function( file ) { - var me = this; - - file = me._wrapFile( file ); - - // 不过类型判断允许不允许,先派送 `beforeFileQueued` - if ( !me.owner.trigger( 'beforeFileQueued', file ) ) { - return; - } - - // 类型不匹配,则派送错误事件,并返回。 - if ( !me.acceptFile( file ) ) { - me.owner.trigger( 'error', 'Q_TYPE_DENIED', file ); - return; - } - - me.queue.append( file ); - me.owner.trigger( 'fileQueued', file ); - return file; - }, - - getFile: function( fileId ) { - return this.queue.getFile( fileId ); - }, - - /** - * @event filesQueued - * @param {File} files 数组,内容为原始File(lib/File)对象。 - * @description 当一批文件添加进队列以后触发。 - * @for Uploader - */ - - /** - * @property {Boolean} [auto=false] - * @namespace options - * @for Uploader - * @description 设置为 true 后,不需要手动调用上传,有文件选择即开始上传。 - * - */ - - /** - * @method addFiles - * @grammar addFiles( file ) => undefined - * @grammar addFiles( [file1, file2 ...] ) => undefined - * @param {Array of File or File} [files] Files 对象 数组 - * @description 添加文件到队列 - * @for Uploader - */ - addFile: function( files ) { - var me = this; - - if ( !files.length ) { - files = [ files ]; - } - - files = $.map( files, function( file ) { - return me._addFile( file ); - }); - - me.owner.trigger( 'filesQueued', files ); - - if ( me.options.auto ) { - setTimeout(function() { - me.request('start-upload'); - }, 20 ); - } - }, - - getStats: function() { - return this.stats; - }, - - /** - * @event fileDequeued - * @param {File} file File对象 - * @description 当文件被移除队列后触发。 - * @for Uploader - */ - - /** - * @method removeFile - * @grammar removeFile( file ) => undefined - * @grammar removeFile( id ) => undefined - * @grammar removeFile( file, true ) => undefined - * @grammar removeFile( id, true ) => undefined - * @param {File|id} file File对象或这File对象的id - * @description 移除某一文件, 默认只会标记文件状态为已取消,如果第二个参数为 `true` 则会从 queue 中移除。 - * @for Uploader - * @example - * - * $li.on('click', '.remove-this', function() { - * uploader.removeFile( file ); - * }) - */ - removeFile: function( file, remove ) { - var me = this; - - file = file.id ? file : me.queue.getFile( file ); - - this.request( 'cancel-file', file ); - - if ( remove ) { - this.queue.removeFile( file ); - } - }, - - /** - * @method getFiles - * @grammar getFiles() => Array - * @grammar getFiles( status1, status2, status... ) => Array - * @description 返回指定状态的文件集合,不传参数将返回所有状态的文件。 - * @for Uploader - * @example - * console.log( uploader.getFiles() ); // => all files - * console.log( uploader.getFiles('error') ) // => all error files. - */ - getFiles: function() { - return this.queue.getFiles.apply( this.queue, arguments ); - }, - - fetchFile: function() { - return this.queue.fetch.apply( this.queue, arguments ); - }, - - /** - * @method retry - * @grammar retry() => undefined - * @grammar retry( file ) => undefined - * @description 重试上传,重试指定文件,或者从出错的文件开始重新上传。 - * @for Uploader - * @example - * function retry() { - * uploader.retry(); - * } - */ - retry: function( file, noForceStart ) { - var me = this, - files, i, len; - - if ( file ) { - file = file.id ? file : me.queue.getFile( file ); - file.setStatus( Status.QUEUED ); - noForceStart || me.request('start-upload'); - return; - } - - files = me.queue.getFiles( Status.ERROR ); - i = 0; - len = files.length; - - for ( ; i < len; i++ ) { - file = files[ i ]; - file.setStatus( Status.QUEUED ); - } - - me.request('start-upload'); - }, - - /** - * @method sort - * @grammar sort( fn ) => undefined - * @description 排序队列中的文件,在上传之前调整可以控制上传顺序。 - * @for Uploader - */ - sortFiles: function() { - return this.queue.sort.apply( this.queue, arguments ); - }, - - /** - * @event reset - * @description 当 uploader 被重置的时候触发。 - * @for Uploader - */ - - /** - * @method reset - * @grammar reset() => undefined - * @description 重置uploader。目前只重置了队列。 - * @for Uploader - * @example - * uploader.reset(); - */ - reset: function() { - this.owner.trigger('reset'); - this.queue = new Queue(); - this.stats = this.queue.stats; - }, - - destroy: function() { - this.reset(); - this.placeholder && this.placeholder.destroy(); - } - }); - - }); - /** - * @fileOverview 添加获取Runtime相关信息的方法。 - */ - define('widgets/runtime',[ - 'uploader', - 'runtime/runtime', - 'widgets/widget' - ], function( Uploader, Runtime ) { - - Uploader.support = function() { - return Runtime.hasRuntime.apply( Runtime, arguments ); - }; - - /** - * @property {Object} [runtimeOrder=html5,flash] - * @namespace options - * @for Uploader - * @description 指定运行时启动顺序。默认会想尝试 html5 是否支持,如果支持则使用 html5, 否则则使用 flash. - * - * 可以将此值设置成 `flash`,来强制使用 flash 运行时。 - */ - - return Uploader.register({ - name: 'runtime', - - init: function() { - if ( !this.predictRuntimeType() ) { - throw Error('Runtime Error'); - } - }, - - /** - * 预测Uploader将采用哪个`Runtime` - * @grammar predictRuntimeType() => String - * @method predictRuntimeType - * @for Uploader - */ - predictRuntimeType: function() { - var orders = this.options.runtimeOrder || Runtime.orders, - type = this.type, - i, len; - - if ( !type ) { - orders = orders.split( /\s*,\s*/g ); - - for ( i = 0, len = orders.length; i < len; i++ ) { - if ( Runtime.hasRuntime( orders[ i ] ) ) { - this.type = type = orders[ i ]; - break; - } - } - } - - return type; - } - }); - }); - /** - * @fileOverview Transport - */ - define('lib/transport',[ - 'base', - 'runtime/client', - 'mediator' - ], function( Base, RuntimeClient, Mediator ) { - - var $ = Base.$; - - function Transport( opts ) { - var me = this; - - opts = me.options = $.extend( true, {}, Transport.options, opts || {} ); - RuntimeClient.call( this, 'Transport' ); - - this._blob = null; - this._formData = opts.formData || {}; - this._headers = opts.headers || {}; - - this.on( 'progress', this._timeout ); - this.on( 'load error', function() { - me.trigger( 'progress', 1 ); - clearTimeout( me._timer ); - }); - } - - Transport.options = { - server: '', - method: 'POST', - - // 跨域时,是否允许携带cookie, 只有html5 runtime才有效 - withCredentials: false, - fileVal: 'file', - timeout: 2 * 60 * 1000, // 2分钟 - formData: {}, - headers: {}, - sendAsBinary: false - }; - - $.extend( Transport.prototype, { - - // 添加Blob, 只能添加一次,最后一次有效。 - appendBlob: function( key, blob, filename ) { - var me = this, - opts = me.options; - - if ( me.getRuid() ) { - me.disconnectRuntime(); - } - - // 连接到blob归属的同一个runtime. - me.connectRuntime( blob.ruid, function() { - me.exec('init'); - }); - - me._blob = blob; - opts.fileVal = key || opts.fileVal; - opts.filename = filename || opts.filename; - }, - - // 添加其他字段 - append: function( key, value ) { - if ( typeof key === 'object' ) { - $.extend( this._formData, key ); - } else { - this._formData[ key ] = value; - } - }, - - setRequestHeader: function( key, value ) { - if ( typeof key === 'object' ) { - $.extend( this._headers, key ); - } else { - this._headers[ key ] = value; - } - }, - - send: function( method ) { - this.exec( 'send', method ); - this._timeout(); - }, - - abort: function() { - clearTimeout( this._timer ); - return this.exec('abort'); - }, - - destroy: function() { - this.trigger('destroy'); - this.off(); - this.exec('destroy'); - this.disconnectRuntime(); - }, - - getResponse: function() { - return this.exec('getResponse'); - }, - - getResponseAsJson: function() { - return this.exec('getResponseAsJson'); - }, - - getStatus: function() { - return this.exec('getStatus'); - }, - - _timeout: function() { - var me = this, - duration = me.options.timeout; - - if ( !duration ) { - return; - } - - clearTimeout( me._timer ); - me._timer = setTimeout(function() { - me.abort(); - me.trigger( 'error', 'timeout' ); - }, duration ); - } - - }); - - // 让Transport具备事件功能。 - Mediator.installTo( Transport.prototype ); - - return Transport; - }); - /** - * @fileOverview 负责文件上传相关。 - */ - define('widgets/upload',[ - 'base', - 'uploader', - 'file', - 'lib/transport', - 'widgets/widget' - ], function( Base, Uploader, WUFile, Transport ) { - - var $ = Base.$, - isPromise = Base.isPromise, - Status = WUFile.Status; - - // 添加默认配置项 - $.extend( Uploader.options, { - - - /** - * @property {Boolean} [prepareNextFile=false] - * @namespace options - * @for Uploader - * @description 是否允许在文件传输时提前把下一个文件准备好。 - * 对于一个文件的准备工作比较耗时,比如图片压缩,md5序列化。 - * 如果能提前在当前文件传输期处理,可以节省总体耗时。 - */ - prepareNextFile: false, - - /** - * @property {Boolean} [chunked=false] - * @namespace options - * @for Uploader - * @description 是否要分片处理大文件上传。 - */ - chunked: false, - - /** - * @property {Boolean} [chunkSize=5242880] - * @namespace options - * @for Uploader - * @description 如果要分片,分多大一片? 默认大小为5M. - */ - chunkSize: 5 * 1024 * 1024, - - /** - * @property {Boolean} [chunkRetry=2] - * @namespace options - * @for Uploader - * @description 如果某个分片由于网络问题出错,允许自动重传多少次? - */ - chunkRetry: 2, - - /** - * @property {Boolean} [threads=3] - * @namespace options - * @for Uploader - * @description 上传并发数。允许同时最大上传进程数。 - */ - threads: 3, - - - /** - * @property {Object} [formData={}] - * @namespace options - * @for Uploader - * @description 文件上传请求的参数表,每次发送都会发送此对象中的参数。 - */ - formData: {} - - /** - * @property {Object} [fileVal='file'] - * @namespace options - * @for Uploader - * @description 设置文件上传域的name。 - */ - - /** - * @property {Object} [method='POST'] - * @namespace options - * @for Uploader - * @description 文件上传方式,`POST`或者`GET`。 - */ - - /** - * @property {Object} [sendAsBinary=false] - * @namespace options - * @for Uploader - * @description 是否已二进制的流的方式发送文件,这样整个上传内容`php://input`都为文件内容, - * 其他参数在$_GET数组中。 - */ - }); - - // 负责将文件切片。 - function CuteFile( file, chunkSize ) { - var pending = [], - blob = file.source, - total = blob.size, - chunks = chunkSize ? Math.ceil( total / chunkSize ) : 1, - start = 0, - index = 0, - len, api; - - api = { - file: file, - - has: function() { - return !!pending.length; - }, - - shift: function() { - return pending.shift(); - }, - - unshift: function( block ) { - pending.unshift( block ); - } - }; - - while ( index < chunks ) { - len = Math.min( chunkSize, total - start ); - - pending.push({ - file: file, - start: start, - end: chunkSize ? (start + len) : total, - total: total, - chunks: chunks, - chunk: index++, - cuted: api - }); - start += len; - } - - file.blocks = pending.concat(); - file.remaning = pending.length; - - return api; - } - - Uploader.register({ - name: 'upload', - - init: function() { - var owner = this.owner, - me = this; - - this.runing = false; - this.progress = false; - - owner - .on( 'startUpload', function() { - me.progress = true; - }) - .on( 'uploadFinished', function() { - me.progress = false; - }); - - // 记录当前正在传的数据,跟threads相关 - this.pool = []; - - // 缓存分好片的文件。 - this.stack = []; - - // 缓存即将上传的文件。 - this.pending = []; - - // 跟踪还有多少分片在上传中但是没有完成上传。 - this.remaning = 0; - this.__tick = Base.bindFn( this._tick, this ); - - owner.on( 'uploadComplete', function( file ) { - - // 把其他块取消了。 - file.blocks && $.each( file.blocks, function( _, v ) { - v.transport && (v.transport.abort(), v.transport.destroy()); - delete v.transport; - }); - - delete file.blocks; - delete file.remaning; - }); - }, - - reset: function() { - this.request( 'stop-upload', true ); - this.runing = false; - this.pool = []; - this.stack = []; - this.pending = []; - this.remaning = 0; - this._trigged = false; - this._promise = null; - }, - - /** - * @event startUpload - * @description 当开始上传流程时触发。 - * @for Uploader - */ - - /** - * 开始上传。此方法可以从初始状态调用开始上传流程,也可以从暂停状态调用,继续上传流程。 - * - * 可以指定开始某一个文件。 - * @grammar upload() => undefined - * @grammar upload( file | fileId) => undefined - * @method upload - * @for Uploader - */ - startUpload: function(file) { - var me = this; - - // 移出invalid的文件 - $.each( me.request( 'get-files', Status.INVALID ), function() { - me.request( 'remove-file', this ); - }); - - // 如果指定了开始某个文件,则只开始指定文件。 - if ( file ) { - file = file.id ? file : me.request( 'get-file', file ); - - if (file.getStatus() === Status.INTERRUPT) { - $.each( me.pool, function( _, v ) { - - // 之前暂停过。 - if (v.file !== file) { - return; - } - - v.transport && v.transport.send(); - }); - - file.setStatus( Status.QUEUED ); - } else if (file.getStatus() === Status.PROGRESS) { - return; - } else { - file.setStatus( Status.QUEUED ); - } - } else { - $.each( me.request( 'get-files', [ Status.INITED ] ), function() { - this.setStatus( Status.QUEUED ); - }); - } - - if ( me.runing ) { - return; - } - - me.runing = true; - - var files = []; - - // 如果有暂停的,则续传 - $.each( me.pool, function( _, v ) { - var file = v.file; - - if ( file.getStatus() === Status.INTERRUPT ) { - files.push(file); - me._trigged = false; - v.transport && v.transport.send(); - } - }); - - var file; - while ( (file = files.shift()) ) { - file.setStatus( Status.PROGRESS ); - } - - file || $.each( me.request( 'get-files', - Status.INTERRUPT ), function() { - this.setStatus( Status.PROGRESS ); - }); - - me._trigged = false; - Base.nextTick( me.__tick ); - me.owner.trigger('startUpload'); - }, - - /** - * @event stopUpload - * @description 当开始上传流程暂停时触发。 - * @for Uploader - */ - - /** - * 暂停上传。第一个参数为是否中断上传当前正在上传的文件。 - * - * 如果第一个参数是文件,则只暂停指定文件。 - * @grammar stop() => undefined - * @grammar stop( true ) => undefined - * @grammar stop( file ) => undefined - * @method stop - * @for Uploader - */ - stopUpload: function( file, interrupt ) { - var me = this; - - if (file === true) { - interrupt = file; - file = null; - } - - if ( me.runing === false ) { - return; - } - - // 如果只是暂停某个文件。 - if ( file ) { - file = file.id ? file : me.request( 'get-file', file ); - - if ( file.getStatus() !== Status.PROGRESS && - file.getStatus() !== Status.QUEUED ) { - return; - } - - file.setStatus( Status.INTERRUPT ); - $.each( me.pool, function( _, v ) { - - // 只 abort 指定的文件。 - if (v.file !== file) { - return; - } - - v.transport && v.transport.abort(); - me._putback(v); - me._popBlock(v); - }); - - return Base.nextTick( me.__tick ); - } - - me.runing = false; - - if (this._promise && this._promise.file) { - this._promise.file.setStatus( Status.INTERRUPT ); - } - - interrupt && $.each( me.pool, function( _, v ) { - v.transport && v.transport.abort(); - v.file.setStatus( Status.INTERRUPT ); - }); - - me.owner.trigger('stopUpload'); - }, - - /** - * @method cancelFile - * @grammar cancelFile( file ) => undefined - * @grammar cancelFile( id ) => undefined - * @param {File|id} file File对象或这File对象的id - * @description 标记文件状态为已取消, 同时将中断文件传输。 - * @for Uploader - * @example - * - * $li.on('click', '.remove-this', function() { - * uploader.cancelFile( file ); - * }) - */ - cancelFile: function( file ) { - file = file.id ? file : this.request( 'get-file', file ); - - // 如果正在上传。 - file.blocks && $.each( file.blocks, function( _, v ) { - var _tr = v.transport; - - if ( _tr ) { - _tr.abort(); - _tr.destroy(); - delete v.transport; - } - }); - - file.setStatus( Status.CANCELLED ); - this.owner.trigger( 'fileDequeued', file ); - }, - - /** - * 判断`Uplaode`r是否正在上传中。 - * @grammar isInProgress() => Boolean - * @method isInProgress - * @for Uploader - */ - isInProgress: function() { - return !!this.progress; - }, - - _getStats: function() { - return this.request('get-stats'); - }, - - /** - * 掉过一个文件上传,直接标记指定文件为已上传状态。 - * @grammar skipFile( file ) => undefined - * @method skipFile - * @for Uploader - */ - skipFile: function( file, status ) { - file = file.id ? file : this.request( 'get-file', file ); - - file.setStatus( status || Status.COMPLETE ); - file.skipped = true; - - // 如果正在上传。 - file.blocks && $.each( file.blocks, function( _, v ) { - var _tr = v.transport; - - if ( _tr ) { - _tr.abort(); - _tr.destroy(); - delete v.transport; - } - }); - - this.owner.trigger( 'uploadSkip', file ); - }, - - /** - * @event uploadFinished - * @description 当所有文件上传结束时触发。 - * @for Uploader - */ - _tick: function() { - var me = this, - opts = me.options, - fn, val; - - // 上一个promise还没有结束,则等待完成后再执行。 - if ( me._promise ) { - return me._promise.always( me.__tick ); - } - - // 还有位置,且还有文件要处理的话。 - if ( me.pool.length < opts.threads && (val = me._nextBlock()) ) { - me._trigged = false; - - fn = function( val ) { - me._promise = null; - - // 有可能是reject过来的,所以要检测val的类型。 - val && val.file && me._startSend( val ); - Base.nextTick( me.__tick ); - }; - - me._promise = isPromise( val ) ? val.always( fn ) : fn( val ); - - // 没有要上传的了,且没有正在传输的了。 - } else if ( !me.remaning && !me._getStats().numOfQueue && - !me._getStats().numofInterrupt ) { - me.runing = false; - - me._trigged || Base.nextTick(function() { - me.owner.trigger('uploadFinished'); - }); - me._trigged = true; - } - }, - - _putback: function(block) { - var idx; - - block.cuted.unshift(block); - idx = this.stack.indexOf(block.cuted); - - if (!~idx) { - this.stack.unshift(block.cuted); - } - }, - - _getStack: function() { - var i = 0, - act; - - while ( (act = this.stack[ i++ ]) ) { - if ( act.has() && act.file.getStatus() === Status.PROGRESS ) { - return act; - } else if (!act.has() || - act.file.getStatus() !== Status.PROGRESS && - act.file.getStatus() !== Status.INTERRUPT ) { - - // 把已经处理完了的,或者,状态为非 progress(上传中)、 - // interupt(暂停中) 的移除。 - this.stack.splice( --i, 1 ); - } - } - - return null; - }, - - _nextBlock: function() { - var me = this, - opts = me.options, - act, next, done, preparing; - - // 如果当前文件还有没有需要传输的,则直接返回剩下的。 - if ( (act = this._getStack()) ) { - - // 是否提前准备下一个文件 - if ( opts.prepareNextFile && !me.pending.length ) { - me._prepareNextFile(); - } - - return act.shift(); - - // 否则,如果正在运行,则准备下一个文件,并等待完成后返回下个分片。 - } else if ( me.runing ) { - - // 如果缓存中有,则直接在缓存中取,没有则去queue中取。 - if ( !me.pending.length && me._getStats().numOfQueue ) { - me._prepareNextFile(); - } - - next = me.pending.shift(); - done = function( file ) { - if ( !file ) { - return null; - } - - act = CuteFile( file, opts.chunked ? opts.chunkSize : 0 ); - me.stack.push(act); - return act.shift(); - }; - - // 文件可能还在prepare中,也有可能已经完全准备好了。 - if ( isPromise( next) ) { - preparing = next.file; - next = next[ next.pipe ? 'pipe' : 'then' ]( done ); - next.file = preparing; - return next; - } - - return done( next ); - } - }, - - - /** - * @event uploadStart - * @param {File} file File对象 - * @description 某个文件开始上传前触发,一个文件只会触发一次。 - * @for Uploader - */ - _prepareNextFile: function() { - var me = this, - file = me.request('fetch-file'), - pending = me.pending, - promise; - - if ( file ) { - promise = me.request( 'before-send-file', file, function() { - - // 有可能文件被skip掉了。文件被skip掉后,状态坑定不是Queued. - if ( file.getStatus() === Status.PROGRESS || - file.getStatus() === Status.INTERRUPT ) { - return file; - } - - return me._finishFile( file ); - }); - - me.owner.trigger( 'uploadStart', file ); - file.setStatus( Status.PROGRESS ); - - promise.file = file; - - // 如果还在pending中,则替换成文件本身。 - promise.done(function() { - var idx = $.inArray( promise, pending ); - - ~idx && pending.splice( idx, 1, file ); - }); - - // befeore-send-file的钩子就有错误发生。 - promise.fail(function( reason ) { - file.setStatus( Status.ERROR, reason ); - me.owner.trigger( 'uploadError', file, reason ); - me.owner.trigger( 'uploadComplete', file ); - }); - - pending.push( promise ); - } - }, - - // 让出位置了,可以让其他分片开始上传 - _popBlock: function( block ) { - var idx = $.inArray( block, this.pool ); - - this.pool.splice( idx, 1 ); - block.file.remaning--; - this.remaning--; - }, - - // 开始上传,可以被掉过。如果promise被reject了,则表示跳过此分片。 - _startSend: function( block ) { - var me = this, - file = block.file, - promise; - - // 有可能在 before-send-file 的 promise 期间改变了文件状态。 - // 如:暂停,取消 - // 我们不能中断 promise, 但是可以在 promise 完后,不做上传操作。 - if ( file.getStatus() !== Status.PROGRESS ) { - - // 如果是中断,则还需要放回去。 - if (file.getStatus() === Status.INTERRUPT) { - me._putback(block); - } - - return; - } - - me.pool.push( block ); - me.remaning++; - - // 如果没有分片,则直接使用原始的。 - // 不会丢失content-type信息。 - block.blob = block.chunks === 1 ? file.source : - file.source.slice( block.start, block.end ); - - // hook, 每个分片发送之前可能要做些异步的事情。 - promise = me.request( 'before-send', block, function() { - - // 有可能文件已经上传出错了,所以不需要再传输了。 - if ( file.getStatus() === Status.PROGRESS ) { - me._doSend( block ); - } else { - me._popBlock( block ); - Base.nextTick( me.__tick ); - } - }); - - // 如果为fail了,则跳过此分片。 - promise.fail(function() { - if ( file.remaning === 1 ) { - me._finishFile( file ).always(function() { - block.percentage = 1; - me._popBlock( block ); - me.owner.trigger( 'uploadComplete', file ); - Base.nextTick( me.__tick ); - }); - } else { - block.percentage = 1; - me.updateFileProgress( file ); - me._popBlock( block ); - Base.nextTick( me.__tick ); - } - }); - }, - - - /** - * @event uploadBeforeSend - * @param {Object} object - * @param {Object} data 默认的上传参数,可以扩展此对象来控制上传参数。 - * @param {Object} headers 可以扩展此对象来控制上传头部。 - * @description 当某个文件的分块在发送前触发,主要用来询问是否要添加附带参数,大文件在开起分片上传的前提下此事件可能会触发多次。 - * @for Uploader - */ - - /** - * @event uploadAccept - * @param {Object} object - * @param {Object} ret 服务端的返回数据,json格式,如果服务端不是json格式,从ret._raw中取数据,自行解析。 - * @description 当某个文件上传到服务端响应后,会派送此事件来询问服务端响应是否有效。如果此事件handler返回值为`false`, 则此文件将派送`server`类型的`uploadError`事件。 - * @for Uploader - */ - - /** - * @event uploadProgress - * @param {File} file File对象 - * @param {Number} percentage 上传进度 - * @description 上传过程中触发,携带上传进度。 - * @for Uploader - */ - - - /** - * @event uploadError - * @param {File} file File对象 - * @param {String} reason 出错的code - * @description 当文件上传出错时触发。 - * @for Uploader - */ - - /** - * @event uploadSuccess - * @param {File} file File对象 - * @param {Object} response 服务端返回的数据 - * @description 当文件上传成功时触发。 - * @for Uploader - */ - - /** - * @event uploadComplete - * @param {File} [file] File对象 - * @description 不管成功或者失败,文件上传完成时触发。 - * @for Uploader - */ - - // 做上传操作。 - _doSend: function( block ) { - var me = this, - owner = me.owner, - opts = me.options, - file = block.file, - tr = new Transport( opts ), - data = $.extend({}, opts.formData ), - headers = $.extend({}, opts.headers ), - requestAccept, ret; - - block.transport = tr; - - tr.on( 'destroy', function() { - delete block.transport; - me._popBlock( block ); - Base.nextTick( me.__tick ); - }); - - // 广播上传进度。以文件为单位。 - tr.on( 'progress', function( percentage ) { - block.percentage = percentage; - me.updateFileProgress( file ); - }); - - // 用来询问,是否返回的结果是有错误的。 - requestAccept = function( reject ) { - var fn; - - ret = tr.getResponseAsJson() || {}; - ret._raw = tr.getResponse(); - fn = function( value ) { - reject = value; - }; - - // 服务端响应了,不代表成功了,询问是否响应正确。 - if ( !owner.trigger( 'uploadAccept', block, ret, fn ) ) { - reject = reject || 'server'; - } - - return reject; - }; - - // 尝试重试,然后广播文件上传出错。 - tr.on( 'error', function( type, flag ) { - block.retried = block.retried || 0; - - // 自动重试 - if ( block.chunks > 1 && ~'http,abort'.indexOf( type ) && - block.retried < opts.chunkRetry ) { - - block.retried++; - tr.send(); - - } else { - - // http status 500 ~ 600 - if ( !flag && type === 'server' ) { - type = requestAccept( type ); - } - - file.setStatus( Status.ERROR, type ); - owner.trigger( 'uploadError', file, type ); - owner.trigger( 'uploadComplete', file ); - } - }); - - // 上传成功 - tr.on( 'load', function() { - var reason; - - // 如果非预期,转向上传出错。 - if ( (reason = requestAccept()) ) { - tr.trigger( 'error', reason, true ); - return; - } - - // 全部上传完成。 - if ( file.remaning === 1 ) { - me._finishFile( file, ret ); - } else { - tr.destroy(); - } - }); - - // 配置默认的上传字段。 - data = $.extend( data, { - id: file.id, - name: file.name, - type: file.type, - lastModifiedDate: file.lastModifiedDate, - size: file.size - }); - - block.chunks > 1 && $.extend( data, { - chunks: block.chunks, - chunk: block.chunk - }); - - // 在发送之间可以添加字段什么的。。。 - // 如果默认的字段不够使用,可以通过监听此事件来扩展 - owner.trigger( 'uploadBeforeSend', block, data, headers ); - - // 开始发送。 - tr.appendBlob( opts.fileVal, block.blob, file.name ); - tr.append( data ); - tr.setRequestHeader( headers ); - tr.send(); - }, - - // 完成上传。 - _finishFile: function( file, ret, hds ) { - var owner = this.owner; - - return owner - .request( 'after-send-file', arguments, function() { - file.setStatus( Status.COMPLETE ); - owner.trigger( 'uploadSuccess', file, ret, hds ); - }) - .fail(function( reason ) { - - // 如果外部已经标记为invalid什么的,不再改状态。 - if ( file.getStatus() === Status.PROGRESS ) { - file.setStatus( Status.ERROR, reason ); - } - - owner.trigger( 'uploadError', file, reason ); - }) - .always(function() { - owner.trigger( 'uploadComplete', file ); - }); - }, - - updateFileProgress: function(file) { - var totalPercent = 0, - uploaded = 0; - - if (!file.blocks) { - return; - } - - $.each( file.blocks, function( _, v ) { - uploaded += (v.percentage || 0) * (v.end - v.start); - }); - - totalPercent = uploaded / file.size; - this.owner.trigger( 'uploadProgress', file, totalPercent || 0 ); - } - - }); - }); - /** - * @fileOverview 日志组件,主要用来收集错误信息,可以帮助 webuploader 更好的定位问题和发展。 - * - * 如果您不想要启用此功能,请在打包的时候去掉 log 模块。 - * - * 或者可以在初始化的时候通过 options.disableWidgets 属性禁用。 - * - * 如: - * WebUploader.create({ - * ... - * - * disableWidgets: 'log', - * - * ... - * }) - */ - define('widgets/log',[ - 'base', - 'uploader', - 'widgets/widget' - ], function( Base, Uploader ) { - var $ = Base.$, - logUrl = ' http://static.tieba.baidu.com/tb/pms/img/st.gif??', - product = (location.hostname || location.host || 'protected').toLowerCase(), - - // 只针对 baidu 内部产品用户做统计功能。 - enable = product && /baidu/i.exec(product), - base; - - if (!enable) { - return; - } - - base = { - dv: 3, - master: 'webuploader', - online: /test/.exec(product) ? 0 : 1, - module: '', - product: product, - type: 0 - }; - - function send(data) { - var obj = $.extend({}, base, data), - url = logUrl.replace(/^(.*)\?/, '$1' + $.param( obj )), - image = new Image(); - - image.src = url; - } - - return Uploader.register({ - name: 'log', - - init: function() { - var owner = this.owner, - count = 0, - size = 0; - - owner - .on('error', function(code) { - send({ - type: 2, - c_error_code: code - }); - }) - .on('uploadError', function(file, reason) { - send({ - type: 2, - c_error_code: 'UPLOAD_ERROR', - c_reason: '' + reason - }); - }) - .on('uploadComplete', function(file) { - count++; - size += file.size; - }). - on('uploadFinished', function() { - send({ - c_count: count, - c_size: size - }); - count = size = 0; - }); - - send({ - c_usage: 1 - }); - } - }); - }); - /** - * @fileOverview Runtime管理器,负责Runtime的选择, 连接 - */ - define('runtime/compbase',[],function() { - - function CompBase( owner, runtime ) { - - this.owner = owner; - this.options = owner.options; - - this.getRuntime = function() { - return runtime; - }; - - this.getRuid = function() { - return runtime.uid; - }; - - this.trigger = function() { - return owner.trigger.apply( owner, arguments ); - }; - } - - return CompBase; - }); - /** - * @fileOverview Html5Runtime - */ - define('runtime/html5/runtime',[ - 'base', - 'runtime/runtime', - 'runtime/compbase' - ], function( Base, Runtime, CompBase ) { - - var type = 'html5', - components = {}; - - function Html5Runtime() { - var pool = {}, - me = this, - destroy = this.destroy; - - Runtime.apply( me, arguments ); - me.type = type; - - - // 这个方法的调用者,实际上是RuntimeClient - me.exec = function( comp, fn/*, args...*/) { - var client = this, - uid = client.uid, - args = Base.slice( arguments, 2 ), - instance; - - if ( components[ comp ] ) { - instance = pool[ uid ] = pool[ uid ] || - new components[ comp ]( client, me ); - - if ( instance[ fn ] ) { - return instance[ fn ].apply( instance, args ); - } - } - }; - - me.destroy = function() { - // @todo 删除池子中的所有实例 - return destroy && destroy.apply( this, arguments ); - }; - } - - Base.inherits( Runtime, { - constructor: Html5Runtime, - - // 不需要连接其他程序,直接执行callback - init: function() { - var me = this; - setTimeout(function() { - me.trigger('ready'); - }, 1 ); - } - - }); - - // 注册Components - Html5Runtime.register = function( name, component ) { - var klass = components[ name ] = Base.inherits( CompBase, component ); - return klass; - }; - - // 注册html5运行时。 - // 只有在支持的前提下注册。 - if ( window.Blob && window.FileReader && window.DataView ) { - Runtime.addRuntime( type, Html5Runtime ); - } - - return Html5Runtime; - }); - /** - * @fileOverview Blob Html实现 - */ - define('runtime/html5/blob',[ - 'runtime/html5/runtime', - 'lib/blob' - ], function( Html5Runtime, Blob ) { - - return Html5Runtime.register( 'Blob', { - slice: function( start, end ) { - var blob = this.owner.source, - slice = blob.slice || blob.webkitSlice || blob.mozSlice; - - blob = slice.call( blob, start, end ); - - return new Blob( this.getRuid(), blob ); - } - }); - }); - /** - * @fileOverview FilePicker - */ - define('runtime/html5/filepicker',[ - 'base', - 'runtime/html5/runtime' - ], function( Base, Html5Runtime ) { - - var $ = Base.$; - - return Html5Runtime.register( 'FilePicker', { - init: function() { - var container = this.getRuntime().getContainer(), - me = this, - owner = me.owner, - opts = me.options, - label = this.label = $( document.createElement('label') ), - input = this.input = $( document.createElement('input') ), - arr, i, len, mouseHandler; - - input.attr( 'type', 'file' ); - input.attr( 'name', opts.name ); - input.addClass('webuploader-element-invisible'); - - label.on( 'click', function() { - input.trigger('click'); - }); - - label.css({ - opacity: 0, - width: '100%', - height: '100%', - display: 'block', - cursor: 'pointer', - background: '#ffffff' - }); - - if ( opts.multiple ) { - input.attr( 'multiple', 'multiple' ); - } - - // @todo Firefox不支持单独指定后缀 - if ( opts.accept && opts.accept.length > 0 ) { - arr = []; - - for ( i = 0, len = opts.accept.length; i < len; i++ ) { - arr.push( opts.accept[ i ].mimeTypes ); - } - - input.attr( 'accept', arr.join(',') ); - } - - container.append( input ); - container.append( label ); - - mouseHandler = function( e ) { - owner.trigger( e.type ); - }; - - input.on( 'change', function( e ) { - var fn = arguments.callee, - clone; - - me.files = e.target.files; - - // reset input - clone = this.cloneNode( true ); - clone.value = null; - this.parentNode.replaceChild( clone, this ); - - input.off(); - input = $( clone ).on( 'change', fn ) - .on( 'mouseenter mouseleave', mouseHandler ); - - owner.trigger('change'); - }); - - label.on( 'mouseenter mouseleave', mouseHandler ); - - }, - - - getFiles: function() { - return this.files; - }, - - destroy: function() { - this.input.off(); - this.label.off(); - } - }); - }); - /** - * Terms: - * - * Uint8Array, FileReader, BlobBuilder, atob, ArrayBuffer - * @fileOverview Image控件 - */ - define('runtime/html5/util',[ - 'base' - ], function( Base ) { - - var urlAPI = window.createObjectURL && window || - window.URL && URL.revokeObjectURL && URL || - window.webkitURL, - createObjectURL = Base.noop, - revokeObjectURL = createObjectURL; - - if ( urlAPI ) { - - // 更安全的方式调用,比如android里面就能把context改成其他的对象。 - createObjectURL = function() { - return urlAPI.createObjectURL.apply( urlAPI, arguments ); - }; - - revokeObjectURL = function() { - return urlAPI.revokeObjectURL.apply( urlAPI, arguments ); - }; - } - - return { - createObjectURL: createObjectURL, - revokeObjectURL: revokeObjectURL, - - dataURL2Blob: function( dataURI ) { - var byteStr, intArray, ab, i, mimetype, parts; - - parts = dataURI.split(','); - - if ( ~parts[ 0 ].indexOf('base64') ) { - byteStr = atob( parts[ 1 ] ); - } else { - byteStr = decodeURIComponent( parts[ 1 ] ); - } - - ab = new ArrayBuffer( byteStr.length ); - intArray = new Uint8Array( ab ); - - for ( i = 0; i < byteStr.length; i++ ) { - intArray[ i ] = byteStr.charCodeAt( i ); - } - - mimetype = parts[ 0 ].split(':')[ 1 ].split(';')[ 0 ]; - - return this.arrayBufferToBlob( ab, mimetype ); - }, - - dataURL2ArrayBuffer: function( dataURI ) { - var byteStr, intArray, i, parts; - - parts = dataURI.split(','); - - if ( ~parts[ 0 ].indexOf('base64') ) { - byteStr = atob( parts[ 1 ] ); - } else { - byteStr = decodeURIComponent( parts[ 1 ] ); - } - - intArray = new Uint8Array( byteStr.length ); - - for ( i = 0; i < byteStr.length; i++ ) { - intArray[ i ] = byteStr.charCodeAt( i ); - } - - return intArray.buffer; - }, - - arrayBufferToBlob: function( buffer, type ) { - var builder = window.BlobBuilder || window.WebKitBlobBuilder, - bb; - - // android不支持直接new Blob, 只能借助blobbuilder. - if ( builder ) { - bb = new builder(); - bb.append( buffer ); - return bb.getBlob( type ); - } - - return new Blob([ buffer ], type ? { type: type } : {} ); - }, - - // 抽出来主要是为了解决android下面canvas.toDataUrl不支持jpeg. - // 你得到的结果是png. - canvasToDataUrl: function( canvas, type, quality ) { - return canvas.toDataURL( type, quality / 100 ); - }, - - // imagemeat会复写这个方法,如果用户选择加载那个文件了的话。 - parseMeta: function( blob, callback ) { - callback( false, {}); - }, - - // imagemeat会复写这个方法,如果用户选择加载那个文件了的话。 - updateImageHead: function( data ) { - return data; - } - }; - }); - /** - * Terms: - * - * Uint8Array, FileReader, BlobBuilder, atob, ArrayBuffer - * @fileOverview Image控件 - */ - define('runtime/html5/imagemeta',[ - 'runtime/html5/util' - ], function( Util ) { - - var api; - - api = { - parsers: { - 0xffe1: [] - }, - - maxMetaDataSize: 262144, - - parse: function( blob, cb ) { - var me = this, - fr = new FileReader(); - - fr.onload = function() { - cb( false, me._parse( this.result ) ); - fr = fr.onload = fr.onerror = null; - }; - - fr.onerror = function( e ) { - cb( e.message ); - fr = fr.onload = fr.onerror = null; - }; - - blob = blob.slice( 0, me.maxMetaDataSize ); - fr.readAsArrayBuffer( blob.getSource() ); - }, - - _parse: function( buffer, noParse ) { - if ( buffer.byteLength < 6 ) { - return; - } - - var dataview = new DataView( buffer ), - offset = 2, - maxOffset = dataview.byteLength - 4, - headLength = offset, - ret = {}, - markerBytes, markerLength, parsers, i; - - if ( dataview.getUint16( 0 ) === 0xffd8 ) { - - while ( offset < maxOffset ) { - markerBytes = dataview.getUint16( offset ); - - if ( markerBytes >= 0xffe0 && markerBytes <= 0xffef || - markerBytes === 0xfffe ) { - - markerLength = dataview.getUint16( offset + 2 ) + 2; - - if ( offset + markerLength > dataview.byteLength ) { - break; - } - - parsers = api.parsers[ markerBytes ]; - - if ( !noParse && parsers ) { - for ( i = 0; i < parsers.length; i += 1 ) { - parsers[ i ].call( api, dataview, offset, - markerLength, ret ); - } - } - - offset += markerLength; - headLength = offset; - } else { - break; - } - } - - if ( headLength > 6 ) { - if ( buffer.slice ) { - ret.imageHead = buffer.slice( 2, headLength ); - } else { - // Workaround for IE10, which does not yet - // support ArrayBuffer.slice: - ret.imageHead = new Uint8Array( buffer ) - .subarray( 2, headLength ); - } - } - } - - return ret; - }, - - updateImageHead: function( buffer, head ) { - var data = this._parse( buffer, true ), - buf1, buf2, bodyoffset; - - - bodyoffset = 2; - if ( data.imageHead ) { - bodyoffset = 2 + data.imageHead.byteLength; - } - - if ( buffer.slice ) { - buf2 = buffer.slice( bodyoffset ); - } else { - buf2 = new Uint8Array( buffer ).subarray( bodyoffset ); - } - - buf1 = new Uint8Array( head.byteLength + 2 + buf2.byteLength ); - - buf1[ 0 ] = 0xFF; - buf1[ 1 ] = 0xD8; - buf1.set( new Uint8Array( head ), 2 ); - buf1.set( new Uint8Array( buf2 ), head.byteLength + 2 ); - - return buf1.buffer; - } - }; - - Util.parseMeta = function() { - return api.parse.apply( api, arguments ); - }; - - Util.updateImageHead = function() { - return api.updateImageHead.apply( api, arguments ); - }; - - return api; - }); - /** - * 代码来自于:https://github.com/blueimp/JavaScript-Load-Image - * 暂时项目中只用了orientation. - * - * 去除了 Exif Sub IFD Pointer, GPS Info IFD Pointer, Exif Thumbnail. - * @fileOverview EXIF解析 - */ - - // Sample - // ==================================== - // Make : Apple - // Model : iPhone 4S - // Orientation : 1 - // XResolution : 72 [72/1] - // YResolution : 72 [72/1] - // ResolutionUnit : 2 - // Software : QuickTime 7.7.1 - // DateTime : 2013:09:01 22:53:55 - // ExifIFDPointer : 190 - // ExposureTime : 0.058823529411764705 [1/17] - // FNumber : 2.4 [12/5] - // ExposureProgram : Normal program - // ISOSpeedRatings : 800 - // ExifVersion : 0220 - // DateTimeOriginal : 2013:09:01 22:52:51 - // DateTimeDigitized : 2013:09:01 22:52:51 - // ComponentsConfiguration : YCbCr - // ShutterSpeedValue : 4.058893515764426 - // ApertureValue : 2.5260688216892597 [4845/1918] - // BrightnessValue : -0.3126686601998395 - // MeteringMode : Pattern - // Flash : Flash did not fire, compulsory flash mode - // FocalLength : 4.28 [107/25] - // SubjectArea : [4 values] - // FlashpixVersion : 0100 - // ColorSpace : 1 - // PixelXDimension : 2448 - // PixelYDimension : 3264 - // SensingMethod : One-chip color area sensor - // ExposureMode : 0 - // WhiteBalance : Auto white balance - // FocalLengthIn35mmFilm : 35 - // SceneCaptureType : Standard - define('runtime/html5/imagemeta/exif',[ - 'base', - 'runtime/html5/imagemeta' - ], function( Base, ImageMeta ) { - - var EXIF = {}; - - EXIF.ExifMap = function() { - return this; - }; - - EXIF.ExifMap.prototype.map = { - 'Orientation': 0x0112 - }; - - EXIF.ExifMap.prototype.get = function( id ) { - return this[ id ] || this[ this.map[ id ] ]; - }; - - EXIF.exifTagTypes = { - // byte, 8-bit unsigned int: - 1: { - getValue: function( dataView, dataOffset ) { - return dataView.getUint8( dataOffset ); - }, - size: 1 - }, - - // ascii, 8-bit byte: - 2: { - getValue: function( dataView, dataOffset ) { - return String.fromCharCode( dataView.getUint8( dataOffset ) ); - }, - size: 1, - ascii: true - }, - - // short, 16 bit int: - 3: { - getValue: function( dataView, dataOffset, littleEndian ) { - return dataView.getUint16( dataOffset, littleEndian ); - }, - size: 2 - }, - - // long, 32 bit int: - 4: { - getValue: function( dataView, dataOffset, littleEndian ) { - return dataView.getUint32( dataOffset, littleEndian ); - }, - size: 4 - }, - - // rational = two long values, - // first is numerator, second is denominator: - 5: { - getValue: function( dataView, dataOffset, littleEndian ) { - return dataView.getUint32( dataOffset, littleEndian ) / - dataView.getUint32( dataOffset + 4, littleEndian ); - }, - size: 8 - }, - - // slong, 32 bit signed int: - 9: { - getValue: function( dataView, dataOffset, littleEndian ) { - return dataView.getInt32( dataOffset, littleEndian ); - }, - size: 4 - }, - - // srational, two slongs, first is numerator, second is denominator: - 10: { - getValue: function( dataView, dataOffset, littleEndian ) { - return dataView.getInt32( dataOffset, littleEndian ) / - dataView.getInt32( dataOffset + 4, littleEndian ); - }, - size: 8 - } - }; - - // undefined, 8-bit byte, value depending on field: - EXIF.exifTagTypes[ 7 ] = EXIF.exifTagTypes[ 1 ]; - - EXIF.getExifValue = function( dataView, tiffOffset, offset, type, length, - littleEndian ) { - - var tagType = EXIF.exifTagTypes[ type ], - tagSize, dataOffset, values, i, str, c; - - if ( !tagType ) { - Base.log('Invalid Exif data: Invalid tag type.'); - return; - } - - tagSize = tagType.size * length; - - // Determine if the value is contained in the dataOffset bytes, - // or if the value at the dataOffset is a pointer to the actual data: - dataOffset = tagSize > 4 ? tiffOffset + dataView.getUint32( offset + 8, - littleEndian ) : (offset + 8); - - if ( dataOffset + tagSize > dataView.byteLength ) { - Base.log('Invalid Exif data: Invalid data offset.'); - return; - } - - if ( length === 1 ) { - return tagType.getValue( dataView, dataOffset, littleEndian ); - } - - values = []; - - for ( i = 0; i < length; i += 1 ) { - values[ i ] = tagType.getValue( dataView, - dataOffset + i * tagType.size, littleEndian ); - } - - if ( tagType.ascii ) { - str = ''; - - // Concatenate the chars: - for ( i = 0; i < values.length; i += 1 ) { - c = values[ i ]; - - // Ignore the terminating NULL byte(s): - if ( c === '\u0000' ) { - break; - } - str += c; - } - - return str; - } - return values; - }; - - EXIF.parseExifTag = function( dataView, tiffOffset, offset, littleEndian, - data ) { - - var tag = dataView.getUint16( offset, littleEndian ); - data.exif[ tag ] = EXIF.getExifValue( dataView, tiffOffset, offset, - dataView.getUint16( offset + 2, littleEndian ), // tag type - dataView.getUint32( offset + 4, littleEndian ), // tag length - littleEndian ); - }; - - EXIF.parseExifTags = function( dataView, tiffOffset, dirOffset, - littleEndian, data ) { - - var tagsNumber, dirEndOffset, i; - - if ( dirOffset + 6 > dataView.byteLength ) { - Base.log('Invalid Exif data: Invalid directory offset.'); - return; - } - - tagsNumber = dataView.getUint16( dirOffset, littleEndian ); - dirEndOffset = dirOffset + 2 + 12 * tagsNumber; - - if ( dirEndOffset + 4 > dataView.byteLength ) { - Base.log('Invalid Exif data: Invalid directory size.'); - return; - } - - for ( i = 0; i < tagsNumber; i += 1 ) { - this.parseExifTag( dataView, tiffOffset, - dirOffset + 2 + 12 * i, // tag offset - littleEndian, data ); - } - - // Return the offset to the next directory: - return dataView.getUint32( dirEndOffset, littleEndian ); - }; - - // EXIF.getExifThumbnail = function(dataView, offset, length) { - // var hexData, - // i, - // b; - // if (!length || offset + length > dataView.byteLength) { - // Base.log('Invalid Exif data: Invalid thumbnail data.'); - // return; - // } - // hexData = []; - // for (i = 0; i < length; i += 1) { - // b = dataView.getUint8(offset + i); - // hexData.push((b < 16 ? '0' : '') + b.toString(16)); - // } - // return 'data:image/jpeg,%' + hexData.join('%'); - // }; - - EXIF.parseExifData = function( dataView, offset, length, data ) { - - var tiffOffset = offset + 10, - littleEndian, dirOffset; - - // Check for the ASCII code for "Exif" (0x45786966): - if ( dataView.getUint32( offset + 4 ) !== 0x45786966 ) { - // No Exif data, might be XMP data instead - return; - } - if ( tiffOffset + 8 > dataView.byteLength ) { - Base.log('Invalid Exif data: Invalid segment size.'); - return; - } - - // Check for the two null bytes: - if ( dataView.getUint16( offset + 8 ) !== 0x0000 ) { - Base.log('Invalid Exif data: Missing byte alignment offset.'); - return; - } - - // Check the byte alignment: - switch ( dataView.getUint16( tiffOffset ) ) { - case 0x4949: - littleEndian = true; - break; - - case 0x4D4D: - littleEndian = false; - break; - - default: - Base.log('Invalid Exif data: Invalid byte alignment marker.'); - return; - } - - // Check for the TIFF tag marker (0x002A): - if ( dataView.getUint16( tiffOffset + 2, littleEndian ) !== 0x002A ) { - Base.log('Invalid Exif data: Missing TIFF marker.'); - return; - } - - // Retrieve the directory offset bytes, usually 0x00000008 or 8 decimal: - dirOffset = dataView.getUint32( tiffOffset + 4, littleEndian ); - // Create the exif object to store the tags: - data.exif = new EXIF.ExifMap(); - // Parse the tags of the main image directory and retrieve the - // offset to the next directory, usually the thumbnail directory: - dirOffset = EXIF.parseExifTags( dataView, tiffOffset, - tiffOffset + dirOffset, littleEndian, data ); - - // 尝试读取缩略图 - // if ( dirOffset ) { - // thumbnailData = {exif: {}}; - // dirOffset = EXIF.parseExifTags( - // dataView, - // tiffOffset, - // tiffOffset + dirOffset, - // littleEndian, - // thumbnailData - // ); - - // // Check for JPEG Thumbnail offset: - // if (thumbnailData.exif[0x0201]) { - // data.exif.Thumbnail = EXIF.getExifThumbnail( - // dataView, - // tiffOffset + thumbnailData.exif[0x0201], - // thumbnailData.exif[0x0202] // Thumbnail data length - // ); - // } - // } - }; - - ImageMeta.parsers[ 0xffe1 ].push( EXIF.parseExifData ); - return EXIF; - }); - /** - * @fileOverview Image - */ - define('runtime/html5/image',[ - 'base', - 'runtime/html5/runtime', - 'runtime/html5/util' - ], function( Base, Html5Runtime, Util ) { - - var BLANK = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs%3D'; - - return Html5Runtime.register( 'Image', { - - // flag: 标记是否被修改过。 - modified: false, - - init: function() { - var me = this, - img = new Image(); - - img.onload = function() { - - me._info = { - type: me.type, - width: this.width, - height: this.height - }; - - // 读取meta信息。 - if ( !me._metas && 'image/jpeg' === me.type ) { - Util.parseMeta( me._blob, function( error, ret ) { - me._metas = ret; - me.owner.trigger('load'); - }); - } else { - me.owner.trigger('load'); - } - }; - - img.onerror = function() { - me.owner.trigger('error'); - }; - - me._img = img; - }, - - loadFromBlob: function( blob ) { - var me = this, - img = me._img; - - me._blob = blob; - me.type = blob.type; - img.src = Util.createObjectURL( blob.getSource() ); - me.owner.once( 'load', function() { - Util.revokeObjectURL( img.src ); - }); - }, - - resize: function( width, height ) { - var canvas = this._canvas || - (this._canvas = document.createElement('canvas')); - - this._resize( this._img, canvas, width, height ); - this._blob = null; // 没用了,可以删掉了。 - this.modified = true; - this.owner.trigger( 'complete', 'resize' ); - }, - - crop: function( x, y, w, h, s ) { - var cvs = this._canvas || - (this._canvas = document.createElement('canvas')), - opts = this.options, - img = this._img, - iw = img.naturalWidth, - ih = img.naturalHeight, - orientation = this.getOrientation(); - - s = s || 1; - - // todo 解决 orientation 的问题。 - // values that require 90 degree rotation - // if ( ~[ 5, 6, 7, 8 ].indexOf( orientation ) ) { - - // switch ( orientation ) { - // case 6: - // tmp = x; - // x = y; - // y = iw * s - tmp - w; - // console.log(ih * s, tmp, w) - // break; - // } - - // (w ^= h, h ^= w, w ^= h); - // } - - cvs.width = w; - cvs.height = h; - - opts.preserveHeaders || this._rotate2Orientaion( cvs, orientation ); - this._renderImageToCanvas( cvs, img, -x, -y, iw * s, ih * s ); - - this._blob = null; // 没用了,可以删掉了。 - this.modified = true; - this.owner.trigger( 'complete', 'crop' ); - }, - - getAsBlob: function( type ) { - var blob = this._blob, - opts = this.options, - canvas; - - type = type || this.type; - - // blob需要重新生成。 - if ( this.modified || this.type !== type ) { - canvas = this._canvas; - - if ( type === 'image/jpeg' ) { - - blob = Util.canvasToDataUrl( canvas, type, opts.quality ); - - if ( opts.preserveHeaders && this._metas && - this._metas.imageHead ) { - - blob = Util.dataURL2ArrayBuffer( blob ); - blob = Util.updateImageHead( blob, - this._metas.imageHead ); - blob = Util.arrayBufferToBlob( blob, type ); - return blob; - } - } else { - blob = Util.canvasToDataUrl( canvas, type ); - } - - blob = Util.dataURL2Blob( blob ); - } - - return blob; - }, - - getAsDataUrl: function( type ) { - var opts = this.options; - - type = type || this.type; - - if ( type === 'image/jpeg' ) { - return Util.canvasToDataUrl( this._canvas, type, opts.quality ); - } else { - return this._canvas.toDataURL( type ); - } - }, - - getOrientation: function() { - return this._metas && this._metas.exif && - this._metas.exif.get('Orientation') || 1; - }, - - info: function( val ) { - - // setter - if ( val ) { - this._info = val; - return this; - } - - // getter - return this._info; - }, - - meta: function( val ) { - - // setter - if ( val ) { - this._meta = val; - return this; - } - - // getter - return this._meta; - }, - - destroy: function() { - var canvas = this._canvas; - this._img.onload = null; - - if ( canvas ) { - canvas.getContext('2d') - .clearRect( 0, 0, canvas.width, canvas.height ); - canvas.width = canvas.height = 0; - this._canvas = null; - } - - // 释放内存。非常重要,否则释放不了image的内存。 - this._img.src = BLANK; - this._img = this._blob = null; - }, - - _resize: function( img, cvs, width, height ) { - var opts = this.options, - naturalWidth = img.width, - naturalHeight = img.height, - orientation = this.getOrientation(), - scale, w, h, x, y; - - // values that require 90 degree rotation - if ( ~[ 5, 6, 7, 8 ].indexOf( orientation ) ) { - - // 交换width, height的值。 - width ^= height; - height ^= width; - width ^= height; - } - - scale = Math[ opts.crop ? 'max' : 'min' ]( width / naturalWidth, - height / naturalHeight ); - - // 不允许放大。 - opts.allowMagnify || (scale = Math.min( 1, scale )); - - w = naturalWidth * scale; - h = naturalHeight * scale; - - if ( opts.crop ) { - cvs.width = width; - cvs.height = height; - } else { - cvs.width = w; - cvs.height = h; - } - - x = (cvs.width - w) / 2; - y = (cvs.height - h) / 2; - - opts.preserveHeaders || this._rotate2Orientaion( cvs, orientation ); - - this._renderImageToCanvas( cvs, img, x, y, w, h ); - }, - - _rotate2Orientaion: function( canvas, orientation ) { - var width = canvas.width, - height = canvas.height, - ctx = canvas.getContext('2d'); - - switch ( orientation ) { - case 5: - case 6: - case 7: - case 8: - canvas.width = height; - canvas.height = width; - break; - } - - switch ( orientation ) { - case 2: // horizontal flip - ctx.translate( width, 0 ); - ctx.scale( -1, 1 ); - break; - - case 3: // 180 rotate left - ctx.translate( width, height ); - ctx.rotate( Math.PI ); - break; - - case 4: // vertical flip - ctx.translate( 0, height ); - ctx.scale( 1, -1 ); - break; - - case 5: // vertical flip + 90 rotate right - ctx.rotate( 0.5 * Math.PI ); - ctx.scale( 1, -1 ); - break; - - case 6: // 90 rotate right - ctx.rotate( 0.5 * Math.PI ); - ctx.translate( 0, -height ); - break; - - case 7: // horizontal flip + 90 rotate right - ctx.rotate( 0.5 * Math.PI ); - ctx.translate( width, -height ); - ctx.scale( -1, 1 ); - break; - - case 8: // 90 rotate left - ctx.rotate( -0.5 * Math.PI ); - ctx.translate( -width, 0 ); - break; - } - }, - - // https://github.com/stomita/ios-imagefile-megapixel/ - // blob/master/src/megapix-image.js - _renderImageToCanvas: (function() { - - // 如果不是ios, 不需要这么复杂! - if ( !Base.os.ios ) { - return function( canvas ) { - var args = Base.slice( arguments, 1 ), - ctx = canvas.getContext('2d'); - - ctx.drawImage.apply( ctx, args ); - }; - } - - /** - * Detecting vertical squash in loaded image. - * Fixes a bug which squash image vertically while drawing into - * canvas for some images. - */ - function detectVerticalSquash( img, iw, ih ) { - var canvas = document.createElement('canvas'), - ctx = canvas.getContext('2d'), - sy = 0, - ey = ih, - py = ih, - data, alpha, ratio; - - - canvas.width = 1; - canvas.height = ih; - ctx.drawImage( img, 0, 0 ); - data = ctx.getImageData( 0, 0, 1, ih ).data; - - // search image edge pixel position in case - // it is squashed vertically. - while ( py > sy ) { - alpha = data[ (py - 1) * 4 + 3 ]; - - if ( alpha === 0 ) { - ey = py; - } else { - sy = py; - } - - py = (ey + sy) >> 1; - } - - ratio = (py / ih); - return (ratio === 0) ? 1 : ratio; - } - - // fix ie7 bug - // http://stackoverflow.com/questions/11929099/ - // html5-canvas-drawimage-ratio-bug-ios - if ( Base.os.ios >= 7 ) { - return function( canvas, img, x, y, w, h ) { - var iw = img.naturalWidth, - ih = img.naturalHeight, - vertSquashRatio = detectVerticalSquash( img, iw, ih ); - - return canvas.getContext('2d').drawImage( img, 0, 0, - iw * vertSquashRatio, ih * vertSquashRatio, - x, y, w, h ); - }; - } - - /** - * Detect subsampling in loaded image. - * In iOS, larger images than 2M pixels may be - * subsampled in rendering. - */ - function detectSubsampling( img ) { - var iw = img.naturalWidth, - ih = img.naturalHeight, - canvas, ctx; - - // subsampling may happen overmegapixel image - if ( iw * ih > 1024 * 1024 ) { - canvas = document.createElement('canvas'); - canvas.width = canvas.height = 1; - ctx = canvas.getContext('2d'); - ctx.drawImage( img, -iw + 1, 0 ); - - // subsampled image becomes half smaller in rendering size. - // check alpha channel value to confirm image is covering - // edge pixel or not. if alpha value is 0 - // image is not covering, hence subsampled. - return ctx.getImageData( 0, 0, 1, 1 ).data[ 3 ] === 0; - } else { - return false; - } - } - - - return function( canvas, img, x, y, width, height ) { - var iw = img.naturalWidth, - ih = img.naturalHeight, - ctx = canvas.getContext('2d'), - subsampled = detectSubsampling( img ), - doSquash = this.type === 'image/jpeg', - d = 1024, - sy = 0, - dy = 0, - tmpCanvas, tmpCtx, vertSquashRatio, dw, dh, sx, dx; - - if ( subsampled ) { - iw /= 2; - ih /= 2; - } - - ctx.save(); - tmpCanvas = document.createElement('canvas'); - tmpCanvas.width = tmpCanvas.height = d; - - tmpCtx = tmpCanvas.getContext('2d'); - vertSquashRatio = doSquash ? - detectVerticalSquash( img, iw, ih ) : 1; - - dw = Math.ceil( d * width / iw ); - dh = Math.ceil( d * height / ih / vertSquashRatio ); - - while ( sy < ih ) { - sx = 0; - dx = 0; - while ( sx < iw ) { - tmpCtx.clearRect( 0, 0, d, d ); - tmpCtx.drawImage( img, -sx, -sy ); - ctx.drawImage( tmpCanvas, 0, 0, d, d, - x + dx, y + dy, dw, dh ); - sx += d; - dx += dw; - } - sy += d; - dy += dh; - } - ctx.restore(); - tmpCanvas = tmpCtx = null; - }; - })() - }); - }); - /** - * 这个方式性能不行,但是可以解决android里面的toDataUrl的bug - * android里面toDataUrl('image/jpege')得到的结果却是png. - * - * 所以这里没辙,只能借助这个工具 - * @fileOverview jpeg encoder - */ - define('runtime/html5/jpegencoder',[], function( require, exports, module ) { - - /* - Copyright (c) 2008, Adobe Systems Incorporated - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are - met: - - * Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the name of Adobe Systems Incorporated nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS - IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - /* - JPEG encoder ported to JavaScript and optimized by Andreas Ritter, www.bytestrom.eu, 11/2009 - - Basic GUI blocking jpeg encoder - */ - - function JPEGEncoder(quality) { - var self = this; - var fround = Math.round; - var ffloor = Math.floor; - var YTable = new Array(64); - var UVTable = new Array(64); - var fdtbl_Y = new Array(64); - var fdtbl_UV = new Array(64); - var YDC_HT; - var UVDC_HT; - var YAC_HT; - var UVAC_HT; - - var bitcode = new Array(65535); - var category = new Array(65535); - var outputfDCTQuant = new Array(64); - var DU = new Array(64); - var byteout = []; - var bytenew = 0; - var bytepos = 7; - - var YDU = new Array(64); - var UDU = new Array(64); - var VDU = new Array(64); - var clt = new Array(256); - var RGB_YUV_TABLE = new Array(2048); - var currentQuality; - - var ZigZag = [ - 0, 1, 5, 6,14,15,27,28, - 2, 4, 7,13,16,26,29,42, - 3, 8,12,17,25,30,41,43, - 9,11,18,24,31,40,44,53, - 10,19,23,32,39,45,52,54, - 20,22,33,38,46,51,55,60, - 21,34,37,47,50,56,59,61, - 35,36,48,49,57,58,62,63 - ]; - - var std_dc_luminance_nrcodes = [0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0]; - var std_dc_luminance_values = [0,1,2,3,4,5,6,7,8,9,10,11]; - var std_ac_luminance_nrcodes = [0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d]; - var std_ac_luminance_values = [ - 0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12, - 0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07, - 0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08, - 0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0, - 0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16, - 0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28, - 0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39, - 0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49, - 0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59, - 0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69, - 0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79, - 0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89, - 0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98, - 0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7, - 0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6, - 0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5, - 0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4, - 0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2, - 0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea, - 0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8, - 0xf9,0xfa - ]; - - var std_dc_chrominance_nrcodes = [0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0]; - var std_dc_chrominance_values = [0,1,2,3,4,5,6,7,8,9,10,11]; - var std_ac_chrominance_nrcodes = [0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77]; - var std_ac_chrominance_values = [ - 0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21, - 0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71, - 0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91, - 0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0, - 0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34, - 0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26, - 0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38, - 0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48, - 0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58, - 0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68, - 0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78, - 0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87, - 0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96, - 0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5, - 0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4, - 0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3, - 0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2, - 0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda, - 0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9, - 0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8, - 0xf9,0xfa - ]; - - function initQuantTables(sf){ - var YQT = [ - 16, 11, 10, 16, 24, 40, 51, 61, - 12, 12, 14, 19, 26, 58, 60, 55, - 14, 13, 16, 24, 40, 57, 69, 56, - 14, 17, 22, 29, 51, 87, 80, 62, - 18, 22, 37, 56, 68,109,103, 77, - 24, 35, 55, 64, 81,104,113, 92, - 49, 64, 78, 87,103,121,120,101, - 72, 92, 95, 98,112,100,103, 99 - ]; - - for (var i = 0; i < 64; i++) { - var t = ffloor((YQT[i]*sf+50)/100); - if (t < 1) { - t = 1; - } else if (t > 255) { - t = 255; - } - YTable[ZigZag[i]] = t; - } - var UVQT = [ - 17, 18, 24, 47, 99, 99, 99, 99, - 18, 21, 26, 66, 99, 99, 99, 99, - 24, 26, 56, 99, 99, 99, 99, 99, - 47, 66, 99, 99, 99, 99, 99, 99, - 99, 99, 99, 99, 99, 99, 99, 99, - 99, 99, 99, 99, 99, 99, 99, 99, - 99, 99, 99, 99, 99, 99, 99, 99, - 99, 99, 99, 99, 99, 99, 99, 99 - ]; - for (var j = 0; j < 64; j++) { - var u = ffloor((UVQT[j]*sf+50)/100); - if (u < 1) { - u = 1; - } else if (u > 255) { - u = 255; - } - UVTable[ZigZag[j]] = u; - } - var aasf = [ - 1.0, 1.387039845, 1.306562965, 1.175875602, - 1.0, 0.785694958, 0.541196100, 0.275899379 - ]; - var k = 0; - for (var row = 0; row < 8; row++) - { - for (var col = 0; col < 8; col++) - { - fdtbl_Y[k] = (1.0 / (YTable [ZigZag[k]] * aasf[row] * aasf[col] * 8.0)); - fdtbl_UV[k] = (1.0 / (UVTable[ZigZag[k]] * aasf[row] * aasf[col] * 8.0)); - k++; - } - } - } - - function computeHuffmanTbl(nrcodes, std_table){ - var codevalue = 0; - var pos_in_table = 0; - var HT = new Array(); - for (var k = 1; k <= 16; k++) { - for (var j = 1; j <= nrcodes[k]; j++) { - HT[std_table[pos_in_table]] = []; - HT[std_table[pos_in_table]][0] = codevalue; - HT[std_table[pos_in_table]][1] = k; - pos_in_table++; - codevalue++; - } - codevalue*=2; - } - return HT; - } - - function initHuffmanTbl() - { - YDC_HT = computeHuffmanTbl(std_dc_luminance_nrcodes,std_dc_luminance_values); - UVDC_HT = computeHuffmanTbl(std_dc_chrominance_nrcodes,std_dc_chrominance_values); - YAC_HT = computeHuffmanTbl(std_ac_luminance_nrcodes,std_ac_luminance_values); - UVAC_HT = computeHuffmanTbl(std_ac_chrominance_nrcodes,std_ac_chrominance_values); - } - - function initCategoryNumber() - { - var nrlower = 1; - var nrupper = 2; - for (var cat = 1; cat <= 15; cat++) { - //Positive numbers - for (var nr = nrlower; nr>0] = 38470 * i; - RGB_YUV_TABLE[(i+ 512)>>0] = 7471 * i + 0x8000; - RGB_YUV_TABLE[(i+ 768)>>0] = -11059 * i; - RGB_YUV_TABLE[(i+1024)>>0] = -21709 * i; - RGB_YUV_TABLE[(i+1280)>>0] = 32768 * i + 0x807FFF; - RGB_YUV_TABLE[(i+1536)>>0] = -27439 * i; - RGB_YUV_TABLE[(i+1792)>>0] = - 5329 * i; - } - } - - // IO functions - function writeBits(bs) - { - var value = bs[0]; - var posval = bs[1]-1; - while ( posval >= 0 ) { - if (value & (1 << posval) ) { - bytenew |= (1 << bytepos); - } - posval--; - bytepos--; - if (bytepos < 0) { - if (bytenew == 0xFF) { - writeByte(0xFF); - writeByte(0); - } - else { - writeByte(bytenew); - } - bytepos=7; - bytenew=0; - } - } - } - - function writeByte(value) - { - byteout.push(clt[value]); // write char directly instead of converting later - } - - function writeWord(value) - { - writeByte((value>>8)&0xFF); - writeByte((value )&0xFF); - } - - // DCT & quantization core - function fDCTQuant(data, fdtbl) - { - var d0, d1, d2, d3, d4, d5, d6, d7; - /* Pass 1: process rows. */ - var dataOff=0; - var i; - var I8 = 8; - var I64 = 64; - for (i=0; i 0.0) ? ((fDCTQuant + 0.5)|0) : ((fDCTQuant - 0.5)|0); - //outputfDCTQuant[i] = fround(fDCTQuant); - - } - return outputfDCTQuant; - } - - function writeAPP0() - { - writeWord(0xFFE0); // marker - writeWord(16); // length - writeByte(0x4A); // J - writeByte(0x46); // F - writeByte(0x49); // I - writeByte(0x46); // F - writeByte(0); // = "JFIF",'\0' - writeByte(1); // versionhi - writeByte(1); // versionlo - writeByte(0); // xyunits - writeWord(1); // xdensity - writeWord(1); // ydensity - writeByte(0); // thumbnwidth - writeByte(0); // thumbnheight - } - - function writeSOF0(width, height) - { - writeWord(0xFFC0); // marker - writeWord(17); // length, truecolor YUV JPG - writeByte(8); // precision - writeWord(height); - writeWord(width); - writeByte(3); // nrofcomponents - writeByte(1); // IdY - writeByte(0x11); // HVY - writeByte(0); // QTY - writeByte(2); // IdU - writeByte(0x11); // HVU - writeByte(1); // QTU - writeByte(3); // IdV - writeByte(0x11); // HVV - writeByte(1); // QTV - } - - function writeDQT() - { - writeWord(0xFFDB); // marker - writeWord(132); // length - writeByte(0); - for (var i=0; i<64; i++) { - writeByte(YTable[i]); - } - writeByte(1); - for (var j=0; j<64; j++) { - writeByte(UVTable[j]); - } - } - - function writeDHT() - { - writeWord(0xFFC4); // marker - writeWord(0x01A2); // length - - writeByte(0); // HTYDCinfo - for (var i=0; i<16; i++) { - writeByte(std_dc_luminance_nrcodes[i+1]); - } - for (var j=0; j<=11; j++) { - writeByte(std_dc_luminance_values[j]); - } - - writeByte(0x10); // HTYACinfo - for (var k=0; k<16; k++) { - writeByte(std_ac_luminance_nrcodes[k+1]); - } - for (var l=0; l<=161; l++) { - writeByte(std_ac_luminance_values[l]); - } - - writeByte(1); // HTUDCinfo - for (var m=0; m<16; m++) { - writeByte(std_dc_chrominance_nrcodes[m+1]); - } - for (var n=0; n<=11; n++) { - writeByte(std_dc_chrominance_values[n]); - } - - writeByte(0x11); // HTUACinfo - for (var o=0; o<16; o++) { - writeByte(std_ac_chrominance_nrcodes[o+1]); - } - for (var p=0; p<=161; p++) { - writeByte(std_ac_chrominance_values[p]); - } - } - - function writeSOS() - { - writeWord(0xFFDA); // marker - writeWord(12); // length - writeByte(3); // nrofcomponents - writeByte(1); // IdY - writeByte(0); // HTY - writeByte(2); // IdU - writeByte(0x11); // HTU - writeByte(3); // IdV - writeByte(0x11); // HTV - writeByte(0); // Ss - writeByte(0x3f); // Se - writeByte(0); // Bf - } - - function processDU(CDU, fdtbl, DC, HTDC, HTAC){ - var EOB = HTAC[0x00]; - var M16zeroes = HTAC[0xF0]; - var pos; - var I16 = 16; - var I63 = 63; - var I64 = 64; - var DU_DCT = fDCTQuant(CDU, fdtbl); - //ZigZag reorder - for (var j=0;j0)&&(DU[end0pos]==0); end0pos--) {}; - //end0pos = first element in reverse order !=0 - if ( end0pos == 0) { - writeBits(EOB); - return DC; - } - var i = 1; - var lng; - while ( i <= end0pos ) { - var startpos = i; - for (; (DU[i]==0) && (i<=end0pos); ++i) {} - var nrzeroes = i-startpos; - if ( nrzeroes >= I16 ) { - lng = nrzeroes>>4; - for (var nrmarker=1; nrmarker <= lng; ++nrmarker) - writeBits(M16zeroes); - nrzeroes = nrzeroes&0xF; - } - pos = 32767+DU[i]; - writeBits(HTAC[(nrzeroes<<4)+category[pos]]); - writeBits(bitcode[pos]); - i++; - } - if ( end0pos != I63 ) { - writeBits(EOB); - } - return DC; - } - - function initCharLookupTable(){ - var sfcc = String.fromCharCode; - for(var i=0; i < 256; i++){ ///// ACHTUNG // 255 - clt[i] = sfcc(i); - } - } - - this.encode = function(image,quality) // image data object - { - // var time_start = new Date().getTime(); - - if(quality) setQuality(quality); - - // Initialize bit writer - byteout = new Array(); - bytenew=0; - bytepos=7; - - // Add JPEG headers - writeWord(0xFFD8); // SOI - writeAPP0(); - writeDQT(); - writeSOF0(image.width,image.height); - writeDHT(); - writeSOS(); - - - // Encode 8x8 macroblocks - var DCY=0; - var DCU=0; - var DCV=0; - - bytenew=0; - bytepos=7; - - - this.encode.displayName = "_encode_"; - - var imageData = image.data; - var width = image.width; - var height = image.height; - - var quadWidth = width*4; - var tripleWidth = width*3; - - var x, y = 0; - var r, g, b; - var start,p, col,row,pos; - while(y < height){ - x = 0; - while(x < quadWidth){ - start = quadWidth * y + x; - p = start; - col = -1; - row = 0; - - for(pos=0; pos < 64; pos++){ - row = pos >> 3;// /8 - col = ( pos & 7 ) * 4; // %8 - p = start + ( row * quadWidth ) + col; - - if(y+row >= height){ // padding bottom - p-= (quadWidth*(y+1+row-height)); - } - - if(x+col >= quadWidth){ // padding right - p-= ((x+col) - quadWidth +4) - } - - r = imageData[ p++ ]; - g = imageData[ p++ ]; - b = imageData[ p++ ]; - - - /* // calculate YUV values dynamically - YDU[pos]=((( 0.29900)*r+( 0.58700)*g+( 0.11400)*b))-128; //-0x80 - UDU[pos]=(((-0.16874)*r+(-0.33126)*g+( 0.50000)*b)); - VDU[pos]=((( 0.50000)*r+(-0.41869)*g+(-0.08131)*b)); - */ - - // use lookup table (slightly faster) - YDU[pos] = ((RGB_YUV_TABLE[r] + RGB_YUV_TABLE[(g + 256)>>0] + RGB_YUV_TABLE[(b + 512)>>0]) >> 16)-128; - UDU[pos] = ((RGB_YUV_TABLE[(r + 768)>>0] + RGB_YUV_TABLE[(g + 1024)>>0] + RGB_YUV_TABLE[(b + 1280)>>0]) >> 16)-128; - VDU[pos] = ((RGB_YUV_TABLE[(r + 1280)>>0] + RGB_YUV_TABLE[(g + 1536)>>0] + RGB_YUV_TABLE[(b + 1792)>>0]) >> 16)-128; - - } - - DCY = processDU(YDU, fdtbl_Y, DCY, YDC_HT, YAC_HT); - DCU = processDU(UDU, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); - DCV = processDU(VDU, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); - x+=32; - } - y+=8; - } - - - //////////////////////////////////////////////////////////////// - - // Do the bit alignment of the EOI marker - if ( bytepos >= 0 ) { - var fillbits = []; - fillbits[1] = bytepos+1; - fillbits[0] = (1<<(bytepos+1))-1; - writeBits(fillbits); - } - - writeWord(0xFFD9); //EOI - - var jpegDataUri = 'data:image/jpeg;base64,' + btoa(byteout.join('')); - - byteout = []; - - // benchmarking - // var duration = new Date().getTime() - time_start; - // console.log('Encoding time: '+ currentQuality + 'ms'); - // - - return jpegDataUri - } - - function setQuality(quality){ - if (quality <= 0) { - quality = 1; - } - if (quality > 100) { - quality = 100; - } - - if(currentQuality == quality) return // don't recalc if unchanged - - var sf = 0; - if (quality < 50) { - sf = Math.floor(5000 / quality); - } else { - sf = Math.floor(200 - quality*2); - } - - initQuantTables(sf); - currentQuality = quality; - // console.log('Quality set to: '+quality +'%'); - } - - function init(){ - // var time_start = new Date().getTime(); - if(!quality) quality = 50; - // Create tables - initCharLookupTable() - initHuffmanTbl(); - initCategoryNumber(); - initRGBYUVTable(); - - setQuality(quality); - // var duration = new Date().getTime() - time_start; - // console.log('Initialization '+ duration + 'ms'); - } - - init(); - - }; - - JPEGEncoder.encode = function( data, quality ) { - var encoder = new JPEGEncoder( quality ); - - return encoder.encode( data ); - } - - return JPEGEncoder; - }); - /** - * @fileOverview Fix android canvas.toDataUrl bug. - */ - define('runtime/html5/androidpatch',[ - 'runtime/html5/util', - 'runtime/html5/jpegencoder', - 'base' - ], function( Util, encoder, Base ) { - var origin = Util.canvasToDataUrl, - supportJpeg; - - Util.canvasToDataUrl = function( canvas, type, quality ) { - var ctx, w, h, fragement, parts; - - // 非android手机直接跳过。 - if ( !Base.os.android ) { - return origin.apply( null, arguments ); - } - - // 检测是否canvas支持jpeg导出,根据数据格式来判断。 - // JPEG 前两位分别是:255, 216 - if ( type === 'image/jpeg' && typeof supportJpeg === 'undefined' ) { - fragement = origin.apply( null, arguments ); - - parts = fragement.split(','); - - if ( ~parts[ 0 ].indexOf('base64') ) { - fragement = atob( parts[ 1 ] ); - } else { - fragement = decodeURIComponent( parts[ 1 ] ); - } - - fragement = fragement.substring( 0, 2 ); - - supportJpeg = fragement.charCodeAt( 0 ) === 255 && - fragement.charCodeAt( 1 ) === 216; - } - - // 只有在android环境下才修复 - if ( type === 'image/jpeg' && !supportJpeg ) { - w = canvas.width; - h = canvas.height; - ctx = canvas.getContext('2d'); - - return encoder.encode( ctx.getImageData( 0, 0, w, h ), quality ); - } - - return origin.apply( null, arguments ); - }; - }); - /** - * @fileOverview Transport - * @todo 支持chunked传输,优势: - * 可以将大文件分成小块,挨个传输,可以提高大文件成功率,当失败的时候,也只需要重传那小部分, - * 而不需要重头再传一次。另外断点续传也需要用chunked方式。 - */ - define('runtime/html5/transport',[ - 'base', - 'runtime/html5/runtime' - ], function( Base, Html5Runtime ) { - - var noop = Base.noop, - $ = Base.$; - - return Html5Runtime.register( 'Transport', { - init: function() { - this._status = 0; - this._response = null; - }, - - send: function() { - var owner = this.owner, - opts = this.options, - xhr = this._initAjax(), - blob = owner._blob, - server = opts.server, - formData, binary, fr; - - if ( opts.sendAsBinary ) { - server += (/\?/.test( server ) ? '&' : '?') + - $.param( owner._formData ); - - binary = blob.getSource(); - } else { - formData = new FormData(); - $.each( owner._formData, function( k, v ) { - formData.append( k, v ); - }); - - formData.append( opts.fileVal, blob.getSource(), - opts.filename || owner._formData.name || '' ); - } - - if ( opts.withCredentials && 'withCredentials' in xhr ) { - xhr.open( opts.method, server, true ); - xhr.withCredentials = true; - } else { - xhr.open( opts.method, server ); - } - - this._setRequestHeader( xhr, opts.headers ); - - if ( binary ) { - // 强制设置成 content-type 为文件流。 - xhr.overrideMimeType && - xhr.overrideMimeType('application/octet-stream'); - - // android直接发送blob会导致服务端接收到的是空文件。 - // bug详情。 - // https://code.google.com/p/android/issues/detail?id=39882 - // 所以先用fileReader读取出来再通过arraybuffer的方式发送。 - if ( Base.os.android ) { - fr = new FileReader(); - - fr.onload = function() { - xhr.send( this.result ); - fr = fr.onload = null; - }; - - fr.readAsArrayBuffer( binary ); - } else { - xhr.send( binary ); - } - } else { - xhr.send( formData ); - } - }, - - getResponse: function() { - return this._response; - }, - - getResponseAsJson: function() { - return this._parseJson( this._response ); - }, - - getStatus: function() { - return this._status; - }, - - abort: function() { - var xhr = this._xhr; - - if ( xhr ) { - xhr.upload.onprogress = noop; - xhr.onreadystatechange = noop; - xhr.abort(); - - this._xhr = xhr = null; - } - }, - - destroy: function() { - this.abort(); - }, - - _initAjax: function() { - var me = this, - xhr = new XMLHttpRequest(), - opts = this.options; - - if ( opts.withCredentials && !('withCredentials' in xhr) && - typeof XDomainRequest !== 'undefined' ) { - xhr = new XDomainRequest(); - } - - xhr.upload.onprogress = function( e ) { - var percentage = 0; - - if ( e.lengthComputable ) { - percentage = e.loaded / e.total; - } - - return me.trigger( 'progress', percentage ); - }; - - xhr.onreadystatechange = function() { - - if ( xhr.readyState !== 4 ) { - return; - } - - xhr.upload.onprogress = noop; - xhr.onreadystatechange = noop; - me._xhr = null; - me._status = xhr.status; - - if ( xhr.status >= 200 && xhr.status < 300 ) { - me._response = xhr.responseText; - return me.trigger('load'); - } else if ( xhr.status >= 500 && xhr.status < 600 ) { - me._response = xhr.responseText; - return me.trigger( 'error', 'server' ); - } - - - return me.trigger( 'error', me._status ? 'http' : 'abort' ); - }; - - me._xhr = xhr; - return xhr; - }, - - _setRequestHeader: function( xhr, headers ) { - $.each( headers, function( key, val ) { - xhr.setRequestHeader( key, val ); - }); - }, - - _parseJson: function( str ) { - var json; - - try { - json = JSON.parse( str ); - } catch ( ex ) { - json = {}; - } - - return json; - } - }); - }); - define('webuploader',[ - 'base', - 'widgets/filepicker', - 'widgets/image', - 'widgets/queue', - 'widgets/runtime', - 'widgets/upload', - 'widgets/log', - 'runtime/html5/blob', - 'runtime/html5/filepicker', - 'runtime/html5/imagemeta/exif', - 'runtime/html5/image', - 'runtime/html5/androidpatch', - 'runtime/html5/transport' - ], function( Base ) { - return Base; - }); - return require('webuploader'); -}); diff --git a/static/plugins/webuploader/webuploader.custom.min.js b/static/plugins/webuploader/webuploader.custom.min.js deleted file mode 100644 index 09728105..00000000 --- a/static/plugins/webuploader/webuploader.custom.min.js +++ /dev/null @@ -1,2 +0,0 @@ -/* WebUploader 0.1.5 */!function(a,b){var c,d={},e=function(a,b){var c,d,e;if("string"==typeof a)return h(a);for(c=[],d=a.length,e=0;d>e;e++)c.push(h(a[e]));return b.apply(null,c)},f=function(a,b,c){2===arguments.length&&(c=b,b=null),e(b||[],function(){g(a,c,arguments)})},g=function(a,b,c){var f,g={exports:b};"function"==typeof b&&(c.length||(c=[e,g.exports,g]),f=b.apply(null,c),void 0!==f&&(g.exports=f)),d[a]=g.exports},h=function(b){var c=d[b]||a[b];if(!c)throw new Error("`"+b+"` is undefined");return c},i=function(a){var b,c,e,f,g,h;h=function(a){return a&&a.charAt(0).toUpperCase()+a.substr(1)};for(b in d)if(c=a,d.hasOwnProperty(b)){for(e=b.split("/"),g=h(e.pop());f=h(e.shift());)c[f]=c[f]||{},c=c[f];c[g]=d[b]}return a},j=function(c){return a.__dollar=c,i(b(a,f,e))};"object"==typeof module&&"object"==typeof module.exports?module.exports=j():"function"==typeof define&&define.amd?define(["jquery"],j):(c=a.WebUploader,a.WebUploader=j(),a.WebUploader.noConflict=function(){a.WebUploader=c})}(window,function(a,b,c){return b("dollar-third",[],function(){var b=a.__dollar||a.jQuery||a.Zepto;if(!b)throw new Error("jQuery or Zepto not found!");return b}),b("dollar",["dollar-third"],function(a){return a}),b("promise-builtin",["dollar"],function(a){function b(b){var c,d,e,f,g,h,i=[],j=!b&&[],k=function(a){for(h=a,c=!0,g=e||0,e=0,f=i.length,d=!0;i&&f>g;g++)i[g].apply(a[0],a[1]);d=!1,i&&(j?j.length&&k(j.shift()):i=[])},l={add:function(){if(i){var b=i.length;!function c(b){a.each(b,function(b,d){var e=a.type(d);"function"===e?i.push(d):d&&d.length&&"string"!==e&&c(d)})}(arguments),d?f=i.length:h&&(e=b,k(h))}return this},disable:function(){return i=j=h=void 0,this},lock:function(){return j=void 0,h||l.disable(),this},fireWith:function(a,b){return!i||c&&!j||(b=b||[],b=[a,b.slice?b.slice():b],d?j.push(b):k(b)),this},fire:function(){return l.fireWith(this,arguments),this}};return l}function c(d){var e=[["resolve","done",b(!0),"resolved"],["reject","fail",b(!0),"rejected"],["notify","progress",b()]],f="pending",g={state:function(){return f},always:function(){return h.done(arguments).fail(arguments),this},then:function(){var b=arguments;return c(function(c){a.each(e,function(d,e){var f=e[0],i=a.isFunction(b[d])&&b[d];h[e[1]](function(){var b;b=i&&i.apply(this,arguments),b&&a.isFunction(b.promise)?b.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f+"With"](this===g?c.promise():this,i?[b]:arguments)})}),b=null}).promise()},promise:function(b){return null!=b?a.extend(b,g):g}},h={};return g.pipe=g.then,a.each(e,function(a,b){var c=b[2],d=b[3];g[b[1]]=c.add,d&&c.add(function(){f=d},e[1^a][2].disable,e[2][2].lock),h[b[0]]=function(){return h[b[0]+"With"](this===h?g:this,arguments),this},h[b[0]+"With"]=c.fireWith}),g.promise(h),d&&d.call(h,h),h}var d;return d={Deferred:c,isPromise:function(a){return a&&"function"==typeof a.then},when:function(b){var d,e,f,g=0,h=[].slice,i=h.call(arguments),j=i.length,k=1!==j||b&&a.isFunction(b.promise)?j:0,l=1===k?b:c(),m=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?h.call(arguments):e,c===d?l.notifyWith(b,c):--k||l.resolveWith(b,c)}};if(j>1)for(d=new Array(j),e=new Array(j),f=new Array(j);j>g;g++)i[g]&&a.isFunction(i[g].promise)?i[g].promise().done(m(g,f,i)).fail(l.reject).progress(m(g,e,d)):--k;return k||l.resolveWith(f,i),l.promise()}}}),b("promise",["promise-builtin"],function(a){return a}),b("base",["dollar","promise"],function(b,c){function d(a){return function(){return h.apply(a,arguments)}}function e(a,b){return function(){return a.apply(b,arguments)}}function f(a){var b;return Object.create?Object.create(a):(b=function(){},b.prototype=a,new b)}var g=function(){},h=Function.call;return{version:"0.1.5",$:b,Deferred:c.Deferred,isPromise:c.isPromise,when:c.when,browser:function(a){var b={},c=a.match(/WebKit\/([\d.]+)/),d=a.match(/Chrome\/([\d.]+)/)||a.match(/CriOS\/([\d.]+)/),e=a.match(/MSIE\s([\d\.]+)/)||a.match(/(?:trident)(?:.*rv:([\w.]+))?/i),f=a.match(/Firefox\/([\d.]+)/),g=a.match(/Safari\/([\d.]+)/),h=a.match(/OPR\/([\d.]+)/);return c&&(b.webkit=parseFloat(c[1])),d&&(b.chrome=parseFloat(d[1])),e&&(b.ie=parseFloat(e[1])),f&&(b.firefox=parseFloat(f[1])),g&&(b.safari=parseFloat(g[1])),h&&(b.opera=parseFloat(h[1])),b}(navigator.userAgent),os:function(a){var b={},c=a.match(/(?:Android);?[\s\/]+([\d.]+)?/),d=a.match(/(?:iPad|iPod|iPhone).*OS\s([\d_]+)/);return c&&(b.android=parseFloat(c[1])),d&&(b.ios=parseFloat(d[1].replace(/_/g,"."))),b}(navigator.userAgent),inherits:function(a,c,d){var e;return"function"==typeof c?(e=c,c=null):e=c&&c.hasOwnProperty("constructor")?c.constructor:function(){return a.apply(this,arguments)},b.extend(!0,e,a,d||{}),e.__super__=a.prototype,e.prototype=f(a.prototype),c&&b.extend(!0,e.prototype,c),e},noop:g,bindFn:e,log:function(){return a.console?e(console.log,console):g}(),nextTick:function(){return function(a){setTimeout(a,1)}}(),slice:d([].slice),guid:function(){var a=0;return function(b){for(var c=(+new Date).toString(32),d=0;5>d;d++)c+=Math.floor(65535*Math.random()).toString(32);return(b||"wu_")+c+(a++).toString(32)}}(),formatSize:function(a,b,c){var d;for(c=c||["B","K","M","G","TB"];(d=c.shift())&&a>1024;)a/=1024;return("B"===d?a:a.toFixed(b||2))+d}}}),b("mediator",["base"],function(a){function b(a,b,c,d){return f.grep(a,function(a){return!(!a||b&&a.e!==b||c&&a.cb!==c&&a.cb._cb!==c||d&&a.ctx!==d)})}function c(a,b,c){f.each((a||"").split(h),function(a,d){c(d,b)})}function d(a,b){for(var c,d=!1,e=-1,f=a.length;++e1?void(d.isPlainObject(b)&&d.isPlainObject(c[a])?d.extend(c[a],b):c[a]=b):a?c[a]:c},getStats:function(){var a=this.request("get-stats");return a?{successNum:a.numOfSuccess,progressNum:a.numOfProgress,cancelNum:a.numOfCancel,invalidNum:a.numOfInvalid,uploadFailNum:a.numOfUploadFailed,queueNum:a.numOfQueue,interruptNum:a.numofInterrupt}:{}},trigger:function(a){var c=[].slice.call(arguments,1),e=this.options,f="on"+a.substring(0,1).toUpperCase()+a.substring(1);return b.trigger.apply(this,arguments)===!1||d.isFunction(e[f])&&e[f].apply(this,c)===!1||d.isFunction(this[f])&&this[f].apply(this,c)===!1||b.trigger.apply(b,[this,a].concat(c))===!1?!1:!0},destroy:function(){this.request("destroy",arguments),this.off()},request:a.noop}),a.create=c.create=function(a){return new c(a)},a.Uploader=c,c}),b("runtime/runtime",["base","mediator"],function(a,b){function c(b){this.options=d.extend({container:document.body},b),this.uid=a.guid("rt_")}var d=a.$,e={},f=function(a){for(var b in a)if(a.hasOwnProperty(b))return b;return null};return d.extend(c.prototype,{getContainer:function(){var a,b,c=this.options;return this._container?this._container:(a=d(c.container||document.body),b=d(document.createElement("div")),b.attr("id","rt_"+this.uid),b.css({position:"absolute",top:"0px",left:"0px",width:"1px",height:"1px",overflow:"hidden"}),a.append(b),a.addClass("webuploader-container"),this._container=b,this._parent=a,b)},init:a.noop,exec:a.noop,destroy:function(){this._container&&this._container.remove(),this._parent&&this._parent.removeClass("webuploader-container"),this.off()}}),c.orders="html5,flash",c.addRuntime=function(a,b){e[a]=b},c.hasRuntime=function(a){return!!(a?e[a]:f(e))},c.create=function(a,b){var g,h;if(b=b||c.orders,d.each(b.split(/\s*,\s*/g),function(){return e[this]?(g=this,!1):void 0}),g=g||f(e),!g)throw new Error("Runtime Error");return h=new e[g](a)},b.installTo(c.prototype),c}),b("runtime/client",["base","mediator","runtime/runtime"],function(a,b,c){function d(b,d){var f,g=a.Deferred();this.uid=a.guid("client_"),this.runtimeReady=function(a){return g.done(a)},this.connectRuntime=function(b,h){if(f)throw new Error("already connected!");return g.done(h),"string"==typeof b&&e.get(b)&&(f=e.get(b)),f=f||e.get(null,d),f?(a.$.extend(f.options,b),f.__promise.then(g.resolve),f.__client++):(f=c.create(b,b.runtimeOrder),f.__promise=g.promise(),f.once("ready",g.resolve),f.init(),e.add(f),f.__client=1),d&&(f.__standalone=d),f},this.getRuntime=function(){return f},this.disconnectRuntime=function(){f&&(f.__client--,f.__client<=0&&(e.remove(f),delete f.__promise,f.destroy()),f=null)},this.exec=function(){if(f){var c=a.slice(arguments);return b&&c.unshift(b),f.exec.apply(this,c)}},this.getRuid=function(){return f&&f.uid},this.destroy=function(a){return function(){a&&a.apply(this,arguments),this.trigger("destroy"),this.off(),this.exec("destroy"),this.disconnectRuntime()}}(this.destroy)}var e;return e=function(){var a={};return{add:function(b){a[b.uid]=b},get:function(b,c){var d;if(b)return a[b];for(d in a)if(!c||!a[d].__standalone)return a[d];return null},remove:function(b){delete a[b.uid]}}}(),b.installTo(d.prototype),d}),b("lib/blob",["base","runtime/client"],function(a,b){function c(a,c){var d=this;d.source=c,d.ruid=a,this.size=c.size||0,this.type=!c.type&&this.ext&&~"jpg,jpeg,png,gif,bmp".indexOf(this.ext)?"image/"+("jpg"===this.ext?"jpeg":this.ext):c.type||"application/octet-stream",b.call(d,"Blob"),this.uid=c.uid||this.uid,a&&d.connectRuntime(a)}return a.inherits(b,{constructor:c,slice:function(a,b){return this.exec("slice",a,b)},getSource:function(){return this.source}}),c}),b("lib/file",["base","lib/blob"],function(a,b){function c(a,c){var f;this.name=c.name||"untitled"+d++,f=e.exec(c.name)?RegExp.$1.toLowerCase():"",!f&&c.type&&(f=/\/(jpg|jpeg|png|gif|bmp)$/i.exec(c.type)?RegExp.$1.toLowerCase():"",this.name+="."+f),this.ext=f,this.lastModifiedDate=c.lastModifiedDate||(new Date).toLocaleString(),b.apply(this,arguments)}var d=1,e=/\.([^.]+)$/;return a.inherits(b,c)}),b("lib/filepicker",["base","runtime/client","lib/file"],function(b,c,d){function e(a){if(a=this.options=f.extend({},e.options,a),a.container=f(a.id),!a.container.length)throw new Error("按钮指定错误");a.innerHTML=a.innerHTML||a.label||a.container.html()||"",a.button=f(a.button||document.createElement("div")),a.button.html(a.innerHTML),a.container.html(a.button),c.call(this,"FilePicker",!0)}var f=b.$;return e.options={button:null,container:null,label:null,innerHTML:null,multiple:!0,accept:null,name:"file"},b.inherits(c,{constructor:e,init:function(){var c=this,e=c.options,g=e.button;g.addClass("webuploader-pick"),c.on("all",function(a){var b;switch(a){case"mouseenter":g.addClass("webuploader-pick-hover");break;case"mouseleave":g.removeClass("webuploader-pick-hover");break;case"change":b=c.exec("getFiles"),c.trigger("select",f.map(b,function(a){return a=new d(c.getRuid(),a),a._refer=e.container,a}),e.container)}}),c.connectRuntime(e,function(){c.refresh(),c.exec("init",e),c.trigger("ready")}),this._resizeHandler=b.bindFn(this.refresh,this),f(a).on("resize",this._resizeHandler)},refresh:function(){var a=this.getRuntime().getContainer(),b=this.options.button,c=b.outerWidth?b.outerWidth():b.width(),d=b.outerHeight?b.outerHeight():b.height(),e=b.offset();c&&d&&a.css({bottom:"auto",right:"auto",width:c+"px",height:d+"px"}).offset(e)},enable:function(){var a=this.options.button;a.removeClass("webuploader-pick-disable"),this.refresh()},disable:function(){var a=this.options.button;this.getRuntime().getContainer().css({top:"-99999px"}),a.addClass("webuploader-pick-disable")},destroy:function(){var b=this.options.button;f(a).off("resize",this._resizeHandler),b.removeClass("webuploader-pick-disable webuploader-pick-hover webuploader-pick")}}),e}),b("widgets/widget",["base","uploader"],function(a,b){function c(a){if(!a)return!1;var b=a.length,c=e.type(a);return 1===a.nodeType&&b?!0:"array"===c||"function"!==c&&"string"!==c&&(0===b||"number"==typeof b&&b>0&&b-1 in a)}function d(a){this.owner=a,this.options=a.options}var e=a.$,f=b.prototype._init,g=b.prototype.destroy,h={},i=[];return e.extend(d.prototype,{init:a.noop,invoke:function(a,b){var c=this.responseMap;return c&&a in c&&c[a]in this&&e.isFunction(this[c[a]])?this[c[a]].apply(this,b):h},request:function(){return this.owner.request.apply(this.owner,arguments)}}),e.extend(b.prototype,{_init:function(){var a=this,b=a._widgets=[],c=a.options.disableWidgets||"";return e.each(i,function(d,e){(!c||!~c.indexOf(e._name))&&b.push(new e(a))}),f.apply(a,arguments)},request:function(b,d,e){var f,g,i,j,k=0,l=this._widgets,m=l&&l.length,n=[],o=[];for(d=c(d)?d:[d];m>k;k++)f=l[k],g=f.invoke(b,d),g!==h&&(a.isPromise(g)?o.push(g):n.push(g));return e||o.length?(i=a.when.apply(a,o),j=i.pipe?"pipe":"then",i[j](function(){var b=a.Deferred(),c=arguments;return 1===c.length&&(c=c[0]),setTimeout(function(){b.resolve(c)},1),b.promise()})[e?j:"done"](e||a.noop)):n[0]},destroy:function(){g.apply(this,arguments),this._widgets=null}}),b.register=d.register=function(b,c){var f,g={init:"init",destroy:"destroy",name:"anonymous"};return 1===arguments.length?(c=b,e.each(c,function(a){return"_"===a[0]||"name"===a?void("name"===a&&(g.name=c.name)):void(g[a.replace(/[A-Z]/g,"-$&").toLowerCase()]=a)})):g=e.extend(g,b),c.responseMap=g,f=a.inherits(d,c),f._name=g.name,i.push(f),f},b.unRegister=d.unRegister=function(a){if(a&&"anonymous"!==a)for(var b=i.length;b--;)i[b]._name===a&&i.splice(b,1)},d}),b("widgets/filepicker",["base","uploader","lib/filepicker","widgets/widget"],function(a,b,c){var d=a.$;return d.extend(b.options,{pick:null,accept:null}),b.register({name:"picker",init:function(a){return this.pickers=[],a.pick&&this.addBtn(a.pick)},refresh:function(){d.each(this.pickers,function(){this.refresh()})},addBtn:function(b){var e=this,f=e.options,g=f.accept,h=[];if(b)return d.isPlainObject(b)||(b={id:b}),d(b.id).each(function(){var i,j,k;k=a.Deferred(),i=d.extend({},b,{accept:d.isPlainObject(g)?[g]:g,swf:f.swf,runtimeOrder:f.runtimeOrder,id:this}),j=new c(i),j.once("ready",k.resolve),j.on("select",function(a){e.owner.request("add-file",[a])}),j.init(),e.pickers.push(j),h.push(k.promise())}),a.when.apply(a,h)},disable:function(){d.each(this.pickers,function(){this.disable()})},enable:function(){d.each(this.pickers,function(){this.enable()})},destroy:function(){d.each(this.pickers,function(){this.destroy()}),this.pickers=null}})}),b("lib/image",["base","runtime/client","lib/blob"],function(a,b,c){function d(a){this.options=e.extend({},d.options,a),b.call(this,"Image"),this.on("load",function(){this._info=this.exec("info"),this._meta=this.exec("meta")})}var e=a.$;return d.options={quality:90,crop:!1,preserveHeaders:!1,allowMagnify:!1},a.inherits(b,{constructor:d,info:function(a){return a?(this._info=a,this):this._info},meta:function(a){return a?(this._meta=a,this):this._meta},loadFromBlob:function(a){var b=this,c=a.getRuid();this.connectRuntime(c,function(){b.exec("init",b.options),b.exec("loadFromBlob",a)})},resize:function(){var b=a.slice(arguments);return this.exec.apply(this,["resize"].concat(b))},crop:function(){var b=a.slice(arguments);return this.exec.apply(this,["crop"].concat(b))},getAsDataUrl:function(a){return this.exec("getAsDataUrl",a)},getAsBlob:function(a){var b=this.exec("getAsBlob",a);return new c(this.getRuid(),b)}}),d}),b("widgets/image",["base","uploader","lib/image","widgets/widget"],function(a,b,c){var d,e=a.$;return d=function(a){var b=0,c=[],d=function(){for(var d;c.length&&a>b;)d=c.shift(),b+=d[0],d[1]()};return function(a,e,f){c.push([e,f]),a.once("destroy",function(){b-=e,setTimeout(d,1)}),setTimeout(d,1)}}(5242880),e.extend(b.options,{thumb:{width:110,height:110,quality:70,allowMagnify:!0,crop:!0,preserveHeaders:!1,type:"image/jpeg"},compress:{width:1600,height:1600,quality:90,allowMagnify:!1,crop:!1,preserveHeaders:!0}}),b.register({name:"image",makeThumb:function(a,b,f,g){var h,i;return a=this.request("get-file",a),a.type.match(/^image/)?(h=e.extend({},this.options.thumb),e.isPlainObject(f)&&(h=e.extend(h,f),f=null),f=f||h.width,g=g||h.height,i=new c(h),i.once("load",function(){a._info=a._info||i.info(),a._meta=a._meta||i.meta(),1>=f&&f>0&&(f=a._info.width*f),1>=g&&g>0&&(g=a._info.height*g),i.resize(f,g)}),i.once("complete",function(){b(!1,i.getAsDataUrl(h.type)),i.destroy()}),i.once("error",function(a){b(a||!0),i.destroy()}),void d(i,a.source.size,function(){a._info&&i.info(a._info),a._meta&&i.meta(a._meta),i.loadFromBlob(a.source)})):void b(!0)},beforeSendFile:function(b){var d,f,g=this.options.compress||this.options.resize,h=g&&g.compressSize||0,i=g&&g.noCompressIfLarger||!1;return b=this.request("get-file",b),!g||!~"image/jpeg,image/jpg".indexOf(b.type)||b.size=a&&a>0&&(a=b._info.width*a),1>=c&&c>0&&(c=b._info.height*c),d.resize(a,c)}),d.once("complete",function(){var a,c;try{a=d.getAsBlob(g.type),c=b.size,(!i||a.sizeb;b++)if(c=this._queue[b],a===c.getStatus())return c;return null},sort:function(a){"function"==typeof a&&this._queue.sort(a)},getFiles:function(){for(var a,b=[].slice.call(arguments,0),c=[],d=0,f=this._queue.length;f>d;d++)a=this._queue[d],(!b.length||~e.inArray(a.getStatus(),b))&&c.push(a);return c},removeFile:function(a){var b=this._map[a.id];b&&(delete this._map[a.id],a.destroy(),this.stats.numofDeleted++)},_fileAdded:function(a){var b=this,c=this._map[a.id];c||(this._map[a.id]=a,a.on("statuschange",function(a,c){b._onFileStatusChange(a,c)}))},_onFileStatusChange:function(a,b){var c=this.stats;switch(b){case f.PROGRESS:c.numOfProgress--;break;case f.QUEUED:c.numOfQueue--;break;case f.ERROR:c.numOfUploadFailed--;break;case f.INVALID:c.numOfInvalid--;break;case f.INTERRUPT:c.numofInterrupt--}switch(a){case f.QUEUED:c.numOfQueue++;break;case f.PROGRESS:c.numOfProgress++;break;case f.ERROR:c.numOfUploadFailed++;break;case f.COMPLETE:c.numOfSuccess++;break;case f.CANCELLED:c.numOfCancel++;break;case f.INVALID:c.numOfInvalid++;break;case f.INTERRUPT:c.numofInterrupt++}}}),b.installTo(d.prototype),d}),b("widgets/queue",["base","uploader","queue","file","lib/file","runtime/client","widgets/widget"],function(a,b,c,d,e,f){var g=a.$,h=/\.\w+$/,i=d.Status;return b.register({name:"queue",init:function(b){var d,e,h,i,j,k,l,m=this;if(g.isPlainObject(b.accept)&&(b.accept=[b.accept]),b.accept){for(j=[],h=0,e=b.accept.length;e>h;h++)i=b.accept[h].extensions,i&&j.push(i);j.length&&(k="\\."+j.join(",").replace(/,/g,"$|\\.").replace(/\*/g,".*")+"$"),m.accept=new RegExp(k,"i")}return m.queue=new c,m.stats=m.queue.stats,"html5"===this.request("predict-runtime-type")?(d=a.Deferred(),this.placeholder=l=new f("Placeholder"),l.connectRuntime({runtimeOrder:"html5"},function(){m._ruid=l.getRuid(),d.resolve()}),d.promise()):void 0},_wrapFile:function(a){if(!(a instanceof d)){if(!(a instanceof e)){if(!this._ruid)throw new Error("Can't add external files.");a=new e(this._ruid,a)}a=new d(a)}return a},acceptFile:function(a){var b=!a||!a.size||this.accept&&h.exec(a.name)&&!this.accept.test(a.name);return!b},_addFile:function(a){var b=this;return a=b._wrapFile(a),b.owner.trigger("beforeFileQueued",a)?b.acceptFile(a)?(b.queue.append(a),b.owner.trigger("fileQueued",a),a):void b.owner.trigger("error","Q_TYPE_DENIED",a):void 0},getFile:function(a){return this.queue.getFile(a)},addFile:function(a){var b=this;a.length||(a=[a]),a=g.map(a,function(a){return b._addFile(a)}),b.owner.trigger("filesQueued",a),b.options.auto&&setTimeout(function(){b.request("start-upload")},20)},getStats:function(){return this.stats},removeFile:function(a,b){var c=this;a=a.id?a:c.queue.getFile(a),this.request("cancel-file",a),b&&this.queue.removeFile(a)},getFiles:function(){return this.queue.getFiles.apply(this.queue,arguments)},fetchFile:function(){return this.queue.fetch.apply(this.queue,arguments)},retry:function(a,b){var c,d,e,f=this;if(a)return a=a.id?a:f.queue.getFile(a),a.setStatus(i.QUEUED),void(b||f.request("start-upload"));for(c=f.queue.getFiles(i.ERROR),d=0,e=c.length;e>d;d++)a=c[d],a.setStatus(i.QUEUED);f.request("start-upload")},sortFiles:function(){return this.queue.sort.apply(this.queue,arguments)},reset:function(){this.owner.trigger("reset"),this.queue=new c,this.stats=this.queue.stats},destroy:function(){this.reset(),this.placeholder&&this.placeholder.destroy()}})}),b("widgets/runtime",["uploader","runtime/runtime","widgets/widget"],function(a,b){return a.support=function(){return b.hasRuntime.apply(b,arguments)},a.register({name:"runtime",init:function(){if(!this.predictRuntimeType())throw Error("Runtime Error")},predictRuntimeType:function(){var a,c,d=this.options.runtimeOrder||b.orders,e=this.type;if(!e)for(d=d.split(/\s*,\s*/g),a=0,c=d.length;c>a;a++)if(b.hasRuntime(d[a])){this.type=e=d[a];break}return e}})}),b("lib/transport",["base","runtime/client","mediator"],function(a,b,c){function d(a){var c=this;a=c.options=e.extend(!0,{},d.options,a||{}),b.call(this,"Transport"),this._blob=null,this._formData=a.formData||{},this._headers=a.headers||{},this.on("progress",this._timeout),this.on("load error",function(){c.trigger("progress",1),clearTimeout(c._timer)})}var e=a.$;return d.options={server:"",method:"POST",withCredentials:!1,fileVal:"file",timeout:12e4,formData:{},headers:{},sendAsBinary:!1},e.extend(d.prototype,{appendBlob:function(a,b,c){var d=this,e=d.options;d.getRuid()&&d.disconnectRuntime(),d.connectRuntime(b.ruid,function(){d.exec("init")}),d._blob=b,e.fileVal=a||e.fileVal,e.filename=c||e.filename},append:function(a,b){"object"==typeof a?e.extend(this._formData,a):this._formData[a]=b},setRequestHeader:function(a,b){"object"==typeof a?e.extend(this._headers,a):this._headers[a]=b},send:function(a){this.exec("send",a),this._timeout()},abort:function(){return clearTimeout(this._timer),this.exec("abort")},destroy:function(){this.trigger("destroy"),this.off(),this.exec("destroy"),this.disconnectRuntime()},getResponse:function(){return this.exec("getResponse")},getResponseAsJson:function(){return this.exec("getResponseAsJson")},getStatus:function(){return this.exec("getStatus")},_timeout:function(){var a=this,b=a.options.timeout;b&&(clearTimeout(a._timer),a._timer=setTimeout(function(){a.abort(),a.trigger("error","timeout")},b))}}),c.installTo(d.prototype),d}),b("widgets/upload",["base","uploader","file","lib/transport","widgets/widget"],function(a,b,c,d){function e(a,b){var c,d,e=[],f=a.source,g=f.size,h=b?Math.ceil(g/b):1,i=0,j=0;for(d={file:a,has:function(){return!!e.length},shift:function(){return e.shift()},unshift:function(a){e.unshift(a)}};h>j;)c=Math.min(b,g-i),e.push({file:a,start:i,end:b?i+c:g,total:g,chunks:h,chunk:j++,cuted:d}),i+=c;return a.blocks=e.concat(),a.remaning=e.length,d}var f=a.$,g=a.isPromise,h=c.Status;f.extend(b.options,{prepareNextFile:!1,chunked:!1,chunkSize:5242880,chunkRetry:2,threads:3,formData:{}}),b.register({name:"upload",init:function(){var b=this.owner,c=this;this.runing=!1,this.progress=!1,b.on("startUpload",function(){c.progress=!0}).on("uploadFinished",function(){c.progress=!1}),this.pool=[],this.stack=[],this.pending=[],this.remaning=0,this.__tick=a.bindFn(this._tick,this),b.on("uploadComplete",function(a){a.blocks&&f.each(a.blocks,function(a,b){b.transport&&(b.transport.abort(),b.transport.destroy()),delete b.transport}),delete a.blocks,delete a.remaning})},reset:function(){this.request("stop-upload",!0),this.runing=!1,this.pool=[],this.stack=[],this.pending=[],this.remaning=0,this._trigged=!1,this._promise=null},startUpload:function(b){var c=this;if(f.each(c.request("get-files",h.INVALID),function(){c.request("remove-file",this)}),b)if(b=b.id?b:c.request("get-file",b),b.getStatus()===h.INTERRUPT)f.each(c.pool,function(a,c){c.file===b&&c.transport&&c.transport.send()}),b.setStatus(h.QUEUED);else{if(b.getStatus()===h.PROGRESS)return;b.setStatus(h.QUEUED)}else f.each(c.request("get-files",[h.INITED]),function(){this.setStatus(h.QUEUED)});if(!c.runing){c.runing=!0;var d=[];f.each(c.pool,function(a,b){var e=b.file;e.getStatus()===h.INTERRUPT&&(d.push(e),c._trigged=!1,b.transport&&b.transport.send())});for(var b;b=d.shift();)b.setStatus(h.PROGRESS);b||f.each(c.request("get-files",h.INTERRUPT),function(){this.setStatus(h.PROGRESS)}),c._trigged=!1,a.nextTick(c.__tick),c.owner.trigger("startUpload")}},stopUpload:function(b,c){var d=this;if(b===!0&&(c=b,b=null),d.runing!==!1){if(b){if(b=b.id?b:d.request("get-file",b),b.getStatus()!==h.PROGRESS&&b.getStatus()!==h.QUEUED)return;return b.setStatus(h.INTERRUPT),f.each(d.pool,function(a,c){c.file===b&&(c.transport&&c.transport.abort(),d._putback(c),d._popBlock(c))}),a.nextTick(d.__tick)}d.runing=!1,this._promise&&this._promise.file&&this._promise.file.setStatus(h.INTERRUPT),c&&f.each(d.pool,function(a,b){b.transport&&b.transport.abort(),b.file.setStatus(h.INTERRUPT)}),d.owner.trigger("stopUpload")}},cancelFile:function(a){a=a.id?a:this.request("get-file",a),a.blocks&&f.each(a.blocks,function(a,b){var c=b.transport;c&&(c.abort(),c.destroy(),delete b.transport)}),a.setStatus(h.CANCELLED),this.owner.trigger("fileDequeued",a)},isInProgress:function(){return!!this.progress},_getStats:function(){return this.request("get-stats")},skipFile:function(a,b){a=a.id?a:this.request("get-file",a),a.setStatus(b||h.COMPLETE),a.skipped=!0,a.blocks&&f.each(a.blocks,function(a,b){var c=b.transport;c&&(c.abort(),c.destroy(),delete b.transport)}),this.owner.trigger("uploadSkip",a)},_tick:function(){var b,c,d=this,e=d.options;return d._promise?d._promise.always(d.__tick):void(d.pool.length1&&~"http,abort".indexOf(a)&&b.retried1&&f.extend(m,{chunks:b.chunks,chunk:b.chunk}),i.trigger("uploadBeforeSend",b,m,n),l.appendBlob(j.fileVal,b.blob,k.name),l.append(m),l.setRequestHeader(n),l.send()},_finishFile:function(a,b,c){var d=this.owner;return d.request("after-send-file",arguments,function(){a.setStatus(h.COMPLETE),d.trigger("uploadSuccess",a,b,c) -}).fail(function(b){a.getStatus()===h.PROGRESS&&a.setStatus(h.ERROR,b),d.trigger("uploadError",a,b)}).always(function(){d.trigger("uploadComplete",a)})},updateFileProgress:function(a){var b=0,c=0;a.blocks&&(f.each(a.blocks,function(a,b){c+=(b.percentage||0)*(b.end-b.start)}),b=c/a.size,this.owner.trigger("uploadProgress",a,b||0))}})}),b("widgets/log",["base","uploader","widgets/widget"],function(a,b){function c(a){var b=e.extend({},d,a),c=f.replace(/^(.*)\?/,"$1"+e.param(b)),g=new Image;g.src=c}var d,e=a.$,f=" http://static.tieba.baidu.com/tb/pms/img/st.gif??",g=(location.hostname||location.host||"protected").toLowerCase(),h=g&&/baidu/i.exec(g);if(h)return d={dv:3,master:"webuploader",online:/test/.exec(g)?0:1,module:"",product:g,type:0},b.register({name:"log",init:function(){var a=this.owner,b=0,d=0;a.on("error",function(a){c({type:2,c_error_code:a})}).on("uploadError",function(a,b){c({type:2,c_error_code:"UPLOAD_ERROR",c_reason:""+b})}).on("uploadComplete",function(a){b++,d+=a.size}).on("uploadFinished",function(){c({c_count:b,c_size:d}),b=d=0}),c({c_usage:1})}})}),b("runtime/compbase",[],function(){function a(a,b){this.owner=a,this.options=a.options,this.getRuntime=function(){return b},this.getRuid=function(){return b.uid},this.trigger=function(){return a.trigger.apply(a,arguments)}}return a}),b("runtime/html5/runtime",["base","runtime/runtime","runtime/compbase"],function(b,c,d){function e(){var a={},d=this,e=this.destroy;c.apply(d,arguments),d.type=f,d.exec=function(c,e){var f,h=this,i=h.uid,j=b.slice(arguments,2);return g[c]&&(f=a[i]=a[i]||new g[c](h,d),f[e])?f[e].apply(f,j):void 0},d.destroy=function(){return e&&e.apply(this,arguments)}}var f="html5",g={};return b.inherits(c,{constructor:e,init:function(){var a=this;setTimeout(function(){a.trigger("ready")},1)}}),e.register=function(a,c){var e=g[a]=b.inherits(d,c);return e},a.Blob&&a.FileReader&&a.DataView&&c.addRuntime(f,e),e}),b("runtime/html5/blob",["runtime/html5/runtime","lib/blob"],function(a,b){return a.register("Blob",{slice:function(a,c){var d=this.owner.source,e=d.slice||d.webkitSlice||d.mozSlice;return d=e.call(d,a,c),new b(this.getRuid(),d)}})}),b("runtime/html5/filepicker",["base","runtime/html5/runtime"],function(a,b){var c=a.$;return b.register("FilePicker",{init:function(){var a,b,d,e,f=this.getRuntime().getContainer(),g=this,h=g.owner,i=g.options,j=this.label=c(document.createElement("label")),k=this.input=c(document.createElement("input"));if(k.attr("type","file"),k.attr("name",i.name),k.addClass("webuploader-element-invisible"),j.on("click",function(){k.trigger("click")}),j.css({opacity:0,width:"100%",height:"100%",display:"block",cursor:"pointer",background:"#ffffff"}),i.multiple&&k.attr("multiple","multiple"),i.accept&&i.accept.length>0){for(a=[],b=0,d=i.accept.length;d>b;b++)a.push(i.accept[b].mimeTypes);k.attr("accept",a.join(","))}f.append(k),f.append(j),e=function(a){h.trigger(a.type)},k.on("change",function(a){var b,d=arguments.callee;g.files=a.target.files,b=this.cloneNode(!0),b.value=null,this.parentNode.replaceChild(b,this),k.off(),k=c(b).on("change",d).on("mouseenter mouseleave",e),h.trigger("change")}),j.on("mouseenter mouseleave",e)},getFiles:function(){return this.files},destroy:function(){this.input.off(),this.label.off()}})}),b("runtime/html5/util",["base"],function(b){var c=a.createObjectURL&&a||a.URL&&URL.revokeObjectURL&&URL||a.webkitURL,d=b.noop,e=d;return c&&(d=function(){return c.createObjectURL.apply(c,arguments)},e=function(){return c.revokeObjectURL.apply(c,arguments)}),{createObjectURL:d,revokeObjectURL:e,dataURL2Blob:function(a){var b,c,d,e,f,g;for(g=a.split(","),b=~g[0].indexOf("base64")?atob(g[1]):decodeURIComponent(g[1]),d=new ArrayBuffer(b.length),c=new Uint8Array(d),e=0;ei&&(d=h.getUint16(i),d>=65504&&65519>=d||65534===d)&&(e=h.getUint16(i+2)+2,!(i+e>h.byteLength));){if(f=b.parsers[d],!c&&f)for(g=0;g6&&(l.imageHead=a.slice?a.slice(2,k):new Uint8Array(a).subarray(2,k))}return l}},updateImageHead:function(a,b){var c,d,e,f=this._parse(a,!0);return e=2,f.imageHead&&(e=2+f.imageHead.byteLength),d=a.slice?a.slice(e):new Uint8Array(a).subarray(e),c=new Uint8Array(b.byteLength+2+d.byteLength),c[0]=255,c[1]=216,c.set(new Uint8Array(b),2),c.set(new Uint8Array(d),b.byteLength+2),c.buffer}},a.parseMeta=function(){return b.parse.apply(b,arguments)},a.updateImageHead=function(){return b.updateImageHead.apply(b,arguments)},b}),b("runtime/html5/imagemeta/exif",["base","runtime/html5/imagemeta"],function(a,b){var c={};return c.ExifMap=function(){return this},c.ExifMap.prototype.map={Orientation:274},c.ExifMap.prototype.get=function(a){return this[a]||this[this.map[a]]},c.exifTagTypes={1:{getValue:function(a,b){return a.getUint8(b)},size:1},2:{getValue:function(a,b){return String.fromCharCode(a.getUint8(b))},size:1,ascii:!0},3:{getValue:function(a,b,c){return a.getUint16(b,c)},size:2},4:{getValue:function(a,b,c){return a.getUint32(b,c)},size:4},5:{getValue:function(a,b,c){return a.getUint32(b,c)/a.getUint32(b+4,c)},size:8},9:{getValue:function(a,b,c){return a.getInt32(b,c)},size:4},10:{getValue:function(a,b,c){return a.getInt32(b,c)/a.getInt32(b+4,c)},size:8}},c.exifTagTypes[7]=c.exifTagTypes[1],c.getExifValue=function(b,d,e,f,g,h){var i,j,k,l,m,n,o=c.exifTagTypes[f];if(!o)return void a.log("Invalid Exif data: Invalid tag type.");if(i=o.size*g,j=i>4?d+b.getUint32(e+8,h):e+8,j+i>b.byteLength)return void a.log("Invalid Exif data: Invalid data offset.");if(1===g)return o.getValue(b,j,h);for(k=[],l=0;g>l;l+=1)k[l]=o.getValue(b,j+l*o.size,h);if(o.ascii){for(m="",l=0;lb.byteLength)return void a.log("Invalid Exif data: Invalid directory offset.");if(g=b.getUint16(d,e),h=d+2+12*g,h+4>b.byteLength)return void a.log("Invalid Exif data: Invalid directory size.");for(i=0;g>i;i+=1)this.parseExifTag(b,c,d+2+12*i,e,f);return b.getUint32(h,e)},c.parseExifData=function(b,d,e,f){var g,h,i=d+10;if(1165519206===b.getUint32(d+4)){if(i+8>b.byteLength)return void a.log("Invalid Exif data: Invalid segment size.");if(0!==b.getUint16(d+8))return void a.log("Invalid Exif data: Missing byte alignment offset.");switch(b.getUint16(i)){case 18761:g=!0;break;case 19789:g=!1;break;default:return void a.log("Invalid Exif data: Invalid byte alignment marker.")}if(42!==b.getUint16(i+2,g))return void a.log("Invalid Exif data: Missing TIFF marker.");h=b.getUint32(i+4,g),f.exif=new c.ExifMap,h=c.parseExifTags(b,i,i+h,g,f)}},b.parsers[65505].push(c.parseExifData),c}),b("runtime/html5/image",["base","runtime/html5/runtime","runtime/html5/util"],function(a,b,c){var d="data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs%3D";return b.register("Image",{modified:!1,init:function(){var a=this,b=new Image;b.onload=function(){a._info={type:a.type,width:this.width,height:this.height},a._metas||"image/jpeg"!==a.type?a.owner.trigger("load"):c.parseMeta(a._blob,function(b,c){a._metas=c,a.owner.trigger("load")})},b.onerror=function(){a.owner.trigger("error")},a._img=b},loadFromBlob:function(a){var b=this,d=b._img;b._blob=a,b.type=a.type,d.src=c.createObjectURL(a.getSource()),b.owner.once("load",function(){c.revokeObjectURL(d.src)})},resize:function(a,b){var c=this._canvas||(this._canvas=document.createElement("canvas"));this._resize(this._img,c,a,b),this._blob=null,this.modified=!0,this.owner.trigger("complete","resize")},crop:function(a,b,c,d,e){var f=this._canvas||(this._canvas=document.createElement("canvas")),g=this.options,h=this._img,i=h.naturalWidth,j=h.naturalHeight,k=this.getOrientation();e=e||1,f.width=c,f.height=d,g.preserveHeaders||this._rotate2Orientaion(f,k),this._renderImageToCanvas(f,h,-a,-b,i*e,j*e),this._blob=null,this.modified=!0,this.owner.trigger("complete","crop")},getAsBlob:function(a){var b,d=this._blob,e=this.options;if(a=a||this.type,this.modified||this.type!==a){if(b=this._canvas,"image/jpeg"===a){if(d=c.canvasToDataUrl(b,a,e.quality),e.preserveHeaders&&this._metas&&this._metas.imageHead)return d=c.dataURL2ArrayBuffer(d),d=c.updateImageHead(d,this._metas.imageHead),d=c.arrayBufferToBlob(d,a)}else d=c.canvasToDataUrl(b,a);d=c.dataURL2Blob(d)}return d},getAsDataUrl:function(a){var b=this.options;return a=a||this.type,"image/jpeg"===a?c.canvasToDataUrl(this._canvas,a,b.quality):this._canvas.toDataURL(a)},getOrientation:function(){return this._metas&&this._metas.exif&&this._metas.exif.get("Orientation")||1},info:function(a){return a?(this._info=a,this):this._info},meta:function(a){return a?(this._meta=a,this):this._meta},destroy:function(){var a=this._canvas;this._img.onload=null,a&&(a.getContext("2d").clearRect(0,0,a.width,a.height),a.width=a.height=0,this._canvas=null),this._img.src=d,this._img=this._blob=null},_resize:function(a,b,c,d){var e,f,g,h,i,j=this.options,k=a.width,l=a.height,m=this.getOrientation();~[5,6,7,8].indexOf(m)&&(c^=d,d^=c,c^=d),e=Math[j.crop?"max":"min"](c/k,d/l),j.allowMagnify||(e=Math.min(1,e)),f=k*e,g=l*e,j.crop?(b.width=c,b.height=d):(b.width=f,b.height=g),h=(b.width-f)/2,i=(b.height-g)/2,j.preserveHeaders||this._rotate2Orientaion(b,m),this._renderImageToCanvas(b,a,h,i,f,g)},_rotate2Orientaion:function(a,b){var c=a.width,d=a.height,e=a.getContext("2d");switch(b){case 5:case 6:case 7:case 8:a.width=d,a.height=c}switch(b){case 2:e.translate(c,0),e.scale(-1,1);break;case 3:e.translate(c,d),e.rotate(Math.PI);break;case 4:e.translate(0,d),e.scale(1,-1);break;case 5:e.rotate(.5*Math.PI),e.scale(1,-1);break;case 6:e.rotate(.5*Math.PI),e.translate(0,-d);break;case 7:e.rotate(.5*Math.PI),e.translate(c,-d),e.scale(-1,1);break;case 8:e.rotate(-.5*Math.PI),e.translate(-c,0)}},_renderImageToCanvas:function(){function b(a,b,c){var d,e,f,g=document.createElement("canvas"),h=g.getContext("2d"),i=0,j=c,k=c;for(g.width=1,g.height=c,h.drawImage(a,0,0),d=h.getImageData(0,0,1,c).data;k>i;)e=d[4*(k-1)+3],0===e?j=k:i=k,k=j+i>>1;return f=k/c,0===f?1:f}function c(a){var b,c,d=a.naturalWidth,e=a.naturalHeight;return d*e>1048576?(b=document.createElement("canvas"),b.width=b.height=1,c=b.getContext("2d"),c.drawImage(a,-d+1,0),0===c.getImageData(0,0,1,1).data[3]):!1}return a.os.ios?a.os.ios>=7?function(a,c,d,e,f,g){var h=c.naturalWidth,i=c.naturalHeight,j=b(c,h,i);return a.getContext("2d").drawImage(c,0,0,h*j,i*j,d,e,f,g)}:function(a,d,e,f,g,h){var i,j,k,l,m,n,o,p=d.naturalWidth,q=d.naturalHeight,r=a.getContext("2d"),s=c(d),t="image/jpeg"===this.type,u=1024,v=0,w=0;for(s&&(p/=2,q/=2),r.save(),i=document.createElement("canvas"),i.width=i.height=u,j=i.getContext("2d"),k=t?b(d,p,q):1,l=Math.ceil(u*g/p),m=Math.ceil(u*h/q/k);q>v;){for(n=0,o=0;p>n;)j.clearRect(0,0,u,u),j.drawImage(d,-n,-v),r.drawImage(i,0,0,u,u,e+o,f+w,l,m),n+=u,o+=l;v+=u,w+=m}r.restore(),i=j=null}:function(b){var c=a.slice(arguments,1),d=b.getContext("2d");d.drawImage.apply(d,c)}}()})}),b("runtime/html5/jpegencoder",[],function(){function a(a){function b(a){for(var b=[16,11,10,16,24,40,51,61,12,12,14,19,26,58,60,55,14,13,16,24,40,57,69,56,14,17,22,29,51,87,80,62,18,22,37,56,68,109,103,77,24,35,55,64,81,104,113,92,49,64,78,87,103,121,120,101,72,92,95,98,112,100,103,99],c=0;64>c;c++){var d=y((b[c]*a+50)/100);1>d?d=1:d>255&&(d=255),z[P[c]]=d}for(var e=[17,18,24,47,99,99,99,99,18,21,26,66,99,99,99,99,24,26,56,99,99,99,99,99,47,66,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99],f=0;64>f;f++){var g=y((e[f]*a+50)/100);1>g?g=1:g>255&&(g=255),A[P[f]]=g}for(var h=[1,1.387039845,1.306562965,1.175875602,1,.785694958,.5411961,.275899379],i=0,j=0;8>j;j++)for(var k=0;8>k;k++)B[i]=1/(z[P[i]]*h[j]*h[k]*8),C[i]=1/(A[P[i]]*h[j]*h[k]*8),i++}function c(a,b){for(var c=0,d=0,e=new Array,f=1;16>=f;f++){for(var g=1;g<=a[f];g++)e[b[d]]=[],e[b[d]][0]=c,e[b[d]][1]=f,d++,c++;c*=2}return e}function d(){t=c(Q,R),u=c(U,V),v=c(S,T),w=c(W,X)}function e(){for(var a=1,b=2,c=1;15>=c;c++){for(var d=a;b>d;d++)E[32767+d]=c,D[32767+d]=[],D[32767+d][1]=c,D[32767+d][0]=d;for(var e=-(b-1);-a>=e;e++)E[32767+e]=c,D[32767+e]=[],D[32767+e][1]=c,D[32767+e][0]=b-1+e;a<<=1,b<<=1}}function f(){for(var a=0;256>a;a++)O[a]=19595*a,O[a+256>>0]=38470*a,O[a+512>>0]=7471*a+32768,O[a+768>>0]=-11059*a,O[a+1024>>0]=-21709*a,O[a+1280>>0]=32768*a+8421375,O[a+1536>>0]=-27439*a,O[a+1792>>0]=-5329*a}function g(a){for(var b=a[0],c=a[1]-1;c>=0;)b&1<J&&(255==I?(h(255),h(0)):h(I),J=7,I=0)}function h(a){H.push(N[a])}function i(a){h(a>>8&255),h(255&a)}function j(a,b){var c,d,e,f,g,h,i,j,k,l=0,m=8,n=64;for(k=0;m>k;++k){c=a[l],d=a[l+1],e=a[l+2],f=a[l+3],g=a[l+4],h=a[l+5],i=a[l+6],j=a[l+7];var o=c+j,p=c-j,q=d+i,r=d-i,s=e+h,t=e-h,u=f+g,v=f-g,w=o+u,x=o-u,y=q+s,z=q-s;a[l]=w+y,a[l+4]=w-y;var A=.707106781*(z+x);a[l+2]=x+A,a[l+6]=x-A,w=v+t,y=t+r,z=r+p;var B=.382683433*(w-z),C=.5411961*w+B,D=1.306562965*z+B,E=.707106781*y,G=p+E,H=p-E;a[l+5]=H+C,a[l+3]=H-C,a[l+1]=G+D,a[l+7]=G-D,l+=8}for(l=0,k=0;m>k;++k){c=a[l],d=a[l+8],e=a[l+16],f=a[l+24],g=a[l+32],h=a[l+40],i=a[l+48],j=a[l+56];var I=c+j,J=c-j,K=d+i,L=d-i,M=e+h,N=e-h,O=f+g,P=f-g,Q=I+O,R=I-O,S=K+M,T=K-M;a[l]=Q+S,a[l+32]=Q-S;var U=.707106781*(T+R);a[l+16]=R+U,a[l+48]=R-U,Q=P+N,S=N+L,T=L+J;var V=.382683433*(Q-T),W=.5411961*Q+V,X=1.306562965*T+V,Y=.707106781*S,Z=J+Y,$=J-Y;a[l+40]=$+W,a[l+24]=$-W,a[l+8]=Z+X,a[l+56]=Z-X,l++}var _;for(k=0;n>k;++k)_=a[k]*b[k],F[k]=_>0?_+.5|0:_-.5|0;return F}function k(){i(65504),i(16),h(74),h(70),h(73),h(70),h(0),h(1),h(1),h(0),i(1),i(1),h(0),h(0)}function l(a,b){i(65472),i(17),h(8),i(b),i(a),h(3),h(1),h(17),h(0),h(2),h(17),h(1),h(3),h(17),h(1)}function m(){i(65499),i(132),h(0);for(var a=0;64>a;a++)h(z[a]);h(1);for(var b=0;64>b;b++)h(A[b])}function n(){i(65476),i(418),h(0);for(var a=0;16>a;a++)h(Q[a+1]);for(var b=0;11>=b;b++)h(R[b]);h(16);for(var c=0;16>c;c++)h(S[c+1]);for(var d=0;161>=d;d++)h(T[d]);h(1);for(var e=0;16>e;e++)h(U[e+1]);for(var f=0;11>=f;f++)h(V[f]);h(17);for(var g=0;16>g;g++)h(W[g+1]);for(var j=0;161>=j;j++)h(X[j])}function o(){i(65498),i(12),h(3),h(1),h(0),h(2),h(17),h(3),h(17),h(0),h(63),h(0)}function p(a,b,c,d,e){for(var f,h=e[0],i=e[240],k=16,l=63,m=64,n=j(a,b),o=0;m>o;++o)G[P[o]]=n[o];var p=G[0]-c;c=G[0],0==p?g(d[0]):(f=32767+p,g(d[E[f]]),g(D[f]));for(var q=63;q>0&&0==G[q];q--);if(0==q)return g(h),c;for(var r,s=1;q>=s;){for(var t=s;0==G[s]&&q>=s;++s);var u=s-t;if(u>=k){r=u>>4;for(var v=1;r>=v;++v)g(i);u=15&u}f=32767+G[s],g(e[(u<<4)+E[f]]),g(D[f]),s++}return q!=l&&g(h),c}function q(){for(var a=String.fromCharCode,b=0;256>b;b++)N[b]=a(b)}function r(a){if(0>=a&&(a=1),a>100&&(a=100),x!=a){var c=0;c=Math.floor(50>a?5e3/a:200-2*a),b(c),x=a}}function s(){a||(a=50),q(),d(),e(),f(),r(a)}var t,u,v,w,x,y=(Math.round,Math.floor),z=new Array(64),A=new Array(64),B=new Array(64),C=new Array(64),D=new Array(65535),E=new Array(65535),F=new Array(64),G=new Array(64),H=[],I=0,J=7,K=new Array(64),L=new Array(64),M=new Array(64),N=new Array(256),O=new Array(2048),P=[0,1,5,6,14,15,27,28,2,4,7,13,16,26,29,42,3,8,12,17,25,30,41,43,9,11,18,24,31,40,44,53,10,19,23,32,39,45,52,54,20,22,33,38,46,51,55,60,21,34,37,47,50,56,59,61,35,36,48,49,57,58,62,63],Q=[0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0],R=[0,1,2,3,4,5,6,7,8,9,10,11],S=[0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,125],T=[1,2,3,0,4,17,5,18,33,49,65,6,19,81,97,7,34,113,20,50,129,145,161,8,35,66,177,193,21,82,209,240,36,51,98,114,130,9,10,22,23,24,25,26,37,38,39,40,41,42,52,53,54,55,56,57,58,67,68,69,70,71,72,73,74,83,84,85,86,87,88,89,90,99,100,101,102,103,104,105,106,115,116,117,118,119,120,121,122,131,132,133,134,135,136,137,138,146,147,148,149,150,151,152,153,154,162,163,164,165,166,167,168,169,170,178,179,180,181,182,183,184,185,186,194,195,196,197,198,199,200,201,202,210,211,212,213,214,215,216,217,218,225,226,227,228,229,230,231,232,233,234,241,242,243,244,245,246,247,248,249,250],U=[0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0],V=[0,1,2,3,4,5,6,7,8,9,10,11],W=[0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,119],X=[0,1,2,3,17,4,5,33,49,6,18,65,81,7,97,113,19,34,50,129,8,20,66,145,161,177,193,9,35,51,82,240,21,98,114,209,10,22,36,52,225,37,241,23,24,25,26,38,39,40,41,42,53,54,55,56,57,58,67,68,69,70,71,72,73,74,83,84,85,86,87,88,89,90,99,100,101,102,103,104,105,106,115,116,117,118,119,120,121,122,130,131,132,133,134,135,136,137,138,146,147,148,149,150,151,152,153,154,162,163,164,165,166,167,168,169,170,178,179,180,181,182,183,184,185,186,194,195,196,197,198,199,200,201,202,210,211,212,213,214,215,216,217,218,226,227,228,229,230,231,232,233,234,242,243,244,245,246,247,248,249,250];this.encode=function(a,b){b&&r(b),H=new Array,I=0,J=7,i(65496),k(),m(),l(a.width,a.height),n(),o();var c=0,d=0,e=0;I=0,J=7,this.encode.displayName="_encode_";for(var f,h,j,q,s,x,y,z,A,D=a.data,E=a.width,F=a.height,G=4*E,N=0;F>N;){for(f=0;G>f;){for(s=G*N+f,x=s,y=-1,z=0,A=0;64>A;A++)z=A>>3,y=4*(7&A),x=s+z*G+y,N+z>=F&&(x-=G*(N+1+z-F)),f+y>=G&&(x-=f+y-G+4),h=D[x++],j=D[x++],q=D[x++],K[A]=(O[h]+O[j+256>>0]+O[q+512>>0]>>16)-128,L[A]=(O[h+768>>0]+O[j+1024>>0]+O[q+1280>>0]>>16)-128,M[A]=(O[h+1280>>0]+O[j+1536>>0]+O[q+1792>>0]>>16)-128;c=p(K,B,c,t,v),d=p(L,C,d,u,w),e=p(M,C,e,u,w),f+=32}N+=8}if(J>=0){var P=[];P[1]=J+1,P[0]=(1<=200&&b.status<300?(a._response=b.responseText,a.trigger("load")):b.status>=500&&b.status<600?(a._response=b.responseText,a.trigger("error","server")):a.trigger("error",a._status?"http":"abort")):void 0},a._xhr=b,b},_setRequestHeader:function(a,b){d.each(b,function(b,c){a.setRequestHeader(b,c)})},_parseJson:function(a){var b;try{b=JSON.parse(a)}catch(c){b={}}return b}})}),b("webuploader",["base","widgets/filepicker","widgets/image","widgets/queue","widgets/runtime","widgets/upload","widgets/log","runtime/html5/blob","runtime/html5/filepicker","runtime/html5/imagemeta/exif","runtime/html5/image","runtime/html5/androidpatch","runtime/html5/transport"],function(a){return a}),c("webuploader")}); \ No newline at end of file diff --git a/static/plugins/webuploader/webuploader.fis.js b/static/plugins/webuploader/webuploader.fis.js deleted file mode 100644 index c82ca434..00000000 --- a/static/plugins/webuploader/webuploader.fis.js +++ /dev/null @@ -1,8083 +0,0 @@ -/*! WebUploader 0.1.5 */ - - -var jQuery = require('example:widget/ui/jquery/jquery.js') - -return (function( root, factory ) { - var modules = {}, - - // 内部require, 简单不完全实现。 - // https://github.com/amdjs/amdjs-api/wiki/require - _require = function( deps, callback ) { - var args, len, i; - - // 如果deps不是数组,则直接返回指定module - if ( typeof deps === 'string' ) { - return getModule( deps ); - } else { - args = []; - for( len = deps.length, i = 0; i < len; i++ ) { - args.push( getModule( deps[ i ] ) ); - } - - return callback.apply( null, args ); - } - }, - - // 内部define,暂时不支持不指定id. - _define = function( id, deps, factory ) { - if ( arguments.length === 2 ) { - factory = deps; - deps = null; - } - - _require( deps || [], function() { - setModule( id, factory, arguments ); - }); - }, - - // 设置module, 兼容CommonJs写法。 - setModule = function( id, factory, args ) { - var module = { - exports: factory - }, - returned; - - if ( typeof factory === 'function' ) { - args.length || (args = [ _require, module.exports, module ]); - returned = factory.apply( null, args ); - returned !== undefined && (module.exports = returned); - } - - modules[ id ] = module.exports; - }, - - // 根据id获取module - getModule = function( id ) { - var module = modules[ id ] || root[ id ]; - - if ( !module ) { - throw new Error( '`' + id + '` is undefined' ); - } - - return module; - }, - - // 将所有modules,将路径ids装换成对象。 - exportsTo = function( obj ) { - var key, host, parts, part, last, ucFirst; - - // make the first character upper case. - ucFirst = function( str ) { - return str && (str.charAt( 0 ).toUpperCase() + str.substr( 1 )); - }; - - for ( key in modules ) { - host = obj; - - if ( !modules.hasOwnProperty( key ) ) { - continue; - } - - parts = key.split('/'); - last = ucFirst( parts.pop() ); - - while( (part = ucFirst( parts.shift() )) ) { - host[ part ] = host[ part ] || {}; - host = host[ part ]; - } - - host[ last ] = modules[ key ]; - } - - return obj; - }, - - makeExport = function( dollar ) { - root.__dollar = dollar; - - // exports every module. - return exportsTo( factory( root, _define, _require ) ); - }; - - return makeExport( jQuery ); -})( window, function( window, define, require ) { - - - /** - * @fileOverview jQuery or Zepto - */ - define('dollar-third',[],function() { - var $ = window.__dollar || window.jQuery || window.Zepto; - - if ( !$ ) { - throw new Error('jQuery or Zepto not found!'); - } - - return $; - }); - /** - * @fileOverview Dom 操作相关 - */ - define('dollar',[ - 'dollar-third' - ], function( _ ) { - return _; - }); - /** - * @fileOverview 使用jQuery的Promise - */ - define('promise-third',[ - 'dollar' - ], function( $ ) { - return { - Deferred: $.Deferred, - when: $.when, - - isPromise: function( anything ) { - return anything && typeof anything.then === 'function'; - } - }; - }); - /** - * @fileOverview Promise/A+ - */ - define('promise',[ - 'promise-third' - ], function( _ ) { - return _; - }); - /** - * @fileOverview 基础类方法。 - */ - - /** - * Web Uploader内部类的详细说明,以下提及的功能类,都可以在`WebUploader`这个变量中访问到。 - * - * As you know, Web Uploader的每个文件都是用过[AMD](https://github.com/amdjs/amdjs-api/wiki/AMD)规范中的`define`组织起来的, 每个Module都会有个module id. - * 默认module id为该文件的路径,而此路径将会转化成名字空间存放在WebUploader中。如: - * - * * module `base`:WebUploader.Base - * * module `file`: WebUploader.File - * * module `lib/dnd`: WebUploader.Lib.Dnd - * * module `runtime/html5/dnd`: WebUploader.Runtime.Html5.Dnd - * - * - * 以下文档中对类的使用可能省略掉了`WebUploader`前缀。 - * @module WebUploader - * @title WebUploader API文档 - */ - define('base',[ - 'dollar', - 'promise' - ], function( $, promise ) { - - var noop = function() {}, - call = Function.call; - - // http://jsperf.com/uncurrythis - // 反科里化 - function uncurryThis( fn ) { - return function() { - return call.apply( fn, arguments ); - }; - } - - function bindFn( fn, context ) { - return function() { - return fn.apply( context, arguments ); - }; - } - - function createObject( proto ) { - var f; - - if ( Object.create ) { - return Object.create( proto ); - } else { - f = function() {}; - f.prototype = proto; - return new f(); - } - } - - - /** - * 基础类,提供一些简单常用的方法。 - * @class Base - */ - return { - - /** - * @property {String} version 当前版本号。 - */ - version: '0.1.5', - - /** - * @property {jQuery|Zepto} $ 引用依赖的jQuery或者Zepto对象。 - */ - $: $, - - Deferred: promise.Deferred, - - isPromise: promise.isPromise, - - when: promise.when, - - /** - * @description 简单的浏览器检查结果。 - * - * * `webkit` webkit版本号,如果浏览器为非webkit内核,此属性为`undefined`。 - * * `chrome` chrome浏览器版本号,如果浏览器为chrome,此属性为`undefined`。 - * * `ie` ie浏览器版本号,如果浏览器为非ie,此属性为`undefined`。**暂不支持ie10+** - * * `firefox` firefox浏览器版本号,如果浏览器为非firefox,此属性为`undefined`。 - * * `safari` safari浏览器版本号,如果浏览器为非safari,此属性为`undefined`。 - * * `opera` opera浏览器版本号,如果浏览器为非opera,此属性为`undefined`。 - * - * @property {Object} [browser] - */ - browser: (function( ua ) { - var ret = {}, - webkit = ua.match( /WebKit\/([\d.]+)/ ), - chrome = ua.match( /Chrome\/([\d.]+)/ ) || - ua.match( /CriOS\/([\d.]+)/ ), - - ie = ua.match( /MSIE\s([\d\.]+)/ ) || - ua.match( /(?:trident)(?:.*rv:([\w.]+))?/i ), - firefox = ua.match( /Firefox\/([\d.]+)/ ), - safari = ua.match( /Safari\/([\d.]+)/ ), - opera = ua.match( /OPR\/([\d.]+)/ ); - - webkit && (ret.webkit = parseFloat( webkit[ 1 ] )); - chrome && (ret.chrome = parseFloat( chrome[ 1 ] )); - ie && (ret.ie = parseFloat( ie[ 1 ] )); - firefox && (ret.firefox = parseFloat( firefox[ 1 ] )); - safari && (ret.safari = parseFloat( safari[ 1 ] )); - opera && (ret.opera = parseFloat( opera[ 1 ] )); - - return ret; - })( navigator.userAgent ), - - /** - * @description 操作系统检查结果。 - * - * * `android` 如果在android浏览器环境下,此值为对应的android版本号,否则为`undefined`。 - * * `ios` 如果在ios浏览器环境下,此值为对应的ios版本号,否则为`undefined`。 - * @property {Object} [os] - */ - os: (function( ua ) { - var ret = {}, - - // osx = !!ua.match( /\(Macintosh\; Intel / ), - android = ua.match( /(?:Android);?[\s\/]+([\d.]+)?/ ), - ios = ua.match( /(?:iPad|iPod|iPhone).*OS\s([\d_]+)/ ); - - // osx && (ret.osx = true); - android && (ret.android = parseFloat( android[ 1 ] )); - ios && (ret.ios = parseFloat( ios[ 1 ].replace( /_/g, '.' ) )); - - return ret; - })( navigator.userAgent ), - - /** - * 实现类与类之间的继承。 - * @method inherits - * @grammar Base.inherits( super ) => child - * @grammar Base.inherits( super, protos ) => child - * @grammar Base.inherits( super, protos, statics ) => child - * @param {Class} super 父类 - * @param {Object | Function} [protos] 子类或者对象。如果对象中包含constructor,子类将是用此属性值。 - * @param {Function} [protos.constructor] 子类构造器,不指定的话将创建个临时的直接执行父类构造器的方法。 - * @param {Object} [statics] 静态属性或方法。 - * @return {Class} 返回子类。 - * @example - * function Person() { - * console.log( 'Super' ); - * } - * Person.prototype.hello = function() { - * console.log( 'hello' ); - * }; - * - * var Manager = Base.inherits( Person, { - * world: function() { - * console.log( 'World' ); - * } - * }); - * - * // 因为没有指定构造器,父类的构造器将会执行。 - * var instance = new Manager(); // => Super - * - * // 继承子父类的方法 - * instance.hello(); // => hello - * instance.world(); // => World - * - * // 子类的__super__属性指向父类 - * console.log( Manager.__super__ === Person ); // => true - */ - inherits: function( Super, protos, staticProtos ) { - var child; - - if ( typeof protos === 'function' ) { - child = protos; - protos = null; - } else if ( protos && protos.hasOwnProperty('constructor') ) { - child = protos.constructor; - } else { - child = function() { - return Super.apply( this, arguments ); - }; - } - - // 复制静态方法 - $.extend( true, child, Super, staticProtos || {} ); - - /* jshint camelcase: false */ - - // 让子类的__super__属性指向父类。 - child.__super__ = Super.prototype; - - // 构建原型,添加原型方法或属性。 - // 暂时用Object.create实现。 - child.prototype = createObject( Super.prototype ); - protos && $.extend( true, child.prototype, protos ); - - return child; - }, - - /** - * 一个不做任何事情的方法。可以用来赋值给默认的callback. - * @method noop - */ - noop: noop, - - /** - * 返回一个新的方法,此方法将已指定的`context`来执行。 - * @grammar Base.bindFn( fn, context ) => Function - * @method bindFn - * @example - * var doSomething = function() { - * console.log( this.name ); - * }, - * obj = { - * name: 'Object Name' - * }, - * aliasFn = Base.bind( doSomething, obj ); - * - * aliasFn(); // => Object Name - * - */ - bindFn: bindFn, - - /** - * 引用Console.log如果存在的话,否则引用一个[空函数noop](#WebUploader:Base.noop)。 - * @grammar Base.log( args... ) => undefined - * @method log - */ - log: (function() { - if ( window.console ) { - return bindFn( console.log, console ); - } - return noop; - })(), - - nextTick: (function() { - - return function( cb ) { - setTimeout( cb, 1 ); - }; - - // @bug 当浏览器不在当前窗口时就停了。 - // var next = window.requestAnimationFrame || - // window.webkitRequestAnimationFrame || - // window.mozRequestAnimationFrame || - // function( cb ) { - // window.setTimeout( cb, 1000 / 60 ); - // }; - - // // fix: Uncaught TypeError: Illegal invocation - // return bindFn( next, window ); - })(), - - /** - * 被[uncurrythis](http://www.2ality.com/2011/11/uncurrying-this.html)的数组slice方法。 - * 将用来将非数组对象转化成数组对象。 - * @grammar Base.slice( target, start[, end] ) => Array - * @method slice - * @example - * function doSomthing() { - * var args = Base.slice( arguments, 1 ); - * console.log( args ); - * } - * - * doSomthing( 'ignored', 'arg2', 'arg3' ); // => Array ["arg2", "arg3"] - */ - slice: uncurryThis( [].slice ), - - /** - * 生成唯一的ID - * @method guid - * @grammar Base.guid() => String - * @grammar Base.guid( prefx ) => String - */ - guid: (function() { - var counter = 0; - - return function( prefix ) { - var guid = (+new Date()).toString( 32 ), - i = 0; - - for ( ; i < 5; i++ ) { - guid += Math.floor( Math.random() * 65535 ).toString( 32 ); - } - - return (prefix || 'wu_') + guid + (counter++).toString( 32 ); - }; - })(), - - /** - * 格式化文件大小, 输出成带单位的字符串 - * @method formatSize - * @grammar Base.formatSize( size ) => String - * @grammar Base.formatSize( size, pointLength ) => String - * @grammar Base.formatSize( size, pointLength, units ) => String - * @param {Number} size 文件大小 - * @param {Number} [pointLength=2] 精确到的小数点数。 - * @param {Array} [units=[ 'B', 'K', 'M', 'G', 'TB' ]] 单位数组。从字节,到千字节,一直往上指定。如果单位数组里面只指定了到了K(千字节),同时文件大小大于M, 此方法的输出将还是显示成多少K. - * @example - * console.log( Base.formatSize( 100 ) ); // => 100B - * console.log( Base.formatSize( 1024 ) ); // => 1.00K - * console.log( Base.formatSize( 1024, 0 ) ); // => 1K - * console.log( Base.formatSize( 1024 * 1024 ) ); // => 1.00M - * console.log( Base.formatSize( 1024 * 1024 * 1024 ) ); // => 1.00G - * console.log( Base.formatSize( 1024 * 1024 * 1024, 0, ['B', 'KB', 'MB'] ) ); // => 1024MB - */ - formatSize: function( size, pointLength, units ) { - var unit; - - units = units || [ 'B', 'K', 'M', 'G', 'TB' ]; - - while ( (unit = units.shift()) && size > 1024 ) { - size = size / 1024; - } - - return (unit === 'B' ? size : size.toFixed( pointLength || 2 )) + - unit; - } - }; - }); - /** - * 事件处理类,可以独立使用,也可以扩展给对象使用。 - * @fileOverview Mediator - */ - define('mediator',[ - 'base' - ], function( Base ) { - var $ = Base.$, - slice = [].slice, - separator = /\s+/, - protos; - - // 根据条件过滤出事件handlers. - function findHandlers( arr, name, callback, context ) { - return $.grep( arr, function( handler ) { - return handler && - (!name || handler.e === name) && - (!callback || handler.cb === callback || - handler.cb._cb === callback) && - (!context || handler.ctx === context); - }); - } - - function eachEvent( events, callback, iterator ) { - // 不支持对象,只支持多个event用空格隔开 - $.each( (events || '').split( separator ), function( _, key ) { - iterator( key, callback ); - }); - } - - function triggerHanders( events, args ) { - var stoped = false, - i = -1, - len = events.length, - handler; - - while ( ++i < len ) { - handler = events[ i ]; - - if ( handler.cb.apply( handler.ctx2, args ) === false ) { - stoped = true; - break; - } - } - - return !stoped; - } - - protos = { - - /** - * 绑定事件。 - * - * `callback`方法在执行时,arguments将会来源于trigger的时候携带的参数。如 - * ```javascript - * var obj = {}; - * - * // 使得obj有事件行为 - * Mediator.installTo( obj ); - * - * obj.on( 'testa', function( arg1, arg2 ) { - * console.log( arg1, arg2 ); // => 'arg1', 'arg2' - * }); - * - * obj.trigger( 'testa', 'arg1', 'arg2' ); - * ``` - * - * 如果`callback`中,某一个方法`return false`了,则后续的其他`callback`都不会被执行到。 - * 切会影响到`trigger`方法的返回值,为`false`。 - * - * `on`还可以用来添加一个特殊事件`all`, 这样所有的事件触发都会响应到。同时此类`callback`中的arguments有一个不同处, - * 就是第一个参数为`type`,记录当前是什么事件在触发。此类`callback`的优先级比脚低,会再正常`callback`执行完后触发。 - * ```javascript - * obj.on( 'all', function( type, arg1, arg2 ) { - * console.log( type, arg1, arg2 ); // => 'testa', 'arg1', 'arg2' - * }); - * ``` - * - * @method on - * @grammar on( name, callback[, context] ) => self - * @param {String} name 事件名,支持多个事件用空格隔开 - * @param {Function} callback 事件处理器 - * @param {Object} [context] 事件处理器的上下文。 - * @return {self} 返回自身,方便链式 - * @chainable - * @class Mediator - */ - on: function( name, callback, context ) { - var me = this, - set; - - if ( !callback ) { - return this; - } - - set = this._events || (this._events = []); - - eachEvent( name, callback, function( name, callback ) { - var handler = { e: name }; - - handler.cb = callback; - handler.ctx = context; - handler.ctx2 = context || me; - handler.id = set.length; - - set.push( handler ); - }); - - return this; - }, - - /** - * 绑定事件,且当handler执行完后,自动解除绑定。 - * @method once - * @grammar once( name, callback[, context] ) => self - * @param {String} name 事件名 - * @param {Function} callback 事件处理器 - * @param {Object} [context] 事件处理器的上下文。 - * @return {self} 返回自身,方便链式 - * @chainable - */ - once: function( name, callback, context ) { - var me = this; - - if ( !callback ) { - return me; - } - - eachEvent( name, callback, function( name, callback ) { - var once = function() { - me.off( name, once ); - return callback.apply( context || me, arguments ); - }; - - once._cb = callback; - me.on( name, once, context ); - }); - - return me; - }, - - /** - * 解除事件绑定 - * @method off - * @grammar off( [name[, callback[, context] ] ] ) => self - * @param {String} [name] 事件名 - * @param {Function} [callback] 事件处理器 - * @param {Object} [context] 事件处理器的上下文。 - * @return {self} 返回自身,方便链式 - * @chainable - */ - off: function( name, cb, ctx ) { - var events = this._events; - - if ( !events ) { - return this; - } - - if ( !name && !cb && !ctx ) { - this._events = []; - return this; - } - - eachEvent( name, cb, function( name, cb ) { - $.each( findHandlers( events, name, cb, ctx ), function() { - delete events[ this.id ]; - }); - }); - - return this; - }, - - /** - * 触发事件 - * @method trigger - * @grammar trigger( name[, args...] ) => self - * @param {String} type 事件名 - * @param {*} [...] 任意参数 - * @return {Boolean} 如果handler中return false了,则返回false, 否则返回true - */ - trigger: function( type ) { - var args, events, allEvents; - - if ( !this._events || !type ) { - return this; - } - - args = slice.call( arguments, 1 ); - events = findHandlers( this._events, type ); - allEvents = findHandlers( this._events, 'all' ); - - return triggerHanders( events, args ) && - triggerHanders( allEvents, arguments ); - } - }; - - /** - * 中介者,它本身是个单例,但可以通过[installTo](#WebUploader:Mediator:installTo)方法,使任何对象具备事件行为。 - * 主要目的是负责模块与模块之间的合作,降低耦合度。 - * - * @class Mediator - */ - return $.extend({ - - /** - * 可以通过这个接口,使任何对象具备事件功能。 - * @method installTo - * @param {Object} obj 需要具备事件行为的对象。 - * @return {Object} 返回obj. - */ - installTo: function( obj ) { - return $.extend( obj, protos ); - } - - }, protos ); - }); - /** - * @fileOverview Uploader上传类 - */ - define('uploader',[ - 'base', - 'mediator' - ], function( Base, Mediator ) { - - var $ = Base.$; - - /** - * 上传入口类。 - * @class Uploader - * @constructor - * @grammar new Uploader( opts ) => Uploader - * @example - * var uploader = WebUploader.Uploader({ - * swf: 'path_of_swf/Uploader.swf', - * - * // 开起分片上传。 - * chunked: true - * }); - */ - function Uploader( opts ) { - this.options = $.extend( true, {}, Uploader.options, opts ); - this._init( this.options ); - } - - // default Options - // widgets中有相应扩展 - Uploader.options = {}; - Mediator.installTo( Uploader.prototype ); - - // 批量添加纯命令式方法。 - $.each({ - upload: 'start-upload', - stop: 'stop-upload', - getFile: 'get-file', - getFiles: 'get-files', - addFile: 'add-file', - addFiles: 'add-file', - sort: 'sort-files', - removeFile: 'remove-file', - cancelFile: 'cancel-file', - skipFile: 'skip-file', - retry: 'retry', - isInProgress: 'is-in-progress', - makeThumb: 'make-thumb', - md5File: 'md5-file', - getDimension: 'get-dimension', - addButton: 'add-btn', - predictRuntimeType: 'predict-runtime-type', - refresh: 'refresh', - disable: 'disable', - enable: 'enable', - reset: 'reset' - }, function( fn, command ) { - Uploader.prototype[ fn ] = function() { - return this.request( command, arguments ); - }; - }); - - $.extend( Uploader.prototype, { - state: 'pending', - - _init: function( opts ) { - var me = this; - - me.request( 'init', opts, function() { - me.state = 'ready'; - me.trigger('ready'); - }); - }, - - /** - * 获取或者设置Uploader配置项。 - * @method option - * @grammar option( key ) => * - * @grammar option( key, val ) => self - * @example - * - * // 初始状态图片上传前不会压缩 - * var uploader = new WebUploader.Uploader({ - * compress: null; - * }); - * - * // 修改后图片上传前,尝试将图片压缩到1600 * 1600 - * uploader.option( 'compress', { - * width: 1600, - * height: 1600 - * }); - */ - option: function( key, val ) { - var opts = this.options; - - // setter - if ( arguments.length > 1 ) { - - if ( $.isPlainObject( val ) && - $.isPlainObject( opts[ key ] ) ) { - $.extend( opts[ key ], val ); - } else { - opts[ key ] = val; - } - - } else { // getter - return key ? opts[ key ] : opts; - } - }, - - /** - * 获取文件统计信息。返回一个包含一下信息的对象。 - * * `successNum` 上传成功的文件数 - * * `progressNum` 上传中的文件数 - * * `cancelNum` 被删除的文件数 - * * `invalidNum` 无效的文件数 - * * `uploadFailNum` 上传失败的文件数 - * * `queueNum` 还在队列中的文件数 - * * `interruptNum` 被暂停的文件数 - * @method getStats - * @grammar getStats() => Object - */ - getStats: function() { - // return this._mgr.getStats.apply( this._mgr, arguments ); - var stats = this.request('get-stats'); - - return stats ? { - successNum: stats.numOfSuccess, - progressNum: stats.numOfProgress, - - // who care? - // queueFailNum: 0, - cancelNum: stats.numOfCancel, - invalidNum: stats.numOfInvalid, - uploadFailNum: stats.numOfUploadFailed, - queueNum: stats.numOfQueue, - interruptNum: stats.numofInterrupt - } : {}; - }, - - // 需要重写此方法来来支持opts.onEvent和instance.onEvent的处理器 - trigger: function( type/*, args...*/ ) { - var args = [].slice.call( arguments, 1 ), - opts = this.options, - name = 'on' + type.substring( 0, 1 ).toUpperCase() + - type.substring( 1 ); - - if ( - // 调用通过on方法注册的handler. - Mediator.trigger.apply( this, arguments ) === false || - - // 调用opts.onEvent - $.isFunction( opts[ name ] ) && - opts[ name ].apply( this, args ) === false || - - // 调用this.onEvent - $.isFunction( this[ name ] ) && - this[ name ].apply( this, args ) === false || - - // 广播所有uploader的事件。 - Mediator.trigger.apply( Mediator, - [ this, type ].concat( args ) ) === false ) { - - return false; - } - - return true; - }, - - /** - * 销毁 webuploader 实例 - * @method destroy - * @grammar destroy() => undefined - */ - destroy: function() { - this.request( 'destroy', arguments ); - this.off(); - }, - - // widgets/widget.js将补充此方法的详细文档。 - request: Base.noop - }); - - /** - * 创建Uploader实例,等同于new Uploader( opts ); - * @method create - * @class Base - * @static - * @grammar Base.create( opts ) => Uploader - */ - Base.create = Uploader.create = function( opts ) { - return new Uploader( opts ); - }; - - // 暴露Uploader,可以通过它来扩展业务逻辑。 - Base.Uploader = Uploader; - - return Uploader; - }); - /** - * @fileOverview Runtime管理器,负责Runtime的选择, 连接 - */ - define('runtime/runtime',[ - 'base', - 'mediator' - ], function( Base, Mediator ) { - - var $ = Base.$, - factories = {}, - - // 获取对象的第一个key - getFirstKey = function( obj ) { - for ( var key in obj ) { - if ( obj.hasOwnProperty( key ) ) { - return key; - } - } - return null; - }; - - // 接口类。 - function Runtime( options ) { - this.options = $.extend({ - container: document.body - }, options ); - this.uid = Base.guid('rt_'); - } - - $.extend( Runtime.prototype, { - - getContainer: function() { - var opts = this.options, - parent, container; - - if ( this._container ) { - return this._container; - } - - parent = $( opts.container || document.body ); - container = $( document.createElement('div') ); - - container.attr( 'id', 'rt_' + this.uid ); - container.css({ - position: 'absolute', - top: '0px', - left: '0px', - width: '1px', - height: '1px', - overflow: 'hidden' - }); - - parent.append( container ); - parent.addClass('webuploader-container'); - this._container = container; - this._parent = parent; - return container; - }, - - init: Base.noop, - exec: Base.noop, - - destroy: function() { - this._container && this._container.remove(); - this._parent && this._parent.removeClass('webuploader-container'); - this.off(); - } - }); - - Runtime.orders = 'html5,flash'; - - - /** - * 添加Runtime实现。 - * @param {String} type 类型 - * @param {Runtime} factory 具体Runtime实现。 - */ - Runtime.addRuntime = function( type, factory ) { - factories[ type ] = factory; - }; - - Runtime.hasRuntime = function( type ) { - return !!(type ? factories[ type ] : getFirstKey( factories )); - }; - - Runtime.create = function( opts, orders ) { - var type, runtime; - - orders = orders || Runtime.orders; - $.each( orders.split( /\s*,\s*/g ), function() { - if ( factories[ this ] ) { - type = this; - return false; - } - }); - - type = type || getFirstKey( factories ); - - if ( !type ) { - throw new Error('Runtime Error'); - } - - runtime = new factories[ type ]( opts ); - return runtime; - }; - - Mediator.installTo( Runtime.prototype ); - return Runtime; - }); - - /** - * @fileOverview Runtime管理器,负责Runtime的选择, 连接 - */ - define('runtime/client',[ - 'base', - 'mediator', - 'runtime/runtime' - ], function( Base, Mediator, Runtime ) { - - var cache; - - cache = (function() { - var obj = {}; - - return { - add: function( runtime ) { - obj[ runtime.uid ] = runtime; - }, - - get: function( ruid, standalone ) { - var i; - - if ( ruid ) { - return obj[ ruid ]; - } - - for ( i in obj ) { - // 有些类型不能重用,比如filepicker. - if ( standalone && obj[ i ].__standalone ) { - continue; - } - - return obj[ i ]; - } - - return null; - }, - - remove: function( runtime ) { - delete obj[ runtime.uid ]; - } - }; - })(); - - function RuntimeClient( component, standalone ) { - var deferred = Base.Deferred(), - runtime; - - this.uid = Base.guid('client_'); - - // 允许runtime没有初始化之前,注册一些方法在初始化后执行。 - this.runtimeReady = function( cb ) { - return deferred.done( cb ); - }; - - this.connectRuntime = function( opts, cb ) { - - // already connected. - if ( runtime ) { - throw new Error('already connected!'); - } - - deferred.done( cb ); - - if ( typeof opts === 'string' && cache.get( opts ) ) { - runtime = cache.get( opts ); - } - - // 像filePicker只能独立存在,不能公用。 - runtime = runtime || cache.get( null, standalone ); - - // 需要创建 - if ( !runtime ) { - runtime = Runtime.create( opts, opts.runtimeOrder ); - runtime.__promise = deferred.promise(); - runtime.once( 'ready', deferred.resolve ); - runtime.init(); - cache.add( runtime ); - runtime.__client = 1; - } else { - // 来自cache - Base.$.extend( runtime.options, opts ); - runtime.__promise.then( deferred.resolve ); - runtime.__client++; - } - - standalone && (runtime.__standalone = standalone); - return runtime; - }; - - this.getRuntime = function() { - return runtime; - }; - - this.disconnectRuntime = function() { - if ( !runtime ) { - return; - } - - runtime.__client--; - - if ( runtime.__client <= 0 ) { - cache.remove( runtime ); - delete runtime.__promise; - runtime.destroy(); - } - - runtime = null; - }; - - this.exec = function() { - if ( !runtime ) { - return; - } - - var args = Base.slice( arguments ); - component && args.unshift( component ); - - return runtime.exec.apply( this, args ); - }; - - this.getRuid = function() { - return runtime && runtime.uid; - }; - - this.destroy = (function( destroy ) { - return function() { - destroy && destroy.apply( this, arguments ); - this.trigger('destroy'); - this.off(); - this.exec('destroy'); - this.disconnectRuntime(); - }; - })( this.destroy ); - } - - Mediator.installTo( RuntimeClient.prototype ); - return RuntimeClient; - }); - /** - * @fileOverview 错误信息 - */ - define('lib/dnd',[ - 'base', - 'mediator', - 'runtime/client' - ], function( Base, Mediator, RuntimeClent ) { - - var $ = Base.$; - - function DragAndDrop( opts ) { - opts = this.options = $.extend({}, DragAndDrop.options, opts ); - - opts.container = $( opts.container ); - - if ( !opts.container.length ) { - return; - } - - RuntimeClent.call( this, 'DragAndDrop' ); - } - - DragAndDrop.options = { - accept: null, - disableGlobalDnd: false - }; - - Base.inherits( RuntimeClent, { - constructor: DragAndDrop, - - init: function() { - var me = this; - - me.connectRuntime( me.options, function() { - me.exec('init'); - me.trigger('ready'); - }); - } - }); - - Mediator.installTo( DragAndDrop.prototype ); - - return DragAndDrop; - }); - /** - * @fileOverview 组件基类。 - */ - define('widgets/widget',[ - 'base', - 'uploader' - ], function( Base, Uploader ) { - - var $ = Base.$, - _init = Uploader.prototype._init, - _destroy = Uploader.prototype.destroy, - IGNORE = {}, - widgetClass = []; - - function isArrayLike( obj ) { - if ( !obj ) { - return false; - } - - var length = obj.length, - type = $.type( obj ); - - if ( obj.nodeType === 1 && length ) { - return true; - } - - return type === 'array' || type !== 'function' && type !== 'string' && - (length === 0 || typeof length === 'number' && length > 0 && - (length - 1) in obj); - } - - function Widget( uploader ) { - this.owner = uploader; - this.options = uploader.options; - } - - $.extend( Widget.prototype, { - - init: Base.noop, - - // 类Backbone的事件监听声明,监听uploader实例上的事件 - // widget直接无法监听事件,事件只能通过uploader来传递 - invoke: function( apiName, args ) { - - /* - { - 'make-thumb': 'makeThumb' - } - */ - var map = this.responseMap; - - // 如果无API响应声明则忽略 - if ( !map || !(apiName in map) || !(map[ apiName ] in this) || - !$.isFunction( this[ map[ apiName ] ] ) ) { - - return IGNORE; - } - - return this[ map[ apiName ] ].apply( this, args ); - - }, - - /** - * 发送命令。当传入`callback`或者`handler`中返回`promise`时。返回一个当所有`handler`中的promise都完成后完成的新`promise`。 - * @method request - * @grammar request( command, args ) => * | Promise - * @grammar request( command, args, callback ) => Promise - * @for Uploader - */ - request: function() { - return this.owner.request.apply( this.owner, arguments ); - } - }); - - // 扩展Uploader. - $.extend( Uploader.prototype, { - - /** - * @property {String | Array} [disableWidgets=undefined] - * @namespace options - * @for Uploader - * @description 默认所有 Uploader.register 了的 widget 都会被加载,如果禁用某一部分,请通过此 option 指定黑名单。 - */ - - // 覆写_init用来初始化widgets - _init: function() { - var me = this, - widgets = me._widgets = [], - deactives = me.options.disableWidgets || ''; - - $.each( widgetClass, function( _, klass ) { - (!deactives || !~deactives.indexOf( klass._name )) && - widgets.push( new klass( me ) ); - }); - - return _init.apply( me, arguments ); - }, - - request: function( apiName, args, callback ) { - var i = 0, - widgets = this._widgets, - len = widgets && widgets.length, - rlts = [], - dfds = [], - widget, rlt, promise, key; - - args = isArrayLike( args ) ? args : [ args ]; - - for ( ; i < len; i++ ) { - widget = widgets[ i ]; - rlt = widget.invoke( apiName, args ); - - if ( rlt !== IGNORE ) { - - // Deferred对象 - if ( Base.isPromise( rlt ) ) { - dfds.push( rlt ); - } else { - rlts.push( rlt ); - } - } - } - - // 如果有callback,则用异步方式。 - if ( callback || dfds.length ) { - promise = Base.when.apply( Base, dfds ); - key = promise.pipe ? 'pipe' : 'then'; - - // 很重要不能删除。删除了会死循环。 - // 保证执行顺序。让callback总是在下一个 tick 中执行。 - return promise[ key ](function() { - var deferred = Base.Deferred(), - args = arguments; - - if ( args.length === 1 ) { - args = args[ 0 ]; - } - - setTimeout(function() { - deferred.resolve( args ); - }, 1 ); - - return deferred.promise(); - })[ callback ? key : 'done' ]( callback || Base.noop ); - } else { - return rlts[ 0 ]; - } - }, - - destroy: function() { - _destroy.apply( this, arguments ); - this._widgets = null; - } - }); - - /** - * 添加组件 - * @grammar Uploader.register(proto); - * @grammar Uploader.register(map, proto); - * @param {object} responseMap API 名称与函数实现的映射 - * @param {object} proto 组件原型,构造函数通过 constructor 属性定义 - * @method Uploader.register - * @for Uploader - * @example - * Uploader.register({ - * 'make-thumb': 'makeThumb' - * }, { - * init: function( options ) {}, - * makeThumb: function() {} - * }); - * - * Uploader.register({ - * 'make-thumb': function() { - * - * } - * }); - */ - Uploader.register = Widget.register = function( responseMap, widgetProto ) { - var map = { init: 'init', destroy: 'destroy', name: 'anonymous' }, - klass; - - if ( arguments.length === 1 ) { - widgetProto = responseMap; - - // 自动生成 map 表。 - $.each(widgetProto, function(key) { - if ( key[0] === '_' || key === 'name' ) { - key === 'name' && (map.name = widgetProto.name); - return; - } - - map[key.replace(/[A-Z]/g, '-$&').toLowerCase()] = key; - }); - - } else { - map = $.extend( map, responseMap ); - } - - widgetProto.responseMap = map; - klass = Base.inherits( Widget, widgetProto ); - klass._name = map.name; - widgetClass.push( klass ); - - return klass; - }; - - /** - * 删除插件,只有在注册时指定了名字的才能被删除。 - * @grammar Uploader.unRegister(name); - * @param {string} name 组件名字 - * @method Uploader.unRegister - * @for Uploader - * @example - * - * Uploader.register({ - * name: 'custom', - * - * 'make-thumb': function() { - * - * } - * }); - * - * Uploader.unRegister('custom'); - */ - Uploader.unRegister = Widget.unRegister = function( name ) { - if ( !name || name === 'anonymous' ) { - return; - } - - // 删除指定的插件。 - for ( var i = widgetClass.length; i--; ) { - if ( widgetClass[i]._name === name ) { - widgetClass.splice(i, 1) - } - } - }; - - return Widget; - }); - /** - * @fileOverview DragAndDrop Widget。 - */ - define('widgets/filednd',[ - 'base', - 'uploader', - 'lib/dnd', - 'widgets/widget' - ], function( Base, Uploader, Dnd ) { - var $ = Base.$; - - Uploader.options.dnd = ''; - - /** - * @property {Selector} [dnd=undefined] 指定Drag And Drop拖拽的容器,如果不指定,则不启动。 - * @namespace options - * @for Uploader - */ - - /** - * @property {Selector} [disableGlobalDnd=false] 是否禁掉整个页面的拖拽功能,如果不禁用,图片拖进来的时候会默认被浏览器打开。 - * @namespace options - * @for Uploader - */ - - /** - * @event dndAccept - * @param {DataTransferItemList} items DataTransferItem - * @description 阻止此事件可以拒绝某些类型的文件拖入进来。目前只有 chrome 提供这样的 API,且只能通过 mime-type 验证。 - * @for Uploader - */ - return Uploader.register({ - name: 'dnd', - - init: function( opts ) { - - if ( !opts.dnd || - this.request('predict-runtime-type') !== 'html5' ) { - return; - } - - var me = this, - deferred = Base.Deferred(), - options = $.extend({}, { - disableGlobalDnd: opts.disableGlobalDnd, - container: opts.dnd, - accept: opts.accept - }), - dnd; - - this.dnd = dnd = new Dnd( options ); - - dnd.once( 'ready', deferred.resolve ); - dnd.on( 'drop', function( files ) { - me.request( 'add-file', [ files ]); - }); - - // 检测文件是否全部允许添加。 - dnd.on( 'accept', function( items ) { - return me.owner.trigger( 'dndAccept', items ); - }); - - dnd.init(); - - return deferred.promise(); - }, - - destroy: function() { - this.dnd && this.dnd.destroy(); - } - }); - }); - - /** - * @fileOverview 错误信息 - */ - define('lib/filepaste',[ - 'base', - 'mediator', - 'runtime/client' - ], function( Base, Mediator, RuntimeClent ) { - - var $ = Base.$; - - function FilePaste( opts ) { - opts = this.options = $.extend({}, opts ); - opts.container = $( opts.container || document.body ); - RuntimeClent.call( this, 'FilePaste' ); - } - - Base.inherits( RuntimeClent, { - constructor: FilePaste, - - init: function() { - var me = this; - - me.connectRuntime( me.options, function() { - me.exec('init'); - me.trigger('ready'); - }); - } - }); - - Mediator.installTo( FilePaste.prototype ); - - return FilePaste; - }); - /** - * @fileOverview 组件基类。 - */ - define('widgets/filepaste',[ - 'base', - 'uploader', - 'lib/filepaste', - 'widgets/widget' - ], function( Base, Uploader, FilePaste ) { - var $ = Base.$; - - /** - * @property {Selector} [paste=undefined] 指定监听paste事件的容器,如果不指定,不启用此功能。此功能为通过粘贴来添加截屏的图片。建议设置为`document.body`. - * @namespace options - * @for Uploader - */ - return Uploader.register({ - name: 'paste', - - init: function( opts ) { - - if ( !opts.paste || - this.request('predict-runtime-type') !== 'html5' ) { - return; - } - - var me = this, - deferred = Base.Deferred(), - options = $.extend({}, { - container: opts.paste, - accept: opts.accept - }), - paste; - - this.paste = paste = new FilePaste( options ); - - paste.once( 'ready', deferred.resolve ); - paste.on( 'paste', function( files ) { - me.owner.request( 'add-file', [ files ]); - }); - paste.init(); - - return deferred.promise(); - }, - - destroy: function() { - this.paste && this.paste.destroy(); - } - }); - }); - /** - * @fileOverview Blob - */ - define('lib/blob',[ - 'base', - 'runtime/client' - ], function( Base, RuntimeClient ) { - - function Blob( ruid, source ) { - var me = this; - - me.source = source; - me.ruid = ruid; - this.size = source.size || 0; - - // 如果没有指定 mimetype, 但是知道文件后缀。 - if ( !source.type && this.ext && - ~'jpg,jpeg,png,gif,bmp'.indexOf( this.ext ) ) { - this.type = 'image/' + (this.ext === 'jpg' ? 'jpeg' : this.ext); - } else { - this.type = source.type || 'application/octet-stream'; - } - - RuntimeClient.call( me, 'Blob' ); - this.uid = source.uid || this.uid; - - if ( ruid ) { - me.connectRuntime( ruid ); - } - } - - Base.inherits( RuntimeClient, { - constructor: Blob, - - slice: function( start, end ) { - return this.exec( 'slice', start, end ); - }, - - getSource: function() { - return this.source; - } - }); - - return Blob; - }); - /** - * 为了统一化Flash的File和HTML5的File而存在。 - * 以至于要调用Flash里面的File,也可以像调用HTML5版本的File一下。 - * @fileOverview File - */ - define('lib/file',[ - 'base', - 'lib/blob' - ], function( Base, Blob ) { - - var uid = 1, - rExt = /\.([^.]+)$/; - - function File( ruid, file ) { - var ext; - - this.name = file.name || ('untitled' + uid++); - ext = rExt.exec( file.name ) ? RegExp.$1.toLowerCase() : ''; - - // todo 支持其他类型文件的转换。 - // 如果有 mimetype, 但是文件名里面没有找出后缀规律 - if ( !ext && file.type ) { - ext = /\/(jpg|jpeg|png|gif|bmp)$/i.exec( file.type ) ? - RegExp.$1.toLowerCase() : ''; - this.name += '.' + ext; - } - - this.ext = ext; - this.lastModifiedDate = file.lastModifiedDate || - (new Date()).toLocaleString(); - - Blob.apply( this, arguments ); - } - - return Base.inherits( Blob, File ); - }); - - /** - * @fileOverview 错误信息 - */ - define('lib/filepicker',[ - 'base', - 'runtime/client', - 'lib/file' - ], function( Base, RuntimeClent, File ) { - - var $ = Base.$; - - function FilePicker( opts ) { - opts = this.options = $.extend({}, FilePicker.options, opts ); - - opts.container = $( opts.id ); - - if ( !opts.container.length ) { - throw new Error('按钮指定错误'); - } - - opts.innerHTML = opts.innerHTML || opts.label || - opts.container.html() || ''; - - opts.button = $( opts.button || document.createElement('div') ); - opts.button.html( opts.innerHTML ); - opts.container.html( opts.button ); - - RuntimeClent.call( this, 'FilePicker', true ); - } - - FilePicker.options = { - button: null, - container: null, - label: null, - innerHTML: null, - multiple: true, - accept: null, - name: 'file' - }; - - Base.inherits( RuntimeClent, { - constructor: FilePicker, - - init: function() { - var me = this, - opts = me.options, - button = opts.button; - - button.addClass('webuploader-pick'); - - me.on( 'all', function( type ) { - var files; - - switch ( type ) { - case 'mouseenter': - button.addClass('webuploader-pick-hover'); - break; - - case 'mouseleave': - button.removeClass('webuploader-pick-hover'); - break; - - case 'change': - files = me.exec('getFiles'); - me.trigger( 'select', $.map( files, function( file ) { - file = new File( me.getRuid(), file ); - - // 记录来源。 - file._refer = opts.container; - return file; - }), opts.container ); - break; - } - }); - - me.connectRuntime( opts, function() { - me.refresh(); - me.exec( 'init', opts ); - me.trigger('ready'); - }); - - this._resizeHandler = Base.bindFn( this.refresh, this ); - $( window ).on( 'resize', this._resizeHandler ); - }, - - refresh: function() { - var shimContainer = this.getRuntime().getContainer(), - button = this.options.button, - width = button.outerWidth ? - button.outerWidth() : button.width(), - - height = button.outerHeight ? - button.outerHeight() : button.height(), - - pos = button.offset(); - - width && height && shimContainer.css({ - bottom: 'auto', - right: 'auto', - width: width + 'px', - height: height + 'px' - }).offset( pos ); - }, - - enable: function() { - var btn = this.options.button; - - btn.removeClass('webuploader-pick-disable'); - this.refresh(); - }, - - disable: function() { - var btn = this.options.button; - - this.getRuntime().getContainer().css({ - top: '-99999px' - }); - - btn.addClass('webuploader-pick-disable'); - }, - - destroy: function() { - var btn = this.options.button; - $( window ).off( 'resize', this._resizeHandler ); - btn.removeClass('webuploader-pick-disable webuploader-pick-hover ' + - 'webuploader-pick'); - } - }); - - return FilePicker; - }); - - /** - * @fileOverview 文件选择相关 - */ - define('widgets/filepicker',[ - 'base', - 'uploader', - 'lib/filepicker', - 'widgets/widget' - ], function( Base, Uploader, FilePicker ) { - var $ = Base.$; - - $.extend( Uploader.options, { - - /** - * @property {Selector | Object} [pick=undefined] - * @namespace options - * @for Uploader - * @description 指定选择文件的按钮容器,不指定则不创建按钮。 - * - * * `id` {Seletor|dom} 指定选择文件的按钮容器,不指定则不创建按钮。**注意** 这里虽然写的是 id, 但是不是只支持 id, 还支持 class, 或者 dom 节点。 - * * `label` {String} 请采用 `innerHTML` 代替 - * * `innerHTML` {String} 指定按钮文字。不指定时优先从指定的容器中看是否自带文字。 - * * `multiple` {Boolean} 是否开起同时选择多个文件能力。 - */ - pick: null, - - /** - * @property {Arroy} [accept=null] - * @namespace options - * @for Uploader - * @description 指定接受哪些类型的文件。 由于目前还有ext转mimeType表,所以这里需要分开指定。 - * - * * `title` {String} 文字描述 - * * `extensions` {String} 允许的文件后缀,不带点,多个用逗号分割。 - * * `mimeTypes` {String} 多个用逗号分割。 - * - * 如: - * - * ``` - * { - * title: 'Images', - * extensions: 'gif,jpg,jpeg,bmp,png', - * mimeTypes: 'image/*' - * } - * ``` - */ - accept: null/*{ - title: 'Images', - extensions: 'gif,jpg,jpeg,bmp,png', - mimeTypes: 'image/*' - }*/ - }); - - return Uploader.register({ - name: 'picker', - - init: function( opts ) { - this.pickers = []; - return opts.pick && this.addBtn( opts.pick ); - }, - - refresh: function() { - $.each( this.pickers, function() { - this.refresh(); - }); - }, - - /** - * @method addButton - * @for Uploader - * @grammar addButton( pick ) => Promise - * @description - * 添加文件选择按钮,如果一个按钮不够,需要调用此方法来添加。参数跟[options.pick](#WebUploader:Uploader:options)一致。 - * @example - * uploader.addButton({ - * id: '#btnContainer', - * innerHTML: '选择文件' - * }); - */ - addBtn: function( pick ) { - var me = this, - opts = me.options, - accept = opts.accept, - promises = []; - - if ( !pick ) { - return; - } - - $.isPlainObject( pick ) || (pick = { - id: pick - }); - - $( pick.id ).each(function() { - var options, picker, deferred; - - deferred = Base.Deferred(); - - options = $.extend({}, pick, { - accept: $.isPlainObject( accept ) ? [ accept ] : accept, - swf: opts.swf, - runtimeOrder: opts.runtimeOrder, - id: this - }); - - picker = new FilePicker( options ); - - picker.once( 'ready', deferred.resolve ); - picker.on( 'select', function( files ) { - me.owner.request( 'add-file', [ files ]); - }); - picker.init(); - - me.pickers.push( picker ); - - promises.push( deferred.promise() ); - }); - - return Base.when.apply( Base, promises ); - }, - - disable: function() { - $.each( this.pickers, function() { - this.disable(); - }); - }, - - enable: function() { - $.each( this.pickers, function() { - this.enable(); - }); - }, - - destroy: function() { - $.each( this.pickers, function() { - this.destroy(); - }); - this.pickers = null; - } - }); - }); - /** - * @fileOverview Image - */ - define('lib/image',[ - 'base', - 'runtime/client', - 'lib/blob' - ], function( Base, RuntimeClient, Blob ) { - var $ = Base.$; - - // 构造器。 - function Image( opts ) { - this.options = $.extend({}, Image.options, opts ); - RuntimeClient.call( this, 'Image' ); - - this.on( 'load', function() { - this._info = this.exec('info'); - this._meta = this.exec('meta'); - }); - } - - // 默认选项。 - Image.options = { - - // 默认的图片处理质量 - quality: 90, - - // 是否裁剪 - crop: false, - - // 是否保留头部信息 - preserveHeaders: false, - - // 是否允许放大。 - allowMagnify: false - }; - - // 继承RuntimeClient. - Base.inherits( RuntimeClient, { - constructor: Image, - - info: function( val ) { - - // setter - if ( val ) { - this._info = val; - return this; - } - - // getter - return this._info; - }, - - meta: function( val ) { - - // setter - if ( val ) { - this._meta = val; - return this; - } - - // getter - return this._meta; - }, - - loadFromBlob: function( blob ) { - var me = this, - ruid = blob.getRuid(); - - this.connectRuntime( ruid, function() { - me.exec( 'init', me.options ); - me.exec( 'loadFromBlob', blob ); - }); - }, - - resize: function() { - var args = Base.slice( arguments ); - return this.exec.apply( this, [ 'resize' ].concat( args ) ); - }, - - crop: function() { - var args = Base.slice( arguments ); - return this.exec.apply( this, [ 'crop' ].concat( args ) ); - }, - - getAsDataUrl: function( type ) { - return this.exec( 'getAsDataUrl', type ); - }, - - getAsBlob: function( type ) { - var blob = this.exec( 'getAsBlob', type ); - - return new Blob( this.getRuid(), blob ); - } - }); - - return Image; - }); - /** - * @fileOverview 图片操作, 负责预览图片和上传前压缩图片 - */ - define('widgets/image',[ - 'base', - 'uploader', - 'lib/image', - 'widgets/widget' - ], function( Base, Uploader, Image ) { - - var $ = Base.$, - throttle; - - // 根据要处理的文件大小来节流,一次不能处理太多,会卡。 - throttle = (function( max ) { - var occupied = 0, - waiting = [], - tick = function() { - var item; - - while ( waiting.length && occupied < max ) { - item = waiting.shift(); - occupied += item[ 0 ]; - item[ 1 ](); - } - }; - - return function( emiter, size, cb ) { - waiting.push([ size, cb ]); - emiter.once( 'destroy', function() { - occupied -= size; - setTimeout( tick, 1 ); - }); - setTimeout( tick, 1 ); - }; - })( 5 * 1024 * 1024 ); - - $.extend( Uploader.options, { - - /** - * @property {Object} [thumb] - * @namespace options - * @for Uploader - * @description 配置生成缩略图的选项。 - * - * 默认为: - * - * ```javascript - * { - * width: 110, - * height: 110, - * - * // 图片质量,只有type为`image/jpeg`的时候才有效。 - * quality: 70, - * - * // 是否允许放大,如果想要生成小图的时候不失真,此选项应该设置为false. - * allowMagnify: true, - * - * // 是否允许裁剪。 - * crop: true, - * - * // 为空的话则保留原有图片格式。 - * // 否则强制转换成指定的类型。 - * type: 'image/jpeg' - * } - * ``` - */ - thumb: { - width: 110, - height: 110, - quality: 70, - allowMagnify: true, - crop: true, - preserveHeaders: false, - - // 为空的话则保留原有图片格式。 - // 否则强制转换成指定的类型。 - // IE 8下面 base64 大小不能超过 32K 否则预览失败,而非 jpeg 编码的图片很可 - // 能会超过 32k, 所以这里设置成预览的时候都是 image/jpeg - type: 'image/jpeg' - }, - - /** - * @property {Object} [compress] - * @namespace options - * @for Uploader - * @description 配置压缩的图片的选项。如果此选项为`false`, 则图片在上传前不进行压缩。 - * - * 默认为: - * - * ```javascript - * { - * width: 1600, - * height: 1600, - * - * // 图片质量,只有type为`image/jpeg`的时候才有效。 - * quality: 90, - * - * // 是否允许放大,如果想要生成小图的时候不失真,此选项应该设置为false. - * allowMagnify: false, - * - * // 是否允许裁剪。 - * crop: false, - * - * // 是否保留头部meta信息。 - * preserveHeaders: true, - * - * // 如果发现压缩后文件大小比原来还大,则使用原来图片 - * // 此属性可能会影响图片自动纠正功能 - * noCompressIfLarger: false, - * - * // 单位字节,如果图片大小小于此值,不会采用压缩。 - * compressSize: 0 - * } - * ``` - */ - compress: { - width: 1600, - height: 1600, - quality: 90, - allowMagnify: false, - crop: false, - preserveHeaders: true - } - }); - - return Uploader.register({ - - name: 'image', - - - /** - * 生成缩略图,此过程为异步,所以需要传入`callback`。 - * 通常情况在图片加入队里后调用此方法来生成预览图以增强交互效果。 - * - * 当 width 或者 height 的值介于 0 - 1 时,被当成百分比使用。 - * - * `callback`中可以接收到两个参数。 - * * 第一个为error,如果生成缩略图有错误,此error将为真。 - * * 第二个为ret, 缩略图的Data URL值。 - * - * **注意** - * Date URL在IE6/7中不支持,所以不用调用此方法了,直接显示一张暂不支持预览图片好了。 - * 也可以借助服务端,将 base64 数据传给服务端,生成一个临时文件供预览。 - * - * @method makeThumb - * @grammar makeThumb( file, callback ) => undefined - * @grammar makeThumb( file, callback, width, height ) => undefined - * @for Uploader - * @example - * - * uploader.on( 'fileQueued', function( file ) { - * var $li = ...; - * - * uploader.makeThumb( file, function( error, ret ) { - * if ( error ) { - * $li.text('预览错误'); - * } else { - * $li.append(''); - * } - * }); - * - * }); - */ - makeThumb: function( file, cb, width, height ) { - var opts, image; - - file = this.request( 'get-file', file ); - - // 只预览图片格式。 - if ( !file.type.match( /^image/ ) ) { - cb( true ); - return; - } - - opts = $.extend({}, this.options.thumb ); - - // 如果传入的是object. - if ( $.isPlainObject( width ) ) { - opts = $.extend( opts, width ); - width = null; - } - - width = width || opts.width; - height = height || opts.height; - - image = new Image( opts ); - - image.once( 'load', function() { - file._info = file._info || image.info(); - file._meta = file._meta || image.meta(); - - // 如果 width 的值介于 0 - 1 - // 说明设置的是百分比。 - if ( width <= 1 && width > 0 ) { - width = file._info.width * width; - } - - // 同样的规则应用于 height - if ( height <= 1 && height > 0 ) { - height = file._info.height * height; - } - - image.resize( width, height ); - }); - - // 当 resize 完后 - image.once( 'complete', function() { - cb( false, image.getAsDataUrl( opts.type ) ); - image.destroy(); - }); - - image.once( 'error', function( reason ) { - cb( reason || true ); - image.destroy(); - }); - - throttle( image, file.source.size, function() { - file._info && image.info( file._info ); - file._meta && image.meta( file._meta ); - image.loadFromBlob( file.source ); - }); - }, - - beforeSendFile: function( file ) { - var opts = this.options.compress || this.options.resize, - compressSize = opts && opts.compressSize || 0, - noCompressIfLarger = opts && opts.noCompressIfLarger || false, - image, deferred; - - file = this.request( 'get-file', file ); - - // 只压缩 jpeg 图片格式。 - // gif 可能会丢失针 - // bmp png 基本上尺寸都不大,且压缩比比较小。 - if ( !opts || !~'image/jpeg,image/jpg'.indexOf( file.type ) || - file.size < compressSize || - file._compressed ) { - return; - } - - opts = $.extend({}, opts ); - deferred = Base.Deferred(); - - image = new Image( opts ); - - deferred.always(function() { - image.destroy(); - image = null; - }); - image.once( 'error', deferred.reject ); - image.once( 'load', function() { - var width = opts.width, - height = opts.height; - - file._info = file._info || image.info(); - file._meta = file._meta || image.meta(); - - // 如果 width 的值介于 0 - 1 - // 说明设置的是百分比。 - if ( width <= 1 && width > 0 ) { - width = file._info.width * width; - } - - // 同样的规则应用于 height - if ( height <= 1 && height > 0 ) { - height = file._info.height * height; - } - - image.resize( width, height ); - }); - - image.once( 'complete', function() { - var blob, size; - - // 移动端 UC / qq 浏览器的无图模式下 - // ctx.getImageData 处理大图的时候会报 Exception - // INDEX_SIZE_ERR: DOM Exception 1 - try { - blob = image.getAsBlob( opts.type ); - - size = file.size; - - // 如果压缩后,比原来还大则不用压缩后的。 - if ( !noCompressIfLarger || blob.size < size ) { - // file.source.destroy && file.source.destroy(); - file.source = blob; - file.size = blob.size; - - file.trigger( 'resize', blob.size, size ); - } - - // 标记,避免重复压缩。 - file._compressed = true; - deferred.resolve(); - } catch ( e ) { - // 出错了直接继续,让其上传原始图片 - deferred.resolve(); - } - }); - - file._info && image.info( file._info ); - file._meta && image.meta( file._meta ); - - image.loadFromBlob( file.source ); - return deferred.promise(); - } - }); - }); - /** - * @fileOverview 文件属性封装 - */ - define('file',[ - 'base', - 'mediator' - ], function( Base, Mediator ) { - - var $ = Base.$, - idPrefix = 'WU_FILE_', - idSuffix = 0, - rExt = /\.([^.]+)$/, - statusMap = {}; - - function gid() { - return idPrefix + idSuffix++; - } - - /** - * 文件类 - * @class File - * @constructor 构造函数 - * @grammar new File( source ) => File - * @param {Lib.File} source [lib.File](#Lib.File)实例, 此source对象是带有Runtime信息的。 - */ - function WUFile( source ) { - - /** - * 文件名,包括扩展名(后缀) - * @property name - * @type {string} - */ - this.name = source.name || 'Untitled'; - - /** - * 文件体积(字节) - * @property size - * @type {uint} - * @default 0 - */ - this.size = source.size || 0; - - /** - * 文件MIMETYPE类型,与文件类型的对应关系请参考[http://t.cn/z8ZnFny](http://t.cn/z8ZnFny) - * @property type - * @type {string} - * @default 'application/octet-stream' - */ - this.type = source.type || 'application/octet-stream'; - - /** - * 文件最后修改日期 - * @property lastModifiedDate - * @type {int} - * @default 当前时间戳 - */ - this.lastModifiedDate = source.lastModifiedDate || (new Date() * 1); - - /** - * 文件ID,每个对象具有唯一ID,与文件名无关 - * @property id - * @type {string} - */ - this.id = gid(); - - /** - * 文件扩展名,通过文件名获取,例如test.png的扩展名为png - * @property ext - * @type {string} - */ - this.ext = rExt.exec( this.name ) ? RegExp.$1 : ''; - - - /** - * 状态文字说明。在不同的status语境下有不同的用途。 - * @property statusText - * @type {string} - */ - this.statusText = ''; - - // 存储文件状态,防止通过属性直接修改 - statusMap[ this.id ] = WUFile.Status.INITED; - - this.source = source; - this.loaded = 0; - - this.on( 'error', function( msg ) { - this.setStatus( WUFile.Status.ERROR, msg ); - }); - } - - $.extend( WUFile.prototype, { - - /** - * 设置状态,状态变化时会触发`change`事件。 - * @method setStatus - * @grammar setStatus( status[, statusText] ); - * @param {File.Status|String} status [文件状态值](#WebUploader:File:File.Status) - * @param {String} [statusText=''] 状态说明,常在error时使用,用http, abort,server等来标记是由于什么原因导致文件错误。 - */ - setStatus: function( status, text ) { - - var prevStatus = statusMap[ this.id ]; - - typeof text !== 'undefined' && (this.statusText = text); - - if ( status !== prevStatus ) { - statusMap[ this.id ] = status; - /** - * 文件状态变化 - * @event statuschange - */ - this.trigger( 'statuschange', status, prevStatus ); - } - - }, - - /** - * 获取文件状态 - * @return {File.Status} - * @example - 文件状态具体包括以下几种类型: - { - // 初始化 - INITED: 0, - // 已入队列 - QUEUED: 1, - // 正在上传 - PROGRESS: 2, - // 上传出错 - ERROR: 3, - // 上传成功 - COMPLETE: 4, - // 上传取消 - CANCELLED: 5 - } - */ - getStatus: function() { - return statusMap[ this.id ]; - }, - - /** - * 获取文件原始信息。 - * @return {*} - */ - getSource: function() { - return this.source; - }, - - destroy: function() { - this.off(); - delete statusMap[ this.id ]; - } - }); - - Mediator.installTo( WUFile.prototype ); - - /** - * 文件状态值,具体包括以下几种类型: - * * `inited` 初始状态 - * * `queued` 已经进入队列, 等待上传 - * * `progress` 上传中 - * * `complete` 上传完成。 - * * `error` 上传出错,可重试 - * * `interrupt` 上传中断,可续传。 - * * `invalid` 文件不合格,不能重试上传。会自动从队列中移除。 - * * `cancelled` 文件被移除。 - * @property {Object} Status - * @namespace File - * @class File - * @static - */ - WUFile.Status = { - INITED: 'inited', // 初始状态 - QUEUED: 'queued', // 已经进入队列, 等待上传 - PROGRESS: 'progress', // 上传中 - ERROR: 'error', // 上传出错,可重试 - COMPLETE: 'complete', // 上传完成。 - CANCELLED: 'cancelled', // 上传取消。 - INTERRUPT: 'interrupt', // 上传中断,可续传。 - INVALID: 'invalid' // 文件不合格,不能重试上传。 - }; - - return WUFile; - }); - - /** - * @fileOverview 文件队列 - */ - define('queue',[ - 'base', - 'mediator', - 'file' - ], function( Base, Mediator, WUFile ) { - - var $ = Base.$, - STATUS = WUFile.Status; - - /** - * 文件队列, 用来存储各个状态中的文件。 - * @class Queue - * @extends Mediator - */ - function Queue() { - - /** - * 统计文件数。 - * * `numOfQueue` 队列中的文件数。 - * * `numOfSuccess` 上传成功的文件数 - * * `numOfCancel` 被取消的文件数 - * * `numOfProgress` 正在上传中的文件数 - * * `numOfUploadFailed` 上传错误的文件数。 - * * `numOfInvalid` 无效的文件数。 - * * `numofDeleted` 被移除的文件数。 - * @property {Object} stats - */ - this.stats = { - numOfQueue: 0, - numOfSuccess: 0, - numOfCancel: 0, - numOfProgress: 0, - numOfUploadFailed: 0, - numOfInvalid: 0, - numofDeleted: 0, - numofInterrupt: 0 - }; - - // 上传队列,仅包括等待上传的文件 - this._queue = []; - - // 存储所有文件 - this._map = {}; - } - - $.extend( Queue.prototype, { - - /** - * 将新文件加入对队列尾部 - * - * @method append - * @param {File} file 文件对象 - */ - append: function( file ) { - this._queue.push( file ); - this._fileAdded( file ); - return this; - }, - - /** - * 将新文件加入对队列头部 - * - * @method prepend - * @param {File} file 文件对象 - */ - prepend: function( file ) { - this._queue.unshift( file ); - this._fileAdded( file ); - return this; - }, - - /** - * 获取文件对象 - * - * @method getFile - * @param {String} fileId 文件ID - * @return {File} - */ - getFile: function( fileId ) { - if ( typeof fileId !== 'string' ) { - return fileId; - } - return this._map[ fileId ]; - }, - - /** - * 从队列中取出一个指定状态的文件。 - * @grammar fetch( status ) => File - * @method fetch - * @param {String} status [文件状态值](#WebUploader:File:File.Status) - * @return {File} [File](#WebUploader:File) - */ - fetch: function( status ) { - var len = this._queue.length, - i, file; - - status = status || STATUS.QUEUED; - - for ( i = 0; i < len; i++ ) { - file = this._queue[ i ]; - - if ( status === file.getStatus() ) { - return file; - } - } - - return null; - }, - - /** - * 对队列进行排序,能够控制文件上传顺序。 - * @grammar sort( fn ) => undefined - * @method sort - * @param {Function} fn 排序方法 - */ - sort: function( fn ) { - if ( typeof fn === 'function' ) { - this._queue.sort( fn ); - } - }, - - /** - * 获取指定类型的文件列表, 列表中每一个成员为[File](#WebUploader:File)对象。 - * @grammar getFiles( [status1[, status2 ...]] ) => Array - * @method getFiles - * @param {String} [status] [文件状态值](#WebUploader:File:File.Status) - */ - getFiles: function() { - var sts = [].slice.call( arguments, 0 ), - ret = [], - i = 0, - len = this._queue.length, - file; - - for ( ; i < len; i++ ) { - file = this._queue[ i ]; - - if ( sts.length && !~$.inArray( file.getStatus(), sts ) ) { - continue; - } - - ret.push( file ); - } - - return ret; - }, - - /** - * 在队列中删除文件。 - * @grammar removeFile( file ) => Array - * @method removeFile - * @param {File} 文件对象。 - */ - removeFile: function( file ) { - var me = this, - existing = this._map[ file.id ]; - - if ( existing ) { - delete this._map[ file.id ]; - file.destroy(); - this.stats.numofDeleted++; - } - }, - - _fileAdded: function( file ) { - var me = this, - existing = this._map[ file.id ]; - - if ( !existing ) { - this._map[ file.id ] = file; - - file.on( 'statuschange', function( cur, pre ) { - me._onFileStatusChange( cur, pre ); - }); - } - }, - - _onFileStatusChange: function( curStatus, preStatus ) { - var stats = this.stats; - - switch ( preStatus ) { - case STATUS.PROGRESS: - stats.numOfProgress--; - break; - - case STATUS.QUEUED: - stats.numOfQueue --; - break; - - case STATUS.ERROR: - stats.numOfUploadFailed--; - break; - - case STATUS.INVALID: - stats.numOfInvalid--; - break; - - case STATUS.INTERRUPT: - stats.numofInterrupt--; - break; - } - - switch ( curStatus ) { - case STATUS.QUEUED: - stats.numOfQueue++; - break; - - case STATUS.PROGRESS: - stats.numOfProgress++; - break; - - case STATUS.ERROR: - stats.numOfUploadFailed++; - break; - - case STATUS.COMPLETE: - stats.numOfSuccess++; - break; - - case STATUS.CANCELLED: - stats.numOfCancel++; - break; - - - case STATUS.INVALID: - stats.numOfInvalid++; - break; - - case STATUS.INTERRUPT: - stats.numofInterrupt++; - break; - } - } - - }); - - Mediator.installTo( Queue.prototype ); - - return Queue; - }); - /** - * @fileOverview 队列 - */ - define('widgets/queue',[ - 'base', - 'uploader', - 'queue', - 'file', - 'lib/file', - 'runtime/client', - 'widgets/widget' - ], function( Base, Uploader, Queue, WUFile, File, RuntimeClient ) { - - var $ = Base.$, - rExt = /\.\w+$/, - Status = WUFile.Status; - - return Uploader.register({ - name: 'queue', - - init: function( opts ) { - var me = this, - deferred, len, i, item, arr, accept, runtime; - - if ( $.isPlainObject( opts.accept ) ) { - opts.accept = [ opts.accept ]; - } - - // accept中的中生成匹配正则。 - if ( opts.accept ) { - arr = []; - - for ( i = 0, len = opts.accept.length; i < len; i++ ) { - item = opts.accept[ i ].extensions; - item && arr.push( item ); - } - - if ( arr.length ) { - accept = '\\.' + arr.join(',') - .replace( /,/g, '$|\\.' ) - .replace( /\*/g, '.*' ) + '$'; - } - - me.accept = new RegExp( accept, 'i' ); - } - - me.queue = new Queue(); - me.stats = me.queue.stats; - - // 如果当前不是html5运行时,那就算了。 - // 不执行后续操作 - if ( this.request('predict-runtime-type') !== 'html5' ) { - return; - } - - // 创建一个 html5 运行时的 placeholder - // 以至于外部添加原生 File 对象的时候能正确包裹一下供 webuploader 使用。 - deferred = Base.Deferred(); - this.placeholder = runtime = new RuntimeClient('Placeholder'); - runtime.connectRuntime({ - runtimeOrder: 'html5' - }, function() { - me._ruid = runtime.getRuid(); - deferred.resolve(); - }); - return deferred.promise(); - }, - - - // 为了支持外部直接添加一个原生File对象。 - _wrapFile: function( file ) { - if ( !(file instanceof WUFile) ) { - - if ( !(file instanceof File) ) { - if ( !this._ruid ) { - throw new Error('Can\'t add external files.'); - } - file = new File( this._ruid, file ); - } - - file = new WUFile( file ); - } - - return file; - }, - - // 判断文件是否可以被加入队列 - acceptFile: function( file ) { - var invalid = !file || !file.size || this.accept && - - // 如果名字中有后缀,才做后缀白名单处理。 - rExt.exec( file.name ) && !this.accept.test( file.name ); - - return !invalid; - }, - - - /** - * @event beforeFileQueued - * @param {File} file File对象 - * @description 当文件被加入队列之前触发,此事件的handler返回值为`false`,则此文件不会被添加进入队列。 - * @for Uploader - */ - - /** - * @event fileQueued - * @param {File} file File对象 - * @description 当文件被加入队列以后触发。 - * @for Uploader - */ - - _addFile: function( file ) { - var me = this; - - file = me._wrapFile( file ); - - // 不过类型判断允许不允许,先派送 `beforeFileQueued` - if ( !me.owner.trigger( 'beforeFileQueued', file ) ) { - return; - } - - // 类型不匹配,则派送错误事件,并返回。 - if ( !me.acceptFile( file ) ) { - me.owner.trigger( 'error', 'Q_TYPE_DENIED', file ); - return; - } - - me.queue.append( file ); - me.owner.trigger( 'fileQueued', file ); - return file; - }, - - getFile: function( fileId ) { - return this.queue.getFile( fileId ); - }, - - /** - * @event filesQueued - * @param {File} files 数组,内容为原始File(lib/File)对象。 - * @description 当一批文件添加进队列以后触发。 - * @for Uploader - */ - - /** - * @property {Boolean} [auto=false] - * @namespace options - * @for Uploader - * @description 设置为 true 后,不需要手动调用上传,有文件选择即开始上传。 - * - */ - - /** - * @method addFiles - * @grammar addFiles( file ) => undefined - * @grammar addFiles( [file1, file2 ...] ) => undefined - * @param {Array of File or File} [files] Files 对象 数组 - * @description 添加文件到队列 - * @for Uploader - */ - addFile: function( files ) { - var me = this; - - if ( !files.length ) { - files = [ files ]; - } - - files = $.map( files, function( file ) { - return me._addFile( file ); - }); - - me.owner.trigger( 'filesQueued', files ); - - if ( me.options.auto ) { - setTimeout(function() { - me.request('start-upload'); - }, 20 ); - } - }, - - getStats: function() { - return this.stats; - }, - - /** - * @event fileDequeued - * @param {File} file File对象 - * @description 当文件被移除队列后触发。 - * @for Uploader - */ - - /** - * @method removeFile - * @grammar removeFile( file ) => undefined - * @grammar removeFile( id ) => undefined - * @grammar removeFile( file, true ) => undefined - * @grammar removeFile( id, true ) => undefined - * @param {File|id} file File对象或这File对象的id - * @description 移除某一文件, 默认只会标记文件状态为已取消,如果第二个参数为 `true` 则会从 queue 中移除。 - * @for Uploader - * @example - * - * $li.on('click', '.remove-this', function() { - * uploader.removeFile( file ); - * }) - */ - removeFile: function( file, remove ) { - var me = this; - - file = file.id ? file : me.queue.getFile( file ); - - this.request( 'cancel-file', file ); - - if ( remove ) { - this.queue.removeFile( file ); - } - }, - - /** - * @method getFiles - * @grammar getFiles() => Array - * @grammar getFiles( status1, status2, status... ) => Array - * @description 返回指定状态的文件集合,不传参数将返回所有状态的文件。 - * @for Uploader - * @example - * console.log( uploader.getFiles() ); // => all files - * console.log( uploader.getFiles('error') ) // => all error files. - */ - getFiles: function() { - return this.queue.getFiles.apply( this.queue, arguments ); - }, - - fetchFile: function() { - return this.queue.fetch.apply( this.queue, arguments ); - }, - - /** - * @method retry - * @grammar retry() => undefined - * @grammar retry( file ) => undefined - * @description 重试上传,重试指定文件,或者从出错的文件开始重新上传。 - * @for Uploader - * @example - * function retry() { - * uploader.retry(); - * } - */ - retry: function( file, noForceStart ) { - var me = this, - files, i, len; - - if ( file ) { - file = file.id ? file : me.queue.getFile( file ); - file.setStatus( Status.QUEUED ); - noForceStart || me.request('start-upload'); - return; - } - - files = me.queue.getFiles( Status.ERROR ); - i = 0; - len = files.length; - - for ( ; i < len; i++ ) { - file = files[ i ]; - file.setStatus( Status.QUEUED ); - } - - me.request('start-upload'); - }, - - /** - * @method sort - * @grammar sort( fn ) => undefined - * @description 排序队列中的文件,在上传之前调整可以控制上传顺序。 - * @for Uploader - */ - sortFiles: function() { - return this.queue.sort.apply( this.queue, arguments ); - }, - - /** - * @event reset - * @description 当 uploader 被重置的时候触发。 - * @for Uploader - */ - - /** - * @method reset - * @grammar reset() => undefined - * @description 重置uploader。目前只重置了队列。 - * @for Uploader - * @example - * uploader.reset(); - */ - reset: function() { - this.owner.trigger('reset'); - this.queue = new Queue(); - this.stats = this.queue.stats; - }, - - destroy: function() { - this.reset(); - this.placeholder && this.placeholder.destroy(); - } - }); - - }); - /** - * @fileOverview 添加获取Runtime相关信息的方法。 - */ - define('widgets/runtime',[ - 'uploader', - 'runtime/runtime', - 'widgets/widget' - ], function( Uploader, Runtime ) { - - Uploader.support = function() { - return Runtime.hasRuntime.apply( Runtime, arguments ); - }; - - /** - * @property {Object} [runtimeOrder=html5,flash] - * @namespace options - * @for Uploader - * @description 指定运行时启动顺序。默认会想尝试 html5 是否支持,如果支持则使用 html5, 否则则使用 flash. - * - * 可以将此值设置成 `flash`,来强制使用 flash 运行时。 - */ - - return Uploader.register({ - name: 'runtime', - - init: function() { - if ( !this.predictRuntimeType() ) { - throw Error('Runtime Error'); - } - }, - - /** - * 预测Uploader将采用哪个`Runtime` - * @grammar predictRuntimeType() => String - * @method predictRuntimeType - * @for Uploader - */ - predictRuntimeType: function() { - var orders = this.options.runtimeOrder || Runtime.orders, - type = this.type, - i, len; - - if ( !type ) { - orders = orders.split( /\s*,\s*/g ); - - for ( i = 0, len = orders.length; i < len; i++ ) { - if ( Runtime.hasRuntime( orders[ i ] ) ) { - this.type = type = orders[ i ]; - break; - } - } - } - - return type; - } - }); - }); - /** - * @fileOverview Transport - */ - define('lib/transport',[ - 'base', - 'runtime/client', - 'mediator' - ], function( Base, RuntimeClient, Mediator ) { - - var $ = Base.$; - - function Transport( opts ) { - var me = this; - - opts = me.options = $.extend( true, {}, Transport.options, opts || {} ); - RuntimeClient.call( this, 'Transport' ); - - this._blob = null; - this._formData = opts.formData || {}; - this._headers = opts.headers || {}; - - this.on( 'progress', this._timeout ); - this.on( 'load error', function() { - me.trigger( 'progress', 1 ); - clearTimeout( me._timer ); - }); - } - - Transport.options = { - server: '', - method: 'POST', - - // 跨域时,是否允许携带cookie, 只有html5 runtime才有效 - withCredentials: false, - fileVal: 'file', - timeout: 2 * 60 * 1000, // 2分钟 - formData: {}, - headers: {}, - sendAsBinary: false - }; - - $.extend( Transport.prototype, { - - // 添加Blob, 只能添加一次,最后一次有效。 - appendBlob: function( key, blob, filename ) { - var me = this, - opts = me.options; - - if ( me.getRuid() ) { - me.disconnectRuntime(); - } - - // 连接到blob归属的同一个runtime. - me.connectRuntime( blob.ruid, function() { - me.exec('init'); - }); - - me._blob = blob; - opts.fileVal = key || opts.fileVal; - opts.filename = filename || opts.filename; - }, - - // 添加其他字段 - append: function( key, value ) { - if ( typeof key === 'object' ) { - $.extend( this._formData, key ); - } else { - this._formData[ key ] = value; - } - }, - - setRequestHeader: function( key, value ) { - if ( typeof key === 'object' ) { - $.extend( this._headers, key ); - } else { - this._headers[ key ] = value; - } - }, - - send: function( method ) { - this.exec( 'send', method ); - this._timeout(); - }, - - abort: function() { - clearTimeout( this._timer ); - return this.exec('abort'); - }, - - destroy: function() { - this.trigger('destroy'); - this.off(); - this.exec('destroy'); - this.disconnectRuntime(); - }, - - getResponse: function() { - return this.exec('getResponse'); - }, - - getResponseAsJson: function() { - return this.exec('getResponseAsJson'); - }, - - getStatus: function() { - return this.exec('getStatus'); - }, - - _timeout: function() { - var me = this, - duration = me.options.timeout; - - if ( !duration ) { - return; - } - - clearTimeout( me._timer ); - me._timer = setTimeout(function() { - me.abort(); - me.trigger( 'error', 'timeout' ); - }, duration ); - } - - }); - - // 让Transport具备事件功能。 - Mediator.installTo( Transport.prototype ); - - return Transport; - }); - /** - * @fileOverview 负责文件上传相关。 - */ - define('widgets/upload',[ - 'base', - 'uploader', - 'file', - 'lib/transport', - 'widgets/widget' - ], function( Base, Uploader, WUFile, Transport ) { - - var $ = Base.$, - isPromise = Base.isPromise, - Status = WUFile.Status; - - // 添加默认配置项 - $.extend( Uploader.options, { - - - /** - * @property {Boolean} [prepareNextFile=false] - * @namespace options - * @for Uploader - * @description 是否允许在文件传输时提前把下一个文件准备好。 - * 对于一个文件的准备工作比较耗时,比如图片压缩,md5序列化。 - * 如果能提前在当前文件传输期处理,可以节省总体耗时。 - */ - prepareNextFile: false, - - /** - * @property {Boolean} [chunked=false] - * @namespace options - * @for Uploader - * @description 是否要分片处理大文件上传。 - */ - chunked: false, - - /** - * @property {Boolean} [chunkSize=5242880] - * @namespace options - * @for Uploader - * @description 如果要分片,分多大一片? 默认大小为5M. - */ - chunkSize: 5 * 1024 * 1024, - - /** - * @property {Boolean} [chunkRetry=2] - * @namespace options - * @for Uploader - * @description 如果某个分片由于网络问题出错,允许自动重传多少次? - */ - chunkRetry: 2, - - /** - * @property {Boolean} [threads=3] - * @namespace options - * @for Uploader - * @description 上传并发数。允许同时最大上传进程数。 - */ - threads: 3, - - - /** - * @property {Object} [formData={}] - * @namespace options - * @for Uploader - * @description 文件上传请求的参数表,每次发送都会发送此对象中的参数。 - */ - formData: {} - - /** - * @property {Object} [fileVal='file'] - * @namespace options - * @for Uploader - * @description 设置文件上传域的name。 - */ - - /** - * @property {Object} [method='POST'] - * @namespace options - * @for Uploader - * @description 文件上传方式,`POST`或者`GET`。 - */ - - /** - * @property {Object} [sendAsBinary=false] - * @namespace options - * @for Uploader - * @description 是否已二进制的流的方式发送文件,这样整个上传内容`php://input`都为文件内容, - * 其他参数在$_GET数组中。 - */ - }); - - // 负责将文件切片。 - function CuteFile( file, chunkSize ) { - var pending = [], - blob = file.source, - total = blob.size, - chunks = chunkSize ? Math.ceil( total / chunkSize ) : 1, - start = 0, - index = 0, - len, api; - - api = { - file: file, - - has: function() { - return !!pending.length; - }, - - shift: function() { - return pending.shift(); - }, - - unshift: function( block ) { - pending.unshift( block ); - } - }; - - while ( index < chunks ) { - len = Math.min( chunkSize, total - start ); - - pending.push({ - file: file, - start: start, - end: chunkSize ? (start + len) : total, - total: total, - chunks: chunks, - chunk: index++, - cuted: api - }); - start += len; - } - - file.blocks = pending.concat(); - file.remaning = pending.length; - - return api; - } - - Uploader.register({ - name: 'upload', - - init: function() { - var owner = this.owner, - me = this; - - this.runing = false; - this.progress = false; - - owner - .on( 'startUpload', function() { - me.progress = true; - }) - .on( 'uploadFinished', function() { - me.progress = false; - }); - - // 记录当前正在传的数据,跟threads相关 - this.pool = []; - - // 缓存分好片的文件。 - this.stack = []; - - // 缓存即将上传的文件。 - this.pending = []; - - // 跟踪还有多少分片在上传中但是没有完成上传。 - this.remaning = 0; - this.__tick = Base.bindFn( this._tick, this ); - - owner.on( 'uploadComplete', function( file ) { - - // 把其他块取消了。 - file.blocks && $.each( file.blocks, function( _, v ) { - v.transport && (v.transport.abort(), v.transport.destroy()); - delete v.transport; - }); - - delete file.blocks; - delete file.remaning; - }); - }, - - reset: function() { - this.request( 'stop-upload', true ); - this.runing = false; - this.pool = []; - this.stack = []; - this.pending = []; - this.remaning = 0; - this._trigged = false; - this._promise = null; - }, - - /** - * @event startUpload - * @description 当开始上传流程时触发。 - * @for Uploader - */ - - /** - * 开始上传。此方法可以从初始状态调用开始上传流程,也可以从暂停状态调用,继续上传流程。 - * - * 可以指定开始某一个文件。 - * @grammar upload() => undefined - * @grammar upload( file | fileId) => undefined - * @method upload - * @for Uploader - */ - startUpload: function(file) { - var me = this; - - // 移出invalid的文件 - $.each( me.request( 'get-files', Status.INVALID ), function() { - me.request( 'remove-file', this ); - }); - - // 如果指定了开始某个文件,则只开始指定文件。 - if ( file ) { - file = file.id ? file : me.request( 'get-file', file ); - - if (file.getStatus() === Status.INTERRUPT) { - $.each( me.pool, function( _, v ) { - - // 之前暂停过。 - if (v.file !== file) { - return; - } - - v.transport && v.transport.send(); - }); - - file.setStatus( Status.QUEUED ); - } else if (file.getStatus() === Status.PROGRESS) { - return; - } else { - file.setStatus( Status.QUEUED ); - } - } else { - $.each( me.request( 'get-files', [ Status.INITED ] ), function() { - this.setStatus( Status.QUEUED ); - }); - } - - if ( me.runing ) { - return; - } - - me.runing = true; - - var files = []; - - // 如果有暂停的,则续传 - $.each( me.pool, function( _, v ) { - var file = v.file; - - if ( file.getStatus() === Status.INTERRUPT ) { - files.push(file); - me._trigged = false; - v.transport && v.transport.send(); - } - }); - - var file; - while ( (file = files.shift()) ) { - file.setStatus( Status.PROGRESS ); - } - - file || $.each( me.request( 'get-files', - Status.INTERRUPT ), function() { - this.setStatus( Status.PROGRESS ); - }); - - me._trigged = false; - Base.nextTick( me.__tick ); - me.owner.trigger('startUpload'); - }, - - /** - * @event stopUpload - * @description 当开始上传流程暂停时触发。 - * @for Uploader - */ - - /** - * 暂停上传。第一个参数为是否中断上传当前正在上传的文件。 - * - * 如果第一个参数是文件,则只暂停指定文件。 - * @grammar stop() => undefined - * @grammar stop( true ) => undefined - * @grammar stop( file ) => undefined - * @method stop - * @for Uploader - */ - stopUpload: function( file, interrupt ) { - var me = this; - - if (file === true) { - interrupt = file; - file = null; - } - - if ( me.runing === false ) { - return; - } - - // 如果只是暂停某个文件。 - if ( file ) { - file = file.id ? file : me.request( 'get-file', file ); - - if ( file.getStatus() !== Status.PROGRESS && - file.getStatus() !== Status.QUEUED ) { - return; - } - - file.setStatus( Status.INTERRUPT ); - $.each( me.pool, function( _, v ) { - - // 只 abort 指定的文件。 - if (v.file !== file) { - return; - } - - v.transport && v.transport.abort(); - me._putback(v); - me._popBlock(v); - }); - - return Base.nextTick( me.__tick ); - } - - me.runing = false; - - if (this._promise && this._promise.file) { - this._promise.file.setStatus( Status.INTERRUPT ); - } - - interrupt && $.each( me.pool, function( _, v ) { - v.transport && v.transport.abort(); - v.file.setStatus( Status.INTERRUPT ); - }); - - me.owner.trigger('stopUpload'); - }, - - /** - * @method cancelFile - * @grammar cancelFile( file ) => undefined - * @grammar cancelFile( id ) => undefined - * @param {File|id} file File对象或这File对象的id - * @description 标记文件状态为已取消, 同时将中断文件传输。 - * @for Uploader - * @example - * - * $li.on('click', '.remove-this', function() { - * uploader.cancelFile( file ); - * }) - */ - cancelFile: function( file ) { - file = file.id ? file : this.request( 'get-file', file ); - - // 如果正在上传。 - file.blocks && $.each( file.blocks, function( _, v ) { - var _tr = v.transport; - - if ( _tr ) { - _tr.abort(); - _tr.destroy(); - delete v.transport; - } - }); - - file.setStatus( Status.CANCELLED ); - this.owner.trigger( 'fileDequeued', file ); - }, - - /** - * 判断`Uplaode`r是否正在上传中。 - * @grammar isInProgress() => Boolean - * @method isInProgress - * @for Uploader - */ - isInProgress: function() { - return !!this.progress; - }, - - _getStats: function() { - return this.request('get-stats'); - }, - - /** - * 掉过一个文件上传,直接标记指定文件为已上传状态。 - * @grammar skipFile( file ) => undefined - * @method skipFile - * @for Uploader - */ - skipFile: function( file, status ) { - file = file.id ? file : this.request( 'get-file', file ); - - file.setStatus( status || Status.COMPLETE ); - file.skipped = true; - - // 如果正在上传。 - file.blocks && $.each( file.blocks, function( _, v ) { - var _tr = v.transport; - - if ( _tr ) { - _tr.abort(); - _tr.destroy(); - delete v.transport; - } - }); - - this.owner.trigger( 'uploadSkip', file ); - }, - - /** - * @event uploadFinished - * @description 当所有文件上传结束时触发。 - * @for Uploader - */ - _tick: function() { - var me = this, - opts = me.options, - fn, val; - - // 上一个promise还没有结束,则等待完成后再执行。 - if ( me._promise ) { - return me._promise.always( me.__tick ); - } - - // 还有位置,且还有文件要处理的话。 - if ( me.pool.length < opts.threads && (val = me._nextBlock()) ) { - me._trigged = false; - - fn = function( val ) { - me._promise = null; - - // 有可能是reject过来的,所以要检测val的类型。 - val && val.file && me._startSend( val ); - Base.nextTick( me.__tick ); - }; - - me._promise = isPromise( val ) ? val.always( fn ) : fn( val ); - - // 没有要上传的了,且没有正在传输的了。 - } else if ( !me.remaning && !me._getStats().numOfQueue && - !me._getStats().numofInterrupt ) { - me.runing = false; - - me._trigged || Base.nextTick(function() { - me.owner.trigger('uploadFinished'); - }); - me._trigged = true; - } - }, - - _putback: function(block) { - var idx; - - block.cuted.unshift(block); - idx = this.stack.indexOf(block.cuted); - - if (!~idx) { - this.stack.unshift(block.cuted); - } - }, - - _getStack: function() { - var i = 0, - act; - - while ( (act = this.stack[ i++ ]) ) { - if ( act.has() && act.file.getStatus() === Status.PROGRESS ) { - return act; - } else if (!act.has() || - act.file.getStatus() !== Status.PROGRESS && - act.file.getStatus() !== Status.INTERRUPT ) { - - // 把已经处理完了的,或者,状态为非 progress(上传中)、 - // interupt(暂停中) 的移除。 - this.stack.splice( --i, 1 ); - } - } - - return null; - }, - - _nextBlock: function() { - var me = this, - opts = me.options, - act, next, done, preparing; - - // 如果当前文件还有没有需要传输的,则直接返回剩下的。 - if ( (act = this._getStack()) ) { - - // 是否提前准备下一个文件 - if ( opts.prepareNextFile && !me.pending.length ) { - me._prepareNextFile(); - } - - return act.shift(); - - // 否则,如果正在运行,则准备下一个文件,并等待完成后返回下个分片。 - } else if ( me.runing ) { - - // 如果缓存中有,则直接在缓存中取,没有则去queue中取。 - if ( !me.pending.length && me._getStats().numOfQueue ) { - me._prepareNextFile(); - } - - next = me.pending.shift(); - done = function( file ) { - if ( !file ) { - return null; - } - - act = CuteFile( file, opts.chunked ? opts.chunkSize : 0 ); - me.stack.push(act); - return act.shift(); - }; - - // 文件可能还在prepare中,也有可能已经完全准备好了。 - if ( isPromise( next) ) { - preparing = next.file; - next = next[ next.pipe ? 'pipe' : 'then' ]( done ); - next.file = preparing; - return next; - } - - return done( next ); - } - }, - - - /** - * @event uploadStart - * @param {File} file File对象 - * @description 某个文件开始上传前触发,一个文件只会触发一次。 - * @for Uploader - */ - _prepareNextFile: function() { - var me = this, - file = me.request('fetch-file'), - pending = me.pending, - promise; - - if ( file ) { - promise = me.request( 'before-send-file', file, function() { - - // 有可能文件被skip掉了。文件被skip掉后,状态坑定不是Queued. - if ( file.getStatus() === Status.PROGRESS || - file.getStatus() === Status.INTERRUPT ) { - return file; - } - - return me._finishFile( file ); - }); - - me.owner.trigger( 'uploadStart', file ); - file.setStatus( Status.PROGRESS ); - - promise.file = file; - - // 如果还在pending中,则替换成文件本身。 - promise.done(function() { - var idx = $.inArray( promise, pending ); - - ~idx && pending.splice( idx, 1, file ); - }); - - // befeore-send-file的钩子就有错误发生。 - promise.fail(function( reason ) { - file.setStatus( Status.ERROR, reason ); - me.owner.trigger( 'uploadError', file, reason ); - me.owner.trigger( 'uploadComplete', file ); - }); - - pending.push( promise ); - } - }, - - // 让出位置了,可以让其他分片开始上传 - _popBlock: function( block ) { - var idx = $.inArray( block, this.pool ); - - this.pool.splice( idx, 1 ); - block.file.remaning--; - this.remaning--; - }, - - // 开始上传,可以被掉过。如果promise被reject了,则表示跳过此分片。 - _startSend: function( block ) { - var me = this, - file = block.file, - promise; - - // 有可能在 before-send-file 的 promise 期间改变了文件状态。 - // 如:暂停,取消 - // 我们不能中断 promise, 但是可以在 promise 完后,不做上传操作。 - if ( file.getStatus() !== Status.PROGRESS ) { - - // 如果是中断,则还需要放回去。 - if (file.getStatus() === Status.INTERRUPT) { - me._putback(block); - } - - return; - } - - me.pool.push( block ); - me.remaning++; - - // 如果没有分片,则直接使用原始的。 - // 不会丢失content-type信息。 - block.blob = block.chunks === 1 ? file.source : - file.source.slice( block.start, block.end ); - - // hook, 每个分片发送之前可能要做些异步的事情。 - promise = me.request( 'before-send', block, function() { - - // 有可能文件已经上传出错了,所以不需要再传输了。 - if ( file.getStatus() === Status.PROGRESS ) { - me._doSend( block ); - } else { - me._popBlock( block ); - Base.nextTick( me.__tick ); - } - }); - - // 如果为fail了,则跳过此分片。 - promise.fail(function() { - if ( file.remaning === 1 ) { - me._finishFile( file ).always(function() { - block.percentage = 1; - me._popBlock( block ); - me.owner.trigger( 'uploadComplete', file ); - Base.nextTick( me.__tick ); - }); - } else { - block.percentage = 1; - me.updateFileProgress( file ); - me._popBlock( block ); - Base.nextTick( me.__tick ); - } - }); - }, - - - /** - * @event uploadBeforeSend - * @param {Object} object - * @param {Object} data 默认的上传参数,可以扩展此对象来控制上传参数。 - * @param {Object} headers 可以扩展此对象来控制上传头部。 - * @description 当某个文件的分块在发送前触发,主要用来询问是否要添加附带参数,大文件在开起分片上传的前提下此事件可能会触发多次。 - * @for Uploader - */ - - /** - * @event uploadAccept - * @param {Object} object - * @param {Object} ret 服务端的返回数据,json格式,如果服务端不是json格式,从ret._raw中取数据,自行解析。 - * @description 当某个文件上传到服务端响应后,会派送此事件来询问服务端响应是否有效。如果此事件handler返回值为`false`, 则此文件将派送`server`类型的`uploadError`事件。 - * @for Uploader - */ - - /** - * @event uploadProgress - * @param {File} file File对象 - * @param {Number} percentage 上传进度 - * @description 上传过程中触发,携带上传进度。 - * @for Uploader - */ - - - /** - * @event uploadError - * @param {File} file File对象 - * @param {String} reason 出错的code - * @description 当文件上传出错时触发。 - * @for Uploader - */ - - /** - * @event uploadSuccess - * @param {File} file File对象 - * @param {Object} response 服务端返回的数据 - * @description 当文件上传成功时触发。 - * @for Uploader - */ - - /** - * @event uploadComplete - * @param {File} [file] File对象 - * @description 不管成功或者失败,文件上传完成时触发。 - * @for Uploader - */ - - // 做上传操作。 - _doSend: function( block ) { - var me = this, - owner = me.owner, - opts = me.options, - file = block.file, - tr = new Transport( opts ), - data = $.extend({}, opts.formData ), - headers = $.extend({}, opts.headers ), - requestAccept, ret; - - block.transport = tr; - - tr.on( 'destroy', function() { - delete block.transport; - me._popBlock( block ); - Base.nextTick( me.__tick ); - }); - - // 广播上传进度。以文件为单位。 - tr.on( 'progress', function( percentage ) { - block.percentage = percentage; - me.updateFileProgress( file ); - }); - - // 用来询问,是否返回的结果是有错误的。 - requestAccept = function( reject ) { - var fn; - - ret = tr.getResponseAsJson() || {}; - ret._raw = tr.getResponse(); - fn = function( value ) { - reject = value; - }; - - // 服务端响应了,不代表成功了,询问是否响应正确。 - if ( !owner.trigger( 'uploadAccept', block, ret, fn ) ) { - reject = reject || 'server'; - } - - return reject; - }; - - // 尝试重试,然后广播文件上传出错。 - tr.on( 'error', function( type, flag ) { - block.retried = block.retried || 0; - - // 自动重试 - if ( block.chunks > 1 && ~'http,abort'.indexOf( type ) && - block.retried < opts.chunkRetry ) { - - block.retried++; - tr.send(); - - } else { - - // http status 500 ~ 600 - if ( !flag && type === 'server' ) { - type = requestAccept( type ); - } - - file.setStatus( Status.ERROR, type ); - owner.trigger( 'uploadError', file, type ); - owner.trigger( 'uploadComplete', file ); - } - }); - - // 上传成功 - tr.on( 'load', function() { - var reason; - - // 如果非预期,转向上传出错。 - if ( (reason = requestAccept()) ) { - tr.trigger( 'error', reason, true ); - return; - } - - // 全部上传完成。 - if ( file.remaning === 1 ) { - me._finishFile( file, ret ); - } else { - tr.destroy(); - } - }); - - // 配置默认的上传字段。 - data = $.extend( data, { - id: file.id, - name: file.name, - type: file.type, - lastModifiedDate: file.lastModifiedDate, - size: file.size - }); - - block.chunks > 1 && $.extend( data, { - chunks: block.chunks, - chunk: block.chunk - }); - - // 在发送之间可以添加字段什么的。。。 - // 如果默认的字段不够使用,可以通过监听此事件来扩展 - owner.trigger( 'uploadBeforeSend', block, data, headers ); - - // 开始发送。 - tr.appendBlob( opts.fileVal, block.blob, file.name ); - tr.append( data ); - tr.setRequestHeader( headers ); - tr.send(); - }, - - // 完成上传。 - _finishFile: function( file, ret, hds ) { - var owner = this.owner; - - return owner - .request( 'after-send-file', arguments, function() { - file.setStatus( Status.COMPLETE ); - owner.trigger( 'uploadSuccess', file, ret, hds ); - }) - .fail(function( reason ) { - - // 如果外部已经标记为invalid什么的,不再改状态。 - if ( file.getStatus() === Status.PROGRESS ) { - file.setStatus( Status.ERROR, reason ); - } - - owner.trigger( 'uploadError', file, reason ); - }) - .always(function() { - owner.trigger( 'uploadComplete', file ); - }); - }, - - updateFileProgress: function(file) { - var totalPercent = 0, - uploaded = 0; - - if (!file.blocks) { - return; - } - - $.each( file.blocks, function( _, v ) { - uploaded += (v.percentage || 0) * (v.end - v.start); - }); - - totalPercent = uploaded / file.size; - this.owner.trigger( 'uploadProgress', file, totalPercent || 0 ); - } - - }); - }); - /** - * @fileOverview 各种验证,包括文件总大小是否超出、单文件是否超出和文件是否重复。 - */ - - define('widgets/validator',[ - 'base', - 'uploader', - 'file', - 'widgets/widget' - ], function( Base, Uploader, WUFile ) { - - var $ = Base.$, - validators = {}, - api; - - /** - * @event error - * @param {String} type 错误类型。 - * @description 当validate不通过时,会以派送错误事件的形式通知调用者。通过`upload.on('error', handler)`可以捕获到此类错误,目前有以下错误会在特定的情况下派送错来。 - * - * * `Q_EXCEED_NUM_LIMIT` 在设置了`fileNumLimit`且尝试给`uploader`添加的文件数量超出这个值时派送。 - * * `Q_EXCEED_SIZE_LIMIT` 在设置了`Q_EXCEED_SIZE_LIMIT`且尝试给`uploader`添加的文件总大小超出这个值时派送。 - * * `Q_TYPE_DENIED` 当文件类型不满足时触发。。 - * @for Uploader - */ - - // 暴露给外面的api - api = { - - // 添加验证器 - addValidator: function( type, cb ) { - validators[ type ] = cb; - }, - - // 移除验证器 - removeValidator: function( type ) { - delete validators[ type ]; - } - }; - - // 在Uploader初始化的时候启动Validators的初始化 - Uploader.register({ - name: 'validator', - - init: function() { - var me = this; - Base.nextTick(function() { - $.each( validators, function() { - this.call( me.owner ); - }); - }); - } - }); - - /** - * @property {int} [fileNumLimit=undefined] - * @namespace options - * @for Uploader - * @description 验证文件总数量, 超出则不允许加入队列。 - */ - api.addValidator( 'fileNumLimit', function() { - var uploader = this, - opts = uploader.options, - count = 0, - max = parseInt( opts.fileNumLimit, 10 ), - flag = true; - - if ( !max ) { - return; - } - - uploader.on( 'beforeFileQueued', function( file ) { - - if ( count >= max && flag ) { - flag = false; - this.trigger( 'error', 'Q_EXCEED_NUM_LIMIT', max, file ); - setTimeout(function() { - flag = true; - }, 1 ); - } - - return count >= max ? false : true; - }); - - uploader.on( 'fileQueued', function() { - count++; - }); - - uploader.on( 'fileDequeued', function() { - count--; - }); - - uploader.on( 'reset', function() { - count = 0; - }); - }); - - - /** - * @property {int} [fileSizeLimit=undefined] - * @namespace options - * @for Uploader - * @description 验证文件总大小是否超出限制, 超出则不允许加入队列。 - */ - api.addValidator( 'fileSizeLimit', function() { - var uploader = this, - opts = uploader.options, - count = 0, - max = parseInt( opts.fileSizeLimit, 10 ), - flag = true; - - if ( !max ) { - return; - } - - uploader.on( 'beforeFileQueued', function( file ) { - var invalid = count + file.size > max; - - if ( invalid && flag ) { - flag = false; - this.trigger( 'error', 'Q_EXCEED_SIZE_LIMIT', max, file ); - setTimeout(function() { - flag = true; - }, 1 ); - } - - return invalid ? false : true; - }); - - uploader.on( 'fileQueued', function( file ) { - count += file.size; - }); - - uploader.on( 'fileDequeued', function( file ) { - count -= file.size; - }); - - uploader.on( 'reset', function() { - count = 0; - }); - }); - - /** - * @property {int} [fileSingleSizeLimit=undefined] - * @namespace options - * @for Uploader - * @description 验证单个文件大小是否超出限制, 超出则不允许加入队列。 - */ - api.addValidator( 'fileSingleSizeLimit', function() { - var uploader = this, - opts = uploader.options, - max = opts.fileSingleSizeLimit; - - if ( !max ) { - return; - } - - uploader.on( 'beforeFileQueued', function( file ) { - - if ( file.size > max ) { - file.setStatus( WUFile.Status.INVALID, 'exceed_size' ); - this.trigger( 'error', 'F_EXCEED_SIZE', max, file ); - return false; - } - - }); - - }); - - /** - * @property {Boolean} [duplicate=undefined] - * @namespace options - * @for Uploader - * @description 去重, 根据文件名字、文件大小和最后修改时间来生成hash Key. - */ - api.addValidator( 'duplicate', function() { - var uploader = this, - opts = uploader.options, - mapping = {}; - - if ( opts.duplicate ) { - return; - } - - function hashString( str ) { - var hash = 0, - i = 0, - len = str.length, - _char; - - for ( ; i < len; i++ ) { - _char = str.charCodeAt( i ); - hash = _char + (hash << 6) + (hash << 16) - hash; - } - - return hash; - } - - uploader.on( 'beforeFileQueued', function( file ) { - var hash = file.__hash || (file.__hash = hashString( file.name + - file.size + file.lastModifiedDate )); - - // 已经重复了 - if ( mapping[ hash ] ) { - this.trigger( 'error', 'F_DUPLICATE', file ); - return false; - } - }); - - uploader.on( 'fileQueued', function( file ) { - var hash = file.__hash; - - hash && (mapping[ hash ] = true); - }); - - uploader.on( 'fileDequeued', function( file ) { - var hash = file.__hash; - - hash && (delete mapping[ hash ]); - }); - - uploader.on( 'reset', function() { - mapping = {}; - }); - }); - - return api; - }); - - /** - * @fileOverview Md5 - */ - define('lib/md5',[ - 'runtime/client', - 'mediator' - ], function( RuntimeClient, Mediator ) { - - function Md5() { - RuntimeClient.call( this, 'Md5' ); - } - - // 让 Md5 具备事件功能。 - Mediator.installTo( Md5.prototype ); - - Md5.prototype.loadFromBlob = function( blob ) { - var me = this; - - if ( me.getRuid() ) { - me.disconnectRuntime(); - } - - // 连接到blob归属的同一个runtime. - me.connectRuntime( blob.ruid, function() { - me.exec('init'); - me.exec( 'loadFromBlob', blob ); - }); - }; - - Md5.prototype.getResult = function() { - return this.exec('getResult'); - }; - - return Md5; - }); - /** - * @fileOverview 图片操作, 负责预览图片和上传前压缩图片 - */ - define('widgets/md5',[ - 'base', - 'uploader', - 'lib/md5', - 'lib/blob', - 'widgets/widget' - ], function( Base, Uploader, Md5, Blob ) { - - return Uploader.register({ - name: 'md5', - - - /** - * 计算文件 md5 值,返回一个 promise 对象,可以监听 progress 进度。 - * - * - * @method md5File - * @grammar md5File( file[, start[, end]] ) => promise - * @for Uploader - * @example - * - * uploader.on( 'fileQueued', function( file ) { - * var $li = ...; - * - * uploader.md5File( file ) - * - * // 及时显示进度 - * .progress(function(percentage) { - * console.log('Percentage:', percentage); - * }) - * - * // 完成 - * .then(function(val) { - * console.log('md5 result:', val); - * }); - * - * }); - */ - md5File: function( file, start, end ) { - var md5 = new Md5(), - deferred = Base.Deferred(), - blob = (file instanceof Blob) ? file : - this.request( 'get-file', file ).source; - - md5.on( 'progress load', function( e ) { - e = e || {}; - deferred.notify( e.total ? e.loaded / e.total : 1 ); - }); - - md5.on( 'complete', function() { - deferred.resolve( md5.getResult() ); - }); - - md5.on( 'error', function( reason ) { - deferred.reject( reason ); - }); - - if ( arguments.length > 1 ) { - start = start || 0; - end = end || 0; - start < 0 && (start = blob.size + start); - end < 0 && (end = blob.size + end); - end = Math.min( end, blob.size ); - blob = blob.slice( start, end ); - } - - md5.loadFromBlob( blob ); - - return deferred.promise(); - } - }); - }); - /** - * @fileOverview Runtime管理器,负责Runtime的选择, 连接 - */ - define('runtime/compbase',[],function() { - - function CompBase( owner, runtime ) { - - this.owner = owner; - this.options = owner.options; - - this.getRuntime = function() { - return runtime; - }; - - this.getRuid = function() { - return runtime.uid; - }; - - this.trigger = function() { - return owner.trigger.apply( owner, arguments ); - }; - } - - return CompBase; - }); - /** - * @fileOverview Html5Runtime - */ - define('runtime/html5/runtime',[ - 'base', - 'runtime/runtime', - 'runtime/compbase' - ], function( Base, Runtime, CompBase ) { - - var type = 'html5', - components = {}; - - function Html5Runtime() { - var pool = {}, - me = this, - destroy = this.destroy; - - Runtime.apply( me, arguments ); - me.type = type; - - - // 这个方法的调用者,实际上是RuntimeClient - me.exec = function( comp, fn/*, args...*/) { - var client = this, - uid = client.uid, - args = Base.slice( arguments, 2 ), - instance; - - if ( components[ comp ] ) { - instance = pool[ uid ] = pool[ uid ] || - new components[ comp ]( client, me ); - - if ( instance[ fn ] ) { - return instance[ fn ].apply( instance, args ); - } - } - }; - - me.destroy = function() { - // @todo 删除池子中的所有实例 - return destroy && destroy.apply( this, arguments ); - }; - } - - Base.inherits( Runtime, { - constructor: Html5Runtime, - - // 不需要连接其他程序,直接执行callback - init: function() { - var me = this; - setTimeout(function() { - me.trigger('ready'); - }, 1 ); - } - - }); - - // 注册Components - Html5Runtime.register = function( name, component ) { - var klass = components[ name ] = Base.inherits( CompBase, component ); - return klass; - }; - - // 注册html5运行时。 - // 只有在支持的前提下注册。 - if ( window.Blob && window.FileReader && window.DataView ) { - Runtime.addRuntime( type, Html5Runtime ); - } - - return Html5Runtime; - }); - /** - * @fileOverview Blob Html实现 - */ - define('runtime/html5/blob',[ - 'runtime/html5/runtime', - 'lib/blob' - ], function( Html5Runtime, Blob ) { - - return Html5Runtime.register( 'Blob', { - slice: function( start, end ) { - var blob = this.owner.source, - slice = blob.slice || blob.webkitSlice || blob.mozSlice; - - blob = slice.call( blob, start, end ); - - return new Blob( this.getRuid(), blob ); - } - }); - }); - /** - * @fileOverview FilePaste - */ - define('runtime/html5/dnd',[ - 'base', - 'runtime/html5/runtime', - 'lib/file' - ], function( Base, Html5Runtime, File ) { - - var $ = Base.$, - prefix = 'webuploader-dnd-'; - - return Html5Runtime.register( 'DragAndDrop', { - init: function() { - var elem = this.elem = this.options.container; - - this.dragEnterHandler = Base.bindFn( this._dragEnterHandler, this ); - this.dragOverHandler = Base.bindFn( this._dragOverHandler, this ); - this.dragLeaveHandler = Base.bindFn( this._dragLeaveHandler, this ); - this.dropHandler = Base.bindFn( this._dropHandler, this ); - this.dndOver = false; - - elem.on( 'dragenter', this.dragEnterHandler ); - elem.on( 'dragover', this.dragOverHandler ); - elem.on( 'dragleave', this.dragLeaveHandler ); - elem.on( 'drop', this.dropHandler ); - - if ( this.options.disableGlobalDnd ) { - $( document ).on( 'dragover', this.dragOverHandler ); - $( document ).on( 'drop', this.dropHandler ); - } - }, - - _dragEnterHandler: function( e ) { - var me = this, - denied = me._denied || false, - items; - - e = e.originalEvent || e; - - if ( !me.dndOver ) { - me.dndOver = true; - - // 注意只有 chrome 支持。 - items = e.dataTransfer.items; - - if ( items && items.length ) { - me._denied = denied = !me.trigger( 'accept', items ); - } - - me.elem.addClass( prefix + 'over' ); - me.elem[ denied ? 'addClass' : - 'removeClass' ]( prefix + 'denied' ); - } - - e.dataTransfer.dropEffect = denied ? 'none' : 'copy'; - - return false; - }, - - _dragOverHandler: function( e ) { - // 只处理框内的。 - var parentElem = this.elem.parent().get( 0 ); - if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) { - return false; - } - - clearTimeout( this._leaveTimer ); - this._dragEnterHandler.call( this, e ); - - return false; - }, - - _dragLeaveHandler: function() { - var me = this, - handler; - - handler = function() { - me.dndOver = false; - me.elem.removeClass( prefix + 'over ' + prefix + 'denied' ); - }; - - clearTimeout( me._leaveTimer ); - me._leaveTimer = setTimeout( handler, 100 ); - return false; - }, - - _dropHandler: function( e ) { - var me = this, - ruid = me.getRuid(), - parentElem = me.elem.parent().get( 0 ), - dataTransfer, data; - - // 只处理框内的。 - if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) { - return false; - } - - e = e.originalEvent || e; - dataTransfer = e.dataTransfer; - - // 如果是页面内拖拽,还不能处理,不阻止事件。 - // 此处 ie11 下会报参数错误, - try { - data = dataTransfer.getData('text/html'); - } catch( err ) { - } - - if ( data ) { - return; - } - - me._getTansferFiles( dataTransfer, function( results ) { - me.trigger( 'drop', $.map( results, function( file ) { - return new File( ruid, file ); - }) ); - }); - - me.dndOver = false; - me.elem.removeClass( prefix + 'over' ); - return false; - }, - - // 如果传入 callback 则去查看文件夹,否则只管当前文件夹。 - _getTansferFiles: function( dataTransfer, callback ) { - var results = [], - promises = [], - items, files, file, item, i, len, canAccessFolder; - - items = dataTransfer.items; - files = dataTransfer.files; - - canAccessFolder = !!(items && items[ 0 ].webkitGetAsEntry); - - for ( i = 0, len = files.length; i < len; i++ ) { - file = files[ i ]; - item = items && items[ i ]; - - if ( canAccessFolder && item.webkitGetAsEntry().isDirectory ) { - - promises.push( this._traverseDirectoryTree( - item.webkitGetAsEntry(), results ) ); - } else { - results.push( file ); - } - } - - Base.when.apply( Base, promises ).done(function() { - - if ( !results.length ) { - return; - } - - callback( results ); - }); - }, - - _traverseDirectoryTree: function( entry, results ) { - var deferred = Base.Deferred(), - me = this; - - if ( entry.isFile ) { - entry.file(function( file ) { - results.push( file ); - deferred.resolve(); - }); - } else if ( entry.isDirectory ) { - entry.createReader().readEntries(function( entries ) { - var len = entries.length, - promises = [], - arr = [], // 为了保证顺序。 - i; - - for ( i = 0; i < len; i++ ) { - promises.push( me._traverseDirectoryTree( - entries[ i ], arr ) ); - } - - Base.when.apply( Base, promises ).then(function() { - results.push.apply( results, arr ); - deferred.resolve(); - }, deferred.reject ); - }); - } - - return deferred.promise(); - }, - - destroy: function() { - var elem = this.elem; - - // 还没 init 就调用 destroy - if (!elem) { - return; - } - - elem.off( 'dragenter', this.dragEnterHandler ); - elem.off( 'dragover', this.dragOverHandler ); - elem.off( 'dragleave', this.dragLeaveHandler ); - elem.off( 'drop', this.dropHandler ); - - if ( this.options.disableGlobalDnd ) { - $( document ).off( 'dragover', this.dragOverHandler ); - $( document ).off( 'drop', this.dropHandler ); - } - } - }); - }); - - /** - * @fileOverview FilePaste - */ - define('runtime/html5/filepaste',[ - 'base', - 'runtime/html5/runtime', - 'lib/file' - ], function( Base, Html5Runtime, File ) { - - return Html5Runtime.register( 'FilePaste', { - init: function() { - var opts = this.options, - elem = this.elem = opts.container, - accept = '.*', - arr, i, len, item; - - // accetp的mimeTypes中生成匹配正则。 - if ( opts.accept ) { - arr = []; - - for ( i = 0, len = opts.accept.length; i < len; i++ ) { - item = opts.accept[ i ].mimeTypes; - item && arr.push( item ); - } - - if ( arr.length ) { - accept = arr.join(','); - accept = accept.replace( /,/g, '|' ).replace( /\*/g, '.*' ); - } - } - this.accept = accept = new RegExp( accept, 'i' ); - this.hander = Base.bindFn( this._pasteHander, this ); - elem.on( 'paste', this.hander ); - }, - - _pasteHander: function( e ) { - var allowed = [], - ruid = this.getRuid(), - items, item, blob, i, len; - - e = e.originalEvent || e; - items = e.clipboardData.items; - - for ( i = 0, len = items.length; i < len; i++ ) { - item = items[ i ]; - - if ( item.kind !== 'file' || !(blob = item.getAsFile()) ) { - continue; - } - - allowed.push( new File( ruid, blob ) ); - } - - if ( allowed.length ) { - // 不阻止非文件粘贴(文字粘贴)的事件冒泡 - e.preventDefault(); - e.stopPropagation(); - this.trigger( 'paste', allowed ); - } - }, - - destroy: function() { - this.elem.off( 'paste', this.hander ); - } - }); - }); - - /** - * @fileOverview FilePicker - */ - define('runtime/html5/filepicker',[ - 'base', - 'runtime/html5/runtime' - ], function( Base, Html5Runtime ) { - - var $ = Base.$; - - return Html5Runtime.register( 'FilePicker', { - init: function() { - var container = this.getRuntime().getContainer(), - me = this, - owner = me.owner, - opts = me.options, - label = this.label = $( document.createElement('label') ), - input = this.input = $( document.createElement('input') ), - arr, i, len, mouseHandler; - - input.attr( 'type', 'file' ); - input.attr( 'name', opts.name ); - input.addClass('webuploader-element-invisible'); - - label.on( 'click', function() { - input.trigger('click'); - }); - - label.css({ - opacity: 0, - width: '100%', - height: '100%', - display: 'block', - cursor: 'pointer', - background: '#ffffff' - }); - - if ( opts.multiple ) { - input.attr( 'multiple', 'multiple' ); - } - - // @todo Firefox不支持单独指定后缀 - if ( opts.accept && opts.accept.length > 0 ) { - arr = []; - - for ( i = 0, len = opts.accept.length; i < len; i++ ) { - arr.push( opts.accept[ i ].mimeTypes ); - } - - input.attr( 'accept', arr.join(',') ); - } - - container.append( input ); - container.append( label ); - - mouseHandler = function( e ) { - owner.trigger( e.type ); - }; - - input.on( 'change', function( e ) { - var fn = arguments.callee, - clone; - - me.files = e.target.files; - - // reset input - clone = this.cloneNode( true ); - clone.value = null; - this.parentNode.replaceChild( clone, this ); - - input.off(); - input = $( clone ).on( 'change', fn ) - .on( 'mouseenter mouseleave', mouseHandler ); - - owner.trigger('change'); - }); - - label.on( 'mouseenter mouseleave', mouseHandler ); - - }, - - - getFiles: function() { - return this.files; - }, - - destroy: function() { - this.input.off(); - this.label.off(); - } - }); - }); - /** - * Terms: - * - * Uint8Array, FileReader, BlobBuilder, atob, ArrayBuffer - * @fileOverview Image控件 - */ - define('runtime/html5/util',[ - 'base' - ], function( Base ) { - - var urlAPI = window.createObjectURL && window || - window.URL && URL.revokeObjectURL && URL || - window.webkitURL, - createObjectURL = Base.noop, - revokeObjectURL = createObjectURL; - - if ( urlAPI ) { - - // 更安全的方式调用,比如android里面就能把context改成其他的对象。 - createObjectURL = function() { - return urlAPI.createObjectURL.apply( urlAPI, arguments ); - }; - - revokeObjectURL = function() { - return urlAPI.revokeObjectURL.apply( urlAPI, arguments ); - }; - } - - return { - createObjectURL: createObjectURL, - revokeObjectURL: revokeObjectURL, - - dataURL2Blob: function( dataURI ) { - var byteStr, intArray, ab, i, mimetype, parts; - - parts = dataURI.split(','); - - if ( ~parts[ 0 ].indexOf('base64') ) { - byteStr = atob( parts[ 1 ] ); - } else { - byteStr = decodeURIComponent( parts[ 1 ] ); - } - - ab = new ArrayBuffer( byteStr.length ); - intArray = new Uint8Array( ab ); - - for ( i = 0; i < byteStr.length; i++ ) { - intArray[ i ] = byteStr.charCodeAt( i ); - } - - mimetype = parts[ 0 ].split(':')[ 1 ].split(';')[ 0 ]; - - return this.arrayBufferToBlob( ab, mimetype ); - }, - - dataURL2ArrayBuffer: function( dataURI ) { - var byteStr, intArray, i, parts; - - parts = dataURI.split(','); - - if ( ~parts[ 0 ].indexOf('base64') ) { - byteStr = atob( parts[ 1 ] ); - } else { - byteStr = decodeURIComponent( parts[ 1 ] ); - } - - intArray = new Uint8Array( byteStr.length ); - - for ( i = 0; i < byteStr.length; i++ ) { - intArray[ i ] = byteStr.charCodeAt( i ); - } - - return intArray.buffer; - }, - - arrayBufferToBlob: function( buffer, type ) { - var builder = window.BlobBuilder || window.WebKitBlobBuilder, - bb; - - // android不支持直接new Blob, 只能借助blobbuilder. - if ( builder ) { - bb = new builder(); - bb.append( buffer ); - return bb.getBlob( type ); - } - - return new Blob([ buffer ], type ? { type: type } : {} ); - }, - - // 抽出来主要是为了解决android下面canvas.toDataUrl不支持jpeg. - // 你得到的结果是png. - canvasToDataUrl: function( canvas, type, quality ) { - return canvas.toDataURL( type, quality / 100 ); - }, - - // imagemeat会复写这个方法,如果用户选择加载那个文件了的话。 - parseMeta: function( blob, callback ) { - callback( false, {}); - }, - - // imagemeat会复写这个方法,如果用户选择加载那个文件了的话。 - updateImageHead: function( data ) { - return data; - } - }; - }); - /** - * Terms: - * - * Uint8Array, FileReader, BlobBuilder, atob, ArrayBuffer - * @fileOverview Image控件 - */ - define('runtime/html5/imagemeta',[ - 'runtime/html5/util' - ], function( Util ) { - - var api; - - api = { - parsers: { - 0xffe1: [] - }, - - maxMetaDataSize: 262144, - - parse: function( blob, cb ) { - var me = this, - fr = new FileReader(); - - fr.onload = function() { - cb( false, me._parse( this.result ) ); - fr = fr.onload = fr.onerror = null; - }; - - fr.onerror = function( e ) { - cb( e.message ); - fr = fr.onload = fr.onerror = null; - }; - - blob = blob.slice( 0, me.maxMetaDataSize ); - fr.readAsArrayBuffer( blob.getSource() ); - }, - - _parse: function( buffer, noParse ) { - if ( buffer.byteLength < 6 ) { - return; - } - - var dataview = new DataView( buffer ), - offset = 2, - maxOffset = dataview.byteLength - 4, - headLength = offset, - ret = {}, - markerBytes, markerLength, parsers, i; - - if ( dataview.getUint16( 0 ) === 0xffd8 ) { - - while ( offset < maxOffset ) { - markerBytes = dataview.getUint16( offset ); - - if ( markerBytes >= 0xffe0 && markerBytes <= 0xffef || - markerBytes === 0xfffe ) { - - markerLength = dataview.getUint16( offset + 2 ) + 2; - - if ( offset + markerLength > dataview.byteLength ) { - break; - } - - parsers = api.parsers[ markerBytes ]; - - if ( !noParse && parsers ) { - for ( i = 0; i < parsers.length; i += 1 ) { - parsers[ i ].call( api, dataview, offset, - markerLength, ret ); - } - } - - offset += markerLength; - headLength = offset; - } else { - break; - } - } - - if ( headLength > 6 ) { - if ( buffer.slice ) { - ret.imageHead = buffer.slice( 2, headLength ); - } else { - // Workaround for IE10, which does not yet - // support ArrayBuffer.slice: - ret.imageHead = new Uint8Array( buffer ) - .subarray( 2, headLength ); - } - } - } - - return ret; - }, - - updateImageHead: function( buffer, head ) { - var data = this._parse( buffer, true ), - buf1, buf2, bodyoffset; - - - bodyoffset = 2; - if ( data.imageHead ) { - bodyoffset = 2 + data.imageHead.byteLength; - } - - if ( buffer.slice ) { - buf2 = buffer.slice( bodyoffset ); - } else { - buf2 = new Uint8Array( buffer ).subarray( bodyoffset ); - } - - buf1 = new Uint8Array( head.byteLength + 2 + buf2.byteLength ); - - buf1[ 0 ] = 0xFF; - buf1[ 1 ] = 0xD8; - buf1.set( new Uint8Array( head ), 2 ); - buf1.set( new Uint8Array( buf2 ), head.byteLength + 2 ); - - return buf1.buffer; - } - }; - - Util.parseMeta = function() { - return api.parse.apply( api, arguments ); - }; - - Util.updateImageHead = function() { - return api.updateImageHead.apply( api, arguments ); - }; - - return api; - }); - /** - * 代码来自于:https://github.com/blueimp/JavaScript-Load-Image - * 暂时项目中只用了orientation. - * - * 去除了 Exif Sub IFD Pointer, GPS Info IFD Pointer, Exif Thumbnail. - * @fileOverview EXIF解析 - */ - - // Sample - // ==================================== - // Make : Apple - // Model : iPhone 4S - // Orientation : 1 - // XResolution : 72 [72/1] - // YResolution : 72 [72/1] - // ResolutionUnit : 2 - // Software : QuickTime 7.7.1 - // DateTime : 2013:09:01 22:53:55 - // ExifIFDPointer : 190 - // ExposureTime : 0.058823529411764705 [1/17] - // FNumber : 2.4 [12/5] - // ExposureProgram : Normal program - // ISOSpeedRatings : 800 - // ExifVersion : 0220 - // DateTimeOriginal : 2013:09:01 22:52:51 - // DateTimeDigitized : 2013:09:01 22:52:51 - // ComponentsConfiguration : YCbCr - // ShutterSpeedValue : 4.058893515764426 - // ApertureValue : 2.5260688216892597 [4845/1918] - // BrightnessValue : -0.3126686601998395 - // MeteringMode : Pattern - // Flash : Flash did not fire, compulsory flash mode - // FocalLength : 4.28 [107/25] - // SubjectArea : [4 values] - // FlashpixVersion : 0100 - // ColorSpace : 1 - // PixelXDimension : 2448 - // PixelYDimension : 3264 - // SensingMethod : One-chip color area sensor - // ExposureMode : 0 - // WhiteBalance : Auto white balance - // FocalLengthIn35mmFilm : 35 - // SceneCaptureType : Standard - define('runtime/html5/imagemeta/exif',[ - 'base', - 'runtime/html5/imagemeta' - ], function( Base, ImageMeta ) { - - var EXIF = {}; - - EXIF.ExifMap = function() { - return this; - }; - - EXIF.ExifMap.prototype.map = { - 'Orientation': 0x0112 - }; - - EXIF.ExifMap.prototype.get = function( id ) { - return this[ id ] || this[ this.map[ id ] ]; - }; - - EXIF.exifTagTypes = { - // byte, 8-bit unsigned int: - 1: { - getValue: function( dataView, dataOffset ) { - return dataView.getUint8( dataOffset ); - }, - size: 1 - }, - - // ascii, 8-bit byte: - 2: { - getValue: function( dataView, dataOffset ) { - return String.fromCharCode( dataView.getUint8( dataOffset ) ); - }, - size: 1, - ascii: true - }, - - // short, 16 bit int: - 3: { - getValue: function( dataView, dataOffset, littleEndian ) { - return dataView.getUint16( dataOffset, littleEndian ); - }, - size: 2 - }, - - // long, 32 bit int: - 4: { - getValue: function( dataView, dataOffset, littleEndian ) { - return dataView.getUint32( dataOffset, littleEndian ); - }, - size: 4 - }, - - // rational = two long values, - // first is numerator, second is denominator: - 5: { - getValue: function( dataView, dataOffset, littleEndian ) { - return dataView.getUint32( dataOffset, littleEndian ) / - dataView.getUint32( dataOffset + 4, littleEndian ); - }, - size: 8 - }, - - // slong, 32 bit signed int: - 9: { - getValue: function( dataView, dataOffset, littleEndian ) { - return dataView.getInt32( dataOffset, littleEndian ); - }, - size: 4 - }, - - // srational, two slongs, first is numerator, second is denominator: - 10: { - getValue: function( dataView, dataOffset, littleEndian ) { - return dataView.getInt32( dataOffset, littleEndian ) / - dataView.getInt32( dataOffset + 4, littleEndian ); - }, - size: 8 - } - }; - - // undefined, 8-bit byte, value depending on field: - EXIF.exifTagTypes[ 7 ] = EXIF.exifTagTypes[ 1 ]; - - EXIF.getExifValue = function( dataView, tiffOffset, offset, type, length, - littleEndian ) { - - var tagType = EXIF.exifTagTypes[ type ], - tagSize, dataOffset, values, i, str, c; - - if ( !tagType ) { - Base.log('Invalid Exif data: Invalid tag type.'); - return; - } - - tagSize = tagType.size * length; - - // Determine if the value is contained in the dataOffset bytes, - // or if the value at the dataOffset is a pointer to the actual data: - dataOffset = tagSize > 4 ? tiffOffset + dataView.getUint32( offset + 8, - littleEndian ) : (offset + 8); - - if ( dataOffset + tagSize > dataView.byteLength ) { - Base.log('Invalid Exif data: Invalid data offset.'); - return; - } - - if ( length === 1 ) { - return tagType.getValue( dataView, dataOffset, littleEndian ); - } - - values = []; - - for ( i = 0; i < length; i += 1 ) { - values[ i ] = tagType.getValue( dataView, - dataOffset + i * tagType.size, littleEndian ); - } - - if ( tagType.ascii ) { - str = ''; - - // Concatenate the chars: - for ( i = 0; i < values.length; i += 1 ) { - c = values[ i ]; - - // Ignore the terminating NULL byte(s): - if ( c === '\u0000' ) { - break; - } - str += c; - } - - return str; - } - return values; - }; - - EXIF.parseExifTag = function( dataView, tiffOffset, offset, littleEndian, - data ) { - - var tag = dataView.getUint16( offset, littleEndian ); - data.exif[ tag ] = EXIF.getExifValue( dataView, tiffOffset, offset, - dataView.getUint16( offset + 2, littleEndian ), // tag type - dataView.getUint32( offset + 4, littleEndian ), // tag length - littleEndian ); - }; - - EXIF.parseExifTags = function( dataView, tiffOffset, dirOffset, - littleEndian, data ) { - - var tagsNumber, dirEndOffset, i; - - if ( dirOffset + 6 > dataView.byteLength ) { - Base.log('Invalid Exif data: Invalid directory offset.'); - return; - } - - tagsNumber = dataView.getUint16( dirOffset, littleEndian ); - dirEndOffset = dirOffset + 2 + 12 * tagsNumber; - - if ( dirEndOffset + 4 > dataView.byteLength ) { - Base.log('Invalid Exif data: Invalid directory size.'); - return; - } - - for ( i = 0; i < tagsNumber; i += 1 ) { - this.parseExifTag( dataView, tiffOffset, - dirOffset + 2 + 12 * i, // tag offset - littleEndian, data ); - } - - // Return the offset to the next directory: - return dataView.getUint32( dirEndOffset, littleEndian ); - }; - - // EXIF.getExifThumbnail = function(dataView, offset, length) { - // var hexData, - // i, - // b; - // if (!length || offset + length > dataView.byteLength) { - // Base.log('Invalid Exif data: Invalid thumbnail data.'); - // return; - // } - // hexData = []; - // for (i = 0; i < length; i += 1) { - // b = dataView.getUint8(offset + i); - // hexData.push((b < 16 ? '0' : '') + b.toString(16)); - // } - // return 'data:image/jpeg,%' + hexData.join('%'); - // }; - - EXIF.parseExifData = function( dataView, offset, length, data ) { - - var tiffOffset = offset + 10, - littleEndian, dirOffset; - - // Check for the ASCII code for "Exif" (0x45786966): - if ( dataView.getUint32( offset + 4 ) !== 0x45786966 ) { - // No Exif data, might be XMP data instead - return; - } - if ( tiffOffset + 8 > dataView.byteLength ) { - Base.log('Invalid Exif data: Invalid segment size.'); - return; - } - - // Check for the two null bytes: - if ( dataView.getUint16( offset + 8 ) !== 0x0000 ) { - Base.log('Invalid Exif data: Missing byte alignment offset.'); - return; - } - - // Check the byte alignment: - switch ( dataView.getUint16( tiffOffset ) ) { - case 0x4949: - littleEndian = true; - break; - - case 0x4D4D: - littleEndian = false; - break; - - default: - Base.log('Invalid Exif data: Invalid byte alignment marker.'); - return; - } - - // Check for the TIFF tag marker (0x002A): - if ( dataView.getUint16( tiffOffset + 2, littleEndian ) !== 0x002A ) { - Base.log('Invalid Exif data: Missing TIFF marker.'); - return; - } - - // Retrieve the directory offset bytes, usually 0x00000008 or 8 decimal: - dirOffset = dataView.getUint32( tiffOffset + 4, littleEndian ); - // Create the exif object to store the tags: - data.exif = new EXIF.ExifMap(); - // Parse the tags of the main image directory and retrieve the - // offset to the next directory, usually the thumbnail directory: - dirOffset = EXIF.parseExifTags( dataView, tiffOffset, - tiffOffset + dirOffset, littleEndian, data ); - - // 尝试读取缩略图 - // if ( dirOffset ) { - // thumbnailData = {exif: {}}; - // dirOffset = EXIF.parseExifTags( - // dataView, - // tiffOffset, - // tiffOffset + dirOffset, - // littleEndian, - // thumbnailData - // ); - - // // Check for JPEG Thumbnail offset: - // if (thumbnailData.exif[0x0201]) { - // data.exif.Thumbnail = EXIF.getExifThumbnail( - // dataView, - // tiffOffset + thumbnailData.exif[0x0201], - // thumbnailData.exif[0x0202] // Thumbnail data length - // ); - // } - // } - }; - - ImageMeta.parsers[ 0xffe1 ].push( EXIF.parseExifData ); - return EXIF; - }); - /** - * 这个方式性能不行,但是可以解决android里面的toDataUrl的bug - * android里面toDataUrl('image/jpege')得到的结果却是png. - * - * 所以这里没辙,只能借助这个工具 - * @fileOverview jpeg encoder - */ - define('runtime/html5/jpegencoder',[], function( require, exports, module ) { - - /* - Copyright (c) 2008, Adobe Systems Incorporated - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are - met: - - * Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the name of Adobe Systems Incorporated nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS - IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - /* - JPEG encoder ported to JavaScript and optimized by Andreas Ritter, www.bytestrom.eu, 11/2009 - - Basic GUI blocking jpeg encoder - */ - - function JPEGEncoder(quality) { - var self = this; - var fround = Math.round; - var ffloor = Math.floor; - var YTable = new Array(64); - var UVTable = new Array(64); - var fdtbl_Y = new Array(64); - var fdtbl_UV = new Array(64); - var YDC_HT; - var UVDC_HT; - var YAC_HT; - var UVAC_HT; - - var bitcode = new Array(65535); - var category = new Array(65535); - var outputfDCTQuant = new Array(64); - var DU = new Array(64); - var byteout = []; - var bytenew = 0; - var bytepos = 7; - - var YDU = new Array(64); - var UDU = new Array(64); - var VDU = new Array(64); - var clt = new Array(256); - var RGB_YUV_TABLE = new Array(2048); - var currentQuality; - - var ZigZag = [ - 0, 1, 5, 6,14,15,27,28, - 2, 4, 7,13,16,26,29,42, - 3, 8,12,17,25,30,41,43, - 9,11,18,24,31,40,44,53, - 10,19,23,32,39,45,52,54, - 20,22,33,38,46,51,55,60, - 21,34,37,47,50,56,59,61, - 35,36,48,49,57,58,62,63 - ]; - - var std_dc_luminance_nrcodes = [0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0]; - var std_dc_luminance_values = [0,1,2,3,4,5,6,7,8,9,10,11]; - var std_ac_luminance_nrcodes = [0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d]; - var std_ac_luminance_values = [ - 0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12, - 0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07, - 0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08, - 0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0, - 0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16, - 0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28, - 0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39, - 0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49, - 0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59, - 0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69, - 0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79, - 0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89, - 0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98, - 0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7, - 0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6, - 0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5, - 0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4, - 0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2, - 0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea, - 0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8, - 0xf9,0xfa - ]; - - var std_dc_chrominance_nrcodes = [0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0]; - var std_dc_chrominance_values = [0,1,2,3,4,5,6,7,8,9,10,11]; - var std_ac_chrominance_nrcodes = [0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77]; - var std_ac_chrominance_values = [ - 0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21, - 0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71, - 0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91, - 0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0, - 0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34, - 0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26, - 0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38, - 0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48, - 0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58, - 0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68, - 0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78, - 0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87, - 0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96, - 0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5, - 0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4, - 0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3, - 0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2, - 0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda, - 0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9, - 0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8, - 0xf9,0xfa - ]; - - function initQuantTables(sf){ - var YQT = [ - 16, 11, 10, 16, 24, 40, 51, 61, - 12, 12, 14, 19, 26, 58, 60, 55, - 14, 13, 16, 24, 40, 57, 69, 56, - 14, 17, 22, 29, 51, 87, 80, 62, - 18, 22, 37, 56, 68,109,103, 77, - 24, 35, 55, 64, 81,104,113, 92, - 49, 64, 78, 87,103,121,120,101, - 72, 92, 95, 98,112,100,103, 99 - ]; - - for (var i = 0; i < 64; i++) { - var t = ffloor((YQT[i]*sf+50)/100); - if (t < 1) { - t = 1; - } else if (t > 255) { - t = 255; - } - YTable[ZigZag[i]] = t; - } - var UVQT = [ - 17, 18, 24, 47, 99, 99, 99, 99, - 18, 21, 26, 66, 99, 99, 99, 99, - 24, 26, 56, 99, 99, 99, 99, 99, - 47, 66, 99, 99, 99, 99, 99, 99, - 99, 99, 99, 99, 99, 99, 99, 99, - 99, 99, 99, 99, 99, 99, 99, 99, - 99, 99, 99, 99, 99, 99, 99, 99, - 99, 99, 99, 99, 99, 99, 99, 99 - ]; - for (var j = 0; j < 64; j++) { - var u = ffloor((UVQT[j]*sf+50)/100); - if (u < 1) { - u = 1; - } else if (u > 255) { - u = 255; - } - UVTable[ZigZag[j]] = u; - } - var aasf = [ - 1.0, 1.387039845, 1.306562965, 1.175875602, - 1.0, 0.785694958, 0.541196100, 0.275899379 - ]; - var k = 0; - for (var row = 0; row < 8; row++) - { - for (var col = 0; col < 8; col++) - { - fdtbl_Y[k] = (1.0 / (YTable [ZigZag[k]] * aasf[row] * aasf[col] * 8.0)); - fdtbl_UV[k] = (1.0 / (UVTable[ZigZag[k]] * aasf[row] * aasf[col] * 8.0)); - k++; - } - } - } - - function computeHuffmanTbl(nrcodes, std_table){ - var codevalue = 0; - var pos_in_table = 0; - var HT = new Array(); - for (var k = 1; k <= 16; k++) { - for (var j = 1; j <= nrcodes[k]; j++) { - HT[std_table[pos_in_table]] = []; - HT[std_table[pos_in_table]][0] = codevalue; - HT[std_table[pos_in_table]][1] = k; - pos_in_table++; - codevalue++; - } - codevalue*=2; - } - return HT; - } - - function initHuffmanTbl() - { - YDC_HT = computeHuffmanTbl(std_dc_luminance_nrcodes,std_dc_luminance_values); - UVDC_HT = computeHuffmanTbl(std_dc_chrominance_nrcodes,std_dc_chrominance_values); - YAC_HT = computeHuffmanTbl(std_ac_luminance_nrcodes,std_ac_luminance_values); - UVAC_HT = computeHuffmanTbl(std_ac_chrominance_nrcodes,std_ac_chrominance_values); - } - - function initCategoryNumber() - { - var nrlower = 1; - var nrupper = 2; - for (var cat = 1; cat <= 15; cat++) { - //Positive numbers - for (var nr = nrlower; nr>0] = 38470 * i; - RGB_YUV_TABLE[(i+ 512)>>0] = 7471 * i + 0x8000; - RGB_YUV_TABLE[(i+ 768)>>0] = -11059 * i; - RGB_YUV_TABLE[(i+1024)>>0] = -21709 * i; - RGB_YUV_TABLE[(i+1280)>>0] = 32768 * i + 0x807FFF; - RGB_YUV_TABLE[(i+1536)>>0] = -27439 * i; - RGB_YUV_TABLE[(i+1792)>>0] = - 5329 * i; - } - } - - // IO functions - function writeBits(bs) - { - var value = bs[0]; - var posval = bs[1]-1; - while ( posval >= 0 ) { - if (value & (1 << posval) ) { - bytenew |= (1 << bytepos); - } - posval--; - bytepos--; - if (bytepos < 0) { - if (bytenew == 0xFF) { - writeByte(0xFF); - writeByte(0); - } - else { - writeByte(bytenew); - } - bytepos=7; - bytenew=0; - } - } - } - - function writeByte(value) - { - byteout.push(clt[value]); // write char directly instead of converting later - } - - function writeWord(value) - { - writeByte((value>>8)&0xFF); - writeByte((value )&0xFF); - } - - // DCT & quantization core - function fDCTQuant(data, fdtbl) - { - var d0, d1, d2, d3, d4, d5, d6, d7; - /* Pass 1: process rows. */ - var dataOff=0; - var i; - var I8 = 8; - var I64 = 64; - for (i=0; i 0.0) ? ((fDCTQuant + 0.5)|0) : ((fDCTQuant - 0.5)|0); - //outputfDCTQuant[i] = fround(fDCTQuant); - - } - return outputfDCTQuant; - } - - function writeAPP0() - { - writeWord(0xFFE0); // marker - writeWord(16); // length - writeByte(0x4A); // J - writeByte(0x46); // F - writeByte(0x49); // I - writeByte(0x46); // F - writeByte(0); // = "JFIF",'\0' - writeByte(1); // versionhi - writeByte(1); // versionlo - writeByte(0); // xyunits - writeWord(1); // xdensity - writeWord(1); // ydensity - writeByte(0); // thumbnwidth - writeByte(0); // thumbnheight - } - - function writeSOF0(width, height) - { - writeWord(0xFFC0); // marker - writeWord(17); // length, truecolor YUV JPG - writeByte(8); // precision - writeWord(height); - writeWord(width); - writeByte(3); // nrofcomponents - writeByte(1); // IdY - writeByte(0x11); // HVY - writeByte(0); // QTY - writeByte(2); // IdU - writeByte(0x11); // HVU - writeByte(1); // QTU - writeByte(3); // IdV - writeByte(0x11); // HVV - writeByte(1); // QTV - } - - function writeDQT() - { - writeWord(0xFFDB); // marker - writeWord(132); // length - writeByte(0); - for (var i=0; i<64; i++) { - writeByte(YTable[i]); - } - writeByte(1); - for (var j=0; j<64; j++) { - writeByte(UVTable[j]); - } - } - - function writeDHT() - { - writeWord(0xFFC4); // marker - writeWord(0x01A2); // length - - writeByte(0); // HTYDCinfo - for (var i=0; i<16; i++) { - writeByte(std_dc_luminance_nrcodes[i+1]); - } - for (var j=0; j<=11; j++) { - writeByte(std_dc_luminance_values[j]); - } - - writeByte(0x10); // HTYACinfo - for (var k=0; k<16; k++) { - writeByte(std_ac_luminance_nrcodes[k+1]); - } - for (var l=0; l<=161; l++) { - writeByte(std_ac_luminance_values[l]); - } - - writeByte(1); // HTUDCinfo - for (var m=0; m<16; m++) { - writeByte(std_dc_chrominance_nrcodes[m+1]); - } - for (var n=0; n<=11; n++) { - writeByte(std_dc_chrominance_values[n]); - } - - writeByte(0x11); // HTUACinfo - for (var o=0; o<16; o++) { - writeByte(std_ac_chrominance_nrcodes[o+1]); - } - for (var p=0; p<=161; p++) { - writeByte(std_ac_chrominance_values[p]); - } - } - - function writeSOS() - { - writeWord(0xFFDA); // marker - writeWord(12); // length - writeByte(3); // nrofcomponents - writeByte(1); // IdY - writeByte(0); // HTY - writeByte(2); // IdU - writeByte(0x11); // HTU - writeByte(3); // IdV - writeByte(0x11); // HTV - writeByte(0); // Ss - writeByte(0x3f); // Se - writeByte(0); // Bf - } - - function processDU(CDU, fdtbl, DC, HTDC, HTAC){ - var EOB = HTAC[0x00]; - var M16zeroes = HTAC[0xF0]; - var pos; - var I16 = 16; - var I63 = 63; - var I64 = 64; - var DU_DCT = fDCTQuant(CDU, fdtbl); - //ZigZag reorder - for (var j=0;j0)&&(DU[end0pos]==0); end0pos--) {}; - //end0pos = first element in reverse order !=0 - if ( end0pos == 0) { - writeBits(EOB); - return DC; - } - var i = 1; - var lng; - while ( i <= end0pos ) { - var startpos = i; - for (; (DU[i]==0) && (i<=end0pos); ++i) {} - var nrzeroes = i-startpos; - if ( nrzeroes >= I16 ) { - lng = nrzeroes>>4; - for (var nrmarker=1; nrmarker <= lng; ++nrmarker) - writeBits(M16zeroes); - nrzeroes = nrzeroes&0xF; - } - pos = 32767+DU[i]; - writeBits(HTAC[(nrzeroes<<4)+category[pos]]); - writeBits(bitcode[pos]); - i++; - } - if ( end0pos != I63 ) { - writeBits(EOB); - } - return DC; - } - - function initCharLookupTable(){ - var sfcc = String.fromCharCode; - for(var i=0; i < 256; i++){ ///// ACHTUNG // 255 - clt[i] = sfcc(i); - } - } - - this.encode = function(image,quality) // image data object - { - // var time_start = new Date().getTime(); - - if(quality) setQuality(quality); - - // Initialize bit writer - byteout = new Array(); - bytenew=0; - bytepos=7; - - // Add JPEG headers - writeWord(0xFFD8); // SOI - writeAPP0(); - writeDQT(); - writeSOF0(image.width,image.height); - writeDHT(); - writeSOS(); - - - // Encode 8x8 macroblocks - var DCY=0; - var DCU=0; - var DCV=0; - - bytenew=0; - bytepos=7; - - - this.encode.displayName = "_encode_"; - - var imageData = image.data; - var width = image.width; - var height = image.height; - - var quadWidth = width*4; - var tripleWidth = width*3; - - var x, y = 0; - var r, g, b; - var start,p, col,row,pos; - while(y < height){ - x = 0; - while(x < quadWidth){ - start = quadWidth * y + x; - p = start; - col = -1; - row = 0; - - for(pos=0; pos < 64; pos++){ - row = pos >> 3;// /8 - col = ( pos & 7 ) * 4; // %8 - p = start + ( row * quadWidth ) + col; - - if(y+row >= height){ // padding bottom - p-= (quadWidth*(y+1+row-height)); - } - - if(x+col >= quadWidth){ // padding right - p-= ((x+col) - quadWidth +4) - } - - r = imageData[ p++ ]; - g = imageData[ p++ ]; - b = imageData[ p++ ]; - - - /* // calculate YUV values dynamically - YDU[pos]=((( 0.29900)*r+( 0.58700)*g+( 0.11400)*b))-128; //-0x80 - UDU[pos]=(((-0.16874)*r+(-0.33126)*g+( 0.50000)*b)); - VDU[pos]=((( 0.50000)*r+(-0.41869)*g+(-0.08131)*b)); - */ - - // use lookup table (slightly faster) - YDU[pos] = ((RGB_YUV_TABLE[r] + RGB_YUV_TABLE[(g + 256)>>0] + RGB_YUV_TABLE[(b + 512)>>0]) >> 16)-128; - UDU[pos] = ((RGB_YUV_TABLE[(r + 768)>>0] + RGB_YUV_TABLE[(g + 1024)>>0] + RGB_YUV_TABLE[(b + 1280)>>0]) >> 16)-128; - VDU[pos] = ((RGB_YUV_TABLE[(r + 1280)>>0] + RGB_YUV_TABLE[(g + 1536)>>0] + RGB_YUV_TABLE[(b + 1792)>>0]) >> 16)-128; - - } - - DCY = processDU(YDU, fdtbl_Y, DCY, YDC_HT, YAC_HT); - DCU = processDU(UDU, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); - DCV = processDU(VDU, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); - x+=32; - } - y+=8; - } - - - //////////////////////////////////////////////////////////////// - - // Do the bit alignment of the EOI marker - if ( bytepos >= 0 ) { - var fillbits = []; - fillbits[1] = bytepos+1; - fillbits[0] = (1<<(bytepos+1))-1; - writeBits(fillbits); - } - - writeWord(0xFFD9); //EOI - - var jpegDataUri = 'data:image/jpeg;base64,' + btoa(byteout.join('')); - - byteout = []; - - // benchmarking - // var duration = new Date().getTime() - time_start; - // console.log('Encoding time: '+ currentQuality + 'ms'); - // - - return jpegDataUri - } - - function setQuality(quality){ - if (quality <= 0) { - quality = 1; - } - if (quality > 100) { - quality = 100; - } - - if(currentQuality == quality) return // don't recalc if unchanged - - var sf = 0; - if (quality < 50) { - sf = Math.floor(5000 / quality); - } else { - sf = Math.floor(200 - quality*2); - } - - initQuantTables(sf); - currentQuality = quality; - // console.log('Quality set to: '+quality +'%'); - } - - function init(){ - // var time_start = new Date().getTime(); - if(!quality) quality = 50; - // Create tables - initCharLookupTable() - initHuffmanTbl(); - initCategoryNumber(); - initRGBYUVTable(); - - setQuality(quality); - // var duration = new Date().getTime() - time_start; - // console.log('Initialization '+ duration + 'ms'); - } - - init(); - - }; - - JPEGEncoder.encode = function( data, quality ) { - var encoder = new JPEGEncoder( quality ); - - return encoder.encode( data ); - } - - return JPEGEncoder; - }); - /** - * @fileOverview Fix android canvas.toDataUrl bug. - */ - define('runtime/html5/androidpatch',[ - 'runtime/html5/util', - 'runtime/html5/jpegencoder', - 'base' - ], function( Util, encoder, Base ) { - var origin = Util.canvasToDataUrl, - supportJpeg; - - Util.canvasToDataUrl = function( canvas, type, quality ) { - var ctx, w, h, fragement, parts; - - // 非android手机直接跳过。 - if ( !Base.os.android ) { - return origin.apply( null, arguments ); - } - - // 检测是否canvas支持jpeg导出,根据数据格式来判断。 - // JPEG 前两位分别是:255, 216 - if ( type === 'image/jpeg' && typeof supportJpeg === 'undefined' ) { - fragement = origin.apply( null, arguments ); - - parts = fragement.split(','); - - if ( ~parts[ 0 ].indexOf('base64') ) { - fragement = atob( parts[ 1 ] ); - } else { - fragement = decodeURIComponent( parts[ 1 ] ); - } - - fragement = fragement.substring( 0, 2 ); - - supportJpeg = fragement.charCodeAt( 0 ) === 255 && - fragement.charCodeAt( 1 ) === 216; - } - - // 只有在android环境下才修复 - if ( type === 'image/jpeg' && !supportJpeg ) { - w = canvas.width; - h = canvas.height; - ctx = canvas.getContext('2d'); - - return encoder.encode( ctx.getImageData( 0, 0, w, h ), quality ); - } - - return origin.apply( null, arguments ); - }; - }); - /** - * @fileOverview Image - */ - define('runtime/html5/image',[ - 'base', - 'runtime/html5/runtime', - 'runtime/html5/util' - ], function( Base, Html5Runtime, Util ) { - - var BLANK = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs%3D'; - - return Html5Runtime.register( 'Image', { - - // flag: 标记是否被修改过。 - modified: false, - - init: function() { - var me = this, - img = new Image(); - - img.onload = function() { - - me._info = { - type: me.type, - width: this.width, - height: this.height - }; - - // 读取meta信息。 - if ( !me._metas && 'image/jpeg' === me.type ) { - Util.parseMeta( me._blob, function( error, ret ) { - me._metas = ret; - me.owner.trigger('load'); - }); - } else { - me.owner.trigger('load'); - } - }; - - img.onerror = function() { - me.owner.trigger('error'); - }; - - me._img = img; - }, - - loadFromBlob: function( blob ) { - var me = this, - img = me._img; - - me._blob = blob; - me.type = blob.type; - img.src = Util.createObjectURL( blob.getSource() ); - me.owner.once( 'load', function() { - Util.revokeObjectURL( img.src ); - }); - }, - - resize: function( width, height ) { - var canvas = this._canvas || - (this._canvas = document.createElement('canvas')); - - this._resize( this._img, canvas, width, height ); - this._blob = null; // 没用了,可以删掉了。 - this.modified = true; - this.owner.trigger( 'complete', 'resize' ); - }, - - crop: function( x, y, w, h, s ) { - var cvs = this._canvas || - (this._canvas = document.createElement('canvas')), - opts = this.options, - img = this._img, - iw = img.naturalWidth, - ih = img.naturalHeight, - orientation = this.getOrientation(); - - s = s || 1; - - // todo 解决 orientation 的问题。 - // values that require 90 degree rotation - // if ( ~[ 5, 6, 7, 8 ].indexOf( orientation ) ) { - - // switch ( orientation ) { - // case 6: - // tmp = x; - // x = y; - // y = iw * s - tmp - w; - // console.log(ih * s, tmp, w) - // break; - // } - - // (w ^= h, h ^= w, w ^= h); - // } - - cvs.width = w; - cvs.height = h; - - opts.preserveHeaders || this._rotate2Orientaion( cvs, orientation ); - this._renderImageToCanvas( cvs, img, -x, -y, iw * s, ih * s ); - - this._blob = null; // 没用了,可以删掉了。 - this.modified = true; - this.owner.trigger( 'complete', 'crop' ); - }, - - getAsBlob: function( type ) { - var blob = this._blob, - opts = this.options, - canvas; - - type = type || this.type; - - // blob需要重新生成。 - if ( this.modified || this.type !== type ) { - canvas = this._canvas; - - if ( type === 'image/jpeg' ) { - - blob = Util.canvasToDataUrl( canvas, type, opts.quality ); - - if ( opts.preserveHeaders && this._metas && - this._metas.imageHead ) { - - blob = Util.dataURL2ArrayBuffer( blob ); - blob = Util.updateImageHead( blob, - this._metas.imageHead ); - blob = Util.arrayBufferToBlob( blob, type ); - return blob; - } - } else { - blob = Util.canvasToDataUrl( canvas, type ); - } - - blob = Util.dataURL2Blob( blob ); - } - - return blob; - }, - - getAsDataUrl: function( type ) { - var opts = this.options; - - type = type || this.type; - - if ( type === 'image/jpeg' ) { - return Util.canvasToDataUrl( this._canvas, type, opts.quality ); - } else { - return this._canvas.toDataURL( type ); - } - }, - - getOrientation: function() { - return this._metas && this._metas.exif && - this._metas.exif.get('Orientation') || 1; - }, - - info: function( val ) { - - // setter - if ( val ) { - this._info = val; - return this; - } - - // getter - return this._info; - }, - - meta: function( val ) { - - // setter - if ( val ) { - this._meta = val; - return this; - } - - // getter - return this._meta; - }, - - destroy: function() { - var canvas = this._canvas; - this._img.onload = null; - - if ( canvas ) { - canvas.getContext('2d') - .clearRect( 0, 0, canvas.width, canvas.height ); - canvas.width = canvas.height = 0; - this._canvas = null; - } - - // 释放内存。非常重要,否则释放不了image的内存。 - this._img.src = BLANK; - this._img = this._blob = null; - }, - - _resize: function( img, cvs, width, height ) { - var opts = this.options, - naturalWidth = img.width, - naturalHeight = img.height, - orientation = this.getOrientation(), - scale, w, h, x, y; - - // values that require 90 degree rotation - if ( ~[ 5, 6, 7, 8 ].indexOf( orientation ) ) { - - // 交换width, height的值。 - width ^= height; - height ^= width; - width ^= height; - } - - scale = Math[ opts.crop ? 'max' : 'min' ]( width / naturalWidth, - height / naturalHeight ); - - // 不允许放大。 - opts.allowMagnify || (scale = Math.min( 1, scale )); - - w = naturalWidth * scale; - h = naturalHeight * scale; - - if ( opts.crop ) { - cvs.width = width; - cvs.height = height; - } else { - cvs.width = w; - cvs.height = h; - } - - x = (cvs.width - w) / 2; - y = (cvs.height - h) / 2; - - opts.preserveHeaders || this._rotate2Orientaion( cvs, orientation ); - - this._renderImageToCanvas( cvs, img, x, y, w, h ); - }, - - _rotate2Orientaion: function( canvas, orientation ) { - var width = canvas.width, - height = canvas.height, - ctx = canvas.getContext('2d'); - - switch ( orientation ) { - case 5: - case 6: - case 7: - case 8: - canvas.width = height; - canvas.height = width; - break; - } - - switch ( orientation ) { - case 2: // horizontal flip - ctx.translate( width, 0 ); - ctx.scale( -1, 1 ); - break; - - case 3: // 180 rotate left - ctx.translate( width, height ); - ctx.rotate( Math.PI ); - break; - - case 4: // vertical flip - ctx.translate( 0, height ); - ctx.scale( 1, -1 ); - break; - - case 5: // vertical flip + 90 rotate right - ctx.rotate( 0.5 * Math.PI ); - ctx.scale( 1, -1 ); - break; - - case 6: // 90 rotate right - ctx.rotate( 0.5 * Math.PI ); - ctx.translate( 0, -height ); - break; - - case 7: // horizontal flip + 90 rotate right - ctx.rotate( 0.5 * Math.PI ); - ctx.translate( width, -height ); - ctx.scale( -1, 1 ); - break; - - case 8: // 90 rotate left - ctx.rotate( -0.5 * Math.PI ); - ctx.translate( -width, 0 ); - break; - } - }, - - // https://github.com/stomita/ios-imagefile-megapixel/ - // blob/master/src/megapix-image.js - _renderImageToCanvas: (function() { - - // 如果不是ios, 不需要这么复杂! - if ( !Base.os.ios ) { - return function( canvas ) { - var args = Base.slice( arguments, 1 ), - ctx = canvas.getContext('2d'); - - ctx.drawImage.apply( ctx, args ); - }; - } - - /** - * Detecting vertical squash in loaded image. - * Fixes a bug which squash image vertically while drawing into - * canvas for some images. - */ - function detectVerticalSquash( img, iw, ih ) { - var canvas = document.createElement('canvas'), - ctx = canvas.getContext('2d'), - sy = 0, - ey = ih, - py = ih, - data, alpha, ratio; - - - canvas.width = 1; - canvas.height = ih; - ctx.drawImage( img, 0, 0 ); - data = ctx.getImageData( 0, 0, 1, ih ).data; - - // search image edge pixel position in case - // it is squashed vertically. - while ( py > sy ) { - alpha = data[ (py - 1) * 4 + 3 ]; - - if ( alpha === 0 ) { - ey = py; - } else { - sy = py; - } - - py = (ey + sy) >> 1; - } - - ratio = (py / ih); - return (ratio === 0) ? 1 : ratio; - } - - // fix ie7 bug - // http://stackoverflow.com/questions/11929099/ - // html5-canvas-drawimage-ratio-bug-ios - if ( Base.os.ios >= 7 ) { - return function( canvas, img, x, y, w, h ) { - var iw = img.naturalWidth, - ih = img.naturalHeight, - vertSquashRatio = detectVerticalSquash( img, iw, ih ); - - return canvas.getContext('2d').drawImage( img, 0, 0, - iw * vertSquashRatio, ih * vertSquashRatio, - x, y, w, h ); - }; - } - - /** - * Detect subsampling in loaded image. - * In iOS, larger images than 2M pixels may be - * subsampled in rendering. - */ - function detectSubsampling( img ) { - var iw = img.naturalWidth, - ih = img.naturalHeight, - canvas, ctx; - - // subsampling may happen overmegapixel image - if ( iw * ih > 1024 * 1024 ) { - canvas = document.createElement('canvas'); - canvas.width = canvas.height = 1; - ctx = canvas.getContext('2d'); - ctx.drawImage( img, -iw + 1, 0 ); - - // subsampled image becomes half smaller in rendering size. - // check alpha channel value to confirm image is covering - // edge pixel or not. if alpha value is 0 - // image is not covering, hence subsampled. - return ctx.getImageData( 0, 0, 1, 1 ).data[ 3 ] === 0; - } else { - return false; - } - } - - - return function( canvas, img, x, y, width, height ) { - var iw = img.naturalWidth, - ih = img.naturalHeight, - ctx = canvas.getContext('2d'), - subsampled = detectSubsampling( img ), - doSquash = this.type === 'image/jpeg', - d = 1024, - sy = 0, - dy = 0, - tmpCanvas, tmpCtx, vertSquashRatio, dw, dh, sx, dx; - - if ( subsampled ) { - iw /= 2; - ih /= 2; - } - - ctx.save(); - tmpCanvas = document.createElement('canvas'); - tmpCanvas.width = tmpCanvas.height = d; - - tmpCtx = tmpCanvas.getContext('2d'); - vertSquashRatio = doSquash ? - detectVerticalSquash( img, iw, ih ) : 1; - - dw = Math.ceil( d * width / iw ); - dh = Math.ceil( d * height / ih / vertSquashRatio ); - - while ( sy < ih ) { - sx = 0; - dx = 0; - while ( sx < iw ) { - tmpCtx.clearRect( 0, 0, d, d ); - tmpCtx.drawImage( img, -sx, -sy ); - ctx.drawImage( tmpCanvas, 0, 0, d, d, - x + dx, y + dy, dw, dh ); - sx += d; - dx += dw; - } - sy += d; - dy += dh; - } - ctx.restore(); - tmpCanvas = tmpCtx = null; - }; - })() - }); - }); - /** - * @fileOverview Transport - * @todo 支持chunked传输,优势: - * 可以将大文件分成小块,挨个传输,可以提高大文件成功率,当失败的时候,也只需要重传那小部分, - * 而不需要重头再传一次。另外断点续传也需要用chunked方式。 - */ - define('runtime/html5/transport',[ - 'base', - 'runtime/html5/runtime' - ], function( Base, Html5Runtime ) { - - var noop = Base.noop, - $ = Base.$; - - return Html5Runtime.register( 'Transport', { - init: function() { - this._status = 0; - this._response = null; - }, - - send: function() { - var owner = this.owner, - opts = this.options, - xhr = this._initAjax(), - blob = owner._blob, - server = opts.server, - formData, binary, fr; - - if ( opts.sendAsBinary ) { - server += (/\?/.test( server ) ? '&' : '?') + - $.param( owner._formData ); - - binary = blob.getSource(); - } else { - formData = new FormData(); - $.each( owner._formData, function( k, v ) { - formData.append( k, v ); - }); - - formData.append( opts.fileVal, blob.getSource(), - opts.filename || owner._formData.name || '' ); - } - - if ( opts.withCredentials && 'withCredentials' in xhr ) { - xhr.open( opts.method, server, true ); - xhr.withCredentials = true; - } else { - xhr.open( opts.method, server ); - } - - this._setRequestHeader( xhr, opts.headers ); - - if ( binary ) { - // 强制设置成 content-type 为文件流。 - xhr.overrideMimeType && - xhr.overrideMimeType('application/octet-stream'); - - // android直接发送blob会导致服务端接收到的是空文件。 - // bug详情。 - // https://code.google.com/p/android/issues/detail?id=39882 - // 所以先用fileReader读取出来再通过arraybuffer的方式发送。 - if ( Base.os.android ) { - fr = new FileReader(); - - fr.onload = function() { - xhr.send( this.result ); - fr = fr.onload = null; - }; - - fr.readAsArrayBuffer( binary ); - } else { - xhr.send( binary ); - } - } else { - xhr.send( formData ); - } - }, - - getResponse: function() { - return this._response; - }, - - getResponseAsJson: function() { - return this._parseJson( this._response ); - }, - - getStatus: function() { - return this._status; - }, - - abort: function() { - var xhr = this._xhr; - - if ( xhr ) { - xhr.upload.onprogress = noop; - xhr.onreadystatechange = noop; - xhr.abort(); - - this._xhr = xhr = null; - } - }, - - destroy: function() { - this.abort(); - }, - - _initAjax: function() { - var me = this, - xhr = new XMLHttpRequest(), - opts = this.options; - - if ( opts.withCredentials && !('withCredentials' in xhr) && - typeof XDomainRequest !== 'undefined' ) { - xhr = new XDomainRequest(); - } - - xhr.upload.onprogress = function( e ) { - var percentage = 0; - - if ( e.lengthComputable ) { - percentage = e.loaded / e.total; - } - - return me.trigger( 'progress', percentage ); - }; - - xhr.onreadystatechange = function() { - - if ( xhr.readyState !== 4 ) { - return; - } - - xhr.upload.onprogress = noop; - xhr.onreadystatechange = noop; - me._xhr = null; - me._status = xhr.status; - - if ( xhr.status >= 200 && xhr.status < 300 ) { - me._response = xhr.responseText; - return me.trigger('load'); - } else if ( xhr.status >= 500 && xhr.status < 600 ) { - me._response = xhr.responseText; - return me.trigger( 'error', 'server' ); - } - - - return me.trigger( 'error', me._status ? 'http' : 'abort' ); - }; - - me._xhr = xhr; - return xhr; - }, - - _setRequestHeader: function( xhr, headers ) { - $.each( headers, function( key, val ) { - xhr.setRequestHeader( key, val ); - }); - }, - - _parseJson: function( str ) { - var json; - - try { - json = JSON.parse( str ); - } catch ( ex ) { - json = {}; - } - - return json; - } - }); - }); - /** - * @fileOverview Transport flash实现 - */ - define('runtime/html5/md5',[ - 'runtime/html5/runtime' - ], function( FlashRuntime ) { - - /* - * Fastest md5 implementation around (JKM md5) - * Credits: Joseph Myers - * - * @see http://www.myersdaily.org/joseph/javascript/md5-text.html - * @see http://jsperf.com/md5-shootout/7 - */ - - /* this function is much faster, - so if possible we use it. Some IEs - are the only ones I know of that - need the idiotic second function, - generated by an if clause. */ - var add32 = function (a, b) { - return (a + b) & 0xFFFFFFFF; - }, - - cmn = function (q, a, b, x, s, t) { - a = add32(add32(a, q), add32(x, t)); - return add32((a << s) | (a >>> (32 - s)), b); - }, - - ff = function (a, b, c, d, x, s, t) { - return cmn((b & c) | ((~b) & d), a, b, x, s, t); - }, - - gg = function (a, b, c, d, x, s, t) { - return cmn((b & d) | (c & (~d)), a, b, x, s, t); - }, - - hh = function (a, b, c, d, x, s, t) { - return cmn(b ^ c ^ d, a, b, x, s, t); - }, - - ii = function (a, b, c, d, x, s, t) { - return cmn(c ^ (b | (~d)), a, b, x, s, t); - }, - - md5cycle = function (x, k) { - var a = x[0], - b = x[1], - c = x[2], - d = x[3]; - - a = ff(a, b, c, d, k[0], 7, -680876936); - d = ff(d, a, b, c, k[1], 12, -389564586); - c = ff(c, d, a, b, k[2], 17, 606105819); - b = ff(b, c, d, a, k[3], 22, -1044525330); - a = ff(a, b, c, d, k[4], 7, -176418897); - d = ff(d, a, b, c, k[5], 12, 1200080426); - c = ff(c, d, a, b, k[6], 17, -1473231341); - b = ff(b, c, d, a, k[7], 22, -45705983); - a = ff(a, b, c, d, k[8], 7, 1770035416); - d = ff(d, a, b, c, k[9], 12, -1958414417); - c = ff(c, d, a, b, k[10], 17, -42063); - b = ff(b, c, d, a, k[11], 22, -1990404162); - a = ff(a, b, c, d, k[12], 7, 1804603682); - d = ff(d, a, b, c, k[13], 12, -40341101); - c = ff(c, d, a, b, k[14], 17, -1502002290); - b = ff(b, c, d, a, k[15], 22, 1236535329); - - a = gg(a, b, c, d, k[1], 5, -165796510); - d = gg(d, a, b, c, k[6], 9, -1069501632); - c = gg(c, d, a, b, k[11], 14, 643717713); - b = gg(b, c, d, a, k[0], 20, -373897302); - a = gg(a, b, c, d, k[5], 5, -701558691); - d = gg(d, a, b, c, k[10], 9, 38016083); - c = gg(c, d, a, b, k[15], 14, -660478335); - b = gg(b, c, d, a, k[4], 20, -405537848); - a = gg(a, b, c, d, k[9], 5, 568446438); - d = gg(d, a, b, c, k[14], 9, -1019803690); - c = gg(c, d, a, b, k[3], 14, -187363961); - b = gg(b, c, d, a, k[8], 20, 1163531501); - a = gg(a, b, c, d, k[13], 5, -1444681467); - d = gg(d, a, b, c, k[2], 9, -51403784); - c = gg(c, d, a, b, k[7], 14, 1735328473); - b = gg(b, c, d, a, k[12], 20, -1926607734); - - a = hh(a, b, c, d, k[5], 4, -378558); - d = hh(d, a, b, c, k[8], 11, -2022574463); - c = hh(c, d, a, b, k[11], 16, 1839030562); - b = hh(b, c, d, a, k[14], 23, -35309556); - a = hh(a, b, c, d, k[1], 4, -1530992060); - d = hh(d, a, b, c, k[4], 11, 1272893353); - c = hh(c, d, a, b, k[7], 16, -155497632); - b = hh(b, c, d, a, k[10], 23, -1094730640); - a = hh(a, b, c, d, k[13], 4, 681279174); - d = hh(d, a, b, c, k[0], 11, -358537222); - c = hh(c, d, a, b, k[3], 16, -722521979); - b = hh(b, c, d, a, k[6], 23, 76029189); - a = hh(a, b, c, d, k[9], 4, -640364487); - d = hh(d, a, b, c, k[12], 11, -421815835); - c = hh(c, d, a, b, k[15], 16, 530742520); - b = hh(b, c, d, a, k[2], 23, -995338651); - - a = ii(a, b, c, d, k[0], 6, -198630844); - d = ii(d, a, b, c, k[7], 10, 1126891415); - c = ii(c, d, a, b, k[14], 15, -1416354905); - b = ii(b, c, d, a, k[5], 21, -57434055); - a = ii(a, b, c, d, k[12], 6, 1700485571); - d = ii(d, a, b, c, k[3], 10, -1894986606); - c = ii(c, d, a, b, k[10], 15, -1051523); - b = ii(b, c, d, a, k[1], 21, -2054922799); - a = ii(a, b, c, d, k[8], 6, 1873313359); - d = ii(d, a, b, c, k[15], 10, -30611744); - c = ii(c, d, a, b, k[6], 15, -1560198380); - b = ii(b, c, d, a, k[13], 21, 1309151649); - a = ii(a, b, c, d, k[4], 6, -145523070); - d = ii(d, a, b, c, k[11], 10, -1120210379); - c = ii(c, d, a, b, k[2], 15, 718787259); - b = ii(b, c, d, a, k[9], 21, -343485551); - - x[0] = add32(a, x[0]); - x[1] = add32(b, x[1]); - x[2] = add32(c, x[2]); - x[3] = add32(d, x[3]); - }, - - /* there needs to be support for Unicode here, - * unless we pretend that we can redefine the MD-5 - * algorithm for multi-byte characters (perhaps - * by adding every four 16-bit characters and - * shortening the sum to 32 bits). Otherwise - * I suggest performing MD-5 as if every character - * was two bytes--e.g., 0040 0025 = @%--but then - * how will an ordinary MD-5 sum be matched? - * There is no way to standardize text to something - * like UTF-8 before transformation; speed cost is - * utterly prohibitive. The JavaScript standard - * itself needs to look at this: it should start - * providing access to strings as preformed UTF-8 - * 8-bit unsigned value arrays. - */ - md5blk = function (s) { - var md5blks = [], - i; /* Andy King said do it this way. */ - - for (i = 0; i < 64; i += 4) { - md5blks[i >> 2] = s.charCodeAt(i) + (s.charCodeAt(i + 1) << 8) + (s.charCodeAt(i + 2) << 16) + (s.charCodeAt(i + 3) << 24); - } - return md5blks; - }, - - md5blk_array = function (a) { - var md5blks = [], - i; /* Andy King said do it this way. */ - - for (i = 0; i < 64; i += 4) { - md5blks[i >> 2] = a[i] + (a[i + 1] << 8) + (a[i + 2] << 16) + (a[i + 3] << 24); - } - return md5blks; - }, - - md51 = function (s) { - var n = s.length, - state = [1732584193, -271733879, -1732584194, 271733878], - i, - length, - tail, - tmp, - lo, - hi; - - for (i = 64; i <= n; i += 64) { - md5cycle(state, md5blk(s.substring(i - 64, i))); - } - s = s.substring(i - 64); - length = s.length; - tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - for (i = 0; i < length; i += 1) { - tail[i >> 2] |= s.charCodeAt(i) << ((i % 4) << 3); - } - tail[i >> 2] |= 0x80 << ((i % 4) << 3); - if (i > 55) { - md5cycle(state, tail); - for (i = 0; i < 16; i += 1) { - tail[i] = 0; - } - } - - // Beware that the final length might not fit in 32 bits so we take care of that - tmp = n * 8; - tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/); - lo = parseInt(tmp[2], 16); - hi = parseInt(tmp[1], 16) || 0; - - tail[14] = lo; - tail[15] = hi; - - md5cycle(state, tail); - return state; - }, - - md51_array = function (a) { - var n = a.length, - state = [1732584193, -271733879, -1732584194, 271733878], - i, - length, - tail, - tmp, - lo, - hi; - - for (i = 64; i <= n; i += 64) { - md5cycle(state, md5blk_array(a.subarray(i - 64, i))); - } - - // Not sure if it is a bug, however IE10 will always produce a sub array of length 1 - // containing the last element of the parent array if the sub array specified starts - // beyond the length of the parent array - weird. - // https://connect.microsoft.com/IE/feedback/details/771452/typed-array-subarray-issue - a = (i - 64) < n ? a.subarray(i - 64) : new Uint8Array(0); - - length = a.length; - tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - for (i = 0; i < length; i += 1) { - tail[i >> 2] |= a[i] << ((i % 4) << 3); - } - - tail[i >> 2] |= 0x80 << ((i % 4) << 3); - if (i > 55) { - md5cycle(state, tail); - for (i = 0; i < 16; i += 1) { - tail[i] = 0; - } - } - - // Beware that the final length might not fit in 32 bits so we take care of that - tmp = n * 8; - tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/); - lo = parseInt(tmp[2], 16); - hi = parseInt(tmp[1], 16) || 0; - - tail[14] = lo; - tail[15] = hi; - - md5cycle(state, tail); - - return state; - }, - - hex_chr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'], - - rhex = function (n) { - var s = '', - j; - for (j = 0; j < 4; j += 1) { - s += hex_chr[(n >> (j * 8 + 4)) & 0x0F] + hex_chr[(n >> (j * 8)) & 0x0F]; - } - return s; - }, - - hex = function (x) { - var i; - for (i = 0; i < x.length; i += 1) { - x[i] = rhex(x[i]); - } - return x.join(''); - }, - - md5 = function (s) { - return hex(md51(s)); - }, - - - - //////////////////////////////////////////////////////////////////////////// - - /** - * SparkMD5 OOP implementation. - * - * Use this class to perform an incremental md5, otherwise use the - * static methods instead. - */ - SparkMD5 = function () { - // call reset to init the instance - this.reset(); - }; - - - // In some cases the fast add32 function cannot be used.. - if (md5('hello') !== '5d41402abc4b2a76b9719d911017c592') { - add32 = function (x, y) { - var lsw = (x & 0xFFFF) + (y & 0xFFFF), - msw = (x >> 16) + (y >> 16) + (lsw >> 16); - return (msw << 16) | (lsw & 0xFFFF); - }; - } - - - /** - * Appends a string. - * A conversion will be applied if an utf8 string is detected. - * - * @param {String} str The string to be appended - * - * @return {SparkMD5} The instance itself - */ - SparkMD5.prototype.append = function (str) { - // converts the string to utf8 bytes if necessary - if (/[\u0080-\uFFFF]/.test(str)) { - str = unescape(encodeURIComponent(str)); - } - - // then append as binary - this.appendBinary(str); - - return this; - }; - - /** - * Appends a binary string. - * - * @param {String} contents The binary string to be appended - * - * @return {SparkMD5} The instance itself - */ - SparkMD5.prototype.appendBinary = function (contents) { - this._buff += contents; - this._length += contents.length; - - var length = this._buff.length, - i; - - for (i = 64; i <= length; i += 64) { - md5cycle(this._state, md5blk(this._buff.substring(i - 64, i))); - } - - this._buff = this._buff.substr(i - 64); - - return this; - }; - - /** - * Finishes the incremental computation, reseting the internal state and - * returning the result. - * Use the raw parameter to obtain the raw result instead of the hex one. - * - * @param {Boolean} raw True to get the raw result, false to get the hex result - * - * @return {String|Array} The result - */ - SparkMD5.prototype.end = function (raw) { - var buff = this._buff, - length = buff.length, - i, - tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ret; - - for (i = 0; i < length; i += 1) { - tail[i >> 2] |= buff.charCodeAt(i) << ((i % 4) << 3); - } - - this._finish(tail, length); - ret = !!raw ? this._state : hex(this._state); - - this.reset(); - - return ret; - }; - - /** - * Finish the final calculation based on the tail. - * - * @param {Array} tail The tail (will be modified) - * @param {Number} length The length of the remaining buffer - */ - SparkMD5.prototype._finish = function (tail, length) { - var i = length, - tmp, - lo, - hi; - - tail[i >> 2] |= 0x80 << ((i % 4) << 3); - if (i > 55) { - md5cycle(this._state, tail); - for (i = 0; i < 16; i += 1) { - tail[i] = 0; - } - } - - // Do the final computation based on the tail and length - // Beware that the final length may not fit in 32 bits so we take care of that - tmp = this._length * 8; - tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/); - lo = parseInt(tmp[2], 16); - hi = parseInt(tmp[1], 16) || 0; - - tail[14] = lo; - tail[15] = hi; - md5cycle(this._state, tail); - }; - - /** - * Resets the internal state of the computation. - * - * @return {SparkMD5} The instance itself - */ - SparkMD5.prototype.reset = function () { - this._buff = ""; - this._length = 0; - this._state = [1732584193, -271733879, -1732584194, 271733878]; - - return this; - }; - - /** - * Releases memory used by the incremental buffer and other aditional - * resources. If you plan to use the instance again, use reset instead. - */ - SparkMD5.prototype.destroy = function () { - delete this._state; - delete this._buff; - delete this._length; - }; - - - /** - * Performs the md5 hash on a string. - * A conversion will be applied if utf8 string is detected. - * - * @param {String} str The string - * @param {Boolean} raw True to get the raw result, false to get the hex result - * - * @return {String|Array} The result - */ - SparkMD5.hash = function (str, raw) { - // converts the string to utf8 bytes if necessary - if (/[\u0080-\uFFFF]/.test(str)) { - str = unescape(encodeURIComponent(str)); - } - - var hash = md51(str); - - return !!raw ? hash : hex(hash); - }; - - /** - * Performs the md5 hash on a binary string. - * - * @param {String} content The binary string - * @param {Boolean} raw True to get the raw result, false to get the hex result - * - * @return {String|Array} The result - */ - SparkMD5.hashBinary = function (content, raw) { - var hash = md51(content); - - return !!raw ? hash : hex(hash); - }; - - /** - * SparkMD5 OOP implementation for array buffers. - * - * Use this class to perform an incremental md5 ONLY for array buffers. - */ - SparkMD5.ArrayBuffer = function () { - // call reset to init the instance - this.reset(); - }; - - //////////////////////////////////////////////////////////////////////////// - - /** - * Appends an array buffer. - * - * @param {ArrayBuffer} arr The array to be appended - * - * @return {SparkMD5.ArrayBuffer} The instance itself - */ - SparkMD5.ArrayBuffer.prototype.append = function (arr) { - // TODO: we could avoid the concatenation here but the algorithm would be more complex - // if you find yourself needing extra performance, please make a PR. - var buff = this._concatArrayBuffer(this._buff, arr), - length = buff.length, - i; - - this._length += arr.byteLength; - - for (i = 64; i <= length; i += 64) { - md5cycle(this._state, md5blk_array(buff.subarray(i - 64, i))); - } - - // Avoids IE10 weirdness (documented above) - this._buff = (i - 64) < length ? buff.subarray(i - 64) : new Uint8Array(0); - - return this; - }; - - /** - * Finishes the incremental computation, reseting the internal state and - * returning the result. - * Use the raw parameter to obtain the raw result instead of the hex one. - * - * @param {Boolean} raw True to get the raw result, false to get the hex result - * - * @return {String|Array} The result - */ - SparkMD5.ArrayBuffer.prototype.end = function (raw) { - var buff = this._buff, - length = buff.length, - tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - i, - ret; - - for (i = 0; i < length; i += 1) { - tail[i >> 2] |= buff[i] << ((i % 4) << 3); - } - - this._finish(tail, length); - ret = !!raw ? this._state : hex(this._state); - - this.reset(); - - return ret; - }; - - SparkMD5.ArrayBuffer.prototype._finish = SparkMD5.prototype._finish; - - /** - * Resets the internal state of the computation. - * - * @return {SparkMD5.ArrayBuffer} The instance itself - */ - SparkMD5.ArrayBuffer.prototype.reset = function () { - this._buff = new Uint8Array(0); - this._length = 0; - this._state = [1732584193, -271733879, -1732584194, 271733878]; - - return this; - }; - - /** - * Releases memory used by the incremental buffer and other aditional - * resources. If you plan to use the instance again, use reset instead. - */ - SparkMD5.ArrayBuffer.prototype.destroy = SparkMD5.prototype.destroy; - - /** - * Concats two array buffers, returning a new one. - * - * @param {ArrayBuffer} first The first array buffer - * @param {ArrayBuffer} second The second array buffer - * - * @return {ArrayBuffer} The new array buffer - */ - SparkMD5.ArrayBuffer.prototype._concatArrayBuffer = function (first, second) { - var firstLength = first.length, - result = new Uint8Array(firstLength + second.byteLength); - - result.set(first); - result.set(new Uint8Array(second), firstLength); - - return result; - }; - - /** - * Performs the md5 hash on an array buffer. - * - * @param {ArrayBuffer} arr The array buffer - * @param {Boolean} raw True to get the raw result, false to get the hex result - * - * @return {String|Array} The result - */ - SparkMD5.ArrayBuffer.hash = function (arr, raw) { - var hash = md51_array(new Uint8Array(arr)); - - return !!raw ? hash : hex(hash); - }; - - return FlashRuntime.register( 'Md5', { - init: function() { - // do nothing. - }, - - loadFromBlob: function( file ) { - var blob = file.getSource(), - chunkSize = 2 * 1024 * 1024, - chunks = Math.ceil( blob.size / chunkSize ), - chunk = 0, - owner = this.owner, - spark = new SparkMD5.ArrayBuffer(), - me = this, - blobSlice = blob.mozSlice || blob.webkitSlice || blob.slice, - loadNext, fr; - - fr = new FileReader(); - - loadNext = function() { - var start, end; - - start = chunk * chunkSize; - end = Math.min( start + chunkSize, blob.size ); - - fr.onload = function( e ) { - spark.append( e.target.result ); - owner.trigger( 'progress', { - total: file.size, - loaded: end - }); - }; - - fr.onloadend = function() { - fr.onloadend = fr.onload = null; - - if ( ++chunk < chunks ) { - setTimeout( loadNext, 1 ); - } else { - setTimeout(function(){ - owner.trigger('load'); - me.result = spark.end(); - loadNext = file = blob = spark = null; - owner.trigger('complete'); - }, 50 ); - } - }; - - fr.readAsArrayBuffer( blobSlice.call( blob, start, end ) ); - }; - - loadNext(); - }, - - getResult: function() { - return this.result; - } - }); - }); - /** - * @fileOverview FlashRuntime - */ - define('runtime/flash/runtime',[ - 'base', - 'runtime/runtime', - 'runtime/compbase' - ], function( Base, Runtime, CompBase ) { - - var $ = Base.$, - type = 'flash', - components = {}; - - - function getFlashVersion() { - var version; - - try { - version = navigator.plugins[ 'Shockwave Flash' ]; - version = version.description; - } catch ( ex ) { - try { - version = new ActiveXObject('ShockwaveFlash.ShockwaveFlash') - .GetVariable('$version'); - } catch ( ex2 ) { - version = '0.0'; - } - } - version = version.match( /\d+/g ); - return parseFloat( version[ 0 ] + '.' + version[ 1 ], 10 ); - } - - function FlashRuntime() { - var pool = {}, - clients = {}, - destroy = this.destroy, - me = this, - jsreciver = Base.guid('webuploader_'); - - Runtime.apply( me, arguments ); - me.type = type; - - - // 这个方法的调用者,实际上是RuntimeClient - me.exec = function( comp, fn/*, args...*/ ) { - var client = this, - uid = client.uid, - args = Base.slice( arguments, 2 ), - instance; - - clients[ uid ] = client; - - if ( components[ comp ] ) { - if ( !pool[ uid ] ) { - pool[ uid ] = new components[ comp ]( client, me ); - } - - instance = pool[ uid ]; - - if ( instance[ fn ] ) { - return instance[ fn ].apply( instance, args ); - } - } - - return me.flashExec.apply( client, arguments ); - }; - - function handler( evt, obj ) { - var type = evt.type || evt, - parts, uid; - - parts = type.split('::'); - uid = parts[ 0 ]; - type = parts[ 1 ]; - - // console.log.apply( console, arguments ); - - if ( type === 'Ready' && uid === me.uid ) { - me.trigger('ready'); - } else if ( clients[ uid ] ) { - clients[ uid ].trigger( type.toLowerCase(), evt, obj ); - } - - // Base.log( evt, obj ); - } - - // flash的接受器。 - window[ jsreciver ] = function() { - var args = arguments; - - // 为了能捕获得到。 - setTimeout(function() { - handler.apply( null, args ); - }, 1 ); - }; - - this.jsreciver = jsreciver; - - this.destroy = function() { - // @todo 删除池子中的所有实例 - return destroy && destroy.apply( this, arguments ); - }; - - this.flashExec = function( comp, fn ) { - var flash = me.getFlash(), - args = Base.slice( arguments, 2 ); - - return flash.exec( this.uid, comp, fn, args ); - }; - - // @todo - } - - Base.inherits( Runtime, { - constructor: FlashRuntime, - - init: function() { - var container = this.getContainer(), - opts = this.options, - html; - - // if not the minimal height, shims are not initialized - // in older browsers (e.g FF3.6, IE6,7,8, Safari 4.0,5.0, etc) - container.css({ - position: 'absolute', - top: '-8px', - left: '-8px', - width: '9px', - height: '9px', - overflow: 'hidden' - }); - - // insert flash object - html = '' + - '' + - '' + - '' + - ''; - - container.html( html ); - }, - - getFlash: function() { - if ( this._flash ) { - return this._flash; - } - - this._flash = $( '#' + this.uid ).get( 0 ); - return this._flash; - } - - }); - - FlashRuntime.register = function( name, component ) { - component = components[ name ] = Base.inherits( CompBase, $.extend({ - - // @todo fix this later - flashExec: function() { - var owner = this.owner, - runtime = this.getRuntime(); - - return runtime.flashExec.apply( owner, arguments ); - } - }, component ) ); - - return component; - }; - - if ( getFlashVersion() >= 11.4 ) { - Runtime.addRuntime( type, FlashRuntime ); - } - - return FlashRuntime; - }); - /** - * @fileOverview FilePicker - */ - define('runtime/flash/filepicker',[ - 'base', - 'runtime/flash/runtime' - ], function( Base, FlashRuntime ) { - var $ = Base.$; - - return FlashRuntime.register( 'FilePicker', { - init: function( opts ) { - var copy = $.extend({}, opts ), - len, i; - - // 修复Flash再没有设置title的情况下无法弹出flash文件选择框的bug. - len = copy.accept && copy.accept.length; - for ( i = 0; i < len; i++ ) { - if ( !copy.accept[ i ].title ) { - copy.accept[ i ].title = 'Files'; - } - } - - delete copy.button; - delete copy.id; - delete copy.container; - - this.flashExec( 'FilePicker', 'init', copy ); - }, - - destroy: function() { - this.flashExec( 'FilePicker', 'destroy' ); - } - }); - }); - /** - * @fileOverview 图片压缩 - */ - define('runtime/flash/image',[ - 'runtime/flash/runtime' - ], function( FlashRuntime ) { - - return FlashRuntime.register( 'Image', { - // init: function( options ) { - // var owner = this.owner; - - // this.flashExec( 'Image', 'init', options ); - // owner.on( 'load', function() { - // debugger; - // }); - // }, - - loadFromBlob: function( blob ) { - var owner = this.owner; - - owner.info() && this.flashExec( 'Image', 'info', owner.info() ); - owner.meta() && this.flashExec( 'Image', 'meta', owner.meta() ); - - this.flashExec( 'Image', 'loadFromBlob', blob.uid ); - } - }); - }); - /** - * @fileOverview Transport flash实现 - */ - define('runtime/flash/transport',[ - 'base', - 'runtime/flash/runtime', - 'runtime/client' - ], function( Base, FlashRuntime, RuntimeClient ) { - var $ = Base.$; - - return FlashRuntime.register( 'Transport', { - init: function() { - this._status = 0; - this._response = null; - this._responseJson = null; - }, - - send: function() { - var owner = this.owner, - opts = this.options, - xhr = this._initAjax(), - blob = owner._blob, - server = opts.server, - binary; - - xhr.connectRuntime( blob.ruid ); - - if ( opts.sendAsBinary ) { - server += (/\?/.test( server ) ? '&' : '?') + - $.param( owner._formData ); - - binary = blob.uid; - } else { - $.each( owner._formData, function( k, v ) { - xhr.exec( 'append', k, v ); - }); - - xhr.exec( 'appendBlob', opts.fileVal, blob.uid, - opts.filename || owner._formData.name || '' ); - } - - this._setRequestHeader( xhr, opts.headers ); - xhr.exec( 'send', { - method: opts.method, - url: server, - forceURLStream: opts.forceURLStream, - mimeType: 'application/octet-stream' - }, binary ); - }, - - getStatus: function() { - return this._status; - }, - - getResponse: function() { - return this._response || ''; - }, - - getResponseAsJson: function() { - return this._responseJson; - }, - - abort: function() { - var xhr = this._xhr; - - if ( xhr ) { - xhr.exec('abort'); - xhr.destroy(); - this._xhr = xhr = null; - } - }, - - destroy: function() { - this.abort(); - }, - - _initAjax: function() { - var me = this, - xhr = new RuntimeClient('XMLHttpRequest'); - - xhr.on( 'uploadprogress progress', function( e ) { - var percent = e.loaded / e.total; - percent = Math.min( 1, Math.max( 0, percent ) ); - return me.trigger( 'progress', percent ); - }); - - xhr.on( 'load', function() { - var status = xhr.exec('getStatus'), - readBody = false, - err = '', - p; - - xhr.off(); - me._xhr = null; - - if ( status >= 200 && status < 300 ) { - readBody = true; - } else if ( status >= 500 && status < 600 ) { - readBody = true; - err = 'server'; - } else { - err = 'http'; - } - - if ( readBody ) { - me._response = xhr.exec('getResponse'); - me._response = decodeURIComponent( me._response ); - - // flash 处理可能存在 bug, 没辙只能靠 js 了 - // try { - // me._responseJson = xhr.exec('getResponseAsJson'); - // } catch ( error ) { - - p = window.JSON && window.JSON.parse || function( s ) { - try { - return new Function('return ' + s).call(); - } catch ( err ) { - return {}; - } - }; - me._responseJson = me._response ? p(me._response) : {}; - - // } - } - - xhr.destroy(); - xhr = null; - - return err ? me.trigger( 'error', err ) : me.trigger('load'); - }); - - xhr.on( 'error', function() { - xhr.off(); - me._xhr = null; - me.trigger( 'error', 'http' ); - }); - - me._xhr = xhr; - return xhr; - }, - - _setRequestHeader: function( xhr, headers ) { - $.each( headers, function( key, val ) { - xhr.exec( 'setRequestHeader', key, val ); - }); - } - }); - }); - /** - * @fileOverview Blob Html实现 - */ - define('runtime/flash/blob',[ - 'runtime/flash/runtime', - 'lib/blob' - ], function( FlashRuntime, Blob ) { - - return FlashRuntime.register( 'Blob', { - slice: function( start, end ) { - var blob = this.flashExec( 'Blob', 'slice', start, end ); - - return new Blob( blob.uid, blob ); - } - }); - }); - /** - * @fileOverview Md5 flash实现 - */ - define('runtime/flash/md5',[ - 'runtime/flash/runtime' - ], function( FlashRuntime ) { - - return FlashRuntime.register( 'Md5', { - init: function() { - // do nothing. - }, - - loadFromBlob: function( blob ) { - return this.flashExec( 'Md5', 'loadFromBlob', blob.uid ); - } - }); - }); - /** - * @fileOverview 完全版本。 - */ - define('preset/all',[ - 'base', - - // widgets - 'widgets/filednd', - 'widgets/filepaste', - 'widgets/filepicker', - 'widgets/image', - 'widgets/queue', - 'widgets/runtime', - 'widgets/upload', - 'widgets/validator', - 'widgets/md5', - - // runtimes - // html5 - 'runtime/html5/blob', - 'runtime/html5/dnd', - 'runtime/html5/filepaste', - 'runtime/html5/filepicker', - 'runtime/html5/imagemeta/exif', - 'runtime/html5/androidpatch', - 'runtime/html5/image', - 'runtime/html5/transport', - 'runtime/html5/md5', - - // flash - 'runtime/flash/filepicker', - 'runtime/flash/image', - 'runtime/flash/transport', - 'runtime/flash/blob', - 'runtime/flash/md5' - ], function( Base ) { - return Base; - }); - /** - * @fileOverview 日志组件,主要用来收集错误信息,可以帮助 webuploader 更好的定位问题和发展。 - * - * 如果您不想要启用此功能,请在打包的时候去掉 log 模块。 - * - * 或者可以在初始化的时候通过 options.disableWidgets 属性禁用。 - * - * 如: - * WebUploader.create({ - * ... - * - * disableWidgets: 'log', - * - * ... - * }) - */ - define('widgets/log',[ - 'base', - 'uploader', - 'widgets/widget' - ], function( Base, Uploader ) { - var $ = Base.$, - logUrl = ' http://static.tieba.baidu.com/tb/pms/img/st.gif??', - product = (location.hostname || location.host || 'protected').toLowerCase(), - - // 只针对 baidu 内部产品用户做统计功能。 - enable = product && /baidu/i.exec(product), - base; - - if (!enable) { - return; - } - - base = { - dv: 3, - master: 'webuploader', - online: /test/.exec(product) ? 0 : 1, - module: '', - product: product, - type: 0 - }; - - function send(data) { - var obj = $.extend({}, base, data), - url = logUrl.replace(/^(.*)\?/, '$1' + $.param( obj )), - image = new Image(); - - image.src = url; - } - - return Uploader.register({ - name: 'log', - - init: function() { - var owner = this.owner, - count = 0, - size = 0; - - owner - .on('error', function(code) { - send({ - type: 2, - c_error_code: code - }); - }) - .on('uploadError', function(file, reason) { - send({ - type: 2, - c_error_code: 'UPLOAD_ERROR', - c_reason: '' + reason - }); - }) - .on('uploadComplete', function(file) { - count++; - size += file.size; - }). - on('uploadFinished', function() { - send({ - c_count: count, - c_size: size - }); - count = size = 0; - }); - - send({ - c_usage: 1 - }); - } - }); - }); - /** - * @fileOverview Uploader上传类 - */ - define('webuploader',[ - 'preset/all', - 'widgets/log' - ], function( preset ) { - return preset; - }); - - var _require = require; - return _require('webuploader'); -}); diff --git a/static/plugins/webuploader/webuploader.flashonly.js b/static/plugins/webuploader/webuploader.flashonly.js deleted file mode 100644 index 956e256b..00000000 --- a/static/plugins/webuploader/webuploader.flashonly.js +++ /dev/null @@ -1,4622 +0,0 @@ -/*! WebUploader 0.1.5 */ - - -/** - * @fileOverview 让内部各个部件的代码可以用[amd](https://github.com/amdjs/amdjs-api/wiki/AMD)模块定义方式组织起来。 - * - * AMD API 内部的简单不完全实现,请忽略。只有当WebUploader被合并成一个文件的时候才会引入。 - */ -(function( root, factory ) { - var modules = {}, - - // 内部require, 简单不完全实现。 - // https://github.com/amdjs/amdjs-api/wiki/require - _require = function( deps, callback ) { - var args, len, i; - - // 如果deps不是数组,则直接返回指定module - if ( typeof deps === 'string' ) { - return getModule( deps ); - } else { - args = []; - for( len = deps.length, i = 0; i < len; i++ ) { - args.push( getModule( deps[ i ] ) ); - } - - return callback.apply( null, args ); - } - }, - - // 内部define,暂时不支持不指定id. - _define = function( id, deps, factory ) { - if ( arguments.length === 2 ) { - factory = deps; - deps = null; - } - - _require( deps || [], function() { - setModule( id, factory, arguments ); - }); - }, - - // 设置module, 兼容CommonJs写法。 - setModule = function( id, factory, args ) { - var module = { - exports: factory - }, - returned; - - if ( typeof factory === 'function' ) { - args.length || (args = [ _require, module.exports, module ]); - returned = factory.apply( null, args ); - returned !== undefined && (module.exports = returned); - } - - modules[ id ] = module.exports; - }, - - // 根据id获取module - getModule = function( id ) { - var module = modules[ id ] || root[ id ]; - - if ( !module ) { - throw new Error( '`' + id + '` is undefined' ); - } - - return module; - }, - - // 将所有modules,将路径ids装换成对象。 - exportsTo = function( obj ) { - var key, host, parts, part, last, ucFirst; - - // make the first character upper case. - ucFirst = function( str ) { - return str && (str.charAt( 0 ).toUpperCase() + str.substr( 1 )); - }; - - for ( key in modules ) { - host = obj; - - if ( !modules.hasOwnProperty( key ) ) { - continue; - } - - parts = key.split('/'); - last = ucFirst( parts.pop() ); - - while( (part = ucFirst( parts.shift() )) ) { - host[ part ] = host[ part ] || {}; - host = host[ part ]; - } - - host[ last ] = modules[ key ]; - } - - return obj; - }, - - makeExport = function( dollar ) { - root.__dollar = dollar; - - // exports every module. - return exportsTo( factory( root, _define, _require ) ); - }, - - origin; - - if ( typeof module === 'object' && typeof module.exports === 'object' ) { - - // For CommonJS and CommonJS-like environments where a proper window is present, - module.exports = makeExport(); - } else if ( typeof define === 'function' && define.amd ) { - - // Allow using this built library as an AMD module - // in another project. That other project will only - // see this AMD call, not the internal modules in - // the closure below. - define([ 'jquery' ], makeExport ); - } else { - - // Browser globals case. Just assign the - // result to a property on the global. - origin = root.WebUploader; - root.WebUploader = makeExport(); - root.WebUploader.noConflict = function() { - root.WebUploader = origin; - }; - } -})( window, function( window, define, require ) { - - - /** - * @fileOverview jQuery or Zepto - */ - define('dollar-third',[],function() { - var $ = window.__dollar || window.jQuery || window.Zepto; - - if ( !$ ) { - throw new Error('jQuery or Zepto not found!'); - } - - return $; - }); - /** - * @fileOverview Dom 操作相关 - */ - define('dollar',[ - 'dollar-third' - ], function( _ ) { - return _; - }); - /** - * @fileOverview 使用jQuery的Promise - */ - define('promise-third',[ - 'dollar' - ], function( $ ) { - return { - Deferred: $.Deferred, - when: $.when, - - isPromise: function( anything ) { - return anything && typeof anything.then === 'function'; - } - }; - }); - /** - * @fileOverview Promise/A+ - */ - define('promise',[ - 'promise-third' - ], function( _ ) { - return _; - }); - /** - * @fileOverview 基础类方法。 - */ - - /** - * Web Uploader内部类的详细说明,以下提及的功能类,都可以在`WebUploader`这个变量中访问到。 - * - * As you know, Web Uploader的每个文件都是用过[AMD](https://github.com/amdjs/amdjs-api/wiki/AMD)规范中的`define`组织起来的, 每个Module都会有个module id. - * 默认module id为该文件的路径,而此路径将会转化成名字空间存放在WebUploader中。如: - * - * * module `base`:WebUploader.Base - * * module `file`: WebUploader.File - * * module `lib/dnd`: WebUploader.Lib.Dnd - * * module `runtime/html5/dnd`: WebUploader.Runtime.Html5.Dnd - * - * - * 以下文档中对类的使用可能省略掉了`WebUploader`前缀。 - * @module WebUploader - * @title WebUploader API文档 - */ - define('base',[ - 'dollar', - 'promise' - ], function( $, promise ) { - - var noop = function() {}, - call = Function.call; - - // http://jsperf.com/uncurrythis - // 反科里化 - function uncurryThis( fn ) { - return function() { - return call.apply( fn, arguments ); - }; - } - - function bindFn( fn, context ) { - return function() { - return fn.apply( context, arguments ); - }; - } - - function createObject( proto ) { - var f; - - if ( Object.create ) { - return Object.create( proto ); - } else { - f = function() {}; - f.prototype = proto; - return new f(); - } - } - - - /** - * 基础类,提供一些简单常用的方法。 - * @class Base - */ - return { - - /** - * @property {String} version 当前版本号。 - */ - version: '0.1.5', - - /** - * @property {jQuery|Zepto} $ 引用依赖的jQuery或者Zepto对象。 - */ - $: $, - - Deferred: promise.Deferred, - - isPromise: promise.isPromise, - - when: promise.when, - - /** - * @description 简单的浏览器检查结果。 - * - * * `webkit` webkit版本号,如果浏览器为非webkit内核,此属性为`undefined`。 - * * `chrome` chrome浏览器版本号,如果浏览器为chrome,此属性为`undefined`。 - * * `ie` ie浏览器版本号,如果浏览器为非ie,此属性为`undefined`。**暂不支持ie10+** - * * `firefox` firefox浏览器版本号,如果浏览器为非firefox,此属性为`undefined`。 - * * `safari` safari浏览器版本号,如果浏览器为非safari,此属性为`undefined`。 - * * `opera` opera浏览器版本号,如果浏览器为非opera,此属性为`undefined`。 - * - * @property {Object} [browser] - */ - browser: (function( ua ) { - var ret = {}, - webkit = ua.match( /WebKit\/([\d.]+)/ ), - chrome = ua.match( /Chrome\/([\d.]+)/ ) || - ua.match( /CriOS\/([\d.]+)/ ), - - ie = ua.match( /MSIE\s([\d\.]+)/ ) || - ua.match( /(?:trident)(?:.*rv:([\w.]+))?/i ), - firefox = ua.match( /Firefox\/([\d.]+)/ ), - safari = ua.match( /Safari\/([\d.]+)/ ), - opera = ua.match( /OPR\/([\d.]+)/ ); - - webkit && (ret.webkit = parseFloat( webkit[ 1 ] )); - chrome && (ret.chrome = parseFloat( chrome[ 1 ] )); - ie && (ret.ie = parseFloat( ie[ 1 ] )); - firefox && (ret.firefox = parseFloat( firefox[ 1 ] )); - safari && (ret.safari = parseFloat( safari[ 1 ] )); - opera && (ret.opera = parseFloat( opera[ 1 ] )); - - return ret; - })( navigator.userAgent ), - - /** - * @description 操作系统检查结果。 - * - * * `android` 如果在android浏览器环境下,此值为对应的android版本号,否则为`undefined`。 - * * `ios` 如果在ios浏览器环境下,此值为对应的ios版本号,否则为`undefined`。 - * @property {Object} [os] - */ - os: (function( ua ) { - var ret = {}, - - // osx = !!ua.match( /\(Macintosh\; Intel / ), - android = ua.match( /(?:Android);?[\s\/]+([\d.]+)?/ ), - ios = ua.match( /(?:iPad|iPod|iPhone).*OS\s([\d_]+)/ ); - - // osx && (ret.osx = true); - android && (ret.android = parseFloat( android[ 1 ] )); - ios && (ret.ios = parseFloat( ios[ 1 ].replace( /_/g, '.' ) )); - - return ret; - })( navigator.userAgent ), - - /** - * 实现类与类之间的继承。 - * @method inherits - * @grammar Base.inherits( super ) => child - * @grammar Base.inherits( super, protos ) => child - * @grammar Base.inherits( super, protos, statics ) => child - * @param {Class} super 父类 - * @param {Object | Function} [protos] 子类或者对象。如果对象中包含constructor,子类将是用此属性值。 - * @param {Function} [protos.constructor] 子类构造器,不指定的话将创建个临时的直接执行父类构造器的方法。 - * @param {Object} [statics] 静态属性或方法。 - * @return {Class} 返回子类。 - * @example - * function Person() { - * console.log( 'Super' ); - * } - * Person.prototype.hello = function() { - * console.log( 'hello' ); - * }; - * - * var Manager = Base.inherits( Person, { - * world: function() { - * console.log( 'World' ); - * } - * }); - * - * // 因为没有指定构造器,父类的构造器将会执行。 - * var instance = new Manager(); // => Super - * - * // 继承子父类的方法 - * instance.hello(); // => hello - * instance.world(); // => World - * - * // 子类的__super__属性指向父类 - * console.log( Manager.__super__ === Person ); // => true - */ - inherits: function( Super, protos, staticProtos ) { - var child; - - if ( typeof protos === 'function' ) { - child = protos; - protos = null; - } else if ( protos && protos.hasOwnProperty('constructor') ) { - child = protos.constructor; - } else { - child = function() { - return Super.apply( this, arguments ); - }; - } - - // 复制静态方法 - $.extend( true, child, Super, staticProtos || {} ); - - /* jshint camelcase: false */ - - // 让子类的__super__属性指向父类。 - child.__super__ = Super.prototype; - - // 构建原型,添加原型方法或属性。 - // 暂时用Object.create实现。 - child.prototype = createObject( Super.prototype ); - protos && $.extend( true, child.prototype, protos ); - - return child; - }, - - /** - * 一个不做任何事情的方法。可以用来赋值给默认的callback. - * @method noop - */ - noop: noop, - - /** - * 返回一个新的方法,此方法将已指定的`context`来执行。 - * @grammar Base.bindFn( fn, context ) => Function - * @method bindFn - * @example - * var doSomething = function() { - * console.log( this.name ); - * }, - * obj = { - * name: 'Object Name' - * }, - * aliasFn = Base.bind( doSomething, obj ); - * - * aliasFn(); // => Object Name - * - */ - bindFn: bindFn, - - /** - * 引用Console.log如果存在的话,否则引用一个[空函数noop](#WebUploader:Base.noop)。 - * @grammar Base.log( args... ) => undefined - * @method log - */ - log: (function() { - if ( window.console ) { - return bindFn( console.log, console ); - } - return noop; - })(), - - nextTick: (function() { - - return function( cb ) { - setTimeout( cb, 1 ); - }; - - // @bug 当浏览器不在当前窗口时就停了。 - // var next = window.requestAnimationFrame || - // window.webkitRequestAnimationFrame || - // window.mozRequestAnimationFrame || - // function( cb ) { - // window.setTimeout( cb, 1000 / 60 ); - // }; - - // // fix: Uncaught TypeError: Illegal invocation - // return bindFn( next, window ); - })(), - - /** - * 被[uncurrythis](http://www.2ality.com/2011/11/uncurrying-this.html)的数组slice方法。 - * 将用来将非数组对象转化成数组对象。 - * @grammar Base.slice( target, start[, end] ) => Array - * @method slice - * @example - * function doSomthing() { - * var args = Base.slice( arguments, 1 ); - * console.log( args ); - * } - * - * doSomthing( 'ignored', 'arg2', 'arg3' ); // => Array ["arg2", "arg3"] - */ - slice: uncurryThis( [].slice ), - - /** - * 生成唯一的ID - * @method guid - * @grammar Base.guid() => String - * @grammar Base.guid( prefx ) => String - */ - guid: (function() { - var counter = 0; - - return function( prefix ) { - var guid = (+new Date()).toString( 32 ), - i = 0; - - for ( ; i < 5; i++ ) { - guid += Math.floor( Math.random() * 65535 ).toString( 32 ); - } - - return (prefix || 'wu_') + guid + (counter++).toString( 32 ); - }; - })(), - - /** - * 格式化文件大小, 输出成带单位的字符串 - * @method formatSize - * @grammar Base.formatSize( size ) => String - * @grammar Base.formatSize( size, pointLength ) => String - * @grammar Base.formatSize( size, pointLength, units ) => String - * @param {Number} size 文件大小 - * @param {Number} [pointLength=2] 精确到的小数点数。 - * @param {Array} [units=[ 'B', 'K', 'M', 'G', 'TB' ]] 单位数组。从字节,到千字节,一直往上指定。如果单位数组里面只指定了到了K(千字节),同时文件大小大于M, 此方法的输出将还是显示成多少K. - * @example - * console.log( Base.formatSize( 100 ) ); // => 100B - * console.log( Base.formatSize( 1024 ) ); // => 1.00K - * console.log( Base.formatSize( 1024, 0 ) ); // => 1K - * console.log( Base.formatSize( 1024 * 1024 ) ); // => 1.00M - * console.log( Base.formatSize( 1024 * 1024 * 1024 ) ); // => 1.00G - * console.log( Base.formatSize( 1024 * 1024 * 1024, 0, ['B', 'KB', 'MB'] ) ); // => 1024MB - */ - formatSize: function( size, pointLength, units ) { - var unit; - - units = units || [ 'B', 'K', 'M', 'G', 'TB' ]; - - while ( (unit = units.shift()) && size > 1024 ) { - size = size / 1024; - } - - return (unit === 'B' ? size : size.toFixed( pointLength || 2 )) + - unit; - } - }; - }); - /** - * 事件处理类,可以独立使用,也可以扩展给对象使用。 - * @fileOverview Mediator - */ - define('mediator',[ - 'base' - ], function( Base ) { - var $ = Base.$, - slice = [].slice, - separator = /\s+/, - protos; - - // 根据条件过滤出事件handlers. - function findHandlers( arr, name, callback, context ) { - return $.grep( arr, function( handler ) { - return handler && - (!name || handler.e === name) && - (!callback || handler.cb === callback || - handler.cb._cb === callback) && - (!context || handler.ctx === context); - }); - } - - function eachEvent( events, callback, iterator ) { - // 不支持对象,只支持多个event用空格隔开 - $.each( (events || '').split( separator ), function( _, key ) { - iterator( key, callback ); - }); - } - - function triggerHanders( events, args ) { - var stoped = false, - i = -1, - len = events.length, - handler; - - while ( ++i < len ) { - handler = events[ i ]; - - if ( handler.cb.apply( handler.ctx2, args ) === false ) { - stoped = true; - break; - } - } - - return !stoped; - } - - protos = { - - /** - * 绑定事件。 - * - * `callback`方法在执行时,arguments将会来源于trigger的时候携带的参数。如 - * ```javascript - * var obj = {}; - * - * // 使得obj有事件行为 - * Mediator.installTo( obj ); - * - * obj.on( 'testa', function( arg1, arg2 ) { - * console.log( arg1, arg2 ); // => 'arg1', 'arg2' - * }); - * - * obj.trigger( 'testa', 'arg1', 'arg2' ); - * ``` - * - * 如果`callback`中,某一个方法`return false`了,则后续的其他`callback`都不会被执行到。 - * 切会影响到`trigger`方法的返回值,为`false`。 - * - * `on`还可以用来添加一个特殊事件`all`, 这样所有的事件触发都会响应到。同时此类`callback`中的arguments有一个不同处, - * 就是第一个参数为`type`,记录当前是什么事件在触发。此类`callback`的优先级比脚低,会再正常`callback`执行完后触发。 - * ```javascript - * obj.on( 'all', function( type, arg1, arg2 ) { - * console.log( type, arg1, arg2 ); // => 'testa', 'arg1', 'arg2' - * }); - * ``` - * - * @method on - * @grammar on( name, callback[, context] ) => self - * @param {String} name 事件名,支持多个事件用空格隔开 - * @param {Function} callback 事件处理器 - * @param {Object} [context] 事件处理器的上下文。 - * @return {self} 返回自身,方便链式 - * @chainable - * @class Mediator - */ - on: function( name, callback, context ) { - var me = this, - set; - - if ( !callback ) { - return this; - } - - set = this._events || (this._events = []); - - eachEvent( name, callback, function( name, callback ) { - var handler = { e: name }; - - handler.cb = callback; - handler.ctx = context; - handler.ctx2 = context || me; - handler.id = set.length; - - set.push( handler ); - }); - - return this; - }, - - /** - * 绑定事件,且当handler执行完后,自动解除绑定。 - * @method once - * @grammar once( name, callback[, context] ) => self - * @param {String} name 事件名 - * @param {Function} callback 事件处理器 - * @param {Object} [context] 事件处理器的上下文。 - * @return {self} 返回自身,方便链式 - * @chainable - */ - once: function( name, callback, context ) { - var me = this; - - if ( !callback ) { - return me; - } - - eachEvent( name, callback, function( name, callback ) { - var once = function() { - me.off( name, once ); - return callback.apply( context || me, arguments ); - }; - - once._cb = callback; - me.on( name, once, context ); - }); - - return me; - }, - - /** - * 解除事件绑定 - * @method off - * @grammar off( [name[, callback[, context] ] ] ) => self - * @param {String} [name] 事件名 - * @param {Function} [callback] 事件处理器 - * @param {Object} [context] 事件处理器的上下文。 - * @return {self} 返回自身,方便链式 - * @chainable - */ - off: function( name, cb, ctx ) { - var events = this._events; - - if ( !events ) { - return this; - } - - if ( !name && !cb && !ctx ) { - this._events = []; - return this; - } - - eachEvent( name, cb, function( name, cb ) { - $.each( findHandlers( events, name, cb, ctx ), function() { - delete events[ this.id ]; - }); - }); - - return this; - }, - - /** - * 触发事件 - * @method trigger - * @grammar trigger( name[, args...] ) => self - * @param {String} type 事件名 - * @param {*} [...] 任意参数 - * @return {Boolean} 如果handler中return false了,则返回false, 否则返回true - */ - trigger: function( type ) { - var args, events, allEvents; - - if ( !this._events || !type ) { - return this; - } - - args = slice.call( arguments, 1 ); - events = findHandlers( this._events, type ); - allEvents = findHandlers( this._events, 'all' ); - - return triggerHanders( events, args ) && - triggerHanders( allEvents, arguments ); - } - }; - - /** - * 中介者,它本身是个单例,但可以通过[installTo](#WebUploader:Mediator:installTo)方法,使任何对象具备事件行为。 - * 主要目的是负责模块与模块之间的合作,降低耦合度。 - * - * @class Mediator - */ - return $.extend({ - - /** - * 可以通过这个接口,使任何对象具备事件功能。 - * @method installTo - * @param {Object} obj 需要具备事件行为的对象。 - * @return {Object} 返回obj. - */ - installTo: function( obj ) { - return $.extend( obj, protos ); - } - - }, protos ); - }); - /** - * @fileOverview Uploader上传类 - */ - define('uploader',[ - 'base', - 'mediator' - ], function( Base, Mediator ) { - - var $ = Base.$; - - /** - * 上传入口类。 - * @class Uploader - * @constructor - * @grammar new Uploader( opts ) => Uploader - * @example - * var uploader = WebUploader.Uploader({ - * swf: 'path_of_swf/Uploader.swf', - * - * // 开起分片上传。 - * chunked: true - * }); - */ - function Uploader( opts ) { - this.options = $.extend( true, {}, Uploader.options, opts ); - this._init( this.options ); - } - - // default Options - // widgets中有相应扩展 - Uploader.options = {}; - Mediator.installTo( Uploader.prototype ); - - // 批量添加纯命令式方法。 - $.each({ - upload: 'start-upload', - stop: 'stop-upload', - getFile: 'get-file', - getFiles: 'get-files', - addFile: 'add-file', - addFiles: 'add-file', - sort: 'sort-files', - removeFile: 'remove-file', - cancelFile: 'cancel-file', - skipFile: 'skip-file', - retry: 'retry', - isInProgress: 'is-in-progress', - makeThumb: 'make-thumb', - md5File: 'md5-file', - getDimension: 'get-dimension', - addButton: 'add-btn', - predictRuntimeType: 'predict-runtime-type', - refresh: 'refresh', - disable: 'disable', - enable: 'enable', - reset: 'reset' - }, function( fn, command ) { - Uploader.prototype[ fn ] = function() { - return this.request( command, arguments ); - }; - }); - - $.extend( Uploader.prototype, { - state: 'pending', - - _init: function( opts ) { - var me = this; - - me.request( 'init', opts, function() { - me.state = 'ready'; - me.trigger('ready'); - }); - }, - - /** - * 获取或者设置Uploader配置项。 - * @method option - * @grammar option( key ) => * - * @grammar option( key, val ) => self - * @example - * - * // 初始状态图片上传前不会压缩 - * var uploader = new WebUploader.Uploader({ - * compress: null; - * }); - * - * // 修改后图片上传前,尝试将图片压缩到1600 * 1600 - * uploader.option( 'compress', { - * width: 1600, - * height: 1600 - * }); - */ - option: function( key, val ) { - var opts = this.options; - - // setter - if ( arguments.length > 1 ) { - - if ( $.isPlainObject( val ) && - $.isPlainObject( opts[ key ] ) ) { - $.extend( opts[ key ], val ); - } else { - opts[ key ] = val; - } - - } else { // getter - return key ? opts[ key ] : opts; - } - }, - - /** - * 获取文件统计信息。返回一个包含一下信息的对象。 - * * `successNum` 上传成功的文件数 - * * `progressNum` 上传中的文件数 - * * `cancelNum` 被删除的文件数 - * * `invalidNum` 无效的文件数 - * * `uploadFailNum` 上传失败的文件数 - * * `queueNum` 还在队列中的文件数 - * * `interruptNum` 被暂停的文件数 - * @method getStats - * @grammar getStats() => Object - */ - getStats: function() { - // return this._mgr.getStats.apply( this._mgr, arguments ); - var stats = this.request('get-stats'); - - return stats ? { - successNum: stats.numOfSuccess, - progressNum: stats.numOfProgress, - - // who care? - // queueFailNum: 0, - cancelNum: stats.numOfCancel, - invalidNum: stats.numOfInvalid, - uploadFailNum: stats.numOfUploadFailed, - queueNum: stats.numOfQueue, - interruptNum: stats.numofInterrupt - } : {}; - }, - - // 需要重写此方法来来支持opts.onEvent和instance.onEvent的处理器 - trigger: function( type/*, args...*/ ) { - var args = [].slice.call( arguments, 1 ), - opts = this.options, - name = 'on' + type.substring( 0, 1 ).toUpperCase() + - type.substring( 1 ); - - if ( - // 调用通过on方法注册的handler. - Mediator.trigger.apply( this, arguments ) === false || - - // 调用opts.onEvent - $.isFunction( opts[ name ] ) && - opts[ name ].apply( this, args ) === false || - - // 调用this.onEvent - $.isFunction( this[ name ] ) && - this[ name ].apply( this, args ) === false || - - // 广播所有uploader的事件。 - Mediator.trigger.apply( Mediator, - [ this, type ].concat( args ) ) === false ) { - - return false; - } - - return true; - }, - - /** - * 销毁 webuploader 实例 - * @method destroy - * @grammar destroy() => undefined - */ - destroy: function() { - this.request( 'destroy', arguments ); - this.off(); - }, - - // widgets/widget.js将补充此方法的详细文档。 - request: Base.noop - }); - - /** - * 创建Uploader实例,等同于new Uploader( opts ); - * @method create - * @class Base - * @static - * @grammar Base.create( opts ) => Uploader - */ - Base.create = Uploader.create = function( opts ) { - return new Uploader( opts ); - }; - - // 暴露Uploader,可以通过它来扩展业务逻辑。 - Base.Uploader = Uploader; - - return Uploader; - }); - /** - * @fileOverview Runtime管理器,负责Runtime的选择, 连接 - */ - define('runtime/runtime',[ - 'base', - 'mediator' - ], function( Base, Mediator ) { - - var $ = Base.$, - factories = {}, - - // 获取对象的第一个key - getFirstKey = function( obj ) { - for ( var key in obj ) { - if ( obj.hasOwnProperty( key ) ) { - return key; - } - } - return null; - }; - - // 接口类。 - function Runtime( options ) { - this.options = $.extend({ - container: document.body - }, options ); - this.uid = Base.guid('rt_'); - } - - $.extend( Runtime.prototype, { - - getContainer: function() { - var opts = this.options, - parent, container; - - if ( this._container ) { - return this._container; - } - - parent = $( opts.container || document.body ); - container = $( document.createElement('div') ); - - container.attr( 'id', 'rt_' + this.uid ); - container.css({ - position: 'absolute', - top: '0px', - left: '0px', - width: '1px', - height: '1px', - overflow: 'hidden' - }); - - parent.append( container ); - parent.addClass('webuploader-container'); - this._container = container; - this._parent = parent; - return container; - }, - - init: Base.noop, - exec: Base.noop, - - destroy: function() { - this._container && this._container.remove(); - this._parent && this._parent.removeClass('webuploader-container'); - this.off(); - } - }); - - Runtime.orders = 'html5,flash'; - - - /** - * 添加Runtime实现。 - * @param {String} type 类型 - * @param {Runtime} factory 具体Runtime实现。 - */ - Runtime.addRuntime = function( type, factory ) { - factories[ type ] = factory; - }; - - Runtime.hasRuntime = function( type ) { - return !!(type ? factories[ type ] : getFirstKey( factories )); - }; - - Runtime.create = function( opts, orders ) { - var type, runtime; - - orders = orders || Runtime.orders; - $.each( orders.split( /\s*,\s*/g ), function() { - if ( factories[ this ] ) { - type = this; - return false; - } - }); - - type = type || getFirstKey( factories ); - - if ( !type ) { - throw new Error('Runtime Error'); - } - - runtime = new factories[ type ]( opts ); - return runtime; - }; - - Mediator.installTo( Runtime.prototype ); - return Runtime; - }); - - /** - * @fileOverview Runtime管理器,负责Runtime的选择, 连接 - */ - define('runtime/client',[ - 'base', - 'mediator', - 'runtime/runtime' - ], function( Base, Mediator, Runtime ) { - - var cache; - - cache = (function() { - var obj = {}; - - return { - add: function( runtime ) { - obj[ runtime.uid ] = runtime; - }, - - get: function( ruid, standalone ) { - var i; - - if ( ruid ) { - return obj[ ruid ]; - } - - for ( i in obj ) { - // 有些类型不能重用,比如filepicker. - if ( standalone && obj[ i ].__standalone ) { - continue; - } - - return obj[ i ]; - } - - return null; - }, - - remove: function( runtime ) { - delete obj[ runtime.uid ]; - } - }; - })(); - - function RuntimeClient( component, standalone ) { - var deferred = Base.Deferred(), - runtime; - - this.uid = Base.guid('client_'); - - // 允许runtime没有初始化之前,注册一些方法在初始化后执行。 - this.runtimeReady = function( cb ) { - return deferred.done( cb ); - }; - - this.connectRuntime = function( opts, cb ) { - - // already connected. - if ( runtime ) { - throw new Error('already connected!'); - } - - deferred.done( cb ); - - if ( typeof opts === 'string' && cache.get( opts ) ) { - runtime = cache.get( opts ); - } - - // 像filePicker只能独立存在,不能公用。 - runtime = runtime || cache.get( null, standalone ); - - // 需要创建 - if ( !runtime ) { - runtime = Runtime.create( opts, opts.runtimeOrder ); - runtime.__promise = deferred.promise(); - runtime.once( 'ready', deferred.resolve ); - runtime.init(); - cache.add( runtime ); - runtime.__client = 1; - } else { - // 来自cache - Base.$.extend( runtime.options, opts ); - runtime.__promise.then( deferred.resolve ); - runtime.__client++; - } - - standalone && (runtime.__standalone = standalone); - return runtime; - }; - - this.getRuntime = function() { - return runtime; - }; - - this.disconnectRuntime = function() { - if ( !runtime ) { - return; - } - - runtime.__client--; - - if ( runtime.__client <= 0 ) { - cache.remove( runtime ); - delete runtime.__promise; - runtime.destroy(); - } - - runtime = null; - }; - - this.exec = function() { - if ( !runtime ) { - return; - } - - var args = Base.slice( arguments ); - component && args.unshift( component ); - - return runtime.exec.apply( this, args ); - }; - - this.getRuid = function() { - return runtime && runtime.uid; - }; - - this.destroy = (function( destroy ) { - return function() { - destroy && destroy.apply( this, arguments ); - this.trigger('destroy'); - this.off(); - this.exec('destroy'); - this.disconnectRuntime(); - }; - })( this.destroy ); - } - - Mediator.installTo( RuntimeClient.prototype ); - return RuntimeClient; - }); - /** - * @fileOverview Blob - */ - define('lib/blob',[ - 'base', - 'runtime/client' - ], function( Base, RuntimeClient ) { - - function Blob( ruid, source ) { - var me = this; - - me.source = source; - me.ruid = ruid; - this.size = source.size || 0; - - // 如果没有指定 mimetype, 但是知道文件后缀。 - if ( !source.type && this.ext && - ~'jpg,jpeg,png,gif,bmp'.indexOf( this.ext ) ) { - this.type = 'image/' + (this.ext === 'jpg' ? 'jpeg' : this.ext); - } else { - this.type = source.type || 'application/octet-stream'; - } - - RuntimeClient.call( me, 'Blob' ); - this.uid = source.uid || this.uid; - - if ( ruid ) { - me.connectRuntime( ruid ); - } - } - - Base.inherits( RuntimeClient, { - constructor: Blob, - - slice: function( start, end ) { - return this.exec( 'slice', start, end ); - }, - - getSource: function() { - return this.source; - } - }); - - return Blob; - }); - /** - * 为了统一化Flash的File和HTML5的File而存在。 - * 以至于要调用Flash里面的File,也可以像调用HTML5版本的File一下。 - * @fileOverview File - */ - define('lib/file',[ - 'base', - 'lib/blob' - ], function( Base, Blob ) { - - var uid = 1, - rExt = /\.([^.]+)$/; - - function File( ruid, file ) { - var ext; - - this.name = file.name || ('untitled' + uid++); - ext = rExt.exec( file.name ) ? RegExp.$1.toLowerCase() : ''; - - // todo 支持其他类型文件的转换。 - // 如果有 mimetype, 但是文件名里面没有找出后缀规律 - if ( !ext && file.type ) { - ext = /\/(jpg|jpeg|png|gif|bmp)$/i.exec( file.type ) ? - RegExp.$1.toLowerCase() : ''; - this.name += '.' + ext; - } - - this.ext = ext; - this.lastModifiedDate = file.lastModifiedDate || - (new Date()).toLocaleString(); - - Blob.apply( this, arguments ); - } - - return Base.inherits( Blob, File ); - }); - - /** - * @fileOverview 错误信息 - */ - define('lib/filepicker',[ - 'base', - 'runtime/client', - 'lib/file' - ], function( Base, RuntimeClent, File ) { - - var $ = Base.$; - - function FilePicker( opts ) { - opts = this.options = $.extend({}, FilePicker.options, opts ); - - opts.container = $( opts.id ); - - if ( !opts.container.length ) { - throw new Error('按钮指定错误'); - } - - opts.innerHTML = opts.innerHTML || opts.label || - opts.container.html() || ''; - - opts.button = $( opts.button || document.createElement('div') ); - opts.button.html( opts.innerHTML ); - opts.container.html( opts.button ); - - RuntimeClent.call( this, 'FilePicker', true ); - } - - FilePicker.options = { - button: null, - container: null, - label: null, - innerHTML: null, - multiple: true, - accept: null, - name: 'file' - }; - - Base.inherits( RuntimeClent, { - constructor: FilePicker, - - init: function() { - var me = this, - opts = me.options, - button = opts.button; - - button.addClass('webuploader-pick'); - - me.on( 'all', function( type ) { - var files; - - switch ( type ) { - case 'mouseenter': - button.addClass('webuploader-pick-hover'); - break; - - case 'mouseleave': - button.removeClass('webuploader-pick-hover'); - break; - - case 'change': - files = me.exec('getFiles'); - me.trigger( 'select', $.map( files, function( file ) { - file = new File( me.getRuid(), file ); - - // 记录来源。 - file._refer = opts.container; - return file; - }), opts.container ); - break; - } - }); - - me.connectRuntime( opts, function() { - me.refresh(); - me.exec( 'init', opts ); - me.trigger('ready'); - }); - - this._resizeHandler = Base.bindFn( this.refresh, this ); - $( window ).on( 'resize', this._resizeHandler ); - }, - - refresh: function() { - var shimContainer = this.getRuntime().getContainer(), - button = this.options.button, - width = button.outerWidth ? - button.outerWidth() : button.width(), - - height = button.outerHeight ? - button.outerHeight() : button.height(), - - pos = button.offset(); - - width && height && shimContainer.css({ - bottom: 'auto', - right: 'auto', - width: width + 'px', - height: height + 'px' - }).offset( pos ); - }, - - enable: function() { - var btn = this.options.button; - - btn.removeClass('webuploader-pick-disable'); - this.refresh(); - }, - - disable: function() { - var btn = this.options.button; - - this.getRuntime().getContainer().css({ - top: '-99999px' - }); - - btn.addClass('webuploader-pick-disable'); - }, - - destroy: function() { - var btn = this.options.button; - $( window ).off( 'resize', this._resizeHandler ); - btn.removeClass('webuploader-pick-disable webuploader-pick-hover ' + - 'webuploader-pick'); - } - }); - - return FilePicker; - }); - - /** - * @fileOverview 组件基类。 - */ - define('widgets/widget',[ - 'base', - 'uploader' - ], function( Base, Uploader ) { - - var $ = Base.$, - _init = Uploader.prototype._init, - _destroy = Uploader.prototype.destroy, - IGNORE = {}, - widgetClass = []; - - function isArrayLike( obj ) { - if ( !obj ) { - return false; - } - - var length = obj.length, - type = $.type( obj ); - - if ( obj.nodeType === 1 && length ) { - return true; - } - - return type === 'array' || type !== 'function' && type !== 'string' && - (length === 0 || typeof length === 'number' && length > 0 && - (length - 1) in obj); - } - - function Widget( uploader ) { - this.owner = uploader; - this.options = uploader.options; - } - - $.extend( Widget.prototype, { - - init: Base.noop, - - // 类Backbone的事件监听声明,监听uploader实例上的事件 - // widget直接无法监听事件,事件只能通过uploader来传递 - invoke: function( apiName, args ) { - - /* - { - 'make-thumb': 'makeThumb' - } - */ - var map = this.responseMap; - - // 如果无API响应声明则忽略 - if ( !map || !(apiName in map) || !(map[ apiName ] in this) || - !$.isFunction( this[ map[ apiName ] ] ) ) { - - return IGNORE; - } - - return this[ map[ apiName ] ].apply( this, args ); - - }, - - /** - * 发送命令。当传入`callback`或者`handler`中返回`promise`时。返回一个当所有`handler`中的promise都完成后完成的新`promise`。 - * @method request - * @grammar request( command, args ) => * | Promise - * @grammar request( command, args, callback ) => Promise - * @for Uploader - */ - request: function() { - return this.owner.request.apply( this.owner, arguments ); - } - }); - - // 扩展Uploader. - $.extend( Uploader.prototype, { - - /** - * @property {String | Array} [disableWidgets=undefined] - * @namespace options - * @for Uploader - * @description 默认所有 Uploader.register 了的 widget 都会被加载,如果禁用某一部分,请通过此 option 指定黑名单。 - */ - - // 覆写_init用来初始化widgets - _init: function() { - var me = this, - widgets = me._widgets = [], - deactives = me.options.disableWidgets || ''; - - $.each( widgetClass, function( _, klass ) { - (!deactives || !~deactives.indexOf( klass._name )) && - widgets.push( new klass( me ) ); - }); - - return _init.apply( me, arguments ); - }, - - request: function( apiName, args, callback ) { - var i = 0, - widgets = this._widgets, - len = widgets && widgets.length, - rlts = [], - dfds = [], - widget, rlt, promise, key; - - args = isArrayLike( args ) ? args : [ args ]; - - for ( ; i < len; i++ ) { - widget = widgets[ i ]; - rlt = widget.invoke( apiName, args ); - - if ( rlt !== IGNORE ) { - - // Deferred对象 - if ( Base.isPromise( rlt ) ) { - dfds.push( rlt ); - } else { - rlts.push( rlt ); - } - } - } - - // 如果有callback,则用异步方式。 - if ( callback || dfds.length ) { - promise = Base.when.apply( Base, dfds ); - key = promise.pipe ? 'pipe' : 'then'; - - // 很重要不能删除。删除了会死循环。 - // 保证执行顺序。让callback总是在下一个 tick 中执行。 - return promise[ key ](function() { - var deferred = Base.Deferred(), - args = arguments; - - if ( args.length === 1 ) { - args = args[ 0 ]; - } - - setTimeout(function() { - deferred.resolve( args ); - }, 1 ); - - return deferred.promise(); - })[ callback ? key : 'done' ]( callback || Base.noop ); - } else { - return rlts[ 0 ]; - } - }, - - destroy: function() { - _destroy.apply( this, arguments ); - this._widgets = null; - } - }); - - /** - * 添加组件 - * @grammar Uploader.register(proto); - * @grammar Uploader.register(map, proto); - * @param {object} responseMap API 名称与函数实现的映射 - * @param {object} proto 组件原型,构造函数通过 constructor 属性定义 - * @method Uploader.register - * @for Uploader - * @example - * Uploader.register({ - * 'make-thumb': 'makeThumb' - * }, { - * init: function( options ) {}, - * makeThumb: function() {} - * }); - * - * Uploader.register({ - * 'make-thumb': function() { - * - * } - * }); - */ - Uploader.register = Widget.register = function( responseMap, widgetProto ) { - var map = { init: 'init', destroy: 'destroy', name: 'anonymous' }, - klass; - - if ( arguments.length === 1 ) { - widgetProto = responseMap; - - // 自动生成 map 表。 - $.each(widgetProto, function(key) { - if ( key[0] === '_' || key === 'name' ) { - key === 'name' && (map.name = widgetProto.name); - return; - } - - map[key.replace(/[A-Z]/g, '-$&').toLowerCase()] = key; - }); - - } else { - map = $.extend( map, responseMap ); - } - - widgetProto.responseMap = map; - klass = Base.inherits( Widget, widgetProto ); - klass._name = map.name; - widgetClass.push( klass ); - - return klass; - }; - - /** - * 删除插件,只有在注册时指定了名字的才能被删除。 - * @grammar Uploader.unRegister(name); - * @param {string} name 组件名字 - * @method Uploader.unRegister - * @for Uploader - * @example - * - * Uploader.register({ - * name: 'custom', - * - * 'make-thumb': function() { - * - * } - * }); - * - * Uploader.unRegister('custom'); - */ - Uploader.unRegister = Widget.unRegister = function( name ) { - if ( !name || name === 'anonymous' ) { - return; - } - - // 删除指定的插件。 - for ( var i = widgetClass.length; i--; ) { - if ( widgetClass[i]._name === name ) { - widgetClass.splice(i, 1) - } - } - }; - - return Widget; - }); - /** - * @fileOverview 文件选择相关 - */ - define('widgets/filepicker',[ - 'base', - 'uploader', - 'lib/filepicker', - 'widgets/widget' - ], function( Base, Uploader, FilePicker ) { - var $ = Base.$; - - $.extend( Uploader.options, { - - /** - * @property {Selector | Object} [pick=undefined] - * @namespace options - * @for Uploader - * @description 指定选择文件的按钮容器,不指定则不创建按钮。 - * - * * `id` {Seletor|dom} 指定选择文件的按钮容器,不指定则不创建按钮。**注意** 这里虽然写的是 id, 但是不是只支持 id, 还支持 class, 或者 dom 节点。 - * * `label` {String} 请采用 `innerHTML` 代替 - * * `innerHTML` {String} 指定按钮文字。不指定时优先从指定的容器中看是否自带文字。 - * * `multiple` {Boolean} 是否开起同时选择多个文件能力。 - */ - pick: null, - - /** - * @property {Arroy} [accept=null] - * @namespace options - * @for Uploader - * @description 指定接受哪些类型的文件。 由于目前还有ext转mimeType表,所以这里需要分开指定。 - * - * * `title` {String} 文字描述 - * * `extensions` {String} 允许的文件后缀,不带点,多个用逗号分割。 - * * `mimeTypes` {String} 多个用逗号分割。 - * - * 如: - * - * ``` - * { - * title: 'Images', - * extensions: 'gif,jpg,jpeg,bmp,png', - * mimeTypes: 'image/*' - * } - * ``` - */ - accept: null/*{ - title: 'Images', - extensions: 'gif,jpg,jpeg,bmp,png', - mimeTypes: 'image/*' - }*/ - }); - - return Uploader.register({ - name: 'picker', - - init: function( opts ) { - this.pickers = []; - return opts.pick && this.addBtn( opts.pick ); - }, - - refresh: function() { - $.each( this.pickers, function() { - this.refresh(); - }); - }, - - /** - * @method addButton - * @for Uploader - * @grammar addButton( pick ) => Promise - * @description - * 添加文件选择按钮,如果一个按钮不够,需要调用此方法来添加。参数跟[options.pick](#WebUploader:Uploader:options)一致。 - * @example - * uploader.addButton({ - * id: '#btnContainer', - * innerHTML: '选择文件' - * }); - */ - addBtn: function( pick ) { - var me = this, - opts = me.options, - accept = opts.accept, - promises = []; - - if ( !pick ) { - return; - } - - $.isPlainObject( pick ) || (pick = { - id: pick - }); - - $( pick.id ).each(function() { - var options, picker, deferred; - - deferred = Base.Deferred(); - - options = $.extend({}, pick, { - accept: $.isPlainObject( accept ) ? [ accept ] : accept, - swf: opts.swf, - runtimeOrder: opts.runtimeOrder, - id: this - }); - - picker = new FilePicker( options ); - - picker.once( 'ready', deferred.resolve ); - picker.on( 'select', function( files ) { - me.owner.request( 'add-file', [ files ]); - }); - picker.init(); - - me.pickers.push( picker ); - - promises.push( deferred.promise() ); - }); - - return Base.when.apply( Base, promises ); - }, - - disable: function() { - $.each( this.pickers, function() { - this.disable(); - }); - }, - - enable: function() { - $.each( this.pickers, function() { - this.enable(); - }); - }, - - destroy: function() { - $.each( this.pickers, function() { - this.destroy(); - }); - this.pickers = null; - } - }); - }); - /** - * @fileOverview Image - */ - define('lib/image',[ - 'base', - 'runtime/client', - 'lib/blob' - ], function( Base, RuntimeClient, Blob ) { - var $ = Base.$; - - // 构造器。 - function Image( opts ) { - this.options = $.extend({}, Image.options, opts ); - RuntimeClient.call( this, 'Image' ); - - this.on( 'load', function() { - this._info = this.exec('info'); - this._meta = this.exec('meta'); - }); - } - - // 默认选项。 - Image.options = { - - // 默认的图片处理质量 - quality: 90, - - // 是否裁剪 - crop: false, - - // 是否保留头部信息 - preserveHeaders: false, - - // 是否允许放大。 - allowMagnify: false - }; - - // 继承RuntimeClient. - Base.inherits( RuntimeClient, { - constructor: Image, - - info: function( val ) { - - // setter - if ( val ) { - this._info = val; - return this; - } - - // getter - return this._info; - }, - - meta: function( val ) { - - // setter - if ( val ) { - this._meta = val; - return this; - } - - // getter - return this._meta; - }, - - loadFromBlob: function( blob ) { - var me = this, - ruid = blob.getRuid(); - - this.connectRuntime( ruid, function() { - me.exec( 'init', me.options ); - me.exec( 'loadFromBlob', blob ); - }); - }, - - resize: function() { - var args = Base.slice( arguments ); - return this.exec.apply( this, [ 'resize' ].concat( args ) ); - }, - - crop: function() { - var args = Base.slice( arguments ); - return this.exec.apply( this, [ 'crop' ].concat( args ) ); - }, - - getAsDataUrl: function( type ) { - return this.exec( 'getAsDataUrl', type ); - }, - - getAsBlob: function( type ) { - var blob = this.exec( 'getAsBlob', type ); - - return new Blob( this.getRuid(), blob ); - } - }); - - return Image; - }); - /** - * @fileOverview 图片操作, 负责预览图片和上传前压缩图片 - */ - define('widgets/image',[ - 'base', - 'uploader', - 'lib/image', - 'widgets/widget' - ], function( Base, Uploader, Image ) { - - var $ = Base.$, - throttle; - - // 根据要处理的文件大小来节流,一次不能处理太多,会卡。 - throttle = (function( max ) { - var occupied = 0, - waiting = [], - tick = function() { - var item; - - while ( waiting.length && occupied < max ) { - item = waiting.shift(); - occupied += item[ 0 ]; - item[ 1 ](); - } - }; - - return function( emiter, size, cb ) { - waiting.push([ size, cb ]); - emiter.once( 'destroy', function() { - occupied -= size; - setTimeout( tick, 1 ); - }); - setTimeout( tick, 1 ); - }; - })( 5 * 1024 * 1024 ); - - $.extend( Uploader.options, { - - /** - * @property {Object} [thumb] - * @namespace options - * @for Uploader - * @description 配置生成缩略图的选项。 - * - * 默认为: - * - * ```javascript - * { - * width: 110, - * height: 110, - * - * // 图片质量,只有type为`image/jpeg`的时候才有效。 - * quality: 70, - * - * // 是否允许放大,如果想要生成小图的时候不失真,此选项应该设置为false. - * allowMagnify: true, - * - * // 是否允许裁剪。 - * crop: true, - * - * // 为空的话则保留原有图片格式。 - * // 否则强制转换成指定的类型。 - * type: 'image/jpeg' - * } - * ``` - */ - thumb: { - width: 110, - height: 110, - quality: 70, - allowMagnify: true, - crop: true, - preserveHeaders: false, - - // 为空的话则保留原有图片格式。 - // 否则强制转换成指定的类型。 - // IE 8下面 base64 大小不能超过 32K 否则预览失败,而非 jpeg 编码的图片很可 - // 能会超过 32k, 所以这里设置成预览的时候都是 image/jpeg - type: 'image/jpeg' - }, - - /** - * @property {Object} [compress] - * @namespace options - * @for Uploader - * @description 配置压缩的图片的选项。如果此选项为`false`, 则图片在上传前不进行压缩。 - * - * 默认为: - * - * ```javascript - * { - * width: 1600, - * height: 1600, - * - * // 图片质量,只有type为`image/jpeg`的时候才有效。 - * quality: 90, - * - * // 是否允许放大,如果想要生成小图的时候不失真,此选项应该设置为false. - * allowMagnify: false, - * - * // 是否允许裁剪。 - * crop: false, - * - * // 是否保留头部meta信息。 - * preserveHeaders: true, - * - * // 如果发现压缩后文件大小比原来还大,则使用原来图片 - * // 此属性可能会影响图片自动纠正功能 - * noCompressIfLarger: false, - * - * // 单位字节,如果图片大小小于此值,不会采用压缩。 - * compressSize: 0 - * } - * ``` - */ - compress: { - width: 1600, - height: 1600, - quality: 90, - allowMagnify: false, - crop: false, - preserveHeaders: true - } - }); - - return Uploader.register({ - - name: 'image', - - - /** - * 生成缩略图,此过程为异步,所以需要传入`callback`。 - * 通常情况在图片加入队里后调用此方法来生成预览图以增强交互效果。 - * - * 当 width 或者 height 的值介于 0 - 1 时,被当成百分比使用。 - * - * `callback`中可以接收到两个参数。 - * * 第一个为error,如果生成缩略图有错误,此error将为真。 - * * 第二个为ret, 缩略图的Data URL值。 - * - * **注意** - * Date URL在IE6/7中不支持,所以不用调用此方法了,直接显示一张暂不支持预览图片好了。 - * 也可以借助服务端,将 base64 数据传给服务端,生成一个临时文件供预览。 - * - * @method makeThumb - * @grammar makeThumb( file, callback ) => undefined - * @grammar makeThumb( file, callback, width, height ) => undefined - * @for Uploader - * @example - * - * uploader.on( 'fileQueued', function( file ) { - * var $li = ...; - * - * uploader.makeThumb( file, function( error, ret ) { - * if ( error ) { - * $li.text('预览错误'); - * } else { - * $li.append(''); - * } - * }); - * - * }); - */ - makeThumb: function( file, cb, width, height ) { - var opts, image; - - file = this.request( 'get-file', file ); - - // 只预览图片格式。 - if ( !file.type.match( /^image/ ) ) { - cb( true ); - return; - } - - opts = $.extend({}, this.options.thumb ); - - // 如果传入的是object. - if ( $.isPlainObject( width ) ) { - opts = $.extend( opts, width ); - width = null; - } - - width = width || opts.width; - height = height || opts.height; - - image = new Image( opts ); - - image.once( 'load', function() { - file._info = file._info || image.info(); - file._meta = file._meta || image.meta(); - - // 如果 width 的值介于 0 - 1 - // 说明设置的是百分比。 - if ( width <= 1 && width > 0 ) { - width = file._info.width * width; - } - - // 同样的规则应用于 height - if ( height <= 1 && height > 0 ) { - height = file._info.height * height; - } - - image.resize( width, height ); - }); - - // 当 resize 完后 - image.once( 'complete', function() { - cb( false, image.getAsDataUrl( opts.type ) ); - image.destroy(); - }); - - image.once( 'error', function( reason ) { - cb( reason || true ); - image.destroy(); - }); - - throttle( image, file.source.size, function() { - file._info && image.info( file._info ); - file._meta && image.meta( file._meta ); - image.loadFromBlob( file.source ); - }); - }, - - beforeSendFile: function( file ) { - var opts = this.options.compress || this.options.resize, - compressSize = opts && opts.compressSize || 0, - noCompressIfLarger = opts && opts.noCompressIfLarger || false, - image, deferred; - - file = this.request( 'get-file', file ); - - // 只压缩 jpeg 图片格式。 - // gif 可能会丢失针 - // bmp png 基本上尺寸都不大,且压缩比比较小。 - if ( !opts || !~'image/jpeg,image/jpg'.indexOf( file.type ) || - file.size < compressSize || - file._compressed ) { - return; - } - - opts = $.extend({}, opts ); - deferred = Base.Deferred(); - - image = new Image( opts ); - - deferred.always(function() { - image.destroy(); - image = null; - }); - image.once( 'error', deferred.reject ); - image.once( 'load', function() { - var width = opts.width, - height = opts.height; - - file._info = file._info || image.info(); - file._meta = file._meta || image.meta(); - - // 如果 width 的值介于 0 - 1 - // 说明设置的是百分比。 - if ( width <= 1 && width > 0 ) { - width = file._info.width * width; - } - - // 同样的规则应用于 height - if ( height <= 1 && height > 0 ) { - height = file._info.height * height; - } - - image.resize( width, height ); - }); - - image.once( 'complete', function() { - var blob, size; - - // 移动端 UC / qq 浏览器的无图模式下 - // ctx.getImageData 处理大图的时候会报 Exception - // INDEX_SIZE_ERR: DOM Exception 1 - try { - blob = image.getAsBlob( opts.type ); - - size = file.size; - - // 如果压缩后,比原来还大则不用压缩后的。 - if ( !noCompressIfLarger || blob.size < size ) { - // file.source.destroy && file.source.destroy(); - file.source = blob; - file.size = blob.size; - - file.trigger( 'resize', blob.size, size ); - } - - // 标记,避免重复压缩。 - file._compressed = true; - deferred.resolve(); - } catch ( e ) { - // 出错了直接继续,让其上传原始图片 - deferred.resolve(); - } - }); - - file._info && image.info( file._info ); - file._meta && image.meta( file._meta ); - - image.loadFromBlob( file.source ); - return deferred.promise(); - } - }); - }); - /** - * @fileOverview 文件属性封装 - */ - define('file',[ - 'base', - 'mediator' - ], function( Base, Mediator ) { - - var $ = Base.$, - idPrefix = 'WU_FILE_', - idSuffix = 0, - rExt = /\.([^.]+)$/, - statusMap = {}; - - function gid() { - return idPrefix + idSuffix++; - } - - /** - * 文件类 - * @class File - * @constructor 构造函数 - * @grammar new File( source ) => File - * @param {Lib.File} source [lib.File](#Lib.File)实例, 此source对象是带有Runtime信息的。 - */ - function WUFile( source ) { - - /** - * 文件名,包括扩展名(后缀) - * @property name - * @type {string} - */ - this.name = source.name || 'Untitled'; - - /** - * 文件体积(字节) - * @property size - * @type {uint} - * @default 0 - */ - this.size = source.size || 0; - - /** - * 文件MIMETYPE类型,与文件类型的对应关系请参考[http://t.cn/z8ZnFny](http://t.cn/z8ZnFny) - * @property type - * @type {string} - * @default 'application/octet-stream' - */ - this.type = source.type || 'application/octet-stream'; - - /** - * 文件最后修改日期 - * @property lastModifiedDate - * @type {int} - * @default 当前时间戳 - */ - this.lastModifiedDate = source.lastModifiedDate || (new Date() * 1); - - /** - * 文件ID,每个对象具有唯一ID,与文件名无关 - * @property id - * @type {string} - */ - this.id = gid(); - - /** - * 文件扩展名,通过文件名获取,例如test.png的扩展名为png - * @property ext - * @type {string} - */ - this.ext = rExt.exec( this.name ) ? RegExp.$1 : ''; - - - /** - * 状态文字说明。在不同的status语境下有不同的用途。 - * @property statusText - * @type {string} - */ - this.statusText = ''; - - // 存储文件状态,防止通过属性直接修改 - statusMap[ this.id ] = WUFile.Status.INITED; - - this.source = source; - this.loaded = 0; - - this.on( 'error', function( msg ) { - this.setStatus( WUFile.Status.ERROR, msg ); - }); - } - - $.extend( WUFile.prototype, { - - /** - * 设置状态,状态变化时会触发`change`事件。 - * @method setStatus - * @grammar setStatus( status[, statusText] ); - * @param {File.Status|String} status [文件状态值](#WebUploader:File:File.Status) - * @param {String} [statusText=''] 状态说明,常在error时使用,用http, abort,server等来标记是由于什么原因导致文件错误。 - */ - setStatus: function( status, text ) { - - var prevStatus = statusMap[ this.id ]; - - typeof text !== 'undefined' && (this.statusText = text); - - if ( status !== prevStatus ) { - statusMap[ this.id ] = status; - /** - * 文件状态变化 - * @event statuschange - */ - this.trigger( 'statuschange', status, prevStatus ); - } - - }, - - /** - * 获取文件状态 - * @return {File.Status} - * @example - 文件状态具体包括以下几种类型: - { - // 初始化 - INITED: 0, - // 已入队列 - QUEUED: 1, - // 正在上传 - PROGRESS: 2, - // 上传出错 - ERROR: 3, - // 上传成功 - COMPLETE: 4, - // 上传取消 - CANCELLED: 5 - } - */ - getStatus: function() { - return statusMap[ this.id ]; - }, - - /** - * 获取文件原始信息。 - * @return {*} - */ - getSource: function() { - return this.source; - }, - - destroy: function() { - this.off(); - delete statusMap[ this.id ]; - } - }); - - Mediator.installTo( WUFile.prototype ); - - /** - * 文件状态值,具体包括以下几种类型: - * * `inited` 初始状态 - * * `queued` 已经进入队列, 等待上传 - * * `progress` 上传中 - * * `complete` 上传完成。 - * * `error` 上传出错,可重试 - * * `interrupt` 上传中断,可续传。 - * * `invalid` 文件不合格,不能重试上传。会自动从队列中移除。 - * * `cancelled` 文件被移除。 - * @property {Object} Status - * @namespace File - * @class File - * @static - */ - WUFile.Status = { - INITED: 'inited', // 初始状态 - QUEUED: 'queued', // 已经进入队列, 等待上传 - PROGRESS: 'progress', // 上传中 - ERROR: 'error', // 上传出错,可重试 - COMPLETE: 'complete', // 上传完成。 - CANCELLED: 'cancelled', // 上传取消。 - INTERRUPT: 'interrupt', // 上传中断,可续传。 - INVALID: 'invalid' // 文件不合格,不能重试上传。 - }; - - return WUFile; - }); - - /** - * @fileOverview 文件队列 - */ - define('queue',[ - 'base', - 'mediator', - 'file' - ], function( Base, Mediator, WUFile ) { - - var $ = Base.$, - STATUS = WUFile.Status; - - /** - * 文件队列, 用来存储各个状态中的文件。 - * @class Queue - * @extends Mediator - */ - function Queue() { - - /** - * 统计文件数。 - * * `numOfQueue` 队列中的文件数。 - * * `numOfSuccess` 上传成功的文件数 - * * `numOfCancel` 被取消的文件数 - * * `numOfProgress` 正在上传中的文件数 - * * `numOfUploadFailed` 上传错误的文件数。 - * * `numOfInvalid` 无效的文件数。 - * * `numofDeleted` 被移除的文件数。 - * @property {Object} stats - */ - this.stats = { - numOfQueue: 0, - numOfSuccess: 0, - numOfCancel: 0, - numOfProgress: 0, - numOfUploadFailed: 0, - numOfInvalid: 0, - numofDeleted: 0, - numofInterrupt: 0 - }; - - // 上传队列,仅包括等待上传的文件 - this._queue = []; - - // 存储所有文件 - this._map = {}; - } - - $.extend( Queue.prototype, { - - /** - * 将新文件加入对队列尾部 - * - * @method append - * @param {File} file 文件对象 - */ - append: function( file ) { - this._queue.push( file ); - this._fileAdded( file ); - return this; - }, - - /** - * 将新文件加入对队列头部 - * - * @method prepend - * @param {File} file 文件对象 - */ - prepend: function( file ) { - this._queue.unshift( file ); - this._fileAdded( file ); - return this; - }, - - /** - * 获取文件对象 - * - * @method getFile - * @param {String} fileId 文件ID - * @return {File} - */ - getFile: function( fileId ) { - if ( typeof fileId !== 'string' ) { - return fileId; - } - return this._map[ fileId ]; - }, - - /** - * 从队列中取出一个指定状态的文件。 - * @grammar fetch( status ) => File - * @method fetch - * @param {String} status [文件状态值](#WebUploader:File:File.Status) - * @return {File} [File](#WebUploader:File) - */ - fetch: function( status ) { - var len = this._queue.length, - i, file; - - status = status || STATUS.QUEUED; - - for ( i = 0; i < len; i++ ) { - file = this._queue[ i ]; - - if ( status === file.getStatus() ) { - return file; - } - } - - return null; - }, - - /** - * 对队列进行排序,能够控制文件上传顺序。 - * @grammar sort( fn ) => undefined - * @method sort - * @param {Function} fn 排序方法 - */ - sort: function( fn ) { - if ( typeof fn === 'function' ) { - this._queue.sort( fn ); - } - }, - - /** - * 获取指定类型的文件列表, 列表中每一个成员为[File](#WebUploader:File)对象。 - * @grammar getFiles( [status1[, status2 ...]] ) => Array - * @method getFiles - * @param {String} [status] [文件状态值](#WebUploader:File:File.Status) - */ - getFiles: function() { - var sts = [].slice.call( arguments, 0 ), - ret = [], - i = 0, - len = this._queue.length, - file; - - for ( ; i < len; i++ ) { - file = this._queue[ i ]; - - if ( sts.length && !~$.inArray( file.getStatus(), sts ) ) { - continue; - } - - ret.push( file ); - } - - return ret; - }, - - /** - * 在队列中删除文件。 - * @grammar removeFile( file ) => Array - * @method removeFile - * @param {File} 文件对象。 - */ - removeFile: function( file ) { - var me = this, - existing = this._map[ file.id ]; - - if ( existing ) { - delete this._map[ file.id ]; - file.destroy(); - this.stats.numofDeleted++; - } - }, - - _fileAdded: function( file ) { - var me = this, - existing = this._map[ file.id ]; - - if ( !existing ) { - this._map[ file.id ] = file; - - file.on( 'statuschange', function( cur, pre ) { - me._onFileStatusChange( cur, pre ); - }); - } - }, - - _onFileStatusChange: function( curStatus, preStatus ) { - var stats = this.stats; - - switch ( preStatus ) { - case STATUS.PROGRESS: - stats.numOfProgress--; - break; - - case STATUS.QUEUED: - stats.numOfQueue --; - break; - - case STATUS.ERROR: - stats.numOfUploadFailed--; - break; - - case STATUS.INVALID: - stats.numOfInvalid--; - break; - - case STATUS.INTERRUPT: - stats.numofInterrupt--; - break; - } - - switch ( curStatus ) { - case STATUS.QUEUED: - stats.numOfQueue++; - break; - - case STATUS.PROGRESS: - stats.numOfProgress++; - break; - - case STATUS.ERROR: - stats.numOfUploadFailed++; - break; - - case STATUS.COMPLETE: - stats.numOfSuccess++; - break; - - case STATUS.CANCELLED: - stats.numOfCancel++; - break; - - - case STATUS.INVALID: - stats.numOfInvalid++; - break; - - case STATUS.INTERRUPT: - stats.numofInterrupt++; - break; - } - } - - }); - - Mediator.installTo( Queue.prototype ); - - return Queue; - }); - /** - * @fileOverview 队列 - */ - define('widgets/queue',[ - 'base', - 'uploader', - 'queue', - 'file', - 'lib/file', - 'runtime/client', - 'widgets/widget' - ], function( Base, Uploader, Queue, WUFile, File, RuntimeClient ) { - - var $ = Base.$, - rExt = /\.\w+$/, - Status = WUFile.Status; - - return Uploader.register({ - name: 'queue', - - init: function( opts ) { - var me = this, - deferred, len, i, item, arr, accept, runtime; - - if ( $.isPlainObject( opts.accept ) ) { - opts.accept = [ opts.accept ]; - } - - // accept中的中生成匹配正则。 - if ( opts.accept ) { - arr = []; - - for ( i = 0, len = opts.accept.length; i < len; i++ ) { - item = opts.accept[ i ].extensions; - item && arr.push( item ); - } - - if ( arr.length ) { - accept = '\\.' + arr.join(',') - .replace( /,/g, '$|\\.' ) - .replace( /\*/g, '.*' ) + '$'; - } - - me.accept = new RegExp( accept, 'i' ); - } - - me.queue = new Queue(); - me.stats = me.queue.stats; - - // 如果当前不是html5运行时,那就算了。 - // 不执行后续操作 - if ( this.request('predict-runtime-type') !== 'html5' ) { - return; - } - - // 创建一个 html5 运行时的 placeholder - // 以至于外部添加原生 File 对象的时候能正确包裹一下供 webuploader 使用。 - deferred = Base.Deferred(); - this.placeholder = runtime = new RuntimeClient('Placeholder'); - runtime.connectRuntime({ - runtimeOrder: 'html5' - }, function() { - me._ruid = runtime.getRuid(); - deferred.resolve(); - }); - return deferred.promise(); - }, - - - // 为了支持外部直接添加一个原生File对象。 - _wrapFile: function( file ) { - if ( !(file instanceof WUFile) ) { - - if ( !(file instanceof File) ) { - if ( !this._ruid ) { - throw new Error('Can\'t add external files.'); - } - file = new File( this._ruid, file ); - } - - file = new WUFile( file ); - } - - return file; - }, - - // 判断文件是否可以被加入队列 - acceptFile: function( file ) { - var invalid = !file || !file.size || this.accept && - - // 如果名字中有后缀,才做后缀白名单处理。 - rExt.exec( file.name ) && !this.accept.test( file.name ); - - return !invalid; - }, - - - /** - * @event beforeFileQueued - * @param {File} file File对象 - * @description 当文件被加入队列之前触发,此事件的handler返回值为`false`,则此文件不会被添加进入队列。 - * @for Uploader - */ - - /** - * @event fileQueued - * @param {File} file File对象 - * @description 当文件被加入队列以后触发。 - * @for Uploader - */ - - _addFile: function( file ) { - var me = this; - - file = me._wrapFile( file ); - - // 不过类型判断允许不允许,先派送 `beforeFileQueued` - if ( !me.owner.trigger( 'beforeFileQueued', file ) ) { - return; - } - - // 类型不匹配,则派送错误事件,并返回。 - if ( !me.acceptFile( file ) ) { - me.owner.trigger( 'error', 'Q_TYPE_DENIED', file ); - return; - } - - me.queue.append( file ); - me.owner.trigger( 'fileQueued', file ); - return file; - }, - - getFile: function( fileId ) { - return this.queue.getFile( fileId ); - }, - - /** - * @event filesQueued - * @param {File} files 数组,内容为原始File(lib/File)对象。 - * @description 当一批文件添加进队列以后触发。 - * @for Uploader - */ - - /** - * @property {Boolean} [auto=false] - * @namespace options - * @for Uploader - * @description 设置为 true 后,不需要手动调用上传,有文件选择即开始上传。 - * - */ - - /** - * @method addFiles - * @grammar addFiles( file ) => undefined - * @grammar addFiles( [file1, file2 ...] ) => undefined - * @param {Array of File or File} [files] Files 对象 数组 - * @description 添加文件到队列 - * @for Uploader - */ - addFile: function( files ) { - var me = this; - - if ( !files.length ) { - files = [ files ]; - } - - files = $.map( files, function( file ) { - return me._addFile( file ); - }); - - me.owner.trigger( 'filesQueued', files ); - - if ( me.options.auto ) { - setTimeout(function() { - me.request('start-upload'); - }, 20 ); - } - }, - - getStats: function() { - return this.stats; - }, - - /** - * @event fileDequeued - * @param {File} file File对象 - * @description 当文件被移除队列后触发。 - * @for Uploader - */ - - /** - * @method removeFile - * @grammar removeFile( file ) => undefined - * @grammar removeFile( id ) => undefined - * @grammar removeFile( file, true ) => undefined - * @grammar removeFile( id, true ) => undefined - * @param {File|id} file File对象或这File对象的id - * @description 移除某一文件, 默认只会标记文件状态为已取消,如果第二个参数为 `true` 则会从 queue 中移除。 - * @for Uploader - * @example - * - * $li.on('click', '.remove-this', function() { - * uploader.removeFile( file ); - * }) - */ - removeFile: function( file, remove ) { - var me = this; - - file = file.id ? file : me.queue.getFile( file ); - - this.request( 'cancel-file', file ); - - if ( remove ) { - this.queue.removeFile( file ); - } - }, - - /** - * @method getFiles - * @grammar getFiles() => Array - * @grammar getFiles( status1, status2, status... ) => Array - * @description 返回指定状态的文件集合,不传参数将返回所有状态的文件。 - * @for Uploader - * @example - * console.log( uploader.getFiles() ); // => all files - * console.log( uploader.getFiles('error') ) // => all error files. - */ - getFiles: function() { - return this.queue.getFiles.apply( this.queue, arguments ); - }, - - fetchFile: function() { - return this.queue.fetch.apply( this.queue, arguments ); - }, - - /** - * @method retry - * @grammar retry() => undefined - * @grammar retry( file ) => undefined - * @description 重试上传,重试指定文件,或者从出错的文件开始重新上传。 - * @for Uploader - * @example - * function retry() { - * uploader.retry(); - * } - */ - retry: function( file, noForceStart ) { - var me = this, - files, i, len; - - if ( file ) { - file = file.id ? file : me.queue.getFile( file ); - file.setStatus( Status.QUEUED ); - noForceStart || me.request('start-upload'); - return; - } - - files = me.queue.getFiles( Status.ERROR ); - i = 0; - len = files.length; - - for ( ; i < len; i++ ) { - file = files[ i ]; - file.setStatus( Status.QUEUED ); - } - - me.request('start-upload'); - }, - - /** - * @method sort - * @grammar sort( fn ) => undefined - * @description 排序队列中的文件,在上传之前调整可以控制上传顺序。 - * @for Uploader - */ - sortFiles: function() { - return this.queue.sort.apply( this.queue, arguments ); - }, - - /** - * @event reset - * @description 当 uploader 被重置的时候触发。 - * @for Uploader - */ - - /** - * @method reset - * @grammar reset() => undefined - * @description 重置uploader。目前只重置了队列。 - * @for Uploader - * @example - * uploader.reset(); - */ - reset: function() { - this.owner.trigger('reset'); - this.queue = new Queue(); - this.stats = this.queue.stats; - }, - - destroy: function() { - this.reset(); - this.placeholder && this.placeholder.destroy(); - } - }); - - }); - /** - * @fileOverview 添加获取Runtime相关信息的方法。 - */ - define('widgets/runtime',[ - 'uploader', - 'runtime/runtime', - 'widgets/widget' - ], function( Uploader, Runtime ) { - - Uploader.support = function() { - return Runtime.hasRuntime.apply( Runtime, arguments ); - }; - - /** - * @property {Object} [runtimeOrder=html5,flash] - * @namespace options - * @for Uploader - * @description 指定运行时启动顺序。默认会想尝试 html5 是否支持,如果支持则使用 html5, 否则则使用 flash. - * - * 可以将此值设置成 `flash`,来强制使用 flash 运行时。 - */ - - return Uploader.register({ - name: 'runtime', - - init: function() { - if ( !this.predictRuntimeType() ) { - throw Error('Runtime Error'); - } - }, - - /** - * 预测Uploader将采用哪个`Runtime` - * @grammar predictRuntimeType() => String - * @method predictRuntimeType - * @for Uploader - */ - predictRuntimeType: function() { - var orders = this.options.runtimeOrder || Runtime.orders, - type = this.type, - i, len; - - if ( !type ) { - orders = orders.split( /\s*,\s*/g ); - - for ( i = 0, len = orders.length; i < len; i++ ) { - if ( Runtime.hasRuntime( orders[ i ] ) ) { - this.type = type = orders[ i ]; - break; - } - } - } - - return type; - } - }); - }); - /** - * @fileOverview Transport - */ - define('lib/transport',[ - 'base', - 'runtime/client', - 'mediator' - ], function( Base, RuntimeClient, Mediator ) { - - var $ = Base.$; - - function Transport( opts ) { - var me = this; - - opts = me.options = $.extend( true, {}, Transport.options, opts || {} ); - RuntimeClient.call( this, 'Transport' ); - - this._blob = null; - this._formData = opts.formData || {}; - this._headers = opts.headers || {}; - - this.on( 'progress', this._timeout ); - this.on( 'load error', function() { - me.trigger( 'progress', 1 ); - clearTimeout( me._timer ); - }); - } - - Transport.options = { - server: '', - method: 'POST', - - // 跨域时,是否允许携带cookie, 只有html5 runtime才有效 - withCredentials: false, - fileVal: 'file', - timeout: 2 * 60 * 1000, // 2分钟 - formData: {}, - headers: {}, - sendAsBinary: false - }; - - $.extend( Transport.prototype, { - - // 添加Blob, 只能添加一次,最后一次有效。 - appendBlob: function( key, blob, filename ) { - var me = this, - opts = me.options; - - if ( me.getRuid() ) { - me.disconnectRuntime(); - } - - // 连接到blob归属的同一个runtime. - me.connectRuntime( blob.ruid, function() { - me.exec('init'); - }); - - me._blob = blob; - opts.fileVal = key || opts.fileVal; - opts.filename = filename || opts.filename; - }, - - // 添加其他字段 - append: function( key, value ) { - if ( typeof key === 'object' ) { - $.extend( this._formData, key ); - } else { - this._formData[ key ] = value; - } - }, - - setRequestHeader: function( key, value ) { - if ( typeof key === 'object' ) { - $.extend( this._headers, key ); - } else { - this._headers[ key ] = value; - } - }, - - send: function( method ) { - this.exec( 'send', method ); - this._timeout(); - }, - - abort: function() { - clearTimeout( this._timer ); - return this.exec('abort'); - }, - - destroy: function() { - this.trigger('destroy'); - this.off(); - this.exec('destroy'); - this.disconnectRuntime(); - }, - - getResponse: function() { - return this.exec('getResponse'); - }, - - getResponseAsJson: function() { - return this.exec('getResponseAsJson'); - }, - - getStatus: function() { - return this.exec('getStatus'); - }, - - _timeout: function() { - var me = this, - duration = me.options.timeout; - - if ( !duration ) { - return; - } - - clearTimeout( me._timer ); - me._timer = setTimeout(function() { - me.abort(); - me.trigger( 'error', 'timeout' ); - }, duration ); - } - - }); - - // 让Transport具备事件功能。 - Mediator.installTo( Transport.prototype ); - - return Transport; - }); - /** - * @fileOverview 负责文件上传相关。 - */ - define('widgets/upload',[ - 'base', - 'uploader', - 'file', - 'lib/transport', - 'widgets/widget' - ], function( Base, Uploader, WUFile, Transport ) { - - var $ = Base.$, - isPromise = Base.isPromise, - Status = WUFile.Status; - - // 添加默认配置项 - $.extend( Uploader.options, { - - - /** - * @property {Boolean} [prepareNextFile=false] - * @namespace options - * @for Uploader - * @description 是否允许在文件传输时提前把下一个文件准备好。 - * 对于一个文件的准备工作比较耗时,比如图片压缩,md5序列化。 - * 如果能提前在当前文件传输期处理,可以节省总体耗时。 - */ - prepareNextFile: false, - - /** - * @property {Boolean} [chunked=false] - * @namespace options - * @for Uploader - * @description 是否要分片处理大文件上传。 - */ - chunked: false, - - /** - * @property {Boolean} [chunkSize=5242880] - * @namespace options - * @for Uploader - * @description 如果要分片,分多大一片? 默认大小为5M. - */ - chunkSize: 5 * 1024 * 1024, - - /** - * @property {Boolean} [chunkRetry=2] - * @namespace options - * @for Uploader - * @description 如果某个分片由于网络问题出错,允许自动重传多少次? - */ - chunkRetry: 2, - - /** - * @property {Boolean} [threads=3] - * @namespace options - * @for Uploader - * @description 上传并发数。允许同时最大上传进程数。 - */ - threads: 3, - - - /** - * @property {Object} [formData={}] - * @namespace options - * @for Uploader - * @description 文件上传请求的参数表,每次发送都会发送此对象中的参数。 - */ - formData: {} - - /** - * @property {Object} [fileVal='file'] - * @namespace options - * @for Uploader - * @description 设置文件上传域的name。 - */ - - /** - * @property {Object} [method='POST'] - * @namespace options - * @for Uploader - * @description 文件上传方式,`POST`或者`GET`。 - */ - - /** - * @property {Object} [sendAsBinary=false] - * @namespace options - * @for Uploader - * @description 是否已二进制的流的方式发送文件,这样整个上传内容`php://input`都为文件内容, - * 其他参数在$_GET数组中。 - */ - }); - - // 负责将文件切片。 - function CuteFile( file, chunkSize ) { - var pending = [], - blob = file.source, - total = blob.size, - chunks = chunkSize ? Math.ceil( total / chunkSize ) : 1, - start = 0, - index = 0, - len, api; - - api = { - file: file, - - has: function() { - return !!pending.length; - }, - - shift: function() { - return pending.shift(); - }, - - unshift: function( block ) { - pending.unshift( block ); - } - }; - - while ( index < chunks ) { - len = Math.min( chunkSize, total - start ); - - pending.push({ - file: file, - start: start, - end: chunkSize ? (start + len) : total, - total: total, - chunks: chunks, - chunk: index++, - cuted: api - }); - start += len; - } - - file.blocks = pending.concat(); - file.remaning = pending.length; - - return api; - } - - Uploader.register({ - name: 'upload', - - init: function() { - var owner = this.owner, - me = this; - - this.runing = false; - this.progress = false; - - owner - .on( 'startUpload', function() { - me.progress = true; - }) - .on( 'uploadFinished', function() { - me.progress = false; - }); - - // 记录当前正在传的数据,跟threads相关 - this.pool = []; - - // 缓存分好片的文件。 - this.stack = []; - - // 缓存即将上传的文件。 - this.pending = []; - - // 跟踪还有多少分片在上传中但是没有完成上传。 - this.remaning = 0; - this.__tick = Base.bindFn( this._tick, this ); - - owner.on( 'uploadComplete', function( file ) { - - // 把其他块取消了。 - file.blocks && $.each( file.blocks, function( _, v ) { - v.transport && (v.transport.abort(), v.transport.destroy()); - delete v.transport; - }); - - delete file.blocks; - delete file.remaning; - }); - }, - - reset: function() { - this.request( 'stop-upload', true ); - this.runing = false; - this.pool = []; - this.stack = []; - this.pending = []; - this.remaning = 0; - this._trigged = false; - this._promise = null; - }, - - /** - * @event startUpload - * @description 当开始上传流程时触发。 - * @for Uploader - */ - - /** - * 开始上传。此方法可以从初始状态调用开始上传流程,也可以从暂停状态调用,继续上传流程。 - * - * 可以指定开始某一个文件。 - * @grammar upload() => undefined - * @grammar upload( file | fileId) => undefined - * @method upload - * @for Uploader - */ - startUpload: function(file) { - var me = this; - - // 移出invalid的文件 - $.each( me.request( 'get-files', Status.INVALID ), function() { - me.request( 'remove-file', this ); - }); - - // 如果指定了开始某个文件,则只开始指定文件。 - if ( file ) { - file = file.id ? file : me.request( 'get-file', file ); - - if (file.getStatus() === Status.INTERRUPT) { - $.each( me.pool, function( _, v ) { - - // 之前暂停过。 - if (v.file !== file) { - return; - } - - v.transport && v.transport.send(); - }); - - file.setStatus( Status.QUEUED ); - } else if (file.getStatus() === Status.PROGRESS) { - return; - } else { - file.setStatus( Status.QUEUED ); - } - } else { - $.each( me.request( 'get-files', [ Status.INITED ] ), function() { - this.setStatus( Status.QUEUED ); - }); - } - - if ( me.runing ) { - return; - } - - me.runing = true; - - var files = []; - - // 如果有暂停的,则续传 - $.each( me.pool, function( _, v ) { - var file = v.file; - - if ( file.getStatus() === Status.INTERRUPT ) { - files.push(file); - me._trigged = false; - v.transport && v.transport.send(); - } - }); - - var file; - while ( (file = files.shift()) ) { - file.setStatus( Status.PROGRESS ); - } - - file || $.each( me.request( 'get-files', - Status.INTERRUPT ), function() { - this.setStatus( Status.PROGRESS ); - }); - - me._trigged = false; - Base.nextTick( me.__tick ); - me.owner.trigger('startUpload'); - }, - - /** - * @event stopUpload - * @description 当开始上传流程暂停时触发。 - * @for Uploader - */ - - /** - * 暂停上传。第一个参数为是否中断上传当前正在上传的文件。 - * - * 如果第一个参数是文件,则只暂停指定文件。 - * @grammar stop() => undefined - * @grammar stop( true ) => undefined - * @grammar stop( file ) => undefined - * @method stop - * @for Uploader - */ - stopUpload: function( file, interrupt ) { - var me = this; - - if (file === true) { - interrupt = file; - file = null; - } - - if ( me.runing === false ) { - return; - } - - // 如果只是暂停某个文件。 - if ( file ) { - file = file.id ? file : me.request( 'get-file', file ); - - if ( file.getStatus() !== Status.PROGRESS && - file.getStatus() !== Status.QUEUED ) { - return; - } - - file.setStatus( Status.INTERRUPT ); - $.each( me.pool, function( _, v ) { - - // 只 abort 指定的文件。 - if (v.file !== file) { - return; - } - - v.transport && v.transport.abort(); - me._putback(v); - me._popBlock(v); - }); - - return Base.nextTick( me.__tick ); - } - - me.runing = false; - - if (this._promise && this._promise.file) { - this._promise.file.setStatus( Status.INTERRUPT ); - } - - interrupt && $.each( me.pool, function( _, v ) { - v.transport && v.transport.abort(); - v.file.setStatus( Status.INTERRUPT ); - }); - - me.owner.trigger('stopUpload'); - }, - - /** - * @method cancelFile - * @grammar cancelFile( file ) => undefined - * @grammar cancelFile( id ) => undefined - * @param {File|id} file File对象或这File对象的id - * @description 标记文件状态为已取消, 同时将中断文件传输。 - * @for Uploader - * @example - * - * $li.on('click', '.remove-this', function() { - * uploader.cancelFile( file ); - * }) - */ - cancelFile: function( file ) { - file = file.id ? file : this.request( 'get-file', file ); - - // 如果正在上传。 - file.blocks && $.each( file.blocks, function( _, v ) { - var _tr = v.transport; - - if ( _tr ) { - _tr.abort(); - _tr.destroy(); - delete v.transport; - } - }); - - file.setStatus( Status.CANCELLED ); - this.owner.trigger( 'fileDequeued', file ); - }, - - /** - * 判断`Uplaode`r是否正在上传中。 - * @grammar isInProgress() => Boolean - * @method isInProgress - * @for Uploader - */ - isInProgress: function() { - return !!this.progress; - }, - - _getStats: function() { - return this.request('get-stats'); - }, - - /** - * 掉过一个文件上传,直接标记指定文件为已上传状态。 - * @grammar skipFile( file ) => undefined - * @method skipFile - * @for Uploader - */ - skipFile: function( file, status ) { - file = file.id ? file : this.request( 'get-file', file ); - - file.setStatus( status || Status.COMPLETE ); - file.skipped = true; - - // 如果正在上传。 - file.blocks && $.each( file.blocks, function( _, v ) { - var _tr = v.transport; - - if ( _tr ) { - _tr.abort(); - _tr.destroy(); - delete v.transport; - } - }); - - this.owner.trigger( 'uploadSkip', file ); - }, - - /** - * @event uploadFinished - * @description 当所有文件上传结束时触发。 - * @for Uploader - */ - _tick: function() { - var me = this, - opts = me.options, - fn, val; - - // 上一个promise还没有结束,则等待完成后再执行。 - if ( me._promise ) { - return me._promise.always( me.__tick ); - } - - // 还有位置,且还有文件要处理的话。 - if ( me.pool.length < opts.threads && (val = me._nextBlock()) ) { - me._trigged = false; - - fn = function( val ) { - me._promise = null; - - // 有可能是reject过来的,所以要检测val的类型。 - val && val.file && me._startSend( val ); - Base.nextTick( me.__tick ); - }; - - me._promise = isPromise( val ) ? val.always( fn ) : fn( val ); - - // 没有要上传的了,且没有正在传输的了。 - } else if ( !me.remaning && !me._getStats().numOfQueue && - !me._getStats().numofInterrupt ) { - me.runing = false; - - me._trigged || Base.nextTick(function() { - me.owner.trigger('uploadFinished'); - }); - me._trigged = true; - } - }, - - _putback: function(block) { - var idx; - - block.cuted.unshift(block); - idx = this.stack.indexOf(block.cuted); - - if (!~idx) { - this.stack.unshift(block.cuted); - } - }, - - _getStack: function() { - var i = 0, - act; - - while ( (act = this.stack[ i++ ]) ) { - if ( act.has() && act.file.getStatus() === Status.PROGRESS ) { - return act; - } else if (!act.has() || - act.file.getStatus() !== Status.PROGRESS && - act.file.getStatus() !== Status.INTERRUPT ) { - - // 把已经处理完了的,或者,状态为非 progress(上传中)、 - // interupt(暂停中) 的移除。 - this.stack.splice( --i, 1 ); - } - } - - return null; - }, - - _nextBlock: function() { - var me = this, - opts = me.options, - act, next, done, preparing; - - // 如果当前文件还有没有需要传输的,则直接返回剩下的。 - if ( (act = this._getStack()) ) { - - // 是否提前准备下一个文件 - if ( opts.prepareNextFile && !me.pending.length ) { - me._prepareNextFile(); - } - - return act.shift(); - - // 否则,如果正在运行,则准备下一个文件,并等待完成后返回下个分片。 - } else if ( me.runing ) { - - // 如果缓存中有,则直接在缓存中取,没有则去queue中取。 - if ( !me.pending.length && me._getStats().numOfQueue ) { - me._prepareNextFile(); - } - - next = me.pending.shift(); - done = function( file ) { - if ( !file ) { - return null; - } - - act = CuteFile( file, opts.chunked ? opts.chunkSize : 0 ); - me.stack.push(act); - return act.shift(); - }; - - // 文件可能还在prepare中,也有可能已经完全准备好了。 - if ( isPromise( next) ) { - preparing = next.file; - next = next[ next.pipe ? 'pipe' : 'then' ]( done ); - next.file = preparing; - return next; - } - - return done( next ); - } - }, - - - /** - * @event uploadStart - * @param {File} file File对象 - * @description 某个文件开始上传前触发,一个文件只会触发一次。 - * @for Uploader - */ - _prepareNextFile: function() { - var me = this, - file = me.request('fetch-file'), - pending = me.pending, - promise; - - if ( file ) { - promise = me.request( 'before-send-file', file, function() { - - // 有可能文件被skip掉了。文件被skip掉后,状态坑定不是Queued. - if ( file.getStatus() === Status.PROGRESS || - file.getStatus() === Status.INTERRUPT ) { - return file; - } - - return me._finishFile( file ); - }); - - me.owner.trigger( 'uploadStart', file ); - file.setStatus( Status.PROGRESS ); - - promise.file = file; - - // 如果还在pending中,则替换成文件本身。 - promise.done(function() { - var idx = $.inArray( promise, pending ); - - ~idx && pending.splice( idx, 1, file ); - }); - - // befeore-send-file的钩子就有错误发生。 - promise.fail(function( reason ) { - file.setStatus( Status.ERROR, reason ); - me.owner.trigger( 'uploadError', file, reason ); - me.owner.trigger( 'uploadComplete', file ); - }); - - pending.push( promise ); - } - }, - - // 让出位置了,可以让其他分片开始上传 - _popBlock: function( block ) { - var idx = $.inArray( block, this.pool ); - - this.pool.splice( idx, 1 ); - block.file.remaning--; - this.remaning--; - }, - - // 开始上传,可以被掉过。如果promise被reject了,则表示跳过此分片。 - _startSend: function( block ) { - var me = this, - file = block.file, - promise; - - // 有可能在 before-send-file 的 promise 期间改变了文件状态。 - // 如:暂停,取消 - // 我们不能中断 promise, 但是可以在 promise 完后,不做上传操作。 - if ( file.getStatus() !== Status.PROGRESS ) { - - // 如果是中断,则还需要放回去。 - if (file.getStatus() === Status.INTERRUPT) { - me._putback(block); - } - - return; - } - - me.pool.push( block ); - me.remaning++; - - // 如果没有分片,则直接使用原始的。 - // 不会丢失content-type信息。 - block.blob = block.chunks === 1 ? file.source : - file.source.slice( block.start, block.end ); - - // hook, 每个分片发送之前可能要做些异步的事情。 - promise = me.request( 'before-send', block, function() { - - // 有可能文件已经上传出错了,所以不需要再传输了。 - if ( file.getStatus() === Status.PROGRESS ) { - me._doSend( block ); - } else { - me._popBlock( block ); - Base.nextTick( me.__tick ); - } - }); - - // 如果为fail了,则跳过此分片。 - promise.fail(function() { - if ( file.remaning === 1 ) { - me._finishFile( file ).always(function() { - block.percentage = 1; - me._popBlock( block ); - me.owner.trigger( 'uploadComplete', file ); - Base.nextTick( me.__tick ); - }); - } else { - block.percentage = 1; - me.updateFileProgress( file ); - me._popBlock( block ); - Base.nextTick( me.__tick ); - } - }); - }, - - - /** - * @event uploadBeforeSend - * @param {Object} object - * @param {Object} data 默认的上传参数,可以扩展此对象来控制上传参数。 - * @param {Object} headers 可以扩展此对象来控制上传头部。 - * @description 当某个文件的分块在发送前触发,主要用来询问是否要添加附带参数,大文件在开起分片上传的前提下此事件可能会触发多次。 - * @for Uploader - */ - - /** - * @event uploadAccept - * @param {Object} object - * @param {Object} ret 服务端的返回数据,json格式,如果服务端不是json格式,从ret._raw中取数据,自行解析。 - * @description 当某个文件上传到服务端响应后,会派送此事件来询问服务端响应是否有效。如果此事件handler返回值为`false`, 则此文件将派送`server`类型的`uploadError`事件。 - * @for Uploader - */ - - /** - * @event uploadProgress - * @param {File} file File对象 - * @param {Number} percentage 上传进度 - * @description 上传过程中触发,携带上传进度。 - * @for Uploader - */ - - - /** - * @event uploadError - * @param {File} file File对象 - * @param {String} reason 出错的code - * @description 当文件上传出错时触发。 - * @for Uploader - */ - - /** - * @event uploadSuccess - * @param {File} file File对象 - * @param {Object} response 服务端返回的数据 - * @description 当文件上传成功时触发。 - * @for Uploader - */ - - /** - * @event uploadComplete - * @param {File} [file] File对象 - * @description 不管成功或者失败,文件上传完成时触发。 - * @for Uploader - */ - - // 做上传操作。 - _doSend: function( block ) { - var me = this, - owner = me.owner, - opts = me.options, - file = block.file, - tr = new Transport( opts ), - data = $.extend({}, opts.formData ), - headers = $.extend({}, opts.headers ), - requestAccept, ret; - - block.transport = tr; - - tr.on( 'destroy', function() { - delete block.transport; - me._popBlock( block ); - Base.nextTick( me.__tick ); - }); - - // 广播上传进度。以文件为单位。 - tr.on( 'progress', function( percentage ) { - block.percentage = percentage; - me.updateFileProgress( file ); - }); - - // 用来询问,是否返回的结果是有错误的。 - requestAccept = function( reject ) { - var fn; - - ret = tr.getResponseAsJson() || {}; - ret._raw = tr.getResponse(); - fn = function( value ) { - reject = value; - }; - - // 服务端响应了,不代表成功了,询问是否响应正确。 - if ( !owner.trigger( 'uploadAccept', block, ret, fn ) ) { - reject = reject || 'server'; - } - - return reject; - }; - - // 尝试重试,然后广播文件上传出错。 - tr.on( 'error', function( type, flag ) { - block.retried = block.retried || 0; - - // 自动重试 - if ( block.chunks > 1 && ~'http,abort'.indexOf( type ) && - block.retried < opts.chunkRetry ) { - - block.retried++; - tr.send(); - - } else { - - // http status 500 ~ 600 - if ( !flag && type === 'server' ) { - type = requestAccept( type ); - } - - file.setStatus( Status.ERROR, type ); - owner.trigger( 'uploadError', file, type ); - owner.trigger( 'uploadComplete', file ); - } - }); - - // 上传成功 - tr.on( 'load', function() { - var reason; - - // 如果非预期,转向上传出错。 - if ( (reason = requestAccept()) ) { - tr.trigger( 'error', reason, true ); - return; - } - - // 全部上传完成。 - if ( file.remaning === 1 ) { - me._finishFile( file, ret ); - } else { - tr.destroy(); - } - }); - - // 配置默认的上传字段。 - data = $.extend( data, { - id: file.id, - name: file.name, - type: file.type, - lastModifiedDate: file.lastModifiedDate, - size: file.size - }); - - block.chunks > 1 && $.extend( data, { - chunks: block.chunks, - chunk: block.chunk - }); - - // 在发送之间可以添加字段什么的。。。 - // 如果默认的字段不够使用,可以通过监听此事件来扩展 - owner.trigger( 'uploadBeforeSend', block, data, headers ); - - // 开始发送。 - tr.appendBlob( opts.fileVal, block.blob, file.name ); - tr.append( data ); - tr.setRequestHeader( headers ); - tr.send(); - }, - - // 完成上传。 - _finishFile: function( file, ret, hds ) { - var owner = this.owner; - - return owner - .request( 'after-send-file', arguments, function() { - file.setStatus( Status.COMPLETE ); - owner.trigger( 'uploadSuccess', file, ret, hds ); - }) - .fail(function( reason ) { - - // 如果外部已经标记为invalid什么的,不再改状态。 - if ( file.getStatus() === Status.PROGRESS ) { - file.setStatus( Status.ERROR, reason ); - } - - owner.trigger( 'uploadError', file, reason ); - }) - .always(function() { - owner.trigger( 'uploadComplete', file ); - }); - }, - - updateFileProgress: function(file) { - var totalPercent = 0, - uploaded = 0; - - if (!file.blocks) { - return; - } - - $.each( file.blocks, function( _, v ) { - uploaded += (v.percentage || 0) * (v.end - v.start); - }); - - totalPercent = uploaded / file.size; - this.owner.trigger( 'uploadProgress', file, totalPercent || 0 ); - } - - }); - }); - /** - * @fileOverview 各种验证,包括文件总大小是否超出、单文件是否超出和文件是否重复。 - */ - - define('widgets/validator',[ - 'base', - 'uploader', - 'file', - 'widgets/widget' - ], function( Base, Uploader, WUFile ) { - - var $ = Base.$, - validators = {}, - api; - - /** - * @event error - * @param {String} type 错误类型。 - * @description 当validate不通过时,会以派送错误事件的形式通知调用者。通过`upload.on('error', handler)`可以捕获到此类错误,目前有以下错误会在特定的情况下派送错来。 - * - * * `Q_EXCEED_NUM_LIMIT` 在设置了`fileNumLimit`且尝试给`uploader`添加的文件数量超出这个值时派送。 - * * `Q_EXCEED_SIZE_LIMIT` 在设置了`Q_EXCEED_SIZE_LIMIT`且尝试给`uploader`添加的文件总大小超出这个值时派送。 - * * `Q_TYPE_DENIED` 当文件类型不满足时触发。。 - * @for Uploader - */ - - // 暴露给外面的api - api = { - - // 添加验证器 - addValidator: function( type, cb ) { - validators[ type ] = cb; - }, - - // 移除验证器 - removeValidator: function( type ) { - delete validators[ type ]; - } - }; - - // 在Uploader初始化的时候启动Validators的初始化 - Uploader.register({ - name: 'validator', - - init: function() { - var me = this; - Base.nextTick(function() { - $.each( validators, function() { - this.call( me.owner ); - }); - }); - } - }); - - /** - * @property {int} [fileNumLimit=undefined] - * @namespace options - * @for Uploader - * @description 验证文件总数量, 超出则不允许加入队列。 - */ - api.addValidator( 'fileNumLimit', function() { - var uploader = this, - opts = uploader.options, - count = 0, - max = parseInt( opts.fileNumLimit, 10 ), - flag = true; - - if ( !max ) { - return; - } - - uploader.on( 'beforeFileQueued', function( file ) { - - if ( count >= max && flag ) { - flag = false; - this.trigger( 'error', 'Q_EXCEED_NUM_LIMIT', max, file ); - setTimeout(function() { - flag = true; - }, 1 ); - } - - return count >= max ? false : true; - }); - - uploader.on( 'fileQueued', function() { - count++; - }); - - uploader.on( 'fileDequeued', function() { - count--; - }); - - uploader.on( 'reset', function() { - count = 0; - }); - }); - - - /** - * @property {int} [fileSizeLimit=undefined] - * @namespace options - * @for Uploader - * @description 验证文件总大小是否超出限制, 超出则不允许加入队列。 - */ - api.addValidator( 'fileSizeLimit', function() { - var uploader = this, - opts = uploader.options, - count = 0, - max = parseInt( opts.fileSizeLimit, 10 ), - flag = true; - - if ( !max ) { - return; - } - - uploader.on( 'beforeFileQueued', function( file ) { - var invalid = count + file.size > max; - - if ( invalid && flag ) { - flag = false; - this.trigger( 'error', 'Q_EXCEED_SIZE_LIMIT', max, file ); - setTimeout(function() { - flag = true; - }, 1 ); - } - - return invalid ? false : true; - }); - - uploader.on( 'fileQueued', function( file ) { - count += file.size; - }); - - uploader.on( 'fileDequeued', function( file ) { - count -= file.size; - }); - - uploader.on( 'reset', function() { - count = 0; - }); - }); - - /** - * @property {int} [fileSingleSizeLimit=undefined] - * @namespace options - * @for Uploader - * @description 验证单个文件大小是否超出限制, 超出则不允许加入队列。 - */ - api.addValidator( 'fileSingleSizeLimit', function() { - var uploader = this, - opts = uploader.options, - max = opts.fileSingleSizeLimit; - - if ( !max ) { - return; - } - - uploader.on( 'beforeFileQueued', function( file ) { - - if ( file.size > max ) { - file.setStatus( WUFile.Status.INVALID, 'exceed_size' ); - this.trigger( 'error', 'F_EXCEED_SIZE', max, file ); - return false; - } - - }); - - }); - - /** - * @property {Boolean} [duplicate=undefined] - * @namespace options - * @for Uploader - * @description 去重, 根据文件名字、文件大小和最后修改时间来生成hash Key. - */ - api.addValidator( 'duplicate', function() { - var uploader = this, - opts = uploader.options, - mapping = {}; - - if ( opts.duplicate ) { - return; - } - - function hashString( str ) { - var hash = 0, - i = 0, - len = str.length, - _char; - - for ( ; i < len; i++ ) { - _char = str.charCodeAt( i ); - hash = _char + (hash << 6) + (hash << 16) - hash; - } - - return hash; - } - - uploader.on( 'beforeFileQueued', function( file ) { - var hash = file.__hash || (file.__hash = hashString( file.name + - file.size + file.lastModifiedDate )); - - // 已经重复了 - if ( mapping[ hash ] ) { - this.trigger( 'error', 'F_DUPLICATE', file ); - return false; - } - }); - - uploader.on( 'fileQueued', function( file ) { - var hash = file.__hash; - - hash && (mapping[ hash ] = true); - }); - - uploader.on( 'fileDequeued', function( file ) { - var hash = file.__hash; - - hash && (delete mapping[ hash ]); - }); - - uploader.on( 'reset', function() { - mapping = {}; - }); - }); - - return api; - }); - - /** - * @fileOverview Runtime管理器,负责Runtime的选择, 连接 - */ - define('runtime/compbase',[],function() { - - function CompBase( owner, runtime ) { - - this.owner = owner; - this.options = owner.options; - - this.getRuntime = function() { - return runtime; - }; - - this.getRuid = function() { - return runtime.uid; - }; - - this.trigger = function() { - return owner.trigger.apply( owner, arguments ); - }; - } - - return CompBase; - }); - /** - * @fileOverview FlashRuntime - */ - define('runtime/flash/runtime',[ - 'base', - 'runtime/runtime', - 'runtime/compbase' - ], function( Base, Runtime, CompBase ) { - - var $ = Base.$, - type = 'flash', - components = {}; - - - function getFlashVersion() { - var version; - - try { - version = navigator.plugins[ 'Shockwave Flash' ]; - version = version.description; - } catch ( ex ) { - try { - version = new ActiveXObject('ShockwaveFlash.ShockwaveFlash') - .GetVariable('$version'); - } catch ( ex2 ) { - version = '0.0'; - } - } - version = version.match( /\d+/g ); - return parseFloat( version[ 0 ] + '.' + version[ 1 ], 10 ); - } - - function FlashRuntime() { - var pool = {}, - clients = {}, - destroy = this.destroy, - me = this, - jsreciver = Base.guid('webuploader_'); - - Runtime.apply( me, arguments ); - me.type = type; - - - // 这个方法的调用者,实际上是RuntimeClient - me.exec = function( comp, fn/*, args...*/ ) { - var client = this, - uid = client.uid, - args = Base.slice( arguments, 2 ), - instance; - - clients[ uid ] = client; - - if ( components[ comp ] ) { - if ( !pool[ uid ] ) { - pool[ uid ] = new components[ comp ]( client, me ); - } - - instance = pool[ uid ]; - - if ( instance[ fn ] ) { - return instance[ fn ].apply( instance, args ); - } - } - - return me.flashExec.apply( client, arguments ); - }; - - function handler( evt, obj ) { - var type = evt.type || evt, - parts, uid; - - parts = type.split('::'); - uid = parts[ 0 ]; - type = parts[ 1 ]; - - // console.log.apply( console, arguments ); - - if ( type === 'Ready' && uid === me.uid ) { - me.trigger('ready'); - } else if ( clients[ uid ] ) { - clients[ uid ].trigger( type.toLowerCase(), evt, obj ); - } - - // Base.log( evt, obj ); - } - - // flash的接受器。 - window[ jsreciver ] = function() { - var args = arguments; - - // 为了能捕获得到。 - setTimeout(function() { - handler.apply( null, args ); - }, 1 ); - }; - - this.jsreciver = jsreciver; - - this.destroy = function() { - // @todo 删除池子中的所有实例 - return destroy && destroy.apply( this, arguments ); - }; - - this.flashExec = function( comp, fn ) { - var flash = me.getFlash(), - args = Base.slice( arguments, 2 ); - - return flash.exec( this.uid, comp, fn, args ); - }; - - // @todo - } - - Base.inherits( Runtime, { - constructor: FlashRuntime, - - init: function() { - var container = this.getContainer(), - opts = this.options, - html; - - // if not the minimal height, shims are not initialized - // in older browsers (e.g FF3.6, IE6,7,8, Safari 4.0,5.0, etc) - container.css({ - position: 'absolute', - top: '-8px', - left: '-8px', - width: '9px', - height: '9px', - overflow: 'hidden' - }); - - // insert flash object - html = '' + - '' + - '' + - '' + - ''; - - container.html( html ); - }, - - getFlash: function() { - if ( this._flash ) { - return this._flash; - } - - this._flash = $( '#' + this.uid ).get( 0 ); - return this._flash; - } - - }); - - FlashRuntime.register = function( name, component ) { - component = components[ name ] = Base.inherits( CompBase, $.extend({ - - // @todo fix this later - flashExec: function() { - var owner = this.owner, - runtime = this.getRuntime(); - - return runtime.flashExec.apply( owner, arguments ); - } - }, component ) ); - - return component; - }; - - if ( getFlashVersion() >= 11.4 ) { - Runtime.addRuntime( type, FlashRuntime ); - } - - return FlashRuntime; - }); - /** - * @fileOverview FilePicker - */ - define('runtime/flash/filepicker',[ - 'base', - 'runtime/flash/runtime' - ], function( Base, FlashRuntime ) { - var $ = Base.$; - - return FlashRuntime.register( 'FilePicker', { - init: function( opts ) { - var copy = $.extend({}, opts ), - len, i; - - // 修复Flash再没有设置title的情况下无法弹出flash文件选择框的bug. - len = copy.accept && copy.accept.length; - for ( i = 0; i < len; i++ ) { - if ( !copy.accept[ i ].title ) { - copy.accept[ i ].title = 'Files'; - } - } - - delete copy.button; - delete copy.id; - delete copy.container; - - this.flashExec( 'FilePicker', 'init', copy ); - }, - - destroy: function() { - this.flashExec( 'FilePicker', 'destroy' ); - } - }); - }); - /** - * @fileOverview 图片压缩 - */ - define('runtime/flash/image',[ - 'runtime/flash/runtime' - ], function( FlashRuntime ) { - - return FlashRuntime.register( 'Image', { - // init: function( options ) { - // var owner = this.owner; - - // this.flashExec( 'Image', 'init', options ); - // owner.on( 'load', function() { - // debugger; - // }); - // }, - - loadFromBlob: function( blob ) { - var owner = this.owner; - - owner.info() && this.flashExec( 'Image', 'info', owner.info() ); - owner.meta() && this.flashExec( 'Image', 'meta', owner.meta() ); - - this.flashExec( 'Image', 'loadFromBlob', blob.uid ); - } - }); - }); - /** - * @fileOverview Blob Html实现 - */ - define('runtime/flash/blob',[ - 'runtime/flash/runtime', - 'lib/blob' - ], function( FlashRuntime, Blob ) { - - return FlashRuntime.register( 'Blob', { - slice: function( start, end ) { - var blob = this.flashExec( 'Blob', 'slice', start, end ); - - return new Blob( blob.uid, blob ); - } - }); - }); - /** - * @fileOverview Transport flash实现 - */ - define('runtime/flash/transport',[ - 'base', - 'runtime/flash/runtime', - 'runtime/client' - ], function( Base, FlashRuntime, RuntimeClient ) { - var $ = Base.$; - - return FlashRuntime.register( 'Transport', { - init: function() { - this._status = 0; - this._response = null; - this._responseJson = null; - }, - - send: function() { - var owner = this.owner, - opts = this.options, - xhr = this._initAjax(), - blob = owner._blob, - server = opts.server, - binary; - - xhr.connectRuntime( blob.ruid ); - - if ( opts.sendAsBinary ) { - server += (/\?/.test( server ) ? '&' : '?') + - $.param( owner._formData ); - - binary = blob.uid; - } else { - $.each( owner._formData, function( k, v ) { - xhr.exec( 'append', k, v ); - }); - - xhr.exec( 'appendBlob', opts.fileVal, blob.uid, - opts.filename || owner._formData.name || '' ); - } - - this._setRequestHeader( xhr, opts.headers ); - xhr.exec( 'send', { - method: opts.method, - url: server, - forceURLStream: opts.forceURLStream, - mimeType: 'application/octet-stream' - }, binary ); - }, - - getStatus: function() { - return this._status; - }, - - getResponse: function() { - return this._response || ''; - }, - - getResponseAsJson: function() { - return this._responseJson; - }, - - abort: function() { - var xhr = this._xhr; - - if ( xhr ) { - xhr.exec('abort'); - xhr.destroy(); - this._xhr = xhr = null; - } - }, - - destroy: function() { - this.abort(); - }, - - _initAjax: function() { - var me = this, - xhr = new RuntimeClient('XMLHttpRequest'); - - xhr.on( 'uploadprogress progress', function( e ) { - var percent = e.loaded / e.total; - percent = Math.min( 1, Math.max( 0, percent ) ); - return me.trigger( 'progress', percent ); - }); - - xhr.on( 'load', function() { - var status = xhr.exec('getStatus'), - readBody = false, - err = '', - p; - - xhr.off(); - me._xhr = null; - - if ( status >= 200 && status < 300 ) { - readBody = true; - } else if ( status >= 500 && status < 600 ) { - readBody = true; - err = 'server'; - } else { - err = 'http'; - } - - if ( readBody ) { - me._response = xhr.exec('getResponse'); - me._response = decodeURIComponent( me._response ); - - // flash 处理可能存在 bug, 没辙只能靠 js 了 - // try { - // me._responseJson = xhr.exec('getResponseAsJson'); - // } catch ( error ) { - - p = window.JSON && window.JSON.parse || function( s ) { - try { - return new Function('return ' + s).call(); - } catch ( err ) { - return {}; - } - }; - me._responseJson = me._response ? p(me._response) : {}; - - // } - } - - xhr.destroy(); - xhr = null; - - return err ? me.trigger( 'error', err ) : me.trigger('load'); - }); - - xhr.on( 'error', function() { - xhr.off(); - me._xhr = null; - me.trigger( 'error', 'http' ); - }); - - me._xhr = xhr; - return xhr; - }, - - _setRequestHeader: function( xhr, headers ) { - $.each( headers, function( key, val ) { - xhr.exec( 'setRequestHeader', key, val ); - }); - } - }); - }); - /** - * @fileOverview 只有flash实现的文件版本。 - */ - define('preset/flashonly',[ - 'base', - - // widgets - 'widgets/filepicker', - 'widgets/image', - 'widgets/queue', - 'widgets/runtime', - 'widgets/upload', - 'widgets/validator', - - // runtimes - - // flash - 'runtime/flash/filepicker', - 'runtime/flash/image', - 'runtime/flash/blob', - 'runtime/flash/transport' - ], function( Base ) { - return Base; - }); - define('webuploader',[ - 'preset/flashonly' - ], function( preset ) { - return preset; - }); - return require('webuploader'); -}); diff --git a/static/plugins/webuploader/webuploader.flashonly.min.js b/static/plugins/webuploader/webuploader.flashonly.min.js deleted file mode 100644 index 7a2c8abb..00000000 --- a/static/plugins/webuploader/webuploader.flashonly.min.js +++ /dev/null @@ -1,2 +0,0 @@ -/* WebUploader 0.1.5 */!function(a,b){var c,d={},e=function(a,b){var c,d,e;if("string"==typeof a)return h(a);for(c=[],d=a.length,e=0;d>e;e++)c.push(h(a[e]));return b.apply(null,c)},f=function(a,b,c){2===arguments.length&&(c=b,b=null),e(b||[],function(){g(a,c,arguments)})},g=function(a,b,c){var f,g={exports:b};"function"==typeof b&&(c.length||(c=[e,g.exports,g]),f=b.apply(null,c),void 0!==f&&(g.exports=f)),d[a]=g.exports},h=function(b){var c=d[b]||a[b];if(!c)throw new Error("`"+b+"` is undefined");return c},i=function(a){var b,c,e,f,g,h;h=function(a){return a&&a.charAt(0).toUpperCase()+a.substr(1)};for(b in d)if(c=a,d.hasOwnProperty(b)){for(e=b.split("/"),g=h(e.pop());f=h(e.shift());)c[f]=c[f]||{},c=c[f];c[g]=d[b]}return a},j=function(c){return a.__dollar=c,i(b(a,f,e))};"object"==typeof module&&"object"==typeof module.exports?module.exports=j():"function"==typeof define&&define.amd?define(["jquery"],j):(c=a.WebUploader,a.WebUploader=j(),a.WebUploader.noConflict=function(){a.WebUploader=c})}(window,function(a,b,c){return b("dollar-third",[],function(){var b=a.__dollar||a.jQuery||a.Zepto;if(!b)throw new Error("jQuery or Zepto not found!");return b}),b("dollar",["dollar-third"],function(a){return a}),b("promise-third",["dollar"],function(a){return{Deferred:a.Deferred,when:a.when,isPromise:function(a){return a&&"function"==typeof a.then}}}),b("promise",["promise-third"],function(a){return a}),b("base",["dollar","promise"],function(b,c){function d(a){return function(){return h.apply(a,arguments)}}function e(a,b){return function(){return a.apply(b,arguments)}}function f(a){var b;return Object.create?Object.create(a):(b=function(){},b.prototype=a,new b)}var g=function(){},h=Function.call;return{version:"0.1.5",$:b,Deferred:c.Deferred,isPromise:c.isPromise,when:c.when,browser:function(a){var b={},c=a.match(/WebKit\/([\d.]+)/),d=a.match(/Chrome\/([\d.]+)/)||a.match(/CriOS\/([\d.]+)/),e=a.match(/MSIE\s([\d\.]+)/)||a.match(/(?:trident)(?:.*rv:([\w.]+))?/i),f=a.match(/Firefox\/([\d.]+)/),g=a.match(/Safari\/([\d.]+)/),h=a.match(/OPR\/([\d.]+)/);return c&&(b.webkit=parseFloat(c[1])),d&&(b.chrome=parseFloat(d[1])),e&&(b.ie=parseFloat(e[1])),f&&(b.firefox=parseFloat(f[1])),g&&(b.safari=parseFloat(g[1])),h&&(b.opera=parseFloat(h[1])),b}(navigator.userAgent),os:function(a){var b={},c=a.match(/(?:Android);?[\s\/]+([\d.]+)?/),d=a.match(/(?:iPad|iPod|iPhone).*OS\s([\d_]+)/);return c&&(b.android=parseFloat(c[1])),d&&(b.ios=parseFloat(d[1].replace(/_/g,"."))),b}(navigator.userAgent),inherits:function(a,c,d){var e;return"function"==typeof c?(e=c,c=null):e=c&&c.hasOwnProperty("constructor")?c.constructor:function(){return a.apply(this,arguments)},b.extend(!0,e,a,d||{}),e.__super__=a.prototype,e.prototype=f(a.prototype),c&&b.extend(!0,e.prototype,c),e},noop:g,bindFn:e,log:function(){return a.console?e(console.log,console):g}(),nextTick:function(){return function(a){setTimeout(a,1)}}(),slice:d([].slice),guid:function(){var a=0;return function(b){for(var c=(+new Date).toString(32),d=0;5>d;d++)c+=Math.floor(65535*Math.random()).toString(32);return(b||"wu_")+c+(a++).toString(32)}}(),formatSize:function(a,b,c){var d;for(c=c||["B","K","M","G","TB"];(d=c.shift())&&a>1024;)a/=1024;return("B"===d?a:a.toFixed(b||2))+d}}}),b("mediator",["base"],function(a){function b(a,b,c,d){return f.grep(a,function(a){return!(!a||b&&a.e!==b||c&&a.cb!==c&&a.cb._cb!==c||d&&a.ctx!==d)})}function c(a,b,c){f.each((a||"").split(h),function(a,d){c(d,b)})}function d(a,b){for(var c,d=!1,e=-1,f=a.length;++e1?void(d.isPlainObject(b)&&d.isPlainObject(c[a])?d.extend(c[a],b):c[a]=b):a?c[a]:c},getStats:function(){var a=this.request("get-stats");return a?{successNum:a.numOfSuccess,progressNum:a.numOfProgress,cancelNum:a.numOfCancel,invalidNum:a.numOfInvalid,uploadFailNum:a.numOfUploadFailed,queueNum:a.numOfQueue,interruptNum:a.numofInterrupt}:{}},trigger:function(a){var c=[].slice.call(arguments,1),e=this.options,f="on"+a.substring(0,1).toUpperCase()+a.substring(1);return b.trigger.apply(this,arguments)===!1||d.isFunction(e[f])&&e[f].apply(this,c)===!1||d.isFunction(this[f])&&this[f].apply(this,c)===!1||b.trigger.apply(b,[this,a].concat(c))===!1?!1:!0},destroy:function(){this.request("destroy",arguments),this.off()},request:a.noop}),a.create=c.create=function(a){return new c(a)},a.Uploader=c,c}),b("runtime/runtime",["base","mediator"],function(a,b){function c(b){this.options=d.extend({container:document.body},b),this.uid=a.guid("rt_")}var d=a.$,e={},f=function(a){for(var b in a)if(a.hasOwnProperty(b))return b;return null};return d.extend(c.prototype,{getContainer:function(){var a,b,c=this.options;return this._container?this._container:(a=d(c.container||document.body),b=d(document.createElement("div")),b.attr("id","rt_"+this.uid),b.css({position:"absolute",top:"0px",left:"0px",width:"1px",height:"1px",overflow:"hidden"}),a.append(b),a.addClass("webuploader-container"),this._container=b,this._parent=a,b)},init:a.noop,exec:a.noop,destroy:function(){this._container&&this._container.remove(),this._parent&&this._parent.removeClass("webuploader-container"),this.off()}}),c.orders="html5,flash",c.addRuntime=function(a,b){e[a]=b},c.hasRuntime=function(a){return!!(a?e[a]:f(e))},c.create=function(a,b){var g,h;if(b=b||c.orders,d.each(b.split(/\s*,\s*/g),function(){return e[this]?(g=this,!1):void 0}),g=g||f(e),!g)throw new Error("Runtime Error");return h=new e[g](a)},b.installTo(c.prototype),c}),b("runtime/client",["base","mediator","runtime/runtime"],function(a,b,c){function d(b,d){var f,g=a.Deferred();this.uid=a.guid("client_"),this.runtimeReady=function(a){return g.done(a)},this.connectRuntime=function(b,h){if(f)throw new Error("already connected!");return g.done(h),"string"==typeof b&&e.get(b)&&(f=e.get(b)),f=f||e.get(null,d),f?(a.$.extend(f.options,b),f.__promise.then(g.resolve),f.__client++):(f=c.create(b,b.runtimeOrder),f.__promise=g.promise(),f.once("ready",g.resolve),f.init(),e.add(f),f.__client=1),d&&(f.__standalone=d),f},this.getRuntime=function(){return f},this.disconnectRuntime=function(){f&&(f.__client--,f.__client<=0&&(e.remove(f),delete f.__promise,f.destroy()),f=null)},this.exec=function(){if(f){var c=a.slice(arguments);return b&&c.unshift(b),f.exec.apply(this,c)}},this.getRuid=function(){return f&&f.uid},this.destroy=function(a){return function(){a&&a.apply(this,arguments),this.trigger("destroy"),this.off(),this.exec("destroy"),this.disconnectRuntime()}}(this.destroy)}var e;return e=function(){var a={};return{add:function(b){a[b.uid]=b},get:function(b,c){var d;if(b)return a[b];for(d in a)if(!c||!a[d].__standalone)return a[d];return null},remove:function(b){delete a[b.uid]}}}(),b.installTo(d.prototype),d}),b("lib/blob",["base","runtime/client"],function(a,b){function c(a,c){var d=this;d.source=c,d.ruid=a,this.size=c.size||0,this.type=!c.type&&this.ext&&~"jpg,jpeg,png,gif,bmp".indexOf(this.ext)?"image/"+("jpg"===this.ext?"jpeg":this.ext):c.type||"application/octet-stream",b.call(d,"Blob"),this.uid=c.uid||this.uid,a&&d.connectRuntime(a)}return a.inherits(b,{constructor:c,slice:function(a,b){return this.exec("slice",a,b)},getSource:function(){return this.source}}),c}),b("lib/file",["base","lib/blob"],function(a,b){function c(a,c){var f;this.name=c.name||"untitled"+d++,f=e.exec(c.name)?RegExp.$1.toLowerCase():"",!f&&c.type&&(f=/\/(jpg|jpeg|png|gif|bmp)$/i.exec(c.type)?RegExp.$1.toLowerCase():"",this.name+="."+f),this.ext=f,this.lastModifiedDate=c.lastModifiedDate||(new Date).toLocaleString(),b.apply(this,arguments)}var d=1,e=/\.([^.]+)$/;return a.inherits(b,c)}),b("lib/filepicker",["base","runtime/client","lib/file"],function(b,c,d){function e(a){if(a=this.options=f.extend({},e.options,a),a.container=f(a.id),!a.container.length)throw new Error("按钮指定错误");a.innerHTML=a.innerHTML||a.label||a.container.html()||"",a.button=f(a.button||document.createElement("div")),a.button.html(a.innerHTML),a.container.html(a.button),c.call(this,"FilePicker",!0)}var f=b.$;return e.options={button:null,container:null,label:null,innerHTML:null,multiple:!0,accept:null,name:"file"},b.inherits(c,{constructor:e,init:function(){var c=this,e=c.options,g=e.button;g.addClass("webuploader-pick"),c.on("all",function(a){var b;switch(a){case"mouseenter":g.addClass("webuploader-pick-hover");break;case"mouseleave":g.removeClass("webuploader-pick-hover");break;case"change":b=c.exec("getFiles"),c.trigger("select",f.map(b,function(a){return a=new d(c.getRuid(),a),a._refer=e.container,a}),e.container)}}),c.connectRuntime(e,function(){c.refresh(),c.exec("init",e),c.trigger("ready")}),this._resizeHandler=b.bindFn(this.refresh,this),f(a).on("resize",this._resizeHandler)},refresh:function(){var a=this.getRuntime().getContainer(),b=this.options.button,c=b.outerWidth?b.outerWidth():b.width(),d=b.outerHeight?b.outerHeight():b.height(),e=b.offset();c&&d&&a.css({bottom:"auto",right:"auto",width:c+"px",height:d+"px"}).offset(e)},enable:function(){var a=this.options.button;a.removeClass("webuploader-pick-disable"),this.refresh()},disable:function(){var a=this.options.button;this.getRuntime().getContainer().css({top:"-99999px"}),a.addClass("webuploader-pick-disable")},destroy:function(){var b=this.options.button;f(a).off("resize",this._resizeHandler),b.removeClass("webuploader-pick-disable webuploader-pick-hover webuploader-pick")}}),e}),b("widgets/widget",["base","uploader"],function(a,b){function c(a){if(!a)return!1;var b=a.length,c=e.type(a);return 1===a.nodeType&&b?!0:"array"===c||"function"!==c&&"string"!==c&&(0===b||"number"==typeof b&&b>0&&b-1 in a)}function d(a){this.owner=a,this.options=a.options}var e=a.$,f=b.prototype._init,g=b.prototype.destroy,h={},i=[];return e.extend(d.prototype,{init:a.noop,invoke:function(a,b){var c=this.responseMap;return c&&a in c&&c[a]in this&&e.isFunction(this[c[a]])?this[c[a]].apply(this,b):h},request:function(){return this.owner.request.apply(this.owner,arguments)}}),e.extend(b.prototype,{_init:function(){var a=this,b=a._widgets=[],c=a.options.disableWidgets||"";return e.each(i,function(d,e){(!c||!~c.indexOf(e._name))&&b.push(new e(a))}),f.apply(a,arguments)},request:function(b,d,e){var f,g,i,j,k=0,l=this._widgets,m=l&&l.length,n=[],o=[];for(d=c(d)?d:[d];m>k;k++)f=l[k],g=f.invoke(b,d),g!==h&&(a.isPromise(g)?o.push(g):n.push(g));return e||o.length?(i=a.when.apply(a,o),j=i.pipe?"pipe":"then",i[j](function(){var b=a.Deferred(),c=arguments;return 1===c.length&&(c=c[0]),setTimeout(function(){b.resolve(c)},1),b.promise()})[e?j:"done"](e||a.noop)):n[0]},destroy:function(){g.apply(this,arguments),this._widgets=null}}),b.register=d.register=function(b,c){var f,g={init:"init",destroy:"destroy",name:"anonymous"};return 1===arguments.length?(c=b,e.each(c,function(a){return"_"===a[0]||"name"===a?void("name"===a&&(g.name=c.name)):void(g[a.replace(/[A-Z]/g,"-$&").toLowerCase()]=a)})):g=e.extend(g,b),c.responseMap=g,f=a.inherits(d,c),f._name=g.name,i.push(f),f},b.unRegister=d.unRegister=function(a){if(a&&"anonymous"!==a)for(var b=i.length;b--;)i[b]._name===a&&i.splice(b,1)},d}),b("widgets/filepicker",["base","uploader","lib/filepicker","widgets/widget"],function(a,b,c){var d=a.$;return d.extend(b.options,{pick:null,accept:null}),b.register({name:"picker",init:function(a){return this.pickers=[],a.pick&&this.addBtn(a.pick)},refresh:function(){d.each(this.pickers,function(){this.refresh()})},addBtn:function(b){var e=this,f=e.options,g=f.accept,h=[];if(b)return d.isPlainObject(b)||(b={id:b}),d(b.id).each(function(){var i,j,k;k=a.Deferred(),i=d.extend({},b,{accept:d.isPlainObject(g)?[g]:g,swf:f.swf,runtimeOrder:f.runtimeOrder,id:this}),j=new c(i),j.once("ready",k.resolve),j.on("select",function(a){e.owner.request("add-file",[a])}),j.init(),e.pickers.push(j),h.push(k.promise())}),a.when.apply(a,h)},disable:function(){d.each(this.pickers,function(){this.disable()})},enable:function(){d.each(this.pickers,function(){this.enable()})},destroy:function(){d.each(this.pickers,function(){this.destroy()}),this.pickers=null}})}),b("lib/image",["base","runtime/client","lib/blob"],function(a,b,c){function d(a){this.options=e.extend({},d.options,a),b.call(this,"Image"),this.on("load",function(){this._info=this.exec("info"),this._meta=this.exec("meta")})}var e=a.$;return d.options={quality:90,crop:!1,preserveHeaders:!1,allowMagnify:!1},a.inherits(b,{constructor:d,info:function(a){return a?(this._info=a,this):this._info},meta:function(a){return a?(this._meta=a,this):this._meta},loadFromBlob:function(a){var b=this,c=a.getRuid();this.connectRuntime(c,function(){b.exec("init",b.options),b.exec("loadFromBlob",a)})},resize:function(){var b=a.slice(arguments);return this.exec.apply(this,["resize"].concat(b))},crop:function(){var b=a.slice(arguments);return this.exec.apply(this,["crop"].concat(b))},getAsDataUrl:function(a){return this.exec("getAsDataUrl",a)},getAsBlob:function(a){var b=this.exec("getAsBlob",a);return new c(this.getRuid(),b)}}),d}),b("widgets/image",["base","uploader","lib/image","widgets/widget"],function(a,b,c){var d,e=a.$;return d=function(a){var b=0,c=[],d=function(){for(var d;c.length&&a>b;)d=c.shift(),b+=d[0],d[1]()};return function(a,e,f){c.push([e,f]),a.once("destroy",function(){b-=e,setTimeout(d,1)}),setTimeout(d,1)}}(5242880),e.extend(b.options,{thumb:{width:110,height:110,quality:70,allowMagnify:!0,crop:!0,preserveHeaders:!1,type:"image/jpeg"},compress:{width:1600,height:1600,quality:90,allowMagnify:!1,crop:!1,preserveHeaders:!0}}),b.register({name:"image",makeThumb:function(a,b,f,g){var h,i;return a=this.request("get-file",a),a.type.match(/^image/)?(h=e.extend({},this.options.thumb),e.isPlainObject(f)&&(h=e.extend(h,f),f=null),f=f||h.width,g=g||h.height,i=new c(h),i.once("load",function(){a._info=a._info||i.info(),a._meta=a._meta||i.meta(),1>=f&&f>0&&(f=a._info.width*f),1>=g&&g>0&&(g=a._info.height*g),i.resize(f,g)}),i.once("complete",function(){b(!1,i.getAsDataUrl(h.type)),i.destroy()}),i.once("error",function(a){b(a||!0),i.destroy()}),void d(i,a.source.size,function(){a._info&&i.info(a._info),a._meta&&i.meta(a._meta),i.loadFromBlob(a.source)})):void b(!0)},beforeSendFile:function(b){var d,f,g=this.options.compress||this.options.resize,h=g&&g.compressSize||0,i=g&&g.noCompressIfLarger||!1;return b=this.request("get-file",b),!g||!~"image/jpeg,image/jpg".indexOf(b.type)||b.size=a&&a>0&&(a=b._info.width*a),1>=c&&c>0&&(c=b._info.height*c),d.resize(a,c)}),d.once("complete",function(){var a,c;try{a=d.getAsBlob(g.type),c=b.size,(!i||a.sizeb;b++)if(c=this._queue[b],a===c.getStatus())return c;return null},sort:function(a){"function"==typeof a&&this._queue.sort(a)},getFiles:function(){for(var a,b=[].slice.call(arguments,0),c=[],d=0,f=this._queue.length;f>d;d++)a=this._queue[d],(!b.length||~e.inArray(a.getStatus(),b))&&c.push(a);return c},removeFile:function(a){var b=this._map[a.id];b&&(delete this._map[a.id],a.destroy(),this.stats.numofDeleted++)},_fileAdded:function(a){var b=this,c=this._map[a.id];c||(this._map[a.id]=a,a.on("statuschange",function(a,c){b._onFileStatusChange(a,c)}))},_onFileStatusChange:function(a,b){var c=this.stats;switch(b){case f.PROGRESS:c.numOfProgress--;break;case f.QUEUED:c.numOfQueue--;break;case f.ERROR:c.numOfUploadFailed--;break;case f.INVALID:c.numOfInvalid--;break;case f.INTERRUPT:c.numofInterrupt--}switch(a){case f.QUEUED:c.numOfQueue++;break;case f.PROGRESS:c.numOfProgress++;break;case f.ERROR:c.numOfUploadFailed++;break;case f.COMPLETE:c.numOfSuccess++;break;case f.CANCELLED:c.numOfCancel++;break;case f.INVALID:c.numOfInvalid++;break;case f.INTERRUPT:c.numofInterrupt++}}}),b.installTo(d.prototype),d}),b("widgets/queue",["base","uploader","queue","file","lib/file","runtime/client","widgets/widget"],function(a,b,c,d,e,f){var g=a.$,h=/\.\w+$/,i=d.Status;return b.register({name:"queue",init:function(b){var d,e,h,i,j,k,l,m=this;if(g.isPlainObject(b.accept)&&(b.accept=[b.accept]),b.accept){for(j=[],h=0,e=b.accept.length;e>h;h++)i=b.accept[h].extensions,i&&j.push(i);j.length&&(k="\\."+j.join(",").replace(/,/g,"$|\\.").replace(/\*/g,".*")+"$"),m.accept=new RegExp(k,"i")}return m.queue=new c,m.stats=m.queue.stats,"html5"===this.request("predict-runtime-type")?(d=a.Deferred(),this.placeholder=l=new f("Placeholder"),l.connectRuntime({runtimeOrder:"html5"},function(){m._ruid=l.getRuid(),d.resolve()}),d.promise()):void 0},_wrapFile:function(a){if(!(a instanceof d)){if(!(a instanceof e)){if(!this._ruid)throw new Error("Can't add external files.");a=new e(this._ruid,a)}a=new d(a)}return a},acceptFile:function(a){var b=!a||!a.size||this.accept&&h.exec(a.name)&&!this.accept.test(a.name);return!b},_addFile:function(a){var b=this;return a=b._wrapFile(a),b.owner.trigger("beforeFileQueued",a)?b.acceptFile(a)?(b.queue.append(a),b.owner.trigger("fileQueued",a),a):void b.owner.trigger("error","Q_TYPE_DENIED",a):void 0},getFile:function(a){return this.queue.getFile(a)},addFile:function(a){var b=this;a.length||(a=[a]),a=g.map(a,function(a){return b._addFile(a)}),b.owner.trigger("filesQueued",a),b.options.auto&&setTimeout(function(){b.request("start-upload")},20)},getStats:function(){return this.stats},removeFile:function(a,b){var c=this;a=a.id?a:c.queue.getFile(a),this.request("cancel-file",a),b&&this.queue.removeFile(a)},getFiles:function(){return this.queue.getFiles.apply(this.queue,arguments)},fetchFile:function(){return this.queue.fetch.apply(this.queue,arguments)},retry:function(a,b){var c,d,e,f=this;if(a)return a=a.id?a:f.queue.getFile(a),a.setStatus(i.QUEUED),void(b||f.request("start-upload"));for(c=f.queue.getFiles(i.ERROR),d=0,e=c.length;e>d;d++)a=c[d],a.setStatus(i.QUEUED);f.request("start-upload")},sortFiles:function(){return this.queue.sort.apply(this.queue,arguments)},reset:function(){this.owner.trigger("reset"),this.queue=new c,this.stats=this.queue.stats},destroy:function(){this.reset(),this.placeholder&&this.placeholder.destroy()}})}),b("widgets/runtime",["uploader","runtime/runtime","widgets/widget"],function(a,b){return a.support=function(){return b.hasRuntime.apply(b,arguments)},a.register({name:"runtime",init:function(){if(!this.predictRuntimeType())throw Error("Runtime Error")},predictRuntimeType:function(){var a,c,d=this.options.runtimeOrder||b.orders,e=this.type;if(!e)for(d=d.split(/\s*,\s*/g),a=0,c=d.length;c>a;a++)if(b.hasRuntime(d[a])){this.type=e=d[a];break}return e}})}),b("lib/transport",["base","runtime/client","mediator"],function(a,b,c){function d(a){var c=this;a=c.options=e.extend(!0,{},d.options,a||{}),b.call(this,"Transport"),this._blob=null,this._formData=a.formData||{},this._headers=a.headers||{},this.on("progress",this._timeout),this.on("load error",function(){c.trigger("progress",1),clearTimeout(c._timer)})}var e=a.$;return d.options={server:"",method:"POST",withCredentials:!1,fileVal:"file",timeout:12e4,formData:{},headers:{},sendAsBinary:!1},e.extend(d.prototype,{appendBlob:function(a,b,c){var d=this,e=d.options;d.getRuid()&&d.disconnectRuntime(),d.connectRuntime(b.ruid,function(){d.exec("init")}),d._blob=b,e.fileVal=a||e.fileVal,e.filename=c||e.filename},append:function(a,b){"object"==typeof a?e.extend(this._formData,a):this._formData[a]=b},setRequestHeader:function(a,b){"object"==typeof a?e.extend(this._headers,a):this._headers[a]=b},send:function(a){this.exec("send",a),this._timeout()},abort:function(){return clearTimeout(this._timer),this.exec("abort")},destroy:function(){this.trigger("destroy"),this.off(),this.exec("destroy"),this.disconnectRuntime()},getResponse:function(){return this.exec("getResponse")},getResponseAsJson:function(){return this.exec("getResponseAsJson")},getStatus:function(){return this.exec("getStatus")},_timeout:function(){var a=this,b=a.options.timeout;b&&(clearTimeout(a._timer),a._timer=setTimeout(function(){a.abort(),a.trigger("error","timeout")},b))}}),c.installTo(d.prototype),d}),b("widgets/upload",["base","uploader","file","lib/transport","widgets/widget"],function(a,b,c,d){function e(a,b){var c,d,e=[],f=a.source,g=f.size,h=b?Math.ceil(g/b):1,i=0,j=0;for(d={file:a,has:function(){return!!e.length},shift:function(){return e.shift()},unshift:function(a){e.unshift(a)}};h>j;)c=Math.min(b,g-i),e.push({file:a,start:i,end:b?i+c:g,total:g,chunks:h,chunk:j++,cuted:d}),i+=c;return a.blocks=e.concat(),a.remaning=e.length,d}var f=a.$,g=a.isPromise,h=c.Status;f.extend(b.options,{prepareNextFile:!1,chunked:!1,chunkSize:5242880,chunkRetry:2,threads:3,formData:{}}),b.register({name:"upload",init:function(){var b=this.owner,c=this;this.runing=!1,this.progress=!1,b.on("startUpload",function(){c.progress=!0}).on("uploadFinished",function(){c.progress=!1}),this.pool=[],this.stack=[],this.pending=[],this.remaning=0,this.__tick=a.bindFn(this._tick,this),b.on("uploadComplete",function(a){a.blocks&&f.each(a.blocks,function(a,b){b.transport&&(b.transport.abort(),b.transport.destroy()),delete b.transport}),delete a.blocks,delete a.remaning})},reset:function(){this.request("stop-upload",!0),this.runing=!1,this.pool=[],this.stack=[],this.pending=[],this.remaning=0,this._trigged=!1,this._promise=null},startUpload:function(b){var c=this;if(f.each(c.request("get-files",h.INVALID),function(){c.request("remove-file",this)}),b)if(b=b.id?b:c.request("get-file",b),b.getStatus()===h.INTERRUPT)f.each(c.pool,function(a,c){c.file===b&&c.transport&&c.transport.send()}),b.setStatus(h.QUEUED);else{if(b.getStatus()===h.PROGRESS)return;b.setStatus(h.QUEUED)}else f.each(c.request("get-files",[h.INITED]),function(){this.setStatus(h.QUEUED)});if(!c.runing){c.runing=!0;var d=[];f.each(c.pool,function(a,b){var e=b.file;e.getStatus()===h.INTERRUPT&&(d.push(e),c._trigged=!1,b.transport&&b.transport.send())});for(var b;b=d.shift();)b.setStatus(h.PROGRESS);b||f.each(c.request("get-files",h.INTERRUPT),function(){this.setStatus(h.PROGRESS)}),c._trigged=!1,a.nextTick(c.__tick),c.owner.trigger("startUpload")}},stopUpload:function(b,c){var d=this;if(b===!0&&(c=b,b=null),d.runing!==!1){if(b){if(b=b.id?b:d.request("get-file",b),b.getStatus()!==h.PROGRESS&&b.getStatus()!==h.QUEUED)return;return b.setStatus(h.INTERRUPT),f.each(d.pool,function(a,c){c.file===b&&(c.transport&&c.transport.abort(),d._putback(c),d._popBlock(c))}),a.nextTick(d.__tick)}d.runing=!1,this._promise&&this._promise.file&&this._promise.file.setStatus(h.INTERRUPT),c&&f.each(d.pool,function(a,b){b.transport&&b.transport.abort(),b.file.setStatus(h.INTERRUPT)}),d.owner.trigger("stopUpload")}},cancelFile:function(a){a=a.id?a:this.request("get-file",a),a.blocks&&f.each(a.blocks,function(a,b){var c=b.transport;c&&(c.abort(),c.destroy(),delete b.transport)}),a.setStatus(h.CANCELLED),this.owner.trigger("fileDequeued",a)},isInProgress:function(){return!!this.progress},_getStats:function(){return this.request("get-stats")},skipFile:function(a,b){a=a.id?a:this.request("get-file",a),a.setStatus(b||h.COMPLETE),a.skipped=!0,a.blocks&&f.each(a.blocks,function(a,b){var c=b.transport;c&&(c.abort(),c.destroy(),delete b.transport)}),this.owner.trigger("uploadSkip",a)},_tick:function(){var b,c,d=this,e=d.options;return d._promise?d._promise.always(d.__tick):void(d.pool.length1&&~"http,abort".indexOf(a)&&b.retried1&&f.extend(m,{chunks:b.chunks,chunk:b.chunk}),i.trigger("uploadBeforeSend",b,m,n),l.appendBlob(j.fileVal,b.blob,k.name),l.append(m),l.setRequestHeader(n),l.send()},_finishFile:function(a,b,c){var d=this.owner;return d.request("after-send-file",arguments,function(){a.setStatus(h.COMPLETE),d.trigger("uploadSuccess",a,b,c)}).fail(function(b){a.getStatus()===h.PROGRESS&&a.setStatus(h.ERROR,b),d.trigger("uploadError",a,b)}).always(function(){d.trigger("uploadComplete",a)})},updateFileProgress:function(a){var b=0,c=0;a.blocks&&(f.each(a.blocks,function(a,b){c+=(b.percentage||0)*(b.end-b.start)}),b=c/a.size,this.owner.trigger("uploadProgress",a,b||0))}})}),b("widgets/validator",["base","uploader","file","widgets/widget"],function(a,b,c){var d,e=a.$,f={};return d={addValidator:function(a,b){f[a]=b},removeValidator:function(a){delete f[a]}},b.register({name:"validator",init:function(){var b=this;a.nextTick(function(){e.each(f,function(){this.call(b.owner)})})}}),d.addValidator("fileNumLimit",function(){var a=this,b=a.options,c=0,d=parseInt(b.fileNumLimit,10),e=!0;d&&(a.on("beforeFileQueued",function(a){return c>=d&&e&&(e=!1,this.trigger("error","Q_EXCEED_NUM_LIMIT",d,a),setTimeout(function(){e=!0},1)),c>=d?!1:!0}),a.on("fileQueued",function(){c++}),a.on("fileDequeued",function(){c--}),a.on("reset",function(){c=0}))}),d.addValidator("fileSizeLimit",function(){var a=this,b=a.options,c=0,d=parseInt(b.fileSizeLimit,10),e=!0;d&&(a.on("beforeFileQueued",function(a){var b=c+a.size>d;return b&&e&&(e=!1,this.trigger("error","Q_EXCEED_SIZE_LIMIT",d,a),setTimeout(function(){e=!0},1)),b?!1:!0}),a.on("fileQueued",function(a){c+=a.size}),a.on("fileDequeued",function(a){c-=a.size}),a.on("reset",function(){c=0}))}),d.addValidator("fileSingleSizeLimit",function(){var a=this,b=a.options,d=b.fileSingleSizeLimit;d&&a.on("beforeFileQueued",function(a){return a.size>d?(a.setStatus(c.Status.INVALID,"exceed_size"),this.trigger("error","F_EXCEED_SIZE",d,a),!1):void 0})}),d.addValidator("duplicate",function(){function a(a){for(var b,c=0,d=0,e=a.length;e>d;d++)b=a.charCodeAt(d),c=b+(c<<6)+(c<<16)-c;return c}var b=this,c=b.options,d={};c.duplicate||(b.on("beforeFileQueued",function(b){var c=b.__hash||(b.__hash=a(b.name+b.size+b.lastModifiedDate)); -return d[c]?(this.trigger("error","F_DUPLICATE",b),!1):void 0}),b.on("fileQueued",function(a){var b=a.__hash;b&&(d[b]=!0)}),b.on("fileDequeued",function(a){var b=a.__hash;b&&delete d[b]}),b.on("reset",function(){d={}}))}),d}),b("runtime/compbase",[],function(){function a(a,b){this.owner=a,this.options=a.options,this.getRuntime=function(){return b},this.getRuid=function(){return b.uid},this.trigger=function(){return a.trigger.apply(a,arguments)}}return a}),b("runtime/flash/runtime",["base","runtime/runtime","runtime/compbase"],function(b,c,d){function e(){var a;try{a=navigator.plugins["Shockwave Flash"],a=a.description}catch(b){try{a=new ActiveXObject("ShockwaveFlash.ShockwaveFlash").GetVariable("$version")}catch(c){a="0.0"}}return a=a.match(/\d+/g),parseFloat(a[0]+"."+a[1],10)}function f(){function d(a,b){var c,d,e=a.type||a;c=e.split("::"),d=c[0],e=c[1],"Ready"===e&&d===j.uid?j.trigger("ready"):f[d]&&f[d].trigger(e.toLowerCase(),a,b)}var e={},f={},g=this.destroy,j=this,k=b.guid("webuploader_");c.apply(j,arguments),j.type=h,j.exec=function(a,c){var d,g=this,h=g.uid,k=b.slice(arguments,2);return f[h]=g,i[a]&&(e[h]||(e[h]=new i[a](g,j)),d=e[h],d[c])?d[c].apply(d,k):j.flashExec.apply(g,arguments)},a[k]=function(){var a=arguments;setTimeout(function(){d.apply(null,a)},1)},this.jsreciver=k,this.destroy=function(){return g&&g.apply(this,arguments)},this.flashExec=function(a,c){var d=j.getFlash(),e=b.slice(arguments,2);return d.exec(this.uid,a,c,e)}}var g=b.$,h="flash",i={};return b.inherits(c,{constructor:f,init:function(){var a,c=this.getContainer(),d=this.options;c.css({position:"absolute",top:"-8px",left:"-8px",width:"9px",height:"9px",overflow:"hidden"}),a='',c.html(a)},getFlash:function(){return this._flash?this._flash:(this._flash=g("#"+this.uid).get(0),this._flash)}}),f.register=function(a,c){return c=i[a]=b.inherits(d,g.extend({flashExec:function(){var a=this.owner,b=this.getRuntime();return b.flashExec.apply(a,arguments)}},c))},e()>=11.4&&c.addRuntime(h,f),f}),b("runtime/flash/filepicker",["base","runtime/flash/runtime"],function(a,b){var c=a.$;return b.register("FilePicker",{init:function(a){var b,d,e=c.extend({},a);for(b=e.accept&&e.accept.length,d=0;b>d;d++)e.accept[d].title||(e.accept[d].title="Files");delete e.button,delete e.id,delete e.container,this.flashExec("FilePicker","init",e)},destroy:function(){this.flashExec("FilePicker","destroy")}})}),b("runtime/flash/image",["runtime/flash/runtime"],function(a){return a.register("Image",{loadFromBlob:function(a){var b=this.owner;b.info()&&this.flashExec("Image","info",b.info()),b.meta()&&this.flashExec("Image","meta",b.meta()),this.flashExec("Image","loadFromBlob",a.uid)}})}),b("runtime/flash/blob",["runtime/flash/runtime","lib/blob"],function(a,b){return a.register("Blob",{slice:function(a,c){var d=this.flashExec("Blob","slice",a,c);return new b(d.uid,d)}})}),b("runtime/flash/transport",["base","runtime/flash/runtime","runtime/client"],function(b,c,d){var e=b.$;return c.register("Transport",{init:function(){this._status=0,this._response=null,this._responseJson=null},send:function(){var a,b=this.owner,c=this.options,d=this._initAjax(),f=b._blob,g=c.server;d.connectRuntime(f.ruid),c.sendAsBinary?(g+=(/\?/.test(g)?"&":"?")+e.param(b._formData),a=f.uid):(e.each(b._formData,function(a,b){d.exec("append",a,b)}),d.exec("appendBlob",c.fileVal,f.uid,c.filename||b._formData.name||"")),this._setRequestHeader(d,c.headers),d.exec("send",{method:c.method,url:g,forceURLStream:c.forceURLStream,mimeType:"application/octet-stream"},a)},getStatus:function(){return this._status},getResponse:function(){return this._response||""},getResponseAsJson:function(){return this._responseJson},abort:function(){var a=this._xhr;a&&(a.exec("abort"),a.destroy(),this._xhr=a=null)},destroy:function(){this.abort()},_initAjax:function(){var b=this,c=new d("XMLHttpRequest");return c.on("uploadprogress progress",function(a){var c=a.loaded/a.total;return c=Math.min(1,Math.max(0,c)),b.trigger("progress",c)}),c.on("load",function(){var d,e=c.exec("getStatus"),f=!1,g="";return c.off(),b._xhr=null,e>=200&&300>e?f=!0:e>=500&&600>e?(f=!0,g="server"):g="http",f&&(b._response=c.exec("getResponse"),b._response=decodeURIComponent(b._response),d=a.JSON&&a.JSON.parse||function(a){try{return new Function("return "+a).call()}catch(b){return{}}},b._responseJson=b._response?d(b._response):{}),c.destroy(),c=null,g?b.trigger("error",g):b.trigger("load")}),c.on("error",function(){c.off(),b._xhr=null,b.trigger("error","http")}),b._xhr=c,c},_setRequestHeader:function(a,b){e.each(b,function(b,c){a.exec("setRequestHeader",b,c)})}})}),b("preset/flashonly",["base","widgets/filepicker","widgets/image","widgets/queue","widgets/runtime","widgets/upload","widgets/validator","runtime/flash/filepicker","runtime/flash/image","runtime/flash/blob","runtime/flash/transport"],function(a){return a}),b("webuploader",["preset/flashonly"],function(a){return a}),c("webuploader")}); \ No newline at end of file diff --git a/static/plugins/webuploader/webuploader.html5only.js b/static/plugins/webuploader/webuploader.html5only.js deleted file mode 100644 index 6c21cefa..00000000 --- a/static/plugins/webuploader/webuploader.html5only.js +++ /dev/null @@ -1,6030 +0,0 @@ -/*! WebUploader 0.1.5 */ - - -/** - * @fileOverview 让内部各个部件的代码可以用[amd](https://github.com/amdjs/amdjs-api/wiki/AMD)模块定义方式组织起来。 - * - * AMD API 内部的简单不完全实现,请忽略。只有当WebUploader被合并成一个文件的时候才会引入。 - */ -(function( root, factory ) { - var modules = {}, - - // 内部require, 简单不完全实现。 - // https://github.com/amdjs/amdjs-api/wiki/require - _require = function( deps, callback ) { - var args, len, i; - - // 如果deps不是数组,则直接返回指定module - if ( typeof deps === 'string' ) { - return getModule( deps ); - } else { - args = []; - for( len = deps.length, i = 0; i < len; i++ ) { - args.push( getModule( deps[ i ] ) ); - } - - return callback.apply( null, args ); - } - }, - - // 内部define,暂时不支持不指定id. - _define = function( id, deps, factory ) { - if ( arguments.length === 2 ) { - factory = deps; - deps = null; - } - - _require( deps || [], function() { - setModule( id, factory, arguments ); - }); - }, - - // 设置module, 兼容CommonJs写法。 - setModule = function( id, factory, args ) { - var module = { - exports: factory - }, - returned; - - if ( typeof factory === 'function' ) { - args.length || (args = [ _require, module.exports, module ]); - returned = factory.apply( null, args ); - returned !== undefined && (module.exports = returned); - } - - modules[ id ] = module.exports; - }, - - // 根据id获取module - getModule = function( id ) { - var module = modules[ id ] || root[ id ]; - - if ( !module ) { - throw new Error( '`' + id + '` is undefined' ); - } - - return module; - }, - - // 将所有modules,将路径ids装换成对象。 - exportsTo = function( obj ) { - var key, host, parts, part, last, ucFirst; - - // make the first character upper case. - ucFirst = function( str ) { - return str && (str.charAt( 0 ).toUpperCase() + str.substr( 1 )); - }; - - for ( key in modules ) { - host = obj; - - if ( !modules.hasOwnProperty( key ) ) { - continue; - } - - parts = key.split('/'); - last = ucFirst( parts.pop() ); - - while( (part = ucFirst( parts.shift() )) ) { - host[ part ] = host[ part ] || {}; - host = host[ part ]; - } - - host[ last ] = modules[ key ]; - } - - return obj; - }, - - makeExport = function( dollar ) { - root.__dollar = dollar; - - // exports every module. - return exportsTo( factory( root, _define, _require ) ); - }, - - origin; - - if ( typeof module === 'object' && typeof module.exports === 'object' ) { - - // For CommonJS and CommonJS-like environments where a proper window is present, - module.exports = makeExport(); - } else if ( typeof define === 'function' && define.amd ) { - - // Allow using this built library as an AMD module - // in another project. That other project will only - // see this AMD call, not the internal modules in - // the closure below. - define([ 'jquery' ], makeExport ); - } else { - - // Browser globals case. Just assign the - // result to a property on the global. - origin = root.WebUploader; - root.WebUploader = makeExport(); - root.WebUploader.noConflict = function() { - root.WebUploader = origin; - }; - } -})( window, function( window, define, require ) { - - - /** - * @fileOverview jQuery or Zepto - */ - define('dollar-third',[],function() { - var $ = window.__dollar || window.jQuery || window.Zepto; - - if ( !$ ) { - throw new Error('jQuery or Zepto not found!'); - } - - return $; - }); - /** - * @fileOverview Dom 操作相关 - */ - define('dollar',[ - 'dollar-third' - ], function( _ ) { - return _; - }); - /** - * @fileOverview 使用jQuery的Promise - */ - define('promise-third',[ - 'dollar' - ], function( $ ) { - return { - Deferred: $.Deferred, - when: $.when, - - isPromise: function( anything ) { - return anything && typeof anything.then === 'function'; - } - }; - }); - /** - * @fileOverview Promise/A+ - */ - define('promise',[ - 'promise-third' - ], function( _ ) { - return _; - }); - /** - * @fileOverview 基础类方法。 - */ - - /** - * Web Uploader内部类的详细说明,以下提及的功能类,都可以在`WebUploader`这个变量中访问到。 - * - * As you know, Web Uploader的每个文件都是用过[AMD](https://github.com/amdjs/amdjs-api/wiki/AMD)规范中的`define`组织起来的, 每个Module都会有个module id. - * 默认module id为该文件的路径,而此路径将会转化成名字空间存放在WebUploader中。如: - * - * * module `base`:WebUploader.Base - * * module `file`: WebUploader.File - * * module `lib/dnd`: WebUploader.Lib.Dnd - * * module `runtime/html5/dnd`: WebUploader.Runtime.Html5.Dnd - * - * - * 以下文档中对类的使用可能省略掉了`WebUploader`前缀。 - * @module WebUploader - * @title WebUploader API文档 - */ - define('base',[ - 'dollar', - 'promise' - ], function( $, promise ) { - - var noop = function() {}, - call = Function.call; - - // http://jsperf.com/uncurrythis - // 反科里化 - function uncurryThis( fn ) { - return function() { - return call.apply( fn, arguments ); - }; - } - - function bindFn( fn, context ) { - return function() { - return fn.apply( context, arguments ); - }; - } - - function createObject( proto ) { - var f; - - if ( Object.create ) { - return Object.create( proto ); - } else { - f = function() {}; - f.prototype = proto; - return new f(); - } - } - - - /** - * 基础类,提供一些简单常用的方法。 - * @class Base - */ - return { - - /** - * @property {String} version 当前版本号。 - */ - version: '0.1.5', - - /** - * @property {jQuery|Zepto} $ 引用依赖的jQuery或者Zepto对象。 - */ - $: $, - - Deferred: promise.Deferred, - - isPromise: promise.isPromise, - - when: promise.when, - - /** - * @description 简单的浏览器检查结果。 - * - * * `webkit` webkit版本号,如果浏览器为非webkit内核,此属性为`undefined`。 - * * `chrome` chrome浏览器版本号,如果浏览器为chrome,此属性为`undefined`。 - * * `ie` ie浏览器版本号,如果浏览器为非ie,此属性为`undefined`。**暂不支持ie10+** - * * `firefox` firefox浏览器版本号,如果浏览器为非firefox,此属性为`undefined`。 - * * `safari` safari浏览器版本号,如果浏览器为非safari,此属性为`undefined`。 - * * `opera` opera浏览器版本号,如果浏览器为非opera,此属性为`undefined`。 - * - * @property {Object} [browser] - */ - browser: (function( ua ) { - var ret = {}, - webkit = ua.match( /WebKit\/([\d.]+)/ ), - chrome = ua.match( /Chrome\/([\d.]+)/ ) || - ua.match( /CriOS\/([\d.]+)/ ), - - ie = ua.match( /MSIE\s([\d\.]+)/ ) || - ua.match( /(?:trident)(?:.*rv:([\w.]+))?/i ), - firefox = ua.match( /Firefox\/([\d.]+)/ ), - safari = ua.match( /Safari\/([\d.]+)/ ), - opera = ua.match( /OPR\/([\d.]+)/ ); - - webkit && (ret.webkit = parseFloat( webkit[ 1 ] )); - chrome && (ret.chrome = parseFloat( chrome[ 1 ] )); - ie && (ret.ie = parseFloat( ie[ 1 ] )); - firefox && (ret.firefox = parseFloat( firefox[ 1 ] )); - safari && (ret.safari = parseFloat( safari[ 1 ] )); - opera && (ret.opera = parseFloat( opera[ 1 ] )); - - return ret; - })( navigator.userAgent ), - - /** - * @description 操作系统检查结果。 - * - * * `android` 如果在android浏览器环境下,此值为对应的android版本号,否则为`undefined`。 - * * `ios` 如果在ios浏览器环境下,此值为对应的ios版本号,否则为`undefined`。 - * @property {Object} [os] - */ - os: (function( ua ) { - var ret = {}, - - // osx = !!ua.match( /\(Macintosh\; Intel / ), - android = ua.match( /(?:Android);?[\s\/]+([\d.]+)?/ ), - ios = ua.match( /(?:iPad|iPod|iPhone).*OS\s([\d_]+)/ ); - - // osx && (ret.osx = true); - android && (ret.android = parseFloat( android[ 1 ] )); - ios && (ret.ios = parseFloat( ios[ 1 ].replace( /_/g, '.' ) )); - - return ret; - })( navigator.userAgent ), - - /** - * 实现类与类之间的继承。 - * @method inherits - * @grammar Base.inherits( super ) => child - * @grammar Base.inherits( super, protos ) => child - * @grammar Base.inherits( super, protos, statics ) => child - * @param {Class} super 父类 - * @param {Object | Function} [protos] 子类或者对象。如果对象中包含constructor,子类将是用此属性值。 - * @param {Function} [protos.constructor] 子类构造器,不指定的话将创建个临时的直接执行父类构造器的方法。 - * @param {Object} [statics] 静态属性或方法。 - * @return {Class} 返回子类。 - * @example - * function Person() { - * console.log( 'Super' ); - * } - * Person.prototype.hello = function() { - * console.log( 'hello' ); - * }; - * - * var Manager = Base.inherits( Person, { - * world: function() { - * console.log( 'World' ); - * } - * }); - * - * // 因为没有指定构造器,父类的构造器将会执行。 - * var instance = new Manager(); // => Super - * - * // 继承子父类的方法 - * instance.hello(); // => hello - * instance.world(); // => World - * - * // 子类的__super__属性指向父类 - * console.log( Manager.__super__ === Person ); // => true - */ - inherits: function( Super, protos, staticProtos ) { - var child; - - if ( typeof protos === 'function' ) { - child = protos; - protos = null; - } else if ( protos && protos.hasOwnProperty('constructor') ) { - child = protos.constructor; - } else { - child = function() { - return Super.apply( this, arguments ); - }; - } - - // 复制静态方法 - $.extend( true, child, Super, staticProtos || {} ); - - /* jshint camelcase: false */ - - // 让子类的__super__属性指向父类。 - child.__super__ = Super.prototype; - - // 构建原型,添加原型方法或属性。 - // 暂时用Object.create实现。 - child.prototype = createObject( Super.prototype ); - protos && $.extend( true, child.prototype, protos ); - - return child; - }, - - /** - * 一个不做任何事情的方法。可以用来赋值给默认的callback. - * @method noop - */ - noop: noop, - - /** - * 返回一个新的方法,此方法将已指定的`context`来执行。 - * @grammar Base.bindFn( fn, context ) => Function - * @method bindFn - * @example - * var doSomething = function() { - * console.log( this.name ); - * }, - * obj = { - * name: 'Object Name' - * }, - * aliasFn = Base.bind( doSomething, obj ); - * - * aliasFn(); // => Object Name - * - */ - bindFn: bindFn, - - /** - * 引用Console.log如果存在的话,否则引用一个[空函数noop](#WebUploader:Base.noop)。 - * @grammar Base.log( args... ) => undefined - * @method log - */ - log: (function() { - if ( window.console ) { - return bindFn( console.log, console ); - } - return noop; - })(), - - nextTick: (function() { - - return function( cb ) { - setTimeout( cb, 1 ); - }; - - // @bug 当浏览器不在当前窗口时就停了。 - // var next = window.requestAnimationFrame || - // window.webkitRequestAnimationFrame || - // window.mozRequestAnimationFrame || - // function( cb ) { - // window.setTimeout( cb, 1000 / 60 ); - // }; - - // // fix: Uncaught TypeError: Illegal invocation - // return bindFn( next, window ); - })(), - - /** - * 被[uncurrythis](http://www.2ality.com/2011/11/uncurrying-this.html)的数组slice方法。 - * 将用来将非数组对象转化成数组对象。 - * @grammar Base.slice( target, start[, end] ) => Array - * @method slice - * @example - * function doSomthing() { - * var args = Base.slice( arguments, 1 ); - * console.log( args ); - * } - * - * doSomthing( 'ignored', 'arg2', 'arg3' ); // => Array ["arg2", "arg3"] - */ - slice: uncurryThis( [].slice ), - - /** - * 生成唯一的ID - * @method guid - * @grammar Base.guid() => String - * @grammar Base.guid( prefx ) => String - */ - guid: (function() { - var counter = 0; - - return function( prefix ) { - var guid = (+new Date()).toString( 32 ), - i = 0; - - for ( ; i < 5; i++ ) { - guid += Math.floor( Math.random() * 65535 ).toString( 32 ); - } - - return (prefix || 'wu_') + guid + (counter++).toString( 32 ); - }; - })(), - - /** - * 格式化文件大小, 输出成带单位的字符串 - * @method formatSize - * @grammar Base.formatSize( size ) => String - * @grammar Base.formatSize( size, pointLength ) => String - * @grammar Base.formatSize( size, pointLength, units ) => String - * @param {Number} size 文件大小 - * @param {Number} [pointLength=2] 精确到的小数点数。 - * @param {Array} [units=[ 'B', 'K', 'M', 'G', 'TB' ]] 单位数组。从字节,到千字节,一直往上指定。如果单位数组里面只指定了到了K(千字节),同时文件大小大于M, 此方法的输出将还是显示成多少K. - * @example - * console.log( Base.formatSize( 100 ) ); // => 100B - * console.log( Base.formatSize( 1024 ) ); // => 1.00K - * console.log( Base.formatSize( 1024, 0 ) ); // => 1K - * console.log( Base.formatSize( 1024 * 1024 ) ); // => 1.00M - * console.log( Base.formatSize( 1024 * 1024 * 1024 ) ); // => 1.00G - * console.log( Base.formatSize( 1024 * 1024 * 1024, 0, ['B', 'KB', 'MB'] ) ); // => 1024MB - */ - formatSize: function( size, pointLength, units ) { - var unit; - - units = units || [ 'B', 'K', 'M', 'G', 'TB' ]; - - while ( (unit = units.shift()) && size > 1024 ) { - size = size / 1024; - } - - return (unit === 'B' ? size : size.toFixed( pointLength || 2 )) + - unit; - } - }; - }); - /** - * 事件处理类,可以独立使用,也可以扩展给对象使用。 - * @fileOverview Mediator - */ - define('mediator',[ - 'base' - ], function( Base ) { - var $ = Base.$, - slice = [].slice, - separator = /\s+/, - protos; - - // 根据条件过滤出事件handlers. - function findHandlers( arr, name, callback, context ) { - return $.grep( arr, function( handler ) { - return handler && - (!name || handler.e === name) && - (!callback || handler.cb === callback || - handler.cb._cb === callback) && - (!context || handler.ctx === context); - }); - } - - function eachEvent( events, callback, iterator ) { - // 不支持对象,只支持多个event用空格隔开 - $.each( (events || '').split( separator ), function( _, key ) { - iterator( key, callback ); - }); - } - - function triggerHanders( events, args ) { - var stoped = false, - i = -1, - len = events.length, - handler; - - while ( ++i < len ) { - handler = events[ i ]; - - if ( handler.cb.apply( handler.ctx2, args ) === false ) { - stoped = true; - break; - } - } - - return !stoped; - } - - protos = { - - /** - * 绑定事件。 - * - * `callback`方法在执行时,arguments将会来源于trigger的时候携带的参数。如 - * ```javascript - * var obj = {}; - * - * // 使得obj有事件行为 - * Mediator.installTo( obj ); - * - * obj.on( 'testa', function( arg1, arg2 ) { - * console.log( arg1, arg2 ); // => 'arg1', 'arg2' - * }); - * - * obj.trigger( 'testa', 'arg1', 'arg2' ); - * ``` - * - * 如果`callback`中,某一个方法`return false`了,则后续的其他`callback`都不会被执行到。 - * 切会影响到`trigger`方法的返回值,为`false`。 - * - * `on`还可以用来添加一个特殊事件`all`, 这样所有的事件触发都会响应到。同时此类`callback`中的arguments有一个不同处, - * 就是第一个参数为`type`,记录当前是什么事件在触发。此类`callback`的优先级比脚低,会再正常`callback`执行完后触发。 - * ```javascript - * obj.on( 'all', function( type, arg1, arg2 ) { - * console.log( type, arg1, arg2 ); // => 'testa', 'arg1', 'arg2' - * }); - * ``` - * - * @method on - * @grammar on( name, callback[, context] ) => self - * @param {String} name 事件名,支持多个事件用空格隔开 - * @param {Function} callback 事件处理器 - * @param {Object} [context] 事件处理器的上下文。 - * @return {self} 返回自身,方便链式 - * @chainable - * @class Mediator - */ - on: function( name, callback, context ) { - var me = this, - set; - - if ( !callback ) { - return this; - } - - set = this._events || (this._events = []); - - eachEvent( name, callback, function( name, callback ) { - var handler = { e: name }; - - handler.cb = callback; - handler.ctx = context; - handler.ctx2 = context || me; - handler.id = set.length; - - set.push( handler ); - }); - - return this; - }, - - /** - * 绑定事件,且当handler执行完后,自动解除绑定。 - * @method once - * @grammar once( name, callback[, context] ) => self - * @param {String} name 事件名 - * @param {Function} callback 事件处理器 - * @param {Object} [context] 事件处理器的上下文。 - * @return {self} 返回自身,方便链式 - * @chainable - */ - once: function( name, callback, context ) { - var me = this; - - if ( !callback ) { - return me; - } - - eachEvent( name, callback, function( name, callback ) { - var once = function() { - me.off( name, once ); - return callback.apply( context || me, arguments ); - }; - - once._cb = callback; - me.on( name, once, context ); - }); - - return me; - }, - - /** - * 解除事件绑定 - * @method off - * @grammar off( [name[, callback[, context] ] ] ) => self - * @param {String} [name] 事件名 - * @param {Function} [callback] 事件处理器 - * @param {Object} [context] 事件处理器的上下文。 - * @return {self} 返回自身,方便链式 - * @chainable - */ - off: function( name, cb, ctx ) { - var events = this._events; - - if ( !events ) { - return this; - } - - if ( !name && !cb && !ctx ) { - this._events = []; - return this; - } - - eachEvent( name, cb, function( name, cb ) { - $.each( findHandlers( events, name, cb, ctx ), function() { - delete events[ this.id ]; - }); - }); - - return this; - }, - - /** - * 触发事件 - * @method trigger - * @grammar trigger( name[, args...] ) => self - * @param {String} type 事件名 - * @param {*} [...] 任意参数 - * @return {Boolean} 如果handler中return false了,则返回false, 否则返回true - */ - trigger: function( type ) { - var args, events, allEvents; - - if ( !this._events || !type ) { - return this; - } - - args = slice.call( arguments, 1 ); - events = findHandlers( this._events, type ); - allEvents = findHandlers( this._events, 'all' ); - - return triggerHanders( events, args ) && - triggerHanders( allEvents, arguments ); - } - }; - - /** - * 中介者,它本身是个单例,但可以通过[installTo](#WebUploader:Mediator:installTo)方法,使任何对象具备事件行为。 - * 主要目的是负责模块与模块之间的合作,降低耦合度。 - * - * @class Mediator - */ - return $.extend({ - - /** - * 可以通过这个接口,使任何对象具备事件功能。 - * @method installTo - * @param {Object} obj 需要具备事件行为的对象。 - * @return {Object} 返回obj. - */ - installTo: function( obj ) { - return $.extend( obj, protos ); - } - - }, protos ); - }); - /** - * @fileOverview Uploader上传类 - */ - define('uploader',[ - 'base', - 'mediator' - ], function( Base, Mediator ) { - - var $ = Base.$; - - /** - * 上传入口类。 - * @class Uploader - * @constructor - * @grammar new Uploader( opts ) => Uploader - * @example - * var uploader = WebUploader.Uploader({ - * swf: 'path_of_swf/Uploader.swf', - * - * // 开起分片上传。 - * chunked: true - * }); - */ - function Uploader( opts ) { - this.options = $.extend( true, {}, Uploader.options, opts ); - this._init( this.options ); - } - - // default Options - // widgets中有相应扩展 - Uploader.options = {}; - Mediator.installTo( Uploader.prototype ); - - // 批量添加纯命令式方法。 - $.each({ - upload: 'start-upload', - stop: 'stop-upload', - getFile: 'get-file', - getFiles: 'get-files', - addFile: 'add-file', - addFiles: 'add-file', - sort: 'sort-files', - removeFile: 'remove-file', - cancelFile: 'cancel-file', - skipFile: 'skip-file', - retry: 'retry', - isInProgress: 'is-in-progress', - makeThumb: 'make-thumb', - md5File: 'md5-file', - getDimension: 'get-dimension', - addButton: 'add-btn', - predictRuntimeType: 'predict-runtime-type', - refresh: 'refresh', - disable: 'disable', - enable: 'enable', - reset: 'reset' - }, function( fn, command ) { - Uploader.prototype[ fn ] = function() { - return this.request( command, arguments ); - }; - }); - - $.extend( Uploader.prototype, { - state: 'pending', - - _init: function( opts ) { - var me = this; - - me.request( 'init', opts, function() { - me.state = 'ready'; - me.trigger('ready'); - }); - }, - - /** - * 获取或者设置Uploader配置项。 - * @method option - * @grammar option( key ) => * - * @grammar option( key, val ) => self - * @example - * - * // 初始状态图片上传前不会压缩 - * var uploader = new WebUploader.Uploader({ - * compress: null; - * }); - * - * // 修改后图片上传前,尝试将图片压缩到1600 * 1600 - * uploader.option( 'compress', { - * width: 1600, - * height: 1600 - * }); - */ - option: function( key, val ) { - var opts = this.options; - - // setter - if ( arguments.length > 1 ) { - - if ( $.isPlainObject( val ) && - $.isPlainObject( opts[ key ] ) ) { - $.extend( opts[ key ], val ); - } else { - opts[ key ] = val; - } - - } else { // getter - return key ? opts[ key ] : opts; - } - }, - - /** - * 获取文件统计信息。返回一个包含一下信息的对象。 - * * `successNum` 上传成功的文件数 - * * `progressNum` 上传中的文件数 - * * `cancelNum` 被删除的文件数 - * * `invalidNum` 无效的文件数 - * * `uploadFailNum` 上传失败的文件数 - * * `queueNum` 还在队列中的文件数 - * * `interruptNum` 被暂停的文件数 - * @method getStats - * @grammar getStats() => Object - */ - getStats: function() { - // return this._mgr.getStats.apply( this._mgr, arguments ); - var stats = this.request('get-stats'); - - return stats ? { - successNum: stats.numOfSuccess, - progressNum: stats.numOfProgress, - - // who care? - // queueFailNum: 0, - cancelNum: stats.numOfCancel, - invalidNum: stats.numOfInvalid, - uploadFailNum: stats.numOfUploadFailed, - queueNum: stats.numOfQueue, - interruptNum: stats.numofInterrupt - } : {}; - }, - - // 需要重写此方法来来支持opts.onEvent和instance.onEvent的处理器 - trigger: function( type/*, args...*/ ) { - var args = [].slice.call( arguments, 1 ), - opts = this.options, - name = 'on' + type.substring( 0, 1 ).toUpperCase() + - type.substring( 1 ); - - if ( - // 调用通过on方法注册的handler. - Mediator.trigger.apply( this, arguments ) === false || - - // 调用opts.onEvent - $.isFunction( opts[ name ] ) && - opts[ name ].apply( this, args ) === false || - - // 调用this.onEvent - $.isFunction( this[ name ] ) && - this[ name ].apply( this, args ) === false || - - // 广播所有uploader的事件。 - Mediator.trigger.apply( Mediator, - [ this, type ].concat( args ) ) === false ) { - - return false; - } - - return true; - }, - - /** - * 销毁 webuploader 实例 - * @method destroy - * @grammar destroy() => undefined - */ - destroy: function() { - this.request( 'destroy', arguments ); - this.off(); - }, - - // widgets/widget.js将补充此方法的详细文档。 - request: Base.noop - }); - - /** - * 创建Uploader实例,等同于new Uploader( opts ); - * @method create - * @class Base - * @static - * @grammar Base.create( opts ) => Uploader - */ - Base.create = Uploader.create = function( opts ) { - return new Uploader( opts ); - }; - - // 暴露Uploader,可以通过它来扩展业务逻辑。 - Base.Uploader = Uploader; - - return Uploader; - }); - /** - * @fileOverview Runtime管理器,负责Runtime的选择, 连接 - */ - define('runtime/runtime',[ - 'base', - 'mediator' - ], function( Base, Mediator ) { - - var $ = Base.$, - factories = {}, - - // 获取对象的第一个key - getFirstKey = function( obj ) { - for ( var key in obj ) { - if ( obj.hasOwnProperty( key ) ) { - return key; - } - } - return null; - }; - - // 接口类。 - function Runtime( options ) { - this.options = $.extend({ - container: document.body - }, options ); - this.uid = Base.guid('rt_'); - } - - $.extend( Runtime.prototype, { - - getContainer: function() { - var opts = this.options, - parent, container; - - if ( this._container ) { - return this._container; - } - - parent = $( opts.container || document.body ); - container = $( document.createElement('div') ); - - container.attr( 'id', 'rt_' + this.uid ); - container.css({ - position: 'absolute', - top: '0px', - left: '0px', - width: '1px', - height: '1px', - overflow: 'hidden' - }); - - parent.append( container ); - parent.addClass('webuploader-container'); - this._container = container; - this._parent = parent; - return container; - }, - - init: Base.noop, - exec: Base.noop, - - destroy: function() { - this._container && this._container.remove(); - this._parent && this._parent.removeClass('webuploader-container'); - this.off(); - } - }); - - Runtime.orders = 'html5,flash'; - - - /** - * 添加Runtime实现。 - * @param {String} type 类型 - * @param {Runtime} factory 具体Runtime实现。 - */ - Runtime.addRuntime = function( type, factory ) { - factories[ type ] = factory; - }; - - Runtime.hasRuntime = function( type ) { - return !!(type ? factories[ type ] : getFirstKey( factories )); - }; - - Runtime.create = function( opts, orders ) { - var type, runtime; - - orders = orders || Runtime.orders; - $.each( orders.split( /\s*,\s*/g ), function() { - if ( factories[ this ] ) { - type = this; - return false; - } - }); - - type = type || getFirstKey( factories ); - - if ( !type ) { - throw new Error('Runtime Error'); - } - - runtime = new factories[ type ]( opts ); - return runtime; - }; - - Mediator.installTo( Runtime.prototype ); - return Runtime; - }); - - /** - * @fileOverview Runtime管理器,负责Runtime的选择, 连接 - */ - define('runtime/client',[ - 'base', - 'mediator', - 'runtime/runtime' - ], function( Base, Mediator, Runtime ) { - - var cache; - - cache = (function() { - var obj = {}; - - return { - add: function( runtime ) { - obj[ runtime.uid ] = runtime; - }, - - get: function( ruid, standalone ) { - var i; - - if ( ruid ) { - return obj[ ruid ]; - } - - for ( i in obj ) { - // 有些类型不能重用,比如filepicker. - if ( standalone && obj[ i ].__standalone ) { - continue; - } - - return obj[ i ]; - } - - return null; - }, - - remove: function( runtime ) { - delete obj[ runtime.uid ]; - } - }; - })(); - - function RuntimeClient( component, standalone ) { - var deferred = Base.Deferred(), - runtime; - - this.uid = Base.guid('client_'); - - // 允许runtime没有初始化之前,注册一些方法在初始化后执行。 - this.runtimeReady = function( cb ) { - return deferred.done( cb ); - }; - - this.connectRuntime = function( opts, cb ) { - - // already connected. - if ( runtime ) { - throw new Error('already connected!'); - } - - deferred.done( cb ); - - if ( typeof opts === 'string' && cache.get( opts ) ) { - runtime = cache.get( opts ); - } - - // 像filePicker只能独立存在,不能公用。 - runtime = runtime || cache.get( null, standalone ); - - // 需要创建 - if ( !runtime ) { - runtime = Runtime.create( opts, opts.runtimeOrder ); - runtime.__promise = deferred.promise(); - runtime.once( 'ready', deferred.resolve ); - runtime.init(); - cache.add( runtime ); - runtime.__client = 1; - } else { - // 来自cache - Base.$.extend( runtime.options, opts ); - runtime.__promise.then( deferred.resolve ); - runtime.__client++; - } - - standalone && (runtime.__standalone = standalone); - return runtime; - }; - - this.getRuntime = function() { - return runtime; - }; - - this.disconnectRuntime = function() { - if ( !runtime ) { - return; - } - - runtime.__client--; - - if ( runtime.__client <= 0 ) { - cache.remove( runtime ); - delete runtime.__promise; - runtime.destroy(); - } - - runtime = null; - }; - - this.exec = function() { - if ( !runtime ) { - return; - } - - var args = Base.slice( arguments ); - component && args.unshift( component ); - - return runtime.exec.apply( this, args ); - }; - - this.getRuid = function() { - return runtime && runtime.uid; - }; - - this.destroy = (function( destroy ) { - return function() { - destroy && destroy.apply( this, arguments ); - this.trigger('destroy'); - this.off(); - this.exec('destroy'); - this.disconnectRuntime(); - }; - })( this.destroy ); - } - - Mediator.installTo( RuntimeClient.prototype ); - return RuntimeClient; - }); - /** - * @fileOverview 错误信息 - */ - define('lib/dnd',[ - 'base', - 'mediator', - 'runtime/client' - ], function( Base, Mediator, RuntimeClent ) { - - var $ = Base.$; - - function DragAndDrop( opts ) { - opts = this.options = $.extend({}, DragAndDrop.options, opts ); - - opts.container = $( opts.container ); - - if ( !opts.container.length ) { - return; - } - - RuntimeClent.call( this, 'DragAndDrop' ); - } - - DragAndDrop.options = { - accept: null, - disableGlobalDnd: false - }; - - Base.inherits( RuntimeClent, { - constructor: DragAndDrop, - - init: function() { - var me = this; - - me.connectRuntime( me.options, function() { - me.exec('init'); - me.trigger('ready'); - }); - } - }); - - Mediator.installTo( DragAndDrop.prototype ); - - return DragAndDrop; - }); - /** - * @fileOverview 组件基类。 - */ - define('widgets/widget',[ - 'base', - 'uploader' - ], function( Base, Uploader ) { - - var $ = Base.$, - _init = Uploader.prototype._init, - _destroy = Uploader.prototype.destroy, - IGNORE = {}, - widgetClass = []; - - function isArrayLike( obj ) { - if ( !obj ) { - return false; - } - - var length = obj.length, - type = $.type( obj ); - - if ( obj.nodeType === 1 && length ) { - return true; - } - - return type === 'array' || type !== 'function' && type !== 'string' && - (length === 0 || typeof length === 'number' && length > 0 && - (length - 1) in obj); - } - - function Widget( uploader ) { - this.owner = uploader; - this.options = uploader.options; - } - - $.extend( Widget.prototype, { - - init: Base.noop, - - // 类Backbone的事件监听声明,监听uploader实例上的事件 - // widget直接无法监听事件,事件只能通过uploader来传递 - invoke: function( apiName, args ) { - - /* - { - 'make-thumb': 'makeThumb' - } - */ - var map = this.responseMap; - - // 如果无API响应声明则忽略 - if ( !map || !(apiName in map) || !(map[ apiName ] in this) || - !$.isFunction( this[ map[ apiName ] ] ) ) { - - return IGNORE; - } - - return this[ map[ apiName ] ].apply( this, args ); - - }, - - /** - * 发送命令。当传入`callback`或者`handler`中返回`promise`时。返回一个当所有`handler`中的promise都完成后完成的新`promise`。 - * @method request - * @grammar request( command, args ) => * | Promise - * @grammar request( command, args, callback ) => Promise - * @for Uploader - */ - request: function() { - return this.owner.request.apply( this.owner, arguments ); - } - }); - - // 扩展Uploader. - $.extend( Uploader.prototype, { - - /** - * @property {String | Array} [disableWidgets=undefined] - * @namespace options - * @for Uploader - * @description 默认所有 Uploader.register 了的 widget 都会被加载,如果禁用某一部分,请通过此 option 指定黑名单。 - */ - - // 覆写_init用来初始化widgets - _init: function() { - var me = this, - widgets = me._widgets = [], - deactives = me.options.disableWidgets || ''; - - $.each( widgetClass, function( _, klass ) { - (!deactives || !~deactives.indexOf( klass._name )) && - widgets.push( new klass( me ) ); - }); - - return _init.apply( me, arguments ); - }, - - request: function( apiName, args, callback ) { - var i = 0, - widgets = this._widgets, - len = widgets && widgets.length, - rlts = [], - dfds = [], - widget, rlt, promise, key; - - args = isArrayLike( args ) ? args : [ args ]; - - for ( ; i < len; i++ ) { - widget = widgets[ i ]; - rlt = widget.invoke( apiName, args ); - - if ( rlt !== IGNORE ) { - - // Deferred对象 - if ( Base.isPromise( rlt ) ) { - dfds.push( rlt ); - } else { - rlts.push( rlt ); - } - } - } - - // 如果有callback,则用异步方式。 - if ( callback || dfds.length ) { - promise = Base.when.apply( Base, dfds ); - key = promise.pipe ? 'pipe' : 'then'; - - // 很重要不能删除。删除了会死循环。 - // 保证执行顺序。让callback总是在下一个 tick 中执行。 - return promise[ key ](function() { - var deferred = Base.Deferred(), - args = arguments; - - if ( args.length === 1 ) { - args = args[ 0 ]; - } - - setTimeout(function() { - deferred.resolve( args ); - }, 1 ); - - return deferred.promise(); - })[ callback ? key : 'done' ]( callback || Base.noop ); - } else { - return rlts[ 0 ]; - } - }, - - destroy: function() { - _destroy.apply( this, arguments ); - this._widgets = null; - } - }); - - /** - * 添加组件 - * @grammar Uploader.register(proto); - * @grammar Uploader.register(map, proto); - * @param {object} responseMap API 名称与函数实现的映射 - * @param {object} proto 组件原型,构造函数通过 constructor 属性定义 - * @method Uploader.register - * @for Uploader - * @example - * Uploader.register({ - * 'make-thumb': 'makeThumb' - * }, { - * init: function( options ) {}, - * makeThumb: function() {} - * }); - * - * Uploader.register({ - * 'make-thumb': function() { - * - * } - * }); - */ - Uploader.register = Widget.register = function( responseMap, widgetProto ) { - var map = { init: 'init', destroy: 'destroy', name: 'anonymous' }, - klass; - - if ( arguments.length === 1 ) { - widgetProto = responseMap; - - // 自动生成 map 表。 - $.each(widgetProto, function(key) { - if ( key[0] === '_' || key === 'name' ) { - key === 'name' && (map.name = widgetProto.name); - return; - } - - map[key.replace(/[A-Z]/g, '-$&').toLowerCase()] = key; - }); - - } else { - map = $.extend( map, responseMap ); - } - - widgetProto.responseMap = map; - klass = Base.inherits( Widget, widgetProto ); - klass._name = map.name; - widgetClass.push( klass ); - - return klass; - }; - - /** - * 删除插件,只有在注册时指定了名字的才能被删除。 - * @grammar Uploader.unRegister(name); - * @param {string} name 组件名字 - * @method Uploader.unRegister - * @for Uploader - * @example - * - * Uploader.register({ - * name: 'custom', - * - * 'make-thumb': function() { - * - * } - * }); - * - * Uploader.unRegister('custom'); - */ - Uploader.unRegister = Widget.unRegister = function( name ) { - if ( !name || name === 'anonymous' ) { - return; - } - - // 删除指定的插件。 - for ( var i = widgetClass.length; i--; ) { - if ( widgetClass[i]._name === name ) { - widgetClass.splice(i, 1) - } - } - }; - - return Widget; - }); - /** - * @fileOverview DragAndDrop Widget。 - */ - define('widgets/filednd',[ - 'base', - 'uploader', - 'lib/dnd', - 'widgets/widget' - ], function( Base, Uploader, Dnd ) { - var $ = Base.$; - - Uploader.options.dnd = ''; - - /** - * @property {Selector} [dnd=undefined] 指定Drag And Drop拖拽的容器,如果不指定,则不启动。 - * @namespace options - * @for Uploader - */ - - /** - * @property {Selector} [disableGlobalDnd=false] 是否禁掉整个页面的拖拽功能,如果不禁用,图片拖进来的时候会默认被浏览器打开。 - * @namespace options - * @for Uploader - */ - - /** - * @event dndAccept - * @param {DataTransferItemList} items DataTransferItem - * @description 阻止此事件可以拒绝某些类型的文件拖入进来。目前只有 chrome 提供这样的 API,且只能通过 mime-type 验证。 - * @for Uploader - */ - return Uploader.register({ - name: 'dnd', - - init: function( opts ) { - - if ( !opts.dnd || - this.request('predict-runtime-type') !== 'html5' ) { - return; - } - - var me = this, - deferred = Base.Deferred(), - options = $.extend({}, { - disableGlobalDnd: opts.disableGlobalDnd, - container: opts.dnd, - accept: opts.accept - }), - dnd; - - this.dnd = dnd = new Dnd( options ); - - dnd.once( 'ready', deferred.resolve ); - dnd.on( 'drop', function( files ) { - me.request( 'add-file', [ files ]); - }); - - // 检测文件是否全部允许添加。 - dnd.on( 'accept', function( items ) { - return me.owner.trigger( 'dndAccept', items ); - }); - - dnd.init(); - - return deferred.promise(); - }, - - destroy: function() { - this.dnd && this.dnd.destroy(); - } - }); - }); - - /** - * @fileOverview 错误信息 - */ - define('lib/filepaste',[ - 'base', - 'mediator', - 'runtime/client' - ], function( Base, Mediator, RuntimeClent ) { - - var $ = Base.$; - - function FilePaste( opts ) { - opts = this.options = $.extend({}, opts ); - opts.container = $( opts.container || document.body ); - RuntimeClent.call( this, 'FilePaste' ); - } - - Base.inherits( RuntimeClent, { - constructor: FilePaste, - - init: function() { - var me = this; - - me.connectRuntime( me.options, function() { - me.exec('init'); - me.trigger('ready'); - }); - } - }); - - Mediator.installTo( FilePaste.prototype ); - - return FilePaste; - }); - /** - * @fileOverview 组件基类。 - */ - define('widgets/filepaste',[ - 'base', - 'uploader', - 'lib/filepaste', - 'widgets/widget' - ], function( Base, Uploader, FilePaste ) { - var $ = Base.$; - - /** - * @property {Selector} [paste=undefined] 指定监听paste事件的容器,如果不指定,不启用此功能。此功能为通过粘贴来添加截屏的图片。建议设置为`document.body`. - * @namespace options - * @for Uploader - */ - return Uploader.register({ - name: 'paste', - - init: function( opts ) { - - if ( !opts.paste || - this.request('predict-runtime-type') !== 'html5' ) { - return; - } - - var me = this, - deferred = Base.Deferred(), - options = $.extend({}, { - container: opts.paste, - accept: opts.accept - }), - paste; - - this.paste = paste = new FilePaste( options ); - - paste.once( 'ready', deferred.resolve ); - paste.on( 'paste', function( files ) { - me.owner.request( 'add-file', [ files ]); - }); - paste.init(); - - return deferred.promise(); - }, - - destroy: function() { - this.paste && this.paste.destroy(); - } - }); - }); - /** - * @fileOverview Blob - */ - define('lib/blob',[ - 'base', - 'runtime/client' - ], function( Base, RuntimeClient ) { - - function Blob( ruid, source ) { - var me = this; - - me.source = source; - me.ruid = ruid; - this.size = source.size || 0; - - // 如果没有指定 mimetype, 但是知道文件后缀。 - if ( !source.type && this.ext && - ~'jpg,jpeg,png,gif,bmp'.indexOf( this.ext ) ) { - this.type = 'image/' + (this.ext === 'jpg' ? 'jpeg' : this.ext); - } else { - this.type = source.type || 'application/octet-stream'; - } - - RuntimeClient.call( me, 'Blob' ); - this.uid = source.uid || this.uid; - - if ( ruid ) { - me.connectRuntime( ruid ); - } - } - - Base.inherits( RuntimeClient, { - constructor: Blob, - - slice: function( start, end ) { - return this.exec( 'slice', start, end ); - }, - - getSource: function() { - return this.source; - } - }); - - return Blob; - }); - /** - * 为了统一化Flash的File和HTML5的File而存在。 - * 以至于要调用Flash里面的File,也可以像调用HTML5版本的File一下。 - * @fileOverview File - */ - define('lib/file',[ - 'base', - 'lib/blob' - ], function( Base, Blob ) { - - var uid = 1, - rExt = /\.([^.]+)$/; - - function File( ruid, file ) { - var ext; - - this.name = file.name || ('untitled' + uid++); - ext = rExt.exec( file.name ) ? RegExp.$1.toLowerCase() : ''; - - // todo 支持其他类型文件的转换。 - // 如果有 mimetype, 但是文件名里面没有找出后缀规律 - if ( !ext && file.type ) { - ext = /\/(jpg|jpeg|png|gif|bmp)$/i.exec( file.type ) ? - RegExp.$1.toLowerCase() : ''; - this.name += '.' + ext; - } - - this.ext = ext; - this.lastModifiedDate = file.lastModifiedDate || - (new Date()).toLocaleString(); - - Blob.apply( this, arguments ); - } - - return Base.inherits( Blob, File ); - }); - - /** - * @fileOverview 错误信息 - */ - define('lib/filepicker',[ - 'base', - 'runtime/client', - 'lib/file' - ], function( Base, RuntimeClent, File ) { - - var $ = Base.$; - - function FilePicker( opts ) { - opts = this.options = $.extend({}, FilePicker.options, opts ); - - opts.container = $( opts.id ); - - if ( !opts.container.length ) { - throw new Error('按钮指定错误'); - } - - opts.innerHTML = opts.innerHTML || opts.label || - opts.container.html() || ''; - - opts.button = $( opts.button || document.createElement('div') ); - opts.button.html( opts.innerHTML ); - opts.container.html( opts.button ); - - RuntimeClent.call( this, 'FilePicker', true ); - } - - FilePicker.options = { - button: null, - container: null, - label: null, - innerHTML: null, - multiple: true, - accept: null, - name: 'file' - }; - - Base.inherits( RuntimeClent, { - constructor: FilePicker, - - init: function() { - var me = this, - opts = me.options, - button = opts.button; - - button.addClass('webuploader-pick'); - - me.on( 'all', function( type ) { - var files; - - switch ( type ) { - case 'mouseenter': - button.addClass('webuploader-pick-hover'); - break; - - case 'mouseleave': - button.removeClass('webuploader-pick-hover'); - break; - - case 'change': - files = me.exec('getFiles'); - me.trigger( 'select', $.map( files, function( file ) { - file = new File( me.getRuid(), file ); - - // 记录来源。 - file._refer = opts.container; - return file; - }), opts.container ); - break; - } - }); - - me.connectRuntime( opts, function() { - me.refresh(); - me.exec( 'init', opts ); - me.trigger('ready'); - }); - - this._resizeHandler = Base.bindFn( this.refresh, this ); - $( window ).on( 'resize', this._resizeHandler ); - }, - - refresh: function() { - var shimContainer = this.getRuntime().getContainer(), - button = this.options.button, - width = button.outerWidth ? - button.outerWidth() : button.width(), - - height = button.outerHeight ? - button.outerHeight() : button.height(), - - pos = button.offset(); - - width && height && shimContainer.css({ - bottom: 'auto', - right: 'auto', - width: width + 'px', - height: height + 'px' - }).offset( pos ); - }, - - enable: function() { - var btn = this.options.button; - - btn.removeClass('webuploader-pick-disable'); - this.refresh(); - }, - - disable: function() { - var btn = this.options.button; - - this.getRuntime().getContainer().css({ - top: '-99999px' - }); - - btn.addClass('webuploader-pick-disable'); - }, - - destroy: function() { - var btn = this.options.button; - $( window ).off( 'resize', this._resizeHandler ); - btn.removeClass('webuploader-pick-disable webuploader-pick-hover ' + - 'webuploader-pick'); - } - }); - - return FilePicker; - }); - - /** - * @fileOverview 文件选择相关 - */ - define('widgets/filepicker',[ - 'base', - 'uploader', - 'lib/filepicker', - 'widgets/widget' - ], function( Base, Uploader, FilePicker ) { - var $ = Base.$; - - $.extend( Uploader.options, { - - /** - * @property {Selector | Object} [pick=undefined] - * @namespace options - * @for Uploader - * @description 指定选择文件的按钮容器,不指定则不创建按钮。 - * - * * `id` {Seletor|dom} 指定选择文件的按钮容器,不指定则不创建按钮。**注意** 这里虽然写的是 id, 但是不是只支持 id, 还支持 class, 或者 dom 节点。 - * * `label` {String} 请采用 `innerHTML` 代替 - * * `innerHTML` {String} 指定按钮文字。不指定时优先从指定的容器中看是否自带文字。 - * * `multiple` {Boolean} 是否开起同时选择多个文件能力。 - */ - pick: null, - - /** - * @property {Arroy} [accept=null] - * @namespace options - * @for Uploader - * @description 指定接受哪些类型的文件。 由于目前还有ext转mimeType表,所以这里需要分开指定。 - * - * * `title` {String} 文字描述 - * * `extensions` {String} 允许的文件后缀,不带点,多个用逗号分割。 - * * `mimeTypes` {String} 多个用逗号分割。 - * - * 如: - * - * ``` - * { - * title: 'Images', - * extensions: 'gif,jpg,jpeg,bmp,png', - * mimeTypes: 'image/*' - * } - * ``` - */ - accept: null/*{ - title: 'Images', - extensions: 'gif,jpg,jpeg,bmp,png', - mimeTypes: 'image/*' - }*/ - }); - - return Uploader.register({ - name: 'picker', - - init: function( opts ) { - this.pickers = []; - return opts.pick && this.addBtn( opts.pick ); - }, - - refresh: function() { - $.each( this.pickers, function() { - this.refresh(); - }); - }, - - /** - * @method addButton - * @for Uploader - * @grammar addButton( pick ) => Promise - * @description - * 添加文件选择按钮,如果一个按钮不够,需要调用此方法来添加。参数跟[options.pick](#WebUploader:Uploader:options)一致。 - * @example - * uploader.addButton({ - * id: '#btnContainer', - * innerHTML: '选择文件' - * }); - */ - addBtn: function( pick ) { - var me = this, - opts = me.options, - accept = opts.accept, - promises = []; - - if ( !pick ) { - return; - } - - $.isPlainObject( pick ) || (pick = { - id: pick - }); - - $( pick.id ).each(function() { - var options, picker, deferred; - - deferred = Base.Deferred(); - - options = $.extend({}, pick, { - accept: $.isPlainObject( accept ) ? [ accept ] : accept, - swf: opts.swf, - runtimeOrder: opts.runtimeOrder, - id: this - }); - - picker = new FilePicker( options ); - - picker.once( 'ready', deferred.resolve ); - picker.on( 'select', function( files ) { - me.owner.request( 'add-file', [ files ]); - }); - picker.init(); - - me.pickers.push( picker ); - - promises.push( deferred.promise() ); - }); - - return Base.when.apply( Base, promises ); - }, - - disable: function() { - $.each( this.pickers, function() { - this.disable(); - }); - }, - - enable: function() { - $.each( this.pickers, function() { - this.enable(); - }); - }, - - destroy: function() { - $.each( this.pickers, function() { - this.destroy(); - }); - this.pickers = null; - } - }); - }); - /** - * @fileOverview Image - */ - define('lib/image',[ - 'base', - 'runtime/client', - 'lib/blob' - ], function( Base, RuntimeClient, Blob ) { - var $ = Base.$; - - // 构造器。 - function Image( opts ) { - this.options = $.extend({}, Image.options, opts ); - RuntimeClient.call( this, 'Image' ); - - this.on( 'load', function() { - this._info = this.exec('info'); - this._meta = this.exec('meta'); - }); - } - - // 默认选项。 - Image.options = { - - // 默认的图片处理质量 - quality: 90, - - // 是否裁剪 - crop: false, - - // 是否保留头部信息 - preserveHeaders: false, - - // 是否允许放大。 - allowMagnify: false - }; - - // 继承RuntimeClient. - Base.inherits( RuntimeClient, { - constructor: Image, - - info: function( val ) { - - // setter - if ( val ) { - this._info = val; - return this; - } - - // getter - return this._info; - }, - - meta: function( val ) { - - // setter - if ( val ) { - this._meta = val; - return this; - } - - // getter - return this._meta; - }, - - loadFromBlob: function( blob ) { - var me = this, - ruid = blob.getRuid(); - - this.connectRuntime( ruid, function() { - me.exec( 'init', me.options ); - me.exec( 'loadFromBlob', blob ); - }); - }, - - resize: function() { - var args = Base.slice( arguments ); - return this.exec.apply( this, [ 'resize' ].concat( args ) ); - }, - - crop: function() { - var args = Base.slice( arguments ); - return this.exec.apply( this, [ 'crop' ].concat( args ) ); - }, - - getAsDataUrl: function( type ) { - return this.exec( 'getAsDataUrl', type ); - }, - - getAsBlob: function( type ) { - var blob = this.exec( 'getAsBlob', type ); - - return new Blob( this.getRuid(), blob ); - } - }); - - return Image; - }); - /** - * @fileOverview 图片操作, 负责预览图片和上传前压缩图片 - */ - define('widgets/image',[ - 'base', - 'uploader', - 'lib/image', - 'widgets/widget' - ], function( Base, Uploader, Image ) { - - var $ = Base.$, - throttle; - - // 根据要处理的文件大小来节流,一次不能处理太多,会卡。 - throttle = (function( max ) { - var occupied = 0, - waiting = [], - tick = function() { - var item; - - while ( waiting.length && occupied < max ) { - item = waiting.shift(); - occupied += item[ 0 ]; - item[ 1 ](); - } - }; - - return function( emiter, size, cb ) { - waiting.push([ size, cb ]); - emiter.once( 'destroy', function() { - occupied -= size; - setTimeout( tick, 1 ); - }); - setTimeout( tick, 1 ); - }; - })( 5 * 1024 * 1024 ); - - $.extend( Uploader.options, { - - /** - * @property {Object} [thumb] - * @namespace options - * @for Uploader - * @description 配置生成缩略图的选项。 - * - * 默认为: - * - * ```javascript - * { - * width: 110, - * height: 110, - * - * // 图片质量,只有type为`image/jpeg`的时候才有效。 - * quality: 70, - * - * // 是否允许放大,如果想要生成小图的时候不失真,此选项应该设置为false. - * allowMagnify: true, - * - * // 是否允许裁剪。 - * crop: true, - * - * // 为空的话则保留原有图片格式。 - * // 否则强制转换成指定的类型。 - * type: 'image/jpeg' - * } - * ``` - */ - thumb: { - width: 110, - height: 110, - quality: 70, - allowMagnify: true, - crop: true, - preserveHeaders: false, - - // 为空的话则保留原有图片格式。 - // 否则强制转换成指定的类型。 - // IE 8下面 base64 大小不能超过 32K 否则预览失败,而非 jpeg 编码的图片很可 - // 能会超过 32k, 所以这里设置成预览的时候都是 image/jpeg - type: 'image/jpeg' - }, - - /** - * @property {Object} [compress] - * @namespace options - * @for Uploader - * @description 配置压缩的图片的选项。如果此选项为`false`, 则图片在上传前不进行压缩。 - * - * 默认为: - * - * ```javascript - * { - * width: 1600, - * height: 1600, - * - * // 图片质量,只有type为`image/jpeg`的时候才有效。 - * quality: 90, - * - * // 是否允许放大,如果想要生成小图的时候不失真,此选项应该设置为false. - * allowMagnify: false, - * - * // 是否允许裁剪。 - * crop: false, - * - * // 是否保留头部meta信息。 - * preserveHeaders: true, - * - * // 如果发现压缩后文件大小比原来还大,则使用原来图片 - * // 此属性可能会影响图片自动纠正功能 - * noCompressIfLarger: false, - * - * // 单位字节,如果图片大小小于此值,不会采用压缩。 - * compressSize: 0 - * } - * ``` - */ - compress: { - width: 1600, - height: 1600, - quality: 90, - allowMagnify: false, - crop: false, - preserveHeaders: true - } - }); - - return Uploader.register({ - - name: 'image', - - - /** - * 生成缩略图,此过程为异步,所以需要传入`callback`。 - * 通常情况在图片加入队里后调用此方法来生成预览图以增强交互效果。 - * - * 当 width 或者 height 的值介于 0 - 1 时,被当成百分比使用。 - * - * `callback`中可以接收到两个参数。 - * * 第一个为error,如果生成缩略图有错误,此error将为真。 - * * 第二个为ret, 缩略图的Data URL值。 - * - * **注意** - * Date URL在IE6/7中不支持,所以不用调用此方法了,直接显示一张暂不支持预览图片好了。 - * 也可以借助服务端,将 base64 数据传给服务端,生成一个临时文件供预览。 - * - * @method makeThumb - * @grammar makeThumb( file, callback ) => undefined - * @grammar makeThumb( file, callback, width, height ) => undefined - * @for Uploader - * @example - * - * uploader.on( 'fileQueued', function( file ) { - * var $li = ...; - * - * uploader.makeThumb( file, function( error, ret ) { - * if ( error ) { - * $li.text('预览错误'); - * } else { - * $li.append(''); - * } - * }); - * - * }); - */ - makeThumb: function( file, cb, width, height ) { - var opts, image; - - file = this.request( 'get-file', file ); - - // 只预览图片格式。 - if ( !file.type.match( /^image/ ) ) { - cb( true ); - return; - } - - opts = $.extend({}, this.options.thumb ); - - // 如果传入的是object. - if ( $.isPlainObject( width ) ) { - opts = $.extend( opts, width ); - width = null; - } - - width = width || opts.width; - height = height || opts.height; - - image = new Image( opts ); - - image.once( 'load', function() { - file._info = file._info || image.info(); - file._meta = file._meta || image.meta(); - - // 如果 width 的值介于 0 - 1 - // 说明设置的是百分比。 - if ( width <= 1 && width > 0 ) { - width = file._info.width * width; - } - - // 同样的规则应用于 height - if ( height <= 1 && height > 0 ) { - height = file._info.height * height; - } - - image.resize( width, height ); - }); - - // 当 resize 完后 - image.once( 'complete', function() { - cb( false, image.getAsDataUrl( opts.type ) ); - image.destroy(); - }); - - image.once( 'error', function( reason ) { - cb( reason || true ); - image.destroy(); - }); - - throttle( image, file.source.size, function() { - file._info && image.info( file._info ); - file._meta && image.meta( file._meta ); - image.loadFromBlob( file.source ); - }); - }, - - beforeSendFile: function( file ) { - var opts = this.options.compress || this.options.resize, - compressSize = opts && opts.compressSize || 0, - noCompressIfLarger = opts && opts.noCompressIfLarger || false, - image, deferred; - - file = this.request( 'get-file', file ); - - // 只压缩 jpeg 图片格式。 - // gif 可能会丢失针 - // bmp png 基本上尺寸都不大,且压缩比比较小。 - if ( !opts || !~'image/jpeg,image/jpg'.indexOf( file.type ) || - file.size < compressSize || - file._compressed ) { - return; - } - - opts = $.extend({}, opts ); - deferred = Base.Deferred(); - - image = new Image( opts ); - - deferred.always(function() { - image.destroy(); - image = null; - }); - image.once( 'error', deferred.reject ); - image.once( 'load', function() { - var width = opts.width, - height = opts.height; - - file._info = file._info || image.info(); - file._meta = file._meta || image.meta(); - - // 如果 width 的值介于 0 - 1 - // 说明设置的是百分比。 - if ( width <= 1 && width > 0 ) { - width = file._info.width * width; - } - - // 同样的规则应用于 height - if ( height <= 1 && height > 0 ) { - height = file._info.height * height; - } - - image.resize( width, height ); - }); - - image.once( 'complete', function() { - var blob, size; - - // 移动端 UC / qq 浏览器的无图模式下 - // ctx.getImageData 处理大图的时候会报 Exception - // INDEX_SIZE_ERR: DOM Exception 1 - try { - blob = image.getAsBlob( opts.type ); - - size = file.size; - - // 如果压缩后,比原来还大则不用压缩后的。 - if ( !noCompressIfLarger || blob.size < size ) { - // file.source.destroy && file.source.destroy(); - file.source = blob; - file.size = blob.size; - - file.trigger( 'resize', blob.size, size ); - } - - // 标记,避免重复压缩。 - file._compressed = true; - deferred.resolve(); - } catch ( e ) { - // 出错了直接继续,让其上传原始图片 - deferred.resolve(); - } - }); - - file._info && image.info( file._info ); - file._meta && image.meta( file._meta ); - - image.loadFromBlob( file.source ); - return deferred.promise(); - } - }); - }); - /** - * @fileOverview 文件属性封装 - */ - define('file',[ - 'base', - 'mediator' - ], function( Base, Mediator ) { - - var $ = Base.$, - idPrefix = 'WU_FILE_', - idSuffix = 0, - rExt = /\.([^.]+)$/, - statusMap = {}; - - function gid() { - return idPrefix + idSuffix++; - } - - /** - * 文件类 - * @class File - * @constructor 构造函数 - * @grammar new File( source ) => File - * @param {Lib.File} source [lib.File](#Lib.File)实例, 此source对象是带有Runtime信息的。 - */ - function WUFile( source ) { - - /** - * 文件名,包括扩展名(后缀) - * @property name - * @type {string} - */ - this.name = source.name || 'Untitled'; - - /** - * 文件体积(字节) - * @property size - * @type {uint} - * @default 0 - */ - this.size = source.size || 0; - - /** - * 文件MIMETYPE类型,与文件类型的对应关系请参考[http://t.cn/z8ZnFny](http://t.cn/z8ZnFny) - * @property type - * @type {string} - * @default 'application/octet-stream' - */ - this.type = source.type || 'application/octet-stream'; - - /** - * 文件最后修改日期 - * @property lastModifiedDate - * @type {int} - * @default 当前时间戳 - */ - this.lastModifiedDate = source.lastModifiedDate || (new Date() * 1); - - /** - * 文件ID,每个对象具有唯一ID,与文件名无关 - * @property id - * @type {string} - */ - this.id = gid(); - - /** - * 文件扩展名,通过文件名获取,例如test.png的扩展名为png - * @property ext - * @type {string} - */ - this.ext = rExt.exec( this.name ) ? RegExp.$1 : ''; - - - /** - * 状态文字说明。在不同的status语境下有不同的用途。 - * @property statusText - * @type {string} - */ - this.statusText = ''; - - // 存储文件状态,防止通过属性直接修改 - statusMap[ this.id ] = WUFile.Status.INITED; - - this.source = source; - this.loaded = 0; - - this.on( 'error', function( msg ) { - this.setStatus( WUFile.Status.ERROR, msg ); - }); - } - - $.extend( WUFile.prototype, { - - /** - * 设置状态,状态变化时会触发`change`事件。 - * @method setStatus - * @grammar setStatus( status[, statusText] ); - * @param {File.Status|String} status [文件状态值](#WebUploader:File:File.Status) - * @param {String} [statusText=''] 状态说明,常在error时使用,用http, abort,server等来标记是由于什么原因导致文件错误。 - */ - setStatus: function( status, text ) { - - var prevStatus = statusMap[ this.id ]; - - typeof text !== 'undefined' && (this.statusText = text); - - if ( status !== prevStatus ) { - statusMap[ this.id ] = status; - /** - * 文件状态变化 - * @event statuschange - */ - this.trigger( 'statuschange', status, prevStatus ); - } - - }, - - /** - * 获取文件状态 - * @return {File.Status} - * @example - 文件状态具体包括以下几种类型: - { - // 初始化 - INITED: 0, - // 已入队列 - QUEUED: 1, - // 正在上传 - PROGRESS: 2, - // 上传出错 - ERROR: 3, - // 上传成功 - COMPLETE: 4, - // 上传取消 - CANCELLED: 5 - } - */ - getStatus: function() { - return statusMap[ this.id ]; - }, - - /** - * 获取文件原始信息。 - * @return {*} - */ - getSource: function() { - return this.source; - }, - - destroy: function() { - this.off(); - delete statusMap[ this.id ]; - } - }); - - Mediator.installTo( WUFile.prototype ); - - /** - * 文件状态值,具体包括以下几种类型: - * * `inited` 初始状态 - * * `queued` 已经进入队列, 等待上传 - * * `progress` 上传中 - * * `complete` 上传完成。 - * * `error` 上传出错,可重试 - * * `interrupt` 上传中断,可续传。 - * * `invalid` 文件不合格,不能重试上传。会自动从队列中移除。 - * * `cancelled` 文件被移除。 - * @property {Object} Status - * @namespace File - * @class File - * @static - */ - WUFile.Status = { - INITED: 'inited', // 初始状态 - QUEUED: 'queued', // 已经进入队列, 等待上传 - PROGRESS: 'progress', // 上传中 - ERROR: 'error', // 上传出错,可重试 - COMPLETE: 'complete', // 上传完成。 - CANCELLED: 'cancelled', // 上传取消。 - INTERRUPT: 'interrupt', // 上传中断,可续传。 - INVALID: 'invalid' // 文件不合格,不能重试上传。 - }; - - return WUFile; - }); - - /** - * @fileOverview 文件队列 - */ - define('queue',[ - 'base', - 'mediator', - 'file' - ], function( Base, Mediator, WUFile ) { - - var $ = Base.$, - STATUS = WUFile.Status; - - /** - * 文件队列, 用来存储各个状态中的文件。 - * @class Queue - * @extends Mediator - */ - function Queue() { - - /** - * 统计文件数。 - * * `numOfQueue` 队列中的文件数。 - * * `numOfSuccess` 上传成功的文件数 - * * `numOfCancel` 被取消的文件数 - * * `numOfProgress` 正在上传中的文件数 - * * `numOfUploadFailed` 上传错误的文件数。 - * * `numOfInvalid` 无效的文件数。 - * * `numofDeleted` 被移除的文件数。 - * @property {Object} stats - */ - this.stats = { - numOfQueue: 0, - numOfSuccess: 0, - numOfCancel: 0, - numOfProgress: 0, - numOfUploadFailed: 0, - numOfInvalid: 0, - numofDeleted: 0, - numofInterrupt: 0 - }; - - // 上传队列,仅包括等待上传的文件 - this._queue = []; - - // 存储所有文件 - this._map = {}; - } - - $.extend( Queue.prototype, { - - /** - * 将新文件加入对队列尾部 - * - * @method append - * @param {File} file 文件对象 - */ - append: function( file ) { - this._queue.push( file ); - this._fileAdded( file ); - return this; - }, - - /** - * 将新文件加入对队列头部 - * - * @method prepend - * @param {File} file 文件对象 - */ - prepend: function( file ) { - this._queue.unshift( file ); - this._fileAdded( file ); - return this; - }, - - /** - * 获取文件对象 - * - * @method getFile - * @param {String} fileId 文件ID - * @return {File} - */ - getFile: function( fileId ) { - if ( typeof fileId !== 'string' ) { - return fileId; - } - return this._map[ fileId ]; - }, - - /** - * 从队列中取出一个指定状态的文件。 - * @grammar fetch( status ) => File - * @method fetch - * @param {String} status [文件状态值](#WebUploader:File:File.Status) - * @return {File} [File](#WebUploader:File) - */ - fetch: function( status ) { - var len = this._queue.length, - i, file; - - status = status || STATUS.QUEUED; - - for ( i = 0; i < len; i++ ) { - file = this._queue[ i ]; - - if ( status === file.getStatus() ) { - return file; - } - } - - return null; - }, - - /** - * 对队列进行排序,能够控制文件上传顺序。 - * @grammar sort( fn ) => undefined - * @method sort - * @param {Function} fn 排序方法 - */ - sort: function( fn ) { - if ( typeof fn === 'function' ) { - this._queue.sort( fn ); - } - }, - - /** - * 获取指定类型的文件列表, 列表中每一个成员为[File](#WebUploader:File)对象。 - * @grammar getFiles( [status1[, status2 ...]] ) => Array - * @method getFiles - * @param {String} [status] [文件状态值](#WebUploader:File:File.Status) - */ - getFiles: function() { - var sts = [].slice.call( arguments, 0 ), - ret = [], - i = 0, - len = this._queue.length, - file; - - for ( ; i < len; i++ ) { - file = this._queue[ i ]; - - if ( sts.length && !~$.inArray( file.getStatus(), sts ) ) { - continue; - } - - ret.push( file ); - } - - return ret; - }, - - /** - * 在队列中删除文件。 - * @grammar removeFile( file ) => Array - * @method removeFile - * @param {File} 文件对象。 - */ - removeFile: function( file ) { - var me = this, - existing = this._map[ file.id ]; - - if ( existing ) { - delete this._map[ file.id ]; - file.destroy(); - this.stats.numofDeleted++; - } - }, - - _fileAdded: function( file ) { - var me = this, - existing = this._map[ file.id ]; - - if ( !existing ) { - this._map[ file.id ] = file; - - file.on( 'statuschange', function( cur, pre ) { - me._onFileStatusChange( cur, pre ); - }); - } - }, - - _onFileStatusChange: function( curStatus, preStatus ) { - var stats = this.stats; - - switch ( preStatus ) { - case STATUS.PROGRESS: - stats.numOfProgress--; - break; - - case STATUS.QUEUED: - stats.numOfQueue --; - break; - - case STATUS.ERROR: - stats.numOfUploadFailed--; - break; - - case STATUS.INVALID: - stats.numOfInvalid--; - break; - - case STATUS.INTERRUPT: - stats.numofInterrupt--; - break; - } - - switch ( curStatus ) { - case STATUS.QUEUED: - stats.numOfQueue++; - break; - - case STATUS.PROGRESS: - stats.numOfProgress++; - break; - - case STATUS.ERROR: - stats.numOfUploadFailed++; - break; - - case STATUS.COMPLETE: - stats.numOfSuccess++; - break; - - case STATUS.CANCELLED: - stats.numOfCancel++; - break; - - - case STATUS.INVALID: - stats.numOfInvalid++; - break; - - case STATUS.INTERRUPT: - stats.numofInterrupt++; - break; - } - } - - }); - - Mediator.installTo( Queue.prototype ); - - return Queue; - }); - /** - * @fileOverview 队列 - */ - define('widgets/queue',[ - 'base', - 'uploader', - 'queue', - 'file', - 'lib/file', - 'runtime/client', - 'widgets/widget' - ], function( Base, Uploader, Queue, WUFile, File, RuntimeClient ) { - - var $ = Base.$, - rExt = /\.\w+$/, - Status = WUFile.Status; - - return Uploader.register({ - name: 'queue', - - init: function( opts ) { - var me = this, - deferred, len, i, item, arr, accept, runtime; - - if ( $.isPlainObject( opts.accept ) ) { - opts.accept = [ opts.accept ]; - } - - // accept中的中生成匹配正则。 - if ( opts.accept ) { - arr = []; - - for ( i = 0, len = opts.accept.length; i < len; i++ ) { - item = opts.accept[ i ].extensions; - item && arr.push( item ); - } - - if ( arr.length ) { - accept = '\\.' + arr.join(',') - .replace( /,/g, '$|\\.' ) - .replace( /\*/g, '.*' ) + '$'; - } - - me.accept = new RegExp( accept, 'i' ); - } - - me.queue = new Queue(); - me.stats = me.queue.stats; - - // 如果当前不是html5运行时,那就算了。 - // 不执行后续操作 - if ( this.request('predict-runtime-type') !== 'html5' ) { - return; - } - - // 创建一个 html5 运行时的 placeholder - // 以至于外部添加原生 File 对象的时候能正确包裹一下供 webuploader 使用。 - deferred = Base.Deferred(); - this.placeholder = runtime = new RuntimeClient('Placeholder'); - runtime.connectRuntime({ - runtimeOrder: 'html5' - }, function() { - me._ruid = runtime.getRuid(); - deferred.resolve(); - }); - return deferred.promise(); - }, - - - // 为了支持外部直接添加一个原生File对象。 - _wrapFile: function( file ) { - if ( !(file instanceof WUFile) ) { - - if ( !(file instanceof File) ) { - if ( !this._ruid ) { - throw new Error('Can\'t add external files.'); - } - file = new File( this._ruid, file ); - } - - file = new WUFile( file ); - } - - return file; - }, - - // 判断文件是否可以被加入队列 - acceptFile: function( file ) { - var invalid = !file || !file.size || this.accept && - - // 如果名字中有后缀,才做后缀白名单处理。 - rExt.exec( file.name ) && !this.accept.test( file.name ); - - return !invalid; - }, - - - /** - * @event beforeFileQueued - * @param {File} file File对象 - * @description 当文件被加入队列之前触发,此事件的handler返回值为`false`,则此文件不会被添加进入队列。 - * @for Uploader - */ - - /** - * @event fileQueued - * @param {File} file File对象 - * @description 当文件被加入队列以后触发。 - * @for Uploader - */ - - _addFile: function( file ) { - var me = this; - - file = me._wrapFile( file ); - - // 不过类型判断允许不允许,先派送 `beforeFileQueued` - if ( !me.owner.trigger( 'beforeFileQueued', file ) ) { - return; - } - - // 类型不匹配,则派送错误事件,并返回。 - if ( !me.acceptFile( file ) ) { - me.owner.trigger( 'error', 'Q_TYPE_DENIED', file ); - return; - } - - me.queue.append( file ); - me.owner.trigger( 'fileQueued', file ); - return file; - }, - - getFile: function( fileId ) { - return this.queue.getFile( fileId ); - }, - - /** - * @event filesQueued - * @param {File} files 数组,内容为原始File(lib/File)对象。 - * @description 当一批文件添加进队列以后触发。 - * @for Uploader - */ - - /** - * @property {Boolean} [auto=false] - * @namespace options - * @for Uploader - * @description 设置为 true 后,不需要手动调用上传,有文件选择即开始上传。 - * - */ - - /** - * @method addFiles - * @grammar addFiles( file ) => undefined - * @grammar addFiles( [file1, file2 ...] ) => undefined - * @param {Array of File or File} [files] Files 对象 数组 - * @description 添加文件到队列 - * @for Uploader - */ - addFile: function( files ) { - var me = this; - - if ( !files.length ) { - files = [ files ]; - } - - files = $.map( files, function( file ) { - return me._addFile( file ); - }); - - me.owner.trigger( 'filesQueued', files ); - - if ( me.options.auto ) { - setTimeout(function() { - me.request('start-upload'); - }, 20 ); - } - }, - - getStats: function() { - return this.stats; - }, - - /** - * @event fileDequeued - * @param {File} file File对象 - * @description 当文件被移除队列后触发。 - * @for Uploader - */ - - /** - * @method removeFile - * @grammar removeFile( file ) => undefined - * @grammar removeFile( id ) => undefined - * @grammar removeFile( file, true ) => undefined - * @grammar removeFile( id, true ) => undefined - * @param {File|id} file File对象或这File对象的id - * @description 移除某一文件, 默认只会标记文件状态为已取消,如果第二个参数为 `true` 则会从 queue 中移除。 - * @for Uploader - * @example - * - * $li.on('click', '.remove-this', function() { - * uploader.removeFile( file ); - * }) - */ - removeFile: function( file, remove ) { - var me = this; - - file = file.id ? file : me.queue.getFile( file ); - - this.request( 'cancel-file', file ); - - if ( remove ) { - this.queue.removeFile( file ); - } - }, - - /** - * @method getFiles - * @grammar getFiles() => Array - * @grammar getFiles( status1, status2, status... ) => Array - * @description 返回指定状态的文件集合,不传参数将返回所有状态的文件。 - * @for Uploader - * @example - * console.log( uploader.getFiles() ); // => all files - * console.log( uploader.getFiles('error') ) // => all error files. - */ - getFiles: function() { - return this.queue.getFiles.apply( this.queue, arguments ); - }, - - fetchFile: function() { - return this.queue.fetch.apply( this.queue, arguments ); - }, - - /** - * @method retry - * @grammar retry() => undefined - * @grammar retry( file ) => undefined - * @description 重试上传,重试指定文件,或者从出错的文件开始重新上传。 - * @for Uploader - * @example - * function retry() { - * uploader.retry(); - * } - */ - retry: function( file, noForceStart ) { - var me = this, - files, i, len; - - if ( file ) { - file = file.id ? file : me.queue.getFile( file ); - file.setStatus( Status.QUEUED ); - noForceStart || me.request('start-upload'); - return; - } - - files = me.queue.getFiles( Status.ERROR ); - i = 0; - len = files.length; - - for ( ; i < len; i++ ) { - file = files[ i ]; - file.setStatus( Status.QUEUED ); - } - - me.request('start-upload'); - }, - - /** - * @method sort - * @grammar sort( fn ) => undefined - * @description 排序队列中的文件,在上传之前调整可以控制上传顺序。 - * @for Uploader - */ - sortFiles: function() { - return this.queue.sort.apply( this.queue, arguments ); - }, - - /** - * @event reset - * @description 当 uploader 被重置的时候触发。 - * @for Uploader - */ - - /** - * @method reset - * @grammar reset() => undefined - * @description 重置uploader。目前只重置了队列。 - * @for Uploader - * @example - * uploader.reset(); - */ - reset: function() { - this.owner.trigger('reset'); - this.queue = new Queue(); - this.stats = this.queue.stats; - }, - - destroy: function() { - this.reset(); - this.placeholder && this.placeholder.destroy(); - } - }); - - }); - /** - * @fileOverview 添加获取Runtime相关信息的方法。 - */ - define('widgets/runtime',[ - 'uploader', - 'runtime/runtime', - 'widgets/widget' - ], function( Uploader, Runtime ) { - - Uploader.support = function() { - return Runtime.hasRuntime.apply( Runtime, arguments ); - }; - - /** - * @property {Object} [runtimeOrder=html5,flash] - * @namespace options - * @for Uploader - * @description 指定运行时启动顺序。默认会想尝试 html5 是否支持,如果支持则使用 html5, 否则则使用 flash. - * - * 可以将此值设置成 `flash`,来强制使用 flash 运行时。 - */ - - return Uploader.register({ - name: 'runtime', - - init: function() { - if ( !this.predictRuntimeType() ) { - throw Error('Runtime Error'); - } - }, - - /** - * 预测Uploader将采用哪个`Runtime` - * @grammar predictRuntimeType() => String - * @method predictRuntimeType - * @for Uploader - */ - predictRuntimeType: function() { - var orders = this.options.runtimeOrder || Runtime.orders, - type = this.type, - i, len; - - if ( !type ) { - orders = orders.split( /\s*,\s*/g ); - - for ( i = 0, len = orders.length; i < len; i++ ) { - if ( Runtime.hasRuntime( orders[ i ] ) ) { - this.type = type = orders[ i ]; - break; - } - } - } - - return type; - } - }); - }); - /** - * @fileOverview Transport - */ - define('lib/transport',[ - 'base', - 'runtime/client', - 'mediator' - ], function( Base, RuntimeClient, Mediator ) { - - var $ = Base.$; - - function Transport( opts ) { - var me = this; - - opts = me.options = $.extend( true, {}, Transport.options, opts || {} ); - RuntimeClient.call( this, 'Transport' ); - - this._blob = null; - this._formData = opts.formData || {}; - this._headers = opts.headers || {}; - - this.on( 'progress', this._timeout ); - this.on( 'load error', function() { - me.trigger( 'progress', 1 ); - clearTimeout( me._timer ); - }); - } - - Transport.options = { - server: '', - method: 'POST', - - // 跨域时,是否允许携带cookie, 只有html5 runtime才有效 - withCredentials: false, - fileVal: 'file', - timeout: 2 * 60 * 1000, // 2分钟 - formData: {}, - headers: {}, - sendAsBinary: false - }; - - $.extend( Transport.prototype, { - - // 添加Blob, 只能添加一次,最后一次有效。 - appendBlob: function( key, blob, filename ) { - var me = this, - opts = me.options; - - if ( me.getRuid() ) { - me.disconnectRuntime(); - } - - // 连接到blob归属的同一个runtime. - me.connectRuntime( blob.ruid, function() { - me.exec('init'); - }); - - me._blob = blob; - opts.fileVal = key || opts.fileVal; - opts.filename = filename || opts.filename; - }, - - // 添加其他字段 - append: function( key, value ) { - if ( typeof key === 'object' ) { - $.extend( this._formData, key ); - } else { - this._formData[ key ] = value; - } - }, - - setRequestHeader: function( key, value ) { - if ( typeof key === 'object' ) { - $.extend( this._headers, key ); - } else { - this._headers[ key ] = value; - } - }, - - send: function( method ) { - this.exec( 'send', method ); - this._timeout(); - }, - - abort: function() { - clearTimeout( this._timer ); - return this.exec('abort'); - }, - - destroy: function() { - this.trigger('destroy'); - this.off(); - this.exec('destroy'); - this.disconnectRuntime(); - }, - - getResponse: function() { - return this.exec('getResponse'); - }, - - getResponseAsJson: function() { - return this.exec('getResponseAsJson'); - }, - - getStatus: function() { - return this.exec('getStatus'); - }, - - _timeout: function() { - var me = this, - duration = me.options.timeout; - - if ( !duration ) { - return; - } - - clearTimeout( me._timer ); - me._timer = setTimeout(function() { - me.abort(); - me.trigger( 'error', 'timeout' ); - }, duration ); - } - - }); - - // 让Transport具备事件功能。 - Mediator.installTo( Transport.prototype ); - - return Transport; - }); - /** - * @fileOverview 负责文件上传相关。 - */ - define('widgets/upload',[ - 'base', - 'uploader', - 'file', - 'lib/transport', - 'widgets/widget' - ], function( Base, Uploader, WUFile, Transport ) { - - var $ = Base.$, - isPromise = Base.isPromise, - Status = WUFile.Status; - - // 添加默认配置项 - $.extend( Uploader.options, { - - - /** - * @property {Boolean} [prepareNextFile=false] - * @namespace options - * @for Uploader - * @description 是否允许在文件传输时提前把下一个文件准备好。 - * 对于一个文件的准备工作比较耗时,比如图片压缩,md5序列化。 - * 如果能提前在当前文件传输期处理,可以节省总体耗时。 - */ - prepareNextFile: false, - - /** - * @property {Boolean} [chunked=false] - * @namespace options - * @for Uploader - * @description 是否要分片处理大文件上传。 - */ - chunked: false, - - /** - * @property {Boolean} [chunkSize=5242880] - * @namespace options - * @for Uploader - * @description 如果要分片,分多大一片? 默认大小为5M. - */ - chunkSize: 5 * 1024 * 1024, - - /** - * @property {Boolean} [chunkRetry=2] - * @namespace options - * @for Uploader - * @description 如果某个分片由于网络问题出错,允许自动重传多少次? - */ - chunkRetry: 2, - - /** - * @property {Boolean} [threads=3] - * @namespace options - * @for Uploader - * @description 上传并发数。允许同时最大上传进程数。 - */ - threads: 3, - - - /** - * @property {Object} [formData={}] - * @namespace options - * @for Uploader - * @description 文件上传请求的参数表,每次发送都会发送此对象中的参数。 - */ - formData: {} - - /** - * @property {Object} [fileVal='file'] - * @namespace options - * @for Uploader - * @description 设置文件上传域的name。 - */ - - /** - * @property {Object} [method='POST'] - * @namespace options - * @for Uploader - * @description 文件上传方式,`POST`或者`GET`。 - */ - - /** - * @property {Object} [sendAsBinary=false] - * @namespace options - * @for Uploader - * @description 是否已二进制的流的方式发送文件,这样整个上传内容`php://input`都为文件内容, - * 其他参数在$_GET数组中。 - */ - }); - - // 负责将文件切片。 - function CuteFile( file, chunkSize ) { - var pending = [], - blob = file.source, - total = blob.size, - chunks = chunkSize ? Math.ceil( total / chunkSize ) : 1, - start = 0, - index = 0, - len, api; - - api = { - file: file, - - has: function() { - return !!pending.length; - }, - - shift: function() { - return pending.shift(); - }, - - unshift: function( block ) { - pending.unshift( block ); - } - }; - - while ( index < chunks ) { - len = Math.min( chunkSize, total - start ); - - pending.push({ - file: file, - start: start, - end: chunkSize ? (start + len) : total, - total: total, - chunks: chunks, - chunk: index++, - cuted: api - }); - start += len; - } - - file.blocks = pending.concat(); - file.remaning = pending.length; - - return api; - } - - Uploader.register({ - name: 'upload', - - init: function() { - var owner = this.owner, - me = this; - - this.runing = false; - this.progress = false; - - owner - .on( 'startUpload', function() { - me.progress = true; - }) - .on( 'uploadFinished', function() { - me.progress = false; - }); - - // 记录当前正在传的数据,跟threads相关 - this.pool = []; - - // 缓存分好片的文件。 - this.stack = []; - - // 缓存即将上传的文件。 - this.pending = []; - - // 跟踪还有多少分片在上传中但是没有完成上传。 - this.remaning = 0; - this.__tick = Base.bindFn( this._tick, this ); - - owner.on( 'uploadComplete', function( file ) { - - // 把其他块取消了。 - file.blocks && $.each( file.blocks, function( _, v ) { - v.transport && (v.transport.abort(), v.transport.destroy()); - delete v.transport; - }); - - delete file.blocks; - delete file.remaning; - }); - }, - - reset: function() { - this.request( 'stop-upload', true ); - this.runing = false; - this.pool = []; - this.stack = []; - this.pending = []; - this.remaning = 0; - this._trigged = false; - this._promise = null; - }, - - /** - * @event startUpload - * @description 当开始上传流程时触发。 - * @for Uploader - */ - - /** - * 开始上传。此方法可以从初始状态调用开始上传流程,也可以从暂停状态调用,继续上传流程。 - * - * 可以指定开始某一个文件。 - * @grammar upload() => undefined - * @grammar upload( file | fileId) => undefined - * @method upload - * @for Uploader - */ - startUpload: function(file) { - var me = this; - - // 移出invalid的文件 - $.each( me.request( 'get-files', Status.INVALID ), function() { - me.request( 'remove-file', this ); - }); - - // 如果指定了开始某个文件,则只开始指定文件。 - if ( file ) { - file = file.id ? file : me.request( 'get-file', file ); - - if (file.getStatus() === Status.INTERRUPT) { - $.each( me.pool, function( _, v ) { - - // 之前暂停过。 - if (v.file !== file) { - return; - } - - v.transport && v.transport.send(); - }); - - file.setStatus( Status.QUEUED ); - } else if (file.getStatus() === Status.PROGRESS) { - return; - } else { - file.setStatus( Status.QUEUED ); - } - } else { - $.each( me.request( 'get-files', [ Status.INITED ] ), function() { - this.setStatus( Status.QUEUED ); - }); - } - - if ( me.runing ) { - return; - } - - me.runing = true; - - var files = []; - - // 如果有暂停的,则续传 - $.each( me.pool, function( _, v ) { - var file = v.file; - - if ( file.getStatus() === Status.INTERRUPT ) { - files.push(file); - me._trigged = false; - v.transport && v.transport.send(); - } - }); - - var file; - while ( (file = files.shift()) ) { - file.setStatus( Status.PROGRESS ); - } - - file || $.each( me.request( 'get-files', - Status.INTERRUPT ), function() { - this.setStatus( Status.PROGRESS ); - }); - - me._trigged = false; - Base.nextTick( me.__tick ); - me.owner.trigger('startUpload'); - }, - - /** - * @event stopUpload - * @description 当开始上传流程暂停时触发。 - * @for Uploader - */ - - /** - * 暂停上传。第一个参数为是否中断上传当前正在上传的文件。 - * - * 如果第一个参数是文件,则只暂停指定文件。 - * @grammar stop() => undefined - * @grammar stop( true ) => undefined - * @grammar stop( file ) => undefined - * @method stop - * @for Uploader - */ - stopUpload: function( file, interrupt ) { - var me = this; - - if (file === true) { - interrupt = file; - file = null; - } - - if ( me.runing === false ) { - return; - } - - // 如果只是暂停某个文件。 - if ( file ) { - file = file.id ? file : me.request( 'get-file', file ); - - if ( file.getStatus() !== Status.PROGRESS && - file.getStatus() !== Status.QUEUED ) { - return; - } - - file.setStatus( Status.INTERRUPT ); - $.each( me.pool, function( _, v ) { - - // 只 abort 指定的文件。 - if (v.file !== file) { - return; - } - - v.transport && v.transport.abort(); - me._putback(v); - me._popBlock(v); - }); - - return Base.nextTick( me.__tick ); - } - - me.runing = false; - - if (this._promise && this._promise.file) { - this._promise.file.setStatus( Status.INTERRUPT ); - } - - interrupt && $.each( me.pool, function( _, v ) { - v.transport && v.transport.abort(); - v.file.setStatus( Status.INTERRUPT ); - }); - - me.owner.trigger('stopUpload'); - }, - - /** - * @method cancelFile - * @grammar cancelFile( file ) => undefined - * @grammar cancelFile( id ) => undefined - * @param {File|id} file File对象或这File对象的id - * @description 标记文件状态为已取消, 同时将中断文件传输。 - * @for Uploader - * @example - * - * $li.on('click', '.remove-this', function() { - * uploader.cancelFile( file ); - * }) - */ - cancelFile: function( file ) { - file = file.id ? file : this.request( 'get-file', file ); - - // 如果正在上传。 - file.blocks && $.each( file.blocks, function( _, v ) { - var _tr = v.transport; - - if ( _tr ) { - _tr.abort(); - _tr.destroy(); - delete v.transport; - } - }); - - file.setStatus( Status.CANCELLED ); - this.owner.trigger( 'fileDequeued', file ); - }, - - /** - * 判断`Uplaode`r是否正在上传中。 - * @grammar isInProgress() => Boolean - * @method isInProgress - * @for Uploader - */ - isInProgress: function() { - return !!this.progress; - }, - - _getStats: function() { - return this.request('get-stats'); - }, - - /** - * 掉过一个文件上传,直接标记指定文件为已上传状态。 - * @grammar skipFile( file ) => undefined - * @method skipFile - * @for Uploader - */ - skipFile: function( file, status ) { - file = file.id ? file : this.request( 'get-file', file ); - - file.setStatus( status || Status.COMPLETE ); - file.skipped = true; - - // 如果正在上传。 - file.blocks && $.each( file.blocks, function( _, v ) { - var _tr = v.transport; - - if ( _tr ) { - _tr.abort(); - _tr.destroy(); - delete v.transport; - } - }); - - this.owner.trigger( 'uploadSkip', file ); - }, - - /** - * @event uploadFinished - * @description 当所有文件上传结束时触发。 - * @for Uploader - */ - _tick: function() { - var me = this, - opts = me.options, - fn, val; - - // 上一个promise还没有结束,则等待完成后再执行。 - if ( me._promise ) { - return me._promise.always( me.__tick ); - } - - // 还有位置,且还有文件要处理的话。 - if ( me.pool.length < opts.threads && (val = me._nextBlock()) ) { - me._trigged = false; - - fn = function( val ) { - me._promise = null; - - // 有可能是reject过来的,所以要检测val的类型。 - val && val.file && me._startSend( val ); - Base.nextTick( me.__tick ); - }; - - me._promise = isPromise( val ) ? val.always( fn ) : fn( val ); - - // 没有要上传的了,且没有正在传输的了。 - } else if ( !me.remaning && !me._getStats().numOfQueue && - !me._getStats().numofInterrupt ) { - me.runing = false; - - me._trigged || Base.nextTick(function() { - me.owner.trigger('uploadFinished'); - }); - me._trigged = true; - } - }, - - _putback: function(block) { - var idx; - - block.cuted.unshift(block); - idx = this.stack.indexOf(block.cuted); - - if (!~idx) { - this.stack.unshift(block.cuted); - } - }, - - _getStack: function() { - var i = 0, - act; - - while ( (act = this.stack[ i++ ]) ) { - if ( act.has() && act.file.getStatus() === Status.PROGRESS ) { - return act; - } else if (!act.has() || - act.file.getStatus() !== Status.PROGRESS && - act.file.getStatus() !== Status.INTERRUPT ) { - - // 把已经处理完了的,或者,状态为非 progress(上传中)、 - // interupt(暂停中) 的移除。 - this.stack.splice( --i, 1 ); - } - } - - return null; - }, - - _nextBlock: function() { - var me = this, - opts = me.options, - act, next, done, preparing; - - // 如果当前文件还有没有需要传输的,则直接返回剩下的。 - if ( (act = this._getStack()) ) { - - // 是否提前准备下一个文件 - if ( opts.prepareNextFile && !me.pending.length ) { - me._prepareNextFile(); - } - - return act.shift(); - - // 否则,如果正在运行,则准备下一个文件,并等待完成后返回下个分片。 - } else if ( me.runing ) { - - // 如果缓存中有,则直接在缓存中取,没有则去queue中取。 - if ( !me.pending.length && me._getStats().numOfQueue ) { - me._prepareNextFile(); - } - - next = me.pending.shift(); - done = function( file ) { - if ( !file ) { - return null; - } - - act = CuteFile( file, opts.chunked ? opts.chunkSize : 0 ); - me.stack.push(act); - return act.shift(); - }; - - // 文件可能还在prepare中,也有可能已经完全准备好了。 - if ( isPromise( next) ) { - preparing = next.file; - next = next[ next.pipe ? 'pipe' : 'then' ]( done ); - next.file = preparing; - return next; - } - - return done( next ); - } - }, - - - /** - * @event uploadStart - * @param {File} file File对象 - * @description 某个文件开始上传前触发,一个文件只会触发一次。 - * @for Uploader - */ - _prepareNextFile: function() { - var me = this, - file = me.request('fetch-file'), - pending = me.pending, - promise; - - if ( file ) { - promise = me.request( 'before-send-file', file, function() { - - // 有可能文件被skip掉了。文件被skip掉后,状态坑定不是Queued. - if ( file.getStatus() === Status.PROGRESS || - file.getStatus() === Status.INTERRUPT ) { - return file; - } - - return me._finishFile( file ); - }); - - me.owner.trigger( 'uploadStart', file ); - file.setStatus( Status.PROGRESS ); - - promise.file = file; - - // 如果还在pending中,则替换成文件本身。 - promise.done(function() { - var idx = $.inArray( promise, pending ); - - ~idx && pending.splice( idx, 1, file ); - }); - - // befeore-send-file的钩子就有错误发生。 - promise.fail(function( reason ) { - file.setStatus( Status.ERROR, reason ); - me.owner.trigger( 'uploadError', file, reason ); - me.owner.trigger( 'uploadComplete', file ); - }); - - pending.push( promise ); - } - }, - - // 让出位置了,可以让其他分片开始上传 - _popBlock: function( block ) { - var idx = $.inArray( block, this.pool ); - - this.pool.splice( idx, 1 ); - block.file.remaning--; - this.remaning--; - }, - - // 开始上传,可以被掉过。如果promise被reject了,则表示跳过此分片。 - _startSend: function( block ) { - var me = this, - file = block.file, - promise; - - // 有可能在 before-send-file 的 promise 期间改变了文件状态。 - // 如:暂停,取消 - // 我们不能中断 promise, 但是可以在 promise 完后,不做上传操作。 - if ( file.getStatus() !== Status.PROGRESS ) { - - // 如果是中断,则还需要放回去。 - if (file.getStatus() === Status.INTERRUPT) { - me._putback(block); - } - - return; - } - - me.pool.push( block ); - me.remaning++; - - // 如果没有分片,则直接使用原始的。 - // 不会丢失content-type信息。 - block.blob = block.chunks === 1 ? file.source : - file.source.slice( block.start, block.end ); - - // hook, 每个分片发送之前可能要做些异步的事情。 - promise = me.request( 'before-send', block, function() { - - // 有可能文件已经上传出错了,所以不需要再传输了。 - if ( file.getStatus() === Status.PROGRESS ) { - me._doSend( block ); - } else { - me._popBlock( block ); - Base.nextTick( me.__tick ); - } - }); - - // 如果为fail了,则跳过此分片。 - promise.fail(function() { - if ( file.remaning === 1 ) { - me._finishFile( file ).always(function() { - block.percentage = 1; - me._popBlock( block ); - me.owner.trigger( 'uploadComplete', file ); - Base.nextTick( me.__tick ); - }); - } else { - block.percentage = 1; - me.updateFileProgress( file ); - me._popBlock( block ); - Base.nextTick( me.__tick ); - } - }); - }, - - - /** - * @event uploadBeforeSend - * @param {Object} object - * @param {Object} data 默认的上传参数,可以扩展此对象来控制上传参数。 - * @param {Object} headers 可以扩展此对象来控制上传头部。 - * @description 当某个文件的分块在发送前触发,主要用来询问是否要添加附带参数,大文件在开起分片上传的前提下此事件可能会触发多次。 - * @for Uploader - */ - - /** - * @event uploadAccept - * @param {Object} object - * @param {Object} ret 服务端的返回数据,json格式,如果服务端不是json格式,从ret._raw中取数据,自行解析。 - * @description 当某个文件上传到服务端响应后,会派送此事件来询问服务端响应是否有效。如果此事件handler返回值为`false`, 则此文件将派送`server`类型的`uploadError`事件。 - * @for Uploader - */ - - /** - * @event uploadProgress - * @param {File} file File对象 - * @param {Number} percentage 上传进度 - * @description 上传过程中触发,携带上传进度。 - * @for Uploader - */ - - - /** - * @event uploadError - * @param {File} file File对象 - * @param {String} reason 出错的code - * @description 当文件上传出错时触发。 - * @for Uploader - */ - - /** - * @event uploadSuccess - * @param {File} file File对象 - * @param {Object} response 服务端返回的数据 - * @description 当文件上传成功时触发。 - * @for Uploader - */ - - /** - * @event uploadComplete - * @param {File} [file] File对象 - * @description 不管成功或者失败,文件上传完成时触发。 - * @for Uploader - */ - - // 做上传操作。 - _doSend: function( block ) { - var me = this, - owner = me.owner, - opts = me.options, - file = block.file, - tr = new Transport( opts ), - data = $.extend({}, opts.formData ), - headers = $.extend({}, opts.headers ), - requestAccept, ret; - - block.transport = tr; - - tr.on( 'destroy', function() { - delete block.transport; - me._popBlock( block ); - Base.nextTick( me.__tick ); - }); - - // 广播上传进度。以文件为单位。 - tr.on( 'progress', function( percentage ) { - block.percentage = percentage; - me.updateFileProgress( file ); - }); - - // 用来询问,是否返回的结果是有错误的。 - requestAccept = function( reject ) { - var fn; - - ret = tr.getResponseAsJson() || {}; - ret._raw = tr.getResponse(); - fn = function( value ) { - reject = value; - }; - - // 服务端响应了,不代表成功了,询问是否响应正确。 - if ( !owner.trigger( 'uploadAccept', block, ret, fn ) ) { - reject = reject || 'server'; - } - - return reject; - }; - - // 尝试重试,然后广播文件上传出错。 - tr.on( 'error', function( type, flag ) { - block.retried = block.retried || 0; - - // 自动重试 - if ( block.chunks > 1 && ~'http,abort'.indexOf( type ) && - block.retried < opts.chunkRetry ) { - - block.retried++; - tr.send(); - - } else { - - // http status 500 ~ 600 - if ( !flag && type === 'server' ) { - type = requestAccept( type ); - } - - file.setStatus( Status.ERROR, type ); - owner.trigger( 'uploadError', file, type ); - owner.trigger( 'uploadComplete', file ); - } - }); - - // 上传成功 - tr.on( 'load', function() { - var reason; - - // 如果非预期,转向上传出错。 - if ( (reason = requestAccept()) ) { - tr.trigger( 'error', reason, true ); - return; - } - - // 全部上传完成。 - if ( file.remaning === 1 ) { - me._finishFile( file, ret ); - } else { - tr.destroy(); - } - }); - - // 配置默认的上传字段。 - data = $.extend( data, { - id: file.id, - name: file.name, - type: file.type, - lastModifiedDate: file.lastModifiedDate, - size: file.size - }); - - block.chunks > 1 && $.extend( data, { - chunks: block.chunks, - chunk: block.chunk - }); - - // 在发送之间可以添加字段什么的。。。 - // 如果默认的字段不够使用,可以通过监听此事件来扩展 - owner.trigger( 'uploadBeforeSend', block, data, headers ); - - // 开始发送。 - tr.appendBlob( opts.fileVal, block.blob, file.name ); - tr.append( data ); - tr.setRequestHeader( headers ); - tr.send(); - }, - - // 完成上传。 - _finishFile: function( file, ret, hds ) { - var owner = this.owner; - - return owner - .request( 'after-send-file', arguments, function() { - file.setStatus( Status.COMPLETE ); - owner.trigger( 'uploadSuccess', file, ret, hds ); - }) - .fail(function( reason ) { - - // 如果外部已经标记为invalid什么的,不再改状态。 - if ( file.getStatus() === Status.PROGRESS ) { - file.setStatus( Status.ERROR, reason ); - } - - owner.trigger( 'uploadError', file, reason ); - }) - .always(function() { - owner.trigger( 'uploadComplete', file ); - }); - }, - - updateFileProgress: function(file) { - var totalPercent = 0, - uploaded = 0; - - if (!file.blocks) { - return; - } - - $.each( file.blocks, function( _, v ) { - uploaded += (v.percentage || 0) * (v.end - v.start); - }); - - totalPercent = uploaded / file.size; - this.owner.trigger( 'uploadProgress', file, totalPercent || 0 ); - } - - }); - }); - /** - * @fileOverview 各种验证,包括文件总大小是否超出、单文件是否超出和文件是否重复。 - */ - - define('widgets/validator',[ - 'base', - 'uploader', - 'file', - 'widgets/widget' - ], function( Base, Uploader, WUFile ) { - - var $ = Base.$, - validators = {}, - api; - - /** - * @event error - * @param {String} type 错误类型。 - * @description 当validate不通过时,会以派送错误事件的形式通知调用者。通过`upload.on('error', handler)`可以捕获到此类错误,目前有以下错误会在特定的情况下派送错来。 - * - * * `Q_EXCEED_NUM_LIMIT` 在设置了`fileNumLimit`且尝试给`uploader`添加的文件数量超出这个值时派送。 - * * `Q_EXCEED_SIZE_LIMIT` 在设置了`Q_EXCEED_SIZE_LIMIT`且尝试给`uploader`添加的文件总大小超出这个值时派送。 - * * `Q_TYPE_DENIED` 当文件类型不满足时触发。。 - * @for Uploader - */ - - // 暴露给外面的api - api = { - - // 添加验证器 - addValidator: function( type, cb ) { - validators[ type ] = cb; - }, - - // 移除验证器 - removeValidator: function( type ) { - delete validators[ type ]; - } - }; - - // 在Uploader初始化的时候启动Validators的初始化 - Uploader.register({ - name: 'validator', - - init: function() { - var me = this; - Base.nextTick(function() { - $.each( validators, function() { - this.call( me.owner ); - }); - }); - } - }); - - /** - * @property {int} [fileNumLimit=undefined] - * @namespace options - * @for Uploader - * @description 验证文件总数量, 超出则不允许加入队列。 - */ - api.addValidator( 'fileNumLimit', function() { - var uploader = this, - opts = uploader.options, - count = 0, - max = parseInt( opts.fileNumLimit, 10 ), - flag = true; - - if ( !max ) { - return; - } - - uploader.on( 'beforeFileQueued', function( file ) { - - if ( count >= max && flag ) { - flag = false; - this.trigger( 'error', 'Q_EXCEED_NUM_LIMIT', max, file ); - setTimeout(function() { - flag = true; - }, 1 ); - } - - return count >= max ? false : true; - }); - - uploader.on( 'fileQueued', function() { - count++; - }); - - uploader.on( 'fileDequeued', function() { - count--; - }); - - uploader.on( 'reset', function() { - count = 0; - }); - }); - - - /** - * @property {int} [fileSizeLimit=undefined] - * @namespace options - * @for Uploader - * @description 验证文件总大小是否超出限制, 超出则不允许加入队列。 - */ - api.addValidator( 'fileSizeLimit', function() { - var uploader = this, - opts = uploader.options, - count = 0, - max = parseInt( opts.fileSizeLimit, 10 ), - flag = true; - - if ( !max ) { - return; - } - - uploader.on( 'beforeFileQueued', function( file ) { - var invalid = count + file.size > max; - - if ( invalid && flag ) { - flag = false; - this.trigger( 'error', 'Q_EXCEED_SIZE_LIMIT', max, file ); - setTimeout(function() { - flag = true; - }, 1 ); - } - - return invalid ? false : true; - }); - - uploader.on( 'fileQueued', function( file ) { - count += file.size; - }); - - uploader.on( 'fileDequeued', function( file ) { - count -= file.size; - }); - - uploader.on( 'reset', function() { - count = 0; - }); - }); - - /** - * @property {int} [fileSingleSizeLimit=undefined] - * @namespace options - * @for Uploader - * @description 验证单个文件大小是否超出限制, 超出则不允许加入队列。 - */ - api.addValidator( 'fileSingleSizeLimit', function() { - var uploader = this, - opts = uploader.options, - max = opts.fileSingleSizeLimit; - - if ( !max ) { - return; - } - - uploader.on( 'beforeFileQueued', function( file ) { - - if ( file.size > max ) { - file.setStatus( WUFile.Status.INVALID, 'exceed_size' ); - this.trigger( 'error', 'F_EXCEED_SIZE', max, file ); - return false; - } - - }); - - }); - - /** - * @property {Boolean} [duplicate=undefined] - * @namespace options - * @for Uploader - * @description 去重, 根据文件名字、文件大小和最后修改时间来生成hash Key. - */ - api.addValidator( 'duplicate', function() { - var uploader = this, - opts = uploader.options, - mapping = {}; - - if ( opts.duplicate ) { - return; - } - - function hashString( str ) { - var hash = 0, - i = 0, - len = str.length, - _char; - - for ( ; i < len; i++ ) { - _char = str.charCodeAt( i ); - hash = _char + (hash << 6) + (hash << 16) - hash; - } - - return hash; - } - - uploader.on( 'beforeFileQueued', function( file ) { - var hash = file.__hash || (file.__hash = hashString( file.name + - file.size + file.lastModifiedDate )); - - // 已经重复了 - if ( mapping[ hash ] ) { - this.trigger( 'error', 'F_DUPLICATE', file ); - return false; - } - }); - - uploader.on( 'fileQueued', function( file ) { - var hash = file.__hash; - - hash && (mapping[ hash ] = true); - }); - - uploader.on( 'fileDequeued', function( file ) { - var hash = file.__hash; - - hash && (delete mapping[ hash ]); - }); - - uploader.on( 'reset', function() { - mapping = {}; - }); - }); - - return api; - }); - - /** - * @fileOverview Runtime管理器,负责Runtime的选择, 连接 - */ - define('runtime/compbase',[],function() { - - function CompBase( owner, runtime ) { - - this.owner = owner; - this.options = owner.options; - - this.getRuntime = function() { - return runtime; - }; - - this.getRuid = function() { - return runtime.uid; - }; - - this.trigger = function() { - return owner.trigger.apply( owner, arguments ); - }; - } - - return CompBase; - }); - /** - * @fileOverview Html5Runtime - */ - define('runtime/html5/runtime',[ - 'base', - 'runtime/runtime', - 'runtime/compbase' - ], function( Base, Runtime, CompBase ) { - - var type = 'html5', - components = {}; - - function Html5Runtime() { - var pool = {}, - me = this, - destroy = this.destroy; - - Runtime.apply( me, arguments ); - me.type = type; - - - // 这个方法的调用者,实际上是RuntimeClient - me.exec = function( comp, fn/*, args...*/) { - var client = this, - uid = client.uid, - args = Base.slice( arguments, 2 ), - instance; - - if ( components[ comp ] ) { - instance = pool[ uid ] = pool[ uid ] || - new components[ comp ]( client, me ); - - if ( instance[ fn ] ) { - return instance[ fn ].apply( instance, args ); - } - } - }; - - me.destroy = function() { - // @todo 删除池子中的所有实例 - return destroy && destroy.apply( this, arguments ); - }; - } - - Base.inherits( Runtime, { - constructor: Html5Runtime, - - // 不需要连接其他程序,直接执行callback - init: function() { - var me = this; - setTimeout(function() { - me.trigger('ready'); - }, 1 ); - } - - }); - - // 注册Components - Html5Runtime.register = function( name, component ) { - var klass = components[ name ] = Base.inherits( CompBase, component ); - return klass; - }; - - // 注册html5运行时。 - // 只有在支持的前提下注册。 - if ( window.Blob && window.FileReader && window.DataView ) { - Runtime.addRuntime( type, Html5Runtime ); - } - - return Html5Runtime; - }); - /** - * @fileOverview Blob Html实现 - */ - define('runtime/html5/blob',[ - 'runtime/html5/runtime', - 'lib/blob' - ], function( Html5Runtime, Blob ) { - - return Html5Runtime.register( 'Blob', { - slice: function( start, end ) { - var blob = this.owner.source, - slice = blob.slice || blob.webkitSlice || blob.mozSlice; - - blob = slice.call( blob, start, end ); - - return new Blob( this.getRuid(), blob ); - } - }); - }); - /** - * @fileOverview FilePaste - */ - define('runtime/html5/dnd',[ - 'base', - 'runtime/html5/runtime', - 'lib/file' - ], function( Base, Html5Runtime, File ) { - - var $ = Base.$, - prefix = 'webuploader-dnd-'; - - return Html5Runtime.register( 'DragAndDrop', { - init: function() { - var elem = this.elem = this.options.container; - - this.dragEnterHandler = Base.bindFn( this._dragEnterHandler, this ); - this.dragOverHandler = Base.bindFn( this._dragOverHandler, this ); - this.dragLeaveHandler = Base.bindFn( this._dragLeaveHandler, this ); - this.dropHandler = Base.bindFn( this._dropHandler, this ); - this.dndOver = false; - - elem.on( 'dragenter', this.dragEnterHandler ); - elem.on( 'dragover', this.dragOverHandler ); - elem.on( 'dragleave', this.dragLeaveHandler ); - elem.on( 'drop', this.dropHandler ); - - if ( this.options.disableGlobalDnd ) { - $( document ).on( 'dragover', this.dragOverHandler ); - $( document ).on( 'drop', this.dropHandler ); - } - }, - - _dragEnterHandler: function( e ) { - var me = this, - denied = me._denied || false, - items; - - e = e.originalEvent || e; - - if ( !me.dndOver ) { - me.dndOver = true; - - // 注意只有 chrome 支持。 - items = e.dataTransfer.items; - - if ( items && items.length ) { - me._denied = denied = !me.trigger( 'accept', items ); - } - - me.elem.addClass( prefix + 'over' ); - me.elem[ denied ? 'addClass' : - 'removeClass' ]( prefix + 'denied' ); - } - - e.dataTransfer.dropEffect = denied ? 'none' : 'copy'; - - return false; - }, - - _dragOverHandler: function( e ) { - // 只处理框内的。 - var parentElem = this.elem.parent().get( 0 ); - if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) { - return false; - } - - clearTimeout( this._leaveTimer ); - this._dragEnterHandler.call( this, e ); - - return false; - }, - - _dragLeaveHandler: function() { - var me = this, - handler; - - handler = function() { - me.dndOver = false; - me.elem.removeClass( prefix + 'over ' + prefix + 'denied' ); - }; - - clearTimeout( me._leaveTimer ); - me._leaveTimer = setTimeout( handler, 100 ); - return false; - }, - - _dropHandler: function( e ) { - var me = this, - ruid = me.getRuid(), - parentElem = me.elem.parent().get( 0 ), - dataTransfer, data; - - // 只处理框内的。 - if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) { - return false; - } - - e = e.originalEvent || e; - dataTransfer = e.dataTransfer; - - // 如果是页面内拖拽,还不能处理,不阻止事件。 - // 此处 ie11 下会报参数错误, - try { - data = dataTransfer.getData('text/html'); - } catch( err ) { - } - - if ( data ) { - return; - } - - me._getTansferFiles( dataTransfer, function( results ) { - me.trigger( 'drop', $.map( results, function( file ) { - return new File( ruid, file ); - }) ); - }); - - me.dndOver = false; - me.elem.removeClass( prefix + 'over' ); - return false; - }, - - // 如果传入 callback 则去查看文件夹,否则只管当前文件夹。 - _getTansferFiles: function( dataTransfer, callback ) { - var results = [], - promises = [], - items, files, file, item, i, len, canAccessFolder; - - items = dataTransfer.items; - files = dataTransfer.files; - - canAccessFolder = !!(items && items[ 0 ].webkitGetAsEntry); - - for ( i = 0, len = files.length; i < len; i++ ) { - file = files[ i ]; - item = items && items[ i ]; - - if ( canAccessFolder && item.webkitGetAsEntry().isDirectory ) { - - promises.push( this._traverseDirectoryTree( - item.webkitGetAsEntry(), results ) ); - } else { - results.push( file ); - } - } - - Base.when.apply( Base, promises ).done(function() { - - if ( !results.length ) { - return; - } - - callback( results ); - }); - }, - - _traverseDirectoryTree: function( entry, results ) { - var deferred = Base.Deferred(), - me = this; - - if ( entry.isFile ) { - entry.file(function( file ) { - results.push( file ); - deferred.resolve(); - }); - } else if ( entry.isDirectory ) { - entry.createReader().readEntries(function( entries ) { - var len = entries.length, - promises = [], - arr = [], // 为了保证顺序。 - i; - - for ( i = 0; i < len; i++ ) { - promises.push( me._traverseDirectoryTree( - entries[ i ], arr ) ); - } - - Base.when.apply( Base, promises ).then(function() { - results.push.apply( results, arr ); - deferred.resolve(); - }, deferred.reject ); - }); - } - - return deferred.promise(); - }, - - destroy: function() { - var elem = this.elem; - - // 还没 init 就调用 destroy - if (!elem) { - return; - } - - elem.off( 'dragenter', this.dragEnterHandler ); - elem.off( 'dragover', this.dragOverHandler ); - elem.off( 'dragleave', this.dragLeaveHandler ); - elem.off( 'drop', this.dropHandler ); - - if ( this.options.disableGlobalDnd ) { - $( document ).off( 'dragover', this.dragOverHandler ); - $( document ).off( 'drop', this.dropHandler ); - } - } - }); - }); - - /** - * @fileOverview FilePaste - */ - define('runtime/html5/filepaste',[ - 'base', - 'runtime/html5/runtime', - 'lib/file' - ], function( Base, Html5Runtime, File ) { - - return Html5Runtime.register( 'FilePaste', { - init: function() { - var opts = this.options, - elem = this.elem = opts.container, - accept = '.*', - arr, i, len, item; - - // accetp的mimeTypes中生成匹配正则。 - if ( opts.accept ) { - arr = []; - - for ( i = 0, len = opts.accept.length; i < len; i++ ) { - item = opts.accept[ i ].mimeTypes; - item && arr.push( item ); - } - - if ( arr.length ) { - accept = arr.join(','); - accept = accept.replace( /,/g, '|' ).replace( /\*/g, '.*' ); - } - } - this.accept = accept = new RegExp( accept, 'i' ); - this.hander = Base.bindFn( this._pasteHander, this ); - elem.on( 'paste', this.hander ); - }, - - _pasteHander: function( e ) { - var allowed = [], - ruid = this.getRuid(), - items, item, blob, i, len; - - e = e.originalEvent || e; - items = e.clipboardData.items; - - for ( i = 0, len = items.length; i < len; i++ ) { - item = items[ i ]; - - if ( item.kind !== 'file' || !(blob = item.getAsFile()) ) { - continue; - } - - allowed.push( new File( ruid, blob ) ); - } - - if ( allowed.length ) { - // 不阻止非文件粘贴(文字粘贴)的事件冒泡 - e.preventDefault(); - e.stopPropagation(); - this.trigger( 'paste', allowed ); - } - }, - - destroy: function() { - this.elem.off( 'paste', this.hander ); - } - }); - }); - - /** - * @fileOverview FilePicker - */ - define('runtime/html5/filepicker',[ - 'base', - 'runtime/html5/runtime' - ], function( Base, Html5Runtime ) { - - var $ = Base.$; - - return Html5Runtime.register( 'FilePicker', { - init: function() { - var container = this.getRuntime().getContainer(), - me = this, - owner = me.owner, - opts = me.options, - label = this.label = $( document.createElement('label') ), - input = this.input = $( document.createElement('input') ), - arr, i, len, mouseHandler; - - input.attr( 'type', 'file' ); - input.attr( 'name', opts.name ); - input.addClass('webuploader-element-invisible'); - - label.on( 'click', function() { - input.trigger('click'); - }); - - label.css({ - opacity: 0, - width: '100%', - height: '100%', - display: 'block', - cursor: 'pointer', - background: '#ffffff' - }); - - if ( opts.multiple ) { - input.attr( 'multiple', 'multiple' ); - } - - // @todo Firefox不支持单独指定后缀 - if ( opts.accept && opts.accept.length > 0 ) { - arr = []; - - for ( i = 0, len = opts.accept.length; i < len; i++ ) { - arr.push( opts.accept[ i ].mimeTypes ); - } - - input.attr( 'accept', arr.join(',') ); - } - - container.append( input ); - container.append( label ); - - mouseHandler = function( e ) { - owner.trigger( e.type ); - }; - - input.on( 'change', function( e ) { - var fn = arguments.callee, - clone; - - me.files = e.target.files; - - // reset input - clone = this.cloneNode( true ); - clone.value = null; - this.parentNode.replaceChild( clone, this ); - - input.off(); - input = $( clone ).on( 'change', fn ) - .on( 'mouseenter mouseleave', mouseHandler ); - - owner.trigger('change'); - }); - - label.on( 'mouseenter mouseleave', mouseHandler ); - - }, - - - getFiles: function() { - return this.files; - }, - - destroy: function() { - this.input.off(); - this.label.off(); - } - }); - }); - /** - * Terms: - * - * Uint8Array, FileReader, BlobBuilder, atob, ArrayBuffer - * @fileOverview Image控件 - */ - define('runtime/html5/util',[ - 'base' - ], function( Base ) { - - var urlAPI = window.createObjectURL && window || - window.URL && URL.revokeObjectURL && URL || - window.webkitURL, - createObjectURL = Base.noop, - revokeObjectURL = createObjectURL; - - if ( urlAPI ) { - - // 更安全的方式调用,比如android里面就能把context改成其他的对象。 - createObjectURL = function() { - return urlAPI.createObjectURL.apply( urlAPI, arguments ); - }; - - revokeObjectURL = function() { - return urlAPI.revokeObjectURL.apply( urlAPI, arguments ); - }; - } - - return { - createObjectURL: createObjectURL, - revokeObjectURL: revokeObjectURL, - - dataURL2Blob: function( dataURI ) { - var byteStr, intArray, ab, i, mimetype, parts; - - parts = dataURI.split(','); - - if ( ~parts[ 0 ].indexOf('base64') ) { - byteStr = atob( parts[ 1 ] ); - } else { - byteStr = decodeURIComponent( parts[ 1 ] ); - } - - ab = new ArrayBuffer( byteStr.length ); - intArray = new Uint8Array( ab ); - - for ( i = 0; i < byteStr.length; i++ ) { - intArray[ i ] = byteStr.charCodeAt( i ); - } - - mimetype = parts[ 0 ].split(':')[ 1 ].split(';')[ 0 ]; - - return this.arrayBufferToBlob( ab, mimetype ); - }, - - dataURL2ArrayBuffer: function( dataURI ) { - var byteStr, intArray, i, parts; - - parts = dataURI.split(','); - - if ( ~parts[ 0 ].indexOf('base64') ) { - byteStr = atob( parts[ 1 ] ); - } else { - byteStr = decodeURIComponent( parts[ 1 ] ); - } - - intArray = new Uint8Array( byteStr.length ); - - for ( i = 0; i < byteStr.length; i++ ) { - intArray[ i ] = byteStr.charCodeAt( i ); - } - - return intArray.buffer; - }, - - arrayBufferToBlob: function( buffer, type ) { - var builder = window.BlobBuilder || window.WebKitBlobBuilder, - bb; - - // android不支持直接new Blob, 只能借助blobbuilder. - if ( builder ) { - bb = new builder(); - bb.append( buffer ); - return bb.getBlob( type ); - } - - return new Blob([ buffer ], type ? { type: type } : {} ); - }, - - // 抽出来主要是为了解决android下面canvas.toDataUrl不支持jpeg. - // 你得到的结果是png. - canvasToDataUrl: function( canvas, type, quality ) { - return canvas.toDataURL( type, quality / 100 ); - }, - - // imagemeat会复写这个方法,如果用户选择加载那个文件了的话。 - parseMeta: function( blob, callback ) { - callback( false, {}); - }, - - // imagemeat会复写这个方法,如果用户选择加载那个文件了的话。 - updateImageHead: function( data ) { - return data; - } - }; - }); - /** - * Terms: - * - * Uint8Array, FileReader, BlobBuilder, atob, ArrayBuffer - * @fileOverview Image控件 - */ - define('runtime/html5/imagemeta',[ - 'runtime/html5/util' - ], function( Util ) { - - var api; - - api = { - parsers: { - 0xffe1: [] - }, - - maxMetaDataSize: 262144, - - parse: function( blob, cb ) { - var me = this, - fr = new FileReader(); - - fr.onload = function() { - cb( false, me._parse( this.result ) ); - fr = fr.onload = fr.onerror = null; - }; - - fr.onerror = function( e ) { - cb( e.message ); - fr = fr.onload = fr.onerror = null; - }; - - blob = blob.slice( 0, me.maxMetaDataSize ); - fr.readAsArrayBuffer( blob.getSource() ); - }, - - _parse: function( buffer, noParse ) { - if ( buffer.byteLength < 6 ) { - return; - } - - var dataview = new DataView( buffer ), - offset = 2, - maxOffset = dataview.byteLength - 4, - headLength = offset, - ret = {}, - markerBytes, markerLength, parsers, i; - - if ( dataview.getUint16( 0 ) === 0xffd8 ) { - - while ( offset < maxOffset ) { - markerBytes = dataview.getUint16( offset ); - - if ( markerBytes >= 0xffe0 && markerBytes <= 0xffef || - markerBytes === 0xfffe ) { - - markerLength = dataview.getUint16( offset + 2 ) + 2; - - if ( offset + markerLength > dataview.byteLength ) { - break; - } - - parsers = api.parsers[ markerBytes ]; - - if ( !noParse && parsers ) { - for ( i = 0; i < parsers.length; i += 1 ) { - parsers[ i ].call( api, dataview, offset, - markerLength, ret ); - } - } - - offset += markerLength; - headLength = offset; - } else { - break; - } - } - - if ( headLength > 6 ) { - if ( buffer.slice ) { - ret.imageHead = buffer.slice( 2, headLength ); - } else { - // Workaround for IE10, which does not yet - // support ArrayBuffer.slice: - ret.imageHead = new Uint8Array( buffer ) - .subarray( 2, headLength ); - } - } - } - - return ret; - }, - - updateImageHead: function( buffer, head ) { - var data = this._parse( buffer, true ), - buf1, buf2, bodyoffset; - - - bodyoffset = 2; - if ( data.imageHead ) { - bodyoffset = 2 + data.imageHead.byteLength; - } - - if ( buffer.slice ) { - buf2 = buffer.slice( bodyoffset ); - } else { - buf2 = new Uint8Array( buffer ).subarray( bodyoffset ); - } - - buf1 = new Uint8Array( head.byteLength + 2 + buf2.byteLength ); - - buf1[ 0 ] = 0xFF; - buf1[ 1 ] = 0xD8; - buf1.set( new Uint8Array( head ), 2 ); - buf1.set( new Uint8Array( buf2 ), head.byteLength + 2 ); - - return buf1.buffer; - } - }; - - Util.parseMeta = function() { - return api.parse.apply( api, arguments ); - }; - - Util.updateImageHead = function() { - return api.updateImageHead.apply( api, arguments ); - }; - - return api; - }); - /** - * 代码来自于:https://github.com/blueimp/JavaScript-Load-Image - * 暂时项目中只用了orientation. - * - * 去除了 Exif Sub IFD Pointer, GPS Info IFD Pointer, Exif Thumbnail. - * @fileOverview EXIF解析 - */ - - // Sample - // ==================================== - // Make : Apple - // Model : iPhone 4S - // Orientation : 1 - // XResolution : 72 [72/1] - // YResolution : 72 [72/1] - // ResolutionUnit : 2 - // Software : QuickTime 7.7.1 - // DateTime : 2013:09:01 22:53:55 - // ExifIFDPointer : 190 - // ExposureTime : 0.058823529411764705 [1/17] - // FNumber : 2.4 [12/5] - // ExposureProgram : Normal program - // ISOSpeedRatings : 800 - // ExifVersion : 0220 - // DateTimeOriginal : 2013:09:01 22:52:51 - // DateTimeDigitized : 2013:09:01 22:52:51 - // ComponentsConfiguration : YCbCr - // ShutterSpeedValue : 4.058893515764426 - // ApertureValue : 2.5260688216892597 [4845/1918] - // BrightnessValue : -0.3126686601998395 - // MeteringMode : Pattern - // Flash : Flash did not fire, compulsory flash mode - // FocalLength : 4.28 [107/25] - // SubjectArea : [4 values] - // FlashpixVersion : 0100 - // ColorSpace : 1 - // PixelXDimension : 2448 - // PixelYDimension : 3264 - // SensingMethod : One-chip color area sensor - // ExposureMode : 0 - // WhiteBalance : Auto white balance - // FocalLengthIn35mmFilm : 35 - // SceneCaptureType : Standard - define('runtime/html5/imagemeta/exif',[ - 'base', - 'runtime/html5/imagemeta' - ], function( Base, ImageMeta ) { - - var EXIF = {}; - - EXIF.ExifMap = function() { - return this; - }; - - EXIF.ExifMap.prototype.map = { - 'Orientation': 0x0112 - }; - - EXIF.ExifMap.prototype.get = function( id ) { - return this[ id ] || this[ this.map[ id ] ]; - }; - - EXIF.exifTagTypes = { - // byte, 8-bit unsigned int: - 1: { - getValue: function( dataView, dataOffset ) { - return dataView.getUint8( dataOffset ); - }, - size: 1 - }, - - // ascii, 8-bit byte: - 2: { - getValue: function( dataView, dataOffset ) { - return String.fromCharCode( dataView.getUint8( dataOffset ) ); - }, - size: 1, - ascii: true - }, - - // short, 16 bit int: - 3: { - getValue: function( dataView, dataOffset, littleEndian ) { - return dataView.getUint16( dataOffset, littleEndian ); - }, - size: 2 - }, - - // long, 32 bit int: - 4: { - getValue: function( dataView, dataOffset, littleEndian ) { - return dataView.getUint32( dataOffset, littleEndian ); - }, - size: 4 - }, - - // rational = two long values, - // first is numerator, second is denominator: - 5: { - getValue: function( dataView, dataOffset, littleEndian ) { - return dataView.getUint32( dataOffset, littleEndian ) / - dataView.getUint32( dataOffset + 4, littleEndian ); - }, - size: 8 - }, - - // slong, 32 bit signed int: - 9: { - getValue: function( dataView, dataOffset, littleEndian ) { - return dataView.getInt32( dataOffset, littleEndian ); - }, - size: 4 - }, - - // srational, two slongs, first is numerator, second is denominator: - 10: { - getValue: function( dataView, dataOffset, littleEndian ) { - return dataView.getInt32( dataOffset, littleEndian ) / - dataView.getInt32( dataOffset + 4, littleEndian ); - }, - size: 8 - } - }; - - // undefined, 8-bit byte, value depending on field: - EXIF.exifTagTypes[ 7 ] = EXIF.exifTagTypes[ 1 ]; - - EXIF.getExifValue = function( dataView, tiffOffset, offset, type, length, - littleEndian ) { - - var tagType = EXIF.exifTagTypes[ type ], - tagSize, dataOffset, values, i, str, c; - - if ( !tagType ) { - Base.log('Invalid Exif data: Invalid tag type.'); - return; - } - - tagSize = tagType.size * length; - - // Determine if the value is contained in the dataOffset bytes, - // or if the value at the dataOffset is a pointer to the actual data: - dataOffset = tagSize > 4 ? tiffOffset + dataView.getUint32( offset + 8, - littleEndian ) : (offset + 8); - - if ( dataOffset + tagSize > dataView.byteLength ) { - Base.log('Invalid Exif data: Invalid data offset.'); - return; - } - - if ( length === 1 ) { - return tagType.getValue( dataView, dataOffset, littleEndian ); - } - - values = []; - - for ( i = 0; i < length; i += 1 ) { - values[ i ] = tagType.getValue( dataView, - dataOffset + i * tagType.size, littleEndian ); - } - - if ( tagType.ascii ) { - str = ''; - - // Concatenate the chars: - for ( i = 0; i < values.length; i += 1 ) { - c = values[ i ]; - - // Ignore the terminating NULL byte(s): - if ( c === '\u0000' ) { - break; - } - str += c; - } - - return str; - } - return values; - }; - - EXIF.parseExifTag = function( dataView, tiffOffset, offset, littleEndian, - data ) { - - var tag = dataView.getUint16( offset, littleEndian ); - data.exif[ tag ] = EXIF.getExifValue( dataView, tiffOffset, offset, - dataView.getUint16( offset + 2, littleEndian ), // tag type - dataView.getUint32( offset + 4, littleEndian ), // tag length - littleEndian ); - }; - - EXIF.parseExifTags = function( dataView, tiffOffset, dirOffset, - littleEndian, data ) { - - var tagsNumber, dirEndOffset, i; - - if ( dirOffset + 6 > dataView.byteLength ) { - Base.log('Invalid Exif data: Invalid directory offset.'); - return; - } - - tagsNumber = dataView.getUint16( dirOffset, littleEndian ); - dirEndOffset = dirOffset + 2 + 12 * tagsNumber; - - if ( dirEndOffset + 4 > dataView.byteLength ) { - Base.log('Invalid Exif data: Invalid directory size.'); - return; - } - - for ( i = 0; i < tagsNumber; i += 1 ) { - this.parseExifTag( dataView, tiffOffset, - dirOffset + 2 + 12 * i, // tag offset - littleEndian, data ); - } - - // Return the offset to the next directory: - return dataView.getUint32( dirEndOffset, littleEndian ); - }; - - // EXIF.getExifThumbnail = function(dataView, offset, length) { - // var hexData, - // i, - // b; - // if (!length || offset + length > dataView.byteLength) { - // Base.log('Invalid Exif data: Invalid thumbnail data.'); - // return; - // } - // hexData = []; - // for (i = 0; i < length; i += 1) { - // b = dataView.getUint8(offset + i); - // hexData.push((b < 16 ? '0' : '') + b.toString(16)); - // } - // return 'data:image/jpeg,%' + hexData.join('%'); - // }; - - EXIF.parseExifData = function( dataView, offset, length, data ) { - - var tiffOffset = offset + 10, - littleEndian, dirOffset; - - // Check for the ASCII code for "Exif" (0x45786966): - if ( dataView.getUint32( offset + 4 ) !== 0x45786966 ) { - // No Exif data, might be XMP data instead - return; - } - if ( tiffOffset + 8 > dataView.byteLength ) { - Base.log('Invalid Exif data: Invalid segment size.'); - return; - } - - // Check for the two null bytes: - if ( dataView.getUint16( offset + 8 ) !== 0x0000 ) { - Base.log('Invalid Exif data: Missing byte alignment offset.'); - return; - } - - // Check the byte alignment: - switch ( dataView.getUint16( tiffOffset ) ) { - case 0x4949: - littleEndian = true; - break; - - case 0x4D4D: - littleEndian = false; - break; - - default: - Base.log('Invalid Exif data: Invalid byte alignment marker.'); - return; - } - - // Check for the TIFF tag marker (0x002A): - if ( dataView.getUint16( tiffOffset + 2, littleEndian ) !== 0x002A ) { - Base.log('Invalid Exif data: Missing TIFF marker.'); - return; - } - - // Retrieve the directory offset bytes, usually 0x00000008 or 8 decimal: - dirOffset = dataView.getUint32( tiffOffset + 4, littleEndian ); - // Create the exif object to store the tags: - data.exif = new EXIF.ExifMap(); - // Parse the tags of the main image directory and retrieve the - // offset to the next directory, usually the thumbnail directory: - dirOffset = EXIF.parseExifTags( dataView, tiffOffset, - tiffOffset + dirOffset, littleEndian, data ); - - // 尝试读取缩略图 - // if ( dirOffset ) { - // thumbnailData = {exif: {}}; - // dirOffset = EXIF.parseExifTags( - // dataView, - // tiffOffset, - // tiffOffset + dirOffset, - // littleEndian, - // thumbnailData - // ); - - // // Check for JPEG Thumbnail offset: - // if (thumbnailData.exif[0x0201]) { - // data.exif.Thumbnail = EXIF.getExifThumbnail( - // dataView, - // tiffOffset + thumbnailData.exif[0x0201], - // thumbnailData.exif[0x0202] // Thumbnail data length - // ); - // } - // } - }; - - ImageMeta.parsers[ 0xffe1 ].push( EXIF.parseExifData ); - return EXIF; - }); - /** - * @fileOverview Image - */ - define('runtime/html5/image',[ - 'base', - 'runtime/html5/runtime', - 'runtime/html5/util' - ], function( Base, Html5Runtime, Util ) { - - var BLANK = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs%3D'; - - return Html5Runtime.register( 'Image', { - - // flag: 标记是否被修改过。 - modified: false, - - init: function() { - var me = this, - img = new Image(); - - img.onload = function() { - - me._info = { - type: me.type, - width: this.width, - height: this.height - }; - - // 读取meta信息。 - if ( !me._metas && 'image/jpeg' === me.type ) { - Util.parseMeta( me._blob, function( error, ret ) { - me._metas = ret; - me.owner.trigger('load'); - }); - } else { - me.owner.trigger('load'); - } - }; - - img.onerror = function() { - me.owner.trigger('error'); - }; - - me._img = img; - }, - - loadFromBlob: function( blob ) { - var me = this, - img = me._img; - - me._blob = blob; - me.type = blob.type; - img.src = Util.createObjectURL( blob.getSource() ); - me.owner.once( 'load', function() { - Util.revokeObjectURL( img.src ); - }); - }, - - resize: function( width, height ) { - var canvas = this._canvas || - (this._canvas = document.createElement('canvas')); - - this._resize( this._img, canvas, width, height ); - this._blob = null; // 没用了,可以删掉了。 - this.modified = true; - this.owner.trigger( 'complete', 'resize' ); - }, - - crop: function( x, y, w, h, s ) { - var cvs = this._canvas || - (this._canvas = document.createElement('canvas')), - opts = this.options, - img = this._img, - iw = img.naturalWidth, - ih = img.naturalHeight, - orientation = this.getOrientation(); - - s = s || 1; - - // todo 解决 orientation 的问题。 - // values that require 90 degree rotation - // if ( ~[ 5, 6, 7, 8 ].indexOf( orientation ) ) { - - // switch ( orientation ) { - // case 6: - // tmp = x; - // x = y; - // y = iw * s - tmp - w; - // console.log(ih * s, tmp, w) - // break; - // } - - // (w ^= h, h ^= w, w ^= h); - // } - - cvs.width = w; - cvs.height = h; - - opts.preserveHeaders || this._rotate2Orientaion( cvs, orientation ); - this._renderImageToCanvas( cvs, img, -x, -y, iw * s, ih * s ); - - this._blob = null; // 没用了,可以删掉了。 - this.modified = true; - this.owner.trigger( 'complete', 'crop' ); - }, - - getAsBlob: function( type ) { - var blob = this._blob, - opts = this.options, - canvas; - - type = type || this.type; - - // blob需要重新生成。 - if ( this.modified || this.type !== type ) { - canvas = this._canvas; - - if ( type === 'image/jpeg' ) { - - blob = Util.canvasToDataUrl( canvas, type, opts.quality ); - - if ( opts.preserveHeaders && this._metas && - this._metas.imageHead ) { - - blob = Util.dataURL2ArrayBuffer( blob ); - blob = Util.updateImageHead( blob, - this._metas.imageHead ); - blob = Util.arrayBufferToBlob( blob, type ); - return blob; - } - } else { - blob = Util.canvasToDataUrl( canvas, type ); - } - - blob = Util.dataURL2Blob( blob ); - } - - return blob; - }, - - getAsDataUrl: function( type ) { - var opts = this.options; - - type = type || this.type; - - if ( type === 'image/jpeg' ) { - return Util.canvasToDataUrl( this._canvas, type, opts.quality ); - } else { - return this._canvas.toDataURL( type ); - } - }, - - getOrientation: function() { - return this._metas && this._metas.exif && - this._metas.exif.get('Orientation') || 1; - }, - - info: function( val ) { - - // setter - if ( val ) { - this._info = val; - return this; - } - - // getter - return this._info; - }, - - meta: function( val ) { - - // setter - if ( val ) { - this._meta = val; - return this; - } - - // getter - return this._meta; - }, - - destroy: function() { - var canvas = this._canvas; - this._img.onload = null; - - if ( canvas ) { - canvas.getContext('2d') - .clearRect( 0, 0, canvas.width, canvas.height ); - canvas.width = canvas.height = 0; - this._canvas = null; - } - - // 释放内存。非常重要,否则释放不了image的内存。 - this._img.src = BLANK; - this._img = this._blob = null; - }, - - _resize: function( img, cvs, width, height ) { - var opts = this.options, - naturalWidth = img.width, - naturalHeight = img.height, - orientation = this.getOrientation(), - scale, w, h, x, y; - - // values that require 90 degree rotation - if ( ~[ 5, 6, 7, 8 ].indexOf( orientation ) ) { - - // 交换width, height的值。 - width ^= height; - height ^= width; - width ^= height; - } - - scale = Math[ opts.crop ? 'max' : 'min' ]( width / naturalWidth, - height / naturalHeight ); - - // 不允许放大。 - opts.allowMagnify || (scale = Math.min( 1, scale )); - - w = naturalWidth * scale; - h = naturalHeight * scale; - - if ( opts.crop ) { - cvs.width = width; - cvs.height = height; - } else { - cvs.width = w; - cvs.height = h; - } - - x = (cvs.width - w) / 2; - y = (cvs.height - h) / 2; - - opts.preserveHeaders || this._rotate2Orientaion( cvs, orientation ); - - this._renderImageToCanvas( cvs, img, x, y, w, h ); - }, - - _rotate2Orientaion: function( canvas, orientation ) { - var width = canvas.width, - height = canvas.height, - ctx = canvas.getContext('2d'); - - switch ( orientation ) { - case 5: - case 6: - case 7: - case 8: - canvas.width = height; - canvas.height = width; - break; - } - - switch ( orientation ) { - case 2: // horizontal flip - ctx.translate( width, 0 ); - ctx.scale( -1, 1 ); - break; - - case 3: // 180 rotate left - ctx.translate( width, height ); - ctx.rotate( Math.PI ); - break; - - case 4: // vertical flip - ctx.translate( 0, height ); - ctx.scale( 1, -1 ); - break; - - case 5: // vertical flip + 90 rotate right - ctx.rotate( 0.5 * Math.PI ); - ctx.scale( 1, -1 ); - break; - - case 6: // 90 rotate right - ctx.rotate( 0.5 * Math.PI ); - ctx.translate( 0, -height ); - break; - - case 7: // horizontal flip + 90 rotate right - ctx.rotate( 0.5 * Math.PI ); - ctx.translate( width, -height ); - ctx.scale( -1, 1 ); - break; - - case 8: // 90 rotate left - ctx.rotate( -0.5 * Math.PI ); - ctx.translate( -width, 0 ); - break; - } - }, - - // https://github.com/stomita/ios-imagefile-megapixel/ - // blob/master/src/megapix-image.js - _renderImageToCanvas: (function() { - - // 如果不是ios, 不需要这么复杂! - if ( !Base.os.ios ) { - return function( canvas ) { - var args = Base.slice( arguments, 1 ), - ctx = canvas.getContext('2d'); - - ctx.drawImage.apply( ctx, args ); - }; - } - - /** - * Detecting vertical squash in loaded image. - * Fixes a bug which squash image vertically while drawing into - * canvas for some images. - */ - function detectVerticalSquash( img, iw, ih ) { - var canvas = document.createElement('canvas'), - ctx = canvas.getContext('2d'), - sy = 0, - ey = ih, - py = ih, - data, alpha, ratio; - - - canvas.width = 1; - canvas.height = ih; - ctx.drawImage( img, 0, 0 ); - data = ctx.getImageData( 0, 0, 1, ih ).data; - - // search image edge pixel position in case - // it is squashed vertically. - while ( py > sy ) { - alpha = data[ (py - 1) * 4 + 3 ]; - - if ( alpha === 0 ) { - ey = py; - } else { - sy = py; - } - - py = (ey + sy) >> 1; - } - - ratio = (py / ih); - return (ratio === 0) ? 1 : ratio; - } - - // fix ie7 bug - // http://stackoverflow.com/questions/11929099/ - // html5-canvas-drawimage-ratio-bug-ios - if ( Base.os.ios >= 7 ) { - return function( canvas, img, x, y, w, h ) { - var iw = img.naturalWidth, - ih = img.naturalHeight, - vertSquashRatio = detectVerticalSquash( img, iw, ih ); - - return canvas.getContext('2d').drawImage( img, 0, 0, - iw * vertSquashRatio, ih * vertSquashRatio, - x, y, w, h ); - }; - } - - /** - * Detect subsampling in loaded image. - * In iOS, larger images than 2M pixels may be - * subsampled in rendering. - */ - function detectSubsampling( img ) { - var iw = img.naturalWidth, - ih = img.naturalHeight, - canvas, ctx; - - // subsampling may happen overmegapixel image - if ( iw * ih > 1024 * 1024 ) { - canvas = document.createElement('canvas'); - canvas.width = canvas.height = 1; - ctx = canvas.getContext('2d'); - ctx.drawImage( img, -iw + 1, 0 ); - - // subsampled image becomes half smaller in rendering size. - // check alpha channel value to confirm image is covering - // edge pixel or not. if alpha value is 0 - // image is not covering, hence subsampled. - return ctx.getImageData( 0, 0, 1, 1 ).data[ 3 ] === 0; - } else { - return false; - } - } - - - return function( canvas, img, x, y, width, height ) { - var iw = img.naturalWidth, - ih = img.naturalHeight, - ctx = canvas.getContext('2d'), - subsampled = detectSubsampling( img ), - doSquash = this.type === 'image/jpeg', - d = 1024, - sy = 0, - dy = 0, - tmpCanvas, tmpCtx, vertSquashRatio, dw, dh, sx, dx; - - if ( subsampled ) { - iw /= 2; - ih /= 2; - } - - ctx.save(); - tmpCanvas = document.createElement('canvas'); - tmpCanvas.width = tmpCanvas.height = d; - - tmpCtx = tmpCanvas.getContext('2d'); - vertSquashRatio = doSquash ? - detectVerticalSquash( img, iw, ih ) : 1; - - dw = Math.ceil( d * width / iw ); - dh = Math.ceil( d * height / ih / vertSquashRatio ); - - while ( sy < ih ) { - sx = 0; - dx = 0; - while ( sx < iw ) { - tmpCtx.clearRect( 0, 0, d, d ); - tmpCtx.drawImage( img, -sx, -sy ); - ctx.drawImage( tmpCanvas, 0, 0, d, d, - x + dx, y + dy, dw, dh ); - sx += d; - dx += dw; - } - sy += d; - dy += dh; - } - ctx.restore(); - tmpCanvas = tmpCtx = null; - }; - })() - }); - }); - /** - * @fileOverview Transport - * @todo 支持chunked传输,优势: - * 可以将大文件分成小块,挨个传输,可以提高大文件成功率,当失败的时候,也只需要重传那小部分, - * 而不需要重头再传一次。另外断点续传也需要用chunked方式。 - */ - define('runtime/html5/transport',[ - 'base', - 'runtime/html5/runtime' - ], function( Base, Html5Runtime ) { - - var noop = Base.noop, - $ = Base.$; - - return Html5Runtime.register( 'Transport', { - init: function() { - this._status = 0; - this._response = null; - }, - - send: function() { - var owner = this.owner, - opts = this.options, - xhr = this._initAjax(), - blob = owner._blob, - server = opts.server, - formData, binary, fr; - - if ( opts.sendAsBinary ) { - server += (/\?/.test( server ) ? '&' : '?') + - $.param( owner._formData ); - - binary = blob.getSource(); - } else { - formData = new FormData(); - $.each( owner._formData, function( k, v ) { - formData.append( k, v ); - }); - - formData.append( opts.fileVal, blob.getSource(), - opts.filename || owner._formData.name || '' ); - } - - if ( opts.withCredentials && 'withCredentials' in xhr ) { - xhr.open( opts.method, server, true ); - xhr.withCredentials = true; - } else { - xhr.open( opts.method, server ); - } - - this._setRequestHeader( xhr, opts.headers ); - - if ( binary ) { - // 强制设置成 content-type 为文件流。 - xhr.overrideMimeType && - xhr.overrideMimeType('application/octet-stream'); - - // android直接发送blob会导致服务端接收到的是空文件。 - // bug详情。 - // https://code.google.com/p/android/issues/detail?id=39882 - // 所以先用fileReader读取出来再通过arraybuffer的方式发送。 - if ( Base.os.android ) { - fr = new FileReader(); - - fr.onload = function() { - xhr.send( this.result ); - fr = fr.onload = null; - }; - - fr.readAsArrayBuffer( binary ); - } else { - xhr.send( binary ); - } - } else { - xhr.send( formData ); - } - }, - - getResponse: function() { - return this._response; - }, - - getResponseAsJson: function() { - return this._parseJson( this._response ); - }, - - getStatus: function() { - return this._status; - }, - - abort: function() { - var xhr = this._xhr; - - if ( xhr ) { - xhr.upload.onprogress = noop; - xhr.onreadystatechange = noop; - xhr.abort(); - - this._xhr = xhr = null; - } - }, - - destroy: function() { - this.abort(); - }, - - _initAjax: function() { - var me = this, - xhr = new XMLHttpRequest(), - opts = this.options; - - if ( opts.withCredentials && !('withCredentials' in xhr) && - typeof XDomainRequest !== 'undefined' ) { - xhr = new XDomainRequest(); - } - - xhr.upload.onprogress = function( e ) { - var percentage = 0; - - if ( e.lengthComputable ) { - percentage = e.loaded / e.total; - } - - return me.trigger( 'progress', percentage ); - }; - - xhr.onreadystatechange = function() { - - if ( xhr.readyState !== 4 ) { - return; - } - - xhr.upload.onprogress = noop; - xhr.onreadystatechange = noop; - me._xhr = null; - me._status = xhr.status; - - if ( xhr.status >= 200 && xhr.status < 300 ) { - me._response = xhr.responseText; - return me.trigger('load'); - } else if ( xhr.status >= 500 && xhr.status < 600 ) { - me._response = xhr.responseText; - return me.trigger( 'error', 'server' ); - } - - - return me.trigger( 'error', me._status ? 'http' : 'abort' ); - }; - - me._xhr = xhr; - return xhr; - }, - - _setRequestHeader: function( xhr, headers ) { - $.each( headers, function( key, val ) { - xhr.setRequestHeader( key, val ); - }); - }, - - _parseJson: function( str ) { - var json; - - try { - json = JSON.parse( str ); - } catch ( ex ) { - json = {}; - } - - return json; - } - }); - }); - /** - * @fileOverview 只有html5实现的文件版本。 - */ - define('preset/html5only',[ - 'base', - - // widgets - 'widgets/filednd', - 'widgets/filepaste', - 'widgets/filepicker', - 'widgets/image', - 'widgets/queue', - 'widgets/runtime', - 'widgets/upload', - 'widgets/validator', - - // runtimes - // html5 - 'runtime/html5/blob', - 'runtime/html5/dnd', - 'runtime/html5/filepaste', - 'runtime/html5/filepicker', - 'runtime/html5/imagemeta/exif', - 'runtime/html5/image', - 'runtime/html5/transport' - ], function( Base ) { - return Base; - }); - define('webuploader',[ - 'preset/html5only' - ], function( preset ) { - return preset; - }); - return require('webuploader'); -}); diff --git a/static/plugins/webuploader/webuploader.html5only.min.js b/static/plugins/webuploader/webuploader.html5only.min.js deleted file mode 100644 index 8710e4ea..00000000 --- a/static/plugins/webuploader/webuploader.html5only.min.js +++ /dev/null @@ -1,2 +0,0 @@ -/* WebUploader 0.1.5 */!function(a,b){var c,d={},e=function(a,b){var c,d,e;if("string"==typeof a)return h(a);for(c=[],d=a.length,e=0;d>e;e++)c.push(h(a[e]));return b.apply(null,c)},f=function(a,b,c){2===arguments.length&&(c=b,b=null),e(b||[],function(){g(a,c,arguments)})},g=function(a,b,c){var f,g={exports:b};"function"==typeof b&&(c.length||(c=[e,g.exports,g]),f=b.apply(null,c),void 0!==f&&(g.exports=f)),d[a]=g.exports},h=function(b){var c=d[b]||a[b];if(!c)throw new Error("`"+b+"` is undefined");return c},i=function(a){var b,c,e,f,g,h;h=function(a){return a&&a.charAt(0).toUpperCase()+a.substr(1)};for(b in d)if(c=a,d.hasOwnProperty(b)){for(e=b.split("/"),g=h(e.pop());f=h(e.shift());)c[f]=c[f]||{},c=c[f];c[g]=d[b]}return a},j=function(c){return a.__dollar=c,i(b(a,f,e))};"object"==typeof module&&"object"==typeof module.exports?module.exports=j():"function"==typeof define&&define.amd?define(["jquery"],j):(c=a.WebUploader,a.WebUploader=j(),a.WebUploader.noConflict=function(){a.WebUploader=c})}(window,function(a,b,c){return b("dollar-third",[],function(){var b=a.__dollar||a.jQuery||a.Zepto;if(!b)throw new Error("jQuery or Zepto not found!");return b}),b("dollar",["dollar-third"],function(a){return a}),b("promise-third",["dollar"],function(a){return{Deferred:a.Deferred,when:a.when,isPromise:function(a){return a&&"function"==typeof a.then}}}),b("promise",["promise-third"],function(a){return a}),b("base",["dollar","promise"],function(b,c){function d(a){return function(){return h.apply(a,arguments)}}function e(a,b){return function(){return a.apply(b,arguments)}}function f(a){var b;return Object.create?Object.create(a):(b=function(){},b.prototype=a,new b)}var g=function(){},h=Function.call;return{version:"0.1.5",$:b,Deferred:c.Deferred,isPromise:c.isPromise,when:c.when,browser:function(a){var b={},c=a.match(/WebKit\/([\d.]+)/),d=a.match(/Chrome\/([\d.]+)/)||a.match(/CriOS\/([\d.]+)/),e=a.match(/MSIE\s([\d\.]+)/)||a.match(/(?:trident)(?:.*rv:([\w.]+))?/i),f=a.match(/Firefox\/([\d.]+)/),g=a.match(/Safari\/([\d.]+)/),h=a.match(/OPR\/([\d.]+)/);return c&&(b.webkit=parseFloat(c[1])),d&&(b.chrome=parseFloat(d[1])),e&&(b.ie=parseFloat(e[1])),f&&(b.firefox=parseFloat(f[1])),g&&(b.safari=parseFloat(g[1])),h&&(b.opera=parseFloat(h[1])),b}(navigator.userAgent),os:function(a){var b={},c=a.match(/(?:Android);?[\s\/]+([\d.]+)?/),d=a.match(/(?:iPad|iPod|iPhone).*OS\s([\d_]+)/);return c&&(b.android=parseFloat(c[1])),d&&(b.ios=parseFloat(d[1].replace(/_/g,"."))),b}(navigator.userAgent),inherits:function(a,c,d){var e;return"function"==typeof c?(e=c,c=null):e=c&&c.hasOwnProperty("constructor")?c.constructor:function(){return a.apply(this,arguments)},b.extend(!0,e,a,d||{}),e.__super__=a.prototype,e.prototype=f(a.prototype),c&&b.extend(!0,e.prototype,c),e},noop:g,bindFn:e,log:function(){return a.console?e(console.log,console):g}(),nextTick:function(){return function(a){setTimeout(a,1)}}(),slice:d([].slice),guid:function(){var a=0;return function(b){for(var c=(+new Date).toString(32),d=0;5>d;d++)c+=Math.floor(65535*Math.random()).toString(32);return(b||"wu_")+c+(a++).toString(32)}}(),formatSize:function(a,b,c){var d;for(c=c||["B","K","M","G","TB"];(d=c.shift())&&a>1024;)a/=1024;return("B"===d?a:a.toFixed(b||2))+d}}}),b("mediator",["base"],function(a){function b(a,b,c,d){return f.grep(a,function(a){return!(!a||b&&a.e!==b||c&&a.cb!==c&&a.cb._cb!==c||d&&a.ctx!==d)})}function c(a,b,c){f.each((a||"").split(h),function(a,d){c(d,b)})}function d(a,b){for(var c,d=!1,e=-1,f=a.length;++e1?void(d.isPlainObject(b)&&d.isPlainObject(c[a])?d.extend(c[a],b):c[a]=b):a?c[a]:c},getStats:function(){var a=this.request("get-stats");return a?{successNum:a.numOfSuccess,progressNum:a.numOfProgress,cancelNum:a.numOfCancel,invalidNum:a.numOfInvalid,uploadFailNum:a.numOfUploadFailed,queueNum:a.numOfQueue,interruptNum:a.numofInterrupt}:{}},trigger:function(a){var c=[].slice.call(arguments,1),e=this.options,f="on"+a.substring(0,1).toUpperCase()+a.substring(1);return b.trigger.apply(this,arguments)===!1||d.isFunction(e[f])&&e[f].apply(this,c)===!1||d.isFunction(this[f])&&this[f].apply(this,c)===!1||b.trigger.apply(b,[this,a].concat(c))===!1?!1:!0},destroy:function(){this.request("destroy",arguments),this.off()},request:a.noop}),a.create=c.create=function(a){return new c(a)},a.Uploader=c,c}),b("runtime/runtime",["base","mediator"],function(a,b){function c(b){this.options=d.extend({container:document.body},b),this.uid=a.guid("rt_")}var d=a.$,e={},f=function(a){for(var b in a)if(a.hasOwnProperty(b))return b;return null};return d.extend(c.prototype,{getContainer:function(){var a,b,c=this.options;return this._container?this._container:(a=d(c.container||document.body),b=d(document.createElement("div")),b.attr("id","rt_"+this.uid),b.css({position:"absolute",top:"0px",left:"0px",width:"1px",height:"1px",overflow:"hidden"}),a.append(b),a.addClass("webuploader-container"),this._container=b,this._parent=a,b)},init:a.noop,exec:a.noop,destroy:function(){this._container&&this._container.remove(),this._parent&&this._parent.removeClass("webuploader-container"),this.off()}}),c.orders="html5,flash",c.addRuntime=function(a,b){e[a]=b},c.hasRuntime=function(a){return!!(a?e[a]:f(e))},c.create=function(a,b){var g,h;if(b=b||c.orders,d.each(b.split(/\s*,\s*/g),function(){return e[this]?(g=this,!1):void 0}),g=g||f(e),!g)throw new Error("Runtime Error");return h=new e[g](a)},b.installTo(c.prototype),c}),b("runtime/client",["base","mediator","runtime/runtime"],function(a,b,c){function d(b,d){var f,g=a.Deferred();this.uid=a.guid("client_"),this.runtimeReady=function(a){return g.done(a)},this.connectRuntime=function(b,h){if(f)throw new Error("already connected!");return g.done(h),"string"==typeof b&&e.get(b)&&(f=e.get(b)),f=f||e.get(null,d),f?(a.$.extend(f.options,b),f.__promise.then(g.resolve),f.__client++):(f=c.create(b,b.runtimeOrder),f.__promise=g.promise(),f.once("ready",g.resolve),f.init(),e.add(f),f.__client=1),d&&(f.__standalone=d),f},this.getRuntime=function(){return f},this.disconnectRuntime=function(){f&&(f.__client--,f.__client<=0&&(e.remove(f),delete f.__promise,f.destroy()),f=null)},this.exec=function(){if(f){var c=a.slice(arguments);return b&&c.unshift(b),f.exec.apply(this,c)}},this.getRuid=function(){return f&&f.uid},this.destroy=function(a){return function(){a&&a.apply(this,arguments),this.trigger("destroy"),this.off(),this.exec("destroy"),this.disconnectRuntime()}}(this.destroy)}var e;return e=function(){var a={};return{add:function(b){a[b.uid]=b},get:function(b,c){var d;if(b)return a[b];for(d in a)if(!c||!a[d].__standalone)return a[d];return null},remove:function(b){delete a[b.uid]}}}(),b.installTo(d.prototype),d}),b("lib/dnd",["base","mediator","runtime/client"],function(a,b,c){function d(a){a=this.options=e.extend({},d.options,a),a.container=e(a.container),a.container.length&&c.call(this,"DragAndDrop")}var e=a.$;return d.options={accept:null,disableGlobalDnd:!1},a.inherits(c,{constructor:d,init:function(){var a=this;a.connectRuntime(a.options,function(){a.exec("init"),a.trigger("ready")})}}),b.installTo(d.prototype),d}),b("widgets/widget",["base","uploader"],function(a,b){function c(a){if(!a)return!1;var b=a.length,c=e.type(a);return 1===a.nodeType&&b?!0:"array"===c||"function"!==c&&"string"!==c&&(0===b||"number"==typeof b&&b>0&&b-1 in a)}function d(a){this.owner=a,this.options=a.options}var e=a.$,f=b.prototype._init,g=b.prototype.destroy,h={},i=[];return e.extend(d.prototype,{init:a.noop,invoke:function(a,b){var c=this.responseMap;return c&&a in c&&c[a]in this&&e.isFunction(this[c[a]])?this[c[a]].apply(this,b):h},request:function(){return this.owner.request.apply(this.owner,arguments)}}),e.extend(b.prototype,{_init:function(){var a=this,b=a._widgets=[],c=a.options.disableWidgets||"";return e.each(i,function(d,e){(!c||!~c.indexOf(e._name))&&b.push(new e(a))}),f.apply(a,arguments)},request:function(b,d,e){var f,g,i,j,k=0,l=this._widgets,m=l&&l.length,n=[],o=[];for(d=c(d)?d:[d];m>k;k++)f=l[k],g=f.invoke(b,d),g!==h&&(a.isPromise(g)?o.push(g):n.push(g));return e||o.length?(i=a.when.apply(a,o),j=i.pipe?"pipe":"then",i[j](function(){var b=a.Deferred(),c=arguments;return 1===c.length&&(c=c[0]),setTimeout(function(){b.resolve(c)},1),b.promise()})[e?j:"done"](e||a.noop)):n[0]},destroy:function(){g.apply(this,arguments),this._widgets=null}}),b.register=d.register=function(b,c){var f,g={init:"init",destroy:"destroy",name:"anonymous"};return 1===arguments.length?(c=b,e.each(c,function(a){return"_"===a[0]||"name"===a?void("name"===a&&(g.name=c.name)):void(g[a.replace(/[A-Z]/g,"-$&").toLowerCase()]=a)})):g=e.extend(g,b),c.responseMap=g,f=a.inherits(d,c),f._name=g.name,i.push(f),f},b.unRegister=d.unRegister=function(a){if(a&&"anonymous"!==a)for(var b=i.length;b--;)i[b]._name===a&&i.splice(b,1)},d}),b("widgets/filednd",["base","uploader","lib/dnd","widgets/widget"],function(a,b,c){var d=a.$;return b.options.dnd="",b.register({name:"dnd",init:function(b){if(b.dnd&&"html5"===this.request("predict-runtime-type")){var e,f=this,g=a.Deferred(),h=d.extend({},{disableGlobalDnd:b.disableGlobalDnd,container:b.dnd,accept:b.accept});return this.dnd=e=new c(h),e.once("ready",g.resolve),e.on("drop",function(a){f.request("add-file",[a])}),e.on("accept",function(a){return f.owner.trigger("dndAccept",a)}),e.init(),g.promise()}},destroy:function(){this.dnd&&this.dnd.destroy()}})}),b("lib/filepaste",["base","mediator","runtime/client"],function(a,b,c){function d(a){a=this.options=e.extend({},a),a.container=e(a.container||document.body),c.call(this,"FilePaste")}var e=a.$;return a.inherits(c,{constructor:d,init:function(){var a=this;a.connectRuntime(a.options,function(){a.exec("init"),a.trigger("ready")})}}),b.installTo(d.prototype),d}),b("widgets/filepaste",["base","uploader","lib/filepaste","widgets/widget"],function(a,b,c){var d=a.$;return b.register({name:"paste",init:function(b){if(b.paste&&"html5"===this.request("predict-runtime-type")){var e,f=this,g=a.Deferred(),h=d.extend({},{container:b.paste,accept:b.accept});return this.paste=e=new c(h),e.once("ready",g.resolve),e.on("paste",function(a){f.owner.request("add-file",[a])}),e.init(),g.promise()}},destroy:function(){this.paste&&this.paste.destroy()}})}),b("lib/blob",["base","runtime/client"],function(a,b){function c(a,c){var d=this;d.source=c,d.ruid=a,this.size=c.size||0,this.type=!c.type&&this.ext&&~"jpg,jpeg,png,gif,bmp".indexOf(this.ext)?"image/"+("jpg"===this.ext?"jpeg":this.ext):c.type||"application/octet-stream",b.call(d,"Blob"),this.uid=c.uid||this.uid,a&&d.connectRuntime(a)}return a.inherits(b,{constructor:c,slice:function(a,b){return this.exec("slice",a,b)},getSource:function(){return this.source}}),c}),b("lib/file",["base","lib/blob"],function(a,b){function c(a,c){var f;this.name=c.name||"untitled"+d++,f=e.exec(c.name)?RegExp.$1.toLowerCase():"",!f&&c.type&&(f=/\/(jpg|jpeg|png|gif|bmp)$/i.exec(c.type)?RegExp.$1.toLowerCase():"",this.name+="."+f),this.ext=f,this.lastModifiedDate=c.lastModifiedDate||(new Date).toLocaleString(),b.apply(this,arguments)}var d=1,e=/\.([^.]+)$/;return a.inherits(b,c)}),b("lib/filepicker",["base","runtime/client","lib/file"],function(b,c,d){function e(a){if(a=this.options=f.extend({},e.options,a),a.container=f(a.id),!a.container.length)throw new Error("按钮指定错误");a.innerHTML=a.innerHTML||a.label||a.container.html()||"",a.button=f(a.button||document.createElement("div")),a.button.html(a.innerHTML),a.container.html(a.button),c.call(this,"FilePicker",!0)}var f=b.$;return e.options={button:null,container:null,label:null,innerHTML:null,multiple:!0,accept:null,name:"file"},b.inherits(c,{constructor:e,init:function(){var c=this,e=c.options,g=e.button;g.addClass("webuploader-pick"),c.on("all",function(a){var b;switch(a){case"mouseenter":g.addClass("webuploader-pick-hover");break;case"mouseleave":g.removeClass("webuploader-pick-hover");break;case"change":b=c.exec("getFiles"),c.trigger("select",f.map(b,function(a){return a=new d(c.getRuid(),a),a._refer=e.container,a}),e.container)}}),c.connectRuntime(e,function(){c.refresh(),c.exec("init",e),c.trigger("ready")}),this._resizeHandler=b.bindFn(this.refresh,this),f(a).on("resize",this._resizeHandler)},refresh:function(){var a=this.getRuntime().getContainer(),b=this.options.button,c=b.outerWidth?b.outerWidth():b.width(),d=b.outerHeight?b.outerHeight():b.height(),e=b.offset();c&&d&&a.css({bottom:"auto",right:"auto",width:c+"px",height:d+"px"}).offset(e)},enable:function(){var a=this.options.button;a.removeClass("webuploader-pick-disable"),this.refresh()},disable:function(){var a=this.options.button;this.getRuntime().getContainer().css({top:"-99999px"}),a.addClass("webuploader-pick-disable")},destroy:function(){var b=this.options.button;f(a).off("resize",this._resizeHandler),b.removeClass("webuploader-pick-disable webuploader-pick-hover webuploader-pick")}}),e}),b("widgets/filepicker",["base","uploader","lib/filepicker","widgets/widget"],function(a,b,c){var d=a.$;return d.extend(b.options,{pick:null,accept:null}),b.register({name:"picker",init:function(a){return this.pickers=[],a.pick&&this.addBtn(a.pick)},refresh:function(){d.each(this.pickers,function(){this.refresh()})},addBtn:function(b){var e=this,f=e.options,g=f.accept,h=[];if(b)return d.isPlainObject(b)||(b={id:b}),d(b.id).each(function(){var i,j,k;k=a.Deferred(),i=d.extend({},b,{accept:d.isPlainObject(g)?[g]:g,swf:f.swf,runtimeOrder:f.runtimeOrder,id:this}),j=new c(i),j.once("ready",k.resolve),j.on("select",function(a){e.owner.request("add-file",[a])}),j.init(),e.pickers.push(j),h.push(k.promise())}),a.when.apply(a,h)},disable:function(){d.each(this.pickers,function(){this.disable()})},enable:function(){d.each(this.pickers,function(){this.enable()})},destroy:function(){d.each(this.pickers,function(){this.destroy()}),this.pickers=null}})}),b("lib/image",["base","runtime/client","lib/blob"],function(a,b,c){function d(a){this.options=e.extend({},d.options,a),b.call(this,"Image"),this.on("load",function(){this._info=this.exec("info"),this._meta=this.exec("meta")})}var e=a.$;return d.options={quality:90,crop:!1,preserveHeaders:!1,allowMagnify:!1},a.inherits(b,{constructor:d,info:function(a){return a?(this._info=a,this):this._info},meta:function(a){return a?(this._meta=a,this):this._meta},loadFromBlob:function(a){var b=this,c=a.getRuid();this.connectRuntime(c,function(){b.exec("init",b.options),b.exec("loadFromBlob",a)})},resize:function(){var b=a.slice(arguments);return this.exec.apply(this,["resize"].concat(b))},crop:function(){var b=a.slice(arguments);return this.exec.apply(this,["crop"].concat(b))},getAsDataUrl:function(a){return this.exec("getAsDataUrl",a)},getAsBlob:function(a){var b=this.exec("getAsBlob",a);return new c(this.getRuid(),b)}}),d}),b("widgets/image",["base","uploader","lib/image","widgets/widget"],function(a,b,c){var d,e=a.$;return d=function(a){var b=0,c=[],d=function(){for(var d;c.length&&a>b;)d=c.shift(),b+=d[0],d[1]()};return function(a,e,f){c.push([e,f]),a.once("destroy",function(){b-=e,setTimeout(d,1)}),setTimeout(d,1)}}(5242880),e.extend(b.options,{thumb:{width:110,height:110,quality:70,allowMagnify:!0,crop:!0,preserveHeaders:!1,type:"image/jpeg"},compress:{width:1600,height:1600,quality:90,allowMagnify:!1,crop:!1,preserveHeaders:!0}}),b.register({name:"image",makeThumb:function(a,b,f,g){var h,i;return a=this.request("get-file",a),a.type.match(/^image/)?(h=e.extend({},this.options.thumb),e.isPlainObject(f)&&(h=e.extend(h,f),f=null),f=f||h.width,g=g||h.height,i=new c(h),i.once("load",function(){a._info=a._info||i.info(),a._meta=a._meta||i.meta(),1>=f&&f>0&&(f=a._info.width*f),1>=g&&g>0&&(g=a._info.height*g),i.resize(f,g)}),i.once("complete",function(){b(!1,i.getAsDataUrl(h.type)),i.destroy()}),i.once("error",function(a){b(a||!0),i.destroy()}),void d(i,a.source.size,function(){a._info&&i.info(a._info),a._meta&&i.meta(a._meta),i.loadFromBlob(a.source)})):void b(!0)},beforeSendFile:function(b){var d,f,g=this.options.compress||this.options.resize,h=g&&g.compressSize||0,i=g&&g.noCompressIfLarger||!1;return b=this.request("get-file",b),!g||!~"image/jpeg,image/jpg".indexOf(b.type)||b.size=a&&a>0&&(a=b._info.width*a),1>=c&&c>0&&(c=b._info.height*c),d.resize(a,c)}),d.once("complete",function(){var a,c;try{a=d.getAsBlob(g.type),c=b.size,(!i||a.sizeb;b++)if(c=this._queue[b],a===c.getStatus())return c;return null},sort:function(a){"function"==typeof a&&this._queue.sort(a)},getFiles:function(){for(var a,b=[].slice.call(arguments,0),c=[],d=0,f=this._queue.length;f>d;d++)a=this._queue[d],(!b.length||~e.inArray(a.getStatus(),b))&&c.push(a);return c},removeFile:function(a){var b=this._map[a.id];b&&(delete this._map[a.id],a.destroy(),this.stats.numofDeleted++)},_fileAdded:function(a){var b=this,c=this._map[a.id];c||(this._map[a.id]=a,a.on("statuschange",function(a,c){b._onFileStatusChange(a,c)}))},_onFileStatusChange:function(a,b){var c=this.stats;switch(b){case f.PROGRESS:c.numOfProgress--;break;case f.QUEUED:c.numOfQueue--;break;case f.ERROR:c.numOfUploadFailed--;break;case f.INVALID:c.numOfInvalid--;break;case f.INTERRUPT:c.numofInterrupt--}switch(a){case f.QUEUED:c.numOfQueue++;break;case f.PROGRESS:c.numOfProgress++;break;case f.ERROR:c.numOfUploadFailed++;break;case f.COMPLETE:c.numOfSuccess++;break;case f.CANCELLED:c.numOfCancel++;break;case f.INVALID:c.numOfInvalid++;break;case f.INTERRUPT:c.numofInterrupt++}}}),b.installTo(d.prototype),d}),b("widgets/queue",["base","uploader","queue","file","lib/file","runtime/client","widgets/widget"],function(a,b,c,d,e,f){var g=a.$,h=/\.\w+$/,i=d.Status;return b.register({name:"queue",init:function(b){var d,e,h,i,j,k,l,m=this;if(g.isPlainObject(b.accept)&&(b.accept=[b.accept]),b.accept){for(j=[],h=0,e=b.accept.length;e>h;h++)i=b.accept[h].extensions,i&&j.push(i);j.length&&(k="\\."+j.join(",").replace(/,/g,"$|\\.").replace(/\*/g,".*")+"$"),m.accept=new RegExp(k,"i")}return m.queue=new c,m.stats=m.queue.stats,"html5"===this.request("predict-runtime-type")?(d=a.Deferred(),this.placeholder=l=new f("Placeholder"),l.connectRuntime({runtimeOrder:"html5"},function(){m._ruid=l.getRuid(),d.resolve()}),d.promise()):void 0},_wrapFile:function(a){if(!(a instanceof d)){if(!(a instanceof e)){if(!this._ruid)throw new Error("Can't add external files.");a=new e(this._ruid,a)}a=new d(a)}return a},acceptFile:function(a){var b=!a||!a.size||this.accept&&h.exec(a.name)&&!this.accept.test(a.name);return!b},_addFile:function(a){var b=this;return a=b._wrapFile(a),b.owner.trigger("beforeFileQueued",a)?b.acceptFile(a)?(b.queue.append(a),b.owner.trigger("fileQueued",a),a):void b.owner.trigger("error","Q_TYPE_DENIED",a):void 0},getFile:function(a){return this.queue.getFile(a)},addFile:function(a){var b=this;a.length||(a=[a]),a=g.map(a,function(a){return b._addFile(a)}),b.owner.trigger("filesQueued",a),b.options.auto&&setTimeout(function(){b.request("start-upload")},20)},getStats:function(){return this.stats},removeFile:function(a,b){var c=this;a=a.id?a:c.queue.getFile(a),this.request("cancel-file",a),b&&this.queue.removeFile(a)},getFiles:function(){return this.queue.getFiles.apply(this.queue,arguments)},fetchFile:function(){return this.queue.fetch.apply(this.queue,arguments)},retry:function(a,b){var c,d,e,f=this;if(a)return a=a.id?a:f.queue.getFile(a),a.setStatus(i.QUEUED),void(b||f.request("start-upload"));for(c=f.queue.getFiles(i.ERROR),d=0,e=c.length;e>d;d++)a=c[d],a.setStatus(i.QUEUED);f.request("start-upload")},sortFiles:function(){return this.queue.sort.apply(this.queue,arguments)},reset:function(){this.owner.trigger("reset"),this.queue=new c,this.stats=this.queue.stats},destroy:function(){this.reset(),this.placeholder&&this.placeholder.destroy()}})}),b("widgets/runtime",["uploader","runtime/runtime","widgets/widget"],function(a,b){return a.support=function(){return b.hasRuntime.apply(b,arguments)},a.register({name:"runtime",init:function(){if(!this.predictRuntimeType())throw Error("Runtime Error")},predictRuntimeType:function(){var a,c,d=this.options.runtimeOrder||b.orders,e=this.type;if(!e)for(d=d.split(/\s*,\s*/g),a=0,c=d.length;c>a;a++)if(b.hasRuntime(d[a])){this.type=e=d[a];break}return e}})}),b("lib/transport",["base","runtime/client","mediator"],function(a,b,c){function d(a){var c=this;a=c.options=e.extend(!0,{},d.options,a||{}),b.call(this,"Transport"),this._blob=null,this._formData=a.formData||{},this._headers=a.headers||{},this.on("progress",this._timeout),this.on("load error",function(){c.trigger("progress",1),clearTimeout(c._timer)})}var e=a.$;return d.options={server:"",method:"POST",withCredentials:!1,fileVal:"file",timeout:12e4,formData:{},headers:{},sendAsBinary:!1},e.extend(d.prototype,{appendBlob:function(a,b,c){var d=this,e=d.options;d.getRuid()&&d.disconnectRuntime(),d.connectRuntime(b.ruid,function(){d.exec("init")}),d._blob=b,e.fileVal=a||e.fileVal,e.filename=c||e.filename},append:function(a,b){"object"==typeof a?e.extend(this._formData,a):this._formData[a]=b},setRequestHeader:function(a,b){"object"==typeof a?e.extend(this._headers,a):this._headers[a]=b},send:function(a){this.exec("send",a),this._timeout()},abort:function(){return clearTimeout(this._timer),this.exec("abort")},destroy:function(){this.trigger("destroy"),this.off(),this.exec("destroy"),this.disconnectRuntime()},getResponse:function(){return this.exec("getResponse")},getResponseAsJson:function(){return this.exec("getResponseAsJson")},getStatus:function(){return this.exec("getStatus")},_timeout:function(){var a=this,b=a.options.timeout;b&&(clearTimeout(a._timer),a._timer=setTimeout(function(){a.abort(),a.trigger("error","timeout")},b))}}),c.installTo(d.prototype),d}),b("widgets/upload",["base","uploader","file","lib/transport","widgets/widget"],function(a,b,c,d){function e(a,b){var c,d,e=[],f=a.source,g=f.size,h=b?Math.ceil(g/b):1,i=0,j=0;for(d={file:a,has:function(){return!!e.length},shift:function(){return e.shift()},unshift:function(a){e.unshift(a)}};h>j;)c=Math.min(b,g-i),e.push({file:a,start:i,end:b?i+c:g,total:g,chunks:h,chunk:j++,cuted:d}),i+=c;return a.blocks=e.concat(),a.remaning=e.length,d}var f=a.$,g=a.isPromise,h=c.Status;f.extend(b.options,{prepareNextFile:!1,chunked:!1,chunkSize:5242880,chunkRetry:2,threads:3,formData:{}}),b.register({name:"upload",init:function(){var b=this.owner,c=this;this.runing=!1,this.progress=!1,b.on("startUpload",function(){c.progress=!0}).on("uploadFinished",function(){c.progress=!1}),this.pool=[],this.stack=[],this.pending=[],this.remaning=0,this.__tick=a.bindFn(this._tick,this),b.on("uploadComplete",function(a){a.blocks&&f.each(a.blocks,function(a,b){b.transport&&(b.transport.abort(),b.transport.destroy()),delete b.transport}),delete a.blocks,delete a.remaning})},reset:function(){this.request("stop-upload",!0),this.runing=!1,this.pool=[],this.stack=[],this.pending=[],this.remaning=0,this._trigged=!1,this._promise=null},startUpload:function(b){var c=this;if(f.each(c.request("get-files",h.INVALID),function(){c.request("remove-file",this)}),b)if(b=b.id?b:c.request("get-file",b),b.getStatus()===h.INTERRUPT)f.each(c.pool,function(a,c){c.file===b&&c.transport&&c.transport.send()}),b.setStatus(h.QUEUED);else{if(b.getStatus()===h.PROGRESS)return;b.setStatus(h.QUEUED)}else f.each(c.request("get-files",[h.INITED]),function(){this.setStatus(h.QUEUED)});if(!c.runing){c.runing=!0;var d=[];f.each(c.pool,function(a,b){var e=b.file;e.getStatus()===h.INTERRUPT&&(d.push(e),c._trigged=!1,b.transport&&b.transport.send())});for(var b;b=d.shift();)b.setStatus(h.PROGRESS);b||f.each(c.request("get-files",h.INTERRUPT),function(){this.setStatus(h.PROGRESS)}),c._trigged=!1,a.nextTick(c.__tick),c.owner.trigger("startUpload")}},stopUpload:function(b,c){var d=this;if(b===!0&&(c=b,b=null),d.runing!==!1){if(b){if(b=b.id?b:d.request("get-file",b),b.getStatus()!==h.PROGRESS&&b.getStatus()!==h.QUEUED)return;return b.setStatus(h.INTERRUPT),f.each(d.pool,function(a,c){c.file===b&&(c.transport&&c.transport.abort(),d._putback(c),d._popBlock(c))}),a.nextTick(d.__tick)}d.runing=!1,this._promise&&this._promise.file&&this._promise.file.setStatus(h.INTERRUPT),c&&f.each(d.pool,function(a,b){b.transport&&b.transport.abort(),b.file.setStatus(h.INTERRUPT)}),d.owner.trigger("stopUpload")}},cancelFile:function(a){a=a.id?a:this.request("get-file",a),a.blocks&&f.each(a.blocks,function(a,b){var c=b.transport;c&&(c.abort(),c.destroy(),delete b.transport)}),a.setStatus(h.CANCELLED),this.owner.trigger("fileDequeued",a)},isInProgress:function(){return!!this.progress},_getStats:function(){return this.request("get-stats")},skipFile:function(a,b){a=a.id?a:this.request("get-file",a),a.setStatus(b||h.COMPLETE),a.skipped=!0,a.blocks&&f.each(a.blocks,function(a,b){var c=b.transport;c&&(c.abort(),c.destroy(),delete b.transport)}),this.owner.trigger("uploadSkip",a)},_tick:function(){var b,c,d=this,e=d.options;return d._promise?d._promise.always(d.__tick):void(d.pool.length1&&~"http,abort".indexOf(a)&&b.retried1&&f.extend(m,{chunks:b.chunks,chunk:b.chunk}),i.trigger("uploadBeforeSend",b,m,n),l.appendBlob(j.fileVal,b.blob,k.name),l.append(m),l.setRequestHeader(n),l.send()},_finishFile:function(a,b,c){var d=this.owner;return d.request("after-send-file",arguments,function(){a.setStatus(h.COMPLETE),d.trigger("uploadSuccess",a,b,c)}).fail(function(b){a.getStatus()===h.PROGRESS&&a.setStatus(h.ERROR,b),d.trigger("uploadError",a,b) -}).always(function(){d.trigger("uploadComplete",a)})},updateFileProgress:function(a){var b=0,c=0;a.blocks&&(f.each(a.blocks,function(a,b){c+=(b.percentage||0)*(b.end-b.start)}),b=c/a.size,this.owner.trigger("uploadProgress",a,b||0))}})}),b("widgets/validator",["base","uploader","file","widgets/widget"],function(a,b,c){var d,e=a.$,f={};return d={addValidator:function(a,b){f[a]=b},removeValidator:function(a){delete f[a]}},b.register({name:"validator",init:function(){var b=this;a.nextTick(function(){e.each(f,function(){this.call(b.owner)})})}}),d.addValidator("fileNumLimit",function(){var a=this,b=a.options,c=0,d=parseInt(b.fileNumLimit,10),e=!0;d&&(a.on("beforeFileQueued",function(a){return c>=d&&e&&(e=!1,this.trigger("error","Q_EXCEED_NUM_LIMIT",d,a),setTimeout(function(){e=!0},1)),c>=d?!1:!0}),a.on("fileQueued",function(){c++}),a.on("fileDequeued",function(){c--}),a.on("reset",function(){c=0}))}),d.addValidator("fileSizeLimit",function(){var a=this,b=a.options,c=0,d=parseInt(b.fileSizeLimit,10),e=!0;d&&(a.on("beforeFileQueued",function(a){var b=c+a.size>d;return b&&e&&(e=!1,this.trigger("error","Q_EXCEED_SIZE_LIMIT",d,a),setTimeout(function(){e=!0},1)),b?!1:!0}),a.on("fileQueued",function(a){c+=a.size}),a.on("fileDequeued",function(a){c-=a.size}),a.on("reset",function(){c=0}))}),d.addValidator("fileSingleSizeLimit",function(){var a=this,b=a.options,d=b.fileSingleSizeLimit;d&&a.on("beforeFileQueued",function(a){return a.size>d?(a.setStatus(c.Status.INVALID,"exceed_size"),this.trigger("error","F_EXCEED_SIZE",d,a),!1):void 0})}),d.addValidator("duplicate",function(){function a(a){for(var b,c=0,d=0,e=a.length;e>d;d++)b=a.charCodeAt(d),c=b+(c<<6)+(c<<16)-c;return c}var b=this,c=b.options,d={};c.duplicate||(b.on("beforeFileQueued",function(b){var c=b.__hash||(b.__hash=a(b.name+b.size+b.lastModifiedDate));return d[c]?(this.trigger("error","F_DUPLICATE",b),!1):void 0}),b.on("fileQueued",function(a){var b=a.__hash;b&&(d[b]=!0)}),b.on("fileDequeued",function(a){var b=a.__hash;b&&delete d[b]}),b.on("reset",function(){d={}}))}),d}),b("runtime/compbase",[],function(){function a(a,b){this.owner=a,this.options=a.options,this.getRuntime=function(){return b},this.getRuid=function(){return b.uid},this.trigger=function(){return a.trigger.apply(a,arguments)}}return a}),b("runtime/html5/runtime",["base","runtime/runtime","runtime/compbase"],function(b,c,d){function e(){var a={},d=this,e=this.destroy;c.apply(d,arguments),d.type=f,d.exec=function(c,e){var f,h=this,i=h.uid,j=b.slice(arguments,2);return g[c]&&(f=a[i]=a[i]||new g[c](h,d),f[e])?f[e].apply(f,j):void 0},d.destroy=function(){return e&&e.apply(this,arguments)}}var f="html5",g={};return b.inherits(c,{constructor:e,init:function(){var a=this;setTimeout(function(){a.trigger("ready")},1)}}),e.register=function(a,c){var e=g[a]=b.inherits(d,c);return e},a.Blob&&a.FileReader&&a.DataView&&c.addRuntime(f,e),e}),b("runtime/html5/blob",["runtime/html5/runtime","lib/blob"],function(a,b){return a.register("Blob",{slice:function(a,c){var d=this.owner.source,e=d.slice||d.webkitSlice||d.mozSlice;return d=e.call(d,a,c),new b(this.getRuid(),d)}})}),b("runtime/html5/dnd",["base","runtime/html5/runtime","lib/file"],function(a,b,c){var d=a.$,e="webuploader-dnd-";return b.register("DragAndDrop",{init:function(){var b=this.elem=this.options.container;this.dragEnterHandler=a.bindFn(this._dragEnterHandler,this),this.dragOverHandler=a.bindFn(this._dragOverHandler,this),this.dragLeaveHandler=a.bindFn(this._dragLeaveHandler,this),this.dropHandler=a.bindFn(this._dropHandler,this),this.dndOver=!1,b.on("dragenter",this.dragEnterHandler),b.on("dragover",this.dragOverHandler),b.on("dragleave",this.dragLeaveHandler),b.on("drop",this.dropHandler),this.options.disableGlobalDnd&&(d(document).on("dragover",this.dragOverHandler),d(document).on("drop",this.dropHandler))},_dragEnterHandler:function(a){var b,c=this,d=c._denied||!1;return a=a.originalEvent||a,c.dndOver||(c.dndOver=!0,b=a.dataTransfer.items,b&&b.length&&(c._denied=d=!c.trigger("accept",b)),c.elem.addClass(e+"over"),c.elem[d?"addClass":"removeClass"](e+"denied")),a.dataTransfer.dropEffect=d?"none":"copy",!1},_dragOverHandler:function(a){var b=this.elem.parent().get(0);return b&&!d.contains(b,a.currentTarget)?!1:(clearTimeout(this._leaveTimer),this._dragEnterHandler.call(this,a),!1)},_dragLeaveHandler:function(){var a,b=this;return a=function(){b.dndOver=!1,b.elem.removeClass(e+"over "+e+"denied")},clearTimeout(b._leaveTimer),b._leaveTimer=setTimeout(a,100),!1},_dropHandler:function(a){var b,f,g=this,h=g.getRuid(),i=g.elem.parent().get(0);if(i&&!d.contains(i,a.currentTarget))return!1;a=a.originalEvent||a,b=a.dataTransfer;try{f=b.getData("text/html")}catch(j){}return f?void 0:(g._getTansferFiles(b,function(a){g.trigger("drop",d.map(a,function(a){return new c(h,a)}))}),g.dndOver=!1,g.elem.removeClass(e+"over"),!1)},_getTansferFiles:function(b,c){var d,e,f,g,h,i,j,k=[],l=[];for(d=b.items,e=b.files,j=!(!d||!d[0].webkitGetAsEntry),h=0,i=e.length;i>h;h++)f=e[h],g=d&&d[h],j&&g.webkitGetAsEntry().isDirectory?l.push(this._traverseDirectoryTree(g.webkitGetAsEntry(),k)):k.push(f);a.when.apply(a,l).done(function(){k.length&&c(k)})},_traverseDirectoryTree:function(b,c){var d=a.Deferred(),e=this;return b.isFile?b.file(function(a){c.push(a),d.resolve()}):b.isDirectory&&b.createReader().readEntries(function(b){var f,g=b.length,h=[],i=[];for(f=0;g>f;f++)h.push(e._traverseDirectoryTree(b[f],i));a.when.apply(a,h).then(function(){c.push.apply(c,i),d.resolve()},d.reject)}),d.promise()},destroy:function(){var a=this.elem;a&&(a.off("dragenter",this.dragEnterHandler),a.off("dragover",this.dragOverHandler),a.off("dragleave",this.dragLeaveHandler),a.off("drop",this.dropHandler),this.options.disableGlobalDnd&&(d(document).off("dragover",this.dragOverHandler),d(document).off("drop",this.dropHandler)))}})}),b("runtime/html5/filepaste",["base","runtime/html5/runtime","lib/file"],function(a,b,c){return b.register("FilePaste",{init:function(){var b,c,d,e,f=this.options,g=this.elem=f.container,h=".*";if(f.accept){for(b=[],c=0,d=f.accept.length;d>c;c++)e=f.accept[c].mimeTypes,e&&b.push(e);b.length&&(h=b.join(","),h=h.replace(/,/g,"|").replace(/\*/g,".*"))}this.accept=h=new RegExp(h,"i"),this.hander=a.bindFn(this._pasteHander,this),g.on("paste",this.hander)},_pasteHander:function(a){var b,d,e,f,g,h=[],i=this.getRuid();for(a=a.originalEvent||a,b=a.clipboardData.items,f=0,g=b.length;g>f;f++)d=b[f],"file"===d.kind&&(e=d.getAsFile())&&h.push(new c(i,e));h.length&&(a.preventDefault(),a.stopPropagation(),this.trigger("paste",h))},destroy:function(){this.elem.off("paste",this.hander)}})}),b("runtime/html5/filepicker",["base","runtime/html5/runtime"],function(a,b){var c=a.$;return b.register("FilePicker",{init:function(){var a,b,d,e,f=this.getRuntime().getContainer(),g=this,h=g.owner,i=g.options,j=this.label=c(document.createElement("label")),k=this.input=c(document.createElement("input"));if(k.attr("type","file"),k.attr("name",i.name),k.addClass("webuploader-element-invisible"),j.on("click",function(){k.trigger("click")}),j.css({opacity:0,width:"100%",height:"100%",display:"block",cursor:"pointer",background:"#ffffff"}),i.multiple&&k.attr("multiple","multiple"),i.accept&&i.accept.length>0){for(a=[],b=0,d=i.accept.length;d>b;b++)a.push(i.accept[b].mimeTypes);k.attr("accept",a.join(","))}f.append(k),f.append(j),e=function(a){h.trigger(a.type)},k.on("change",function(a){var b,d=arguments.callee;g.files=a.target.files,b=this.cloneNode(!0),b.value=null,this.parentNode.replaceChild(b,this),k.off(),k=c(b).on("change",d).on("mouseenter mouseleave",e),h.trigger("change")}),j.on("mouseenter mouseleave",e)},getFiles:function(){return this.files},destroy:function(){this.input.off(),this.label.off()}})}),b("runtime/html5/util",["base"],function(b){var c=a.createObjectURL&&a||a.URL&&URL.revokeObjectURL&&URL||a.webkitURL,d=b.noop,e=d;return c&&(d=function(){return c.createObjectURL.apply(c,arguments)},e=function(){return c.revokeObjectURL.apply(c,arguments)}),{createObjectURL:d,revokeObjectURL:e,dataURL2Blob:function(a){var b,c,d,e,f,g;for(g=a.split(","),b=~g[0].indexOf("base64")?atob(g[1]):decodeURIComponent(g[1]),d=new ArrayBuffer(b.length),c=new Uint8Array(d),e=0;ei&&(d=h.getUint16(i),d>=65504&&65519>=d||65534===d)&&(e=h.getUint16(i+2)+2,!(i+e>h.byteLength));){if(f=b.parsers[d],!c&&f)for(g=0;g6&&(l.imageHead=a.slice?a.slice(2,k):new Uint8Array(a).subarray(2,k))}return l}},updateImageHead:function(a,b){var c,d,e,f=this._parse(a,!0);return e=2,f.imageHead&&(e=2+f.imageHead.byteLength),d=a.slice?a.slice(e):new Uint8Array(a).subarray(e),c=new Uint8Array(b.byteLength+2+d.byteLength),c[0]=255,c[1]=216,c.set(new Uint8Array(b),2),c.set(new Uint8Array(d),b.byteLength+2),c.buffer}},a.parseMeta=function(){return b.parse.apply(b,arguments)},a.updateImageHead=function(){return b.updateImageHead.apply(b,arguments)},b}),b("runtime/html5/imagemeta/exif",["base","runtime/html5/imagemeta"],function(a,b){var c={};return c.ExifMap=function(){return this},c.ExifMap.prototype.map={Orientation:274},c.ExifMap.prototype.get=function(a){return this[a]||this[this.map[a]]},c.exifTagTypes={1:{getValue:function(a,b){return a.getUint8(b)},size:1},2:{getValue:function(a,b){return String.fromCharCode(a.getUint8(b))},size:1,ascii:!0},3:{getValue:function(a,b,c){return a.getUint16(b,c)},size:2},4:{getValue:function(a,b,c){return a.getUint32(b,c)},size:4},5:{getValue:function(a,b,c){return a.getUint32(b,c)/a.getUint32(b+4,c)},size:8},9:{getValue:function(a,b,c){return a.getInt32(b,c)},size:4},10:{getValue:function(a,b,c){return a.getInt32(b,c)/a.getInt32(b+4,c)},size:8}},c.exifTagTypes[7]=c.exifTagTypes[1],c.getExifValue=function(b,d,e,f,g,h){var i,j,k,l,m,n,o=c.exifTagTypes[f];if(!o)return void a.log("Invalid Exif data: Invalid tag type.");if(i=o.size*g,j=i>4?d+b.getUint32(e+8,h):e+8,j+i>b.byteLength)return void a.log("Invalid Exif data: Invalid data offset.");if(1===g)return o.getValue(b,j,h);for(k=[],l=0;g>l;l+=1)k[l]=o.getValue(b,j+l*o.size,h);if(o.ascii){for(m="",l=0;lb.byteLength)return void a.log("Invalid Exif data: Invalid directory offset.");if(g=b.getUint16(d,e),h=d+2+12*g,h+4>b.byteLength)return void a.log("Invalid Exif data: Invalid directory size.");for(i=0;g>i;i+=1)this.parseExifTag(b,c,d+2+12*i,e,f);return b.getUint32(h,e)},c.parseExifData=function(b,d,e,f){var g,h,i=d+10;if(1165519206===b.getUint32(d+4)){if(i+8>b.byteLength)return void a.log("Invalid Exif data: Invalid segment size.");if(0!==b.getUint16(d+8))return void a.log("Invalid Exif data: Missing byte alignment offset.");switch(b.getUint16(i)){case 18761:g=!0;break;case 19789:g=!1;break;default:return void a.log("Invalid Exif data: Invalid byte alignment marker.")}if(42!==b.getUint16(i+2,g))return void a.log("Invalid Exif data: Missing TIFF marker.");h=b.getUint32(i+4,g),f.exif=new c.ExifMap,h=c.parseExifTags(b,i,i+h,g,f)}},b.parsers[65505].push(c.parseExifData),c}),b("runtime/html5/image",["base","runtime/html5/runtime","runtime/html5/util"],function(a,b,c){var d="data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs%3D";return b.register("Image",{modified:!1,init:function(){var a=this,b=new Image;b.onload=function(){a._info={type:a.type,width:this.width,height:this.height},a._metas||"image/jpeg"!==a.type?a.owner.trigger("load"):c.parseMeta(a._blob,function(b,c){a._metas=c,a.owner.trigger("load")})},b.onerror=function(){a.owner.trigger("error")},a._img=b},loadFromBlob:function(a){var b=this,d=b._img;b._blob=a,b.type=a.type,d.src=c.createObjectURL(a.getSource()),b.owner.once("load",function(){c.revokeObjectURL(d.src)})},resize:function(a,b){var c=this._canvas||(this._canvas=document.createElement("canvas"));this._resize(this._img,c,a,b),this._blob=null,this.modified=!0,this.owner.trigger("complete","resize")},crop:function(a,b,c,d,e){var f=this._canvas||(this._canvas=document.createElement("canvas")),g=this.options,h=this._img,i=h.naturalWidth,j=h.naturalHeight,k=this.getOrientation();e=e||1,f.width=c,f.height=d,g.preserveHeaders||this._rotate2Orientaion(f,k),this._renderImageToCanvas(f,h,-a,-b,i*e,j*e),this._blob=null,this.modified=!0,this.owner.trigger("complete","crop")},getAsBlob:function(a){var b,d=this._blob,e=this.options;if(a=a||this.type,this.modified||this.type!==a){if(b=this._canvas,"image/jpeg"===a){if(d=c.canvasToDataUrl(b,a,e.quality),e.preserveHeaders&&this._metas&&this._metas.imageHead)return d=c.dataURL2ArrayBuffer(d),d=c.updateImageHead(d,this._metas.imageHead),d=c.arrayBufferToBlob(d,a)}else d=c.canvasToDataUrl(b,a);d=c.dataURL2Blob(d)}return d},getAsDataUrl:function(a){var b=this.options;return a=a||this.type,"image/jpeg"===a?c.canvasToDataUrl(this._canvas,a,b.quality):this._canvas.toDataURL(a)},getOrientation:function(){return this._metas&&this._metas.exif&&this._metas.exif.get("Orientation")||1},info:function(a){return a?(this._info=a,this):this._info},meta:function(a){return a?(this._meta=a,this):this._meta},destroy:function(){var a=this._canvas;this._img.onload=null,a&&(a.getContext("2d").clearRect(0,0,a.width,a.height),a.width=a.height=0,this._canvas=null),this._img.src=d,this._img=this._blob=null},_resize:function(a,b,c,d){var e,f,g,h,i,j=this.options,k=a.width,l=a.height,m=this.getOrientation();~[5,6,7,8].indexOf(m)&&(c^=d,d^=c,c^=d),e=Math[j.crop?"max":"min"](c/k,d/l),j.allowMagnify||(e=Math.min(1,e)),f=k*e,g=l*e,j.crop?(b.width=c,b.height=d):(b.width=f,b.height=g),h=(b.width-f)/2,i=(b.height-g)/2,j.preserveHeaders||this._rotate2Orientaion(b,m),this._renderImageToCanvas(b,a,h,i,f,g)},_rotate2Orientaion:function(a,b){var c=a.width,d=a.height,e=a.getContext("2d");switch(b){case 5:case 6:case 7:case 8:a.width=d,a.height=c}switch(b){case 2:e.translate(c,0),e.scale(-1,1);break;case 3:e.translate(c,d),e.rotate(Math.PI);break;case 4:e.translate(0,d),e.scale(1,-1);break;case 5:e.rotate(.5*Math.PI),e.scale(1,-1);break;case 6:e.rotate(.5*Math.PI),e.translate(0,-d);break;case 7:e.rotate(.5*Math.PI),e.translate(c,-d),e.scale(-1,1);break;case 8:e.rotate(-.5*Math.PI),e.translate(-c,0)}},_renderImageToCanvas:function(){function b(a,b,c){var d,e,f,g=document.createElement("canvas"),h=g.getContext("2d"),i=0,j=c,k=c;for(g.width=1,g.height=c,h.drawImage(a,0,0),d=h.getImageData(0,0,1,c).data;k>i;)e=d[4*(k-1)+3],0===e?j=k:i=k,k=j+i>>1;return f=k/c,0===f?1:f}function c(a){var b,c,d=a.naturalWidth,e=a.naturalHeight;return d*e>1048576?(b=document.createElement("canvas"),b.width=b.height=1,c=b.getContext("2d"),c.drawImage(a,-d+1,0),0===c.getImageData(0,0,1,1).data[3]):!1}return a.os.ios?a.os.ios>=7?function(a,c,d,e,f,g){var h=c.naturalWidth,i=c.naturalHeight,j=b(c,h,i);return a.getContext("2d").drawImage(c,0,0,h*j,i*j,d,e,f,g)}:function(a,d,e,f,g,h){var i,j,k,l,m,n,o,p=d.naturalWidth,q=d.naturalHeight,r=a.getContext("2d"),s=c(d),t="image/jpeg"===this.type,u=1024,v=0,w=0;for(s&&(p/=2,q/=2),r.save(),i=document.createElement("canvas"),i.width=i.height=u,j=i.getContext("2d"),k=t?b(d,p,q):1,l=Math.ceil(u*g/p),m=Math.ceil(u*h/q/k);q>v;){for(n=0,o=0;p>n;)j.clearRect(0,0,u,u),j.drawImage(d,-n,-v),r.drawImage(i,0,0,u,u,e+o,f+w,l,m),n+=u,o+=l;v+=u,w+=m}r.restore(),i=j=null}:function(b){var c=a.slice(arguments,1),d=b.getContext("2d");d.drawImage.apply(d,c)}}()})}),b("runtime/html5/transport",["base","runtime/html5/runtime"],function(a,b){var c=a.noop,d=a.$;return b.register("Transport",{init:function(){this._status=0,this._response=null},send:function(){var b,c,e,f=this.owner,g=this.options,h=this._initAjax(),i=f._blob,j=g.server;g.sendAsBinary?(j+=(/\?/.test(j)?"&":"?")+d.param(f._formData),c=i.getSource()):(b=new FormData,d.each(f._formData,function(a,c){b.append(a,c)}),b.append(g.fileVal,i.getSource(),g.filename||f._formData.name||"")),g.withCredentials&&"withCredentials"in h?(h.open(g.method,j,!0),h.withCredentials=!0):h.open(g.method,j),this._setRequestHeader(h,g.headers),c?(h.overrideMimeType&&h.overrideMimeType("application/octet-stream"),a.os.android?(e=new FileReader,e.onload=function(){h.send(this.result),e=e.onload=null},e.readAsArrayBuffer(c)):h.send(c)):h.send(b)},getResponse:function(){return this._response},getResponseAsJson:function(){return this._parseJson(this._response)},getStatus:function(){return this._status},abort:function(){var a=this._xhr;a&&(a.upload.onprogress=c,a.onreadystatechange=c,a.abort(),this._xhr=a=null)},destroy:function(){this.abort()},_initAjax:function(){var a=this,b=new XMLHttpRequest,d=this.options;return!d.withCredentials||"withCredentials"in b||"undefined"==typeof XDomainRequest||(b=new XDomainRequest),b.upload.onprogress=function(b){var c=0;return b.lengthComputable&&(c=b.loaded/b.total),a.trigger("progress",c)},b.onreadystatechange=function(){return 4===b.readyState?(b.upload.onprogress=c,b.onreadystatechange=c,a._xhr=null,a._status=b.status,b.status>=200&&b.status<300?(a._response=b.responseText,a.trigger("load")):b.status>=500&&b.status<600?(a._response=b.responseText,a.trigger("error","server")):a.trigger("error",a._status?"http":"abort")):void 0},a._xhr=b,b},_setRequestHeader:function(a,b){d.each(b,function(b,c){a.setRequestHeader(b,c)})},_parseJson:function(a){var b;try{b=JSON.parse(a)}catch(c){b={}}return b}})}),b("preset/html5only",["base","widgets/filednd","widgets/filepaste","widgets/filepicker","widgets/image","widgets/queue","widgets/runtime","widgets/upload","widgets/validator","runtime/html5/blob","runtime/html5/dnd","runtime/html5/filepaste","runtime/html5/filepicker","runtime/html5/imagemeta/exif","runtime/html5/image","runtime/html5/transport"],function(a){return a}),b("webuploader",["preset/html5only"],function(a){return a}),c("webuploader")}); \ No newline at end of file diff --git a/static/plugins/webuploader/webuploader.js b/static/plugins/webuploader/webuploader.js deleted file mode 100644 index e1a483b4..00000000 --- a/static/plugins/webuploader/webuploader.js +++ /dev/null @@ -1,8106 +0,0 @@ -/*! WebUploader 0.1.5 */ - - -/** - * @fileOverview 让内部各个部件的代码可以用[amd](https://github.com/amdjs/amdjs-api/wiki/AMD)模块定义方式组织起来。 - * - * AMD API 内部的简单不完全实现,请忽略。只有当WebUploader被合并成一个文件的时候才会引入。 - */ -(function( root, factory ) { - var modules = {}, - - // 内部require, 简单不完全实现。 - // https://github.com/amdjs/amdjs-api/wiki/require - _require = function( deps, callback ) { - var args, len, i; - - // 如果deps不是数组,则直接返回指定module - if ( typeof deps === 'string' ) { - return getModule( deps ); - } else { - args = []; - for( len = deps.length, i = 0; i < len; i++ ) { - args.push( getModule( deps[ i ] ) ); - } - - return callback.apply( null, args ); - } - }, - - // 内部define,暂时不支持不指定id. - _define = function( id, deps, factory ) { - if ( arguments.length === 2 ) { - factory = deps; - deps = null; - } - - _require( deps || [], function() { - setModule( id, factory, arguments ); - }); - }, - - // 设置module, 兼容CommonJs写法。 - setModule = function( id, factory, args ) { - var module = { - exports: factory - }, - returned; - - if ( typeof factory === 'function' ) { - args.length || (args = [ _require, module.exports, module ]); - returned = factory.apply( null, args ); - returned !== undefined && (module.exports = returned); - } - - modules[ id ] = module.exports; - }, - - // 根据id获取module - getModule = function( id ) { - var module = modules[ id ] || root[ id ]; - - if ( !module ) { - throw new Error( '`' + id + '` is undefined' ); - } - - return module; - }, - - // 将所有modules,将路径ids装换成对象。 - exportsTo = function( obj ) { - var key, host, parts, part, last, ucFirst; - - // make the first character upper case. - ucFirst = function( str ) { - return str && (str.charAt( 0 ).toUpperCase() + str.substr( 1 )); - }; - - for ( key in modules ) { - host = obj; - - if ( !modules.hasOwnProperty( key ) ) { - continue; - } - - parts = key.split('/'); - last = ucFirst( parts.pop() ); - - while( (part = ucFirst( parts.shift() )) ) { - host[ part ] = host[ part ] || {}; - host = host[ part ]; - } - - host[ last ] = modules[ key ]; - } - - return obj; - }, - - makeExport = function( dollar ) { - root.__dollar = dollar; - - // exports every module. - return exportsTo( factory( root, _define, _require ) ); - }, - - origin; - - if ( typeof module === 'object' && typeof module.exports === 'object' ) { - - // For CommonJS and CommonJS-like environments where a proper window is present, - module.exports = makeExport(); - } else if ( typeof define === 'function' && define.amd ) { - - // Allow using this built library as an AMD module - // in another project. That other project will only - // see this AMD call, not the internal modules in - // the closure below. - define([ 'jquery' ], makeExport ); - } else { - - // Browser globals case. Just assign the - // result to a property on the global. - origin = root.WebUploader; - root.WebUploader = makeExport(); - root.WebUploader.noConflict = function() { - root.WebUploader = origin; - }; - } -})( window, function( window, define, require ) { - - - /** - * @fileOverview jQuery or Zepto - */ - define('dollar-third',[],function() { - var $ = window.__dollar || window.jQuery || window.Zepto; - - if ( !$ ) { - throw new Error('jQuery or Zepto not found!'); - } - - return $; - }); - /** - * @fileOverview Dom 操作相关 - */ - define('dollar',[ - 'dollar-third' - ], function( _ ) { - return _; - }); - /** - * @fileOverview 使用jQuery的Promise - */ - define('promise-third',[ - 'dollar' - ], function( $ ) { - return { - Deferred: $.Deferred, - when: $.when, - - isPromise: function( anything ) { - return anything && typeof anything.then === 'function'; - } - }; - }); - /** - * @fileOverview Promise/A+ - */ - define('promise',[ - 'promise-third' - ], function( _ ) { - return _; - }); - /** - * @fileOverview 基础类方法。 - */ - - /** - * Web Uploader内部类的详细说明,以下提及的功能类,都可以在`WebUploader`这个变量中访问到。 - * - * As you know, Web Uploader的每个文件都是用过[AMD](https://github.com/amdjs/amdjs-api/wiki/AMD)规范中的`define`组织起来的, 每个Module都会有个module id. - * 默认module id为该文件的路径,而此路径将会转化成名字空间存放在WebUploader中。如: - * - * * module `base`:WebUploader.Base - * * module `file`: WebUploader.File - * * module `lib/dnd`: WebUploader.Lib.Dnd - * * module `runtime/html5/dnd`: WebUploader.Runtime.Html5.Dnd - * - * - * 以下文档中对类的使用可能省略掉了`WebUploader`前缀。 - * @module WebUploader - * @title WebUploader API文档 - */ - define('base',[ - 'dollar', - 'promise' - ], function( $, promise ) { - - var noop = function() {}, - call = Function.call; - - // http://jsperf.com/uncurrythis - // 反科里化 - function uncurryThis( fn ) { - return function() { - return call.apply( fn, arguments ); - }; - } - - function bindFn( fn, context ) { - return function() { - return fn.apply( context, arguments ); - }; - } - - function createObject( proto ) { - var f; - - if ( Object.create ) { - return Object.create( proto ); - } else { - f = function() {}; - f.prototype = proto; - return new f(); - } - } - - - /** - * 基础类,提供一些简单常用的方法。 - * @class Base - */ - return { - - /** - * @property {String} version 当前版本号。 - */ - version: '0.1.5', - - /** - * @property {jQuery|Zepto} $ 引用依赖的jQuery或者Zepto对象。 - */ - $: $, - - Deferred: promise.Deferred, - - isPromise: promise.isPromise, - - when: promise.when, - - /** - * @description 简单的浏览器检查结果。 - * - * * `webkit` webkit版本号,如果浏览器为非webkit内核,此属性为`undefined`。 - * * `chrome` chrome浏览器版本号,如果浏览器为chrome,此属性为`undefined`。 - * * `ie` ie浏览器版本号,如果浏览器为非ie,此属性为`undefined`。**暂不支持ie10+** - * * `firefox` firefox浏览器版本号,如果浏览器为非firefox,此属性为`undefined`。 - * * `safari` safari浏览器版本号,如果浏览器为非safari,此属性为`undefined`。 - * * `opera` opera浏览器版本号,如果浏览器为非opera,此属性为`undefined`。 - * - * @property {Object} [browser] - */ - browser: (function( ua ) { - var ret = {}, - webkit = ua.match( /WebKit\/([\d.]+)/ ), - chrome = ua.match( /Chrome\/([\d.]+)/ ) || - ua.match( /CriOS\/([\d.]+)/ ), - - ie = ua.match( /MSIE\s([\d\.]+)/ ) || - ua.match( /(?:trident)(?:.*rv:([\w.]+))?/i ), - firefox = ua.match( /Firefox\/([\d.]+)/ ), - safari = ua.match( /Safari\/([\d.]+)/ ), - opera = ua.match( /OPR\/([\d.]+)/ ); - - webkit && (ret.webkit = parseFloat( webkit[ 1 ] )); - chrome && (ret.chrome = parseFloat( chrome[ 1 ] )); - ie && (ret.ie = parseFloat( ie[ 1 ] )); - firefox && (ret.firefox = parseFloat( firefox[ 1 ] )); - safari && (ret.safari = parseFloat( safari[ 1 ] )); - opera && (ret.opera = parseFloat( opera[ 1 ] )); - - return ret; - })( navigator.userAgent ), - - /** - * @description 操作系统检查结果。 - * - * * `android` 如果在android浏览器环境下,此值为对应的android版本号,否则为`undefined`。 - * * `ios` 如果在ios浏览器环境下,此值为对应的ios版本号,否则为`undefined`。 - * @property {Object} [os] - */ - os: (function( ua ) { - var ret = {}, - - // osx = !!ua.match( /\(Macintosh\; Intel / ), - android = ua.match( /(?:Android);?[\s\/]+([\d.]+)?/ ), - ios = ua.match( /(?:iPad|iPod|iPhone).*OS\s([\d_]+)/ ); - - // osx && (ret.osx = true); - android && (ret.android = parseFloat( android[ 1 ] )); - ios && (ret.ios = parseFloat( ios[ 1 ].replace( /_/g, '.' ) )); - - return ret; - })( navigator.userAgent ), - - /** - * 实现类与类之间的继承。 - * @method inherits - * @grammar Base.inherits( super ) => child - * @grammar Base.inherits( super, protos ) => child - * @grammar Base.inherits( super, protos, statics ) => child - * @param {Class} super 父类 - * @param {Object | Function} [protos] 子类或者对象。如果对象中包含constructor,子类将是用此属性值。 - * @param {Function} [protos.constructor] 子类构造器,不指定的话将创建个临时的直接执行父类构造器的方法。 - * @param {Object} [statics] 静态属性或方法。 - * @return {Class} 返回子类。 - * @example - * function Person() { - * console.log( 'Super' ); - * } - * Person.prototype.hello = function() { - * console.log( 'hello' ); - * }; - * - * var Manager = Base.inherits( Person, { - * world: function() { - * console.log( 'World' ); - * } - * }); - * - * // 因为没有指定构造器,父类的构造器将会执行。 - * var instance = new Manager(); // => Super - * - * // 继承子父类的方法 - * instance.hello(); // => hello - * instance.world(); // => World - * - * // 子类的__super__属性指向父类 - * console.log( Manager.__super__ === Person ); // => true - */ - inherits: function( Super, protos, staticProtos ) { - var child; - - if ( typeof protos === 'function' ) { - child = protos; - protos = null; - } else if ( protos && protos.hasOwnProperty('constructor') ) { - child = protos.constructor; - } else { - child = function() { - return Super.apply( this, arguments ); - }; - } - - // 复制静态方法 - $.extend( true, child, Super, staticProtos || {} ); - - /* jshint camelcase: false */ - - // 让子类的__super__属性指向父类。 - child.__super__ = Super.prototype; - - // 构建原型,添加原型方法或属性。 - // 暂时用Object.create实现。 - child.prototype = createObject( Super.prototype ); - protos && $.extend( true, child.prototype, protos ); - - return child; - }, - - /** - * 一个不做任何事情的方法。可以用来赋值给默认的callback. - * @method noop - */ - noop: noop, - - /** - * 返回一个新的方法,此方法将已指定的`context`来执行。 - * @grammar Base.bindFn( fn, context ) => Function - * @method bindFn - * @example - * var doSomething = function() { - * console.log( this.name ); - * }, - * obj = { - * name: 'Object Name' - * }, - * aliasFn = Base.bind( doSomething, obj ); - * - * aliasFn(); // => Object Name - * - */ - bindFn: bindFn, - - /** - * 引用Console.log如果存在的话,否则引用一个[空函数noop](#WebUploader:Base.noop)。 - * @grammar Base.log( args... ) => undefined - * @method log - */ - log: (function() { - if ( window.console ) { - return bindFn( console.log, console ); - } - return noop; - })(), - - nextTick: (function() { - - return function( cb ) { - setTimeout( cb, 1 ); - }; - - // @bug 当浏览器不在当前窗口时就停了。 - // var next = window.requestAnimationFrame || - // window.webkitRequestAnimationFrame || - // window.mozRequestAnimationFrame || - // function( cb ) { - // window.setTimeout( cb, 1000 / 60 ); - // }; - - // // fix: Uncaught TypeError: Illegal invocation - // return bindFn( next, window ); - })(), - - /** - * 被[uncurrythis](http://www.2ality.com/2011/11/uncurrying-this.html)的数组slice方法。 - * 将用来将非数组对象转化成数组对象。 - * @grammar Base.slice( target, start[, end] ) => Array - * @method slice - * @example - * function doSomthing() { - * var args = Base.slice( arguments, 1 ); - * console.log( args ); - * } - * - * doSomthing( 'ignored', 'arg2', 'arg3' ); // => Array ["arg2", "arg3"] - */ - slice: uncurryThis( [].slice ), - - /** - * 生成唯一的ID - * @method guid - * @grammar Base.guid() => String - * @grammar Base.guid( prefx ) => String - */ - guid: (function() { - var counter = 0; - - return function( prefix ) { - var guid = (+new Date()).toString( 32 ), - i = 0; - - for ( ; i < 5; i++ ) { - guid += Math.floor( Math.random() * 65535 ).toString( 32 ); - } - - return (prefix || 'wu_') + guid + (counter++).toString( 32 ); - }; - })(), - - /** - * 格式化文件大小, 输出成带单位的字符串 - * @method formatSize - * @grammar Base.formatSize( size ) => String - * @grammar Base.formatSize( size, pointLength ) => String - * @grammar Base.formatSize( size, pointLength, units ) => String - * @param {Number} size 文件大小 - * @param {Number} [pointLength=2] 精确到的小数点数。 - * @param {Array} [units=[ 'B', 'K', 'M', 'G', 'TB' ]] 单位数组。从字节,到千字节,一直往上指定。如果单位数组里面只指定了到了K(千字节),同时文件大小大于M, 此方法的输出将还是显示成多少K. - * @example - * console.log( Base.formatSize( 100 ) ); // => 100B - * console.log( Base.formatSize( 1024 ) ); // => 1.00K - * console.log( Base.formatSize( 1024, 0 ) ); // => 1K - * console.log( Base.formatSize( 1024 * 1024 ) ); // => 1.00M - * console.log( Base.formatSize( 1024 * 1024 * 1024 ) ); // => 1.00G - * console.log( Base.formatSize( 1024 * 1024 * 1024, 0, ['B', 'KB', 'MB'] ) ); // => 1024MB - */ - formatSize: function( size, pointLength, units ) { - var unit; - - units = units || [ 'B', 'K', 'M', 'G', 'TB' ]; - - while ( (unit = units.shift()) && size > 1024 ) { - size = size / 1024; - } - - return (unit === 'B' ? size : size.toFixed( pointLength || 2 )) + - unit; - } - }; - }); - /** - * 事件处理类,可以独立使用,也可以扩展给对象使用。 - * @fileOverview Mediator - */ - define('mediator',[ - 'base' - ], function( Base ) { - var $ = Base.$, - slice = [].slice, - separator = /\s+/, - protos; - - // 根据条件过滤出事件handlers. - function findHandlers( arr, name, callback, context ) { - return $.grep( arr, function( handler ) { - return handler && - (!name || handler.e === name) && - (!callback || handler.cb === callback || - handler.cb._cb === callback) && - (!context || handler.ctx === context); - }); - } - - function eachEvent( events, callback, iterator ) { - // 不支持对象,只支持多个event用空格隔开 - $.each( (events || '').split( separator ), function( _, key ) { - iterator( key, callback ); - }); - } - - function triggerHanders( events, args ) { - var stoped = false, - i = -1, - len = events.length, - handler; - - while ( ++i < len ) { - handler = events[ i ]; - - if ( handler.cb.apply( handler.ctx2, args ) === false ) { - stoped = true; - break; - } - } - - return !stoped; - } - - protos = { - - /** - * 绑定事件。 - * - * `callback`方法在执行时,arguments将会来源于trigger的时候携带的参数。如 - * ```javascript - * var obj = {}; - * - * // 使得obj有事件行为 - * Mediator.installTo( obj ); - * - * obj.on( 'testa', function( arg1, arg2 ) { - * console.log( arg1, arg2 ); // => 'arg1', 'arg2' - * }); - * - * obj.trigger( 'testa', 'arg1', 'arg2' ); - * ``` - * - * 如果`callback`中,某一个方法`return false`了,则后续的其他`callback`都不会被执行到。 - * 切会影响到`trigger`方法的返回值,为`false`。 - * - * `on`还可以用来添加一个特殊事件`all`, 这样所有的事件触发都会响应到。同时此类`callback`中的arguments有一个不同处, - * 就是第一个参数为`type`,记录当前是什么事件在触发。此类`callback`的优先级比脚低,会再正常`callback`执行完后触发。 - * ```javascript - * obj.on( 'all', function( type, arg1, arg2 ) { - * console.log( type, arg1, arg2 ); // => 'testa', 'arg1', 'arg2' - * }); - * ``` - * - * @method on - * @grammar on( name, callback[, context] ) => self - * @param {String} name 事件名,支持多个事件用空格隔开 - * @param {Function} callback 事件处理器 - * @param {Object} [context] 事件处理器的上下文。 - * @return {self} 返回自身,方便链式 - * @chainable - * @class Mediator - */ - on: function( name, callback, context ) { - var me = this, - set; - - if ( !callback ) { - return this; - } - - set = this._events || (this._events = []); - - eachEvent( name, callback, function( name, callback ) { - var handler = { e: name }; - - handler.cb = callback; - handler.ctx = context; - handler.ctx2 = context || me; - handler.id = set.length; - - set.push( handler ); - }); - - return this; - }, - - /** - * 绑定事件,且当handler执行完后,自动解除绑定。 - * @method once - * @grammar once( name, callback[, context] ) => self - * @param {String} name 事件名 - * @param {Function} callback 事件处理器 - * @param {Object} [context] 事件处理器的上下文。 - * @return {self} 返回自身,方便链式 - * @chainable - */ - once: function( name, callback, context ) { - var me = this; - - if ( !callback ) { - return me; - } - - eachEvent( name, callback, function( name, callback ) { - var once = function() { - me.off( name, once ); - return callback.apply( context || me, arguments ); - }; - - once._cb = callback; - me.on( name, once, context ); - }); - - return me; - }, - - /** - * 解除事件绑定 - * @method off - * @grammar off( [name[, callback[, context] ] ] ) => self - * @param {String} [name] 事件名 - * @param {Function} [callback] 事件处理器 - * @param {Object} [context] 事件处理器的上下文。 - * @return {self} 返回自身,方便链式 - * @chainable - */ - off: function( name, cb, ctx ) { - var events = this._events; - - if ( !events ) { - return this; - } - - if ( !name && !cb && !ctx ) { - this._events = []; - return this; - } - - eachEvent( name, cb, function( name, cb ) { - $.each( findHandlers( events, name, cb, ctx ), function() { - delete events[ this.id ]; - }); - }); - - return this; - }, - - /** - * 触发事件 - * @method trigger - * @grammar trigger( name[, args...] ) => self - * @param {String} type 事件名 - * @param {*} [...] 任意参数 - * @return {Boolean} 如果handler中return false了,则返回false, 否则返回true - */ - trigger: function( type ) { - var args, events, allEvents; - - if ( !this._events || !type ) { - return this; - } - - args = slice.call( arguments, 1 ); - events = findHandlers( this._events, type ); - allEvents = findHandlers( this._events, 'all' ); - - return triggerHanders( events, args ) && - triggerHanders( allEvents, arguments ); - } - }; - - /** - * 中介者,它本身是个单例,但可以通过[installTo](#WebUploader:Mediator:installTo)方法,使任何对象具备事件行为。 - * 主要目的是负责模块与模块之间的合作,降低耦合度。 - * - * @class Mediator - */ - return $.extend({ - - /** - * 可以通过这个接口,使任何对象具备事件功能。 - * @method installTo - * @param {Object} obj 需要具备事件行为的对象。 - * @return {Object} 返回obj. - */ - installTo: function( obj ) { - return $.extend( obj, protos ); - } - - }, protos ); - }); - /** - * @fileOverview Uploader上传类 - */ - define('uploader',[ - 'base', - 'mediator' - ], function( Base, Mediator ) { - - var $ = Base.$; - - /** - * 上传入口类。 - * @class Uploader - * @constructor - * @grammar new Uploader( opts ) => Uploader - * @example - * var uploader = WebUploader.Uploader({ - * swf: 'path_of_swf/Uploader.swf', - * - * // 开起分片上传。 - * chunked: true - * }); - */ - function Uploader( opts ) { - this.options = $.extend( true, {}, Uploader.options, opts ); - this._init( this.options ); - } - - // default Options - // widgets中有相应扩展 - Uploader.options = {}; - Mediator.installTo( Uploader.prototype ); - - // 批量添加纯命令式方法。 - $.each({ - upload: 'start-upload', - stop: 'stop-upload', - getFile: 'get-file', - getFiles: 'get-files', - addFile: 'add-file', - addFiles: 'add-file', - sort: 'sort-files', - removeFile: 'remove-file', - cancelFile: 'cancel-file', - skipFile: 'skip-file', - retry: 'retry', - isInProgress: 'is-in-progress', - makeThumb: 'make-thumb', - md5File: 'md5-file', - getDimension: 'get-dimension', - addButton: 'add-btn', - predictRuntimeType: 'predict-runtime-type', - refresh: 'refresh', - disable: 'disable', - enable: 'enable', - reset: 'reset' - }, function( fn, command ) { - Uploader.prototype[ fn ] = function() { - return this.request( command, arguments ); - }; - }); - - $.extend( Uploader.prototype, { - state: 'pending', - - _init: function( opts ) { - var me = this; - - me.request( 'init', opts, function() { - me.state = 'ready'; - me.trigger('ready'); - }); - }, - - /** - * 获取或者设置Uploader配置项。 - * @method option - * @grammar option( key ) => * - * @grammar option( key, val ) => self - * @example - * - * // 初始状态图片上传前不会压缩 - * var uploader = new WebUploader.Uploader({ - * compress: null; - * }); - * - * // 修改后图片上传前,尝试将图片压缩到1600 * 1600 - * uploader.option( 'compress', { - * width: 1600, - * height: 1600 - * }); - */ - option: function( key, val ) { - var opts = this.options; - - // setter - if ( arguments.length > 1 ) { - - if ( $.isPlainObject( val ) && - $.isPlainObject( opts[ key ] ) ) { - $.extend( opts[ key ], val ); - } else { - opts[ key ] = val; - } - - } else { // getter - return key ? opts[ key ] : opts; - } - }, - - /** - * 获取文件统计信息。返回一个包含一下信息的对象。 - * * `successNum` 上传成功的文件数 - * * `progressNum` 上传中的文件数 - * * `cancelNum` 被删除的文件数 - * * `invalidNum` 无效的文件数 - * * `uploadFailNum` 上传失败的文件数 - * * `queueNum` 还在队列中的文件数 - * * `interruptNum` 被暂停的文件数 - * @method getStats - * @grammar getStats() => Object - */ - getStats: function() { - // return this._mgr.getStats.apply( this._mgr, arguments ); - var stats = this.request('get-stats'); - - return stats ? { - successNum: stats.numOfSuccess, - progressNum: stats.numOfProgress, - - // who care? - // queueFailNum: 0, - cancelNum: stats.numOfCancel, - invalidNum: stats.numOfInvalid, - uploadFailNum: stats.numOfUploadFailed, - queueNum: stats.numOfQueue, - interruptNum: stats.numofInterrupt - } : {}; - }, - - // 需要重写此方法来来支持opts.onEvent和instance.onEvent的处理器 - trigger: function( type/*, args...*/ ) { - var args = [].slice.call( arguments, 1 ), - opts = this.options, - name = 'on' + type.substring( 0, 1 ).toUpperCase() + - type.substring( 1 ); - - if ( - // 调用通过on方法注册的handler. - Mediator.trigger.apply( this, arguments ) === false || - - // 调用opts.onEvent - $.isFunction( opts[ name ] ) && - opts[ name ].apply( this, args ) === false || - - // 调用this.onEvent - $.isFunction( this[ name ] ) && - this[ name ].apply( this, args ) === false || - - // 广播所有uploader的事件。 - Mediator.trigger.apply( Mediator, - [ this, type ].concat( args ) ) === false ) { - - return false; - } - - return true; - }, - - /** - * 销毁 webuploader 实例 - * @method destroy - * @grammar destroy() => undefined - */ - destroy: function() { - this.request( 'destroy', arguments ); - this.off(); - }, - - // widgets/widget.js将补充此方法的详细文档。 - request: Base.noop - }); - - /** - * 创建Uploader实例,等同于new Uploader( opts ); - * @method create - * @class Base - * @static - * @grammar Base.create( opts ) => Uploader - */ - Base.create = Uploader.create = function( opts ) { - return new Uploader( opts ); - }; - - // 暴露Uploader,可以通过它来扩展业务逻辑。 - Base.Uploader = Uploader; - - return Uploader; - }); - /** - * @fileOverview Runtime管理器,负责Runtime的选择, 连接 - */ - define('runtime/runtime',[ - 'base', - 'mediator' - ], function( Base, Mediator ) { - - var $ = Base.$, - factories = {}, - - // 获取对象的第一个key - getFirstKey = function( obj ) { - for ( var key in obj ) { - if ( obj.hasOwnProperty( key ) ) { - return key; - } - } - return null; - }; - - // 接口类。 - function Runtime( options ) { - this.options = $.extend({ - container: document.body - }, options ); - this.uid = Base.guid('rt_'); - } - - $.extend( Runtime.prototype, { - - getContainer: function() { - var opts = this.options, - parent, container; - - if ( this._container ) { - return this._container; - } - - parent = $( opts.container || document.body ); - container = $( document.createElement('div') ); - - container.attr( 'id', 'rt_' + this.uid ); - container.css({ - position: 'absolute', - top: '0px', - left: '0px', - width: '1px', - height: '1px', - overflow: 'hidden' - }); - - parent.append( container ); - parent.addClass('webuploader-container'); - this._container = container; - this._parent = parent; - return container; - }, - - init: Base.noop, - exec: Base.noop, - - destroy: function() { - this._container && this._container.remove(); - this._parent && this._parent.removeClass('webuploader-container'); - this.off(); - } - }); - - Runtime.orders = 'html5,flash'; - - - /** - * 添加Runtime实现。 - * @param {String} type 类型 - * @param {Runtime} factory 具体Runtime实现。 - */ - Runtime.addRuntime = function( type, factory ) { - factories[ type ] = factory; - }; - - Runtime.hasRuntime = function( type ) { - return !!(type ? factories[ type ] : getFirstKey( factories )); - }; - - Runtime.create = function( opts, orders ) { - var type, runtime; - - orders = orders || Runtime.orders; - $.each( orders.split( /\s*,\s*/g ), function() { - if ( factories[ this ] ) { - type = this; - return false; - } - }); - - type = type || getFirstKey( factories ); - - if ( !type ) { - throw new Error('Runtime Error'); - } - - runtime = new factories[ type ]( opts ); - return runtime; - }; - - Mediator.installTo( Runtime.prototype ); - return Runtime; - }); - - /** - * @fileOverview Runtime管理器,负责Runtime的选择, 连接 - */ - define('runtime/client',[ - 'base', - 'mediator', - 'runtime/runtime' - ], function( Base, Mediator, Runtime ) { - - var cache; - - cache = (function() { - var obj = {}; - - return { - add: function( runtime ) { - obj[ runtime.uid ] = runtime; - }, - - get: function( ruid, standalone ) { - var i; - - if ( ruid ) { - return obj[ ruid ]; - } - - for ( i in obj ) { - // 有些类型不能重用,比如filepicker. - if ( standalone && obj[ i ].__standalone ) { - continue; - } - - return obj[ i ]; - } - - return null; - }, - - remove: function( runtime ) { - delete obj[ runtime.uid ]; - } - }; - })(); - - function RuntimeClient( component, standalone ) { - var deferred = Base.Deferred(), - runtime; - - this.uid = Base.guid('client_'); - - // 允许runtime没有初始化之前,注册一些方法在初始化后执行。 - this.runtimeReady = function( cb ) { - return deferred.done( cb ); - }; - - this.connectRuntime = function( opts, cb ) { - - // already connected. - if ( runtime ) { - throw new Error('already connected!'); - } - - deferred.done( cb ); - - if ( typeof opts === 'string' && cache.get( opts ) ) { - runtime = cache.get( opts ); - } - - // 像filePicker只能独立存在,不能公用。 - runtime = runtime || cache.get( null, standalone ); - - // 需要创建 - if ( !runtime ) { - runtime = Runtime.create( opts, opts.runtimeOrder ); - runtime.__promise = deferred.promise(); - runtime.once( 'ready', deferred.resolve ); - runtime.init(); - cache.add( runtime ); - runtime.__client = 1; - } else { - // 来自cache - Base.$.extend( runtime.options, opts ); - runtime.__promise.then( deferred.resolve ); - runtime.__client++; - } - - standalone && (runtime.__standalone = standalone); - return runtime; - }; - - this.getRuntime = function() { - return runtime; - }; - - this.disconnectRuntime = function() { - if ( !runtime ) { - return; - } - - runtime.__client--; - - if ( runtime.__client <= 0 ) { - cache.remove( runtime ); - delete runtime.__promise; - runtime.destroy(); - } - - runtime = null; - }; - - this.exec = function() { - if ( !runtime ) { - return; - } - - var args = Base.slice( arguments ); - component && args.unshift( component ); - - return runtime.exec.apply( this, args ); - }; - - this.getRuid = function() { - return runtime && runtime.uid; - }; - - this.destroy = (function( destroy ) { - return function() { - destroy && destroy.apply( this, arguments ); - this.trigger('destroy'); - this.off(); - this.exec('destroy'); - this.disconnectRuntime(); - }; - })( this.destroy ); - } - - Mediator.installTo( RuntimeClient.prototype ); - return RuntimeClient; - }); - /** - * @fileOverview 错误信息 - */ - define('lib/dnd',[ - 'base', - 'mediator', - 'runtime/client' - ], function( Base, Mediator, RuntimeClent ) { - - var $ = Base.$; - - function DragAndDrop( opts ) { - opts = this.options = $.extend({}, DragAndDrop.options, opts ); - - opts.container = $( opts.container ); - - if ( !opts.container.length ) { - return; - } - - RuntimeClent.call( this, 'DragAndDrop' ); - } - - DragAndDrop.options = { - accept: null, - disableGlobalDnd: false - }; - - Base.inherits( RuntimeClent, { - constructor: DragAndDrop, - - init: function() { - var me = this; - - me.connectRuntime( me.options, function() { - me.exec('init'); - me.trigger('ready'); - }); - } - }); - - Mediator.installTo( DragAndDrop.prototype ); - - return DragAndDrop; - }); - /** - * @fileOverview 组件基类。 - */ - define('widgets/widget',[ - 'base', - 'uploader' - ], function( Base, Uploader ) { - - var $ = Base.$, - _init = Uploader.prototype._init, - _destroy = Uploader.prototype.destroy, - IGNORE = {}, - widgetClass = []; - - function isArrayLike( obj ) { - if ( !obj ) { - return false; - } - - var length = obj.length, - type = $.type( obj ); - - if ( obj.nodeType === 1 && length ) { - return true; - } - - return type === 'array' || type !== 'function' && type !== 'string' && - (length === 0 || typeof length === 'number' && length > 0 && - (length - 1) in obj); - } - - function Widget( uploader ) { - this.owner = uploader; - this.options = uploader.options; - } - - $.extend( Widget.prototype, { - - init: Base.noop, - - // 类Backbone的事件监听声明,监听uploader实例上的事件 - // widget直接无法监听事件,事件只能通过uploader来传递 - invoke: function( apiName, args ) { - - /* - { - 'make-thumb': 'makeThumb' - } - */ - var map = this.responseMap; - - // 如果无API响应声明则忽略 - if ( !map || !(apiName in map) || !(map[ apiName ] in this) || - !$.isFunction( this[ map[ apiName ] ] ) ) { - - return IGNORE; - } - - return this[ map[ apiName ] ].apply( this, args ); - - }, - - /** - * 发送命令。当传入`callback`或者`handler`中返回`promise`时。返回一个当所有`handler`中的promise都完成后完成的新`promise`。 - * @method request - * @grammar request( command, args ) => * | Promise - * @grammar request( command, args, callback ) => Promise - * @for Uploader - */ - request: function() { - return this.owner.request.apply( this.owner, arguments ); - } - }); - - // 扩展Uploader. - $.extend( Uploader.prototype, { - - /** - * @property {String | Array} [disableWidgets=undefined] - * @namespace options - * @for Uploader - * @description 默认所有 Uploader.register 了的 widget 都会被加载,如果禁用某一部分,请通过此 option 指定黑名单。 - */ - - // 覆写_init用来初始化widgets - _init: function() { - var me = this, - widgets = me._widgets = [], - deactives = me.options.disableWidgets || ''; - - $.each( widgetClass, function( _, klass ) { - (!deactives || !~deactives.indexOf( klass._name )) && - widgets.push( new klass( me ) ); - }); - - return _init.apply( me, arguments ); - }, - - request: function( apiName, args, callback ) { - var i = 0, - widgets = this._widgets, - len = widgets && widgets.length, - rlts = [], - dfds = [], - widget, rlt, promise, key; - - args = isArrayLike( args ) ? args : [ args ]; - - for ( ; i < len; i++ ) { - widget = widgets[ i ]; - rlt = widget.invoke( apiName, args ); - - if ( rlt !== IGNORE ) { - - // Deferred对象 - if ( Base.isPromise( rlt ) ) { - dfds.push( rlt ); - } else { - rlts.push( rlt ); - } - } - } - - // 如果有callback,则用异步方式。 - if ( callback || dfds.length ) { - promise = Base.when.apply( Base, dfds ); - key = promise.pipe ? 'pipe' : 'then'; - - // 很重要不能删除。删除了会死循环。 - // 保证执行顺序。让callback总是在下一个 tick 中执行。 - return promise[ key ](function() { - var deferred = Base.Deferred(), - args = arguments; - - if ( args.length === 1 ) { - args = args[ 0 ]; - } - - setTimeout(function() { - deferred.resolve( args ); - }, 1 ); - - return deferred.promise(); - })[ callback ? key : 'done' ]( callback || Base.noop ); - } else { - return rlts[ 0 ]; - } - }, - - destroy: function() { - _destroy.apply( this, arguments ); - this._widgets = null; - } - }); - - /** - * 添加组件 - * @grammar Uploader.register(proto); - * @grammar Uploader.register(map, proto); - * @param {object} responseMap API 名称与函数实现的映射 - * @param {object} proto 组件原型,构造函数通过 constructor 属性定义 - * @method Uploader.register - * @for Uploader - * @example - * Uploader.register({ - * 'make-thumb': 'makeThumb' - * }, { - * init: function( options ) {}, - * makeThumb: function() {} - * }); - * - * Uploader.register({ - * 'make-thumb': function() { - * - * } - * }); - */ - Uploader.register = Widget.register = function( responseMap, widgetProto ) { - var map = { init: 'init', destroy: 'destroy', name: 'anonymous' }, - klass; - - if ( arguments.length === 1 ) { - widgetProto = responseMap; - - // 自动生成 map 表。 - $.each(widgetProto, function(key) { - if ( key[0] === '_' || key === 'name' ) { - key === 'name' && (map.name = widgetProto.name); - return; - } - - map[key.replace(/[A-Z]/g, '-$&').toLowerCase()] = key; - }); - - } else { - map = $.extend( map, responseMap ); - } - - widgetProto.responseMap = map; - klass = Base.inherits( Widget, widgetProto ); - klass._name = map.name; - widgetClass.push( klass ); - - return klass; - }; - - /** - * 删除插件,只有在注册时指定了名字的才能被删除。 - * @grammar Uploader.unRegister(name); - * @param {string} name 组件名字 - * @method Uploader.unRegister - * @for Uploader - * @example - * - * Uploader.register({ - * name: 'custom', - * - * 'make-thumb': function() { - * - * } - * }); - * - * Uploader.unRegister('custom'); - */ - Uploader.unRegister = Widget.unRegister = function( name ) { - if ( !name || name === 'anonymous' ) { - return; - } - - // 删除指定的插件。 - for ( var i = widgetClass.length; i--; ) { - if ( widgetClass[i]._name === name ) { - widgetClass.splice(i, 1) - } - } - }; - - return Widget; - }); - /** - * @fileOverview DragAndDrop Widget。 - */ - define('widgets/filednd',[ - 'base', - 'uploader', - 'lib/dnd', - 'widgets/widget' - ], function( Base, Uploader, Dnd ) { - var $ = Base.$; - - Uploader.options.dnd = ''; - - /** - * @property {Selector} [dnd=undefined] 指定Drag And Drop拖拽的容器,如果不指定,则不启动。 - * @namespace options - * @for Uploader - */ - - /** - * @property {Selector} [disableGlobalDnd=false] 是否禁掉整个页面的拖拽功能,如果不禁用,图片拖进来的时候会默认被浏览器打开。 - * @namespace options - * @for Uploader - */ - - /** - * @event dndAccept - * @param {DataTransferItemList} items DataTransferItem - * @description 阻止此事件可以拒绝某些类型的文件拖入进来。目前只有 chrome 提供这样的 API,且只能通过 mime-type 验证。 - * @for Uploader - */ - return Uploader.register({ - name: 'dnd', - - init: function( opts ) { - - if ( !opts.dnd || - this.request('predict-runtime-type') !== 'html5' ) { - return; - } - - var me = this, - deferred = Base.Deferred(), - options = $.extend({}, { - disableGlobalDnd: opts.disableGlobalDnd, - container: opts.dnd, - accept: opts.accept - }), - dnd; - - this.dnd = dnd = new Dnd( options ); - - dnd.once( 'ready', deferred.resolve ); - dnd.on( 'drop', function( files ) { - me.request( 'add-file', [ files ]); - }); - - // 检测文件是否全部允许添加。 - dnd.on( 'accept', function( items ) { - return me.owner.trigger( 'dndAccept', items ); - }); - - dnd.init(); - - return deferred.promise(); - }, - - destroy: function() { - this.dnd && this.dnd.destroy(); - } - }); - }); - - /** - * @fileOverview 错误信息 - */ - define('lib/filepaste',[ - 'base', - 'mediator', - 'runtime/client' - ], function( Base, Mediator, RuntimeClent ) { - - var $ = Base.$; - - function FilePaste( opts ) { - opts = this.options = $.extend({}, opts ); - opts.container = $( opts.container || document.body ); - RuntimeClent.call( this, 'FilePaste' ); - } - - Base.inherits( RuntimeClent, { - constructor: FilePaste, - - init: function() { - var me = this; - - me.connectRuntime( me.options, function() { - me.exec('init'); - me.trigger('ready'); - }); - } - }); - - Mediator.installTo( FilePaste.prototype ); - - return FilePaste; - }); - /** - * @fileOverview 组件基类。 - */ - define('widgets/filepaste',[ - 'base', - 'uploader', - 'lib/filepaste', - 'widgets/widget' - ], function( Base, Uploader, FilePaste ) { - var $ = Base.$; - - /** - * @property {Selector} [paste=undefined] 指定监听paste事件的容器,如果不指定,不启用此功能。此功能为通过粘贴来添加截屏的图片。建议设置为`document.body`. - * @namespace options - * @for Uploader - */ - return Uploader.register({ - name: 'paste', - - init: function( opts ) { - - if ( !opts.paste || - this.request('predict-runtime-type') !== 'html5' ) { - return; - } - - var me = this, - deferred = Base.Deferred(), - options = $.extend({}, { - container: opts.paste, - accept: opts.accept - }), - paste; - - this.paste = paste = new FilePaste( options ); - - paste.once( 'ready', deferred.resolve ); - paste.on( 'paste', function( files ) { - me.owner.request( 'add-file', [ files ]); - }); - paste.init(); - - return deferred.promise(); - }, - - destroy: function() { - this.paste && this.paste.destroy(); - } - }); - }); - /** - * @fileOverview Blob - */ - define('lib/blob',[ - 'base', - 'runtime/client' - ], function( Base, RuntimeClient ) { - - function Blob( ruid, source ) { - var me = this; - - me.source = source; - me.ruid = ruid; - this.size = source.size || 0; - - // 如果没有指定 mimetype, 但是知道文件后缀。 - if ( !source.type && this.ext && - ~'jpg,jpeg,png,gif,bmp'.indexOf( this.ext ) ) { - this.type = 'image/' + (this.ext === 'jpg' ? 'jpeg' : this.ext); - } else { - this.type = source.type || 'application/octet-stream'; - } - - RuntimeClient.call( me, 'Blob' ); - this.uid = source.uid || this.uid; - - if ( ruid ) { - me.connectRuntime( ruid ); - } - } - - Base.inherits( RuntimeClient, { - constructor: Blob, - - slice: function( start, end ) { - return this.exec( 'slice', start, end ); - }, - - getSource: function() { - return this.source; - } - }); - - return Blob; - }); - /** - * 为了统一化Flash的File和HTML5的File而存在。 - * 以至于要调用Flash里面的File,也可以像调用HTML5版本的File一下。 - * @fileOverview File - */ - define('lib/file',[ - 'base', - 'lib/blob' - ], function( Base, Blob ) { - - var uid = 1, - rExt = /\.([^.]+)$/; - - function File( ruid, file ) { - var ext; - - this.name = file.name || ('untitled' + uid++); - ext = rExt.exec( file.name ) ? RegExp.$1.toLowerCase() : ''; - - // todo 支持其他类型文件的转换。 - // 如果有 mimetype, 但是文件名里面没有找出后缀规律 - if ( !ext && file.type ) { - ext = /\/(jpg|jpeg|png|gif|bmp)$/i.exec( file.type ) ? - RegExp.$1.toLowerCase() : ''; - this.name += '.' + ext; - } - - this.ext = ext; - this.lastModifiedDate = file.lastModifiedDate || - (new Date()).toLocaleString(); - - Blob.apply( this, arguments ); - } - - return Base.inherits( Blob, File ); - }); - - /** - * @fileOverview 错误信息 - */ - define('lib/filepicker',[ - 'base', - 'runtime/client', - 'lib/file' - ], function( Base, RuntimeClent, File ) { - - var $ = Base.$; - - function FilePicker( opts ) { - opts = this.options = $.extend({}, FilePicker.options, opts ); - - opts.container = $( opts.id ); - - if ( !opts.container.length ) { - throw new Error('按钮指定错误'); - } - - opts.innerHTML = opts.innerHTML || opts.label || - opts.container.html() || ''; - - opts.button = $( opts.button || document.createElement('div') ); - opts.button.html( opts.innerHTML ); - opts.container.html( opts.button ); - - RuntimeClent.call( this, 'FilePicker', true ); - } - - FilePicker.options = { - button: null, - container: null, - label: null, - innerHTML: null, - multiple: true, - accept: null, - name: 'file' - }; - - Base.inherits( RuntimeClent, { - constructor: FilePicker, - - init: function() { - var me = this, - opts = me.options, - button = opts.button; - - button.addClass('webuploader-pick'); - - me.on( 'all', function( type ) { - var files; - - switch ( type ) { - case 'mouseenter': - button.addClass('webuploader-pick-hover'); - break; - - case 'mouseleave': - button.removeClass('webuploader-pick-hover'); - break; - - case 'change': - files = me.exec('getFiles'); - me.trigger( 'select', $.map( files, function( file ) { - file = new File( me.getRuid(), file ); - - // 记录来源。 - file._refer = opts.container; - return file; - }), opts.container ); - break; - } - }); - - me.connectRuntime( opts, function() { - me.refresh(); - me.exec( 'init', opts ); - me.trigger('ready'); - }); - - this._resizeHandler = Base.bindFn( this.refresh, this ); - $( window ).on( 'resize', this._resizeHandler ); - }, - - refresh: function() { - var shimContainer = this.getRuntime().getContainer(), - button = this.options.button, - width = button.outerWidth ? - button.outerWidth() : button.width(), - - height = button.outerHeight ? - button.outerHeight() : button.height(), - - pos = button.offset(); - - width && height && shimContainer.css({ - bottom: 'auto', - right: 'auto', - width: width + 'px', - height: height + 'px' - }).offset( pos ); - }, - - enable: function() { - var btn = this.options.button; - - btn.removeClass('webuploader-pick-disable'); - this.refresh(); - }, - - disable: function() { - var btn = this.options.button; - - this.getRuntime().getContainer().css({ - top: '-99999px' - }); - - btn.addClass('webuploader-pick-disable'); - }, - - destroy: function() { - var btn = this.options.button; - $( window ).off( 'resize', this._resizeHandler ); - btn.removeClass('webuploader-pick-disable webuploader-pick-hover ' + - 'webuploader-pick'); - } - }); - - return FilePicker; - }); - - /** - * @fileOverview 文件选择相关 - */ - define('widgets/filepicker',[ - 'base', - 'uploader', - 'lib/filepicker', - 'widgets/widget' - ], function( Base, Uploader, FilePicker ) { - var $ = Base.$; - - $.extend( Uploader.options, { - - /** - * @property {Selector | Object} [pick=undefined] - * @namespace options - * @for Uploader - * @description 指定选择文件的按钮容器,不指定则不创建按钮。 - * - * * `id` {Seletor|dom} 指定选择文件的按钮容器,不指定则不创建按钮。**注意** 这里虽然写的是 id, 但是不是只支持 id, 还支持 class, 或者 dom 节点。 - * * `label` {String} 请采用 `innerHTML` 代替 - * * `innerHTML` {String} 指定按钮文字。不指定时优先从指定的容器中看是否自带文字。 - * * `multiple` {Boolean} 是否开起同时选择多个文件能力。 - */ - pick: null, - - /** - * @property {Arroy} [accept=null] - * @namespace options - * @for Uploader - * @description 指定接受哪些类型的文件。 由于目前还有ext转mimeType表,所以这里需要分开指定。 - * - * * `title` {String} 文字描述 - * * `extensions` {String} 允许的文件后缀,不带点,多个用逗号分割。 - * * `mimeTypes` {String} 多个用逗号分割。 - * - * 如: - * - * ``` - * { - * title: 'Images', - * extensions: 'gif,jpg,jpeg,bmp,png', - * mimeTypes: 'image/*' - * } - * ``` - */ - accept: null/*{ - title: 'Images', - extensions: 'gif,jpg,jpeg,bmp,png', - mimeTypes: 'image/*' - }*/ - }); - - return Uploader.register({ - name: 'picker', - - init: function( opts ) { - this.pickers = []; - return opts.pick && this.addBtn( opts.pick ); - }, - - refresh: function() { - $.each( this.pickers, function() { - this.refresh(); - }); - }, - - /** - * @method addButton - * @for Uploader - * @grammar addButton( pick ) => Promise - * @description - * 添加文件选择按钮,如果一个按钮不够,需要调用此方法来添加。参数跟[options.pick](#WebUploader:Uploader:options)一致。 - * @example - * uploader.addButton({ - * id: '#btnContainer', - * innerHTML: '选择文件' - * }); - */ - addBtn: function( pick ) { - var me = this, - opts = me.options, - accept = opts.accept, - promises = []; - - if ( !pick ) { - return; - } - - $.isPlainObject( pick ) || (pick = { - id: pick - }); - - $( pick.id ).each(function() { - var options, picker, deferred; - - deferred = Base.Deferred(); - - options = $.extend({}, pick, { - accept: $.isPlainObject( accept ) ? [ accept ] : accept, - swf: opts.swf, - runtimeOrder: opts.runtimeOrder, - id: this - }); - - picker = new FilePicker( options ); - - picker.once( 'ready', deferred.resolve ); - picker.on( 'select', function( files ) { - me.owner.request( 'add-file', [ files ]); - }); - picker.init(); - - me.pickers.push( picker ); - - promises.push( deferred.promise() ); - }); - - return Base.when.apply( Base, promises ); - }, - - disable: function() { - $.each( this.pickers, function() { - this.disable(); - }); - }, - - enable: function() { - $.each( this.pickers, function() { - this.enable(); - }); - }, - - destroy: function() { - $.each( this.pickers, function() { - this.destroy(); - }); - this.pickers = null; - } - }); - }); - /** - * @fileOverview Image - */ - define('lib/image',[ - 'base', - 'runtime/client', - 'lib/blob' - ], function( Base, RuntimeClient, Blob ) { - var $ = Base.$; - - // 构造器。 - function Image( opts ) { - this.options = $.extend({}, Image.options, opts ); - RuntimeClient.call( this, 'Image' ); - - this.on( 'load', function() { - this._info = this.exec('info'); - this._meta = this.exec('meta'); - }); - } - - // 默认选项。 - Image.options = { - - // 默认的图片处理质量 - quality: 90, - - // 是否裁剪 - crop: false, - - // 是否保留头部信息 - preserveHeaders: false, - - // 是否允许放大。 - allowMagnify: false - }; - - // 继承RuntimeClient. - Base.inherits( RuntimeClient, { - constructor: Image, - - info: function( val ) { - - // setter - if ( val ) { - this._info = val; - return this; - } - - // getter - return this._info; - }, - - meta: function( val ) { - - // setter - if ( val ) { - this._meta = val; - return this; - } - - // getter - return this._meta; - }, - - loadFromBlob: function( blob ) { - var me = this, - ruid = blob.getRuid(); - - this.connectRuntime( ruid, function() { - me.exec( 'init', me.options ); - me.exec( 'loadFromBlob', blob ); - }); - }, - - resize: function() { - var args = Base.slice( arguments ); - return this.exec.apply( this, [ 'resize' ].concat( args ) ); - }, - - crop: function() { - var args = Base.slice( arguments ); - return this.exec.apply( this, [ 'crop' ].concat( args ) ); - }, - - getAsDataUrl: function( type ) { - return this.exec( 'getAsDataUrl', type ); - }, - - getAsBlob: function( type ) { - var blob = this.exec( 'getAsBlob', type ); - - return new Blob( this.getRuid(), blob ); - } - }); - - return Image; - }); - /** - * @fileOverview 图片操作, 负责预览图片和上传前压缩图片 - */ - define('widgets/image',[ - 'base', - 'uploader', - 'lib/image', - 'widgets/widget' - ], function( Base, Uploader, Image ) { - - var $ = Base.$, - throttle; - - // 根据要处理的文件大小来节流,一次不能处理太多,会卡。 - throttle = (function( max ) { - var occupied = 0, - waiting = [], - tick = function() { - var item; - - while ( waiting.length && occupied < max ) { - item = waiting.shift(); - occupied += item[ 0 ]; - item[ 1 ](); - } - }; - - return function( emiter, size, cb ) { - waiting.push([ size, cb ]); - emiter.once( 'destroy', function() { - occupied -= size; - setTimeout( tick, 1 ); - }); - setTimeout( tick, 1 ); - }; - })( 5 * 1024 * 1024 ); - - $.extend( Uploader.options, { - - /** - * @property {Object} [thumb] - * @namespace options - * @for Uploader - * @description 配置生成缩略图的选项。 - * - * 默认为: - * - * ```javascript - * { - * width: 110, - * height: 110, - * - * // 图片质量,只有type为`image/jpeg`的时候才有效。 - * quality: 70, - * - * // 是否允许放大,如果想要生成小图的时候不失真,此选项应该设置为false. - * allowMagnify: true, - * - * // 是否允许裁剪。 - * crop: true, - * - * // 为空的话则保留原有图片格式。 - * // 否则强制转换成指定的类型。 - * type: 'image/jpeg' - * } - * ``` - */ - thumb: { - width: 110, - height: 110, - quality: 70, - allowMagnify: true, - crop: true, - preserveHeaders: false, - - // 为空的话则保留原有图片格式。 - // 否则强制转换成指定的类型。 - // IE 8下面 base64 大小不能超过 32K 否则预览失败,而非 jpeg 编码的图片很可 - // 能会超过 32k, 所以这里设置成预览的时候都是 image/jpeg - type: 'image/jpeg' - }, - - /** - * @property {Object} [compress] - * @namespace options - * @for Uploader - * @description 配置压缩的图片的选项。如果此选项为`false`, 则图片在上传前不进行压缩。 - * - * 默认为: - * - * ```javascript - * { - * width: 1600, - * height: 1600, - * - * // 图片质量,只有type为`image/jpeg`的时候才有效。 - * quality: 90, - * - * // 是否允许放大,如果想要生成小图的时候不失真,此选项应该设置为false. - * allowMagnify: false, - * - * // 是否允许裁剪。 - * crop: false, - * - * // 是否保留头部meta信息。 - * preserveHeaders: true, - * - * // 如果发现压缩后文件大小比原来还大,则使用原来图片 - * // 此属性可能会影响图片自动纠正功能 - * noCompressIfLarger: false, - * - * // 单位字节,如果图片大小小于此值,不会采用压缩。 - * compressSize: 0 - * } - * ``` - */ - compress: { - width: 1600, - height: 1600, - quality: 90, - allowMagnify: false, - crop: false, - preserveHeaders: true - } - }); - - return Uploader.register({ - - name: 'image', - - - /** - * 生成缩略图,此过程为异步,所以需要传入`callback`。 - * 通常情况在图片加入队里后调用此方法来生成预览图以增强交互效果。 - * - * 当 width 或者 height 的值介于 0 - 1 时,被当成百分比使用。 - * - * `callback`中可以接收到两个参数。 - * * 第一个为error,如果生成缩略图有错误,此error将为真。 - * * 第二个为ret, 缩略图的Data URL值。 - * - * **注意** - * Date URL在IE6/7中不支持,所以不用调用此方法了,直接显示一张暂不支持预览图片好了。 - * 也可以借助服务端,将 base64 数据传给服务端,生成一个临时文件供预览。 - * - * @method makeThumb - * @grammar makeThumb( file, callback ) => undefined - * @grammar makeThumb( file, callback, width, height ) => undefined - * @for Uploader - * @example - * - * uploader.on( 'fileQueued', function( file ) { - * var $li = ...; - * - * uploader.makeThumb( file, function( error, ret ) { - * if ( error ) { - * $li.text('预览错误'); - * } else { - * $li.append(''); - * } - * }); - * - * }); - */ - makeThumb: function( file, cb, width, height ) { - var opts, image; - - file = this.request( 'get-file', file ); - - // 只预览图片格式。 - if ( !file.type.match( /^image/ ) ) { - cb( true ); - return; - } - - opts = $.extend({}, this.options.thumb ); - - // 如果传入的是object. - if ( $.isPlainObject( width ) ) { - opts = $.extend( opts, width ); - width = null; - } - - width = width || opts.width; - height = height || opts.height; - - image = new Image( opts ); - - image.once( 'load', function() { - file._info = file._info || image.info(); - file._meta = file._meta || image.meta(); - - // 如果 width 的值介于 0 - 1 - // 说明设置的是百分比。 - if ( width <= 1 && width > 0 ) { - width = file._info.width * width; - } - - // 同样的规则应用于 height - if ( height <= 1 && height > 0 ) { - height = file._info.height * height; - } - - image.resize( width, height ); - }); - - // 当 resize 完后 - image.once( 'complete', function() { - cb( false, image.getAsDataUrl( opts.type ) ); - image.destroy(); - }); - - image.once( 'error', function( reason ) { - cb( reason || true ); - image.destroy(); - }); - - throttle( image, file.source.size, function() { - file._info && image.info( file._info ); - file._meta && image.meta( file._meta ); - image.loadFromBlob( file.source ); - }); - }, - - beforeSendFile: function( file ) { - var opts = this.options.compress || this.options.resize, - compressSize = opts && opts.compressSize || 0, - noCompressIfLarger = opts && opts.noCompressIfLarger || false, - image, deferred; - - file = this.request( 'get-file', file ); - - // 只压缩 jpeg 图片格式。 - // gif 可能会丢失针 - // bmp png 基本上尺寸都不大,且压缩比比较小。 - if ( !opts || !~'image/jpeg,image/jpg'.indexOf( file.type ) || - file.size < compressSize || - file._compressed ) { - return; - } - - opts = $.extend({}, opts ); - deferred = Base.Deferred(); - - image = new Image( opts ); - - deferred.always(function() { - image.destroy(); - image = null; - }); - image.once( 'error', deferred.reject ); - image.once( 'load', function() { - var width = opts.width, - height = opts.height; - - file._info = file._info || image.info(); - file._meta = file._meta || image.meta(); - - // 如果 width 的值介于 0 - 1 - // 说明设置的是百分比。 - if ( width <= 1 && width > 0 ) { - width = file._info.width * width; - } - - // 同样的规则应用于 height - if ( height <= 1 && height > 0 ) { - height = file._info.height * height; - } - - image.resize( width, height ); - }); - - image.once( 'complete', function() { - var blob, size; - - // 移动端 UC / qq 浏览器的无图模式下 - // ctx.getImageData 处理大图的时候会报 Exception - // INDEX_SIZE_ERR: DOM Exception 1 - try { - blob = image.getAsBlob( opts.type ); - - size = file.size; - - // 如果压缩后,比原来还大则不用压缩后的。 - if ( !noCompressIfLarger || blob.size < size ) { - // file.source.destroy && file.source.destroy(); - file.source = blob; - file.size = blob.size; - - file.trigger( 'resize', blob.size, size ); - } - - // 标记,避免重复压缩。 - file._compressed = true; - deferred.resolve(); - } catch ( e ) { - // 出错了直接继续,让其上传原始图片 - deferred.resolve(); - } - }); - - file._info && image.info( file._info ); - file._meta && image.meta( file._meta ); - - image.loadFromBlob( file.source ); - return deferred.promise(); - } - }); - }); - /** - * @fileOverview 文件属性封装 - */ - define('file',[ - 'base', - 'mediator' - ], function( Base, Mediator ) { - - var $ = Base.$, - idPrefix = 'WU_FILE_', - idSuffix = 0, - rExt = /\.([^.]+)$/, - statusMap = {}; - - function gid() { - return idPrefix + idSuffix++; - } - - /** - * 文件类 - * @class File - * @constructor 构造函数 - * @grammar new File( source ) => File - * @param {Lib.File} source [lib.File](#Lib.File)实例, 此source对象是带有Runtime信息的。 - */ - function WUFile( source ) { - - /** - * 文件名,包括扩展名(后缀) - * @property name - * @type {string} - */ - this.name = source.name || 'Untitled'; - - /** - * 文件体积(字节) - * @property size - * @type {uint} - * @default 0 - */ - this.size = source.size || 0; - - /** - * 文件MIMETYPE类型,与文件类型的对应关系请参考[http://t.cn/z8ZnFny](http://t.cn/z8ZnFny) - * @property type - * @type {string} - * @default 'application/octet-stream' - */ - this.type = source.type || 'application/octet-stream'; - - /** - * 文件最后修改日期 - * @property lastModifiedDate - * @type {int} - * @default 当前时间戳 - */ - this.lastModifiedDate = source.lastModifiedDate || (new Date() * 1); - - /** - * 文件ID,每个对象具有唯一ID,与文件名无关 - * @property id - * @type {string} - */ - this.id = gid(); - - /** - * 文件扩展名,通过文件名获取,例如test.png的扩展名为png - * @property ext - * @type {string} - */ - this.ext = rExt.exec( this.name ) ? RegExp.$1 : ''; - - - /** - * 状态文字说明。在不同的status语境下有不同的用途。 - * @property statusText - * @type {string} - */ - this.statusText = ''; - - // 存储文件状态,防止通过属性直接修改 - statusMap[ this.id ] = WUFile.Status.INITED; - - this.source = source; - this.loaded = 0; - - this.on( 'error', function( msg ) { - this.setStatus( WUFile.Status.ERROR, msg ); - }); - } - - $.extend( WUFile.prototype, { - - /** - * 设置状态,状态变化时会触发`change`事件。 - * @method setStatus - * @grammar setStatus( status[, statusText] ); - * @param {File.Status|String} status [文件状态值](#WebUploader:File:File.Status) - * @param {String} [statusText=''] 状态说明,常在error时使用,用http, abort,server等来标记是由于什么原因导致文件错误。 - */ - setStatus: function( status, text ) { - - var prevStatus = statusMap[ this.id ]; - - typeof text !== 'undefined' && (this.statusText = text); - - if ( status !== prevStatus ) { - statusMap[ this.id ] = status; - /** - * 文件状态变化 - * @event statuschange - */ - this.trigger( 'statuschange', status, prevStatus ); - } - - }, - - /** - * 获取文件状态 - * @return {File.Status} - * @example - 文件状态具体包括以下几种类型: - { - // 初始化 - INITED: 0, - // 已入队列 - QUEUED: 1, - // 正在上传 - PROGRESS: 2, - // 上传出错 - ERROR: 3, - // 上传成功 - COMPLETE: 4, - // 上传取消 - CANCELLED: 5 - } - */ - getStatus: function() { - return statusMap[ this.id ]; - }, - - /** - * 获取文件原始信息。 - * @return {*} - */ - getSource: function() { - return this.source; - }, - - destroy: function() { - this.off(); - delete statusMap[ this.id ]; - } - }); - - Mediator.installTo( WUFile.prototype ); - - /** - * 文件状态值,具体包括以下几种类型: - * * `inited` 初始状态 - * * `queued` 已经进入队列, 等待上传 - * * `progress` 上传中 - * * `complete` 上传完成。 - * * `error` 上传出错,可重试 - * * `interrupt` 上传中断,可续传。 - * * `invalid` 文件不合格,不能重试上传。会自动从队列中移除。 - * * `cancelled` 文件被移除。 - * @property {Object} Status - * @namespace File - * @class File - * @static - */ - WUFile.Status = { - INITED: 'inited', // 初始状态 - QUEUED: 'queued', // 已经进入队列, 等待上传 - PROGRESS: 'progress', // 上传中 - ERROR: 'error', // 上传出错,可重试 - COMPLETE: 'complete', // 上传完成。 - CANCELLED: 'cancelled', // 上传取消。 - INTERRUPT: 'interrupt', // 上传中断,可续传。 - INVALID: 'invalid' // 文件不合格,不能重试上传。 - }; - - return WUFile; - }); - - /** - * @fileOverview 文件队列 - */ - define('queue',[ - 'base', - 'mediator', - 'file' - ], function( Base, Mediator, WUFile ) { - - var $ = Base.$, - STATUS = WUFile.Status; - - /** - * 文件队列, 用来存储各个状态中的文件。 - * @class Queue - * @extends Mediator - */ - function Queue() { - - /** - * 统计文件数。 - * * `numOfQueue` 队列中的文件数。 - * * `numOfSuccess` 上传成功的文件数 - * * `numOfCancel` 被取消的文件数 - * * `numOfProgress` 正在上传中的文件数 - * * `numOfUploadFailed` 上传错误的文件数。 - * * `numOfInvalid` 无效的文件数。 - * * `numofDeleted` 被移除的文件数。 - * @property {Object} stats - */ - this.stats = { - numOfQueue: 0, - numOfSuccess: 0, - numOfCancel: 0, - numOfProgress: 0, - numOfUploadFailed: 0, - numOfInvalid: 0, - numofDeleted: 0, - numofInterrupt: 0 - }; - - // 上传队列,仅包括等待上传的文件 - this._queue = []; - - // 存储所有文件 - this._map = {}; - } - - $.extend( Queue.prototype, { - - /** - * 将新文件加入对队列尾部 - * - * @method append - * @param {File} file 文件对象 - */ - append: function( file ) { - this._queue.push( file ); - this._fileAdded( file ); - return this; - }, - - /** - * 将新文件加入对队列头部 - * - * @method prepend - * @param {File} file 文件对象 - */ - prepend: function( file ) { - this._queue.unshift( file ); - this._fileAdded( file ); - return this; - }, - - /** - * 获取文件对象 - * - * @method getFile - * @param {String} fileId 文件ID - * @return {File} - */ - getFile: function( fileId ) { - if ( typeof fileId !== 'string' ) { - return fileId; - } - return this._map[ fileId ]; - }, - - /** - * 从队列中取出一个指定状态的文件。 - * @grammar fetch( status ) => File - * @method fetch - * @param {String} status [文件状态值](#WebUploader:File:File.Status) - * @return {File} [File](#WebUploader:File) - */ - fetch: function( status ) { - var len = this._queue.length, - i, file; - - status = status || STATUS.QUEUED; - - for ( i = 0; i < len; i++ ) { - file = this._queue[ i ]; - - if ( status === file.getStatus() ) { - return file; - } - } - - return null; - }, - - /** - * 对队列进行排序,能够控制文件上传顺序。 - * @grammar sort( fn ) => undefined - * @method sort - * @param {Function} fn 排序方法 - */ - sort: function( fn ) { - if ( typeof fn === 'function' ) { - this._queue.sort( fn ); - } - }, - - /** - * 获取指定类型的文件列表, 列表中每一个成员为[File](#WebUploader:File)对象。 - * @grammar getFiles( [status1[, status2 ...]] ) => Array - * @method getFiles - * @param {String} [status] [文件状态值](#WebUploader:File:File.Status) - */ - getFiles: function() { - var sts = [].slice.call( arguments, 0 ), - ret = [], - i = 0, - len = this._queue.length, - file; - - for ( ; i < len; i++ ) { - file = this._queue[ i ]; - - if ( sts.length && !~$.inArray( file.getStatus(), sts ) ) { - continue; - } - - ret.push( file ); - } - - return ret; - }, - - /** - * 在队列中删除文件。 - * @grammar removeFile( file ) => Array - * @method removeFile - * @param {File} 文件对象。 - */ - removeFile: function( file ) { - var me = this, - existing = this._map[ file.id ]; - - if ( existing ) { - delete this._map[ file.id ]; - file.destroy(); - this.stats.numofDeleted++; - } - }, - - _fileAdded: function( file ) { - var me = this, - existing = this._map[ file.id ]; - - if ( !existing ) { - this._map[ file.id ] = file; - - file.on( 'statuschange', function( cur, pre ) { - me._onFileStatusChange( cur, pre ); - }); - } - }, - - _onFileStatusChange: function( curStatus, preStatus ) { - var stats = this.stats; - - switch ( preStatus ) { - case STATUS.PROGRESS: - stats.numOfProgress--; - break; - - case STATUS.QUEUED: - stats.numOfQueue --; - break; - - case STATUS.ERROR: - stats.numOfUploadFailed--; - break; - - case STATUS.INVALID: - stats.numOfInvalid--; - break; - - case STATUS.INTERRUPT: - stats.numofInterrupt--; - break; - } - - switch ( curStatus ) { - case STATUS.QUEUED: - stats.numOfQueue++; - break; - - case STATUS.PROGRESS: - stats.numOfProgress++; - break; - - case STATUS.ERROR: - stats.numOfUploadFailed++; - break; - - case STATUS.COMPLETE: - stats.numOfSuccess++; - break; - - case STATUS.CANCELLED: - stats.numOfCancel++; - break; - - - case STATUS.INVALID: - stats.numOfInvalid++; - break; - - case STATUS.INTERRUPT: - stats.numofInterrupt++; - break; - } - } - - }); - - Mediator.installTo( Queue.prototype ); - - return Queue; - }); - /** - * @fileOverview 队列 - */ - define('widgets/queue',[ - 'base', - 'uploader', - 'queue', - 'file', - 'lib/file', - 'runtime/client', - 'widgets/widget' - ], function( Base, Uploader, Queue, WUFile, File, RuntimeClient ) { - - var $ = Base.$, - rExt = /\.\w+$/, - Status = WUFile.Status; - - return Uploader.register({ - name: 'queue', - - init: function( opts ) { - var me = this, - deferred, len, i, item, arr, accept, runtime; - - if ( $.isPlainObject( opts.accept ) ) { - opts.accept = [ opts.accept ]; - } - - // accept中的中生成匹配正则。 - if ( opts.accept ) { - arr = []; - - for ( i = 0, len = opts.accept.length; i < len; i++ ) { - item = opts.accept[ i ].extensions; - item && arr.push( item ); - } - - if ( arr.length ) { - accept = '\\.' + arr.join(',') - .replace( /,/g, '$|\\.' ) - .replace( /\*/g, '.*' ) + '$'; - } - - me.accept = new RegExp( accept, 'i' ); - } - - me.queue = new Queue(); - me.stats = me.queue.stats; - - // 如果当前不是html5运行时,那就算了。 - // 不执行后续操作 - if ( this.request('predict-runtime-type') !== 'html5' ) { - return; - } - - // 创建一个 html5 运行时的 placeholder - // 以至于外部添加原生 File 对象的时候能正确包裹一下供 webuploader 使用。 - deferred = Base.Deferred(); - this.placeholder = runtime = new RuntimeClient('Placeholder'); - runtime.connectRuntime({ - runtimeOrder: 'html5' - }, function() { - me._ruid = runtime.getRuid(); - deferred.resolve(); - }); - return deferred.promise(); - }, - - - // 为了支持外部直接添加一个原生File对象。 - _wrapFile: function( file ) { - if ( !(file instanceof WUFile) ) { - - if ( !(file instanceof File) ) { - if ( !this._ruid ) { - throw new Error('Can\'t add external files.'); - } - file = new File( this._ruid, file ); - } - - file = new WUFile( file ); - } - - return file; - }, - - // 判断文件是否可以被加入队列 - acceptFile: function( file ) { - var invalid = !file || !file.size || this.accept && - - // 如果名字中有后缀,才做后缀白名单处理。 - rExt.exec( file.name ) && !this.accept.test( file.name ); - - return !invalid; - }, - - - /** - * @event beforeFileQueued - * @param {File} file File对象 - * @description 当文件被加入队列之前触发,此事件的handler返回值为`false`,则此文件不会被添加进入队列。 - * @for Uploader - */ - - /** - * @event fileQueued - * @param {File} file File对象 - * @description 当文件被加入队列以后触发。 - * @for Uploader - */ - - _addFile: function( file ) { - var me = this; - - file = me._wrapFile( file ); - - // 不过类型判断允许不允许,先派送 `beforeFileQueued` - if ( !me.owner.trigger( 'beforeFileQueued', file ) ) { - return; - } - - // 类型不匹配,则派送错误事件,并返回。 - if ( !me.acceptFile( file ) ) { - me.owner.trigger( 'error', 'Q_TYPE_DENIED', file ); - return; - } - - me.queue.append( file ); - me.owner.trigger( 'fileQueued', file ); - return file; - }, - - getFile: function( fileId ) { - return this.queue.getFile( fileId ); - }, - - /** - * @event filesQueued - * @param {File} files 数组,内容为原始File(lib/File)对象。 - * @description 当一批文件添加进队列以后触发。 - * @for Uploader - */ - - /** - * @property {Boolean} [auto=false] - * @namespace options - * @for Uploader - * @description 设置为 true 后,不需要手动调用上传,有文件选择即开始上传。 - * - */ - - /** - * @method addFiles - * @grammar addFiles( file ) => undefined - * @grammar addFiles( [file1, file2 ...] ) => undefined - * @param {Array of File or File} [files] Files 对象 数组 - * @description 添加文件到队列 - * @for Uploader - */ - addFile: function( files ) { - var me = this; - - if ( !files.length ) { - files = [ files ]; - } - - files = $.map( files, function( file ) { - return me._addFile( file ); - }); - - me.owner.trigger( 'filesQueued', files ); - - if ( me.options.auto ) { - setTimeout(function() { - me.request('start-upload'); - }, 20 ); - } - }, - - getStats: function() { - return this.stats; - }, - - /** - * @event fileDequeued - * @param {File} file File对象 - * @description 当文件被移除队列后触发。 - * @for Uploader - */ - - /** - * @method removeFile - * @grammar removeFile( file ) => undefined - * @grammar removeFile( id ) => undefined - * @grammar removeFile( file, true ) => undefined - * @grammar removeFile( id, true ) => undefined - * @param {File|id} file File对象或这File对象的id - * @description 移除某一文件, 默认只会标记文件状态为已取消,如果第二个参数为 `true` 则会从 queue 中移除。 - * @for Uploader - * @example - * - * $li.on('click', '.remove-this', function() { - * uploader.removeFile( file ); - * }) - */ - removeFile: function( file, remove ) { - var me = this; - - file = file.id ? file : me.queue.getFile( file ); - - this.request( 'cancel-file', file ); - - if ( remove ) { - this.queue.removeFile( file ); - } - }, - - /** - * @method getFiles - * @grammar getFiles() => Array - * @grammar getFiles( status1, status2, status... ) => Array - * @description 返回指定状态的文件集合,不传参数将返回所有状态的文件。 - * @for Uploader - * @example - * console.log( uploader.getFiles() ); // => all files - * console.log( uploader.getFiles('error') ) // => all error files. - */ - getFiles: function() { - return this.queue.getFiles.apply( this.queue, arguments ); - }, - - fetchFile: function() { - return this.queue.fetch.apply( this.queue, arguments ); - }, - - /** - * @method retry - * @grammar retry() => undefined - * @grammar retry( file ) => undefined - * @description 重试上传,重试指定文件,或者从出错的文件开始重新上传。 - * @for Uploader - * @example - * function retry() { - * uploader.retry(); - * } - */ - retry: function( file, noForceStart ) { - var me = this, - files, i, len; - - if ( file ) { - file = file.id ? file : me.queue.getFile( file ); - file.setStatus( Status.QUEUED ); - noForceStart || me.request('start-upload'); - return; - } - - files = me.queue.getFiles( Status.ERROR ); - i = 0; - len = files.length; - - for ( ; i < len; i++ ) { - file = files[ i ]; - file.setStatus( Status.QUEUED ); - } - - me.request('start-upload'); - }, - - /** - * @method sort - * @grammar sort( fn ) => undefined - * @description 排序队列中的文件,在上传之前调整可以控制上传顺序。 - * @for Uploader - */ - sortFiles: function() { - return this.queue.sort.apply( this.queue, arguments ); - }, - - /** - * @event reset - * @description 当 uploader 被重置的时候触发。 - * @for Uploader - */ - - /** - * @method reset - * @grammar reset() => undefined - * @description 重置uploader。目前只重置了队列。 - * @for Uploader - * @example - * uploader.reset(); - */ - reset: function() { - this.owner.trigger('reset'); - this.queue = new Queue(); - this.stats = this.queue.stats; - }, - - destroy: function() { - this.reset(); - this.placeholder && this.placeholder.destroy(); - } - }); - - }); - /** - * @fileOverview 添加获取Runtime相关信息的方法。 - */ - define('widgets/runtime',[ - 'uploader', - 'runtime/runtime', - 'widgets/widget' - ], function( Uploader, Runtime ) { - - Uploader.support = function() { - return Runtime.hasRuntime.apply( Runtime, arguments ); - }; - - /** - * @property {Object} [runtimeOrder=html5,flash] - * @namespace options - * @for Uploader - * @description 指定运行时启动顺序。默认会想尝试 html5 是否支持,如果支持则使用 html5, 否则则使用 flash. - * - * 可以将此值设置成 `flash`,来强制使用 flash 运行时。 - */ - - return Uploader.register({ - name: 'runtime', - - init: function() { - if ( !this.predictRuntimeType() ) { - throw Error('Runtime Error'); - } - }, - - /** - * 预测Uploader将采用哪个`Runtime` - * @grammar predictRuntimeType() => String - * @method predictRuntimeType - * @for Uploader - */ - predictRuntimeType: function() { - var orders = this.options.runtimeOrder || Runtime.orders, - type = this.type, - i, len; - - if ( !type ) { - orders = orders.split( /\s*,\s*/g ); - - for ( i = 0, len = orders.length; i < len; i++ ) { - if ( Runtime.hasRuntime( orders[ i ] ) ) { - this.type = type = orders[ i ]; - break; - } - } - } - - return type; - } - }); - }); - /** - * @fileOverview Transport - */ - define('lib/transport',[ - 'base', - 'runtime/client', - 'mediator' - ], function( Base, RuntimeClient, Mediator ) { - - var $ = Base.$; - - function Transport( opts ) { - var me = this; - - opts = me.options = $.extend( true, {}, Transport.options, opts || {} ); - RuntimeClient.call( this, 'Transport' ); - - this._blob = null; - this._formData = opts.formData || {}; - this._headers = opts.headers || {}; - - this.on( 'progress', this._timeout ); - this.on( 'load error', function() { - me.trigger( 'progress', 1 ); - clearTimeout( me._timer ); - }); - } - - Transport.options = { - server: '', - method: 'POST', - - // 跨域时,是否允许携带cookie, 只有html5 runtime才有效 - withCredentials: false, - fileVal: 'file', - timeout: 2 * 60 * 1000, // 2分钟 - formData: {}, - headers: {}, - sendAsBinary: false - }; - - $.extend( Transport.prototype, { - - // 添加Blob, 只能添加一次,最后一次有效。 - appendBlob: function( key, blob, filename ) { - var me = this, - opts = me.options; - - if ( me.getRuid() ) { - me.disconnectRuntime(); - } - - // 连接到blob归属的同一个runtime. - me.connectRuntime( blob.ruid, function() { - me.exec('init'); - }); - - me._blob = blob; - opts.fileVal = key || opts.fileVal; - opts.filename = filename || opts.filename; - }, - - // 添加其他字段 - append: function( key, value ) { - if ( typeof key === 'object' ) { - $.extend( this._formData, key ); - } else { - this._formData[ key ] = value; - } - }, - - setRequestHeader: function( key, value ) { - if ( typeof key === 'object' ) { - $.extend( this._headers, key ); - } else { - this._headers[ key ] = value; - } - }, - - send: function( method ) { - this.exec( 'send', method ); - this._timeout(); - }, - - abort: function() { - clearTimeout( this._timer ); - return this.exec('abort'); - }, - - destroy: function() { - this.trigger('destroy'); - this.off(); - this.exec('destroy'); - this.disconnectRuntime(); - }, - - getResponse: function() { - return this.exec('getResponse'); - }, - - getResponseAsJson: function() { - return this.exec('getResponseAsJson'); - }, - - getStatus: function() { - return this.exec('getStatus'); - }, - - _timeout: function() { - var me = this, - duration = me.options.timeout; - - if ( !duration ) { - return; - } - - clearTimeout( me._timer ); - me._timer = setTimeout(function() { - me.abort(); - me.trigger( 'error', 'timeout' ); - }, duration ); - } - - }); - - // 让Transport具备事件功能。 - Mediator.installTo( Transport.prototype ); - - return Transport; - }); - /** - * @fileOverview 负责文件上传相关。 - */ - define('widgets/upload',[ - 'base', - 'uploader', - 'file', - 'lib/transport', - 'widgets/widget' - ], function( Base, Uploader, WUFile, Transport ) { - - var $ = Base.$, - isPromise = Base.isPromise, - Status = WUFile.Status; - - // 添加默认配置项 - $.extend( Uploader.options, { - - - /** - * @property {Boolean} [prepareNextFile=false] - * @namespace options - * @for Uploader - * @description 是否允许在文件传输时提前把下一个文件准备好。 - * 对于一个文件的准备工作比较耗时,比如图片压缩,md5序列化。 - * 如果能提前在当前文件传输期处理,可以节省总体耗时。 - */ - prepareNextFile: false, - - /** - * @property {Boolean} [chunked=false] - * @namespace options - * @for Uploader - * @description 是否要分片处理大文件上传。 - */ - chunked: false, - - /** - * @property {Boolean} [chunkSize=5242880] - * @namespace options - * @for Uploader - * @description 如果要分片,分多大一片? 默认大小为5M. - */ - chunkSize: 5 * 1024 * 1024, - - /** - * @property {Boolean} [chunkRetry=2] - * @namespace options - * @for Uploader - * @description 如果某个分片由于网络问题出错,允许自动重传多少次? - */ - chunkRetry: 2, - - /** - * @property {Boolean} [threads=3] - * @namespace options - * @for Uploader - * @description 上传并发数。允许同时最大上传进程数。 - */ - threads: 3, - - - /** - * @property {Object} [formData={}] - * @namespace options - * @for Uploader - * @description 文件上传请求的参数表,每次发送都会发送此对象中的参数。 - */ - formData: {} - - /** - * @property {Object} [fileVal='file'] - * @namespace options - * @for Uploader - * @description 设置文件上传域的name。 - */ - - /** - * @property {Object} [method='POST'] - * @namespace options - * @for Uploader - * @description 文件上传方式,`POST`或者`GET`。 - */ - - /** - * @property {Object} [sendAsBinary=false] - * @namespace options - * @for Uploader - * @description 是否已二进制的流的方式发送文件,这样整个上传内容`php://input`都为文件内容, - * 其他参数在$_GET数组中。 - */ - }); - - // 负责将文件切片。 - function CuteFile( file, chunkSize ) { - var pending = [], - blob = file.source, - total = blob.size, - chunks = chunkSize ? Math.ceil( total / chunkSize ) : 1, - start = 0, - index = 0, - len, api; - - api = { - file: file, - - has: function() { - return !!pending.length; - }, - - shift: function() { - return pending.shift(); - }, - - unshift: function( block ) { - pending.unshift( block ); - } - }; - - while ( index < chunks ) { - len = Math.min( chunkSize, total - start ); - - pending.push({ - file: file, - start: start, - end: chunkSize ? (start + len) : total, - total: total, - chunks: chunks, - chunk: index++, - cuted: api - }); - start += len; - } - - file.blocks = pending.concat(); - file.remaning = pending.length; - - return api; - } - - Uploader.register({ - name: 'upload', - - init: function() { - var owner = this.owner, - me = this; - - this.runing = false; - this.progress = false; - - owner - .on( 'startUpload', function() { - me.progress = true; - }) - .on( 'uploadFinished', function() { - me.progress = false; - }); - - // 记录当前正在传的数据,跟threads相关 - this.pool = []; - - // 缓存分好片的文件。 - this.stack = []; - - // 缓存即将上传的文件。 - this.pending = []; - - // 跟踪还有多少分片在上传中但是没有完成上传。 - this.remaning = 0; - this.__tick = Base.bindFn( this._tick, this ); - - owner.on( 'uploadComplete', function( file ) { - - // 把其他块取消了。 - file.blocks && $.each( file.blocks, function( _, v ) { - v.transport && (v.transport.abort(), v.transport.destroy()); - delete v.transport; - }); - - delete file.blocks; - delete file.remaning; - }); - }, - - reset: function() { - this.request( 'stop-upload', true ); - this.runing = false; - this.pool = []; - this.stack = []; - this.pending = []; - this.remaning = 0; - this._trigged = false; - this._promise = null; - }, - - /** - * @event startUpload - * @description 当开始上传流程时触发。 - * @for Uploader - */ - - /** - * 开始上传。此方法可以从初始状态调用开始上传流程,也可以从暂停状态调用,继续上传流程。 - * - * 可以指定开始某一个文件。 - * @grammar upload() => undefined - * @grammar upload( file | fileId) => undefined - * @method upload - * @for Uploader - */ - startUpload: function(file) { - var me = this; - - // 移出invalid的文件 - $.each( me.request( 'get-files', Status.INVALID ), function() { - me.request( 'remove-file', this ); - }); - - // 如果指定了开始某个文件,则只开始指定文件。 - if ( file ) { - file = file.id ? file : me.request( 'get-file', file ); - - if (file.getStatus() === Status.INTERRUPT) { - $.each( me.pool, function( _, v ) { - - // 之前暂停过。 - if (v.file !== file) { - return; - } - - v.transport && v.transport.send(); - }); - - file.setStatus( Status.QUEUED ); - } else if (file.getStatus() === Status.PROGRESS) { - return; - } else { - file.setStatus( Status.QUEUED ); - } - } else { - $.each( me.request( 'get-files', [ Status.INITED ] ), function() { - this.setStatus( Status.QUEUED ); - }); - } - - if ( me.runing ) { - return; - } - - me.runing = true; - - var files = []; - - // 如果有暂停的,则续传 - $.each( me.pool, function( _, v ) { - var file = v.file; - - if ( file.getStatus() === Status.INTERRUPT ) { - files.push(file); - me._trigged = false; - v.transport && v.transport.send(); - } - }); - - var file; - while ( (file = files.shift()) ) { - file.setStatus( Status.PROGRESS ); - } - - file || $.each( me.request( 'get-files', - Status.INTERRUPT ), function() { - this.setStatus( Status.PROGRESS ); - }); - - me._trigged = false; - Base.nextTick( me.__tick ); - me.owner.trigger('startUpload'); - }, - - /** - * @event stopUpload - * @description 当开始上传流程暂停时触发。 - * @for Uploader - */ - - /** - * 暂停上传。第一个参数为是否中断上传当前正在上传的文件。 - * - * 如果第一个参数是文件,则只暂停指定文件。 - * @grammar stop() => undefined - * @grammar stop( true ) => undefined - * @grammar stop( file ) => undefined - * @method stop - * @for Uploader - */ - stopUpload: function( file, interrupt ) { - var me = this; - - if (file === true) { - interrupt = file; - file = null; - } - - if ( me.runing === false ) { - return; - } - - // 如果只是暂停某个文件。 - if ( file ) { - file = file.id ? file : me.request( 'get-file', file ); - - if ( file.getStatus() !== Status.PROGRESS && - file.getStatus() !== Status.QUEUED ) { - return; - } - - file.setStatus( Status.INTERRUPT ); - $.each( me.pool, function( _, v ) { - - // 只 abort 指定的文件。 - if (v.file !== file) { - return; - } - - v.transport && v.transport.abort(); - me._putback(v); - me._popBlock(v); - }); - - return Base.nextTick( me.__tick ); - } - - me.runing = false; - - if (this._promise && this._promise.file) { - this._promise.file.setStatus( Status.INTERRUPT ); - } - - interrupt && $.each( me.pool, function( _, v ) { - v.transport && v.transport.abort(); - v.file.setStatus( Status.INTERRUPT ); - }); - - me.owner.trigger('stopUpload'); - }, - - /** - * @method cancelFile - * @grammar cancelFile( file ) => undefined - * @grammar cancelFile( id ) => undefined - * @param {File|id} file File对象或这File对象的id - * @description 标记文件状态为已取消, 同时将中断文件传输。 - * @for Uploader - * @example - * - * $li.on('click', '.remove-this', function() { - * uploader.cancelFile( file ); - * }) - */ - cancelFile: function( file ) { - file = file.id ? file : this.request( 'get-file', file ); - - // 如果正在上传。 - file.blocks && $.each( file.blocks, function( _, v ) { - var _tr = v.transport; - - if ( _tr ) { - _tr.abort(); - _tr.destroy(); - delete v.transport; - } - }); - - file.setStatus( Status.CANCELLED ); - this.owner.trigger( 'fileDequeued', file ); - }, - - /** - * 判断`Uplaode`r是否正在上传中。 - * @grammar isInProgress() => Boolean - * @method isInProgress - * @for Uploader - */ - isInProgress: function() { - return !!this.progress; - }, - - _getStats: function() { - return this.request('get-stats'); - }, - - /** - * 掉过一个文件上传,直接标记指定文件为已上传状态。 - * @grammar skipFile( file ) => undefined - * @method skipFile - * @for Uploader - */ - skipFile: function( file, status ) { - file = file.id ? file : this.request( 'get-file', file ); - - file.setStatus( status || Status.COMPLETE ); - file.skipped = true; - - // 如果正在上传。 - file.blocks && $.each( file.blocks, function( _, v ) { - var _tr = v.transport; - - if ( _tr ) { - _tr.abort(); - _tr.destroy(); - delete v.transport; - } - }); - - this.owner.trigger( 'uploadSkip', file ); - }, - - /** - * @event uploadFinished - * @description 当所有文件上传结束时触发。 - * @for Uploader - */ - _tick: function() { - var me = this, - opts = me.options, - fn, val; - - // 上一个promise还没有结束,则等待完成后再执行。 - if ( me._promise ) { - return me._promise.always( me.__tick ); - } - - // 还有位置,且还有文件要处理的话。 - if ( me.pool.length < opts.threads && (val = me._nextBlock()) ) { - me._trigged = false; - - fn = function( val ) { - me._promise = null; - - // 有可能是reject过来的,所以要检测val的类型。 - val && val.file && me._startSend( val ); - Base.nextTick( me.__tick ); - }; - - me._promise = isPromise( val ) ? val.always( fn ) : fn( val ); - - // 没有要上传的了,且没有正在传输的了。 - } else if ( !me.remaning && !me._getStats().numOfQueue && - !me._getStats().numofInterrupt ) { - me.runing = false; - - me._trigged || Base.nextTick(function() { - me.owner.trigger('uploadFinished'); - }); - me._trigged = true; - } - }, - - _putback: function(block) { - var idx; - - block.cuted.unshift(block); - idx = this.stack.indexOf(block.cuted); - - if (!~idx) { - this.stack.unshift(block.cuted); - } - }, - - _getStack: function() { - var i = 0, - act; - - while ( (act = this.stack[ i++ ]) ) { - if ( act.has() && act.file.getStatus() === Status.PROGRESS ) { - return act; - } else if (!act.has() || - act.file.getStatus() !== Status.PROGRESS && - act.file.getStatus() !== Status.INTERRUPT ) { - - // 把已经处理完了的,或者,状态为非 progress(上传中)、 - // interupt(暂停中) 的移除。 - this.stack.splice( --i, 1 ); - } - } - - return null; - }, - - _nextBlock: function() { - var me = this, - opts = me.options, - act, next, done, preparing; - - // 如果当前文件还有没有需要传输的,则直接返回剩下的。 - if ( (act = this._getStack()) ) { - - // 是否提前准备下一个文件 - if ( opts.prepareNextFile && !me.pending.length ) { - me._prepareNextFile(); - } - - return act.shift(); - - // 否则,如果正在运行,则准备下一个文件,并等待完成后返回下个分片。 - } else if ( me.runing ) { - - // 如果缓存中有,则直接在缓存中取,没有则去queue中取。 - if ( !me.pending.length && me._getStats().numOfQueue ) { - me._prepareNextFile(); - } - - next = me.pending.shift(); - done = function( file ) { - if ( !file ) { - return null; - } - - act = CuteFile( file, opts.chunked ? opts.chunkSize : 0 ); - me.stack.push(act); - return act.shift(); - }; - - // 文件可能还在prepare中,也有可能已经完全准备好了。 - if ( isPromise( next) ) { - preparing = next.file; - next = next[ next.pipe ? 'pipe' : 'then' ]( done ); - next.file = preparing; - return next; - } - - return done( next ); - } - }, - - - /** - * @event uploadStart - * @param {File} file File对象 - * @description 某个文件开始上传前触发,一个文件只会触发一次。 - * @for Uploader - */ - _prepareNextFile: function() { - var me = this, - file = me.request('fetch-file'), - pending = me.pending, - promise; - - if ( file ) { - promise = me.request( 'before-send-file', file, function() { - - // 有可能文件被skip掉了。文件被skip掉后,状态坑定不是Queued. - if ( file.getStatus() === Status.PROGRESS || - file.getStatus() === Status.INTERRUPT ) { - return file; - } - - return me._finishFile( file ); - }); - - me.owner.trigger( 'uploadStart', file ); - file.setStatus( Status.PROGRESS ); - - promise.file = file; - - // 如果还在pending中,则替换成文件本身。 - promise.done(function() { - var idx = $.inArray( promise, pending ); - - ~idx && pending.splice( idx, 1, file ); - }); - - // befeore-send-file的钩子就有错误发生。 - promise.fail(function( reason ) { - file.setStatus( Status.ERROR, reason ); - me.owner.trigger( 'uploadError', file, reason ); - me.owner.trigger( 'uploadComplete', file ); - }); - - pending.push( promise ); - } - }, - - // 让出位置了,可以让其他分片开始上传 - _popBlock: function( block ) { - var idx = $.inArray( block, this.pool ); - - this.pool.splice( idx, 1 ); - block.file.remaning--; - this.remaning--; - }, - - // 开始上传,可以被掉过。如果promise被reject了,则表示跳过此分片。 - _startSend: function( block ) { - var me = this, - file = block.file, - promise; - - // 有可能在 before-send-file 的 promise 期间改变了文件状态。 - // 如:暂停,取消 - // 我们不能中断 promise, 但是可以在 promise 完后,不做上传操作。 - if ( file.getStatus() !== Status.PROGRESS ) { - - // 如果是中断,则还需要放回去。 - if (file.getStatus() === Status.INTERRUPT) { - me._putback(block); - } - - return; - } - - me.pool.push( block ); - me.remaning++; - - // 如果没有分片,则直接使用原始的。 - // 不会丢失content-type信息。 - block.blob = block.chunks === 1 ? file.source : - file.source.slice( block.start, block.end ); - - // hook, 每个分片发送之前可能要做些异步的事情。 - promise = me.request( 'before-send', block, function() { - - // 有可能文件已经上传出错了,所以不需要再传输了。 - if ( file.getStatus() === Status.PROGRESS ) { - me._doSend( block ); - } else { - me._popBlock( block ); - Base.nextTick( me.__tick ); - } - }); - - // 如果为fail了,则跳过此分片。 - promise.fail(function() { - if ( file.remaning === 1 ) { - me._finishFile( file ).always(function() { - block.percentage = 1; - me._popBlock( block ); - me.owner.trigger( 'uploadComplete', file ); - Base.nextTick( me.__tick ); - }); - } else { - block.percentage = 1; - me.updateFileProgress( file ); - me._popBlock( block ); - Base.nextTick( me.__tick ); - } - }); - }, - - - /** - * @event uploadBeforeSend - * @param {Object} object - * @param {Object} data 默认的上传参数,可以扩展此对象来控制上传参数。 - * @param {Object} headers 可以扩展此对象来控制上传头部。 - * @description 当某个文件的分块在发送前触发,主要用来询问是否要添加附带参数,大文件在开起分片上传的前提下此事件可能会触发多次。 - * @for Uploader - */ - - /** - * @event uploadAccept - * @param {Object} object - * @param {Object} ret 服务端的返回数据,json格式,如果服务端不是json格式,从ret._raw中取数据,自行解析。 - * @description 当某个文件上传到服务端响应后,会派送此事件来询问服务端响应是否有效。如果此事件handler返回值为`false`, 则此文件将派送`server`类型的`uploadError`事件。 - * @for Uploader - */ - - /** - * @event uploadProgress - * @param {File} file File对象 - * @param {Number} percentage 上传进度 - * @description 上传过程中触发,携带上传进度。 - * @for Uploader - */ - - - /** - * @event uploadError - * @param {File} file File对象 - * @param {String} reason 出错的code - * @description 当文件上传出错时触发。 - * @for Uploader - */ - - /** - * @event uploadSuccess - * @param {File} file File对象 - * @param {Object} response 服务端返回的数据 - * @description 当文件上传成功时触发。 - * @for Uploader - */ - - /** - * @event uploadComplete - * @param {File} [file] File对象 - * @description 不管成功或者失败,文件上传完成时触发。 - * @for Uploader - */ - - // 做上传操作。 - _doSend: function( block ) { - var me = this, - owner = me.owner, - opts = me.options, - file = block.file, - tr = new Transport( opts ), - data = $.extend({}, opts.formData ), - headers = $.extend({}, opts.headers ), - requestAccept, ret; - - block.transport = tr; - - tr.on( 'destroy', function() { - delete block.transport; - me._popBlock( block ); - Base.nextTick( me.__tick ); - }); - - // 广播上传进度。以文件为单位。 - tr.on( 'progress', function( percentage ) { - block.percentage = percentage; - me.updateFileProgress( file ); - }); - - // 用来询问,是否返回的结果是有错误的。 - requestAccept = function( reject ) { - var fn; - - ret = tr.getResponseAsJson() || {}; - ret._raw = tr.getResponse(); - fn = function( value ) { - reject = value; - }; - - // 服务端响应了,不代表成功了,询问是否响应正确。 - if ( !owner.trigger( 'uploadAccept', block, ret, fn ) ) { - reject = reject || 'server'; - } - - return reject; - }; - - // 尝试重试,然后广播文件上传出错。 - tr.on( 'error', function( type, flag ) { - block.retried = block.retried || 0; - - // 自动重试 - if ( block.chunks > 1 && ~'http,abort'.indexOf( type ) && - block.retried < opts.chunkRetry ) { - - block.retried++; - tr.send(); - - } else { - - // http status 500 ~ 600 - if ( !flag && type === 'server' ) { - type = requestAccept( type ); - } - - file.setStatus( Status.ERROR, type ); - owner.trigger( 'uploadError', file, type ); - owner.trigger( 'uploadComplete', file ); - } - }); - - // 上传成功 - tr.on( 'load', function() { - var reason; - - // 如果非预期,转向上传出错。 - if ( (reason = requestAccept()) ) { - tr.trigger( 'error', reason, true ); - return; - } - - // 全部上传完成。 - if ( file.remaning === 1 ) { - me._finishFile( file, ret ); - } else { - tr.destroy(); - } - }); - - // 配置默认的上传字段。 - data = $.extend( data, { - id: file.id, - name: file.name, - type: file.type, - lastModifiedDate: file.lastModifiedDate, - size: file.size - }); - - block.chunks > 1 && $.extend( data, { - chunks: block.chunks, - chunk: block.chunk - }); - - // 在发送之间可以添加字段什么的。。。 - // 如果默认的字段不够使用,可以通过监听此事件来扩展 - owner.trigger( 'uploadBeforeSend', block, data, headers ); - - // 开始发送。 - tr.appendBlob( opts.fileVal, block.blob, file.name ); - tr.append( data ); - tr.setRequestHeader( headers ); - tr.send(); - }, - - // 完成上传。 - _finishFile: function( file, ret, hds ) { - var owner = this.owner; - - return owner - .request( 'after-send-file', arguments, function() { - file.setStatus( Status.COMPLETE ); - owner.trigger( 'uploadSuccess', file, ret, hds ); - }) - .fail(function( reason ) { - - // 如果外部已经标记为invalid什么的,不再改状态。 - if ( file.getStatus() === Status.PROGRESS ) { - file.setStatus( Status.ERROR, reason ); - } - - owner.trigger( 'uploadError', file, reason ); - }) - .always(function() { - owner.trigger( 'uploadComplete', file ); - }); - }, - - updateFileProgress: function(file) { - var totalPercent = 0, - uploaded = 0; - - if (!file.blocks) { - return; - } - - $.each( file.blocks, function( _, v ) { - uploaded += (v.percentage || 0) * (v.end - v.start); - }); - - totalPercent = uploaded / file.size; - this.owner.trigger( 'uploadProgress', file, totalPercent || 0 ); - } - - }); - }); - /** - * @fileOverview 各种验证,包括文件总大小是否超出、单文件是否超出和文件是否重复。 - */ - - define('widgets/validator',[ - 'base', - 'uploader', - 'file', - 'widgets/widget' - ], function( Base, Uploader, WUFile ) { - - var $ = Base.$, - validators = {}, - api; - - /** - * @event error - * @param {String} type 错误类型。 - * @description 当validate不通过时,会以派送错误事件的形式通知调用者。通过`upload.on('error', handler)`可以捕获到此类错误,目前有以下错误会在特定的情况下派送错来。 - * - * * `Q_EXCEED_NUM_LIMIT` 在设置了`fileNumLimit`且尝试给`uploader`添加的文件数量超出这个值时派送。 - * * `Q_EXCEED_SIZE_LIMIT` 在设置了`Q_EXCEED_SIZE_LIMIT`且尝试给`uploader`添加的文件总大小超出这个值时派送。 - * * `Q_TYPE_DENIED` 当文件类型不满足时触发。。 - * @for Uploader - */ - - // 暴露给外面的api - api = { - - // 添加验证器 - addValidator: function( type, cb ) { - validators[ type ] = cb; - }, - - // 移除验证器 - removeValidator: function( type ) { - delete validators[ type ]; - } - }; - - // 在Uploader初始化的时候启动Validators的初始化 - Uploader.register({ - name: 'validator', - - init: function() { - var me = this; - Base.nextTick(function() { - $.each( validators, function() { - this.call( me.owner ); - }); - }); - } - }); - - /** - * @property {int} [fileNumLimit=undefined] - * @namespace options - * @for Uploader - * @description 验证文件总数量, 超出则不允许加入队列。 - */ - api.addValidator( 'fileNumLimit', function() { - var uploader = this, - opts = uploader.options, - count = 0, - max = parseInt( opts.fileNumLimit, 10 ), - flag = true; - - if ( !max ) { - return; - } - - uploader.on( 'beforeFileQueued', function( file ) { - - if ( count >= max && flag ) { - flag = false; - this.trigger( 'error', 'Q_EXCEED_NUM_LIMIT', max, file ); - setTimeout(function() { - flag = true; - }, 1 ); - } - - return count >= max ? false : true; - }); - - uploader.on( 'fileQueued', function() { - count++; - }); - - uploader.on( 'fileDequeued', function() { - count--; - }); - - uploader.on( 'reset', function() { - count = 0; - }); - }); - - - /** - * @property {int} [fileSizeLimit=undefined] - * @namespace options - * @for Uploader - * @description 验证文件总大小是否超出限制, 超出则不允许加入队列。 - */ - api.addValidator( 'fileSizeLimit', function() { - var uploader = this, - opts = uploader.options, - count = 0, - max = parseInt( opts.fileSizeLimit, 10 ), - flag = true; - - if ( !max ) { - return; - } - - uploader.on( 'beforeFileQueued', function( file ) { - var invalid = count + file.size > max; - - if ( invalid && flag ) { - flag = false; - this.trigger( 'error', 'Q_EXCEED_SIZE_LIMIT', max, file ); - setTimeout(function() { - flag = true; - }, 1 ); - } - - return invalid ? false : true; - }); - - uploader.on( 'fileQueued', function( file ) { - count += file.size; - }); - - uploader.on( 'fileDequeued', function( file ) { - count -= file.size; - }); - - uploader.on( 'reset', function() { - count = 0; - }); - }); - - /** - * @property {int} [fileSingleSizeLimit=undefined] - * @namespace options - * @for Uploader - * @description 验证单个文件大小是否超出限制, 超出则不允许加入队列。 - */ - api.addValidator( 'fileSingleSizeLimit', function() { - var uploader = this, - opts = uploader.options, - max = opts.fileSingleSizeLimit; - - if ( !max ) { - return; - } - - uploader.on( 'beforeFileQueued', function( file ) { - - if ( file.size > max ) { - file.setStatus( WUFile.Status.INVALID, 'exceed_size' ); - this.trigger( 'error', 'F_EXCEED_SIZE', max, file ); - return false; - } - - }); - - }); - - /** - * @property {Boolean} [duplicate=undefined] - * @namespace options - * @for Uploader - * @description 去重, 根据文件名字、文件大小和最后修改时间来生成hash Key. - */ - api.addValidator( 'duplicate', function() { - var uploader = this, - opts = uploader.options, - mapping = {}; - - if ( opts.duplicate ) { - return; - } - - function hashString( str ) { - var hash = 0, - i = 0, - len = str.length, - _char; - - for ( ; i < len; i++ ) { - _char = str.charCodeAt( i ); - hash = _char + (hash << 6) + (hash << 16) - hash; - } - - return hash; - } - - uploader.on( 'beforeFileQueued', function( file ) { - var hash = file.__hash || (file.__hash = hashString( file.name + - file.size + file.lastModifiedDate )); - - // 已经重复了 - if ( mapping[ hash ] ) { - this.trigger( 'error', 'F_DUPLICATE', file ); - return false; - } - }); - - uploader.on( 'fileQueued', function( file ) { - var hash = file.__hash; - - hash && (mapping[ hash ] = true); - }); - - uploader.on( 'fileDequeued', function( file ) { - var hash = file.__hash; - - hash && (delete mapping[ hash ]); - }); - - uploader.on( 'reset', function() { - mapping = {}; - }); - }); - - return api; - }); - - /** - * @fileOverview Md5 - */ - define('lib/md5',[ - 'runtime/client', - 'mediator' - ], function( RuntimeClient, Mediator ) { - - function Md5() { - RuntimeClient.call( this, 'Md5' ); - } - - // 让 Md5 具备事件功能。 - Mediator.installTo( Md5.prototype ); - - Md5.prototype.loadFromBlob = function( blob ) { - var me = this; - - if ( me.getRuid() ) { - me.disconnectRuntime(); - } - - // 连接到blob归属的同一个runtime. - me.connectRuntime( blob.ruid, function() { - me.exec('init'); - me.exec( 'loadFromBlob', blob ); - }); - }; - - Md5.prototype.getResult = function() { - return this.exec('getResult'); - }; - - return Md5; - }); - /** - * @fileOverview 图片操作, 负责预览图片和上传前压缩图片 - */ - define('widgets/md5',[ - 'base', - 'uploader', - 'lib/md5', - 'lib/blob', - 'widgets/widget' - ], function( Base, Uploader, Md5, Blob ) { - - return Uploader.register({ - name: 'md5', - - - /** - * 计算文件 md5 值,返回一个 promise 对象,可以监听 progress 进度。 - * - * - * @method md5File - * @grammar md5File( file[, start[, end]] ) => promise - * @for Uploader - * @example - * - * uploader.on( 'fileQueued', function( file ) { - * var $li = ...; - * - * uploader.md5File( file ) - * - * // 及时显示进度 - * .progress(function(percentage) { - * console.log('Percentage:', percentage); - * }) - * - * // 完成 - * .then(function(val) { - * console.log('md5 result:', val); - * }); - * - * }); - */ - md5File: function( file, start, end ) { - var md5 = new Md5(), - deferred = Base.Deferred(), - blob = (file instanceof Blob) ? file : - this.request( 'get-file', file ).source; - - md5.on( 'progress load', function( e ) { - e = e || {}; - deferred.notify( e.total ? e.loaded / e.total : 1 ); - }); - - md5.on( 'complete', function() { - deferred.resolve( md5.getResult() ); - }); - - md5.on( 'error', function( reason ) { - deferred.reject( reason ); - }); - - if ( arguments.length > 1 ) { - start = start || 0; - end = end || 0; - start < 0 && (start = blob.size + start); - end < 0 && (end = blob.size + end); - end = Math.min( end, blob.size ); - blob = blob.slice( start, end ); - } - - md5.loadFromBlob( blob ); - - return deferred.promise(); - } - }); - }); - /** - * @fileOverview Runtime管理器,负责Runtime的选择, 连接 - */ - define('runtime/compbase',[],function() { - - function CompBase( owner, runtime ) { - - this.owner = owner; - this.options = owner.options; - - this.getRuntime = function() { - return runtime; - }; - - this.getRuid = function() { - return runtime.uid; - }; - - this.trigger = function() { - return owner.trigger.apply( owner, arguments ); - }; - } - - return CompBase; - }); - /** - * @fileOverview Html5Runtime - */ - define('runtime/html5/runtime',[ - 'base', - 'runtime/runtime', - 'runtime/compbase' - ], function( Base, Runtime, CompBase ) { - - var type = 'html5', - components = {}; - - function Html5Runtime() { - var pool = {}, - me = this, - destroy = this.destroy; - - Runtime.apply( me, arguments ); - me.type = type; - - - // 这个方法的调用者,实际上是RuntimeClient - me.exec = function( comp, fn/*, args...*/) { - var client = this, - uid = client.uid, - args = Base.slice( arguments, 2 ), - instance; - - if ( components[ comp ] ) { - instance = pool[ uid ] = pool[ uid ] || - new components[ comp ]( client, me ); - - if ( instance[ fn ] ) { - return instance[ fn ].apply( instance, args ); - } - } - }; - - me.destroy = function() { - // @todo 删除池子中的所有实例 - return destroy && destroy.apply( this, arguments ); - }; - } - - Base.inherits( Runtime, { - constructor: Html5Runtime, - - // 不需要连接其他程序,直接执行callback - init: function() { - var me = this; - setTimeout(function() { - me.trigger('ready'); - }, 1 ); - } - - }); - - // 注册Components - Html5Runtime.register = function( name, component ) { - var klass = components[ name ] = Base.inherits( CompBase, component ); - return klass; - }; - - // 注册html5运行时。 - // 只有在支持的前提下注册。 - if ( window.Blob && window.FileReader && window.DataView ) { - Runtime.addRuntime( type, Html5Runtime ); - } - - return Html5Runtime; - }); - /** - * @fileOverview Blob Html实现 - */ - define('runtime/html5/blob',[ - 'runtime/html5/runtime', - 'lib/blob' - ], function( Html5Runtime, Blob ) { - - return Html5Runtime.register( 'Blob', { - slice: function( start, end ) { - var blob = this.owner.source, - slice = blob.slice || blob.webkitSlice || blob.mozSlice; - - blob = slice.call( blob, start, end ); - - return new Blob( this.getRuid(), blob ); - } - }); - }); - /** - * @fileOverview FilePaste - */ - define('runtime/html5/dnd',[ - 'base', - 'runtime/html5/runtime', - 'lib/file' - ], function( Base, Html5Runtime, File ) { - - var $ = Base.$, - prefix = 'webuploader-dnd-'; - - return Html5Runtime.register( 'DragAndDrop', { - init: function() { - var elem = this.elem = this.options.container; - - this.dragEnterHandler = Base.bindFn( this._dragEnterHandler, this ); - this.dragOverHandler = Base.bindFn( this._dragOverHandler, this ); - this.dragLeaveHandler = Base.bindFn( this._dragLeaveHandler, this ); - this.dropHandler = Base.bindFn( this._dropHandler, this ); - this.dndOver = false; - - elem.on( 'dragenter', this.dragEnterHandler ); - elem.on( 'dragover', this.dragOverHandler ); - elem.on( 'dragleave', this.dragLeaveHandler ); - elem.on( 'drop', this.dropHandler ); - - if ( this.options.disableGlobalDnd ) { - $( document ).on( 'dragover', this.dragOverHandler ); - $( document ).on( 'drop', this.dropHandler ); - } - }, - - _dragEnterHandler: function( e ) { - var me = this, - denied = me._denied || false, - items; - - e = e.originalEvent || e; - - if ( !me.dndOver ) { - me.dndOver = true; - - // 注意只有 chrome 支持。 - items = e.dataTransfer.items; - - if ( items && items.length ) { - me._denied = denied = !me.trigger( 'accept', items ); - } - - me.elem.addClass( prefix + 'over' ); - me.elem[ denied ? 'addClass' : - 'removeClass' ]( prefix + 'denied' ); - } - - e.dataTransfer.dropEffect = denied ? 'none' : 'copy'; - - return false; - }, - - _dragOverHandler: function( e ) { - // 只处理框内的。 - var parentElem = this.elem.parent().get( 0 ); - if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) { - return false; - } - - clearTimeout( this._leaveTimer ); - this._dragEnterHandler.call( this, e ); - - return false; - }, - - _dragLeaveHandler: function() { - var me = this, - handler; - - handler = function() { - me.dndOver = false; - me.elem.removeClass( prefix + 'over ' + prefix + 'denied' ); - }; - - clearTimeout( me._leaveTimer ); - me._leaveTimer = setTimeout( handler, 100 ); - return false; - }, - - _dropHandler: function( e ) { - var me = this, - ruid = me.getRuid(), - parentElem = me.elem.parent().get( 0 ), - dataTransfer, data; - - // 只处理框内的。 - if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) { - return false; - } - - e = e.originalEvent || e; - dataTransfer = e.dataTransfer; - - // 如果是页面内拖拽,还不能处理,不阻止事件。 - // 此处 ie11 下会报参数错误, - try { - data = dataTransfer.getData('text/html'); - } catch( err ) { - } - - if ( data ) { - return; - } - - me._getTansferFiles( dataTransfer, function( results ) { - me.trigger( 'drop', $.map( results, function( file ) { - return new File( ruid, file ); - }) ); - }); - - me.dndOver = false; - me.elem.removeClass( prefix + 'over' ); - return false; - }, - - // 如果传入 callback 则去查看文件夹,否则只管当前文件夹。 - _getTansferFiles: function( dataTransfer, callback ) { - var results = [], - promises = [], - items, files, file, item, i, len, canAccessFolder; - - items = dataTransfer.items; - files = dataTransfer.files; - - canAccessFolder = !!(items && items[ 0 ].webkitGetAsEntry); - - for ( i = 0, len = files.length; i < len; i++ ) { - file = files[ i ]; - item = items && items[ i ]; - - if ( canAccessFolder && item.webkitGetAsEntry().isDirectory ) { - - promises.push( this._traverseDirectoryTree( - item.webkitGetAsEntry(), results ) ); - } else { - results.push( file ); - } - } - - Base.when.apply( Base, promises ).done(function() { - - if ( !results.length ) { - return; - } - - callback( results ); - }); - }, - - _traverseDirectoryTree: function( entry, results ) { - var deferred = Base.Deferred(), - me = this; - - if ( entry.isFile ) { - entry.file(function( file ) { - results.push( file ); - deferred.resolve(); - }); - } else if ( entry.isDirectory ) { - entry.createReader().readEntries(function( entries ) { - var len = entries.length, - promises = [], - arr = [], // 为了保证顺序。 - i; - - for ( i = 0; i < len; i++ ) { - promises.push( me._traverseDirectoryTree( - entries[ i ], arr ) ); - } - - Base.when.apply( Base, promises ).then(function() { - results.push.apply( results, arr ); - deferred.resolve(); - }, deferred.reject ); - }); - } - - return deferred.promise(); - }, - - destroy: function() { - var elem = this.elem; - - // 还没 init 就调用 destroy - if (!elem) { - return; - } - - elem.off( 'dragenter', this.dragEnterHandler ); - elem.off( 'dragover', this.dragOverHandler ); - elem.off( 'dragleave', this.dragLeaveHandler ); - elem.off( 'drop', this.dropHandler ); - - if ( this.options.disableGlobalDnd ) { - $( document ).off( 'dragover', this.dragOverHandler ); - $( document ).off( 'drop', this.dropHandler ); - } - } - }); - }); - - /** - * @fileOverview FilePaste - */ - define('runtime/html5/filepaste',[ - 'base', - 'runtime/html5/runtime', - 'lib/file' - ], function( Base, Html5Runtime, File ) { - - return Html5Runtime.register( 'FilePaste', { - init: function() { - var opts = this.options, - elem = this.elem = opts.container, - accept = '.*', - arr, i, len, item; - - // accetp的mimeTypes中生成匹配正则。 - if ( opts.accept ) { - arr = []; - - for ( i = 0, len = opts.accept.length; i < len; i++ ) { - item = opts.accept[ i ].mimeTypes; - item && arr.push( item ); - } - - if ( arr.length ) { - accept = arr.join(','); - accept = accept.replace( /,/g, '|' ).replace( /\*/g, '.*' ); - } - } - this.accept = accept = new RegExp( accept, 'i' ); - this.hander = Base.bindFn( this._pasteHander, this ); - elem.on( 'paste', this.hander ); - }, - - _pasteHander: function( e ) { - var allowed = [], - ruid = this.getRuid(), - items, item, blob, i, len; - - e = e.originalEvent || e; - items = e.clipboardData.items; - - for ( i = 0, len = items.length; i < len; i++ ) { - item = items[ i ]; - - if ( item.kind !== 'file' || !(blob = item.getAsFile()) ) { - continue; - } - - allowed.push( new File( ruid, blob ) ); - } - - if ( allowed.length ) { - // 不阻止非文件粘贴(文字粘贴)的事件冒泡 - e.preventDefault(); - e.stopPropagation(); - this.trigger( 'paste', allowed ); - } - }, - - destroy: function() { - this.elem.off( 'paste', this.hander ); - } - }); - }); - - /** - * @fileOverview FilePicker - */ - define('runtime/html5/filepicker',[ - 'base', - 'runtime/html5/runtime' - ], function( Base, Html5Runtime ) { - - var $ = Base.$; - - return Html5Runtime.register( 'FilePicker', { - init: function() { - var container = this.getRuntime().getContainer(), - me = this, - owner = me.owner, - opts = me.options, - label = this.label = $( document.createElement('label') ), - input = this.input = $( document.createElement('input') ), - arr, i, len, mouseHandler; - - input.attr( 'type', 'file' ); - input.attr( 'name', opts.name ); - input.addClass('webuploader-element-invisible'); - - label.on( 'click', function() { - input.trigger('click'); - }); - - label.css({ - opacity: 0, - width: '100%', - height: '100%', - display: 'block', - cursor: 'pointer', - background: '#ffffff' - }); - - if ( opts.multiple ) { - input.attr( 'multiple', 'multiple' ); - } - - // @todo Firefox不支持单独指定后缀 - if ( opts.accept && opts.accept.length > 0 ) { - arr = []; - - for ( i = 0, len = opts.accept.length; i < len; i++ ) { - arr.push( opts.accept[ i ].mimeTypes ); - } - - input.attr( 'accept', arr.join(',') ); - } - - container.append( input ); - container.append( label ); - - mouseHandler = function( e ) { - owner.trigger( e.type ); - }; - - input.on( 'change', function( e ) { - var fn = arguments.callee, - clone; - - me.files = e.target.files; - - // reset input - clone = this.cloneNode( true ); - clone.value = null; - this.parentNode.replaceChild( clone, this ); - - input.off(); - input = $( clone ).on( 'change', fn ) - .on( 'mouseenter mouseleave', mouseHandler ); - - owner.trigger('change'); - }); - - label.on( 'mouseenter mouseleave', mouseHandler ); - - }, - - - getFiles: function() { - return this.files; - }, - - destroy: function() { - this.input.off(); - this.label.off(); - } - }); - }); - /** - * Terms: - * - * Uint8Array, FileReader, BlobBuilder, atob, ArrayBuffer - * @fileOverview Image控件 - */ - define('runtime/html5/util',[ - 'base' - ], function( Base ) { - - var urlAPI = window.createObjectURL && window || - window.URL && URL.revokeObjectURL && URL || - window.webkitURL, - createObjectURL = Base.noop, - revokeObjectURL = createObjectURL; - - if ( urlAPI ) { - - // 更安全的方式调用,比如android里面就能把context改成其他的对象。 - createObjectURL = function() { - return urlAPI.createObjectURL.apply( urlAPI, arguments ); - }; - - revokeObjectURL = function() { - return urlAPI.revokeObjectURL.apply( urlAPI, arguments ); - }; - } - - return { - createObjectURL: createObjectURL, - revokeObjectURL: revokeObjectURL, - - dataURL2Blob: function( dataURI ) { - var byteStr, intArray, ab, i, mimetype, parts; - - parts = dataURI.split(','); - - if ( ~parts[ 0 ].indexOf('base64') ) { - byteStr = atob( parts[ 1 ] ); - } else { - byteStr = decodeURIComponent( parts[ 1 ] ); - } - - ab = new ArrayBuffer( byteStr.length ); - intArray = new Uint8Array( ab ); - - for ( i = 0; i < byteStr.length; i++ ) { - intArray[ i ] = byteStr.charCodeAt( i ); - } - - mimetype = parts[ 0 ].split(':')[ 1 ].split(';')[ 0 ]; - - return this.arrayBufferToBlob( ab, mimetype ); - }, - - dataURL2ArrayBuffer: function( dataURI ) { - var byteStr, intArray, i, parts; - - parts = dataURI.split(','); - - if ( ~parts[ 0 ].indexOf('base64') ) { - byteStr = atob( parts[ 1 ] ); - } else { - byteStr = decodeURIComponent( parts[ 1 ] ); - } - - intArray = new Uint8Array( byteStr.length ); - - for ( i = 0; i < byteStr.length; i++ ) { - intArray[ i ] = byteStr.charCodeAt( i ); - } - - return intArray.buffer; - }, - - arrayBufferToBlob: function( buffer, type ) { - var builder = window.BlobBuilder || window.WebKitBlobBuilder, - bb; - - // android不支持直接new Blob, 只能借助blobbuilder. - if ( builder ) { - bb = new builder(); - bb.append( buffer ); - return bb.getBlob( type ); - } - - return new Blob([ buffer ], type ? { type: type } : {} ); - }, - - // 抽出来主要是为了解决android下面canvas.toDataUrl不支持jpeg. - // 你得到的结果是png. - canvasToDataUrl: function( canvas, type, quality ) { - return canvas.toDataURL( type, quality / 100 ); - }, - - // imagemeat会复写这个方法,如果用户选择加载那个文件了的话。 - parseMeta: function( blob, callback ) { - callback( false, {}); - }, - - // imagemeat会复写这个方法,如果用户选择加载那个文件了的话。 - updateImageHead: function( data ) { - return data; - } - }; - }); - /** - * Terms: - * - * Uint8Array, FileReader, BlobBuilder, atob, ArrayBuffer - * @fileOverview Image控件 - */ - define('runtime/html5/imagemeta',[ - 'runtime/html5/util' - ], function( Util ) { - - var api; - - api = { - parsers: { - 0xffe1: [] - }, - - maxMetaDataSize: 262144, - - parse: function( blob, cb ) { - var me = this, - fr = new FileReader(); - - fr.onload = function() { - cb( false, me._parse( this.result ) ); - fr = fr.onload = fr.onerror = null; - }; - - fr.onerror = function( e ) { - cb( e.message ); - fr = fr.onload = fr.onerror = null; - }; - - blob = blob.slice( 0, me.maxMetaDataSize ); - fr.readAsArrayBuffer( blob.getSource() ); - }, - - _parse: function( buffer, noParse ) { - if ( buffer.byteLength < 6 ) { - return; - } - - var dataview = new DataView( buffer ), - offset = 2, - maxOffset = dataview.byteLength - 4, - headLength = offset, - ret = {}, - markerBytes, markerLength, parsers, i; - - if ( dataview.getUint16( 0 ) === 0xffd8 ) { - - while ( offset < maxOffset ) { - markerBytes = dataview.getUint16( offset ); - - if ( markerBytes >= 0xffe0 && markerBytes <= 0xffef || - markerBytes === 0xfffe ) { - - markerLength = dataview.getUint16( offset + 2 ) + 2; - - if ( offset + markerLength > dataview.byteLength ) { - break; - } - - parsers = api.parsers[ markerBytes ]; - - if ( !noParse && parsers ) { - for ( i = 0; i < parsers.length; i += 1 ) { - parsers[ i ].call( api, dataview, offset, - markerLength, ret ); - } - } - - offset += markerLength; - headLength = offset; - } else { - break; - } - } - - if ( headLength > 6 ) { - if ( buffer.slice ) { - ret.imageHead = buffer.slice( 2, headLength ); - } else { - // Workaround for IE10, which does not yet - // support ArrayBuffer.slice: - ret.imageHead = new Uint8Array( buffer ) - .subarray( 2, headLength ); - } - } - } - - return ret; - }, - - updateImageHead: function( buffer, head ) { - var data = this._parse( buffer, true ), - buf1, buf2, bodyoffset; - - - bodyoffset = 2; - if ( data.imageHead ) { - bodyoffset = 2 + data.imageHead.byteLength; - } - - if ( buffer.slice ) { - buf2 = buffer.slice( bodyoffset ); - } else { - buf2 = new Uint8Array( buffer ).subarray( bodyoffset ); - } - - buf1 = new Uint8Array( head.byteLength + 2 + buf2.byteLength ); - - buf1[ 0 ] = 0xFF; - buf1[ 1 ] = 0xD8; - buf1.set( new Uint8Array( head ), 2 ); - buf1.set( new Uint8Array( buf2 ), head.byteLength + 2 ); - - return buf1.buffer; - } - }; - - Util.parseMeta = function() { - return api.parse.apply( api, arguments ); - }; - - Util.updateImageHead = function() { - return api.updateImageHead.apply( api, arguments ); - }; - - return api; - }); - /** - * 代码来自于:https://github.com/blueimp/JavaScript-Load-Image - * 暂时项目中只用了orientation. - * - * 去除了 Exif Sub IFD Pointer, GPS Info IFD Pointer, Exif Thumbnail. - * @fileOverview EXIF解析 - */ - - // Sample - // ==================================== - // Make : Apple - // Model : iPhone 4S - // Orientation : 1 - // XResolution : 72 [72/1] - // YResolution : 72 [72/1] - // ResolutionUnit : 2 - // Software : QuickTime 7.7.1 - // DateTime : 2013:09:01 22:53:55 - // ExifIFDPointer : 190 - // ExposureTime : 0.058823529411764705 [1/17] - // FNumber : 2.4 [12/5] - // ExposureProgram : Normal program - // ISOSpeedRatings : 800 - // ExifVersion : 0220 - // DateTimeOriginal : 2013:09:01 22:52:51 - // DateTimeDigitized : 2013:09:01 22:52:51 - // ComponentsConfiguration : YCbCr - // ShutterSpeedValue : 4.058893515764426 - // ApertureValue : 2.5260688216892597 [4845/1918] - // BrightnessValue : -0.3126686601998395 - // MeteringMode : Pattern - // Flash : Flash did not fire, compulsory flash mode - // FocalLength : 4.28 [107/25] - // SubjectArea : [4 values] - // FlashpixVersion : 0100 - // ColorSpace : 1 - // PixelXDimension : 2448 - // PixelYDimension : 3264 - // SensingMethod : One-chip color area sensor - // ExposureMode : 0 - // WhiteBalance : Auto white balance - // FocalLengthIn35mmFilm : 35 - // SceneCaptureType : Standard - define('runtime/html5/imagemeta/exif',[ - 'base', - 'runtime/html5/imagemeta' - ], function( Base, ImageMeta ) { - - var EXIF = {}; - - EXIF.ExifMap = function() { - return this; - }; - - EXIF.ExifMap.prototype.map = { - 'Orientation': 0x0112 - }; - - EXIF.ExifMap.prototype.get = function( id ) { - return this[ id ] || this[ this.map[ id ] ]; - }; - - EXIF.exifTagTypes = { - // byte, 8-bit unsigned int: - 1: { - getValue: function( dataView, dataOffset ) { - return dataView.getUint8( dataOffset ); - }, - size: 1 - }, - - // ascii, 8-bit byte: - 2: { - getValue: function( dataView, dataOffset ) { - return String.fromCharCode( dataView.getUint8( dataOffset ) ); - }, - size: 1, - ascii: true - }, - - // short, 16 bit int: - 3: { - getValue: function( dataView, dataOffset, littleEndian ) { - return dataView.getUint16( dataOffset, littleEndian ); - }, - size: 2 - }, - - // long, 32 bit int: - 4: { - getValue: function( dataView, dataOffset, littleEndian ) { - return dataView.getUint32( dataOffset, littleEndian ); - }, - size: 4 - }, - - // rational = two long values, - // first is numerator, second is denominator: - 5: { - getValue: function( dataView, dataOffset, littleEndian ) { - return dataView.getUint32( dataOffset, littleEndian ) / - dataView.getUint32( dataOffset + 4, littleEndian ); - }, - size: 8 - }, - - // slong, 32 bit signed int: - 9: { - getValue: function( dataView, dataOffset, littleEndian ) { - return dataView.getInt32( dataOffset, littleEndian ); - }, - size: 4 - }, - - // srational, two slongs, first is numerator, second is denominator: - 10: { - getValue: function( dataView, dataOffset, littleEndian ) { - return dataView.getInt32( dataOffset, littleEndian ) / - dataView.getInt32( dataOffset + 4, littleEndian ); - }, - size: 8 - } - }; - - // undefined, 8-bit byte, value depending on field: - EXIF.exifTagTypes[ 7 ] = EXIF.exifTagTypes[ 1 ]; - - EXIF.getExifValue = function( dataView, tiffOffset, offset, type, length, - littleEndian ) { - - var tagType = EXIF.exifTagTypes[ type ], - tagSize, dataOffset, values, i, str, c; - - if ( !tagType ) { - Base.log('Invalid Exif data: Invalid tag type.'); - return; - } - - tagSize = tagType.size * length; - - // Determine if the value is contained in the dataOffset bytes, - // or if the value at the dataOffset is a pointer to the actual data: - dataOffset = tagSize > 4 ? tiffOffset + dataView.getUint32( offset + 8, - littleEndian ) : (offset + 8); - - if ( dataOffset + tagSize > dataView.byteLength ) { - Base.log('Invalid Exif data: Invalid data offset.'); - return; - } - - if ( length === 1 ) { - return tagType.getValue( dataView, dataOffset, littleEndian ); - } - - values = []; - - for ( i = 0; i < length; i += 1 ) { - values[ i ] = tagType.getValue( dataView, - dataOffset + i * tagType.size, littleEndian ); - } - - if ( tagType.ascii ) { - str = ''; - - // Concatenate the chars: - for ( i = 0; i < values.length; i += 1 ) { - c = values[ i ]; - - // Ignore the terminating NULL byte(s): - if ( c === '\u0000' ) { - break; - } - str += c; - } - - return str; - } - return values; - }; - - EXIF.parseExifTag = function( dataView, tiffOffset, offset, littleEndian, - data ) { - - var tag = dataView.getUint16( offset, littleEndian ); - data.exif[ tag ] = EXIF.getExifValue( dataView, tiffOffset, offset, - dataView.getUint16( offset + 2, littleEndian ), // tag type - dataView.getUint32( offset + 4, littleEndian ), // tag length - littleEndian ); - }; - - EXIF.parseExifTags = function( dataView, tiffOffset, dirOffset, - littleEndian, data ) { - - var tagsNumber, dirEndOffset, i; - - if ( dirOffset + 6 > dataView.byteLength ) { - Base.log('Invalid Exif data: Invalid directory offset.'); - return; - } - - tagsNumber = dataView.getUint16( dirOffset, littleEndian ); - dirEndOffset = dirOffset + 2 + 12 * tagsNumber; - - if ( dirEndOffset + 4 > dataView.byteLength ) { - Base.log('Invalid Exif data: Invalid directory size.'); - return; - } - - for ( i = 0; i < tagsNumber; i += 1 ) { - this.parseExifTag( dataView, tiffOffset, - dirOffset + 2 + 12 * i, // tag offset - littleEndian, data ); - } - - // Return the offset to the next directory: - return dataView.getUint32( dirEndOffset, littleEndian ); - }; - - // EXIF.getExifThumbnail = function(dataView, offset, length) { - // var hexData, - // i, - // b; - // if (!length || offset + length > dataView.byteLength) { - // Base.log('Invalid Exif data: Invalid thumbnail data.'); - // return; - // } - // hexData = []; - // for (i = 0; i < length; i += 1) { - // b = dataView.getUint8(offset + i); - // hexData.push((b < 16 ? '0' : '') + b.toString(16)); - // } - // return 'data:image/jpeg,%' + hexData.join('%'); - // }; - - EXIF.parseExifData = function( dataView, offset, length, data ) { - - var tiffOffset = offset + 10, - littleEndian, dirOffset; - - // Check for the ASCII code for "Exif" (0x45786966): - if ( dataView.getUint32( offset + 4 ) !== 0x45786966 ) { - // No Exif data, might be XMP data instead - return; - } - if ( tiffOffset + 8 > dataView.byteLength ) { - Base.log('Invalid Exif data: Invalid segment size.'); - return; - } - - // Check for the two null bytes: - if ( dataView.getUint16( offset + 8 ) !== 0x0000 ) { - Base.log('Invalid Exif data: Missing byte alignment offset.'); - return; - } - - // Check the byte alignment: - switch ( dataView.getUint16( tiffOffset ) ) { - case 0x4949: - littleEndian = true; - break; - - case 0x4D4D: - littleEndian = false; - break; - - default: - Base.log('Invalid Exif data: Invalid byte alignment marker.'); - return; - } - - // Check for the TIFF tag marker (0x002A): - if ( dataView.getUint16( tiffOffset + 2, littleEndian ) !== 0x002A ) { - Base.log('Invalid Exif data: Missing TIFF marker.'); - return; - } - - // Retrieve the directory offset bytes, usually 0x00000008 or 8 decimal: - dirOffset = dataView.getUint32( tiffOffset + 4, littleEndian ); - // Create the exif object to store the tags: - data.exif = new EXIF.ExifMap(); - // Parse the tags of the main image directory and retrieve the - // offset to the next directory, usually the thumbnail directory: - dirOffset = EXIF.parseExifTags( dataView, tiffOffset, - tiffOffset + dirOffset, littleEndian, data ); - - // 尝试读取缩略图 - // if ( dirOffset ) { - // thumbnailData = {exif: {}}; - // dirOffset = EXIF.parseExifTags( - // dataView, - // tiffOffset, - // tiffOffset + dirOffset, - // littleEndian, - // thumbnailData - // ); - - // // Check for JPEG Thumbnail offset: - // if (thumbnailData.exif[0x0201]) { - // data.exif.Thumbnail = EXIF.getExifThumbnail( - // dataView, - // tiffOffset + thumbnailData.exif[0x0201], - // thumbnailData.exif[0x0202] // Thumbnail data length - // ); - // } - // } - }; - - ImageMeta.parsers[ 0xffe1 ].push( EXIF.parseExifData ); - return EXIF; - }); - /** - * 这个方式性能不行,但是可以解决android里面的toDataUrl的bug - * android里面toDataUrl('image/jpege')得到的结果却是png. - * - * 所以这里没辙,只能借助这个工具 - * @fileOverview jpeg encoder - */ - define('runtime/html5/jpegencoder',[], function( require, exports, module ) { - - /* - Copyright (c) 2008, Adobe Systems Incorporated - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are - met: - - * Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the name of Adobe Systems Incorporated nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS - IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - /* - JPEG encoder ported to JavaScript and optimized by Andreas Ritter, www.bytestrom.eu, 11/2009 - - Basic GUI blocking jpeg encoder - */ - - function JPEGEncoder(quality) { - var self = this; - var fround = Math.round; - var ffloor = Math.floor; - var YTable = new Array(64); - var UVTable = new Array(64); - var fdtbl_Y = new Array(64); - var fdtbl_UV = new Array(64); - var YDC_HT; - var UVDC_HT; - var YAC_HT; - var UVAC_HT; - - var bitcode = new Array(65535); - var category = new Array(65535); - var outputfDCTQuant = new Array(64); - var DU = new Array(64); - var byteout = []; - var bytenew = 0; - var bytepos = 7; - - var YDU = new Array(64); - var UDU = new Array(64); - var VDU = new Array(64); - var clt = new Array(256); - var RGB_YUV_TABLE = new Array(2048); - var currentQuality; - - var ZigZag = [ - 0, 1, 5, 6,14,15,27,28, - 2, 4, 7,13,16,26,29,42, - 3, 8,12,17,25,30,41,43, - 9,11,18,24,31,40,44,53, - 10,19,23,32,39,45,52,54, - 20,22,33,38,46,51,55,60, - 21,34,37,47,50,56,59,61, - 35,36,48,49,57,58,62,63 - ]; - - var std_dc_luminance_nrcodes = [0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0]; - var std_dc_luminance_values = [0,1,2,3,4,5,6,7,8,9,10,11]; - var std_ac_luminance_nrcodes = [0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d]; - var std_ac_luminance_values = [ - 0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12, - 0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07, - 0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08, - 0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0, - 0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16, - 0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28, - 0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39, - 0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49, - 0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59, - 0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69, - 0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79, - 0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89, - 0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98, - 0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7, - 0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6, - 0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5, - 0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4, - 0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2, - 0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea, - 0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8, - 0xf9,0xfa - ]; - - var std_dc_chrominance_nrcodes = [0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0]; - var std_dc_chrominance_values = [0,1,2,3,4,5,6,7,8,9,10,11]; - var std_ac_chrominance_nrcodes = [0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77]; - var std_ac_chrominance_values = [ - 0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21, - 0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71, - 0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91, - 0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0, - 0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34, - 0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26, - 0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38, - 0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48, - 0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58, - 0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68, - 0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78, - 0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87, - 0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96, - 0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5, - 0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4, - 0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3, - 0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2, - 0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda, - 0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9, - 0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8, - 0xf9,0xfa - ]; - - function initQuantTables(sf){ - var YQT = [ - 16, 11, 10, 16, 24, 40, 51, 61, - 12, 12, 14, 19, 26, 58, 60, 55, - 14, 13, 16, 24, 40, 57, 69, 56, - 14, 17, 22, 29, 51, 87, 80, 62, - 18, 22, 37, 56, 68,109,103, 77, - 24, 35, 55, 64, 81,104,113, 92, - 49, 64, 78, 87,103,121,120,101, - 72, 92, 95, 98,112,100,103, 99 - ]; - - for (var i = 0; i < 64; i++) { - var t = ffloor((YQT[i]*sf+50)/100); - if (t < 1) { - t = 1; - } else if (t > 255) { - t = 255; - } - YTable[ZigZag[i]] = t; - } - var UVQT = [ - 17, 18, 24, 47, 99, 99, 99, 99, - 18, 21, 26, 66, 99, 99, 99, 99, - 24, 26, 56, 99, 99, 99, 99, 99, - 47, 66, 99, 99, 99, 99, 99, 99, - 99, 99, 99, 99, 99, 99, 99, 99, - 99, 99, 99, 99, 99, 99, 99, 99, - 99, 99, 99, 99, 99, 99, 99, 99, - 99, 99, 99, 99, 99, 99, 99, 99 - ]; - for (var j = 0; j < 64; j++) { - var u = ffloor((UVQT[j]*sf+50)/100); - if (u < 1) { - u = 1; - } else if (u > 255) { - u = 255; - } - UVTable[ZigZag[j]] = u; - } - var aasf = [ - 1.0, 1.387039845, 1.306562965, 1.175875602, - 1.0, 0.785694958, 0.541196100, 0.275899379 - ]; - var k = 0; - for (var row = 0; row < 8; row++) - { - for (var col = 0; col < 8; col++) - { - fdtbl_Y[k] = (1.0 / (YTable [ZigZag[k]] * aasf[row] * aasf[col] * 8.0)); - fdtbl_UV[k] = (1.0 / (UVTable[ZigZag[k]] * aasf[row] * aasf[col] * 8.0)); - k++; - } - } - } - - function computeHuffmanTbl(nrcodes, std_table){ - var codevalue = 0; - var pos_in_table = 0; - var HT = new Array(); - for (var k = 1; k <= 16; k++) { - for (var j = 1; j <= nrcodes[k]; j++) { - HT[std_table[pos_in_table]] = []; - HT[std_table[pos_in_table]][0] = codevalue; - HT[std_table[pos_in_table]][1] = k; - pos_in_table++; - codevalue++; - } - codevalue*=2; - } - return HT; - } - - function initHuffmanTbl() - { - YDC_HT = computeHuffmanTbl(std_dc_luminance_nrcodes,std_dc_luminance_values); - UVDC_HT = computeHuffmanTbl(std_dc_chrominance_nrcodes,std_dc_chrominance_values); - YAC_HT = computeHuffmanTbl(std_ac_luminance_nrcodes,std_ac_luminance_values); - UVAC_HT = computeHuffmanTbl(std_ac_chrominance_nrcodes,std_ac_chrominance_values); - } - - function initCategoryNumber() - { - var nrlower = 1; - var nrupper = 2; - for (var cat = 1; cat <= 15; cat++) { - //Positive numbers - for (var nr = nrlower; nr>0] = 38470 * i; - RGB_YUV_TABLE[(i+ 512)>>0] = 7471 * i + 0x8000; - RGB_YUV_TABLE[(i+ 768)>>0] = -11059 * i; - RGB_YUV_TABLE[(i+1024)>>0] = -21709 * i; - RGB_YUV_TABLE[(i+1280)>>0] = 32768 * i + 0x807FFF; - RGB_YUV_TABLE[(i+1536)>>0] = -27439 * i; - RGB_YUV_TABLE[(i+1792)>>0] = - 5329 * i; - } - } - - // IO functions - function writeBits(bs) - { - var value = bs[0]; - var posval = bs[1]-1; - while ( posval >= 0 ) { - if (value & (1 << posval) ) { - bytenew |= (1 << bytepos); - } - posval--; - bytepos--; - if (bytepos < 0) { - if (bytenew == 0xFF) { - writeByte(0xFF); - writeByte(0); - } - else { - writeByte(bytenew); - } - bytepos=7; - bytenew=0; - } - } - } - - function writeByte(value) - { - byteout.push(clt[value]); // write char directly instead of converting later - } - - function writeWord(value) - { - writeByte((value>>8)&0xFF); - writeByte((value )&0xFF); - } - - // DCT & quantization core - function fDCTQuant(data, fdtbl) - { - var d0, d1, d2, d3, d4, d5, d6, d7; - /* Pass 1: process rows. */ - var dataOff=0; - var i; - var I8 = 8; - var I64 = 64; - for (i=0; i 0.0) ? ((fDCTQuant + 0.5)|0) : ((fDCTQuant - 0.5)|0); - //outputfDCTQuant[i] = fround(fDCTQuant); - - } - return outputfDCTQuant; - } - - function writeAPP0() - { - writeWord(0xFFE0); // marker - writeWord(16); // length - writeByte(0x4A); // J - writeByte(0x46); // F - writeByte(0x49); // I - writeByte(0x46); // F - writeByte(0); // = "JFIF",'\0' - writeByte(1); // versionhi - writeByte(1); // versionlo - writeByte(0); // xyunits - writeWord(1); // xdensity - writeWord(1); // ydensity - writeByte(0); // thumbnwidth - writeByte(0); // thumbnheight - } - - function writeSOF0(width, height) - { - writeWord(0xFFC0); // marker - writeWord(17); // length, truecolor YUV JPG - writeByte(8); // precision - writeWord(height); - writeWord(width); - writeByte(3); // nrofcomponents - writeByte(1); // IdY - writeByte(0x11); // HVY - writeByte(0); // QTY - writeByte(2); // IdU - writeByte(0x11); // HVU - writeByte(1); // QTU - writeByte(3); // IdV - writeByte(0x11); // HVV - writeByte(1); // QTV - } - - function writeDQT() - { - writeWord(0xFFDB); // marker - writeWord(132); // length - writeByte(0); - for (var i=0; i<64; i++) { - writeByte(YTable[i]); - } - writeByte(1); - for (var j=0; j<64; j++) { - writeByte(UVTable[j]); - } - } - - function writeDHT() - { - writeWord(0xFFC4); // marker - writeWord(0x01A2); // length - - writeByte(0); // HTYDCinfo - for (var i=0; i<16; i++) { - writeByte(std_dc_luminance_nrcodes[i+1]); - } - for (var j=0; j<=11; j++) { - writeByte(std_dc_luminance_values[j]); - } - - writeByte(0x10); // HTYACinfo - for (var k=0; k<16; k++) { - writeByte(std_ac_luminance_nrcodes[k+1]); - } - for (var l=0; l<=161; l++) { - writeByte(std_ac_luminance_values[l]); - } - - writeByte(1); // HTUDCinfo - for (var m=0; m<16; m++) { - writeByte(std_dc_chrominance_nrcodes[m+1]); - } - for (var n=0; n<=11; n++) { - writeByte(std_dc_chrominance_values[n]); - } - - writeByte(0x11); // HTUACinfo - for (var o=0; o<16; o++) { - writeByte(std_ac_chrominance_nrcodes[o+1]); - } - for (var p=0; p<=161; p++) { - writeByte(std_ac_chrominance_values[p]); - } - } - - function writeSOS() - { - writeWord(0xFFDA); // marker - writeWord(12); // length - writeByte(3); // nrofcomponents - writeByte(1); // IdY - writeByte(0); // HTY - writeByte(2); // IdU - writeByte(0x11); // HTU - writeByte(3); // IdV - writeByte(0x11); // HTV - writeByte(0); // Ss - writeByte(0x3f); // Se - writeByte(0); // Bf - } - - function processDU(CDU, fdtbl, DC, HTDC, HTAC){ - var EOB = HTAC[0x00]; - var M16zeroes = HTAC[0xF0]; - var pos; - var I16 = 16; - var I63 = 63; - var I64 = 64; - var DU_DCT = fDCTQuant(CDU, fdtbl); - //ZigZag reorder - for (var j=0;j0)&&(DU[end0pos]==0); end0pos--) {}; - //end0pos = first element in reverse order !=0 - if ( end0pos == 0) { - writeBits(EOB); - return DC; - } - var i = 1; - var lng; - while ( i <= end0pos ) { - var startpos = i; - for (; (DU[i]==0) && (i<=end0pos); ++i) {} - var nrzeroes = i-startpos; - if ( nrzeroes >= I16 ) { - lng = nrzeroes>>4; - for (var nrmarker=1; nrmarker <= lng; ++nrmarker) - writeBits(M16zeroes); - nrzeroes = nrzeroes&0xF; - } - pos = 32767+DU[i]; - writeBits(HTAC[(nrzeroes<<4)+category[pos]]); - writeBits(bitcode[pos]); - i++; - } - if ( end0pos != I63 ) { - writeBits(EOB); - } - return DC; - } - - function initCharLookupTable(){ - var sfcc = String.fromCharCode; - for(var i=0; i < 256; i++){ ///// ACHTUNG // 255 - clt[i] = sfcc(i); - } - } - - this.encode = function(image,quality) // image data object - { - // var time_start = new Date().getTime(); - - if(quality) setQuality(quality); - - // Initialize bit writer - byteout = new Array(); - bytenew=0; - bytepos=7; - - // Add JPEG headers - writeWord(0xFFD8); // SOI - writeAPP0(); - writeDQT(); - writeSOF0(image.width,image.height); - writeDHT(); - writeSOS(); - - - // Encode 8x8 macroblocks - var DCY=0; - var DCU=0; - var DCV=0; - - bytenew=0; - bytepos=7; - - - this.encode.displayName = "_encode_"; - - var imageData = image.data; - var width = image.width; - var height = image.height; - - var quadWidth = width*4; - var tripleWidth = width*3; - - var x, y = 0; - var r, g, b; - var start,p, col,row,pos; - while(y < height){ - x = 0; - while(x < quadWidth){ - start = quadWidth * y + x; - p = start; - col = -1; - row = 0; - - for(pos=0; pos < 64; pos++){ - row = pos >> 3;// /8 - col = ( pos & 7 ) * 4; // %8 - p = start + ( row * quadWidth ) + col; - - if(y+row >= height){ // padding bottom - p-= (quadWidth*(y+1+row-height)); - } - - if(x+col >= quadWidth){ // padding right - p-= ((x+col) - quadWidth +4) - } - - r = imageData[ p++ ]; - g = imageData[ p++ ]; - b = imageData[ p++ ]; - - - /* // calculate YUV values dynamically - YDU[pos]=((( 0.29900)*r+( 0.58700)*g+( 0.11400)*b))-128; //-0x80 - UDU[pos]=(((-0.16874)*r+(-0.33126)*g+( 0.50000)*b)); - VDU[pos]=((( 0.50000)*r+(-0.41869)*g+(-0.08131)*b)); - */ - - // use lookup table (slightly faster) - YDU[pos] = ((RGB_YUV_TABLE[r] + RGB_YUV_TABLE[(g + 256)>>0] + RGB_YUV_TABLE[(b + 512)>>0]) >> 16)-128; - UDU[pos] = ((RGB_YUV_TABLE[(r + 768)>>0] + RGB_YUV_TABLE[(g + 1024)>>0] + RGB_YUV_TABLE[(b + 1280)>>0]) >> 16)-128; - VDU[pos] = ((RGB_YUV_TABLE[(r + 1280)>>0] + RGB_YUV_TABLE[(g + 1536)>>0] + RGB_YUV_TABLE[(b + 1792)>>0]) >> 16)-128; - - } - - DCY = processDU(YDU, fdtbl_Y, DCY, YDC_HT, YAC_HT); - DCU = processDU(UDU, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); - DCV = processDU(VDU, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); - x+=32; - } - y+=8; - } - - - //////////////////////////////////////////////////////////////// - - // Do the bit alignment of the EOI marker - if ( bytepos >= 0 ) { - var fillbits = []; - fillbits[1] = bytepos+1; - fillbits[0] = (1<<(bytepos+1))-1; - writeBits(fillbits); - } - - writeWord(0xFFD9); //EOI - - var jpegDataUri = 'data:image/jpeg;base64,' + btoa(byteout.join('')); - - byteout = []; - - // benchmarking - // var duration = new Date().getTime() - time_start; - // console.log('Encoding time: '+ currentQuality + 'ms'); - // - - return jpegDataUri - } - - function setQuality(quality){ - if (quality <= 0) { - quality = 1; - } - if (quality > 100) { - quality = 100; - } - - if(currentQuality == quality) return // don't recalc if unchanged - - var sf = 0; - if (quality < 50) { - sf = Math.floor(5000 / quality); - } else { - sf = Math.floor(200 - quality*2); - } - - initQuantTables(sf); - currentQuality = quality; - // console.log('Quality set to: '+quality +'%'); - } - - function init(){ - // var time_start = new Date().getTime(); - if(!quality) quality = 50; - // Create tables - initCharLookupTable() - initHuffmanTbl(); - initCategoryNumber(); - initRGBYUVTable(); - - setQuality(quality); - // var duration = new Date().getTime() - time_start; - // console.log('Initialization '+ duration + 'ms'); - } - - init(); - - }; - - JPEGEncoder.encode = function( data, quality ) { - var encoder = new JPEGEncoder( quality ); - - return encoder.encode( data ); - } - - return JPEGEncoder; - }); - /** - * @fileOverview Fix android canvas.toDataUrl bug. - */ - define('runtime/html5/androidpatch',[ - 'runtime/html5/util', - 'runtime/html5/jpegencoder', - 'base' - ], function( Util, encoder, Base ) { - var origin = Util.canvasToDataUrl, - supportJpeg; - - Util.canvasToDataUrl = function( canvas, type, quality ) { - var ctx, w, h, fragement, parts; - - // 非android手机直接跳过。 - if ( !Base.os.android ) { - return origin.apply( null, arguments ); - } - - // 检测是否canvas支持jpeg导出,根据数据格式来判断。 - // JPEG 前两位分别是:255, 216 - if ( type === 'image/jpeg' && typeof supportJpeg === 'undefined' ) { - fragement = origin.apply( null, arguments ); - - parts = fragement.split(','); - - if ( ~parts[ 0 ].indexOf('base64') ) { - fragement = atob( parts[ 1 ] ); - } else { - fragement = decodeURIComponent( parts[ 1 ] ); - } - - fragement = fragement.substring( 0, 2 ); - - supportJpeg = fragement.charCodeAt( 0 ) === 255 && - fragement.charCodeAt( 1 ) === 216; - } - - // 只有在android环境下才修复 - if ( type === 'image/jpeg' && !supportJpeg ) { - w = canvas.width; - h = canvas.height; - ctx = canvas.getContext('2d'); - - return encoder.encode( ctx.getImageData( 0, 0, w, h ), quality ); - } - - return origin.apply( null, arguments ); - }; - }); - /** - * @fileOverview Image - */ - define('runtime/html5/image',[ - 'base', - 'runtime/html5/runtime', - 'runtime/html5/util' - ], function( Base, Html5Runtime, Util ) { - - var BLANK = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs%3D'; - - return Html5Runtime.register( 'Image', { - - // flag: 标记是否被修改过。 - modified: false, - - init: function() { - var me = this, - img = new Image(); - - img.onload = function() { - - me._info = { - type: me.type, - width: this.width, - height: this.height - }; - - // 读取meta信息。 - if ( !me._metas && 'image/jpeg' === me.type ) { - Util.parseMeta( me._blob, function( error, ret ) { - me._metas = ret; - me.owner.trigger('load'); - }); - } else { - me.owner.trigger('load'); - } - }; - - img.onerror = function() { - me.owner.trigger('error'); - }; - - me._img = img; - }, - - loadFromBlob: function( blob ) { - var me = this, - img = me._img; - - me._blob = blob; - me.type = blob.type; - img.src = Util.createObjectURL( blob.getSource() ); - me.owner.once( 'load', function() { - Util.revokeObjectURL( img.src ); - }); - }, - - resize: function( width, height ) { - var canvas = this._canvas || - (this._canvas = document.createElement('canvas')); - - this._resize( this._img, canvas, width, height ); - this._blob = null; // 没用了,可以删掉了。 - this.modified = true; - this.owner.trigger( 'complete', 'resize' ); - }, - - crop: function( x, y, w, h, s ) { - var cvs = this._canvas || - (this._canvas = document.createElement('canvas')), - opts = this.options, - img = this._img, - iw = img.naturalWidth, - ih = img.naturalHeight, - orientation = this.getOrientation(); - - s = s || 1; - - // todo 解决 orientation 的问题。 - // values that require 90 degree rotation - // if ( ~[ 5, 6, 7, 8 ].indexOf( orientation ) ) { - - // switch ( orientation ) { - // case 6: - // tmp = x; - // x = y; - // y = iw * s - tmp - w; - // console.log(ih * s, tmp, w) - // break; - // } - - // (w ^= h, h ^= w, w ^= h); - // } - - cvs.width = w; - cvs.height = h; - - opts.preserveHeaders || this._rotate2Orientaion( cvs, orientation ); - this._renderImageToCanvas( cvs, img, -x, -y, iw * s, ih * s ); - - this._blob = null; // 没用了,可以删掉了。 - this.modified = true; - this.owner.trigger( 'complete', 'crop' ); - }, - - getAsBlob: function( type ) { - var blob = this._blob, - opts = this.options, - canvas; - - type = type || this.type; - - // blob需要重新生成。 - if ( this.modified || this.type !== type ) { - canvas = this._canvas; - - if ( type === 'image/jpeg' ) { - - blob = Util.canvasToDataUrl( canvas, type, opts.quality ); - - if ( opts.preserveHeaders && this._metas && - this._metas.imageHead ) { - - blob = Util.dataURL2ArrayBuffer( blob ); - blob = Util.updateImageHead( blob, - this._metas.imageHead ); - blob = Util.arrayBufferToBlob( blob, type ); - return blob; - } - } else { - blob = Util.canvasToDataUrl( canvas, type ); - } - - blob = Util.dataURL2Blob( blob ); - } - - return blob; - }, - - getAsDataUrl: function( type ) { - var opts = this.options; - - type = type || this.type; - - if ( type === 'image/jpeg' ) { - return Util.canvasToDataUrl( this._canvas, type, opts.quality ); - } else { - return this._canvas.toDataURL( type ); - } - }, - - getOrientation: function() { - return this._metas && this._metas.exif && - this._metas.exif.get('Orientation') || 1; - }, - - info: function( val ) { - - // setter - if ( val ) { - this._info = val; - return this; - } - - // getter - return this._info; - }, - - meta: function( val ) { - - // setter - if ( val ) { - this._meta = val; - return this; - } - - // getter - return this._meta; - }, - - destroy: function() { - var canvas = this._canvas; - this._img.onload = null; - - if ( canvas ) { - canvas.getContext('2d') - .clearRect( 0, 0, canvas.width, canvas.height ); - canvas.width = canvas.height = 0; - this._canvas = null; - } - - // 释放内存。非常重要,否则释放不了image的内存。 - this._img.src = BLANK; - this._img = this._blob = null; - }, - - _resize: function( img, cvs, width, height ) { - var opts = this.options, - naturalWidth = img.width, - naturalHeight = img.height, - orientation = this.getOrientation(), - scale, w, h, x, y; - - // values that require 90 degree rotation - if ( ~[ 5, 6, 7, 8 ].indexOf( orientation ) ) { - - // 交换width, height的值。 - width ^= height; - height ^= width; - width ^= height; - } - - scale = Math[ opts.crop ? 'max' : 'min' ]( width / naturalWidth, - height / naturalHeight ); - - // 不允许放大。 - opts.allowMagnify || (scale = Math.min( 1, scale )); - - w = naturalWidth * scale; - h = naturalHeight * scale; - - if ( opts.crop ) { - cvs.width = width; - cvs.height = height; - } else { - cvs.width = w; - cvs.height = h; - } - - x = (cvs.width - w) / 2; - y = (cvs.height - h) / 2; - - opts.preserveHeaders || this._rotate2Orientaion( cvs, orientation ); - - this._renderImageToCanvas( cvs, img, x, y, w, h ); - }, - - _rotate2Orientaion: function( canvas, orientation ) { - var width = canvas.width, - height = canvas.height, - ctx = canvas.getContext('2d'); - - switch ( orientation ) { - case 5: - case 6: - case 7: - case 8: - canvas.width = height; - canvas.height = width; - break; - } - - switch ( orientation ) { - case 2: // horizontal flip - ctx.translate( width, 0 ); - ctx.scale( -1, 1 ); - break; - - case 3: // 180 rotate left - ctx.translate( width, height ); - ctx.rotate( Math.PI ); - break; - - case 4: // vertical flip - ctx.translate( 0, height ); - ctx.scale( 1, -1 ); - break; - - case 5: // vertical flip + 90 rotate right - ctx.rotate( 0.5 * Math.PI ); - ctx.scale( 1, -1 ); - break; - - case 6: // 90 rotate right - ctx.rotate( 0.5 * Math.PI ); - ctx.translate( 0, -height ); - break; - - case 7: // horizontal flip + 90 rotate right - ctx.rotate( 0.5 * Math.PI ); - ctx.translate( width, -height ); - ctx.scale( -1, 1 ); - break; - - case 8: // 90 rotate left - ctx.rotate( -0.5 * Math.PI ); - ctx.translate( -width, 0 ); - break; - } - }, - - // https://github.com/stomita/ios-imagefile-megapixel/ - // blob/master/src/megapix-image.js - _renderImageToCanvas: (function() { - - // 如果不是ios, 不需要这么复杂! - if ( !Base.os.ios ) { - return function( canvas ) { - var args = Base.slice( arguments, 1 ), - ctx = canvas.getContext('2d'); - - ctx.drawImage.apply( ctx, args ); - }; - } - - /** - * Detecting vertical squash in loaded image. - * Fixes a bug which squash image vertically while drawing into - * canvas for some images. - */ - function detectVerticalSquash( img, iw, ih ) { - var canvas = document.createElement('canvas'), - ctx = canvas.getContext('2d'), - sy = 0, - ey = ih, - py = ih, - data, alpha, ratio; - - - canvas.width = 1; - canvas.height = ih; - ctx.drawImage( img, 0, 0 ); - data = ctx.getImageData( 0, 0, 1, ih ).data; - - // search image edge pixel position in case - // it is squashed vertically. - while ( py > sy ) { - alpha = data[ (py - 1) * 4 + 3 ]; - - if ( alpha === 0 ) { - ey = py; - } else { - sy = py; - } - - py = (ey + sy) >> 1; - } - - ratio = (py / ih); - return (ratio === 0) ? 1 : ratio; - } - - // fix ie7 bug - // http://stackoverflow.com/questions/11929099/ - // html5-canvas-drawimage-ratio-bug-ios - if ( Base.os.ios >= 7 ) { - return function( canvas, img, x, y, w, h ) { - var iw = img.naturalWidth, - ih = img.naturalHeight, - vertSquashRatio = detectVerticalSquash( img, iw, ih ); - - return canvas.getContext('2d').drawImage( img, 0, 0, - iw * vertSquashRatio, ih * vertSquashRatio, - x, y, w, h ); - }; - } - - /** - * Detect subsampling in loaded image. - * In iOS, larger images than 2M pixels may be - * subsampled in rendering. - */ - function detectSubsampling( img ) { - var iw = img.naturalWidth, - ih = img.naturalHeight, - canvas, ctx; - - // subsampling may happen overmegapixel image - if ( iw * ih > 1024 * 1024 ) { - canvas = document.createElement('canvas'); - canvas.width = canvas.height = 1; - ctx = canvas.getContext('2d'); - ctx.drawImage( img, -iw + 1, 0 ); - - // subsampled image becomes half smaller in rendering size. - // check alpha channel value to confirm image is covering - // edge pixel or not. if alpha value is 0 - // image is not covering, hence subsampled. - return ctx.getImageData( 0, 0, 1, 1 ).data[ 3 ] === 0; - } else { - return false; - } - } - - - return function( canvas, img, x, y, width, height ) { - var iw = img.naturalWidth, - ih = img.naturalHeight, - ctx = canvas.getContext('2d'), - subsampled = detectSubsampling( img ), - doSquash = this.type === 'image/jpeg', - d = 1024, - sy = 0, - dy = 0, - tmpCanvas, tmpCtx, vertSquashRatio, dw, dh, sx, dx; - - if ( subsampled ) { - iw /= 2; - ih /= 2; - } - - ctx.save(); - tmpCanvas = document.createElement('canvas'); - tmpCanvas.width = tmpCanvas.height = d; - - tmpCtx = tmpCanvas.getContext('2d'); - vertSquashRatio = doSquash ? - detectVerticalSquash( img, iw, ih ) : 1; - - dw = Math.ceil( d * width / iw ); - dh = Math.ceil( d * height / ih / vertSquashRatio ); - - while ( sy < ih ) { - sx = 0; - dx = 0; - while ( sx < iw ) { - tmpCtx.clearRect( 0, 0, d, d ); - tmpCtx.drawImage( img, -sx, -sy ); - ctx.drawImage( tmpCanvas, 0, 0, d, d, - x + dx, y + dy, dw, dh ); - sx += d; - dx += dw; - } - sy += d; - dy += dh; - } - ctx.restore(); - tmpCanvas = tmpCtx = null; - }; - })() - }); - }); - /** - * @fileOverview Transport - * @todo 支持chunked传输,优势: - * 可以将大文件分成小块,挨个传输,可以提高大文件成功率,当失败的时候,也只需要重传那小部分, - * 而不需要重头再传一次。另外断点续传也需要用chunked方式。 - */ - define('runtime/html5/transport',[ - 'base', - 'runtime/html5/runtime' - ], function( Base, Html5Runtime ) { - - var noop = Base.noop, - $ = Base.$; - - return Html5Runtime.register( 'Transport', { - init: function() { - this._status = 0; - this._response = null; - }, - - send: function() { - var owner = this.owner, - opts = this.options, - xhr = this._initAjax(), - blob = owner._blob, - server = opts.server, - formData, binary, fr; - - if ( opts.sendAsBinary ) { - server += (/\?/.test( server ) ? '&' : '?') + - $.param( owner._formData ); - - binary = blob.getSource(); - } else { - formData = new FormData(); - $.each( owner._formData, function( k, v ) { - formData.append( k, v ); - }); - - formData.append( opts.fileVal, blob.getSource(), - opts.filename || owner._formData.name || '' ); - } - - if ( opts.withCredentials && 'withCredentials' in xhr ) { - xhr.open( opts.method, server, true ); - xhr.withCredentials = true; - } else { - xhr.open( opts.method, server ); - } - - this._setRequestHeader( xhr, opts.headers ); - - if ( binary ) { - // 强制设置成 content-type 为文件流。 - xhr.overrideMimeType && - xhr.overrideMimeType('application/octet-stream'); - - // android直接发送blob会导致服务端接收到的是空文件。 - // bug详情。 - // https://code.google.com/p/android/issues/detail?id=39882 - // 所以先用fileReader读取出来再通过arraybuffer的方式发送。 - if ( Base.os.android ) { - fr = new FileReader(); - - fr.onload = function() { - xhr.send( this.result ); - fr = fr.onload = null; - }; - - fr.readAsArrayBuffer( binary ); - } else { - xhr.send( binary ); - } - } else { - xhr.send( formData ); - } - }, - - getResponse: function() { - return this._response; - }, - - getResponseAsJson: function() { - return this._parseJson( this._response ); - }, - - getStatus: function() { - return this._status; - }, - - abort: function() { - var xhr = this._xhr; - - if ( xhr ) { - xhr.upload.onprogress = noop; - xhr.onreadystatechange = noop; - xhr.abort(); - - this._xhr = xhr = null; - } - }, - - destroy: function() { - this.abort(); - }, - - _initAjax: function() { - var me = this, - xhr = new XMLHttpRequest(), - opts = this.options; - - if ( opts.withCredentials && !('withCredentials' in xhr) && - typeof XDomainRequest !== 'undefined' ) { - xhr = new XDomainRequest(); - } - - xhr.upload.onprogress = function( e ) { - var percentage = 0; - - if ( e.lengthComputable ) { - percentage = e.loaded / e.total; - } - - return me.trigger( 'progress', percentage ); - }; - - xhr.onreadystatechange = function() { - - if ( xhr.readyState !== 4 ) { - return; - } - - xhr.upload.onprogress = noop; - xhr.onreadystatechange = noop; - me._xhr = null; - me._status = xhr.status; - - if ( xhr.status >= 200 && xhr.status < 300 ) { - me._response = xhr.responseText; - return me.trigger('load'); - } else if ( xhr.status >= 500 && xhr.status < 600 ) { - me._response = xhr.responseText; - return me.trigger( 'error', 'server' ); - } - - - return me.trigger( 'error', me._status ? 'http' : 'abort' ); - }; - - me._xhr = xhr; - return xhr; - }, - - _setRequestHeader: function( xhr, headers ) { - $.each( headers, function( key, val ) { - xhr.setRequestHeader( key, val ); - }); - }, - - _parseJson: function( str ) { - var json; - - try { - json = JSON.parse( str ); - } catch ( ex ) { - json = {}; - } - - return json; - } - }); - }); - /** - * @fileOverview Transport flash实现 - */ - define('runtime/html5/md5',[ - 'runtime/html5/runtime' - ], function( FlashRuntime ) { - - /* - * Fastest md5 implementation around (JKM md5) - * Credits: Joseph Myers - * - * @see http://www.myersdaily.org/joseph/javascript/md5-text.html - * @see http://jsperf.com/md5-shootout/7 - */ - - /* this function is much faster, - so if possible we use it. Some IEs - are the only ones I know of that - need the idiotic second function, - generated by an if clause. */ - var add32 = function (a, b) { - return (a + b) & 0xFFFFFFFF; - }, - - cmn = function (q, a, b, x, s, t) { - a = add32(add32(a, q), add32(x, t)); - return add32((a << s) | (a >>> (32 - s)), b); - }, - - ff = function (a, b, c, d, x, s, t) { - return cmn((b & c) | ((~b) & d), a, b, x, s, t); - }, - - gg = function (a, b, c, d, x, s, t) { - return cmn((b & d) | (c & (~d)), a, b, x, s, t); - }, - - hh = function (a, b, c, d, x, s, t) { - return cmn(b ^ c ^ d, a, b, x, s, t); - }, - - ii = function (a, b, c, d, x, s, t) { - return cmn(c ^ (b | (~d)), a, b, x, s, t); - }, - - md5cycle = function (x, k) { - var a = x[0], - b = x[1], - c = x[2], - d = x[3]; - - a = ff(a, b, c, d, k[0], 7, -680876936); - d = ff(d, a, b, c, k[1], 12, -389564586); - c = ff(c, d, a, b, k[2], 17, 606105819); - b = ff(b, c, d, a, k[3], 22, -1044525330); - a = ff(a, b, c, d, k[4], 7, -176418897); - d = ff(d, a, b, c, k[5], 12, 1200080426); - c = ff(c, d, a, b, k[6], 17, -1473231341); - b = ff(b, c, d, a, k[7], 22, -45705983); - a = ff(a, b, c, d, k[8], 7, 1770035416); - d = ff(d, a, b, c, k[9], 12, -1958414417); - c = ff(c, d, a, b, k[10], 17, -42063); - b = ff(b, c, d, a, k[11], 22, -1990404162); - a = ff(a, b, c, d, k[12], 7, 1804603682); - d = ff(d, a, b, c, k[13], 12, -40341101); - c = ff(c, d, a, b, k[14], 17, -1502002290); - b = ff(b, c, d, a, k[15], 22, 1236535329); - - a = gg(a, b, c, d, k[1], 5, -165796510); - d = gg(d, a, b, c, k[6], 9, -1069501632); - c = gg(c, d, a, b, k[11], 14, 643717713); - b = gg(b, c, d, a, k[0], 20, -373897302); - a = gg(a, b, c, d, k[5], 5, -701558691); - d = gg(d, a, b, c, k[10], 9, 38016083); - c = gg(c, d, a, b, k[15], 14, -660478335); - b = gg(b, c, d, a, k[4], 20, -405537848); - a = gg(a, b, c, d, k[9], 5, 568446438); - d = gg(d, a, b, c, k[14], 9, -1019803690); - c = gg(c, d, a, b, k[3], 14, -187363961); - b = gg(b, c, d, a, k[8], 20, 1163531501); - a = gg(a, b, c, d, k[13], 5, -1444681467); - d = gg(d, a, b, c, k[2], 9, -51403784); - c = gg(c, d, a, b, k[7], 14, 1735328473); - b = gg(b, c, d, a, k[12], 20, -1926607734); - - a = hh(a, b, c, d, k[5], 4, -378558); - d = hh(d, a, b, c, k[8], 11, -2022574463); - c = hh(c, d, a, b, k[11], 16, 1839030562); - b = hh(b, c, d, a, k[14], 23, -35309556); - a = hh(a, b, c, d, k[1], 4, -1530992060); - d = hh(d, a, b, c, k[4], 11, 1272893353); - c = hh(c, d, a, b, k[7], 16, -155497632); - b = hh(b, c, d, a, k[10], 23, -1094730640); - a = hh(a, b, c, d, k[13], 4, 681279174); - d = hh(d, a, b, c, k[0], 11, -358537222); - c = hh(c, d, a, b, k[3], 16, -722521979); - b = hh(b, c, d, a, k[6], 23, 76029189); - a = hh(a, b, c, d, k[9], 4, -640364487); - d = hh(d, a, b, c, k[12], 11, -421815835); - c = hh(c, d, a, b, k[15], 16, 530742520); - b = hh(b, c, d, a, k[2], 23, -995338651); - - a = ii(a, b, c, d, k[0], 6, -198630844); - d = ii(d, a, b, c, k[7], 10, 1126891415); - c = ii(c, d, a, b, k[14], 15, -1416354905); - b = ii(b, c, d, a, k[5], 21, -57434055); - a = ii(a, b, c, d, k[12], 6, 1700485571); - d = ii(d, a, b, c, k[3], 10, -1894986606); - c = ii(c, d, a, b, k[10], 15, -1051523); - b = ii(b, c, d, a, k[1], 21, -2054922799); - a = ii(a, b, c, d, k[8], 6, 1873313359); - d = ii(d, a, b, c, k[15], 10, -30611744); - c = ii(c, d, a, b, k[6], 15, -1560198380); - b = ii(b, c, d, a, k[13], 21, 1309151649); - a = ii(a, b, c, d, k[4], 6, -145523070); - d = ii(d, a, b, c, k[11], 10, -1120210379); - c = ii(c, d, a, b, k[2], 15, 718787259); - b = ii(b, c, d, a, k[9], 21, -343485551); - - x[0] = add32(a, x[0]); - x[1] = add32(b, x[1]); - x[2] = add32(c, x[2]); - x[3] = add32(d, x[3]); - }, - - /* there needs to be support for Unicode here, - * unless we pretend that we can redefine the MD-5 - * algorithm for multi-byte characters (perhaps - * by adding every four 16-bit characters and - * shortening the sum to 32 bits). Otherwise - * I suggest performing MD-5 as if every character - * was two bytes--e.g., 0040 0025 = @%--but then - * how will an ordinary MD-5 sum be matched? - * There is no way to standardize text to something - * like UTF-8 before transformation; speed cost is - * utterly prohibitive. The JavaScript standard - * itself needs to look at this: it should start - * providing access to strings as preformed UTF-8 - * 8-bit unsigned value arrays. - */ - md5blk = function (s) { - var md5blks = [], - i; /* Andy King said do it this way. */ - - for (i = 0; i < 64; i += 4) { - md5blks[i >> 2] = s.charCodeAt(i) + (s.charCodeAt(i + 1) << 8) + (s.charCodeAt(i + 2) << 16) + (s.charCodeAt(i + 3) << 24); - } - return md5blks; - }, - - md5blk_array = function (a) { - var md5blks = [], - i; /* Andy King said do it this way. */ - - for (i = 0; i < 64; i += 4) { - md5blks[i >> 2] = a[i] + (a[i + 1] << 8) + (a[i + 2] << 16) + (a[i + 3] << 24); - } - return md5blks; - }, - - md51 = function (s) { - var n = s.length, - state = [1732584193, -271733879, -1732584194, 271733878], - i, - length, - tail, - tmp, - lo, - hi; - - for (i = 64; i <= n; i += 64) { - md5cycle(state, md5blk(s.substring(i - 64, i))); - } - s = s.substring(i - 64); - length = s.length; - tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - for (i = 0; i < length; i += 1) { - tail[i >> 2] |= s.charCodeAt(i) << ((i % 4) << 3); - } - tail[i >> 2] |= 0x80 << ((i % 4) << 3); - if (i > 55) { - md5cycle(state, tail); - for (i = 0; i < 16; i += 1) { - tail[i] = 0; - } - } - - // Beware that the final length might not fit in 32 bits so we take care of that - tmp = n * 8; - tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/); - lo = parseInt(tmp[2], 16); - hi = parseInt(tmp[1], 16) || 0; - - tail[14] = lo; - tail[15] = hi; - - md5cycle(state, tail); - return state; - }, - - md51_array = function (a) { - var n = a.length, - state = [1732584193, -271733879, -1732584194, 271733878], - i, - length, - tail, - tmp, - lo, - hi; - - for (i = 64; i <= n; i += 64) { - md5cycle(state, md5blk_array(a.subarray(i - 64, i))); - } - - // Not sure if it is a bug, however IE10 will always produce a sub array of length 1 - // containing the last element of the parent array if the sub array specified starts - // beyond the length of the parent array - weird. - // https://connect.microsoft.com/IE/feedback/details/771452/typed-array-subarray-issue - a = (i - 64) < n ? a.subarray(i - 64) : new Uint8Array(0); - - length = a.length; - tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - for (i = 0; i < length; i += 1) { - tail[i >> 2] |= a[i] << ((i % 4) << 3); - } - - tail[i >> 2] |= 0x80 << ((i % 4) << 3); - if (i > 55) { - md5cycle(state, tail); - for (i = 0; i < 16; i += 1) { - tail[i] = 0; - } - } - - // Beware that the final length might not fit in 32 bits so we take care of that - tmp = n * 8; - tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/); - lo = parseInt(tmp[2], 16); - hi = parseInt(tmp[1], 16) || 0; - - tail[14] = lo; - tail[15] = hi; - - md5cycle(state, tail); - - return state; - }, - - hex_chr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'], - - rhex = function (n) { - var s = '', - j; - for (j = 0; j < 4; j += 1) { - s += hex_chr[(n >> (j * 8 + 4)) & 0x0F] + hex_chr[(n >> (j * 8)) & 0x0F]; - } - return s; - }, - - hex = function (x) { - var i; - for (i = 0; i < x.length; i += 1) { - x[i] = rhex(x[i]); - } - return x.join(''); - }, - - md5 = function (s) { - return hex(md51(s)); - }, - - - - //////////////////////////////////////////////////////////////////////////// - - /** - * SparkMD5 OOP implementation. - * - * Use this class to perform an incremental md5, otherwise use the - * static methods instead. - */ - SparkMD5 = function () { - // call reset to init the instance - this.reset(); - }; - - - // In some cases the fast add32 function cannot be used.. - if (md5('hello') !== '5d41402abc4b2a76b9719d911017c592') { - add32 = function (x, y) { - var lsw = (x & 0xFFFF) + (y & 0xFFFF), - msw = (x >> 16) + (y >> 16) + (lsw >> 16); - return (msw << 16) | (lsw & 0xFFFF); - }; - } - - - /** - * Appends a string. - * A conversion will be applied if an utf8 string is detected. - * - * @param {String} str The string to be appended - * - * @return {SparkMD5} The instance itself - */ - SparkMD5.prototype.append = function (str) { - // converts the string to utf8 bytes if necessary - if (/[\u0080-\uFFFF]/.test(str)) { - str = unescape(encodeURIComponent(str)); - } - - // then append as binary - this.appendBinary(str); - - return this; - }; - - /** - * Appends a binary string. - * - * @param {String} contents The binary string to be appended - * - * @return {SparkMD5} The instance itself - */ - SparkMD5.prototype.appendBinary = function (contents) { - this._buff += contents; - this._length += contents.length; - - var length = this._buff.length, - i; - - for (i = 64; i <= length; i += 64) { - md5cycle(this._state, md5blk(this._buff.substring(i - 64, i))); - } - - this._buff = this._buff.substr(i - 64); - - return this; - }; - - /** - * Finishes the incremental computation, reseting the internal state and - * returning the result. - * Use the raw parameter to obtain the raw result instead of the hex one. - * - * @param {Boolean} raw True to get the raw result, false to get the hex result - * - * @return {String|Array} The result - */ - SparkMD5.prototype.end = function (raw) { - var buff = this._buff, - length = buff.length, - i, - tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ret; - - for (i = 0; i < length; i += 1) { - tail[i >> 2] |= buff.charCodeAt(i) << ((i % 4) << 3); - } - - this._finish(tail, length); - ret = !!raw ? this._state : hex(this._state); - - this.reset(); - - return ret; - }; - - /** - * Finish the final calculation based on the tail. - * - * @param {Array} tail The tail (will be modified) - * @param {Number} length The length of the remaining buffer - */ - SparkMD5.prototype._finish = function (tail, length) { - var i = length, - tmp, - lo, - hi; - - tail[i >> 2] |= 0x80 << ((i % 4) << 3); - if (i > 55) { - md5cycle(this._state, tail); - for (i = 0; i < 16; i += 1) { - tail[i] = 0; - } - } - - // Do the final computation based on the tail and length - // Beware that the final length may not fit in 32 bits so we take care of that - tmp = this._length * 8; - tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/); - lo = parseInt(tmp[2], 16); - hi = parseInt(tmp[1], 16) || 0; - - tail[14] = lo; - tail[15] = hi; - md5cycle(this._state, tail); - }; - - /** - * Resets the internal state of the computation. - * - * @return {SparkMD5} The instance itself - */ - SparkMD5.prototype.reset = function () { - this._buff = ""; - this._length = 0; - this._state = [1732584193, -271733879, -1732584194, 271733878]; - - return this; - }; - - /** - * Releases memory used by the incremental buffer and other aditional - * resources. If you plan to use the instance again, use reset instead. - */ - SparkMD5.prototype.destroy = function () { - delete this._state; - delete this._buff; - delete this._length; - }; - - - /** - * Performs the md5 hash on a string. - * A conversion will be applied if utf8 string is detected. - * - * @param {String} str The string - * @param {Boolean} raw True to get the raw result, false to get the hex result - * - * @return {String|Array} The result - */ - SparkMD5.hash = function (str, raw) { - // converts the string to utf8 bytes if necessary - if (/[\u0080-\uFFFF]/.test(str)) { - str = unescape(encodeURIComponent(str)); - } - - var hash = md51(str); - - return !!raw ? hash : hex(hash); - }; - - /** - * Performs the md5 hash on a binary string. - * - * @param {String} content The binary string - * @param {Boolean} raw True to get the raw result, false to get the hex result - * - * @return {String|Array} The result - */ - SparkMD5.hashBinary = function (content, raw) { - var hash = md51(content); - - return !!raw ? hash : hex(hash); - }; - - /** - * SparkMD5 OOP implementation for array buffers. - * - * Use this class to perform an incremental md5 ONLY for array buffers. - */ - SparkMD5.ArrayBuffer = function () { - // call reset to init the instance - this.reset(); - }; - - //////////////////////////////////////////////////////////////////////////// - - /** - * Appends an array buffer. - * - * @param {ArrayBuffer} arr The array to be appended - * - * @return {SparkMD5.ArrayBuffer} The instance itself - */ - SparkMD5.ArrayBuffer.prototype.append = function (arr) { - // TODO: we could avoid the concatenation here but the algorithm would be more complex - // if you find yourself needing extra performance, please make a PR. - var buff = this._concatArrayBuffer(this._buff, arr), - length = buff.length, - i; - - this._length += arr.byteLength; - - for (i = 64; i <= length; i += 64) { - md5cycle(this._state, md5blk_array(buff.subarray(i - 64, i))); - } - - // Avoids IE10 weirdness (documented above) - this._buff = (i - 64) < length ? buff.subarray(i - 64) : new Uint8Array(0); - - return this; - }; - - /** - * Finishes the incremental computation, reseting the internal state and - * returning the result. - * Use the raw parameter to obtain the raw result instead of the hex one. - * - * @param {Boolean} raw True to get the raw result, false to get the hex result - * - * @return {String|Array} The result - */ - SparkMD5.ArrayBuffer.prototype.end = function (raw) { - var buff = this._buff, - length = buff.length, - tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - i, - ret; - - for (i = 0; i < length; i += 1) { - tail[i >> 2] |= buff[i] << ((i % 4) << 3); - } - - this._finish(tail, length); - ret = !!raw ? this._state : hex(this._state); - - this.reset(); - - return ret; - }; - - SparkMD5.ArrayBuffer.prototype._finish = SparkMD5.prototype._finish; - - /** - * Resets the internal state of the computation. - * - * @return {SparkMD5.ArrayBuffer} The instance itself - */ - SparkMD5.ArrayBuffer.prototype.reset = function () { - this._buff = new Uint8Array(0); - this._length = 0; - this._state = [1732584193, -271733879, -1732584194, 271733878]; - - return this; - }; - - /** - * Releases memory used by the incremental buffer and other aditional - * resources. If you plan to use the instance again, use reset instead. - */ - SparkMD5.ArrayBuffer.prototype.destroy = SparkMD5.prototype.destroy; - - /** - * Concats two array buffers, returning a new one. - * - * @param {ArrayBuffer} first The first array buffer - * @param {ArrayBuffer} second The second array buffer - * - * @return {ArrayBuffer} The new array buffer - */ - SparkMD5.ArrayBuffer.prototype._concatArrayBuffer = function (first, second) { - var firstLength = first.length, - result = new Uint8Array(firstLength + second.byteLength); - - result.set(first); - result.set(new Uint8Array(second), firstLength); - - return result; - }; - - /** - * Performs the md5 hash on an array buffer. - * - * @param {ArrayBuffer} arr The array buffer - * @param {Boolean} raw True to get the raw result, false to get the hex result - * - * @return {String|Array} The result - */ - SparkMD5.ArrayBuffer.hash = function (arr, raw) { - var hash = md51_array(new Uint8Array(arr)); - - return !!raw ? hash : hex(hash); - }; - - return FlashRuntime.register( 'Md5', { - init: function() { - // do nothing. - }, - - loadFromBlob: function( file ) { - var blob = file.getSource(), - chunkSize = 2 * 1024 * 1024, - chunks = Math.ceil( blob.size / chunkSize ), - chunk = 0, - owner = this.owner, - spark = new SparkMD5.ArrayBuffer(), - me = this, - blobSlice = blob.mozSlice || blob.webkitSlice || blob.slice, - loadNext, fr; - - fr = new FileReader(); - - loadNext = function() { - var start, end; - - start = chunk * chunkSize; - end = Math.min( start + chunkSize, blob.size ); - - fr.onload = function( e ) { - spark.append( e.target.result ); - owner.trigger( 'progress', { - total: file.size, - loaded: end - }); - }; - - fr.onloadend = function() { - fr.onloadend = fr.onload = null; - - if ( ++chunk < chunks ) { - setTimeout( loadNext, 1 ); - } else { - setTimeout(function(){ - owner.trigger('load'); - me.result = spark.end(); - loadNext = file = blob = spark = null; - owner.trigger('complete'); - }, 50 ); - } - }; - - fr.readAsArrayBuffer( blobSlice.call( blob, start, end ) ); - }; - - loadNext(); - }, - - getResult: function() { - return this.result; - } - }); - }); - /** - * @fileOverview FlashRuntime - */ - define('runtime/flash/runtime',[ - 'base', - 'runtime/runtime', - 'runtime/compbase' - ], function( Base, Runtime, CompBase ) { - - var $ = Base.$, - type = 'flash', - components = {}; - - - function getFlashVersion() { - var version; - - try { - version = navigator.plugins[ 'Shockwave Flash' ]; - version = version.description; - } catch ( ex ) { - try { - version = new ActiveXObject('ShockwaveFlash.ShockwaveFlash') - .GetVariable('$version'); - } catch ( ex2 ) { - version = '0.0'; - } - } - version = version.match( /\d+/g ); - return parseFloat( version[ 0 ] + '.' + version[ 1 ], 10 ); - } - - function FlashRuntime() { - var pool = {}, - clients = {}, - destroy = this.destroy, - me = this, - jsreciver = Base.guid('webuploader_'); - - Runtime.apply( me, arguments ); - me.type = type; - - - // 这个方法的调用者,实际上是RuntimeClient - me.exec = function( comp, fn/*, args...*/ ) { - var client = this, - uid = client.uid, - args = Base.slice( arguments, 2 ), - instance; - - clients[ uid ] = client; - - if ( components[ comp ] ) { - if ( !pool[ uid ] ) { - pool[ uid ] = new components[ comp ]( client, me ); - } - - instance = pool[ uid ]; - - if ( instance[ fn ] ) { - return instance[ fn ].apply( instance, args ); - } - } - - return me.flashExec.apply( client, arguments ); - }; - - function handler( evt, obj ) { - var type = evt.type || evt, - parts, uid; - - parts = type.split('::'); - uid = parts[ 0 ]; - type = parts[ 1 ]; - - // console.log.apply( console, arguments ); - - if ( type === 'Ready' && uid === me.uid ) { - me.trigger('ready'); - } else if ( clients[ uid ] ) { - clients[ uid ].trigger( type.toLowerCase(), evt, obj ); - } - - // Base.log( evt, obj ); - } - - // flash的接受器。 - window[ jsreciver ] = function() { - var args = arguments; - - // 为了能捕获得到。 - setTimeout(function() { - handler.apply( null, args ); - }, 1 ); - }; - - this.jsreciver = jsreciver; - - this.destroy = function() { - // @todo 删除池子中的所有实例 - return destroy && destroy.apply( this, arguments ); - }; - - this.flashExec = function( comp, fn ) { - var flash = me.getFlash(), - args = Base.slice( arguments, 2 ); - - return flash.exec( this.uid, comp, fn, args ); - }; - - // @todo - } - - Base.inherits( Runtime, { - constructor: FlashRuntime, - - init: function() { - var container = this.getContainer(), - opts = this.options, - html; - - // if not the minimal height, shims are not initialized - // in older browsers (e.g FF3.6, IE6,7,8, Safari 4.0,5.0, etc) - container.css({ - position: 'absolute', - top: '-8px', - left: '-8px', - width: '9px', - height: '9px', - overflow: 'hidden' - }); - - // insert flash object - html = '' + - '' + - '' + - '' + - ''; - - container.html( html ); - }, - - getFlash: function() { - if ( this._flash ) { - return this._flash; - } - - this._flash = $( '#' + this.uid ).get( 0 ); - return this._flash; - } - - }); - - FlashRuntime.register = function( name, component ) { - component = components[ name ] = Base.inherits( CompBase, $.extend({ - - // @todo fix this later - flashExec: function() { - var owner = this.owner, - runtime = this.getRuntime(); - - return runtime.flashExec.apply( owner, arguments ); - } - }, component ) ); - - return component; - }; - - if ( getFlashVersion() >= 11.4 ) { - Runtime.addRuntime( type, FlashRuntime ); - } - - return FlashRuntime; - }); - /** - * @fileOverview FilePicker - */ - define('runtime/flash/filepicker',[ - 'base', - 'runtime/flash/runtime' - ], function( Base, FlashRuntime ) { - var $ = Base.$; - - return FlashRuntime.register( 'FilePicker', { - init: function( opts ) { - var copy = $.extend({}, opts ), - len, i; - - // 修复Flash再没有设置title的情况下无法弹出flash文件选择框的bug. - len = copy.accept && copy.accept.length; - for ( i = 0; i < len; i++ ) { - if ( !copy.accept[ i ].title ) { - copy.accept[ i ].title = 'Files'; - } - } - - delete copy.button; - delete copy.id; - delete copy.container; - - this.flashExec( 'FilePicker', 'init', copy ); - }, - - destroy: function() { - this.flashExec( 'FilePicker', 'destroy' ); - } - }); - }); - /** - * @fileOverview 图片压缩 - */ - define('runtime/flash/image',[ - 'runtime/flash/runtime' - ], function( FlashRuntime ) { - - return FlashRuntime.register( 'Image', { - // init: function( options ) { - // var owner = this.owner; - - // this.flashExec( 'Image', 'init', options ); - // owner.on( 'load', function() { - // debugger; - // }); - // }, - - loadFromBlob: function( blob ) { - var owner = this.owner; - - owner.info() && this.flashExec( 'Image', 'info', owner.info() ); - owner.meta() && this.flashExec( 'Image', 'meta', owner.meta() ); - - this.flashExec( 'Image', 'loadFromBlob', blob.uid ); - } - }); - }); - /** - * @fileOverview Transport flash实现 - */ - define('runtime/flash/transport',[ - 'base', - 'runtime/flash/runtime', - 'runtime/client' - ], function( Base, FlashRuntime, RuntimeClient ) { - var $ = Base.$; - - return FlashRuntime.register( 'Transport', { - init: function() { - this._status = 0; - this._response = null; - this._responseJson = null; - }, - - send: function() { - var owner = this.owner, - opts = this.options, - xhr = this._initAjax(), - blob = owner._blob, - server = opts.server, - binary; - - xhr.connectRuntime( blob.ruid ); - - if ( opts.sendAsBinary ) { - server += (/\?/.test( server ) ? '&' : '?') + - $.param( owner._formData ); - - binary = blob.uid; - } else { - $.each( owner._formData, function( k, v ) { - xhr.exec( 'append', k, v ); - }); - - xhr.exec( 'appendBlob', opts.fileVal, blob.uid, - opts.filename || owner._formData.name || '' ); - } - - this._setRequestHeader( xhr, opts.headers ); - xhr.exec( 'send', { - method: opts.method, - url: server, - forceURLStream: opts.forceURLStream, - mimeType: 'application/octet-stream' - }, binary ); - }, - - getStatus: function() { - return this._status; - }, - - getResponse: function() { - return this._response || ''; - }, - - getResponseAsJson: function() { - return this._responseJson; - }, - - abort: function() { - var xhr = this._xhr; - - if ( xhr ) { - xhr.exec('abort'); - xhr.destroy(); - this._xhr = xhr = null; - } - }, - - destroy: function() { - this.abort(); - }, - - _initAjax: function() { - var me = this, - xhr = new RuntimeClient('XMLHttpRequest'); - - xhr.on( 'uploadprogress progress', function( e ) { - var percent = e.loaded / e.total; - percent = Math.min( 1, Math.max( 0, percent ) ); - return me.trigger( 'progress', percent ); - }); - - xhr.on( 'load', function() { - var status = xhr.exec('getStatus'), - readBody = false, - err = '', - p; - - xhr.off(); - me._xhr = null; - - if ( status >= 200 && status < 300 ) { - readBody = true; - } else if ( status >= 500 && status < 600 ) { - readBody = true; - err = 'server'; - } else { - err = 'http'; - } - - if ( readBody ) { - me._response = xhr.exec('getResponse'); - me._response = decodeURIComponent( me._response ); - - // flash 处理可能存在 bug, 没辙只能靠 js 了 - // try { - // me._responseJson = xhr.exec('getResponseAsJson'); - // } catch ( error ) { - - p = window.JSON && window.JSON.parse || function( s ) { - try { - return new Function('return ' + s).call(); - } catch ( err ) { - return {}; - } - }; - me._responseJson = me._response ? p(me._response) : {}; - - // } - } - - xhr.destroy(); - xhr = null; - - return err ? me.trigger( 'error', err ) : me.trigger('load'); - }); - - xhr.on( 'error', function() { - xhr.off(); - me._xhr = null; - me.trigger( 'error', 'http' ); - }); - - me._xhr = xhr; - return xhr; - }, - - _setRequestHeader: function( xhr, headers ) { - $.each( headers, function( key, val ) { - xhr.exec( 'setRequestHeader', key, val ); - }); - } - }); - }); - /** - * @fileOverview Blob Html实现 - */ - define('runtime/flash/blob',[ - 'runtime/flash/runtime', - 'lib/blob' - ], function( FlashRuntime, Blob ) { - - return FlashRuntime.register( 'Blob', { - slice: function( start, end ) { - var blob = this.flashExec( 'Blob', 'slice', start, end ); - - return new Blob( blob.uid, blob ); - } - }); - }); - /** - * @fileOverview Md5 flash实现 - */ - define('runtime/flash/md5',[ - 'runtime/flash/runtime' - ], function( FlashRuntime ) { - - return FlashRuntime.register( 'Md5', { - init: function() { - // do nothing. - }, - - loadFromBlob: function( blob ) { - return this.flashExec( 'Md5', 'loadFromBlob', blob.uid ); - } - }); - }); - /** - * @fileOverview 完全版本。 - */ - define('preset/all',[ - 'base', - - // widgets - 'widgets/filednd', - 'widgets/filepaste', - 'widgets/filepicker', - 'widgets/image', - 'widgets/queue', - 'widgets/runtime', - 'widgets/upload', - 'widgets/validator', - 'widgets/md5', - - // runtimes - // html5 - 'runtime/html5/blob', - 'runtime/html5/dnd', - 'runtime/html5/filepaste', - 'runtime/html5/filepicker', - 'runtime/html5/imagemeta/exif', - 'runtime/html5/androidpatch', - 'runtime/html5/image', - 'runtime/html5/transport', - 'runtime/html5/md5', - - // flash - 'runtime/flash/filepicker', - 'runtime/flash/image', - 'runtime/flash/transport', - 'runtime/flash/blob', - 'runtime/flash/md5' - ], function( Base ) { - return Base; - }); - /** - * @fileOverview 日志组件,主要用来收集错误信息,可以帮助 webuploader 更好的定位问题和发展。 - * - * 如果您不想要启用此功能,请在打包的时候去掉 log 模块。 - * - * 或者可以在初始化的时候通过 options.disableWidgets 属性禁用。 - * - * 如: - * WebUploader.create({ - * ... - * - * disableWidgets: 'log', - * - * ... - * }) - */ - define('widgets/log',[ - 'base', - 'uploader', - 'widgets/widget' - ], function( Base, Uploader ) { - var $ = Base.$, - logUrl = ' http://static.tieba.baidu.com/tb/pms/img/st.gif??', - product = (location.hostname || location.host || 'protected').toLowerCase(), - - // 只针对 baidu 内部产品用户做统计功能。 - enable = product && /baidu/i.exec(product), - base; - - if (!enable) { - return; - } - - base = { - dv: 3, - master: 'webuploader', - online: /test/.exec(product) ? 0 : 1, - module: '', - product: product, - type: 0 - }; - - function send(data) { - var obj = $.extend({}, base, data), - url = logUrl.replace(/^(.*)\?/, '$1' + $.param( obj )), - image = new Image(); - - image.src = url; - } - - return Uploader.register({ - name: 'log', - - init: function() { - var owner = this.owner, - count = 0, - size = 0; - - owner - .on('error', function(code) { - send({ - type: 2, - c_error_code: code - }); - }) - .on('uploadError', function(file, reason) { - send({ - type: 2, - c_error_code: 'UPLOAD_ERROR', - c_reason: '' + reason - }); - }) - .on('uploadComplete', function(file) { - count++; - size += file.size; - }). - on('uploadFinished', function() { - send({ - c_count: count, - c_size: size - }); - count = size = 0; - }); - - send({ - c_usage: 1 - }); - } - }); - }); - /** - * @fileOverview Uploader上传类 - */ - define('webuploader',[ - 'preset/all', - 'widgets/log' - ], function( preset ) { - return preset; - }); - return require('webuploader'); -}); diff --git a/static/plugins/webuploader/webuploader.min.js b/static/plugins/webuploader/webuploader.min.js deleted file mode 100644 index b7476bea..00000000 --- a/static/plugins/webuploader/webuploader.min.js +++ /dev/null @@ -1,3 +0,0 @@ -/* WebUploader 0.1.5 */!function(a,b){var c,d={},e=function(a,b){var c,d,e;if("string"==typeof a)return h(a);for(c=[],d=a.length,e=0;d>e;e++)c.push(h(a[e]));return b.apply(null,c)},f=function(a,b,c){2===arguments.length&&(c=b,b=null),e(b||[],function(){g(a,c,arguments)})},g=function(a,b,c){var f,g={exports:b};"function"==typeof b&&(c.length||(c=[e,g.exports,g]),f=b.apply(null,c),void 0!==f&&(g.exports=f)),d[a]=g.exports},h=function(b){var c=d[b]||a[b];if(!c)throw new Error("`"+b+"` is undefined");return c},i=function(a){var b,c,e,f,g,h;h=function(a){return a&&a.charAt(0).toUpperCase()+a.substr(1)};for(b in d)if(c=a,d.hasOwnProperty(b)){for(e=b.split("/"),g=h(e.pop());f=h(e.shift());)c[f]=c[f]||{},c=c[f];c[g]=d[b]}return a},j=function(c){return a.__dollar=c,i(b(a,f,e))};"object"==typeof module&&"object"==typeof module.exports?module.exports=j():"function"==typeof define&&define.amd?define(["jquery"],j):(c=a.WebUploader,a.WebUploader=j(),a.WebUploader.noConflict=function(){a.WebUploader=c})}(window,function(a,b,c){return b("dollar-third",[],function(){var b=a.__dollar||a.jQuery||a.Zepto;if(!b)throw new Error("jQuery or Zepto not found!");return b}),b("dollar",["dollar-third"],function(a){return a}),b("promise-third",["dollar"],function(a){return{Deferred:a.Deferred,when:a.when,isPromise:function(a){return a&&"function"==typeof a.then}}}),b("promise",["promise-third"],function(a){return a}),b("base",["dollar","promise"],function(b,c){function d(a){return function(){return h.apply(a,arguments)}}function e(a,b){return function(){return a.apply(b,arguments)}}function f(a){var b;return Object.create?Object.create(a):(b=function(){},b.prototype=a,new b)}var g=function(){},h=Function.call;return{version:"0.1.5",$:b,Deferred:c.Deferred,isPromise:c.isPromise,when:c.when,browser:function(a){var b={},c=a.match(/WebKit\/([\d.]+)/),d=a.match(/Chrome\/([\d.]+)/)||a.match(/CriOS\/([\d.]+)/),e=a.match(/MSIE\s([\d\.]+)/)||a.match(/(?:trident)(?:.*rv:([\w.]+))?/i),f=a.match(/Firefox\/([\d.]+)/),g=a.match(/Safari\/([\d.]+)/),h=a.match(/OPR\/([\d.]+)/);return c&&(b.webkit=parseFloat(c[1])),d&&(b.chrome=parseFloat(d[1])),e&&(b.ie=parseFloat(e[1])),f&&(b.firefox=parseFloat(f[1])),g&&(b.safari=parseFloat(g[1])),h&&(b.opera=parseFloat(h[1])),b}(navigator.userAgent),os:function(a){var b={},c=a.match(/(?:Android);?[\s\/]+([\d.]+)?/),d=a.match(/(?:iPad|iPod|iPhone).*OS\s([\d_]+)/);return c&&(b.android=parseFloat(c[1])),d&&(b.ios=parseFloat(d[1].replace(/_/g,"."))),b}(navigator.userAgent),inherits:function(a,c,d){var e;return"function"==typeof c?(e=c,c=null):e=c&&c.hasOwnProperty("constructor")?c.constructor:function(){return a.apply(this,arguments)},b.extend(!0,e,a,d||{}),e.__super__=a.prototype,e.prototype=f(a.prototype),c&&b.extend(!0,e.prototype,c),e},noop:g,bindFn:e,log:function(){return a.console?e(console.log,console):g}(),nextTick:function(){return function(a){setTimeout(a,1)}}(),slice:d([].slice),guid:function(){var a=0;return function(b){for(var c=(+new Date).toString(32),d=0;5>d;d++)c+=Math.floor(65535*Math.random()).toString(32);return(b||"wu_")+c+(a++).toString(32)}}(),formatSize:function(a,b,c){var d;for(c=c||["B","K","M","G","TB"];(d=c.shift())&&a>1024;)a/=1024;return("B"===d?a:a.toFixed(b||2))+d}}}),b("mediator",["base"],function(a){function b(a,b,c,d){return f.grep(a,function(a){return!(!a||b&&a.e!==b||c&&a.cb!==c&&a.cb._cb!==c||d&&a.ctx!==d)})}function c(a,b,c){f.each((a||"").split(h),function(a,d){c(d,b)})}function d(a,b){for(var c,d=!1,e=-1,f=a.length;++e1?void(d.isPlainObject(b)&&d.isPlainObject(c[a])?d.extend(c[a],b):c[a]=b):a?c[a]:c},getStats:function(){var a=this.request("get-stats");return a?{successNum:a.numOfSuccess,progressNum:a.numOfProgress,cancelNum:a.numOfCancel,invalidNum:a.numOfInvalid,uploadFailNum:a.numOfUploadFailed,queueNum:a.numOfQueue,interruptNum:a.numofInterrupt}:{}},trigger:function(a){var c=[].slice.call(arguments,1),e=this.options,f="on"+a.substring(0,1).toUpperCase()+a.substring(1);return b.trigger.apply(this,arguments)===!1||d.isFunction(e[f])&&e[f].apply(this,c)===!1||d.isFunction(this[f])&&this[f].apply(this,c)===!1||b.trigger.apply(b,[this,a].concat(c))===!1?!1:!0},destroy:function(){this.request("destroy",arguments),this.off()},request:a.noop}),a.create=c.create=function(a){return new c(a)},a.Uploader=c,c}),b("runtime/runtime",["base","mediator"],function(a,b){function c(b){this.options=d.extend({container:document.body},b),this.uid=a.guid("rt_")}var d=a.$,e={},f=function(a){for(var b in a)if(a.hasOwnProperty(b))return b;return null};return d.extend(c.prototype,{getContainer:function(){var a,b,c=this.options;return this._container?this._container:(a=d(c.container||document.body),b=d(document.createElement("div")),b.attr("id","rt_"+this.uid),b.css({position:"absolute",top:"0px",left:"0px",width:"1px",height:"1px",overflow:"hidden"}),a.append(b),a.addClass("webuploader-container"),this._container=b,this._parent=a,b)},init:a.noop,exec:a.noop,destroy:function(){this._container&&this._container.remove(),this._parent&&this._parent.removeClass("webuploader-container"),this.off()}}),c.orders="html5,flash",c.addRuntime=function(a,b){e[a]=b},c.hasRuntime=function(a){return!!(a?e[a]:f(e))},c.create=function(a,b){var g,h;if(b=b||c.orders,d.each(b.split(/\s*,\s*/g),function(){return e[this]?(g=this,!1):void 0}),g=g||f(e),!g)throw new Error("Runtime Error");return h=new e[g](a)},b.installTo(c.prototype),c}),b("runtime/client",["base","mediator","runtime/runtime"],function(a,b,c){function d(b,d){var f,g=a.Deferred();this.uid=a.guid("client_"),this.runtimeReady=function(a){return g.done(a)},this.connectRuntime=function(b,h){if(f)throw new Error("already connected!");return g.done(h),"string"==typeof b&&e.get(b)&&(f=e.get(b)),f=f||e.get(null,d),f?(a.$.extend(f.options,b),f.__promise.then(g.resolve),f.__client++):(f=c.create(b,b.runtimeOrder),f.__promise=g.promise(),f.once("ready",g.resolve),f.init(),e.add(f),f.__client=1),d&&(f.__standalone=d),f},this.getRuntime=function(){return f},this.disconnectRuntime=function(){f&&(f.__client--,f.__client<=0&&(e.remove(f),delete f.__promise,f.destroy()),f=null)},this.exec=function(){if(f){var c=a.slice(arguments);return b&&c.unshift(b),f.exec.apply(this,c)}},this.getRuid=function(){return f&&f.uid},this.destroy=function(a){return function(){a&&a.apply(this,arguments),this.trigger("destroy"),this.off(),this.exec("destroy"),this.disconnectRuntime()}}(this.destroy)}var e;return e=function(){var a={};return{add:function(b){a[b.uid]=b},get:function(b,c){var d;if(b)return a[b];for(d in a)if(!c||!a[d].__standalone)return a[d];return null},remove:function(b){delete a[b.uid]}}}(),b.installTo(d.prototype),d}),b("lib/dnd",["base","mediator","runtime/client"],function(a,b,c){function d(a){a=this.options=e.extend({},d.options,a),a.container=e(a.container),a.container.length&&c.call(this,"DragAndDrop")}var e=a.$;return d.options={accept:null,disableGlobalDnd:!1},a.inherits(c,{constructor:d,init:function(){var a=this;a.connectRuntime(a.options,function(){a.exec("init"),a.trigger("ready")})}}),b.installTo(d.prototype),d}),b("widgets/widget",["base","uploader"],function(a,b){function c(a){if(!a)return!1;var b=a.length,c=e.type(a);return 1===a.nodeType&&b?!0:"array"===c||"function"!==c&&"string"!==c&&(0===b||"number"==typeof b&&b>0&&b-1 in a)}function d(a){this.owner=a,this.options=a.options}var e=a.$,f=b.prototype._init,g=b.prototype.destroy,h={},i=[];return e.extend(d.prototype,{init:a.noop,invoke:function(a,b){var c=this.responseMap;return c&&a in c&&c[a]in this&&e.isFunction(this[c[a]])?this[c[a]].apply(this,b):h},request:function(){return this.owner.request.apply(this.owner,arguments)}}),e.extend(b.prototype,{_init:function(){var a=this,b=a._widgets=[],c=a.options.disableWidgets||"";return e.each(i,function(d,e){(!c||!~c.indexOf(e._name))&&b.push(new e(a))}),f.apply(a,arguments)},request:function(b,d,e){var f,g,i,j,k=0,l=this._widgets,m=l&&l.length,n=[],o=[];for(d=c(d)?d:[d];m>k;k++)f=l[k],g=f.invoke(b,d),g!==h&&(a.isPromise(g)?o.push(g):n.push(g));return e||o.length?(i=a.when.apply(a,o),j=i.pipe?"pipe":"then",i[j](function(){var b=a.Deferred(),c=arguments;return 1===c.length&&(c=c[0]),setTimeout(function(){b.resolve(c)},1),b.promise()})[e?j:"done"](e||a.noop)):n[0]},destroy:function(){g.apply(this,arguments),this._widgets=null}}),b.register=d.register=function(b,c){var f,g={init:"init",destroy:"destroy",name:"anonymous"};return 1===arguments.length?(c=b,e.each(c,function(a){return"_"===a[0]||"name"===a?void("name"===a&&(g.name=c.name)):void(g[a.replace(/[A-Z]/g,"-$&").toLowerCase()]=a)})):g=e.extend(g,b),c.responseMap=g,f=a.inherits(d,c),f._name=g.name,i.push(f),f},b.unRegister=d.unRegister=function(a){if(a&&"anonymous"!==a)for(var b=i.length;b--;)i[b]._name===a&&i.splice(b,1)},d}),b("widgets/filednd",["base","uploader","lib/dnd","widgets/widget"],function(a,b,c){var d=a.$;return b.options.dnd="",b.register({name:"dnd",init:function(b){if(b.dnd&&"html5"===this.request("predict-runtime-type")){var e,f=this,g=a.Deferred(),h=d.extend({},{disableGlobalDnd:b.disableGlobalDnd,container:b.dnd,accept:b.accept});return this.dnd=e=new c(h),e.once("ready",g.resolve),e.on("drop",function(a){f.request("add-file",[a])}),e.on("accept",function(a){return f.owner.trigger("dndAccept",a)}),e.init(),g.promise()}},destroy:function(){this.dnd&&this.dnd.destroy()}})}),b("lib/filepaste",["base","mediator","runtime/client"],function(a,b,c){function d(a){a=this.options=e.extend({},a),a.container=e(a.container||document.body),c.call(this,"FilePaste")}var e=a.$;return a.inherits(c,{constructor:d,init:function(){var a=this;a.connectRuntime(a.options,function(){a.exec("init"),a.trigger("ready")})}}),b.installTo(d.prototype),d}),b("widgets/filepaste",["base","uploader","lib/filepaste","widgets/widget"],function(a,b,c){var d=a.$;return b.register({name:"paste",init:function(b){if(b.paste&&"html5"===this.request("predict-runtime-type")){var e,f=this,g=a.Deferred(),h=d.extend({},{container:b.paste,accept:b.accept});return this.paste=e=new c(h),e.once("ready",g.resolve),e.on("paste",function(a){f.owner.request("add-file",[a])}),e.init(),g.promise()}},destroy:function(){this.paste&&this.paste.destroy()}})}),b("lib/blob",["base","runtime/client"],function(a,b){function c(a,c){var d=this;d.source=c,d.ruid=a,this.size=c.size||0,this.type=!c.type&&this.ext&&~"jpg,jpeg,png,gif,bmp".indexOf(this.ext)?"image/"+("jpg"===this.ext?"jpeg":this.ext):c.type||"application/octet-stream",b.call(d,"Blob"),this.uid=c.uid||this.uid,a&&d.connectRuntime(a)}return a.inherits(b,{constructor:c,slice:function(a,b){return this.exec("slice",a,b)},getSource:function(){return this.source}}),c}),b("lib/file",["base","lib/blob"],function(a,b){function c(a,c){var f;this.name=c.name||"untitled"+d++,f=e.exec(c.name)?RegExp.$1.toLowerCase():"",!f&&c.type&&(f=/\/(jpg|jpeg|png|gif|bmp)$/i.exec(c.type)?RegExp.$1.toLowerCase():"",this.name+="."+f),this.ext=f,this.lastModifiedDate=c.lastModifiedDate||(new Date).toLocaleString(),b.apply(this,arguments)}var d=1,e=/\.([^.]+)$/;return a.inherits(b,c)}),b("lib/filepicker",["base","runtime/client","lib/file"],function(b,c,d){function e(a){if(a=this.options=f.extend({},e.options,a),a.container=f(a.id),!a.container.length)throw new Error("按钮指定错误");a.innerHTML=a.innerHTML||a.label||a.container.html()||"",a.button=f(a.button||document.createElement("div")),a.button.html(a.innerHTML),a.container.html(a.button),c.call(this,"FilePicker",!0)}var f=b.$;return e.options={button:null,container:null,label:null,innerHTML:null,multiple:!0,accept:null,name:"file"},b.inherits(c,{constructor:e,init:function(){var c=this,e=c.options,g=e.button;g.addClass("webuploader-pick"),c.on("all",function(a){var b;switch(a){case"mouseenter":g.addClass("webuploader-pick-hover");break;case"mouseleave":g.removeClass("webuploader-pick-hover");break;case"change":b=c.exec("getFiles"),c.trigger("select",f.map(b,function(a){return a=new d(c.getRuid(),a),a._refer=e.container,a}),e.container)}}),c.connectRuntime(e,function(){c.refresh(),c.exec("init",e),c.trigger("ready")}),this._resizeHandler=b.bindFn(this.refresh,this),f(a).on("resize",this._resizeHandler)},refresh:function(){var a=this.getRuntime().getContainer(),b=this.options.button,c=b.outerWidth?b.outerWidth():b.width(),d=b.outerHeight?b.outerHeight():b.height(),e=b.offset();c&&d&&a.css({bottom:"auto",right:"auto",width:c+"px",height:d+"px"}).offset(e)},enable:function(){var a=this.options.button;a.removeClass("webuploader-pick-disable"),this.refresh()},disable:function(){var a=this.options.button;this.getRuntime().getContainer().css({top:"-99999px"}),a.addClass("webuploader-pick-disable")},destroy:function(){var b=this.options.button;f(a).off("resize",this._resizeHandler),b.removeClass("webuploader-pick-disable webuploader-pick-hover webuploader-pick")}}),e}),b("widgets/filepicker",["base","uploader","lib/filepicker","widgets/widget"],function(a,b,c){var d=a.$;return d.extend(b.options,{pick:null,accept:null}),b.register({name:"picker",init:function(a){return this.pickers=[],a.pick&&this.addBtn(a.pick)},refresh:function(){d.each(this.pickers,function(){this.refresh()})},addBtn:function(b){var e=this,f=e.options,g=f.accept,h=[];if(b)return d.isPlainObject(b)||(b={id:b}),d(b.id).each(function(){var i,j,k;k=a.Deferred(),i=d.extend({},b,{accept:d.isPlainObject(g)?[g]:g,swf:f.swf,runtimeOrder:f.runtimeOrder,id:this}),j=new c(i),j.once("ready",k.resolve),j.on("select",function(a){e.owner.request("add-file",[a])}),j.init(),e.pickers.push(j),h.push(k.promise())}),a.when.apply(a,h)},disable:function(){d.each(this.pickers,function(){this.disable()})},enable:function(){d.each(this.pickers,function(){this.enable()})},destroy:function(){d.each(this.pickers,function(){this.destroy()}),this.pickers=null}})}),b("lib/image",["base","runtime/client","lib/blob"],function(a,b,c){function d(a){this.options=e.extend({},d.options,a),b.call(this,"Image"),this.on("load",function(){this._info=this.exec("info"),this._meta=this.exec("meta")})}var e=a.$;return d.options={quality:90,crop:!1,preserveHeaders:!1,allowMagnify:!1},a.inherits(b,{constructor:d,info:function(a){return a?(this._info=a,this):this._info},meta:function(a){return a?(this._meta=a,this):this._meta},loadFromBlob:function(a){var b=this,c=a.getRuid();this.connectRuntime(c,function(){b.exec("init",b.options),b.exec("loadFromBlob",a)})},resize:function(){var b=a.slice(arguments);return this.exec.apply(this,["resize"].concat(b))},crop:function(){var b=a.slice(arguments);return this.exec.apply(this,["crop"].concat(b))},getAsDataUrl:function(a){return this.exec("getAsDataUrl",a)},getAsBlob:function(a){var b=this.exec("getAsBlob",a);return new c(this.getRuid(),b)}}),d}),b("widgets/image",["base","uploader","lib/image","widgets/widget"],function(a,b,c){var d,e=a.$;return d=function(a){var b=0,c=[],d=function(){for(var d;c.length&&a>b;)d=c.shift(),b+=d[0],d[1]()};return function(a,e,f){c.push([e,f]),a.once("destroy",function(){b-=e,setTimeout(d,1)}),setTimeout(d,1)}}(5242880),e.extend(b.options,{thumb:{width:110,height:110,quality:70,allowMagnify:!0,crop:!0,preserveHeaders:!1,type:"image/jpeg"},compress:{width:1600,height:1600,quality:90,allowMagnify:!1,crop:!1,preserveHeaders:!0}}),b.register({name:"image",makeThumb:function(a,b,f,g){var h,i;return a=this.request("get-file",a),a.type.match(/^image/)?(h=e.extend({},this.options.thumb),e.isPlainObject(f)&&(h=e.extend(h,f),f=null),f=f||h.width,g=g||h.height,i=new c(h),i.once("load",function(){a._info=a._info||i.info(),a._meta=a._meta||i.meta(),1>=f&&f>0&&(f=a._info.width*f),1>=g&&g>0&&(g=a._info.height*g),i.resize(f,g)}),i.once("complete",function(){b(!1,i.getAsDataUrl(h.type)),i.destroy()}),i.once("error",function(a){b(a||!0),i.destroy()}),void d(i,a.source.size,function(){a._info&&i.info(a._info),a._meta&&i.meta(a._meta),i.loadFromBlob(a.source)})):void b(!0)},beforeSendFile:function(b){var d,f,g=this.options.compress||this.options.resize,h=g&&g.compressSize||0,i=g&&g.noCompressIfLarger||!1;return b=this.request("get-file",b),!g||!~"image/jpeg,image/jpg".indexOf(b.type)||b.size=a&&a>0&&(a=b._info.width*a),1>=c&&c>0&&(c=b._info.height*c),d.resize(a,c)}),d.once("complete",function(){var a,c;try{a=d.getAsBlob(g.type),c=b.size,(!i||a.sizeb;b++)if(c=this._queue[b],a===c.getStatus())return c;return null},sort:function(a){"function"==typeof a&&this._queue.sort(a)},getFiles:function(){for(var a,b=[].slice.call(arguments,0),c=[],d=0,f=this._queue.length;f>d;d++)a=this._queue[d],(!b.length||~e.inArray(a.getStatus(),b))&&c.push(a);return c},removeFile:function(a){var b=this._map[a.id];b&&(delete this._map[a.id],a.destroy(),this.stats.numofDeleted++)},_fileAdded:function(a){var b=this,c=this._map[a.id];c||(this._map[a.id]=a,a.on("statuschange",function(a,c){b._onFileStatusChange(a,c)}))},_onFileStatusChange:function(a,b){var c=this.stats;switch(b){case f.PROGRESS:c.numOfProgress--;break;case f.QUEUED:c.numOfQueue--;break;case f.ERROR:c.numOfUploadFailed--;break;case f.INVALID:c.numOfInvalid--;break;case f.INTERRUPT:c.numofInterrupt--}switch(a){case f.QUEUED:c.numOfQueue++;break;case f.PROGRESS:c.numOfProgress++;break;case f.ERROR:c.numOfUploadFailed++;break;case f.COMPLETE:c.numOfSuccess++;break;case f.CANCELLED:c.numOfCancel++;break;case f.INVALID:c.numOfInvalid++;break;case f.INTERRUPT:c.numofInterrupt++}}}),b.installTo(d.prototype),d}),b("widgets/queue",["base","uploader","queue","file","lib/file","runtime/client","widgets/widget"],function(a,b,c,d,e,f){var g=a.$,h=/\.\w+$/,i=d.Status;return b.register({name:"queue",init:function(b){var d,e,h,i,j,k,l,m=this;if(g.isPlainObject(b.accept)&&(b.accept=[b.accept]),b.accept){for(j=[],h=0,e=b.accept.length;e>h;h++)i=b.accept[h].extensions,i&&j.push(i);j.length&&(k="\\."+j.join(",").replace(/,/g,"$|\\.").replace(/\*/g,".*")+"$"),m.accept=new RegExp(k,"i")}return m.queue=new c,m.stats=m.queue.stats,"html5"===this.request("predict-runtime-type")?(d=a.Deferred(),this.placeholder=l=new f("Placeholder"),l.connectRuntime({runtimeOrder:"html5"},function(){m._ruid=l.getRuid(),d.resolve()}),d.promise()):void 0},_wrapFile:function(a){if(!(a instanceof d)){if(!(a instanceof e)){if(!this._ruid)throw new Error("Can't add external files.");a=new e(this._ruid,a)}a=new d(a)}return a},acceptFile:function(a){var b=!a||!a.size||this.accept&&h.exec(a.name)&&!this.accept.test(a.name);return!b},_addFile:function(a){var b=this;return a=b._wrapFile(a),b.owner.trigger("beforeFileQueued",a)?b.acceptFile(a)?(b.queue.append(a),b.owner.trigger("fileQueued",a),a):void b.owner.trigger("error","Q_TYPE_DENIED",a):void 0},getFile:function(a){return this.queue.getFile(a)},addFile:function(a){var b=this;a.length||(a=[a]),a=g.map(a,function(a){return b._addFile(a)}),b.owner.trigger("filesQueued",a),b.options.auto&&setTimeout(function(){b.request("start-upload")},20)},getStats:function(){return this.stats},removeFile:function(a,b){var c=this;a=a.id?a:c.queue.getFile(a),this.request("cancel-file",a),b&&this.queue.removeFile(a)},getFiles:function(){return this.queue.getFiles.apply(this.queue,arguments)},fetchFile:function(){return this.queue.fetch.apply(this.queue,arguments)},retry:function(a,b){var c,d,e,f=this;if(a)return a=a.id?a:f.queue.getFile(a),a.setStatus(i.QUEUED),void(b||f.request("start-upload"));for(c=f.queue.getFiles(i.ERROR),d=0,e=c.length;e>d;d++)a=c[d],a.setStatus(i.QUEUED);f.request("start-upload")},sortFiles:function(){return this.queue.sort.apply(this.queue,arguments)},reset:function(){this.owner.trigger("reset"),this.queue=new c,this.stats=this.queue.stats},destroy:function(){this.reset(),this.placeholder&&this.placeholder.destroy()}})}),b("widgets/runtime",["uploader","runtime/runtime","widgets/widget"],function(a,b){return a.support=function(){return b.hasRuntime.apply(b,arguments)},a.register({name:"runtime",init:function(){if(!this.predictRuntimeType())throw Error("Runtime Error")},predictRuntimeType:function(){var a,c,d=this.options.runtimeOrder||b.orders,e=this.type;if(!e)for(d=d.split(/\s*,\s*/g),a=0,c=d.length;c>a;a++)if(b.hasRuntime(d[a])){this.type=e=d[a];break}return e}})}),b("lib/transport",["base","runtime/client","mediator"],function(a,b,c){function d(a){var c=this;a=c.options=e.extend(!0,{},d.options,a||{}),b.call(this,"Transport"),this._blob=null,this._formData=a.formData||{},this._headers=a.headers||{},this.on("progress",this._timeout),this.on("load error",function(){c.trigger("progress",1),clearTimeout(c._timer)})}var e=a.$;return d.options={server:"",method:"POST",withCredentials:!1,fileVal:"file",timeout:12e4,formData:{},headers:{},sendAsBinary:!1},e.extend(d.prototype,{appendBlob:function(a,b,c){var d=this,e=d.options;d.getRuid()&&d.disconnectRuntime(),d.connectRuntime(b.ruid,function(){d.exec("init")}),d._blob=b,e.fileVal=a||e.fileVal,e.filename=c||e.filename},append:function(a,b){"object"==typeof a?e.extend(this._formData,a):this._formData[a]=b},setRequestHeader:function(a,b){"object"==typeof a?e.extend(this._headers,a):this._headers[a]=b},send:function(a){this.exec("send",a),this._timeout()},abort:function(){return clearTimeout(this._timer),this.exec("abort")},destroy:function(){this.trigger("destroy"),this.off(),this.exec("destroy"),this.disconnectRuntime()},getResponse:function(){return this.exec("getResponse")},getResponseAsJson:function(){return this.exec("getResponseAsJson")},getStatus:function(){return this.exec("getStatus")},_timeout:function(){var a=this,b=a.options.timeout;b&&(clearTimeout(a._timer),a._timer=setTimeout(function(){a.abort(),a.trigger("error","timeout")},b))}}),c.installTo(d.prototype),d}),b("widgets/upload",["base","uploader","file","lib/transport","widgets/widget"],function(a,b,c,d){function e(a,b){var c,d,e=[],f=a.source,g=f.size,h=b?Math.ceil(g/b):1,i=0,j=0;for(d={file:a,has:function(){return!!e.length},shift:function(){return e.shift()},unshift:function(a){e.unshift(a)}};h>j;)c=Math.min(b,g-i),e.push({file:a,start:i,end:b?i+c:g,total:g,chunks:h,chunk:j++,cuted:d}),i+=c;return a.blocks=e.concat(),a.remaning=e.length,d}var f=a.$,g=a.isPromise,h=c.Status;f.extend(b.options,{prepareNextFile:!1,chunked:!1,chunkSize:5242880,chunkRetry:2,threads:3,formData:{}}),b.register({name:"upload",init:function(){var b=this.owner,c=this;this.runing=!1,this.progress=!1,b.on("startUpload",function(){c.progress=!0}).on("uploadFinished",function(){c.progress=!1}),this.pool=[],this.stack=[],this.pending=[],this.remaning=0,this.__tick=a.bindFn(this._tick,this),b.on("uploadComplete",function(a){a.blocks&&f.each(a.blocks,function(a,b){b.transport&&(b.transport.abort(),b.transport.destroy()),delete b.transport}),delete a.blocks,delete a.remaning})},reset:function(){this.request("stop-upload",!0),this.runing=!1,this.pool=[],this.stack=[],this.pending=[],this.remaning=0,this._trigged=!1,this._promise=null},startUpload:function(b){var c=this;if(f.each(c.request("get-files",h.INVALID),function(){c.request("remove-file",this)}),b)if(b=b.id?b:c.request("get-file",b),b.getStatus()===h.INTERRUPT)f.each(c.pool,function(a,c){c.file===b&&c.transport&&c.transport.send()}),b.setStatus(h.QUEUED);else{if(b.getStatus()===h.PROGRESS)return;b.setStatus(h.QUEUED)}else f.each(c.request("get-files",[h.INITED]),function(){this.setStatus(h.QUEUED)});if(!c.runing){c.runing=!0;var d=[];f.each(c.pool,function(a,b){var e=b.file;e.getStatus()===h.INTERRUPT&&(d.push(e),c._trigged=!1,b.transport&&b.transport.send())});for(var b;b=d.shift();)b.setStatus(h.PROGRESS);b||f.each(c.request("get-files",h.INTERRUPT),function(){this.setStatus(h.PROGRESS)}),c._trigged=!1,a.nextTick(c.__tick),c.owner.trigger("startUpload")}},stopUpload:function(b,c){var d=this;if(b===!0&&(c=b,b=null),d.runing!==!1){if(b){if(b=b.id?b:d.request("get-file",b),b.getStatus()!==h.PROGRESS&&b.getStatus()!==h.QUEUED)return;return b.setStatus(h.INTERRUPT),f.each(d.pool,function(a,c){c.file===b&&(c.transport&&c.transport.abort(),d._putback(c),d._popBlock(c))}),a.nextTick(d.__tick)}d.runing=!1,this._promise&&this._promise.file&&this._promise.file.setStatus(h.INTERRUPT),c&&f.each(d.pool,function(a,b){b.transport&&b.transport.abort(),b.file.setStatus(h.INTERRUPT)}),d.owner.trigger("stopUpload")}},cancelFile:function(a){a=a.id?a:this.request("get-file",a),a.blocks&&f.each(a.blocks,function(a,b){var c=b.transport;c&&(c.abort(),c.destroy(),delete b.transport)}),a.setStatus(h.CANCELLED),this.owner.trigger("fileDequeued",a)},isInProgress:function(){return!!this.progress},_getStats:function(){return this.request("get-stats")},skipFile:function(a,b){a=a.id?a:this.request("get-file",a),a.setStatus(b||h.COMPLETE),a.skipped=!0,a.blocks&&f.each(a.blocks,function(a,b){var c=b.transport;c&&(c.abort(),c.destroy(),delete b.transport)}),this.owner.trigger("uploadSkip",a)},_tick:function(){var b,c,d=this,e=d.options;return d._promise?d._promise.always(d.__tick):void(d.pool.length1&&~"http,abort".indexOf(a)&&b.retried1&&f.extend(m,{chunks:b.chunks,chunk:b.chunk}),i.trigger("uploadBeforeSend",b,m,n),l.appendBlob(j.fileVal,b.blob,k.name),l.append(m),l.setRequestHeader(n),l.send()},_finishFile:function(a,b,c){var d=this.owner;return d.request("after-send-file",arguments,function(){a.setStatus(h.COMPLETE),d.trigger("uploadSuccess",a,b,c)}).fail(function(b){a.getStatus()===h.PROGRESS&&a.setStatus(h.ERROR,b),d.trigger("uploadError",a,b) -}).always(function(){d.trigger("uploadComplete",a)})},updateFileProgress:function(a){var b=0,c=0;a.blocks&&(f.each(a.blocks,function(a,b){c+=(b.percentage||0)*(b.end-b.start)}),b=c/a.size,this.owner.trigger("uploadProgress",a,b||0))}})}),b("widgets/validator",["base","uploader","file","widgets/widget"],function(a,b,c){var d,e=a.$,f={};return d={addValidator:function(a,b){f[a]=b},removeValidator:function(a){delete f[a]}},b.register({name:"validator",init:function(){var b=this;a.nextTick(function(){e.each(f,function(){this.call(b.owner)})})}}),d.addValidator("fileNumLimit",function(){var a=this,b=a.options,c=0,d=parseInt(b.fileNumLimit,10),e=!0;d&&(a.on("beforeFileQueued",function(a){return c>=d&&e&&(e=!1,this.trigger("error","Q_EXCEED_NUM_LIMIT",d,a),setTimeout(function(){e=!0},1)),c>=d?!1:!0}),a.on("fileQueued",function(){c++}),a.on("fileDequeued",function(){c--}),a.on("reset",function(){c=0}))}),d.addValidator("fileSizeLimit",function(){var a=this,b=a.options,c=0,d=parseInt(b.fileSizeLimit,10),e=!0;d&&(a.on("beforeFileQueued",function(a){var b=c+a.size>d;return b&&e&&(e=!1,this.trigger("error","Q_EXCEED_SIZE_LIMIT",d,a),setTimeout(function(){e=!0},1)),b?!1:!0}),a.on("fileQueued",function(a){c+=a.size}),a.on("fileDequeued",function(a){c-=a.size}),a.on("reset",function(){c=0}))}),d.addValidator("fileSingleSizeLimit",function(){var a=this,b=a.options,d=b.fileSingleSizeLimit;d&&a.on("beforeFileQueued",function(a){return a.size>d?(a.setStatus(c.Status.INVALID,"exceed_size"),this.trigger("error","F_EXCEED_SIZE",d,a),!1):void 0})}),d.addValidator("duplicate",function(){function a(a){for(var b,c=0,d=0,e=a.length;e>d;d++)b=a.charCodeAt(d),c=b+(c<<6)+(c<<16)-c;return c}var b=this,c=b.options,d={};c.duplicate||(b.on("beforeFileQueued",function(b){var c=b.__hash||(b.__hash=a(b.name+b.size+b.lastModifiedDate));return d[c]?(this.trigger("error","F_DUPLICATE",b),!1):void 0}),b.on("fileQueued",function(a){var b=a.__hash;b&&(d[b]=!0)}),b.on("fileDequeued",function(a){var b=a.__hash;b&&delete d[b]}),b.on("reset",function(){d={}}))}),d}),b("lib/md5",["runtime/client","mediator"],function(a,b){function c(){a.call(this,"Md5")}return b.installTo(c.prototype),c.prototype.loadFromBlob=function(a){var b=this;b.getRuid()&&b.disconnectRuntime(),b.connectRuntime(a.ruid,function(){b.exec("init"),b.exec("loadFromBlob",a)})},c.prototype.getResult=function(){return this.exec("getResult")},c}),b("widgets/md5",["base","uploader","lib/md5","lib/blob","widgets/widget"],function(a,b,c,d){return b.register({name:"md5",md5File:function(b,e,f){var g=new c,h=a.Deferred(),i=b instanceof d?b:this.request("get-file",b).source;return g.on("progress load",function(a){a=a||{},h.notify(a.total?a.loaded/a.total:1)}),g.on("complete",function(){h.resolve(g.getResult())}),g.on("error",function(a){h.reject(a)}),arguments.length>1&&(e=e||0,f=f||0,0>e&&(e=i.size+e),0>f&&(f=i.size+f),f=Math.min(f,i.size),i=i.slice(e,f)),g.loadFromBlob(i),h.promise()}})}),b("runtime/compbase",[],function(){function a(a,b){this.owner=a,this.options=a.options,this.getRuntime=function(){return b},this.getRuid=function(){return b.uid},this.trigger=function(){return a.trigger.apply(a,arguments)}}return a}),b("runtime/html5/runtime",["base","runtime/runtime","runtime/compbase"],function(b,c,d){function e(){var a={},d=this,e=this.destroy;c.apply(d,arguments),d.type=f,d.exec=function(c,e){var f,h=this,i=h.uid,j=b.slice(arguments,2);return g[c]&&(f=a[i]=a[i]||new g[c](h,d),f[e])?f[e].apply(f,j):void 0},d.destroy=function(){return e&&e.apply(this,arguments)}}var f="html5",g={};return b.inherits(c,{constructor:e,init:function(){var a=this;setTimeout(function(){a.trigger("ready")},1)}}),e.register=function(a,c){var e=g[a]=b.inherits(d,c);return e},a.Blob&&a.FileReader&&a.DataView&&c.addRuntime(f,e),e}),b("runtime/html5/blob",["runtime/html5/runtime","lib/blob"],function(a,b){return a.register("Blob",{slice:function(a,c){var d=this.owner.source,e=d.slice||d.webkitSlice||d.mozSlice;return d=e.call(d,a,c),new b(this.getRuid(),d)}})}),b("runtime/html5/dnd",["base","runtime/html5/runtime","lib/file"],function(a,b,c){var d=a.$,e="webuploader-dnd-";return b.register("DragAndDrop",{init:function(){var b=this.elem=this.options.container;this.dragEnterHandler=a.bindFn(this._dragEnterHandler,this),this.dragOverHandler=a.bindFn(this._dragOverHandler,this),this.dragLeaveHandler=a.bindFn(this._dragLeaveHandler,this),this.dropHandler=a.bindFn(this._dropHandler,this),this.dndOver=!1,b.on("dragenter",this.dragEnterHandler),b.on("dragover",this.dragOverHandler),b.on("dragleave",this.dragLeaveHandler),b.on("drop",this.dropHandler),this.options.disableGlobalDnd&&(d(document).on("dragover",this.dragOverHandler),d(document).on("drop",this.dropHandler))},_dragEnterHandler:function(a){var b,c=this,d=c._denied||!1;return a=a.originalEvent||a,c.dndOver||(c.dndOver=!0,b=a.dataTransfer.items,b&&b.length&&(c._denied=d=!c.trigger("accept",b)),c.elem.addClass(e+"over"),c.elem[d?"addClass":"removeClass"](e+"denied")),a.dataTransfer.dropEffect=d?"none":"copy",!1},_dragOverHandler:function(a){var b=this.elem.parent().get(0);return b&&!d.contains(b,a.currentTarget)?!1:(clearTimeout(this._leaveTimer),this._dragEnterHandler.call(this,a),!1)},_dragLeaveHandler:function(){var a,b=this;return a=function(){b.dndOver=!1,b.elem.removeClass(e+"over "+e+"denied")},clearTimeout(b._leaveTimer),b._leaveTimer=setTimeout(a,100),!1},_dropHandler:function(a){var b,f,g=this,h=g.getRuid(),i=g.elem.parent().get(0);if(i&&!d.contains(i,a.currentTarget))return!1;a=a.originalEvent||a,b=a.dataTransfer;try{f=b.getData("text/html")}catch(j){}return f?void 0:(g._getTansferFiles(b,function(a){g.trigger("drop",d.map(a,function(a){return new c(h,a)}))}),g.dndOver=!1,g.elem.removeClass(e+"over"),!1)},_getTansferFiles:function(b,c){var d,e,f,g,h,i,j,k=[],l=[];for(d=b.items,e=b.files,j=!(!d||!d[0].webkitGetAsEntry),h=0,i=e.length;i>h;h++)f=e[h],g=d&&d[h],j&&g.webkitGetAsEntry().isDirectory?l.push(this._traverseDirectoryTree(g.webkitGetAsEntry(),k)):k.push(f);a.when.apply(a,l).done(function(){k.length&&c(k)})},_traverseDirectoryTree:function(b,c){var d=a.Deferred(),e=this;return b.isFile?b.file(function(a){c.push(a),d.resolve()}):b.isDirectory&&b.createReader().readEntries(function(b){var f,g=b.length,h=[],i=[];for(f=0;g>f;f++)h.push(e._traverseDirectoryTree(b[f],i));a.when.apply(a,h).then(function(){c.push.apply(c,i),d.resolve()},d.reject)}),d.promise()},destroy:function(){var a=this.elem;a&&(a.off("dragenter",this.dragEnterHandler),a.off("dragover",this.dragOverHandler),a.off("dragleave",this.dragLeaveHandler),a.off("drop",this.dropHandler),this.options.disableGlobalDnd&&(d(document).off("dragover",this.dragOverHandler),d(document).off("drop",this.dropHandler)))}})}),b("runtime/html5/filepaste",["base","runtime/html5/runtime","lib/file"],function(a,b,c){return b.register("FilePaste",{init:function(){var b,c,d,e,f=this.options,g=this.elem=f.container,h=".*";if(f.accept){for(b=[],c=0,d=f.accept.length;d>c;c++)e=f.accept[c].mimeTypes,e&&b.push(e);b.length&&(h=b.join(","),h=h.replace(/,/g,"|").replace(/\*/g,".*"))}this.accept=h=new RegExp(h,"i"),this.hander=a.bindFn(this._pasteHander,this),g.on("paste",this.hander)},_pasteHander:function(a){var b,d,e,f,g,h=[],i=this.getRuid();for(a=a.originalEvent||a,b=a.clipboardData.items,f=0,g=b.length;g>f;f++)d=b[f],"file"===d.kind&&(e=d.getAsFile())&&h.push(new c(i,e));h.length&&(a.preventDefault(),a.stopPropagation(),this.trigger("paste",h))},destroy:function(){this.elem.off("paste",this.hander)}})}),b("runtime/html5/filepicker",["base","runtime/html5/runtime"],function(a,b){var c=a.$;return b.register("FilePicker",{init:function(){var a,b,d,e,f=this.getRuntime().getContainer(),g=this,h=g.owner,i=g.options,j=this.label=c(document.createElement("label")),k=this.input=c(document.createElement("input"));if(k.attr("type","file"),k.attr("name",i.name),k.addClass("webuploader-element-invisible"),j.on("click",function(){k.trigger("click")}),j.css({opacity:0,width:"100%",height:"100%",display:"block",cursor:"pointer",background:"#ffffff"}),i.multiple&&k.attr("multiple","multiple"),i.accept&&i.accept.length>0){for(a=[],b=0,d=i.accept.length;d>b;b++)a.push(i.accept[b].mimeTypes);k.attr("accept",a.join(","))}f.append(k),f.append(j),e=function(a){h.trigger(a.type)},k.on("change",function(a){var b,d=arguments.callee;g.files=a.target.files,b=this.cloneNode(!0),b.value=null,this.parentNode.replaceChild(b,this),k.off(),k=c(b).on("change",d).on("mouseenter mouseleave",e),h.trigger("change")}),j.on("mouseenter mouseleave",e)},getFiles:function(){return this.files},destroy:function(){this.input.off(),this.label.off()}})}),b("runtime/html5/util",["base"],function(b){var c=a.createObjectURL&&a||a.URL&&URL.revokeObjectURL&&URL||a.webkitURL,d=b.noop,e=d;return c&&(d=function(){return c.createObjectURL.apply(c,arguments)},e=function(){return c.revokeObjectURL.apply(c,arguments)}),{createObjectURL:d,revokeObjectURL:e,dataURL2Blob:function(a){var b,c,d,e,f,g;for(g=a.split(","),b=~g[0].indexOf("base64")?atob(g[1]):decodeURIComponent(g[1]),d=new ArrayBuffer(b.length),c=new Uint8Array(d),e=0;ei&&(d=h.getUint16(i),d>=65504&&65519>=d||65534===d)&&(e=h.getUint16(i+2)+2,!(i+e>h.byteLength));){if(f=b.parsers[d],!c&&f)for(g=0;g6&&(l.imageHead=a.slice?a.slice(2,k):new Uint8Array(a).subarray(2,k))}return l}},updateImageHead:function(a,b){var c,d,e,f=this._parse(a,!0);return e=2,f.imageHead&&(e=2+f.imageHead.byteLength),d=a.slice?a.slice(e):new Uint8Array(a).subarray(e),c=new Uint8Array(b.byteLength+2+d.byteLength),c[0]=255,c[1]=216,c.set(new Uint8Array(b),2),c.set(new Uint8Array(d),b.byteLength+2),c.buffer}},a.parseMeta=function(){return b.parse.apply(b,arguments)},a.updateImageHead=function(){return b.updateImageHead.apply(b,arguments)},b}),b("runtime/html5/imagemeta/exif",["base","runtime/html5/imagemeta"],function(a,b){var c={};return c.ExifMap=function(){return this},c.ExifMap.prototype.map={Orientation:274},c.ExifMap.prototype.get=function(a){return this[a]||this[this.map[a]]},c.exifTagTypes={1:{getValue:function(a,b){return a.getUint8(b)},size:1},2:{getValue:function(a,b){return String.fromCharCode(a.getUint8(b))},size:1,ascii:!0},3:{getValue:function(a,b,c){return a.getUint16(b,c)},size:2},4:{getValue:function(a,b,c){return a.getUint32(b,c)},size:4},5:{getValue:function(a,b,c){return a.getUint32(b,c)/a.getUint32(b+4,c)},size:8},9:{getValue:function(a,b,c){return a.getInt32(b,c)},size:4},10:{getValue:function(a,b,c){return a.getInt32(b,c)/a.getInt32(b+4,c)},size:8}},c.exifTagTypes[7]=c.exifTagTypes[1],c.getExifValue=function(b,d,e,f,g,h){var i,j,k,l,m,n,o=c.exifTagTypes[f];if(!o)return void a.log("Invalid Exif data: Invalid tag type.");if(i=o.size*g,j=i>4?d+b.getUint32(e+8,h):e+8,j+i>b.byteLength)return void a.log("Invalid Exif data: Invalid data offset.");if(1===g)return o.getValue(b,j,h);for(k=[],l=0;g>l;l+=1)k[l]=o.getValue(b,j+l*o.size,h);if(o.ascii){for(m="",l=0;lb.byteLength)return void a.log("Invalid Exif data: Invalid directory offset.");if(g=b.getUint16(d,e),h=d+2+12*g,h+4>b.byteLength)return void a.log("Invalid Exif data: Invalid directory size.");for(i=0;g>i;i+=1)this.parseExifTag(b,c,d+2+12*i,e,f);return b.getUint32(h,e)},c.parseExifData=function(b,d,e,f){var g,h,i=d+10;if(1165519206===b.getUint32(d+4)){if(i+8>b.byteLength)return void a.log("Invalid Exif data: Invalid segment size.");if(0!==b.getUint16(d+8))return void a.log("Invalid Exif data: Missing byte alignment offset.");switch(b.getUint16(i)){case 18761:g=!0;break;case 19789:g=!1;break;default:return void a.log("Invalid Exif data: Invalid byte alignment marker.")}if(42!==b.getUint16(i+2,g))return void a.log("Invalid Exif data: Missing TIFF marker.");h=b.getUint32(i+4,g),f.exif=new c.ExifMap,h=c.parseExifTags(b,i,i+h,g,f)}},b.parsers[65505].push(c.parseExifData),c}),b("runtime/html5/jpegencoder",[],function(){function a(a){function b(a){for(var b=[16,11,10,16,24,40,51,61,12,12,14,19,26,58,60,55,14,13,16,24,40,57,69,56,14,17,22,29,51,87,80,62,18,22,37,56,68,109,103,77,24,35,55,64,81,104,113,92,49,64,78,87,103,121,120,101,72,92,95,98,112,100,103,99],c=0;64>c;c++){var d=y((b[c]*a+50)/100);1>d?d=1:d>255&&(d=255),z[P[c]]=d}for(var e=[17,18,24,47,99,99,99,99,18,21,26,66,99,99,99,99,24,26,56,99,99,99,99,99,47,66,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99],f=0;64>f;f++){var g=y((e[f]*a+50)/100);1>g?g=1:g>255&&(g=255),A[P[f]]=g}for(var h=[1,1.387039845,1.306562965,1.175875602,1,.785694958,.5411961,.275899379],i=0,j=0;8>j;j++)for(var k=0;8>k;k++)B[i]=1/(z[P[i]]*h[j]*h[k]*8),C[i]=1/(A[P[i]]*h[j]*h[k]*8),i++}function c(a,b){for(var c=0,d=0,e=new Array,f=1;16>=f;f++){for(var g=1;g<=a[f];g++)e[b[d]]=[],e[b[d]][0]=c,e[b[d]][1]=f,d++,c++;c*=2}return e}function d(){t=c(Q,R),u=c(U,V),v=c(S,T),w=c(W,X)}function e(){for(var a=1,b=2,c=1;15>=c;c++){for(var d=a;b>d;d++)E[32767+d]=c,D[32767+d]=[],D[32767+d][1]=c,D[32767+d][0]=d;for(var e=-(b-1);-a>=e;e++)E[32767+e]=c,D[32767+e]=[],D[32767+e][1]=c,D[32767+e][0]=b-1+e;a<<=1,b<<=1}}function f(){for(var a=0;256>a;a++)O[a]=19595*a,O[a+256>>0]=38470*a,O[a+512>>0]=7471*a+32768,O[a+768>>0]=-11059*a,O[a+1024>>0]=-21709*a,O[a+1280>>0]=32768*a+8421375,O[a+1536>>0]=-27439*a,O[a+1792>>0]=-5329*a}function g(a){for(var b=a[0],c=a[1]-1;c>=0;)b&1<J&&(255==I?(h(255),h(0)):h(I),J=7,I=0)}function h(a){H.push(N[a])}function i(a){h(a>>8&255),h(255&a)}function j(a,b){var c,d,e,f,g,h,i,j,k,l=0,m=8,n=64;for(k=0;m>k;++k){c=a[l],d=a[l+1],e=a[l+2],f=a[l+3],g=a[l+4],h=a[l+5],i=a[l+6],j=a[l+7];var o=c+j,p=c-j,q=d+i,r=d-i,s=e+h,t=e-h,u=f+g,v=f-g,w=o+u,x=o-u,y=q+s,z=q-s;a[l]=w+y,a[l+4]=w-y;var A=.707106781*(z+x);a[l+2]=x+A,a[l+6]=x-A,w=v+t,y=t+r,z=r+p;var B=.382683433*(w-z),C=.5411961*w+B,D=1.306562965*z+B,E=.707106781*y,G=p+E,H=p-E;a[l+5]=H+C,a[l+3]=H-C,a[l+1]=G+D,a[l+7]=G-D,l+=8}for(l=0,k=0;m>k;++k){c=a[l],d=a[l+8],e=a[l+16],f=a[l+24],g=a[l+32],h=a[l+40],i=a[l+48],j=a[l+56];var I=c+j,J=c-j,K=d+i,L=d-i,M=e+h,N=e-h,O=f+g,P=f-g,Q=I+O,R=I-O,S=K+M,T=K-M;a[l]=Q+S,a[l+32]=Q-S;var U=.707106781*(T+R);a[l+16]=R+U,a[l+48]=R-U,Q=P+N,S=N+L,T=L+J;var V=.382683433*(Q-T),W=.5411961*Q+V,X=1.306562965*T+V,Y=.707106781*S,Z=J+Y,$=J-Y;a[l+40]=$+W,a[l+24]=$-W,a[l+8]=Z+X,a[l+56]=Z-X,l++}var _;for(k=0;n>k;++k)_=a[k]*b[k],F[k]=_>0?_+.5|0:_-.5|0;return F}function k(){i(65504),i(16),h(74),h(70),h(73),h(70),h(0),h(1),h(1),h(0),i(1),i(1),h(0),h(0)}function l(a,b){i(65472),i(17),h(8),i(b),i(a),h(3),h(1),h(17),h(0),h(2),h(17),h(1),h(3),h(17),h(1)}function m(){i(65499),i(132),h(0);for(var a=0;64>a;a++)h(z[a]);h(1);for(var b=0;64>b;b++)h(A[b])}function n(){i(65476),i(418),h(0);for(var a=0;16>a;a++)h(Q[a+1]);for(var b=0;11>=b;b++)h(R[b]);h(16);for(var c=0;16>c;c++)h(S[c+1]);for(var d=0;161>=d;d++)h(T[d]);h(1);for(var e=0;16>e;e++)h(U[e+1]);for(var f=0;11>=f;f++)h(V[f]);h(17);for(var g=0;16>g;g++)h(W[g+1]);for(var j=0;161>=j;j++)h(X[j])}function o(){i(65498),i(12),h(3),h(1),h(0),h(2),h(17),h(3),h(17),h(0),h(63),h(0)}function p(a,b,c,d,e){for(var f,h=e[0],i=e[240],k=16,l=63,m=64,n=j(a,b),o=0;m>o;++o)G[P[o]]=n[o];var p=G[0]-c;c=G[0],0==p?g(d[0]):(f=32767+p,g(d[E[f]]),g(D[f]));for(var q=63;q>0&&0==G[q];q--);if(0==q)return g(h),c;for(var r,s=1;q>=s;){for(var t=s;0==G[s]&&q>=s;++s);var u=s-t;if(u>=k){r=u>>4;for(var v=1;r>=v;++v)g(i);u=15&u}f=32767+G[s],g(e[(u<<4)+E[f]]),g(D[f]),s++}return q!=l&&g(h),c}function q(){for(var a=String.fromCharCode,b=0;256>b;b++)N[b]=a(b)}function r(a){if(0>=a&&(a=1),a>100&&(a=100),x!=a){var c=0;c=Math.floor(50>a?5e3/a:200-2*a),b(c),x=a}}function s(){a||(a=50),q(),d(),e(),f(),r(a)}var t,u,v,w,x,y=(Math.round,Math.floor),z=new Array(64),A=new Array(64),B=new Array(64),C=new Array(64),D=new Array(65535),E=new Array(65535),F=new Array(64),G=new Array(64),H=[],I=0,J=7,K=new Array(64),L=new Array(64),M=new Array(64),N=new Array(256),O=new Array(2048),P=[0,1,5,6,14,15,27,28,2,4,7,13,16,26,29,42,3,8,12,17,25,30,41,43,9,11,18,24,31,40,44,53,10,19,23,32,39,45,52,54,20,22,33,38,46,51,55,60,21,34,37,47,50,56,59,61,35,36,48,49,57,58,62,63],Q=[0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0],R=[0,1,2,3,4,5,6,7,8,9,10,11],S=[0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,125],T=[1,2,3,0,4,17,5,18,33,49,65,6,19,81,97,7,34,113,20,50,129,145,161,8,35,66,177,193,21,82,209,240,36,51,98,114,130,9,10,22,23,24,25,26,37,38,39,40,41,42,52,53,54,55,56,57,58,67,68,69,70,71,72,73,74,83,84,85,86,87,88,89,90,99,100,101,102,103,104,105,106,115,116,117,118,119,120,121,122,131,132,133,134,135,136,137,138,146,147,148,149,150,151,152,153,154,162,163,164,165,166,167,168,169,170,178,179,180,181,182,183,184,185,186,194,195,196,197,198,199,200,201,202,210,211,212,213,214,215,216,217,218,225,226,227,228,229,230,231,232,233,234,241,242,243,244,245,246,247,248,249,250],U=[0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0],V=[0,1,2,3,4,5,6,7,8,9,10,11],W=[0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,119],X=[0,1,2,3,17,4,5,33,49,6,18,65,81,7,97,113,19,34,50,129,8,20,66,145,161,177,193,9,35,51,82,240,21,98,114,209,10,22,36,52,225,37,241,23,24,25,26,38,39,40,41,42,53,54,55,56,57,58,67,68,69,70,71,72,73,74,83,84,85,86,87,88,89,90,99,100,101,102,103,104,105,106,115,116,117,118,119,120,121,122,130,131,132,133,134,135,136,137,138,146,147,148,149,150,151,152,153,154,162,163,164,165,166,167,168,169,170,178,179,180,181,182,183,184,185,186,194,195,196,197,198,199,200,201,202,210,211,212,213,214,215,216,217,218,226,227,228,229,230,231,232,233,234,242,243,244,245,246,247,248,249,250];this.encode=function(a,b){b&&r(b),H=new Array,I=0,J=7,i(65496),k(),m(),l(a.width,a.height),n(),o();var c=0,d=0,e=0;I=0,J=7,this.encode.displayName="_encode_";for(var f,h,j,q,s,x,y,z,A,D=a.data,E=a.width,F=a.height,G=4*E,N=0;F>N;){for(f=0;G>f;){for(s=G*N+f,x=s,y=-1,z=0,A=0;64>A;A++)z=A>>3,y=4*(7&A),x=s+z*G+y,N+z>=F&&(x-=G*(N+1+z-F)),f+y>=G&&(x-=f+y-G+4),h=D[x++],j=D[x++],q=D[x++],K[A]=(O[h]+O[j+256>>0]+O[q+512>>0]>>16)-128,L[A]=(O[h+768>>0]+O[j+1024>>0]+O[q+1280>>0]>>16)-128,M[A]=(O[h+1280>>0]+O[j+1536>>0]+O[q+1792>>0]>>16)-128;c=p(K,B,c,t,v),d=p(L,C,d,u,w),e=p(M,C,e,u,w),f+=32}N+=8}if(J>=0){var P=[];P[1]=J+1,P[0]=(1<i;)e=d[4*(k-1)+3],0===e?j=k:i=k,k=j+i>>1;return f=k/c,0===f?1:f}function c(a){var b,c,d=a.naturalWidth,e=a.naturalHeight;return d*e>1048576?(b=document.createElement("canvas"),b.width=b.height=1,c=b.getContext("2d"),c.drawImage(a,-d+1,0),0===c.getImageData(0,0,1,1).data[3]):!1}return a.os.ios?a.os.ios>=7?function(a,c,d,e,f,g){var h=c.naturalWidth,i=c.naturalHeight,j=b(c,h,i);return a.getContext("2d").drawImage(c,0,0,h*j,i*j,d,e,f,g)}:function(a,d,e,f,g,h){var i,j,k,l,m,n,o,p=d.naturalWidth,q=d.naturalHeight,r=a.getContext("2d"),s=c(d),t="image/jpeg"===this.type,u=1024,v=0,w=0;for(s&&(p/=2,q/=2),r.save(),i=document.createElement("canvas"),i.width=i.height=u,j=i.getContext("2d"),k=t?b(d,p,q):1,l=Math.ceil(u*g/p),m=Math.ceil(u*h/q/k);q>v;){for(n=0,o=0;p>n;)j.clearRect(0,0,u,u),j.drawImage(d,-n,-v),r.drawImage(i,0,0,u,u,e+o,f+w,l,m),n+=u,o+=l;v+=u,w+=m}r.restore(),i=j=null}:function(b){var c=a.slice(arguments,1),d=b.getContext("2d");d.drawImage.apply(d,c)}}()})}),b("runtime/html5/transport",["base","runtime/html5/runtime"],function(a,b){var c=a.noop,d=a.$;return b.register("Transport",{init:function(){this._status=0,this._response=null},send:function(){var b,c,e,f=this.owner,g=this.options,h=this._initAjax(),i=f._blob,j=g.server;g.sendAsBinary?(j+=(/\?/.test(j)?"&":"?")+d.param(f._formData),c=i.getSource()):(b=new FormData,d.each(f._formData,function(a,c){b.append(a,c)}),b.append(g.fileVal,i.getSource(),g.filename||f._formData.name||"")),g.withCredentials&&"withCredentials"in h?(h.open(g.method,j,!0),h.withCredentials=!0):h.open(g.method,j),this._setRequestHeader(h,g.headers),c?(h.overrideMimeType&&h.overrideMimeType("application/octet-stream"),a.os.android?(e=new FileReader,e.onload=function(){h.send(this.result),e=e.onload=null},e.readAsArrayBuffer(c)):h.send(c)):h.send(b)},getResponse:function(){return this._response},getResponseAsJson:function(){return this._parseJson(this._response)},getStatus:function(){return this._status},abort:function(){var a=this._xhr;a&&(a.upload.onprogress=c,a.onreadystatechange=c,a.abort(),this._xhr=a=null)},destroy:function(){this.abort()},_initAjax:function(){var a=this,b=new XMLHttpRequest,d=this.options;return!d.withCredentials||"withCredentials"in b||"undefined"==typeof XDomainRequest||(b=new XDomainRequest),b.upload.onprogress=function(b){var c=0;return b.lengthComputable&&(c=b.loaded/b.total),a.trigger("progress",c)},b.onreadystatechange=function(){return 4===b.readyState?(b.upload.onprogress=c,b.onreadystatechange=c,a._xhr=null,a._status=b.status,b.status>=200&&b.status<300?(a._response=b.responseText,a.trigger("load")):b.status>=500&&b.status<600?(a._response=b.responseText,a.trigger("error","server")):a.trigger("error",a._status?"http":"abort")):void 0},a._xhr=b,b},_setRequestHeader:function(a,b){d.each(b,function(b,c){a.setRequestHeader(b,c)})},_parseJson:function(a){var b;try{b=JSON.parse(a)}catch(c){b={}}return b}})}),b("runtime/html5/md5",["runtime/html5/runtime"],function(a){var b=function(a,b){return a+b&4294967295},c=function(a,c,d,e,f,g){return c=b(b(c,a),b(e,g)),b(c<>>32-f,d)},d=function(a,b,d,e,f,g,h){return c(b&d|~b&e,a,b,f,g,h)},e=function(a,b,d,e,f,g,h){return c(b&e|d&~e,a,b,f,g,h)},f=function(a,b,d,e,f,g,h){return c(b^d^e,a,b,f,g,h)},g=function(a,b,d,e,f,g,h){return c(d^(b|~e),a,b,f,g,h)},h=function(a,c){var h=a[0],i=a[1],j=a[2],k=a[3];h=d(h,i,j,k,c[0],7,-680876936),k=d(k,h,i,j,c[1],12,-389564586),j=d(j,k,h,i,c[2],17,606105819),i=d(i,j,k,h,c[3],22,-1044525330),h=d(h,i,j,k,c[4],7,-176418897),k=d(k,h,i,j,c[5],12,1200080426),j=d(j,k,h,i,c[6],17,-1473231341),i=d(i,j,k,h,c[7],22,-45705983),h=d(h,i,j,k,c[8],7,1770035416),k=d(k,h,i,j,c[9],12,-1958414417),j=d(j,k,h,i,c[10],17,-42063),i=d(i,j,k,h,c[11],22,-1990404162),h=d(h,i,j,k,c[12],7,1804603682),k=d(k,h,i,j,c[13],12,-40341101),j=d(j,k,h,i,c[14],17,-1502002290),i=d(i,j,k,h,c[15],22,1236535329),h=e(h,i,j,k,c[1],5,-165796510),k=e(k,h,i,j,c[6],9,-1069501632),j=e(j,k,h,i,c[11],14,643717713),i=e(i,j,k,h,c[0],20,-373897302),h=e(h,i,j,k,c[5],5,-701558691),k=e(k,h,i,j,c[10],9,38016083),j=e(j,k,h,i,c[15],14,-660478335),i=e(i,j,k,h,c[4],20,-405537848),h=e(h,i,j,k,c[9],5,568446438),k=e(k,h,i,j,c[14],9,-1019803690),j=e(j,k,h,i,c[3],14,-187363961),i=e(i,j,k,h,c[8],20,1163531501),h=e(h,i,j,k,c[13],5,-1444681467),k=e(k,h,i,j,c[2],9,-51403784),j=e(j,k,h,i,c[7],14,1735328473),i=e(i,j,k,h,c[12],20,-1926607734),h=f(h,i,j,k,c[5],4,-378558),k=f(k,h,i,j,c[8],11,-2022574463),j=f(j,k,h,i,c[11],16,1839030562),i=f(i,j,k,h,c[14],23,-35309556),h=f(h,i,j,k,c[1],4,-1530992060),k=f(k,h,i,j,c[4],11,1272893353),j=f(j,k,h,i,c[7],16,-155497632),i=f(i,j,k,h,c[10],23,-1094730640),h=f(h,i,j,k,c[13],4,681279174),k=f(k,h,i,j,c[0],11,-358537222),j=f(j,k,h,i,c[3],16,-722521979),i=f(i,j,k,h,c[6],23,76029189),h=f(h,i,j,k,c[9],4,-640364487),k=f(k,h,i,j,c[12],11,-421815835),j=f(j,k,h,i,c[15],16,530742520),i=f(i,j,k,h,c[2],23,-995338651),h=g(h,i,j,k,c[0],6,-198630844),k=g(k,h,i,j,c[7],10,1126891415),j=g(j,k,h,i,c[14],15,-1416354905),i=g(i,j,k,h,c[5],21,-57434055),h=g(h,i,j,k,c[12],6,1700485571),k=g(k,h,i,j,c[3],10,-1894986606),j=g(j,k,h,i,c[10],15,-1051523),i=g(i,j,k,h,c[1],21,-2054922799),h=g(h,i,j,k,c[8],6,1873313359),k=g(k,h,i,j,c[15],10,-30611744),j=g(j,k,h,i,c[6],15,-1560198380),i=g(i,j,k,h,c[13],21,1309151649),h=g(h,i,j,k,c[4],6,-145523070),k=g(k,h,i,j,c[11],10,-1120210379),j=g(j,k,h,i,c[2],15,718787259),i=g(i,j,k,h,c[9],21,-343485551),a[0]=b(h,a[0]),a[1]=b(i,a[1]),a[2]=b(j,a[2]),a[3]=b(k,a[3])},i=function(a){var b,c=[];for(b=0;64>b;b+=4)c[b>>2]=a.charCodeAt(b)+(a.charCodeAt(b+1)<<8)+(a.charCodeAt(b+2)<<16)+(a.charCodeAt(b+3)<<24);return c},j=function(a){var b,c=[];for(b=0;64>b;b+=4)c[b>>2]=a[b]+(a[b+1]<<8)+(a[b+2]<<16)+(a[b+3]<<24);return c},k=function(a){var b,c,d,e,f,g,j=a.length,k=[1732584193,-271733879,-1732584194,271733878];for(b=64;j>=b;b+=64)h(k,i(a.substring(b-64,b)));for(a=a.substring(b-64),c=a.length,d=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],b=0;c>b;b+=1)d[b>>2]|=a.charCodeAt(b)<<(b%4<<3);if(d[b>>2]|=128<<(b%4<<3),b>55)for(h(k,d),b=0;16>b;b+=1)d[b]=0;return e=8*j,e=e.toString(16).match(/(.*?)(.{0,8})$/),f=parseInt(e[2],16),g=parseInt(e[1],16)||0,d[14]=f,d[15]=g,h(k,d),k},l=function(a){var b,c,d,e,f,g,i=a.length,k=[1732584193,-271733879,-1732584194,271733878];for(b=64;i>=b;b+=64)h(k,j(a.subarray(b-64,b)));for(a=i>b-64?a.subarray(b-64):new Uint8Array(0),c=a.length,d=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],b=0;c>b;b+=1)d[b>>2]|=a[b]<<(b%4<<3);if(d[b>>2]|=128<<(b%4<<3),b>55)for(h(k,d),b=0;16>b;b+=1)d[b]=0;return e=8*i,e=e.toString(16).match(/(.*?)(.{0,8})$/),f=parseInt(e[2],16),g=parseInt(e[1],16)||0,d[14]=f,d[15]=g,h(k,d),k},m=["0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"],n=function(a){var b,c="";for(b=0;4>b;b+=1)c+=m[a>>8*b+4&15]+m[a>>8*b&15];return c},o=function(a){var b;for(b=0;b>16)+(b>>16)+(c>>16);return d<<16|65535&c}),q.prototype.append=function(a){return/[\u0080-\uFFFF]/.test(a)&&(a=unescape(encodeURIComponent(a))),this.appendBinary(a),this},q.prototype.appendBinary=function(a){this._buff+=a,this._length+=a.length;var b,c=this._buff.length;for(b=64;c>=b;b+=64)h(this._state,i(this._buff.substring(b-64,b)));return this._buff=this._buff.substr(b-64),this},q.prototype.end=function(a){var b,c,d=this._buff,e=d.length,f=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];for(b=0;e>b;b+=1)f[b>>2]|=d.charCodeAt(b)<<(b%4<<3);return this._finish(f,e),c=a?this._state:o(this._state),this.reset(),c},q.prototype._finish=function(a,b){var c,d,e,f=b;if(a[f>>2]|=128<<(f%4<<3),f>55)for(h(this._state,a),f=0;16>f;f+=1)a[f]=0;c=8*this._length,c=c.toString(16).match(/(.*?)(.{0,8})$/),d=parseInt(c[2],16),e=parseInt(c[1],16)||0,a[14]=d,a[15]=e,h(this._state,a)},q.prototype.reset=function(){return this._buff="",this._length=0,this._state=[1732584193,-271733879,-1732584194,271733878],this},q.prototype.destroy=function(){delete this._state,delete this._buff,delete this._length},q.hash=function(a,b){/[\u0080-\uFFFF]/.test(a)&&(a=unescape(encodeURIComponent(a)));var c=k(a);return b?c:o(c)},q.hashBinary=function(a,b){var c=k(a);return b?c:o(c)},q.ArrayBuffer=function(){this.reset()},q.ArrayBuffer.prototype.append=function(a){var b,c=this._concatArrayBuffer(this._buff,a),d=c.length;for(this._length+=a.byteLength,b=64;d>=b;b+=64)h(this._state,j(c.subarray(b-64,b)));return this._buff=d>b-64?c.subarray(b-64):new Uint8Array(0),this},q.ArrayBuffer.prototype.end=function(a){var b,c,d=this._buff,e=d.length,f=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]; -for(b=0;e>b;b+=1)f[b>>2]|=d[b]<<(b%4<<3);return this._finish(f,e),c=a?this._state:o(this._state),this.reset(),c},q.ArrayBuffer.prototype._finish=q.prototype._finish,q.ArrayBuffer.prototype.reset=function(){return this._buff=new Uint8Array(0),this._length=0,this._state=[1732584193,-271733879,-1732584194,271733878],this},q.ArrayBuffer.prototype.destroy=q.prototype.destroy,q.ArrayBuffer.prototype._concatArrayBuffer=function(a,b){var c=a.length,d=new Uint8Array(c+b.byteLength);return d.set(a),d.set(new Uint8Array(b),c),d},q.ArrayBuffer.hash=function(a,b){var c=l(new Uint8Array(a));return b?c:o(c)},a.register("Md5",{init:function(){},loadFromBlob:function(a){var b,c,d=a.getSource(),e=2097152,f=Math.ceil(d.size/e),g=0,h=this.owner,i=new q.ArrayBuffer,j=this,k=d.mozSlice||d.webkitSlice||d.slice;c=new FileReader,(b=function(){var l,m;l=g*e,m=Math.min(l+e,d.size),c.onload=function(b){i.append(b.target.result),h.trigger("progress",{total:a.size,loaded:m})},c.onloadend=function(){c.onloadend=c.onload=null,++g',c.html(a)},getFlash:function(){return this._flash?this._flash:(this._flash=g("#"+this.uid).get(0),this._flash)}}),f.register=function(a,c){return c=i[a]=b.inherits(d,g.extend({flashExec:function(){var a=this.owner,b=this.getRuntime();return b.flashExec.apply(a,arguments)}},c))},e()>=11.4&&c.addRuntime(h,f),f}),b("runtime/flash/filepicker",["base","runtime/flash/runtime"],function(a,b){var c=a.$;return b.register("FilePicker",{init:function(a){var b,d,e=c.extend({},a);for(b=e.accept&&e.accept.length,d=0;b>d;d++)e.accept[d].title||(e.accept[d].title="Files");delete e.button,delete e.id,delete e.container,this.flashExec("FilePicker","init",e)},destroy:function(){this.flashExec("FilePicker","destroy")}})}),b("runtime/flash/image",["runtime/flash/runtime"],function(a){return a.register("Image",{loadFromBlob:function(a){var b=this.owner;b.info()&&this.flashExec("Image","info",b.info()),b.meta()&&this.flashExec("Image","meta",b.meta()),this.flashExec("Image","loadFromBlob",a.uid)}})}),b("runtime/flash/transport",["base","runtime/flash/runtime","runtime/client"],function(b,c,d){var e=b.$;return c.register("Transport",{init:function(){this._status=0,this._response=null,this._responseJson=null},send:function(){var a,b=this.owner,c=this.options,d=this._initAjax(),f=b._blob,g=c.server;d.connectRuntime(f.ruid),c.sendAsBinary?(g+=(/\?/.test(g)?"&":"?")+e.param(b._formData),a=f.uid):(e.each(b._formData,function(a,b){d.exec("append",a,b)}),d.exec("appendBlob",c.fileVal,f.uid,c.filename||b._formData.name||"")),this._setRequestHeader(d,c.headers),d.exec("send",{method:c.method,url:g,forceURLStream:c.forceURLStream,mimeType:"application/octet-stream"},a)},getStatus:function(){return this._status},getResponse:function(){return this._response||""},getResponseAsJson:function(){return this._responseJson},abort:function(){var a=this._xhr;a&&(a.exec("abort"),a.destroy(),this._xhr=a=null)},destroy:function(){this.abort()},_initAjax:function(){var b=this,c=new d("XMLHttpRequest");return c.on("uploadprogress progress",function(a){var c=a.loaded/a.total;return c=Math.min(1,Math.max(0,c)),b.trigger("progress",c)}),c.on("load",function(){var d,e=c.exec("getStatus"),f=!1,g="";return c.off(),b._xhr=null,e>=200&&300>e?f=!0:e>=500&&600>e?(f=!0,g="server"):g="http",f&&(b._response=c.exec("getResponse"),b._response=decodeURIComponent(b._response),d=a.JSON&&a.JSON.parse||function(a){try{return new Function("return "+a).call()}catch(b){return{}}},b._responseJson=b._response?d(b._response):{}),c.destroy(),c=null,g?b.trigger("error",g):b.trigger("load")}),c.on("error",function(){c.off(),b._xhr=null,b.trigger("error","http")}),b._xhr=c,c},_setRequestHeader:function(a,b){e.each(b,function(b,c){a.exec("setRequestHeader",b,c)})}})}),b("runtime/flash/blob",["runtime/flash/runtime","lib/blob"],function(a,b){return a.register("Blob",{slice:function(a,c){var d=this.flashExec("Blob","slice",a,c);return new b(d.uid,d)}})}),b("runtime/flash/md5",["runtime/flash/runtime"],function(a){return a.register("Md5",{init:function(){},loadFromBlob:function(a){return this.flashExec("Md5","loadFromBlob",a.uid)}})}),b("preset/all",["base","widgets/filednd","widgets/filepaste","widgets/filepicker","widgets/image","widgets/queue","widgets/runtime","widgets/upload","widgets/validator","widgets/md5","runtime/html5/blob","runtime/html5/dnd","runtime/html5/filepaste","runtime/html5/filepicker","runtime/html5/imagemeta/exif","runtime/html5/androidpatch","runtime/html5/image","runtime/html5/transport","runtime/html5/md5","runtime/flash/filepicker","runtime/flash/image","runtime/flash/transport","runtime/flash/blob","runtime/flash/md5"],function(a){return a}),b("widgets/log",["base","uploader","widgets/widget"],function(a,b){function c(a){var b=e.extend({},d,a),c=f.replace(/^(.*)\?/,"$1"+e.param(b)),g=new Image;g.src=c}var d,e=a.$,f=" http://static.tieba.baidu.com/tb/pms/img/st.gif??",g=(location.hostname||location.host||"protected").toLowerCase(),h=g&&/baidu/i.exec(g);if(h)return d={dv:3,master:"webuploader",online:/test/.exec(g)?0:1,module:"",product:g,type:0},b.register({name:"log",init:function(){var a=this.owner,b=0,d=0;a.on("error",function(a){c({type:2,c_error_code:a})}).on("uploadError",function(a,b){c({type:2,c_error_code:"UPLOAD_ERROR",c_reason:""+b})}).on("uploadComplete",function(a){b++,d+=a.size}).on("uploadFinished",function(){c({c_count:b,c_size:d}),b=d=0}),c({c_usage:1})}})}),b("webuploader",["preset/all","widgets/log"],function(a){return a}),c("webuploader")}); \ No newline at end of file diff --git a/static/plugins/webuploader/webuploader.noimage.js b/static/plugins/webuploader/webuploader.noimage.js deleted file mode 100644 index 43755428..00000000 --- a/static/plugins/webuploader/webuploader.noimage.js +++ /dev/null @@ -1,5026 +0,0 @@ -/*! WebUploader 0.1.5 */ - - -/** - * @fileOverview 让内部各个部件的代码可以用[amd](https://github.com/amdjs/amdjs-api/wiki/AMD)模块定义方式组织起来。 - * - * AMD API 内部的简单不完全实现,请忽略。只有当WebUploader被合并成一个文件的时候才会引入。 - */ -(function( root, factory ) { - var modules = {}, - - // 内部require, 简单不完全实现。 - // https://github.com/amdjs/amdjs-api/wiki/require - _require = function( deps, callback ) { - var args, len, i; - - // 如果deps不是数组,则直接返回指定module - if ( typeof deps === 'string' ) { - return getModule( deps ); - } else { - args = []; - for( len = deps.length, i = 0; i < len; i++ ) { - args.push( getModule( deps[ i ] ) ); - } - - return callback.apply( null, args ); - } - }, - - // 内部define,暂时不支持不指定id. - _define = function( id, deps, factory ) { - if ( arguments.length === 2 ) { - factory = deps; - deps = null; - } - - _require( deps || [], function() { - setModule( id, factory, arguments ); - }); - }, - - // 设置module, 兼容CommonJs写法。 - setModule = function( id, factory, args ) { - var module = { - exports: factory - }, - returned; - - if ( typeof factory === 'function' ) { - args.length || (args = [ _require, module.exports, module ]); - returned = factory.apply( null, args ); - returned !== undefined && (module.exports = returned); - } - - modules[ id ] = module.exports; - }, - - // 根据id获取module - getModule = function( id ) { - var module = modules[ id ] || root[ id ]; - - if ( !module ) { - throw new Error( '`' + id + '` is undefined' ); - } - - return module; - }, - - // 将所有modules,将路径ids装换成对象。 - exportsTo = function( obj ) { - var key, host, parts, part, last, ucFirst; - - // make the first character upper case. - ucFirst = function( str ) { - return str && (str.charAt( 0 ).toUpperCase() + str.substr( 1 )); - }; - - for ( key in modules ) { - host = obj; - - if ( !modules.hasOwnProperty( key ) ) { - continue; - } - - parts = key.split('/'); - last = ucFirst( parts.pop() ); - - while( (part = ucFirst( parts.shift() )) ) { - host[ part ] = host[ part ] || {}; - host = host[ part ]; - } - - host[ last ] = modules[ key ]; - } - - return obj; - }, - - makeExport = function( dollar ) { - root.__dollar = dollar; - - // exports every module. - return exportsTo( factory( root, _define, _require ) ); - }, - - origin; - - if ( typeof module === 'object' && typeof module.exports === 'object' ) { - - // For CommonJS and CommonJS-like environments where a proper window is present, - module.exports = makeExport(); - } else if ( typeof define === 'function' && define.amd ) { - - // Allow using this built library as an AMD module - // in another project. That other project will only - // see this AMD call, not the internal modules in - // the closure below. - define([ 'jquery' ], makeExport ); - } else { - - // Browser globals case. Just assign the - // result to a property on the global. - origin = root.WebUploader; - root.WebUploader = makeExport(); - root.WebUploader.noConflict = function() { - root.WebUploader = origin; - }; - } -})( window, function( window, define, require ) { - - - /** - * @fileOverview jQuery or Zepto - */ - define('dollar-third',[],function() { - var $ = window.__dollar || window.jQuery || window.Zepto; - - if ( !$ ) { - throw new Error('jQuery or Zepto not found!'); - } - - return $; - }); - /** - * @fileOverview Dom 操作相关 - */ - define('dollar',[ - 'dollar-third' - ], function( _ ) { - return _; - }); - /** - * @fileOverview 使用jQuery的Promise - */ - define('promise-third',[ - 'dollar' - ], function( $ ) { - return { - Deferred: $.Deferred, - when: $.when, - - isPromise: function( anything ) { - return anything && typeof anything.then === 'function'; - } - }; - }); - /** - * @fileOverview Promise/A+ - */ - define('promise',[ - 'promise-third' - ], function( _ ) { - return _; - }); - /** - * @fileOverview 基础类方法。 - */ - - /** - * Web Uploader内部类的详细说明,以下提及的功能类,都可以在`WebUploader`这个变量中访问到。 - * - * As you know, Web Uploader的每个文件都是用过[AMD](https://github.com/amdjs/amdjs-api/wiki/AMD)规范中的`define`组织起来的, 每个Module都会有个module id. - * 默认module id为该文件的路径,而此路径将会转化成名字空间存放在WebUploader中。如: - * - * * module `base`:WebUploader.Base - * * module `file`: WebUploader.File - * * module `lib/dnd`: WebUploader.Lib.Dnd - * * module `runtime/html5/dnd`: WebUploader.Runtime.Html5.Dnd - * - * - * 以下文档中对类的使用可能省略掉了`WebUploader`前缀。 - * @module WebUploader - * @title WebUploader API文档 - */ - define('base',[ - 'dollar', - 'promise' - ], function( $, promise ) { - - var noop = function() {}, - call = Function.call; - - // http://jsperf.com/uncurrythis - // 反科里化 - function uncurryThis( fn ) { - return function() { - return call.apply( fn, arguments ); - }; - } - - function bindFn( fn, context ) { - return function() { - return fn.apply( context, arguments ); - }; - } - - function createObject( proto ) { - var f; - - if ( Object.create ) { - return Object.create( proto ); - } else { - f = function() {}; - f.prototype = proto; - return new f(); - } - } - - - /** - * 基础类,提供一些简单常用的方法。 - * @class Base - */ - return { - - /** - * @property {String} version 当前版本号。 - */ - version: '0.1.5', - - /** - * @property {jQuery|Zepto} $ 引用依赖的jQuery或者Zepto对象。 - */ - $: $, - - Deferred: promise.Deferred, - - isPromise: promise.isPromise, - - when: promise.when, - - /** - * @description 简单的浏览器检查结果。 - * - * * `webkit` webkit版本号,如果浏览器为非webkit内核,此属性为`undefined`。 - * * `chrome` chrome浏览器版本号,如果浏览器为chrome,此属性为`undefined`。 - * * `ie` ie浏览器版本号,如果浏览器为非ie,此属性为`undefined`。**暂不支持ie10+** - * * `firefox` firefox浏览器版本号,如果浏览器为非firefox,此属性为`undefined`。 - * * `safari` safari浏览器版本号,如果浏览器为非safari,此属性为`undefined`。 - * * `opera` opera浏览器版本号,如果浏览器为非opera,此属性为`undefined`。 - * - * @property {Object} [browser] - */ - browser: (function( ua ) { - var ret = {}, - webkit = ua.match( /WebKit\/([\d.]+)/ ), - chrome = ua.match( /Chrome\/([\d.]+)/ ) || - ua.match( /CriOS\/([\d.]+)/ ), - - ie = ua.match( /MSIE\s([\d\.]+)/ ) || - ua.match( /(?:trident)(?:.*rv:([\w.]+))?/i ), - firefox = ua.match( /Firefox\/([\d.]+)/ ), - safari = ua.match( /Safari\/([\d.]+)/ ), - opera = ua.match( /OPR\/([\d.]+)/ ); - - webkit && (ret.webkit = parseFloat( webkit[ 1 ] )); - chrome && (ret.chrome = parseFloat( chrome[ 1 ] )); - ie && (ret.ie = parseFloat( ie[ 1 ] )); - firefox && (ret.firefox = parseFloat( firefox[ 1 ] )); - safari && (ret.safari = parseFloat( safari[ 1 ] )); - opera && (ret.opera = parseFloat( opera[ 1 ] )); - - return ret; - })( navigator.userAgent ), - - /** - * @description 操作系统检查结果。 - * - * * `android` 如果在android浏览器环境下,此值为对应的android版本号,否则为`undefined`。 - * * `ios` 如果在ios浏览器环境下,此值为对应的ios版本号,否则为`undefined`。 - * @property {Object} [os] - */ - os: (function( ua ) { - var ret = {}, - - // osx = !!ua.match( /\(Macintosh\; Intel / ), - android = ua.match( /(?:Android);?[\s\/]+([\d.]+)?/ ), - ios = ua.match( /(?:iPad|iPod|iPhone).*OS\s([\d_]+)/ ); - - // osx && (ret.osx = true); - android && (ret.android = parseFloat( android[ 1 ] )); - ios && (ret.ios = parseFloat( ios[ 1 ].replace( /_/g, '.' ) )); - - return ret; - })( navigator.userAgent ), - - /** - * 实现类与类之间的继承。 - * @method inherits - * @grammar Base.inherits( super ) => child - * @grammar Base.inherits( super, protos ) => child - * @grammar Base.inherits( super, protos, statics ) => child - * @param {Class} super 父类 - * @param {Object | Function} [protos] 子类或者对象。如果对象中包含constructor,子类将是用此属性值。 - * @param {Function} [protos.constructor] 子类构造器,不指定的话将创建个临时的直接执行父类构造器的方法。 - * @param {Object} [statics] 静态属性或方法。 - * @return {Class} 返回子类。 - * @example - * function Person() { - * console.log( 'Super' ); - * } - * Person.prototype.hello = function() { - * console.log( 'hello' ); - * }; - * - * var Manager = Base.inherits( Person, { - * world: function() { - * console.log( 'World' ); - * } - * }); - * - * // 因为没有指定构造器,父类的构造器将会执行。 - * var instance = new Manager(); // => Super - * - * // 继承子父类的方法 - * instance.hello(); // => hello - * instance.world(); // => World - * - * // 子类的__super__属性指向父类 - * console.log( Manager.__super__ === Person ); // => true - */ - inherits: function( Super, protos, staticProtos ) { - var child; - - if ( typeof protos === 'function' ) { - child = protos; - protos = null; - } else if ( protos && protos.hasOwnProperty('constructor') ) { - child = protos.constructor; - } else { - child = function() { - return Super.apply( this, arguments ); - }; - } - - // 复制静态方法 - $.extend( true, child, Super, staticProtos || {} ); - - /* jshint camelcase: false */ - - // 让子类的__super__属性指向父类。 - child.__super__ = Super.prototype; - - // 构建原型,添加原型方法或属性。 - // 暂时用Object.create实现。 - child.prototype = createObject( Super.prototype ); - protos && $.extend( true, child.prototype, protos ); - - return child; - }, - - /** - * 一个不做任何事情的方法。可以用来赋值给默认的callback. - * @method noop - */ - noop: noop, - - /** - * 返回一个新的方法,此方法将已指定的`context`来执行。 - * @grammar Base.bindFn( fn, context ) => Function - * @method bindFn - * @example - * var doSomething = function() { - * console.log( this.name ); - * }, - * obj = { - * name: 'Object Name' - * }, - * aliasFn = Base.bind( doSomething, obj ); - * - * aliasFn(); // => Object Name - * - */ - bindFn: bindFn, - - /** - * 引用Console.log如果存在的话,否则引用一个[空函数noop](#WebUploader:Base.noop)。 - * @grammar Base.log( args... ) => undefined - * @method log - */ - log: (function() { - if ( window.console ) { - return bindFn( console.log, console ); - } - return noop; - })(), - - nextTick: (function() { - - return function( cb ) { - setTimeout( cb, 1 ); - }; - - // @bug 当浏览器不在当前窗口时就停了。 - // var next = window.requestAnimationFrame || - // window.webkitRequestAnimationFrame || - // window.mozRequestAnimationFrame || - // function( cb ) { - // window.setTimeout( cb, 1000 / 60 ); - // }; - - // // fix: Uncaught TypeError: Illegal invocation - // return bindFn( next, window ); - })(), - - /** - * 被[uncurrythis](http://www.2ality.com/2011/11/uncurrying-this.html)的数组slice方法。 - * 将用来将非数组对象转化成数组对象。 - * @grammar Base.slice( target, start[, end] ) => Array - * @method slice - * @example - * function doSomthing() { - * var args = Base.slice( arguments, 1 ); - * console.log( args ); - * } - * - * doSomthing( 'ignored', 'arg2', 'arg3' ); // => Array ["arg2", "arg3"] - */ - slice: uncurryThis( [].slice ), - - /** - * 生成唯一的ID - * @method guid - * @grammar Base.guid() => String - * @grammar Base.guid( prefx ) => String - */ - guid: (function() { - var counter = 0; - - return function( prefix ) { - var guid = (+new Date()).toString( 32 ), - i = 0; - - for ( ; i < 5; i++ ) { - guid += Math.floor( Math.random() * 65535 ).toString( 32 ); - } - - return (prefix || 'wu_') + guid + (counter++).toString( 32 ); - }; - })(), - - /** - * 格式化文件大小, 输出成带单位的字符串 - * @method formatSize - * @grammar Base.formatSize( size ) => String - * @grammar Base.formatSize( size, pointLength ) => String - * @grammar Base.formatSize( size, pointLength, units ) => String - * @param {Number} size 文件大小 - * @param {Number} [pointLength=2] 精确到的小数点数。 - * @param {Array} [units=[ 'B', 'K', 'M', 'G', 'TB' ]] 单位数组。从字节,到千字节,一直往上指定。如果单位数组里面只指定了到了K(千字节),同时文件大小大于M, 此方法的输出将还是显示成多少K. - * @example - * console.log( Base.formatSize( 100 ) ); // => 100B - * console.log( Base.formatSize( 1024 ) ); // => 1.00K - * console.log( Base.formatSize( 1024, 0 ) ); // => 1K - * console.log( Base.formatSize( 1024 * 1024 ) ); // => 1.00M - * console.log( Base.formatSize( 1024 * 1024 * 1024 ) ); // => 1.00G - * console.log( Base.formatSize( 1024 * 1024 * 1024, 0, ['B', 'KB', 'MB'] ) ); // => 1024MB - */ - formatSize: function( size, pointLength, units ) { - var unit; - - units = units || [ 'B', 'K', 'M', 'G', 'TB' ]; - - while ( (unit = units.shift()) && size > 1024 ) { - size = size / 1024; - } - - return (unit === 'B' ? size : size.toFixed( pointLength || 2 )) + - unit; - } - }; - }); - /** - * 事件处理类,可以独立使用,也可以扩展给对象使用。 - * @fileOverview Mediator - */ - define('mediator',[ - 'base' - ], function( Base ) { - var $ = Base.$, - slice = [].slice, - separator = /\s+/, - protos; - - // 根据条件过滤出事件handlers. - function findHandlers( arr, name, callback, context ) { - return $.grep( arr, function( handler ) { - return handler && - (!name || handler.e === name) && - (!callback || handler.cb === callback || - handler.cb._cb === callback) && - (!context || handler.ctx === context); - }); - } - - function eachEvent( events, callback, iterator ) { - // 不支持对象,只支持多个event用空格隔开 - $.each( (events || '').split( separator ), function( _, key ) { - iterator( key, callback ); - }); - } - - function triggerHanders( events, args ) { - var stoped = false, - i = -1, - len = events.length, - handler; - - while ( ++i < len ) { - handler = events[ i ]; - - if ( handler.cb.apply( handler.ctx2, args ) === false ) { - stoped = true; - break; - } - } - - return !stoped; - } - - protos = { - - /** - * 绑定事件。 - * - * `callback`方法在执行时,arguments将会来源于trigger的时候携带的参数。如 - * ```javascript - * var obj = {}; - * - * // 使得obj有事件行为 - * Mediator.installTo( obj ); - * - * obj.on( 'testa', function( arg1, arg2 ) { - * console.log( arg1, arg2 ); // => 'arg1', 'arg2' - * }); - * - * obj.trigger( 'testa', 'arg1', 'arg2' ); - * ``` - * - * 如果`callback`中,某一个方法`return false`了,则后续的其他`callback`都不会被执行到。 - * 切会影响到`trigger`方法的返回值,为`false`。 - * - * `on`还可以用来添加一个特殊事件`all`, 这样所有的事件触发都会响应到。同时此类`callback`中的arguments有一个不同处, - * 就是第一个参数为`type`,记录当前是什么事件在触发。此类`callback`的优先级比脚低,会再正常`callback`执行完后触发。 - * ```javascript - * obj.on( 'all', function( type, arg1, arg2 ) { - * console.log( type, arg1, arg2 ); // => 'testa', 'arg1', 'arg2' - * }); - * ``` - * - * @method on - * @grammar on( name, callback[, context] ) => self - * @param {String} name 事件名,支持多个事件用空格隔开 - * @param {Function} callback 事件处理器 - * @param {Object} [context] 事件处理器的上下文。 - * @return {self} 返回自身,方便链式 - * @chainable - * @class Mediator - */ - on: function( name, callback, context ) { - var me = this, - set; - - if ( !callback ) { - return this; - } - - set = this._events || (this._events = []); - - eachEvent( name, callback, function( name, callback ) { - var handler = { e: name }; - - handler.cb = callback; - handler.ctx = context; - handler.ctx2 = context || me; - handler.id = set.length; - - set.push( handler ); - }); - - return this; - }, - - /** - * 绑定事件,且当handler执行完后,自动解除绑定。 - * @method once - * @grammar once( name, callback[, context] ) => self - * @param {String} name 事件名 - * @param {Function} callback 事件处理器 - * @param {Object} [context] 事件处理器的上下文。 - * @return {self} 返回自身,方便链式 - * @chainable - */ - once: function( name, callback, context ) { - var me = this; - - if ( !callback ) { - return me; - } - - eachEvent( name, callback, function( name, callback ) { - var once = function() { - me.off( name, once ); - return callback.apply( context || me, arguments ); - }; - - once._cb = callback; - me.on( name, once, context ); - }); - - return me; - }, - - /** - * 解除事件绑定 - * @method off - * @grammar off( [name[, callback[, context] ] ] ) => self - * @param {String} [name] 事件名 - * @param {Function} [callback] 事件处理器 - * @param {Object} [context] 事件处理器的上下文。 - * @return {self} 返回自身,方便链式 - * @chainable - */ - off: function( name, cb, ctx ) { - var events = this._events; - - if ( !events ) { - return this; - } - - if ( !name && !cb && !ctx ) { - this._events = []; - return this; - } - - eachEvent( name, cb, function( name, cb ) { - $.each( findHandlers( events, name, cb, ctx ), function() { - delete events[ this.id ]; - }); - }); - - return this; - }, - - /** - * 触发事件 - * @method trigger - * @grammar trigger( name[, args...] ) => self - * @param {String} type 事件名 - * @param {*} [...] 任意参数 - * @return {Boolean} 如果handler中return false了,则返回false, 否则返回true - */ - trigger: function( type ) { - var args, events, allEvents; - - if ( !this._events || !type ) { - return this; - } - - args = slice.call( arguments, 1 ); - events = findHandlers( this._events, type ); - allEvents = findHandlers( this._events, 'all' ); - - return triggerHanders( events, args ) && - triggerHanders( allEvents, arguments ); - } - }; - - /** - * 中介者,它本身是个单例,但可以通过[installTo](#WebUploader:Mediator:installTo)方法,使任何对象具备事件行为。 - * 主要目的是负责模块与模块之间的合作,降低耦合度。 - * - * @class Mediator - */ - return $.extend({ - - /** - * 可以通过这个接口,使任何对象具备事件功能。 - * @method installTo - * @param {Object} obj 需要具备事件行为的对象。 - * @return {Object} 返回obj. - */ - installTo: function( obj ) { - return $.extend( obj, protos ); - } - - }, protos ); - }); - /** - * @fileOverview Uploader上传类 - */ - define('uploader',[ - 'base', - 'mediator' - ], function( Base, Mediator ) { - - var $ = Base.$; - - /** - * 上传入口类。 - * @class Uploader - * @constructor - * @grammar new Uploader( opts ) => Uploader - * @example - * var uploader = WebUploader.Uploader({ - * swf: 'path_of_swf/Uploader.swf', - * - * // 开起分片上传。 - * chunked: true - * }); - */ - function Uploader( opts ) { - this.options = $.extend( true, {}, Uploader.options, opts ); - this._init( this.options ); - } - - // default Options - // widgets中有相应扩展 - Uploader.options = {}; - Mediator.installTo( Uploader.prototype ); - - // 批量添加纯命令式方法。 - $.each({ - upload: 'start-upload', - stop: 'stop-upload', - getFile: 'get-file', - getFiles: 'get-files', - addFile: 'add-file', - addFiles: 'add-file', - sort: 'sort-files', - removeFile: 'remove-file', - cancelFile: 'cancel-file', - skipFile: 'skip-file', - retry: 'retry', - isInProgress: 'is-in-progress', - makeThumb: 'make-thumb', - md5File: 'md5-file', - getDimension: 'get-dimension', - addButton: 'add-btn', - predictRuntimeType: 'predict-runtime-type', - refresh: 'refresh', - disable: 'disable', - enable: 'enable', - reset: 'reset' - }, function( fn, command ) { - Uploader.prototype[ fn ] = function() { - return this.request( command, arguments ); - }; - }); - - $.extend( Uploader.prototype, { - state: 'pending', - - _init: function( opts ) { - var me = this; - - me.request( 'init', opts, function() { - me.state = 'ready'; - me.trigger('ready'); - }); - }, - - /** - * 获取或者设置Uploader配置项。 - * @method option - * @grammar option( key ) => * - * @grammar option( key, val ) => self - * @example - * - * // 初始状态图片上传前不会压缩 - * var uploader = new WebUploader.Uploader({ - * compress: null; - * }); - * - * // 修改后图片上传前,尝试将图片压缩到1600 * 1600 - * uploader.option( 'compress', { - * width: 1600, - * height: 1600 - * }); - */ - option: function( key, val ) { - var opts = this.options; - - // setter - if ( arguments.length > 1 ) { - - if ( $.isPlainObject( val ) && - $.isPlainObject( opts[ key ] ) ) { - $.extend( opts[ key ], val ); - } else { - opts[ key ] = val; - } - - } else { // getter - return key ? opts[ key ] : opts; - } - }, - - /** - * 获取文件统计信息。返回一个包含一下信息的对象。 - * * `successNum` 上传成功的文件数 - * * `progressNum` 上传中的文件数 - * * `cancelNum` 被删除的文件数 - * * `invalidNum` 无效的文件数 - * * `uploadFailNum` 上传失败的文件数 - * * `queueNum` 还在队列中的文件数 - * * `interruptNum` 被暂停的文件数 - * @method getStats - * @grammar getStats() => Object - */ - getStats: function() { - // return this._mgr.getStats.apply( this._mgr, arguments ); - var stats = this.request('get-stats'); - - return stats ? { - successNum: stats.numOfSuccess, - progressNum: stats.numOfProgress, - - // who care? - // queueFailNum: 0, - cancelNum: stats.numOfCancel, - invalidNum: stats.numOfInvalid, - uploadFailNum: stats.numOfUploadFailed, - queueNum: stats.numOfQueue, - interruptNum: stats.numofInterrupt - } : {}; - }, - - // 需要重写此方法来来支持opts.onEvent和instance.onEvent的处理器 - trigger: function( type/*, args...*/ ) { - var args = [].slice.call( arguments, 1 ), - opts = this.options, - name = 'on' + type.substring( 0, 1 ).toUpperCase() + - type.substring( 1 ); - - if ( - // 调用通过on方法注册的handler. - Mediator.trigger.apply( this, arguments ) === false || - - // 调用opts.onEvent - $.isFunction( opts[ name ] ) && - opts[ name ].apply( this, args ) === false || - - // 调用this.onEvent - $.isFunction( this[ name ] ) && - this[ name ].apply( this, args ) === false || - - // 广播所有uploader的事件。 - Mediator.trigger.apply( Mediator, - [ this, type ].concat( args ) ) === false ) { - - return false; - } - - return true; - }, - - /** - * 销毁 webuploader 实例 - * @method destroy - * @grammar destroy() => undefined - */ - destroy: function() { - this.request( 'destroy', arguments ); - this.off(); - }, - - // widgets/widget.js将补充此方法的详细文档。 - request: Base.noop - }); - - /** - * 创建Uploader实例,等同于new Uploader( opts ); - * @method create - * @class Base - * @static - * @grammar Base.create( opts ) => Uploader - */ - Base.create = Uploader.create = function( opts ) { - return new Uploader( opts ); - }; - - // 暴露Uploader,可以通过它来扩展业务逻辑。 - Base.Uploader = Uploader; - - return Uploader; - }); - /** - * @fileOverview Runtime管理器,负责Runtime的选择, 连接 - */ - define('runtime/runtime',[ - 'base', - 'mediator' - ], function( Base, Mediator ) { - - var $ = Base.$, - factories = {}, - - // 获取对象的第一个key - getFirstKey = function( obj ) { - for ( var key in obj ) { - if ( obj.hasOwnProperty( key ) ) { - return key; - } - } - return null; - }; - - // 接口类。 - function Runtime( options ) { - this.options = $.extend({ - container: document.body - }, options ); - this.uid = Base.guid('rt_'); - } - - $.extend( Runtime.prototype, { - - getContainer: function() { - var opts = this.options, - parent, container; - - if ( this._container ) { - return this._container; - } - - parent = $( opts.container || document.body ); - container = $( document.createElement('div') ); - - container.attr( 'id', 'rt_' + this.uid ); - container.css({ - position: 'absolute', - top: '0px', - left: '0px', - width: '1px', - height: '1px', - overflow: 'hidden' - }); - - parent.append( container ); - parent.addClass('webuploader-container'); - this._container = container; - this._parent = parent; - return container; - }, - - init: Base.noop, - exec: Base.noop, - - destroy: function() { - this._container && this._container.remove(); - this._parent && this._parent.removeClass('webuploader-container'); - this.off(); - } - }); - - Runtime.orders = 'html5,flash'; - - - /** - * 添加Runtime实现。 - * @param {String} type 类型 - * @param {Runtime} factory 具体Runtime实现。 - */ - Runtime.addRuntime = function( type, factory ) { - factories[ type ] = factory; - }; - - Runtime.hasRuntime = function( type ) { - return !!(type ? factories[ type ] : getFirstKey( factories )); - }; - - Runtime.create = function( opts, orders ) { - var type, runtime; - - orders = orders || Runtime.orders; - $.each( orders.split( /\s*,\s*/g ), function() { - if ( factories[ this ] ) { - type = this; - return false; - } - }); - - type = type || getFirstKey( factories ); - - if ( !type ) { - throw new Error('Runtime Error'); - } - - runtime = new factories[ type ]( opts ); - return runtime; - }; - - Mediator.installTo( Runtime.prototype ); - return Runtime; - }); - - /** - * @fileOverview Runtime管理器,负责Runtime的选择, 连接 - */ - define('runtime/client',[ - 'base', - 'mediator', - 'runtime/runtime' - ], function( Base, Mediator, Runtime ) { - - var cache; - - cache = (function() { - var obj = {}; - - return { - add: function( runtime ) { - obj[ runtime.uid ] = runtime; - }, - - get: function( ruid, standalone ) { - var i; - - if ( ruid ) { - return obj[ ruid ]; - } - - for ( i in obj ) { - // 有些类型不能重用,比如filepicker. - if ( standalone && obj[ i ].__standalone ) { - continue; - } - - return obj[ i ]; - } - - return null; - }, - - remove: function( runtime ) { - delete obj[ runtime.uid ]; - } - }; - })(); - - function RuntimeClient( component, standalone ) { - var deferred = Base.Deferred(), - runtime; - - this.uid = Base.guid('client_'); - - // 允许runtime没有初始化之前,注册一些方法在初始化后执行。 - this.runtimeReady = function( cb ) { - return deferred.done( cb ); - }; - - this.connectRuntime = function( opts, cb ) { - - // already connected. - if ( runtime ) { - throw new Error('already connected!'); - } - - deferred.done( cb ); - - if ( typeof opts === 'string' && cache.get( opts ) ) { - runtime = cache.get( opts ); - } - - // 像filePicker只能独立存在,不能公用。 - runtime = runtime || cache.get( null, standalone ); - - // 需要创建 - if ( !runtime ) { - runtime = Runtime.create( opts, opts.runtimeOrder ); - runtime.__promise = deferred.promise(); - runtime.once( 'ready', deferred.resolve ); - runtime.init(); - cache.add( runtime ); - runtime.__client = 1; - } else { - // 来自cache - Base.$.extend( runtime.options, opts ); - runtime.__promise.then( deferred.resolve ); - runtime.__client++; - } - - standalone && (runtime.__standalone = standalone); - return runtime; - }; - - this.getRuntime = function() { - return runtime; - }; - - this.disconnectRuntime = function() { - if ( !runtime ) { - return; - } - - runtime.__client--; - - if ( runtime.__client <= 0 ) { - cache.remove( runtime ); - delete runtime.__promise; - runtime.destroy(); - } - - runtime = null; - }; - - this.exec = function() { - if ( !runtime ) { - return; - } - - var args = Base.slice( arguments ); - component && args.unshift( component ); - - return runtime.exec.apply( this, args ); - }; - - this.getRuid = function() { - return runtime && runtime.uid; - }; - - this.destroy = (function( destroy ) { - return function() { - destroy && destroy.apply( this, arguments ); - this.trigger('destroy'); - this.off(); - this.exec('destroy'); - this.disconnectRuntime(); - }; - })( this.destroy ); - } - - Mediator.installTo( RuntimeClient.prototype ); - return RuntimeClient; - }); - /** - * @fileOverview 错误信息 - */ - define('lib/dnd',[ - 'base', - 'mediator', - 'runtime/client' - ], function( Base, Mediator, RuntimeClent ) { - - var $ = Base.$; - - function DragAndDrop( opts ) { - opts = this.options = $.extend({}, DragAndDrop.options, opts ); - - opts.container = $( opts.container ); - - if ( !opts.container.length ) { - return; - } - - RuntimeClent.call( this, 'DragAndDrop' ); - } - - DragAndDrop.options = { - accept: null, - disableGlobalDnd: false - }; - - Base.inherits( RuntimeClent, { - constructor: DragAndDrop, - - init: function() { - var me = this; - - me.connectRuntime( me.options, function() { - me.exec('init'); - me.trigger('ready'); - }); - } - }); - - Mediator.installTo( DragAndDrop.prototype ); - - return DragAndDrop; - }); - /** - * @fileOverview 组件基类。 - */ - define('widgets/widget',[ - 'base', - 'uploader' - ], function( Base, Uploader ) { - - var $ = Base.$, - _init = Uploader.prototype._init, - _destroy = Uploader.prototype.destroy, - IGNORE = {}, - widgetClass = []; - - function isArrayLike( obj ) { - if ( !obj ) { - return false; - } - - var length = obj.length, - type = $.type( obj ); - - if ( obj.nodeType === 1 && length ) { - return true; - } - - return type === 'array' || type !== 'function' && type !== 'string' && - (length === 0 || typeof length === 'number' && length > 0 && - (length - 1) in obj); - } - - function Widget( uploader ) { - this.owner = uploader; - this.options = uploader.options; - } - - $.extend( Widget.prototype, { - - init: Base.noop, - - // 类Backbone的事件监听声明,监听uploader实例上的事件 - // widget直接无法监听事件,事件只能通过uploader来传递 - invoke: function( apiName, args ) { - - /* - { - 'make-thumb': 'makeThumb' - } - */ - var map = this.responseMap; - - // 如果无API响应声明则忽略 - if ( !map || !(apiName in map) || !(map[ apiName ] in this) || - !$.isFunction( this[ map[ apiName ] ] ) ) { - - return IGNORE; - } - - return this[ map[ apiName ] ].apply( this, args ); - - }, - - /** - * 发送命令。当传入`callback`或者`handler`中返回`promise`时。返回一个当所有`handler`中的promise都完成后完成的新`promise`。 - * @method request - * @grammar request( command, args ) => * | Promise - * @grammar request( command, args, callback ) => Promise - * @for Uploader - */ - request: function() { - return this.owner.request.apply( this.owner, arguments ); - } - }); - - // 扩展Uploader. - $.extend( Uploader.prototype, { - - /** - * @property {String | Array} [disableWidgets=undefined] - * @namespace options - * @for Uploader - * @description 默认所有 Uploader.register 了的 widget 都会被加载,如果禁用某一部分,请通过此 option 指定黑名单。 - */ - - // 覆写_init用来初始化widgets - _init: function() { - var me = this, - widgets = me._widgets = [], - deactives = me.options.disableWidgets || ''; - - $.each( widgetClass, function( _, klass ) { - (!deactives || !~deactives.indexOf( klass._name )) && - widgets.push( new klass( me ) ); - }); - - return _init.apply( me, arguments ); - }, - - request: function( apiName, args, callback ) { - var i = 0, - widgets = this._widgets, - len = widgets && widgets.length, - rlts = [], - dfds = [], - widget, rlt, promise, key; - - args = isArrayLike( args ) ? args : [ args ]; - - for ( ; i < len; i++ ) { - widget = widgets[ i ]; - rlt = widget.invoke( apiName, args ); - - if ( rlt !== IGNORE ) { - - // Deferred对象 - if ( Base.isPromise( rlt ) ) { - dfds.push( rlt ); - } else { - rlts.push( rlt ); - } - } - } - - // 如果有callback,则用异步方式。 - if ( callback || dfds.length ) { - promise = Base.when.apply( Base, dfds ); - key = promise.pipe ? 'pipe' : 'then'; - - // 很重要不能删除。删除了会死循环。 - // 保证执行顺序。让callback总是在下一个 tick 中执行。 - return promise[ key ](function() { - var deferred = Base.Deferred(), - args = arguments; - - if ( args.length === 1 ) { - args = args[ 0 ]; - } - - setTimeout(function() { - deferred.resolve( args ); - }, 1 ); - - return deferred.promise(); - })[ callback ? key : 'done' ]( callback || Base.noop ); - } else { - return rlts[ 0 ]; - } - }, - - destroy: function() { - _destroy.apply( this, arguments ); - this._widgets = null; - } - }); - - /** - * 添加组件 - * @grammar Uploader.register(proto); - * @grammar Uploader.register(map, proto); - * @param {object} responseMap API 名称与函数实现的映射 - * @param {object} proto 组件原型,构造函数通过 constructor 属性定义 - * @method Uploader.register - * @for Uploader - * @example - * Uploader.register({ - * 'make-thumb': 'makeThumb' - * }, { - * init: function( options ) {}, - * makeThumb: function() {} - * }); - * - * Uploader.register({ - * 'make-thumb': function() { - * - * } - * }); - */ - Uploader.register = Widget.register = function( responseMap, widgetProto ) { - var map = { init: 'init', destroy: 'destroy', name: 'anonymous' }, - klass; - - if ( arguments.length === 1 ) { - widgetProto = responseMap; - - // 自动生成 map 表。 - $.each(widgetProto, function(key) { - if ( key[0] === '_' || key === 'name' ) { - key === 'name' && (map.name = widgetProto.name); - return; - } - - map[key.replace(/[A-Z]/g, '-$&').toLowerCase()] = key; - }); - - } else { - map = $.extend( map, responseMap ); - } - - widgetProto.responseMap = map; - klass = Base.inherits( Widget, widgetProto ); - klass._name = map.name; - widgetClass.push( klass ); - - return klass; - }; - - /** - * 删除插件,只有在注册时指定了名字的才能被删除。 - * @grammar Uploader.unRegister(name); - * @param {string} name 组件名字 - * @method Uploader.unRegister - * @for Uploader - * @example - * - * Uploader.register({ - * name: 'custom', - * - * 'make-thumb': function() { - * - * } - * }); - * - * Uploader.unRegister('custom'); - */ - Uploader.unRegister = Widget.unRegister = function( name ) { - if ( !name || name === 'anonymous' ) { - return; - } - - // 删除指定的插件。 - for ( var i = widgetClass.length; i--; ) { - if ( widgetClass[i]._name === name ) { - widgetClass.splice(i, 1) - } - } - }; - - return Widget; - }); - /** - * @fileOverview DragAndDrop Widget。 - */ - define('widgets/filednd',[ - 'base', - 'uploader', - 'lib/dnd', - 'widgets/widget' - ], function( Base, Uploader, Dnd ) { - var $ = Base.$; - - Uploader.options.dnd = ''; - - /** - * @property {Selector} [dnd=undefined] 指定Drag And Drop拖拽的容器,如果不指定,则不启动。 - * @namespace options - * @for Uploader - */ - - /** - * @property {Selector} [disableGlobalDnd=false] 是否禁掉整个页面的拖拽功能,如果不禁用,图片拖进来的时候会默认被浏览器打开。 - * @namespace options - * @for Uploader - */ - - /** - * @event dndAccept - * @param {DataTransferItemList} items DataTransferItem - * @description 阻止此事件可以拒绝某些类型的文件拖入进来。目前只有 chrome 提供这样的 API,且只能通过 mime-type 验证。 - * @for Uploader - */ - return Uploader.register({ - name: 'dnd', - - init: function( opts ) { - - if ( !opts.dnd || - this.request('predict-runtime-type') !== 'html5' ) { - return; - } - - var me = this, - deferred = Base.Deferred(), - options = $.extend({}, { - disableGlobalDnd: opts.disableGlobalDnd, - container: opts.dnd, - accept: opts.accept - }), - dnd; - - this.dnd = dnd = new Dnd( options ); - - dnd.once( 'ready', deferred.resolve ); - dnd.on( 'drop', function( files ) { - me.request( 'add-file', [ files ]); - }); - - // 检测文件是否全部允许添加。 - dnd.on( 'accept', function( items ) { - return me.owner.trigger( 'dndAccept', items ); - }); - - dnd.init(); - - return deferred.promise(); - }, - - destroy: function() { - this.dnd && this.dnd.destroy(); - } - }); - }); - - /** - * @fileOverview 错误信息 - */ - define('lib/filepaste',[ - 'base', - 'mediator', - 'runtime/client' - ], function( Base, Mediator, RuntimeClent ) { - - var $ = Base.$; - - function FilePaste( opts ) { - opts = this.options = $.extend({}, opts ); - opts.container = $( opts.container || document.body ); - RuntimeClent.call( this, 'FilePaste' ); - } - - Base.inherits( RuntimeClent, { - constructor: FilePaste, - - init: function() { - var me = this; - - me.connectRuntime( me.options, function() { - me.exec('init'); - me.trigger('ready'); - }); - } - }); - - Mediator.installTo( FilePaste.prototype ); - - return FilePaste; - }); - /** - * @fileOverview 组件基类。 - */ - define('widgets/filepaste',[ - 'base', - 'uploader', - 'lib/filepaste', - 'widgets/widget' - ], function( Base, Uploader, FilePaste ) { - var $ = Base.$; - - /** - * @property {Selector} [paste=undefined] 指定监听paste事件的容器,如果不指定,不启用此功能。此功能为通过粘贴来添加截屏的图片。建议设置为`document.body`. - * @namespace options - * @for Uploader - */ - return Uploader.register({ - name: 'paste', - - init: function( opts ) { - - if ( !opts.paste || - this.request('predict-runtime-type') !== 'html5' ) { - return; - } - - var me = this, - deferred = Base.Deferred(), - options = $.extend({}, { - container: opts.paste, - accept: opts.accept - }), - paste; - - this.paste = paste = new FilePaste( options ); - - paste.once( 'ready', deferred.resolve ); - paste.on( 'paste', function( files ) { - me.owner.request( 'add-file', [ files ]); - }); - paste.init(); - - return deferred.promise(); - }, - - destroy: function() { - this.paste && this.paste.destroy(); - } - }); - }); - /** - * @fileOverview Blob - */ - define('lib/blob',[ - 'base', - 'runtime/client' - ], function( Base, RuntimeClient ) { - - function Blob( ruid, source ) { - var me = this; - - me.source = source; - me.ruid = ruid; - this.size = source.size || 0; - - // 如果没有指定 mimetype, 但是知道文件后缀。 - if ( !source.type && this.ext && - ~'jpg,jpeg,png,gif,bmp'.indexOf( this.ext ) ) { - this.type = 'image/' + (this.ext === 'jpg' ? 'jpeg' : this.ext); - } else { - this.type = source.type || 'application/octet-stream'; - } - - RuntimeClient.call( me, 'Blob' ); - this.uid = source.uid || this.uid; - - if ( ruid ) { - me.connectRuntime( ruid ); - } - } - - Base.inherits( RuntimeClient, { - constructor: Blob, - - slice: function( start, end ) { - return this.exec( 'slice', start, end ); - }, - - getSource: function() { - return this.source; - } - }); - - return Blob; - }); - /** - * 为了统一化Flash的File和HTML5的File而存在。 - * 以至于要调用Flash里面的File,也可以像调用HTML5版本的File一下。 - * @fileOverview File - */ - define('lib/file',[ - 'base', - 'lib/blob' - ], function( Base, Blob ) { - - var uid = 1, - rExt = /\.([^.]+)$/; - - function File( ruid, file ) { - var ext; - - this.name = file.name || ('untitled' + uid++); - ext = rExt.exec( file.name ) ? RegExp.$1.toLowerCase() : ''; - - // todo 支持其他类型文件的转换。 - // 如果有 mimetype, 但是文件名里面没有找出后缀规律 - if ( !ext && file.type ) { - ext = /\/(jpg|jpeg|png|gif|bmp)$/i.exec( file.type ) ? - RegExp.$1.toLowerCase() : ''; - this.name += '.' + ext; - } - - this.ext = ext; - this.lastModifiedDate = file.lastModifiedDate || - (new Date()).toLocaleString(); - - Blob.apply( this, arguments ); - } - - return Base.inherits( Blob, File ); - }); - - /** - * @fileOverview 错误信息 - */ - define('lib/filepicker',[ - 'base', - 'runtime/client', - 'lib/file' - ], function( Base, RuntimeClent, File ) { - - var $ = Base.$; - - function FilePicker( opts ) { - opts = this.options = $.extend({}, FilePicker.options, opts ); - - opts.container = $( opts.id ); - - if ( !opts.container.length ) { - throw new Error('按钮指定错误'); - } - - opts.innerHTML = opts.innerHTML || opts.label || - opts.container.html() || ''; - - opts.button = $( opts.button || document.createElement('div') ); - opts.button.html( opts.innerHTML ); - opts.container.html( opts.button ); - - RuntimeClent.call( this, 'FilePicker', true ); - } - - FilePicker.options = { - button: null, - container: null, - label: null, - innerHTML: null, - multiple: true, - accept: null, - name: 'file' - }; - - Base.inherits( RuntimeClent, { - constructor: FilePicker, - - init: function() { - var me = this, - opts = me.options, - button = opts.button; - - button.addClass('webuploader-pick'); - - me.on( 'all', function( type ) { - var files; - - switch ( type ) { - case 'mouseenter': - button.addClass('webuploader-pick-hover'); - break; - - case 'mouseleave': - button.removeClass('webuploader-pick-hover'); - break; - - case 'change': - files = me.exec('getFiles'); - me.trigger( 'select', $.map( files, function( file ) { - file = new File( me.getRuid(), file ); - - // 记录来源。 - file._refer = opts.container; - return file; - }), opts.container ); - break; - } - }); - - me.connectRuntime( opts, function() { - me.refresh(); - me.exec( 'init', opts ); - me.trigger('ready'); - }); - - this._resizeHandler = Base.bindFn( this.refresh, this ); - $( window ).on( 'resize', this._resizeHandler ); - }, - - refresh: function() { - var shimContainer = this.getRuntime().getContainer(), - button = this.options.button, - width = button.outerWidth ? - button.outerWidth() : button.width(), - - height = button.outerHeight ? - button.outerHeight() : button.height(), - - pos = button.offset(); - - width && height && shimContainer.css({ - bottom: 'auto', - right: 'auto', - width: width + 'px', - height: height + 'px' - }).offset( pos ); - }, - - enable: function() { - var btn = this.options.button; - - btn.removeClass('webuploader-pick-disable'); - this.refresh(); - }, - - disable: function() { - var btn = this.options.button; - - this.getRuntime().getContainer().css({ - top: '-99999px' - }); - - btn.addClass('webuploader-pick-disable'); - }, - - destroy: function() { - var btn = this.options.button; - $( window ).off( 'resize', this._resizeHandler ); - btn.removeClass('webuploader-pick-disable webuploader-pick-hover ' + - 'webuploader-pick'); - } - }); - - return FilePicker; - }); - - /** - * @fileOverview 文件选择相关 - */ - define('widgets/filepicker',[ - 'base', - 'uploader', - 'lib/filepicker', - 'widgets/widget' - ], function( Base, Uploader, FilePicker ) { - var $ = Base.$; - - $.extend( Uploader.options, { - - /** - * @property {Selector | Object} [pick=undefined] - * @namespace options - * @for Uploader - * @description 指定选择文件的按钮容器,不指定则不创建按钮。 - * - * * `id` {Seletor|dom} 指定选择文件的按钮容器,不指定则不创建按钮。**注意** 这里虽然写的是 id, 但是不是只支持 id, 还支持 class, 或者 dom 节点。 - * * `label` {String} 请采用 `innerHTML` 代替 - * * `innerHTML` {String} 指定按钮文字。不指定时优先从指定的容器中看是否自带文字。 - * * `multiple` {Boolean} 是否开起同时选择多个文件能力。 - */ - pick: null, - - /** - * @property {Arroy} [accept=null] - * @namespace options - * @for Uploader - * @description 指定接受哪些类型的文件。 由于目前还有ext转mimeType表,所以这里需要分开指定。 - * - * * `title` {String} 文字描述 - * * `extensions` {String} 允许的文件后缀,不带点,多个用逗号分割。 - * * `mimeTypes` {String} 多个用逗号分割。 - * - * 如: - * - * ``` - * { - * title: 'Images', - * extensions: 'gif,jpg,jpeg,bmp,png', - * mimeTypes: 'image/*' - * } - * ``` - */ - accept: null/*{ - title: 'Images', - extensions: 'gif,jpg,jpeg,bmp,png', - mimeTypes: 'image/*' - }*/ - }); - - return Uploader.register({ - name: 'picker', - - init: function( opts ) { - this.pickers = []; - return opts.pick && this.addBtn( opts.pick ); - }, - - refresh: function() { - $.each( this.pickers, function() { - this.refresh(); - }); - }, - - /** - * @method addButton - * @for Uploader - * @grammar addButton( pick ) => Promise - * @description - * 添加文件选择按钮,如果一个按钮不够,需要调用此方法来添加。参数跟[options.pick](#WebUploader:Uploader:options)一致。 - * @example - * uploader.addButton({ - * id: '#btnContainer', - * innerHTML: '选择文件' - * }); - */ - addBtn: function( pick ) { - var me = this, - opts = me.options, - accept = opts.accept, - promises = []; - - if ( !pick ) { - return; - } - - $.isPlainObject( pick ) || (pick = { - id: pick - }); - - $( pick.id ).each(function() { - var options, picker, deferred; - - deferred = Base.Deferred(); - - options = $.extend({}, pick, { - accept: $.isPlainObject( accept ) ? [ accept ] : accept, - swf: opts.swf, - runtimeOrder: opts.runtimeOrder, - id: this - }); - - picker = new FilePicker( options ); - - picker.once( 'ready', deferred.resolve ); - picker.on( 'select', function( files ) { - me.owner.request( 'add-file', [ files ]); - }); - picker.init(); - - me.pickers.push( picker ); - - promises.push( deferred.promise() ); - }); - - return Base.when.apply( Base, promises ); - }, - - disable: function() { - $.each( this.pickers, function() { - this.disable(); - }); - }, - - enable: function() { - $.each( this.pickers, function() { - this.enable(); - }); - }, - - destroy: function() { - $.each( this.pickers, function() { - this.destroy(); - }); - this.pickers = null; - } - }); - }); - /** - * @fileOverview 文件属性封装 - */ - define('file',[ - 'base', - 'mediator' - ], function( Base, Mediator ) { - - var $ = Base.$, - idPrefix = 'WU_FILE_', - idSuffix = 0, - rExt = /\.([^.]+)$/, - statusMap = {}; - - function gid() { - return idPrefix + idSuffix++; - } - - /** - * 文件类 - * @class File - * @constructor 构造函数 - * @grammar new File( source ) => File - * @param {Lib.File} source [lib.File](#Lib.File)实例, 此source对象是带有Runtime信息的。 - */ - function WUFile( source ) { - - /** - * 文件名,包括扩展名(后缀) - * @property name - * @type {string} - */ - this.name = source.name || 'Untitled'; - - /** - * 文件体积(字节) - * @property size - * @type {uint} - * @default 0 - */ - this.size = source.size || 0; - - /** - * 文件MIMETYPE类型,与文件类型的对应关系请参考[http://t.cn/z8ZnFny](http://t.cn/z8ZnFny) - * @property type - * @type {string} - * @default 'application/octet-stream' - */ - this.type = source.type || 'application/octet-stream'; - - /** - * 文件最后修改日期 - * @property lastModifiedDate - * @type {int} - * @default 当前时间戳 - */ - this.lastModifiedDate = source.lastModifiedDate || (new Date() * 1); - - /** - * 文件ID,每个对象具有唯一ID,与文件名无关 - * @property id - * @type {string} - */ - this.id = gid(); - - /** - * 文件扩展名,通过文件名获取,例如test.png的扩展名为png - * @property ext - * @type {string} - */ - this.ext = rExt.exec( this.name ) ? RegExp.$1 : ''; - - - /** - * 状态文字说明。在不同的status语境下有不同的用途。 - * @property statusText - * @type {string} - */ - this.statusText = ''; - - // 存储文件状态,防止通过属性直接修改 - statusMap[ this.id ] = WUFile.Status.INITED; - - this.source = source; - this.loaded = 0; - - this.on( 'error', function( msg ) { - this.setStatus( WUFile.Status.ERROR, msg ); - }); - } - - $.extend( WUFile.prototype, { - - /** - * 设置状态,状态变化时会触发`change`事件。 - * @method setStatus - * @grammar setStatus( status[, statusText] ); - * @param {File.Status|String} status [文件状态值](#WebUploader:File:File.Status) - * @param {String} [statusText=''] 状态说明,常在error时使用,用http, abort,server等来标记是由于什么原因导致文件错误。 - */ - setStatus: function( status, text ) { - - var prevStatus = statusMap[ this.id ]; - - typeof text !== 'undefined' && (this.statusText = text); - - if ( status !== prevStatus ) { - statusMap[ this.id ] = status; - /** - * 文件状态变化 - * @event statuschange - */ - this.trigger( 'statuschange', status, prevStatus ); - } - - }, - - /** - * 获取文件状态 - * @return {File.Status} - * @example - 文件状态具体包括以下几种类型: - { - // 初始化 - INITED: 0, - // 已入队列 - QUEUED: 1, - // 正在上传 - PROGRESS: 2, - // 上传出错 - ERROR: 3, - // 上传成功 - COMPLETE: 4, - // 上传取消 - CANCELLED: 5 - } - */ - getStatus: function() { - return statusMap[ this.id ]; - }, - - /** - * 获取文件原始信息。 - * @return {*} - */ - getSource: function() { - return this.source; - }, - - destroy: function() { - this.off(); - delete statusMap[ this.id ]; - } - }); - - Mediator.installTo( WUFile.prototype ); - - /** - * 文件状态值,具体包括以下几种类型: - * * `inited` 初始状态 - * * `queued` 已经进入队列, 等待上传 - * * `progress` 上传中 - * * `complete` 上传完成。 - * * `error` 上传出错,可重试 - * * `interrupt` 上传中断,可续传。 - * * `invalid` 文件不合格,不能重试上传。会自动从队列中移除。 - * * `cancelled` 文件被移除。 - * @property {Object} Status - * @namespace File - * @class File - * @static - */ - WUFile.Status = { - INITED: 'inited', // 初始状态 - QUEUED: 'queued', // 已经进入队列, 等待上传 - PROGRESS: 'progress', // 上传中 - ERROR: 'error', // 上传出错,可重试 - COMPLETE: 'complete', // 上传完成。 - CANCELLED: 'cancelled', // 上传取消。 - INTERRUPT: 'interrupt', // 上传中断,可续传。 - INVALID: 'invalid' // 文件不合格,不能重试上传。 - }; - - return WUFile; - }); - - /** - * @fileOverview 文件队列 - */ - define('queue',[ - 'base', - 'mediator', - 'file' - ], function( Base, Mediator, WUFile ) { - - var $ = Base.$, - STATUS = WUFile.Status; - - /** - * 文件队列, 用来存储各个状态中的文件。 - * @class Queue - * @extends Mediator - */ - function Queue() { - - /** - * 统计文件数。 - * * `numOfQueue` 队列中的文件数。 - * * `numOfSuccess` 上传成功的文件数 - * * `numOfCancel` 被取消的文件数 - * * `numOfProgress` 正在上传中的文件数 - * * `numOfUploadFailed` 上传错误的文件数。 - * * `numOfInvalid` 无效的文件数。 - * * `numofDeleted` 被移除的文件数。 - * @property {Object} stats - */ - this.stats = { - numOfQueue: 0, - numOfSuccess: 0, - numOfCancel: 0, - numOfProgress: 0, - numOfUploadFailed: 0, - numOfInvalid: 0, - numofDeleted: 0, - numofInterrupt: 0 - }; - - // 上传队列,仅包括等待上传的文件 - this._queue = []; - - // 存储所有文件 - this._map = {}; - } - - $.extend( Queue.prototype, { - - /** - * 将新文件加入对队列尾部 - * - * @method append - * @param {File} file 文件对象 - */ - append: function( file ) { - this._queue.push( file ); - this._fileAdded( file ); - return this; - }, - - /** - * 将新文件加入对队列头部 - * - * @method prepend - * @param {File} file 文件对象 - */ - prepend: function( file ) { - this._queue.unshift( file ); - this._fileAdded( file ); - return this; - }, - - /** - * 获取文件对象 - * - * @method getFile - * @param {String} fileId 文件ID - * @return {File} - */ - getFile: function( fileId ) { - if ( typeof fileId !== 'string' ) { - return fileId; - } - return this._map[ fileId ]; - }, - - /** - * 从队列中取出一个指定状态的文件。 - * @grammar fetch( status ) => File - * @method fetch - * @param {String} status [文件状态值](#WebUploader:File:File.Status) - * @return {File} [File](#WebUploader:File) - */ - fetch: function( status ) { - var len = this._queue.length, - i, file; - - status = status || STATUS.QUEUED; - - for ( i = 0; i < len; i++ ) { - file = this._queue[ i ]; - - if ( status === file.getStatus() ) { - return file; - } - } - - return null; - }, - - /** - * 对队列进行排序,能够控制文件上传顺序。 - * @grammar sort( fn ) => undefined - * @method sort - * @param {Function} fn 排序方法 - */ - sort: function( fn ) { - if ( typeof fn === 'function' ) { - this._queue.sort( fn ); - } - }, - - /** - * 获取指定类型的文件列表, 列表中每一个成员为[File](#WebUploader:File)对象。 - * @grammar getFiles( [status1[, status2 ...]] ) => Array - * @method getFiles - * @param {String} [status] [文件状态值](#WebUploader:File:File.Status) - */ - getFiles: function() { - var sts = [].slice.call( arguments, 0 ), - ret = [], - i = 0, - len = this._queue.length, - file; - - for ( ; i < len; i++ ) { - file = this._queue[ i ]; - - if ( sts.length && !~$.inArray( file.getStatus(), sts ) ) { - continue; - } - - ret.push( file ); - } - - return ret; - }, - - /** - * 在队列中删除文件。 - * @grammar removeFile( file ) => Array - * @method removeFile - * @param {File} 文件对象。 - */ - removeFile: function( file ) { - var me = this, - existing = this._map[ file.id ]; - - if ( existing ) { - delete this._map[ file.id ]; - file.destroy(); - this.stats.numofDeleted++; - } - }, - - _fileAdded: function( file ) { - var me = this, - existing = this._map[ file.id ]; - - if ( !existing ) { - this._map[ file.id ] = file; - - file.on( 'statuschange', function( cur, pre ) { - me._onFileStatusChange( cur, pre ); - }); - } - }, - - _onFileStatusChange: function( curStatus, preStatus ) { - var stats = this.stats; - - switch ( preStatus ) { - case STATUS.PROGRESS: - stats.numOfProgress--; - break; - - case STATUS.QUEUED: - stats.numOfQueue --; - break; - - case STATUS.ERROR: - stats.numOfUploadFailed--; - break; - - case STATUS.INVALID: - stats.numOfInvalid--; - break; - - case STATUS.INTERRUPT: - stats.numofInterrupt--; - break; - } - - switch ( curStatus ) { - case STATUS.QUEUED: - stats.numOfQueue++; - break; - - case STATUS.PROGRESS: - stats.numOfProgress++; - break; - - case STATUS.ERROR: - stats.numOfUploadFailed++; - break; - - case STATUS.COMPLETE: - stats.numOfSuccess++; - break; - - case STATUS.CANCELLED: - stats.numOfCancel++; - break; - - - case STATUS.INVALID: - stats.numOfInvalid++; - break; - - case STATUS.INTERRUPT: - stats.numofInterrupt++; - break; - } - } - - }); - - Mediator.installTo( Queue.prototype ); - - return Queue; - }); - /** - * @fileOverview 队列 - */ - define('widgets/queue',[ - 'base', - 'uploader', - 'queue', - 'file', - 'lib/file', - 'runtime/client', - 'widgets/widget' - ], function( Base, Uploader, Queue, WUFile, File, RuntimeClient ) { - - var $ = Base.$, - rExt = /\.\w+$/, - Status = WUFile.Status; - - return Uploader.register({ - name: 'queue', - - init: function( opts ) { - var me = this, - deferred, len, i, item, arr, accept, runtime; - - if ( $.isPlainObject( opts.accept ) ) { - opts.accept = [ opts.accept ]; - } - - // accept中的中生成匹配正则。 - if ( opts.accept ) { - arr = []; - - for ( i = 0, len = opts.accept.length; i < len; i++ ) { - item = opts.accept[ i ].extensions; - item && arr.push( item ); - } - - if ( arr.length ) { - accept = '\\.' + arr.join(',') - .replace( /,/g, '$|\\.' ) - .replace( /\*/g, '.*' ) + '$'; - } - - me.accept = new RegExp( accept, 'i' ); - } - - me.queue = new Queue(); - me.stats = me.queue.stats; - - // 如果当前不是html5运行时,那就算了。 - // 不执行后续操作 - if ( this.request('predict-runtime-type') !== 'html5' ) { - return; - } - - // 创建一个 html5 运行时的 placeholder - // 以至于外部添加原生 File 对象的时候能正确包裹一下供 webuploader 使用。 - deferred = Base.Deferred(); - this.placeholder = runtime = new RuntimeClient('Placeholder'); - runtime.connectRuntime({ - runtimeOrder: 'html5' - }, function() { - me._ruid = runtime.getRuid(); - deferred.resolve(); - }); - return deferred.promise(); - }, - - - // 为了支持外部直接添加一个原生File对象。 - _wrapFile: function( file ) { - if ( !(file instanceof WUFile) ) { - - if ( !(file instanceof File) ) { - if ( !this._ruid ) { - throw new Error('Can\'t add external files.'); - } - file = new File( this._ruid, file ); - } - - file = new WUFile( file ); - } - - return file; - }, - - // 判断文件是否可以被加入队列 - acceptFile: function( file ) { - var invalid = !file || !file.size || this.accept && - - // 如果名字中有后缀,才做后缀白名单处理。 - rExt.exec( file.name ) && !this.accept.test( file.name ); - - return !invalid; - }, - - - /** - * @event beforeFileQueued - * @param {File} file File对象 - * @description 当文件被加入队列之前触发,此事件的handler返回值为`false`,则此文件不会被添加进入队列。 - * @for Uploader - */ - - /** - * @event fileQueued - * @param {File} file File对象 - * @description 当文件被加入队列以后触发。 - * @for Uploader - */ - - _addFile: function( file ) { - var me = this; - - file = me._wrapFile( file ); - - // 不过类型判断允许不允许,先派送 `beforeFileQueued` - if ( !me.owner.trigger( 'beforeFileQueued', file ) ) { - return; - } - - // 类型不匹配,则派送错误事件,并返回。 - if ( !me.acceptFile( file ) ) { - me.owner.trigger( 'error', 'Q_TYPE_DENIED', file ); - return; - } - - me.queue.append( file ); - me.owner.trigger( 'fileQueued', file ); - return file; - }, - - getFile: function( fileId ) { - return this.queue.getFile( fileId ); - }, - - /** - * @event filesQueued - * @param {File} files 数组,内容为原始File(lib/File)对象。 - * @description 当一批文件添加进队列以后触发。 - * @for Uploader - */ - - /** - * @property {Boolean} [auto=false] - * @namespace options - * @for Uploader - * @description 设置为 true 后,不需要手动调用上传,有文件选择即开始上传。 - * - */ - - /** - * @method addFiles - * @grammar addFiles( file ) => undefined - * @grammar addFiles( [file1, file2 ...] ) => undefined - * @param {Array of File or File} [files] Files 对象 数组 - * @description 添加文件到队列 - * @for Uploader - */ - addFile: function( files ) { - var me = this; - - if ( !files.length ) { - files = [ files ]; - } - - files = $.map( files, function( file ) { - return me._addFile( file ); - }); - - me.owner.trigger( 'filesQueued', files ); - - if ( me.options.auto ) { - setTimeout(function() { - me.request('start-upload'); - }, 20 ); - } - }, - - getStats: function() { - return this.stats; - }, - - /** - * @event fileDequeued - * @param {File} file File对象 - * @description 当文件被移除队列后触发。 - * @for Uploader - */ - - /** - * @method removeFile - * @grammar removeFile( file ) => undefined - * @grammar removeFile( id ) => undefined - * @grammar removeFile( file, true ) => undefined - * @grammar removeFile( id, true ) => undefined - * @param {File|id} file File对象或这File对象的id - * @description 移除某一文件, 默认只会标记文件状态为已取消,如果第二个参数为 `true` 则会从 queue 中移除。 - * @for Uploader - * @example - * - * $li.on('click', '.remove-this', function() { - * uploader.removeFile( file ); - * }) - */ - removeFile: function( file, remove ) { - var me = this; - - file = file.id ? file : me.queue.getFile( file ); - - this.request( 'cancel-file', file ); - - if ( remove ) { - this.queue.removeFile( file ); - } - }, - - /** - * @method getFiles - * @grammar getFiles() => Array - * @grammar getFiles( status1, status2, status... ) => Array - * @description 返回指定状态的文件集合,不传参数将返回所有状态的文件。 - * @for Uploader - * @example - * console.log( uploader.getFiles() ); // => all files - * console.log( uploader.getFiles('error') ) // => all error files. - */ - getFiles: function() { - return this.queue.getFiles.apply( this.queue, arguments ); - }, - - fetchFile: function() { - return this.queue.fetch.apply( this.queue, arguments ); - }, - - /** - * @method retry - * @grammar retry() => undefined - * @grammar retry( file ) => undefined - * @description 重试上传,重试指定文件,或者从出错的文件开始重新上传。 - * @for Uploader - * @example - * function retry() { - * uploader.retry(); - * } - */ - retry: function( file, noForceStart ) { - var me = this, - files, i, len; - - if ( file ) { - file = file.id ? file : me.queue.getFile( file ); - file.setStatus( Status.QUEUED ); - noForceStart || me.request('start-upload'); - return; - } - - files = me.queue.getFiles( Status.ERROR ); - i = 0; - len = files.length; - - for ( ; i < len; i++ ) { - file = files[ i ]; - file.setStatus( Status.QUEUED ); - } - - me.request('start-upload'); - }, - - /** - * @method sort - * @grammar sort( fn ) => undefined - * @description 排序队列中的文件,在上传之前调整可以控制上传顺序。 - * @for Uploader - */ - sortFiles: function() { - return this.queue.sort.apply( this.queue, arguments ); - }, - - /** - * @event reset - * @description 当 uploader 被重置的时候触发。 - * @for Uploader - */ - - /** - * @method reset - * @grammar reset() => undefined - * @description 重置uploader。目前只重置了队列。 - * @for Uploader - * @example - * uploader.reset(); - */ - reset: function() { - this.owner.trigger('reset'); - this.queue = new Queue(); - this.stats = this.queue.stats; - }, - - destroy: function() { - this.reset(); - this.placeholder && this.placeholder.destroy(); - } - }); - - }); - /** - * @fileOverview 添加获取Runtime相关信息的方法。 - */ - define('widgets/runtime',[ - 'uploader', - 'runtime/runtime', - 'widgets/widget' - ], function( Uploader, Runtime ) { - - Uploader.support = function() { - return Runtime.hasRuntime.apply( Runtime, arguments ); - }; - - /** - * @property {Object} [runtimeOrder=html5,flash] - * @namespace options - * @for Uploader - * @description 指定运行时启动顺序。默认会想尝试 html5 是否支持,如果支持则使用 html5, 否则则使用 flash. - * - * 可以将此值设置成 `flash`,来强制使用 flash 运行时。 - */ - - return Uploader.register({ - name: 'runtime', - - init: function() { - if ( !this.predictRuntimeType() ) { - throw Error('Runtime Error'); - } - }, - - /** - * 预测Uploader将采用哪个`Runtime` - * @grammar predictRuntimeType() => String - * @method predictRuntimeType - * @for Uploader - */ - predictRuntimeType: function() { - var orders = this.options.runtimeOrder || Runtime.orders, - type = this.type, - i, len; - - if ( !type ) { - orders = orders.split( /\s*,\s*/g ); - - for ( i = 0, len = orders.length; i < len; i++ ) { - if ( Runtime.hasRuntime( orders[ i ] ) ) { - this.type = type = orders[ i ]; - break; - } - } - } - - return type; - } - }); - }); - /** - * @fileOverview Transport - */ - define('lib/transport',[ - 'base', - 'runtime/client', - 'mediator' - ], function( Base, RuntimeClient, Mediator ) { - - var $ = Base.$; - - function Transport( opts ) { - var me = this; - - opts = me.options = $.extend( true, {}, Transport.options, opts || {} ); - RuntimeClient.call( this, 'Transport' ); - - this._blob = null; - this._formData = opts.formData || {}; - this._headers = opts.headers || {}; - - this.on( 'progress', this._timeout ); - this.on( 'load error', function() { - me.trigger( 'progress', 1 ); - clearTimeout( me._timer ); - }); - } - - Transport.options = { - server: '', - method: 'POST', - - // 跨域时,是否允许携带cookie, 只有html5 runtime才有效 - withCredentials: false, - fileVal: 'file', - timeout: 2 * 60 * 1000, // 2分钟 - formData: {}, - headers: {}, - sendAsBinary: false - }; - - $.extend( Transport.prototype, { - - // 添加Blob, 只能添加一次,最后一次有效。 - appendBlob: function( key, blob, filename ) { - var me = this, - opts = me.options; - - if ( me.getRuid() ) { - me.disconnectRuntime(); - } - - // 连接到blob归属的同一个runtime. - me.connectRuntime( blob.ruid, function() { - me.exec('init'); - }); - - me._blob = blob; - opts.fileVal = key || opts.fileVal; - opts.filename = filename || opts.filename; - }, - - // 添加其他字段 - append: function( key, value ) { - if ( typeof key === 'object' ) { - $.extend( this._formData, key ); - } else { - this._formData[ key ] = value; - } - }, - - setRequestHeader: function( key, value ) { - if ( typeof key === 'object' ) { - $.extend( this._headers, key ); - } else { - this._headers[ key ] = value; - } - }, - - send: function( method ) { - this.exec( 'send', method ); - this._timeout(); - }, - - abort: function() { - clearTimeout( this._timer ); - return this.exec('abort'); - }, - - destroy: function() { - this.trigger('destroy'); - this.off(); - this.exec('destroy'); - this.disconnectRuntime(); - }, - - getResponse: function() { - return this.exec('getResponse'); - }, - - getResponseAsJson: function() { - return this.exec('getResponseAsJson'); - }, - - getStatus: function() { - return this.exec('getStatus'); - }, - - _timeout: function() { - var me = this, - duration = me.options.timeout; - - if ( !duration ) { - return; - } - - clearTimeout( me._timer ); - me._timer = setTimeout(function() { - me.abort(); - me.trigger( 'error', 'timeout' ); - }, duration ); - } - - }); - - // 让Transport具备事件功能。 - Mediator.installTo( Transport.prototype ); - - return Transport; - }); - /** - * @fileOverview 负责文件上传相关。 - */ - define('widgets/upload',[ - 'base', - 'uploader', - 'file', - 'lib/transport', - 'widgets/widget' - ], function( Base, Uploader, WUFile, Transport ) { - - var $ = Base.$, - isPromise = Base.isPromise, - Status = WUFile.Status; - - // 添加默认配置项 - $.extend( Uploader.options, { - - - /** - * @property {Boolean} [prepareNextFile=false] - * @namespace options - * @for Uploader - * @description 是否允许在文件传输时提前把下一个文件准备好。 - * 对于一个文件的准备工作比较耗时,比如图片压缩,md5序列化。 - * 如果能提前在当前文件传输期处理,可以节省总体耗时。 - */ - prepareNextFile: false, - - /** - * @property {Boolean} [chunked=false] - * @namespace options - * @for Uploader - * @description 是否要分片处理大文件上传。 - */ - chunked: false, - - /** - * @property {Boolean} [chunkSize=5242880] - * @namespace options - * @for Uploader - * @description 如果要分片,分多大一片? 默认大小为5M. - */ - chunkSize: 5 * 1024 * 1024, - - /** - * @property {Boolean} [chunkRetry=2] - * @namespace options - * @for Uploader - * @description 如果某个分片由于网络问题出错,允许自动重传多少次? - */ - chunkRetry: 2, - - /** - * @property {Boolean} [threads=3] - * @namespace options - * @for Uploader - * @description 上传并发数。允许同时最大上传进程数。 - */ - threads: 3, - - - /** - * @property {Object} [formData={}] - * @namespace options - * @for Uploader - * @description 文件上传请求的参数表,每次发送都会发送此对象中的参数。 - */ - formData: {} - - /** - * @property {Object} [fileVal='file'] - * @namespace options - * @for Uploader - * @description 设置文件上传域的name。 - */ - - /** - * @property {Object} [method='POST'] - * @namespace options - * @for Uploader - * @description 文件上传方式,`POST`或者`GET`。 - */ - - /** - * @property {Object} [sendAsBinary=false] - * @namespace options - * @for Uploader - * @description 是否已二进制的流的方式发送文件,这样整个上传内容`php://input`都为文件内容, - * 其他参数在$_GET数组中。 - */ - }); - - // 负责将文件切片。 - function CuteFile( file, chunkSize ) { - var pending = [], - blob = file.source, - total = blob.size, - chunks = chunkSize ? Math.ceil( total / chunkSize ) : 1, - start = 0, - index = 0, - len, api; - - api = { - file: file, - - has: function() { - return !!pending.length; - }, - - shift: function() { - return pending.shift(); - }, - - unshift: function( block ) { - pending.unshift( block ); - } - }; - - while ( index < chunks ) { - len = Math.min( chunkSize, total - start ); - - pending.push({ - file: file, - start: start, - end: chunkSize ? (start + len) : total, - total: total, - chunks: chunks, - chunk: index++, - cuted: api - }); - start += len; - } - - file.blocks = pending.concat(); - file.remaning = pending.length; - - return api; - } - - Uploader.register({ - name: 'upload', - - init: function() { - var owner = this.owner, - me = this; - - this.runing = false; - this.progress = false; - - owner - .on( 'startUpload', function() { - me.progress = true; - }) - .on( 'uploadFinished', function() { - me.progress = false; - }); - - // 记录当前正在传的数据,跟threads相关 - this.pool = []; - - // 缓存分好片的文件。 - this.stack = []; - - // 缓存即将上传的文件。 - this.pending = []; - - // 跟踪还有多少分片在上传中但是没有完成上传。 - this.remaning = 0; - this.__tick = Base.bindFn( this._tick, this ); - - owner.on( 'uploadComplete', function( file ) { - - // 把其他块取消了。 - file.blocks && $.each( file.blocks, function( _, v ) { - v.transport && (v.transport.abort(), v.transport.destroy()); - delete v.transport; - }); - - delete file.blocks; - delete file.remaning; - }); - }, - - reset: function() { - this.request( 'stop-upload', true ); - this.runing = false; - this.pool = []; - this.stack = []; - this.pending = []; - this.remaning = 0; - this._trigged = false; - this._promise = null; - }, - - /** - * @event startUpload - * @description 当开始上传流程时触发。 - * @for Uploader - */ - - /** - * 开始上传。此方法可以从初始状态调用开始上传流程,也可以从暂停状态调用,继续上传流程。 - * - * 可以指定开始某一个文件。 - * @grammar upload() => undefined - * @grammar upload( file | fileId) => undefined - * @method upload - * @for Uploader - */ - startUpload: function(file) { - var me = this; - - // 移出invalid的文件 - $.each( me.request( 'get-files', Status.INVALID ), function() { - me.request( 'remove-file', this ); - }); - - // 如果指定了开始某个文件,则只开始指定文件。 - if ( file ) { - file = file.id ? file : me.request( 'get-file', file ); - - if (file.getStatus() === Status.INTERRUPT) { - $.each( me.pool, function( _, v ) { - - // 之前暂停过。 - if (v.file !== file) { - return; - } - - v.transport && v.transport.send(); - }); - - file.setStatus( Status.QUEUED ); - } else if (file.getStatus() === Status.PROGRESS) { - return; - } else { - file.setStatus( Status.QUEUED ); - } - } else { - $.each( me.request( 'get-files', [ Status.INITED ] ), function() { - this.setStatus( Status.QUEUED ); - }); - } - - if ( me.runing ) { - return; - } - - me.runing = true; - - var files = []; - - // 如果有暂停的,则续传 - $.each( me.pool, function( _, v ) { - var file = v.file; - - if ( file.getStatus() === Status.INTERRUPT ) { - files.push(file); - me._trigged = false; - v.transport && v.transport.send(); - } - }); - - var file; - while ( (file = files.shift()) ) { - file.setStatus( Status.PROGRESS ); - } - - file || $.each( me.request( 'get-files', - Status.INTERRUPT ), function() { - this.setStatus( Status.PROGRESS ); - }); - - me._trigged = false; - Base.nextTick( me.__tick ); - me.owner.trigger('startUpload'); - }, - - /** - * @event stopUpload - * @description 当开始上传流程暂停时触发。 - * @for Uploader - */ - - /** - * 暂停上传。第一个参数为是否中断上传当前正在上传的文件。 - * - * 如果第一个参数是文件,则只暂停指定文件。 - * @grammar stop() => undefined - * @grammar stop( true ) => undefined - * @grammar stop( file ) => undefined - * @method stop - * @for Uploader - */ - stopUpload: function( file, interrupt ) { - var me = this; - - if (file === true) { - interrupt = file; - file = null; - } - - if ( me.runing === false ) { - return; - } - - // 如果只是暂停某个文件。 - if ( file ) { - file = file.id ? file : me.request( 'get-file', file ); - - if ( file.getStatus() !== Status.PROGRESS && - file.getStatus() !== Status.QUEUED ) { - return; - } - - file.setStatus( Status.INTERRUPT ); - $.each( me.pool, function( _, v ) { - - // 只 abort 指定的文件。 - if (v.file !== file) { - return; - } - - v.transport && v.transport.abort(); - me._putback(v); - me._popBlock(v); - }); - - return Base.nextTick( me.__tick ); - } - - me.runing = false; - - if (this._promise && this._promise.file) { - this._promise.file.setStatus( Status.INTERRUPT ); - } - - interrupt && $.each( me.pool, function( _, v ) { - v.transport && v.transport.abort(); - v.file.setStatus( Status.INTERRUPT ); - }); - - me.owner.trigger('stopUpload'); - }, - - /** - * @method cancelFile - * @grammar cancelFile( file ) => undefined - * @grammar cancelFile( id ) => undefined - * @param {File|id} file File对象或这File对象的id - * @description 标记文件状态为已取消, 同时将中断文件传输。 - * @for Uploader - * @example - * - * $li.on('click', '.remove-this', function() { - * uploader.cancelFile( file ); - * }) - */ - cancelFile: function( file ) { - file = file.id ? file : this.request( 'get-file', file ); - - // 如果正在上传。 - file.blocks && $.each( file.blocks, function( _, v ) { - var _tr = v.transport; - - if ( _tr ) { - _tr.abort(); - _tr.destroy(); - delete v.transport; - } - }); - - file.setStatus( Status.CANCELLED ); - this.owner.trigger( 'fileDequeued', file ); - }, - - /** - * 判断`Uplaode`r是否正在上传中。 - * @grammar isInProgress() => Boolean - * @method isInProgress - * @for Uploader - */ - isInProgress: function() { - return !!this.progress; - }, - - _getStats: function() { - return this.request('get-stats'); - }, - - /** - * 掉过一个文件上传,直接标记指定文件为已上传状态。 - * @grammar skipFile( file ) => undefined - * @method skipFile - * @for Uploader - */ - skipFile: function( file, status ) { - file = file.id ? file : this.request( 'get-file', file ); - - file.setStatus( status || Status.COMPLETE ); - file.skipped = true; - - // 如果正在上传。 - file.blocks && $.each( file.blocks, function( _, v ) { - var _tr = v.transport; - - if ( _tr ) { - _tr.abort(); - _tr.destroy(); - delete v.transport; - } - }); - - this.owner.trigger( 'uploadSkip', file ); - }, - - /** - * @event uploadFinished - * @description 当所有文件上传结束时触发。 - * @for Uploader - */ - _tick: function() { - var me = this, - opts = me.options, - fn, val; - - // 上一个promise还没有结束,则等待完成后再执行。 - if ( me._promise ) { - return me._promise.always( me.__tick ); - } - - // 还有位置,且还有文件要处理的话。 - if ( me.pool.length < opts.threads && (val = me._nextBlock()) ) { - me._trigged = false; - - fn = function( val ) { - me._promise = null; - - // 有可能是reject过来的,所以要检测val的类型。 - val && val.file && me._startSend( val ); - Base.nextTick( me.__tick ); - }; - - me._promise = isPromise( val ) ? val.always( fn ) : fn( val ); - - // 没有要上传的了,且没有正在传输的了。 - } else if ( !me.remaning && !me._getStats().numOfQueue && - !me._getStats().numofInterrupt ) { - me.runing = false; - - me._trigged || Base.nextTick(function() { - me.owner.trigger('uploadFinished'); - }); - me._trigged = true; - } - }, - - _putback: function(block) { - var idx; - - block.cuted.unshift(block); - idx = this.stack.indexOf(block.cuted); - - if (!~idx) { - this.stack.unshift(block.cuted); - } - }, - - _getStack: function() { - var i = 0, - act; - - while ( (act = this.stack[ i++ ]) ) { - if ( act.has() && act.file.getStatus() === Status.PROGRESS ) { - return act; - } else if (!act.has() || - act.file.getStatus() !== Status.PROGRESS && - act.file.getStatus() !== Status.INTERRUPT ) { - - // 把已经处理完了的,或者,状态为非 progress(上传中)、 - // interupt(暂停中) 的移除。 - this.stack.splice( --i, 1 ); - } - } - - return null; - }, - - _nextBlock: function() { - var me = this, - opts = me.options, - act, next, done, preparing; - - // 如果当前文件还有没有需要传输的,则直接返回剩下的。 - if ( (act = this._getStack()) ) { - - // 是否提前准备下一个文件 - if ( opts.prepareNextFile && !me.pending.length ) { - me._prepareNextFile(); - } - - return act.shift(); - - // 否则,如果正在运行,则准备下一个文件,并等待完成后返回下个分片。 - } else if ( me.runing ) { - - // 如果缓存中有,则直接在缓存中取,没有则去queue中取。 - if ( !me.pending.length && me._getStats().numOfQueue ) { - me._prepareNextFile(); - } - - next = me.pending.shift(); - done = function( file ) { - if ( !file ) { - return null; - } - - act = CuteFile( file, opts.chunked ? opts.chunkSize : 0 ); - me.stack.push(act); - return act.shift(); - }; - - // 文件可能还在prepare中,也有可能已经完全准备好了。 - if ( isPromise( next) ) { - preparing = next.file; - next = next[ next.pipe ? 'pipe' : 'then' ]( done ); - next.file = preparing; - return next; - } - - return done( next ); - } - }, - - - /** - * @event uploadStart - * @param {File} file File对象 - * @description 某个文件开始上传前触发,一个文件只会触发一次。 - * @for Uploader - */ - _prepareNextFile: function() { - var me = this, - file = me.request('fetch-file'), - pending = me.pending, - promise; - - if ( file ) { - promise = me.request( 'before-send-file', file, function() { - - // 有可能文件被skip掉了。文件被skip掉后,状态坑定不是Queued. - if ( file.getStatus() === Status.PROGRESS || - file.getStatus() === Status.INTERRUPT ) { - return file; - } - - return me._finishFile( file ); - }); - - me.owner.trigger( 'uploadStart', file ); - file.setStatus( Status.PROGRESS ); - - promise.file = file; - - // 如果还在pending中,则替换成文件本身。 - promise.done(function() { - var idx = $.inArray( promise, pending ); - - ~idx && pending.splice( idx, 1, file ); - }); - - // befeore-send-file的钩子就有错误发生。 - promise.fail(function( reason ) { - file.setStatus( Status.ERROR, reason ); - me.owner.trigger( 'uploadError', file, reason ); - me.owner.trigger( 'uploadComplete', file ); - }); - - pending.push( promise ); - } - }, - - // 让出位置了,可以让其他分片开始上传 - _popBlock: function( block ) { - var idx = $.inArray( block, this.pool ); - - this.pool.splice( idx, 1 ); - block.file.remaning--; - this.remaning--; - }, - - // 开始上传,可以被掉过。如果promise被reject了,则表示跳过此分片。 - _startSend: function( block ) { - var me = this, - file = block.file, - promise; - - // 有可能在 before-send-file 的 promise 期间改变了文件状态。 - // 如:暂停,取消 - // 我们不能中断 promise, 但是可以在 promise 完后,不做上传操作。 - if ( file.getStatus() !== Status.PROGRESS ) { - - // 如果是中断,则还需要放回去。 - if (file.getStatus() === Status.INTERRUPT) { - me._putback(block); - } - - return; - } - - me.pool.push( block ); - me.remaning++; - - // 如果没有分片,则直接使用原始的。 - // 不会丢失content-type信息。 - block.blob = block.chunks === 1 ? file.source : - file.source.slice( block.start, block.end ); - - // hook, 每个分片发送之前可能要做些异步的事情。 - promise = me.request( 'before-send', block, function() { - - // 有可能文件已经上传出错了,所以不需要再传输了。 - if ( file.getStatus() === Status.PROGRESS ) { - me._doSend( block ); - } else { - me._popBlock( block ); - Base.nextTick( me.__tick ); - } - }); - - // 如果为fail了,则跳过此分片。 - promise.fail(function() { - if ( file.remaning === 1 ) { - me._finishFile( file ).always(function() { - block.percentage = 1; - me._popBlock( block ); - me.owner.trigger( 'uploadComplete', file ); - Base.nextTick( me.__tick ); - }); - } else { - block.percentage = 1; - me.updateFileProgress( file ); - me._popBlock( block ); - Base.nextTick( me.__tick ); - } - }); - }, - - - /** - * @event uploadBeforeSend - * @param {Object} object - * @param {Object} data 默认的上传参数,可以扩展此对象来控制上传参数。 - * @param {Object} headers 可以扩展此对象来控制上传头部。 - * @description 当某个文件的分块在发送前触发,主要用来询问是否要添加附带参数,大文件在开起分片上传的前提下此事件可能会触发多次。 - * @for Uploader - */ - - /** - * @event uploadAccept - * @param {Object} object - * @param {Object} ret 服务端的返回数据,json格式,如果服务端不是json格式,从ret._raw中取数据,自行解析。 - * @description 当某个文件上传到服务端响应后,会派送此事件来询问服务端响应是否有效。如果此事件handler返回值为`false`, 则此文件将派送`server`类型的`uploadError`事件。 - * @for Uploader - */ - - /** - * @event uploadProgress - * @param {File} file File对象 - * @param {Number} percentage 上传进度 - * @description 上传过程中触发,携带上传进度。 - * @for Uploader - */ - - - /** - * @event uploadError - * @param {File} file File对象 - * @param {String} reason 出错的code - * @description 当文件上传出错时触发。 - * @for Uploader - */ - - /** - * @event uploadSuccess - * @param {File} file File对象 - * @param {Object} response 服务端返回的数据 - * @description 当文件上传成功时触发。 - * @for Uploader - */ - - /** - * @event uploadComplete - * @param {File} [file] File对象 - * @description 不管成功或者失败,文件上传完成时触发。 - * @for Uploader - */ - - // 做上传操作。 - _doSend: function( block ) { - var me = this, - owner = me.owner, - opts = me.options, - file = block.file, - tr = new Transport( opts ), - data = $.extend({}, opts.formData ), - headers = $.extend({}, opts.headers ), - requestAccept, ret; - - block.transport = tr; - - tr.on( 'destroy', function() { - delete block.transport; - me._popBlock( block ); - Base.nextTick( me.__tick ); - }); - - // 广播上传进度。以文件为单位。 - tr.on( 'progress', function( percentage ) { - block.percentage = percentage; - me.updateFileProgress( file ); - }); - - // 用来询问,是否返回的结果是有错误的。 - requestAccept = function( reject ) { - var fn; - - ret = tr.getResponseAsJson() || {}; - ret._raw = tr.getResponse(); - fn = function( value ) { - reject = value; - }; - - // 服务端响应了,不代表成功了,询问是否响应正确。 - if ( !owner.trigger( 'uploadAccept', block, ret, fn ) ) { - reject = reject || 'server'; - } - - return reject; - }; - - // 尝试重试,然后广播文件上传出错。 - tr.on( 'error', function( type, flag ) { - block.retried = block.retried || 0; - - // 自动重试 - if ( block.chunks > 1 && ~'http,abort'.indexOf( type ) && - block.retried < opts.chunkRetry ) { - - block.retried++; - tr.send(); - - } else { - - // http status 500 ~ 600 - if ( !flag && type === 'server' ) { - type = requestAccept( type ); - } - - file.setStatus( Status.ERROR, type ); - owner.trigger( 'uploadError', file, type ); - owner.trigger( 'uploadComplete', file ); - } - }); - - // 上传成功 - tr.on( 'load', function() { - var reason; - - // 如果非预期,转向上传出错。 - if ( (reason = requestAccept()) ) { - tr.trigger( 'error', reason, true ); - return; - } - - // 全部上传完成。 - if ( file.remaning === 1 ) { - me._finishFile( file, ret ); - } else { - tr.destroy(); - } - }); - - // 配置默认的上传字段。 - data = $.extend( data, { - id: file.id, - name: file.name, - type: file.type, - lastModifiedDate: file.lastModifiedDate, - size: file.size - }); - - block.chunks > 1 && $.extend( data, { - chunks: block.chunks, - chunk: block.chunk - }); - - // 在发送之间可以添加字段什么的。。。 - // 如果默认的字段不够使用,可以通过监听此事件来扩展 - owner.trigger( 'uploadBeforeSend', block, data, headers ); - - // 开始发送。 - tr.appendBlob( opts.fileVal, block.blob, file.name ); - tr.append( data ); - tr.setRequestHeader( headers ); - tr.send(); - }, - - // 完成上传。 - _finishFile: function( file, ret, hds ) { - var owner = this.owner; - - return owner - .request( 'after-send-file', arguments, function() { - file.setStatus( Status.COMPLETE ); - owner.trigger( 'uploadSuccess', file, ret, hds ); - }) - .fail(function( reason ) { - - // 如果外部已经标记为invalid什么的,不再改状态。 - if ( file.getStatus() === Status.PROGRESS ) { - file.setStatus( Status.ERROR, reason ); - } - - owner.trigger( 'uploadError', file, reason ); - }) - .always(function() { - owner.trigger( 'uploadComplete', file ); - }); - }, - - updateFileProgress: function(file) { - var totalPercent = 0, - uploaded = 0; - - if (!file.blocks) { - return; - } - - $.each( file.blocks, function( _, v ) { - uploaded += (v.percentage || 0) * (v.end - v.start); - }); - - totalPercent = uploaded / file.size; - this.owner.trigger( 'uploadProgress', file, totalPercent || 0 ); - } - - }); - }); - /** - * @fileOverview 各种验证,包括文件总大小是否超出、单文件是否超出和文件是否重复。 - */ - - define('widgets/validator',[ - 'base', - 'uploader', - 'file', - 'widgets/widget' - ], function( Base, Uploader, WUFile ) { - - var $ = Base.$, - validators = {}, - api; - - /** - * @event error - * @param {String} type 错误类型。 - * @description 当validate不通过时,会以派送错误事件的形式通知调用者。通过`upload.on('error', handler)`可以捕获到此类错误,目前有以下错误会在特定的情况下派送错来。 - * - * * `Q_EXCEED_NUM_LIMIT` 在设置了`fileNumLimit`且尝试给`uploader`添加的文件数量超出这个值时派送。 - * * `Q_EXCEED_SIZE_LIMIT` 在设置了`Q_EXCEED_SIZE_LIMIT`且尝试给`uploader`添加的文件总大小超出这个值时派送。 - * * `Q_TYPE_DENIED` 当文件类型不满足时触发。。 - * @for Uploader - */ - - // 暴露给外面的api - api = { - - // 添加验证器 - addValidator: function( type, cb ) { - validators[ type ] = cb; - }, - - // 移除验证器 - removeValidator: function( type ) { - delete validators[ type ]; - } - }; - - // 在Uploader初始化的时候启动Validators的初始化 - Uploader.register({ - name: 'validator', - - init: function() { - var me = this; - Base.nextTick(function() { - $.each( validators, function() { - this.call( me.owner ); - }); - }); - } - }); - - /** - * @property {int} [fileNumLimit=undefined] - * @namespace options - * @for Uploader - * @description 验证文件总数量, 超出则不允许加入队列。 - */ - api.addValidator( 'fileNumLimit', function() { - var uploader = this, - opts = uploader.options, - count = 0, - max = parseInt( opts.fileNumLimit, 10 ), - flag = true; - - if ( !max ) { - return; - } - - uploader.on( 'beforeFileQueued', function( file ) { - - if ( count >= max && flag ) { - flag = false; - this.trigger( 'error', 'Q_EXCEED_NUM_LIMIT', max, file ); - setTimeout(function() { - flag = true; - }, 1 ); - } - - return count >= max ? false : true; - }); - - uploader.on( 'fileQueued', function() { - count++; - }); - - uploader.on( 'fileDequeued', function() { - count--; - }); - - uploader.on( 'reset', function() { - count = 0; - }); - }); - - - /** - * @property {int} [fileSizeLimit=undefined] - * @namespace options - * @for Uploader - * @description 验证文件总大小是否超出限制, 超出则不允许加入队列。 - */ - api.addValidator( 'fileSizeLimit', function() { - var uploader = this, - opts = uploader.options, - count = 0, - max = parseInt( opts.fileSizeLimit, 10 ), - flag = true; - - if ( !max ) { - return; - } - - uploader.on( 'beforeFileQueued', function( file ) { - var invalid = count + file.size > max; - - if ( invalid && flag ) { - flag = false; - this.trigger( 'error', 'Q_EXCEED_SIZE_LIMIT', max, file ); - setTimeout(function() { - flag = true; - }, 1 ); - } - - return invalid ? false : true; - }); - - uploader.on( 'fileQueued', function( file ) { - count += file.size; - }); - - uploader.on( 'fileDequeued', function( file ) { - count -= file.size; - }); - - uploader.on( 'reset', function() { - count = 0; - }); - }); - - /** - * @property {int} [fileSingleSizeLimit=undefined] - * @namespace options - * @for Uploader - * @description 验证单个文件大小是否超出限制, 超出则不允许加入队列。 - */ - api.addValidator( 'fileSingleSizeLimit', function() { - var uploader = this, - opts = uploader.options, - max = opts.fileSingleSizeLimit; - - if ( !max ) { - return; - } - - uploader.on( 'beforeFileQueued', function( file ) { - - if ( file.size > max ) { - file.setStatus( WUFile.Status.INVALID, 'exceed_size' ); - this.trigger( 'error', 'F_EXCEED_SIZE', max, file ); - return false; - } - - }); - - }); - - /** - * @property {Boolean} [duplicate=undefined] - * @namespace options - * @for Uploader - * @description 去重, 根据文件名字、文件大小和最后修改时间来生成hash Key. - */ - api.addValidator( 'duplicate', function() { - var uploader = this, - opts = uploader.options, - mapping = {}; - - if ( opts.duplicate ) { - return; - } - - function hashString( str ) { - var hash = 0, - i = 0, - len = str.length, - _char; - - for ( ; i < len; i++ ) { - _char = str.charCodeAt( i ); - hash = _char + (hash << 6) + (hash << 16) - hash; - } - - return hash; - } - - uploader.on( 'beforeFileQueued', function( file ) { - var hash = file.__hash || (file.__hash = hashString( file.name + - file.size + file.lastModifiedDate )); - - // 已经重复了 - if ( mapping[ hash ] ) { - this.trigger( 'error', 'F_DUPLICATE', file ); - return false; - } - }); - - uploader.on( 'fileQueued', function( file ) { - var hash = file.__hash; - - hash && (mapping[ hash ] = true); - }); - - uploader.on( 'fileDequeued', function( file ) { - var hash = file.__hash; - - hash && (delete mapping[ hash ]); - }); - - uploader.on( 'reset', function() { - mapping = {}; - }); - }); - - return api; - }); - - /** - * @fileOverview Runtime管理器,负责Runtime的选择, 连接 - */ - define('runtime/compbase',[],function() { - - function CompBase( owner, runtime ) { - - this.owner = owner; - this.options = owner.options; - - this.getRuntime = function() { - return runtime; - }; - - this.getRuid = function() { - return runtime.uid; - }; - - this.trigger = function() { - return owner.trigger.apply( owner, arguments ); - }; - } - - return CompBase; - }); - /** - * @fileOverview Html5Runtime - */ - define('runtime/html5/runtime',[ - 'base', - 'runtime/runtime', - 'runtime/compbase' - ], function( Base, Runtime, CompBase ) { - - var type = 'html5', - components = {}; - - function Html5Runtime() { - var pool = {}, - me = this, - destroy = this.destroy; - - Runtime.apply( me, arguments ); - me.type = type; - - - // 这个方法的调用者,实际上是RuntimeClient - me.exec = function( comp, fn/*, args...*/) { - var client = this, - uid = client.uid, - args = Base.slice( arguments, 2 ), - instance; - - if ( components[ comp ] ) { - instance = pool[ uid ] = pool[ uid ] || - new components[ comp ]( client, me ); - - if ( instance[ fn ] ) { - return instance[ fn ].apply( instance, args ); - } - } - }; - - me.destroy = function() { - // @todo 删除池子中的所有实例 - return destroy && destroy.apply( this, arguments ); - }; - } - - Base.inherits( Runtime, { - constructor: Html5Runtime, - - // 不需要连接其他程序,直接执行callback - init: function() { - var me = this; - setTimeout(function() { - me.trigger('ready'); - }, 1 ); - } - - }); - - // 注册Components - Html5Runtime.register = function( name, component ) { - var klass = components[ name ] = Base.inherits( CompBase, component ); - return klass; - }; - - // 注册html5运行时。 - // 只有在支持的前提下注册。 - if ( window.Blob && window.FileReader && window.DataView ) { - Runtime.addRuntime( type, Html5Runtime ); - } - - return Html5Runtime; - }); - /** - * @fileOverview Blob Html实现 - */ - define('runtime/html5/blob',[ - 'runtime/html5/runtime', - 'lib/blob' - ], function( Html5Runtime, Blob ) { - - return Html5Runtime.register( 'Blob', { - slice: function( start, end ) { - var blob = this.owner.source, - slice = blob.slice || blob.webkitSlice || blob.mozSlice; - - blob = slice.call( blob, start, end ); - - return new Blob( this.getRuid(), blob ); - } - }); - }); - /** - * @fileOverview FilePaste - */ - define('runtime/html5/dnd',[ - 'base', - 'runtime/html5/runtime', - 'lib/file' - ], function( Base, Html5Runtime, File ) { - - var $ = Base.$, - prefix = 'webuploader-dnd-'; - - return Html5Runtime.register( 'DragAndDrop', { - init: function() { - var elem = this.elem = this.options.container; - - this.dragEnterHandler = Base.bindFn( this._dragEnterHandler, this ); - this.dragOverHandler = Base.bindFn( this._dragOverHandler, this ); - this.dragLeaveHandler = Base.bindFn( this._dragLeaveHandler, this ); - this.dropHandler = Base.bindFn( this._dropHandler, this ); - this.dndOver = false; - - elem.on( 'dragenter', this.dragEnterHandler ); - elem.on( 'dragover', this.dragOverHandler ); - elem.on( 'dragleave', this.dragLeaveHandler ); - elem.on( 'drop', this.dropHandler ); - - if ( this.options.disableGlobalDnd ) { - $( document ).on( 'dragover', this.dragOverHandler ); - $( document ).on( 'drop', this.dropHandler ); - } - }, - - _dragEnterHandler: function( e ) { - var me = this, - denied = me._denied || false, - items; - - e = e.originalEvent || e; - - if ( !me.dndOver ) { - me.dndOver = true; - - // 注意只有 chrome 支持。 - items = e.dataTransfer.items; - - if ( items && items.length ) { - me._denied = denied = !me.trigger( 'accept', items ); - } - - me.elem.addClass( prefix + 'over' ); - me.elem[ denied ? 'addClass' : - 'removeClass' ]( prefix + 'denied' ); - } - - e.dataTransfer.dropEffect = denied ? 'none' : 'copy'; - - return false; - }, - - _dragOverHandler: function( e ) { - // 只处理框内的。 - var parentElem = this.elem.parent().get( 0 ); - if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) { - return false; - } - - clearTimeout( this._leaveTimer ); - this._dragEnterHandler.call( this, e ); - - return false; - }, - - _dragLeaveHandler: function() { - var me = this, - handler; - - handler = function() { - me.dndOver = false; - me.elem.removeClass( prefix + 'over ' + prefix + 'denied' ); - }; - - clearTimeout( me._leaveTimer ); - me._leaveTimer = setTimeout( handler, 100 ); - return false; - }, - - _dropHandler: function( e ) { - var me = this, - ruid = me.getRuid(), - parentElem = me.elem.parent().get( 0 ), - dataTransfer, data; - - // 只处理框内的。 - if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) { - return false; - } - - e = e.originalEvent || e; - dataTransfer = e.dataTransfer; - - // 如果是页面内拖拽,还不能处理,不阻止事件。 - // 此处 ie11 下会报参数错误, - try { - data = dataTransfer.getData('text/html'); - } catch( err ) { - } - - if ( data ) { - return; - } - - me._getTansferFiles( dataTransfer, function( results ) { - me.trigger( 'drop', $.map( results, function( file ) { - return new File( ruid, file ); - }) ); - }); - - me.dndOver = false; - me.elem.removeClass( prefix + 'over' ); - return false; - }, - - // 如果传入 callback 则去查看文件夹,否则只管当前文件夹。 - _getTansferFiles: function( dataTransfer, callback ) { - var results = [], - promises = [], - items, files, file, item, i, len, canAccessFolder; - - items = dataTransfer.items; - files = dataTransfer.files; - - canAccessFolder = !!(items && items[ 0 ].webkitGetAsEntry); - - for ( i = 0, len = files.length; i < len; i++ ) { - file = files[ i ]; - item = items && items[ i ]; - - if ( canAccessFolder && item.webkitGetAsEntry().isDirectory ) { - - promises.push( this._traverseDirectoryTree( - item.webkitGetAsEntry(), results ) ); - } else { - results.push( file ); - } - } - - Base.when.apply( Base, promises ).done(function() { - - if ( !results.length ) { - return; - } - - callback( results ); - }); - }, - - _traverseDirectoryTree: function( entry, results ) { - var deferred = Base.Deferred(), - me = this; - - if ( entry.isFile ) { - entry.file(function( file ) { - results.push( file ); - deferred.resolve(); - }); - } else if ( entry.isDirectory ) { - entry.createReader().readEntries(function( entries ) { - var len = entries.length, - promises = [], - arr = [], // 为了保证顺序。 - i; - - for ( i = 0; i < len; i++ ) { - promises.push( me._traverseDirectoryTree( - entries[ i ], arr ) ); - } - - Base.when.apply( Base, promises ).then(function() { - results.push.apply( results, arr ); - deferred.resolve(); - }, deferred.reject ); - }); - } - - return deferred.promise(); - }, - - destroy: function() { - var elem = this.elem; - - // 还没 init 就调用 destroy - if (!elem) { - return; - } - - elem.off( 'dragenter', this.dragEnterHandler ); - elem.off( 'dragover', this.dragOverHandler ); - elem.off( 'dragleave', this.dragLeaveHandler ); - elem.off( 'drop', this.dropHandler ); - - if ( this.options.disableGlobalDnd ) { - $( document ).off( 'dragover', this.dragOverHandler ); - $( document ).off( 'drop', this.dropHandler ); - } - } - }); - }); - - /** - * @fileOverview FilePaste - */ - define('runtime/html5/filepaste',[ - 'base', - 'runtime/html5/runtime', - 'lib/file' - ], function( Base, Html5Runtime, File ) { - - return Html5Runtime.register( 'FilePaste', { - init: function() { - var opts = this.options, - elem = this.elem = opts.container, - accept = '.*', - arr, i, len, item; - - // accetp的mimeTypes中生成匹配正则。 - if ( opts.accept ) { - arr = []; - - for ( i = 0, len = opts.accept.length; i < len; i++ ) { - item = opts.accept[ i ].mimeTypes; - item && arr.push( item ); - } - - if ( arr.length ) { - accept = arr.join(','); - accept = accept.replace( /,/g, '|' ).replace( /\*/g, '.*' ); - } - } - this.accept = accept = new RegExp( accept, 'i' ); - this.hander = Base.bindFn( this._pasteHander, this ); - elem.on( 'paste', this.hander ); - }, - - _pasteHander: function( e ) { - var allowed = [], - ruid = this.getRuid(), - items, item, blob, i, len; - - e = e.originalEvent || e; - items = e.clipboardData.items; - - for ( i = 0, len = items.length; i < len; i++ ) { - item = items[ i ]; - - if ( item.kind !== 'file' || !(blob = item.getAsFile()) ) { - continue; - } - - allowed.push( new File( ruid, blob ) ); - } - - if ( allowed.length ) { - // 不阻止非文件粘贴(文字粘贴)的事件冒泡 - e.preventDefault(); - e.stopPropagation(); - this.trigger( 'paste', allowed ); - } - }, - - destroy: function() { - this.elem.off( 'paste', this.hander ); - } - }); - }); - - /** - * @fileOverview FilePicker - */ - define('runtime/html5/filepicker',[ - 'base', - 'runtime/html5/runtime' - ], function( Base, Html5Runtime ) { - - var $ = Base.$; - - return Html5Runtime.register( 'FilePicker', { - init: function() { - var container = this.getRuntime().getContainer(), - me = this, - owner = me.owner, - opts = me.options, - label = this.label = $( document.createElement('label') ), - input = this.input = $( document.createElement('input') ), - arr, i, len, mouseHandler; - - input.attr( 'type', 'file' ); - input.attr( 'name', opts.name ); - input.addClass('webuploader-element-invisible'); - - label.on( 'click', function() { - input.trigger('click'); - }); - - label.css({ - opacity: 0, - width: '100%', - height: '100%', - display: 'block', - cursor: 'pointer', - background: '#ffffff' - }); - - if ( opts.multiple ) { - input.attr( 'multiple', 'multiple' ); - } - - // @todo Firefox不支持单独指定后缀 - if ( opts.accept && opts.accept.length > 0 ) { - arr = []; - - for ( i = 0, len = opts.accept.length; i < len; i++ ) { - arr.push( opts.accept[ i ].mimeTypes ); - } - - input.attr( 'accept', arr.join(',') ); - } - - container.append( input ); - container.append( label ); - - mouseHandler = function( e ) { - owner.trigger( e.type ); - }; - - input.on( 'change', function( e ) { - var fn = arguments.callee, - clone; - - me.files = e.target.files; - - // reset input - clone = this.cloneNode( true ); - clone.value = null; - this.parentNode.replaceChild( clone, this ); - - input.off(); - input = $( clone ).on( 'change', fn ) - .on( 'mouseenter mouseleave', mouseHandler ); - - owner.trigger('change'); - }); - - label.on( 'mouseenter mouseleave', mouseHandler ); - - }, - - - getFiles: function() { - return this.files; - }, - - destroy: function() { - this.input.off(); - this.label.off(); - } - }); - }); - /** - * @fileOverview Transport - * @todo 支持chunked传输,优势: - * 可以将大文件分成小块,挨个传输,可以提高大文件成功率,当失败的时候,也只需要重传那小部分, - * 而不需要重头再传一次。另外断点续传也需要用chunked方式。 - */ - define('runtime/html5/transport',[ - 'base', - 'runtime/html5/runtime' - ], function( Base, Html5Runtime ) { - - var noop = Base.noop, - $ = Base.$; - - return Html5Runtime.register( 'Transport', { - init: function() { - this._status = 0; - this._response = null; - }, - - send: function() { - var owner = this.owner, - opts = this.options, - xhr = this._initAjax(), - blob = owner._blob, - server = opts.server, - formData, binary, fr; - - if ( opts.sendAsBinary ) { - server += (/\?/.test( server ) ? '&' : '?') + - $.param( owner._formData ); - - binary = blob.getSource(); - } else { - formData = new FormData(); - $.each( owner._formData, function( k, v ) { - formData.append( k, v ); - }); - - formData.append( opts.fileVal, blob.getSource(), - opts.filename || owner._formData.name || '' ); - } - - if ( opts.withCredentials && 'withCredentials' in xhr ) { - xhr.open( opts.method, server, true ); - xhr.withCredentials = true; - } else { - xhr.open( opts.method, server ); - } - - this._setRequestHeader( xhr, opts.headers ); - - if ( binary ) { - // 强制设置成 content-type 为文件流。 - xhr.overrideMimeType && - xhr.overrideMimeType('application/octet-stream'); - - // android直接发送blob会导致服务端接收到的是空文件。 - // bug详情。 - // https://code.google.com/p/android/issues/detail?id=39882 - // 所以先用fileReader读取出来再通过arraybuffer的方式发送。 - if ( Base.os.android ) { - fr = new FileReader(); - - fr.onload = function() { - xhr.send( this.result ); - fr = fr.onload = null; - }; - - fr.readAsArrayBuffer( binary ); - } else { - xhr.send( binary ); - } - } else { - xhr.send( formData ); - } - }, - - getResponse: function() { - return this._response; - }, - - getResponseAsJson: function() { - return this._parseJson( this._response ); - }, - - getStatus: function() { - return this._status; - }, - - abort: function() { - var xhr = this._xhr; - - if ( xhr ) { - xhr.upload.onprogress = noop; - xhr.onreadystatechange = noop; - xhr.abort(); - - this._xhr = xhr = null; - } - }, - - destroy: function() { - this.abort(); - }, - - _initAjax: function() { - var me = this, - xhr = new XMLHttpRequest(), - opts = this.options; - - if ( opts.withCredentials && !('withCredentials' in xhr) && - typeof XDomainRequest !== 'undefined' ) { - xhr = new XDomainRequest(); - } - - xhr.upload.onprogress = function( e ) { - var percentage = 0; - - if ( e.lengthComputable ) { - percentage = e.loaded / e.total; - } - - return me.trigger( 'progress', percentage ); - }; - - xhr.onreadystatechange = function() { - - if ( xhr.readyState !== 4 ) { - return; - } - - xhr.upload.onprogress = noop; - xhr.onreadystatechange = noop; - me._xhr = null; - me._status = xhr.status; - - if ( xhr.status >= 200 && xhr.status < 300 ) { - me._response = xhr.responseText; - return me.trigger('load'); - } else if ( xhr.status >= 500 && xhr.status < 600 ) { - me._response = xhr.responseText; - return me.trigger( 'error', 'server' ); - } - - - return me.trigger( 'error', me._status ? 'http' : 'abort' ); - }; - - me._xhr = xhr; - return xhr; - }, - - _setRequestHeader: function( xhr, headers ) { - $.each( headers, function( key, val ) { - xhr.setRequestHeader( key, val ); - }); - }, - - _parseJson: function( str ) { - var json; - - try { - json = JSON.parse( str ); - } catch ( ex ) { - json = {}; - } - - return json; - } - }); - }); - /** - * @fileOverview FlashRuntime - */ - define('runtime/flash/runtime',[ - 'base', - 'runtime/runtime', - 'runtime/compbase' - ], function( Base, Runtime, CompBase ) { - - var $ = Base.$, - type = 'flash', - components = {}; - - - function getFlashVersion() { - var version; - - try { - version = navigator.plugins[ 'Shockwave Flash' ]; - version = version.description; - } catch ( ex ) { - try { - version = new ActiveXObject('ShockwaveFlash.ShockwaveFlash') - .GetVariable('$version'); - } catch ( ex2 ) { - version = '0.0'; - } - } - version = version.match( /\d+/g ); - return parseFloat( version[ 0 ] + '.' + version[ 1 ], 10 ); - } - - function FlashRuntime() { - var pool = {}, - clients = {}, - destroy = this.destroy, - me = this, - jsreciver = Base.guid('webuploader_'); - - Runtime.apply( me, arguments ); - me.type = type; - - - // 这个方法的调用者,实际上是RuntimeClient - me.exec = function( comp, fn/*, args...*/ ) { - var client = this, - uid = client.uid, - args = Base.slice( arguments, 2 ), - instance; - - clients[ uid ] = client; - - if ( components[ comp ] ) { - if ( !pool[ uid ] ) { - pool[ uid ] = new components[ comp ]( client, me ); - } - - instance = pool[ uid ]; - - if ( instance[ fn ] ) { - return instance[ fn ].apply( instance, args ); - } - } - - return me.flashExec.apply( client, arguments ); - }; - - function handler( evt, obj ) { - var type = evt.type || evt, - parts, uid; - - parts = type.split('::'); - uid = parts[ 0 ]; - type = parts[ 1 ]; - - // console.log.apply( console, arguments ); - - if ( type === 'Ready' && uid === me.uid ) { - me.trigger('ready'); - } else if ( clients[ uid ] ) { - clients[ uid ].trigger( type.toLowerCase(), evt, obj ); - } - - // Base.log( evt, obj ); - } - - // flash的接受器。 - window[ jsreciver ] = function() { - var args = arguments; - - // 为了能捕获得到。 - setTimeout(function() { - handler.apply( null, args ); - }, 1 ); - }; - - this.jsreciver = jsreciver; - - this.destroy = function() { - // @todo 删除池子中的所有实例 - return destroy && destroy.apply( this, arguments ); - }; - - this.flashExec = function( comp, fn ) { - var flash = me.getFlash(), - args = Base.slice( arguments, 2 ); - - return flash.exec( this.uid, comp, fn, args ); - }; - - // @todo - } - - Base.inherits( Runtime, { - constructor: FlashRuntime, - - init: function() { - var container = this.getContainer(), - opts = this.options, - html; - - // if not the minimal height, shims are not initialized - // in older browsers (e.g FF3.6, IE6,7,8, Safari 4.0,5.0, etc) - container.css({ - position: 'absolute', - top: '-8px', - left: '-8px', - width: '9px', - height: '9px', - overflow: 'hidden' - }); - - // insert flash object - html = '' + - '' + - '' + - '' + - ''; - - container.html( html ); - }, - - getFlash: function() { - if ( this._flash ) { - return this._flash; - } - - this._flash = $( '#' + this.uid ).get( 0 ); - return this._flash; - } - - }); - - FlashRuntime.register = function( name, component ) { - component = components[ name ] = Base.inherits( CompBase, $.extend({ - - // @todo fix this later - flashExec: function() { - var owner = this.owner, - runtime = this.getRuntime(); - - return runtime.flashExec.apply( owner, arguments ); - } - }, component ) ); - - return component; - }; - - if ( getFlashVersion() >= 11.4 ) { - Runtime.addRuntime( type, FlashRuntime ); - } - - return FlashRuntime; - }); - /** - * @fileOverview FilePicker - */ - define('runtime/flash/filepicker',[ - 'base', - 'runtime/flash/runtime' - ], function( Base, FlashRuntime ) { - var $ = Base.$; - - return FlashRuntime.register( 'FilePicker', { - init: function( opts ) { - var copy = $.extend({}, opts ), - len, i; - - // 修复Flash再没有设置title的情况下无法弹出flash文件选择框的bug. - len = copy.accept && copy.accept.length; - for ( i = 0; i < len; i++ ) { - if ( !copy.accept[ i ].title ) { - copy.accept[ i ].title = 'Files'; - } - } - - delete copy.button; - delete copy.id; - delete copy.container; - - this.flashExec( 'FilePicker', 'init', copy ); - }, - - destroy: function() { - this.flashExec( 'FilePicker', 'destroy' ); - } - }); - }); - /** - * @fileOverview Transport flash实现 - */ - define('runtime/flash/transport',[ - 'base', - 'runtime/flash/runtime', - 'runtime/client' - ], function( Base, FlashRuntime, RuntimeClient ) { - var $ = Base.$; - - return FlashRuntime.register( 'Transport', { - init: function() { - this._status = 0; - this._response = null; - this._responseJson = null; - }, - - send: function() { - var owner = this.owner, - opts = this.options, - xhr = this._initAjax(), - blob = owner._blob, - server = opts.server, - binary; - - xhr.connectRuntime( blob.ruid ); - - if ( opts.sendAsBinary ) { - server += (/\?/.test( server ) ? '&' : '?') + - $.param( owner._formData ); - - binary = blob.uid; - } else { - $.each( owner._formData, function( k, v ) { - xhr.exec( 'append', k, v ); - }); - - xhr.exec( 'appendBlob', opts.fileVal, blob.uid, - opts.filename || owner._formData.name || '' ); - } - - this._setRequestHeader( xhr, opts.headers ); - xhr.exec( 'send', { - method: opts.method, - url: server, - forceURLStream: opts.forceURLStream, - mimeType: 'application/octet-stream' - }, binary ); - }, - - getStatus: function() { - return this._status; - }, - - getResponse: function() { - return this._response || ''; - }, - - getResponseAsJson: function() { - return this._responseJson; - }, - - abort: function() { - var xhr = this._xhr; - - if ( xhr ) { - xhr.exec('abort'); - xhr.destroy(); - this._xhr = xhr = null; - } - }, - - destroy: function() { - this.abort(); - }, - - _initAjax: function() { - var me = this, - xhr = new RuntimeClient('XMLHttpRequest'); - - xhr.on( 'uploadprogress progress', function( e ) { - var percent = e.loaded / e.total; - percent = Math.min( 1, Math.max( 0, percent ) ); - return me.trigger( 'progress', percent ); - }); - - xhr.on( 'load', function() { - var status = xhr.exec('getStatus'), - readBody = false, - err = '', - p; - - xhr.off(); - me._xhr = null; - - if ( status >= 200 && status < 300 ) { - readBody = true; - } else if ( status >= 500 && status < 600 ) { - readBody = true; - err = 'server'; - } else { - err = 'http'; - } - - if ( readBody ) { - me._response = xhr.exec('getResponse'); - me._response = decodeURIComponent( me._response ); - - // flash 处理可能存在 bug, 没辙只能靠 js 了 - // try { - // me._responseJson = xhr.exec('getResponseAsJson'); - // } catch ( error ) { - - p = window.JSON && window.JSON.parse || function( s ) { - try { - return new Function('return ' + s).call(); - } catch ( err ) { - return {}; - } - }; - me._responseJson = me._response ? p(me._response) : {}; - - // } - } - - xhr.destroy(); - xhr = null; - - return err ? me.trigger( 'error', err ) : me.trigger('load'); - }); - - xhr.on( 'error', function() { - xhr.off(); - me._xhr = null; - me.trigger( 'error', 'http' ); - }); - - me._xhr = xhr; - return xhr; - }, - - _setRequestHeader: function( xhr, headers ) { - $.each( headers, function( key, val ) { - xhr.exec( 'setRequestHeader', key, val ); - }); - } - }); - }); - /** - * @fileOverview Blob Html实现 - */ - define('runtime/flash/blob',[ - 'runtime/flash/runtime', - 'lib/blob' - ], function( FlashRuntime, Blob ) { - - return FlashRuntime.register( 'Blob', { - slice: function( start, end ) { - var blob = this.flashExec( 'Blob', 'slice', start, end ); - - return new Blob( blob.uid, blob ); - } - }); - }); - /** - * @fileOverview 没有图像处理的版本。 - */ - define('preset/withoutimage',[ - 'base', - - // widgets - 'widgets/filednd', - 'widgets/filepaste', - 'widgets/filepicker', - 'widgets/queue', - 'widgets/runtime', - 'widgets/upload', - 'widgets/validator', - - // runtimes - // html5 - 'runtime/html5/blob', - 'runtime/html5/dnd', - 'runtime/html5/filepaste', - 'runtime/html5/filepicker', - 'runtime/html5/transport', - - // flash - 'runtime/flash/filepicker', - 'runtime/flash/transport', - 'runtime/flash/blob' - ], function( Base ) { - return Base; - }); - define('webuploader',[ - 'preset/withoutimage' - ], function( preset ) { - return preset; - }); - return require('webuploader'); -}); diff --git a/static/plugins/webuploader/webuploader.noimage.min.js b/static/plugins/webuploader/webuploader.noimage.min.js deleted file mode 100644 index 1eaca480..00000000 --- a/static/plugins/webuploader/webuploader.noimage.min.js +++ /dev/null @@ -1,2 +0,0 @@ -/* WebUploader 0.1.5 */!function(a,b){var c,d={},e=function(a,b){var c,d,e;if("string"==typeof a)return h(a);for(c=[],d=a.length,e=0;d>e;e++)c.push(h(a[e]));return b.apply(null,c)},f=function(a,b,c){2===arguments.length&&(c=b,b=null),e(b||[],function(){g(a,c,arguments)})},g=function(a,b,c){var f,g={exports:b};"function"==typeof b&&(c.length||(c=[e,g.exports,g]),f=b.apply(null,c),void 0!==f&&(g.exports=f)),d[a]=g.exports},h=function(b){var c=d[b]||a[b];if(!c)throw new Error("`"+b+"` is undefined");return c},i=function(a){var b,c,e,f,g,h;h=function(a){return a&&a.charAt(0).toUpperCase()+a.substr(1)};for(b in d)if(c=a,d.hasOwnProperty(b)){for(e=b.split("/"),g=h(e.pop());f=h(e.shift());)c[f]=c[f]||{},c=c[f];c[g]=d[b]}return a},j=function(c){return a.__dollar=c,i(b(a,f,e))};"object"==typeof module&&"object"==typeof module.exports?module.exports=j():"function"==typeof define&&define.amd?define(["jquery"],j):(c=a.WebUploader,a.WebUploader=j(),a.WebUploader.noConflict=function(){a.WebUploader=c})}(window,function(a,b,c){return b("dollar-third",[],function(){var b=a.__dollar||a.jQuery||a.Zepto;if(!b)throw new Error("jQuery or Zepto not found!");return b}),b("dollar",["dollar-third"],function(a){return a}),b("promise-third",["dollar"],function(a){return{Deferred:a.Deferred,when:a.when,isPromise:function(a){return a&&"function"==typeof a.then}}}),b("promise",["promise-third"],function(a){return a}),b("base",["dollar","promise"],function(b,c){function d(a){return function(){return h.apply(a,arguments)}}function e(a,b){return function(){return a.apply(b,arguments)}}function f(a){var b;return Object.create?Object.create(a):(b=function(){},b.prototype=a,new b)}var g=function(){},h=Function.call;return{version:"0.1.5",$:b,Deferred:c.Deferred,isPromise:c.isPromise,when:c.when,browser:function(a){var b={},c=a.match(/WebKit\/([\d.]+)/),d=a.match(/Chrome\/([\d.]+)/)||a.match(/CriOS\/([\d.]+)/),e=a.match(/MSIE\s([\d\.]+)/)||a.match(/(?:trident)(?:.*rv:([\w.]+))?/i),f=a.match(/Firefox\/([\d.]+)/),g=a.match(/Safari\/([\d.]+)/),h=a.match(/OPR\/([\d.]+)/);return c&&(b.webkit=parseFloat(c[1])),d&&(b.chrome=parseFloat(d[1])),e&&(b.ie=parseFloat(e[1])),f&&(b.firefox=parseFloat(f[1])),g&&(b.safari=parseFloat(g[1])),h&&(b.opera=parseFloat(h[1])),b}(navigator.userAgent),os:function(a){var b={},c=a.match(/(?:Android);?[\s\/]+([\d.]+)?/),d=a.match(/(?:iPad|iPod|iPhone).*OS\s([\d_]+)/);return c&&(b.android=parseFloat(c[1])),d&&(b.ios=parseFloat(d[1].replace(/_/g,"."))),b}(navigator.userAgent),inherits:function(a,c,d){var e;return"function"==typeof c?(e=c,c=null):e=c&&c.hasOwnProperty("constructor")?c.constructor:function(){return a.apply(this,arguments)},b.extend(!0,e,a,d||{}),e.__super__=a.prototype,e.prototype=f(a.prototype),c&&b.extend(!0,e.prototype,c),e},noop:g,bindFn:e,log:function(){return a.console?e(console.log,console):g}(),nextTick:function(){return function(a){setTimeout(a,1)}}(),slice:d([].slice),guid:function(){var a=0;return function(b){for(var c=(+new Date).toString(32),d=0;5>d;d++)c+=Math.floor(65535*Math.random()).toString(32);return(b||"wu_")+c+(a++).toString(32)}}(),formatSize:function(a,b,c){var d;for(c=c||["B","K","M","G","TB"];(d=c.shift())&&a>1024;)a/=1024;return("B"===d?a:a.toFixed(b||2))+d}}}),b("mediator",["base"],function(a){function b(a,b,c,d){return f.grep(a,function(a){return!(!a||b&&a.e!==b||c&&a.cb!==c&&a.cb._cb!==c||d&&a.ctx!==d)})}function c(a,b,c){f.each((a||"").split(h),function(a,d){c(d,b)})}function d(a,b){for(var c,d=!1,e=-1,f=a.length;++e1?void(d.isPlainObject(b)&&d.isPlainObject(c[a])?d.extend(c[a],b):c[a]=b):a?c[a]:c},getStats:function(){var a=this.request("get-stats");return a?{successNum:a.numOfSuccess,progressNum:a.numOfProgress,cancelNum:a.numOfCancel,invalidNum:a.numOfInvalid,uploadFailNum:a.numOfUploadFailed,queueNum:a.numOfQueue,interruptNum:a.numofInterrupt}:{}},trigger:function(a){var c=[].slice.call(arguments,1),e=this.options,f="on"+a.substring(0,1).toUpperCase()+a.substring(1);return b.trigger.apply(this,arguments)===!1||d.isFunction(e[f])&&e[f].apply(this,c)===!1||d.isFunction(this[f])&&this[f].apply(this,c)===!1||b.trigger.apply(b,[this,a].concat(c))===!1?!1:!0},destroy:function(){this.request("destroy",arguments),this.off()},request:a.noop}),a.create=c.create=function(a){return new c(a)},a.Uploader=c,c}),b("runtime/runtime",["base","mediator"],function(a,b){function c(b){this.options=d.extend({container:document.body},b),this.uid=a.guid("rt_")}var d=a.$,e={},f=function(a){for(var b in a)if(a.hasOwnProperty(b))return b;return null};return d.extend(c.prototype,{getContainer:function(){var a,b,c=this.options;return this._container?this._container:(a=d(c.container||document.body),b=d(document.createElement("div")),b.attr("id","rt_"+this.uid),b.css({position:"absolute",top:"0px",left:"0px",width:"1px",height:"1px",overflow:"hidden"}),a.append(b),a.addClass("webuploader-container"),this._container=b,this._parent=a,b)},init:a.noop,exec:a.noop,destroy:function(){this._container&&this._container.remove(),this._parent&&this._parent.removeClass("webuploader-container"),this.off()}}),c.orders="html5,flash",c.addRuntime=function(a,b){e[a]=b},c.hasRuntime=function(a){return!!(a?e[a]:f(e))},c.create=function(a,b){var g,h;if(b=b||c.orders,d.each(b.split(/\s*,\s*/g),function(){return e[this]?(g=this,!1):void 0}),g=g||f(e),!g)throw new Error("Runtime Error");return h=new e[g](a)},b.installTo(c.prototype),c}),b("runtime/client",["base","mediator","runtime/runtime"],function(a,b,c){function d(b,d){var f,g=a.Deferred();this.uid=a.guid("client_"),this.runtimeReady=function(a){return g.done(a)},this.connectRuntime=function(b,h){if(f)throw new Error("already connected!");return g.done(h),"string"==typeof b&&e.get(b)&&(f=e.get(b)),f=f||e.get(null,d),f?(a.$.extend(f.options,b),f.__promise.then(g.resolve),f.__client++):(f=c.create(b,b.runtimeOrder),f.__promise=g.promise(),f.once("ready",g.resolve),f.init(),e.add(f),f.__client=1),d&&(f.__standalone=d),f},this.getRuntime=function(){return f},this.disconnectRuntime=function(){f&&(f.__client--,f.__client<=0&&(e.remove(f),delete f.__promise,f.destroy()),f=null)},this.exec=function(){if(f){var c=a.slice(arguments);return b&&c.unshift(b),f.exec.apply(this,c)}},this.getRuid=function(){return f&&f.uid},this.destroy=function(a){return function(){a&&a.apply(this,arguments),this.trigger("destroy"),this.off(),this.exec("destroy"),this.disconnectRuntime()}}(this.destroy)}var e;return e=function(){var a={};return{add:function(b){a[b.uid]=b},get:function(b,c){var d;if(b)return a[b];for(d in a)if(!c||!a[d].__standalone)return a[d];return null},remove:function(b){delete a[b.uid]}}}(),b.installTo(d.prototype),d}),b("lib/dnd",["base","mediator","runtime/client"],function(a,b,c){function d(a){a=this.options=e.extend({},d.options,a),a.container=e(a.container),a.container.length&&c.call(this,"DragAndDrop")}var e=a.$;return d.options={accept:null,disableGlobalDnd:!1},a.inherits(c,{constructor:d,init:function(){var a=this;a.connectRuntime(a.options,function(){a.exec("init"),a.trigger("ready")})}}),b.installTo(d.prototype),d}),b("widgets/widget",["base","uploader"],function(a,b){function c(a){if(!a)return!1;var b=a.length,c=e.type(a);return 1===a.nodeType&&b?!0:"array"===c||"function"!==c&&"string"!==c&&(0===b||"number"==typeof b&&b>0&&b-1 in a)}function d(a){this.owner=a,this.options=a.options}var e=a.$,f=b.prototype._init,g=b.prototype.destroy,h={},i=[];return e.extend(d.prototype,{init:a.noop,invoke:function(a,b){var c=this.responseMap;return c&&a in c&&c[a]in this&&e.isFunction(this[c[a]])?this[c[a]].apply(this,b):h},request:function(){return this.owner.request.apply(this.owner,arguments)}}),e.extend(b.prototype,{_init:function(){var a=this,b=a._widgets=[],c=a.options.disableWidgets||"";return e.each(i,function(d,e){(!c||!~c.indexOf(e._name))&&b.push(new e(a))}),f.apply(a,arguments)},request:function(b,d,e){var f,g,i,j,k=0,l=this._widgets,m=l&&l.length,n=[],o=[];for(d=c(d)?d:[d];m>k;k++)f=l[k],g=f.invoke(b,d),g!==h&&(a.isPromise(g)?o.push(g):n.push(g));return e||o.length?(i=a.when.apply(a,o),j=i.pipe?"pipe":"then",i[j](function(){var b=a.Deferred(),c=arguments;return 1===c.length&&(c=c[0]),setTimeout(function(){b.resolve(c)},1),b.promise()})[e?j:"done"](e||a.noop)):n[0]},destroy:function(){g.apply(this,arguments),this._widgets=null}}),b.register=d.register=function(b,c){var f,g={init:"init",destroy:"destroy",name:"anonymous"};return 1===arguments.length?(c=b,e.each(c,function(a){return"_"===a[0]||"name"===a?void("name"===a&&(g.name=c.name)):void(g[a.replace(/[A-Z]/g,"-$&").toLowerCase()]=a)})):g=e.extend(g,b),c.responseMap=g,f=a.inherits(d,c),f._name=g.name,i.push(f),f},b.unRegister=d.unRegister=function(a){if(a&&"anonymous"!==a)for(var b=i.length;b--;)i[b]._name===a&&i.splice(b,1)},d}),b("widgets/filednd",["base","uploader","lib/dnd","widgets/widget"],function(a,b,c){var d=a.$;return b.options.dnd="",b.register({name:"dnd",init:function(b){if(b.dnd&&"html5"===this.request("predict-runtime-type")){var e,f=this,g=a.Deferred(),h=d.extend({},{disableGlobalDnd:b.disableGlobalDnd,container:b.dnd,accept:b.accept});return this.dnd=e=new c(h),e.once("ready",g.resolve),e.on("drop",function(a){f.request("add-file",[a])}),e.on("accept",function(a){return f.owner.trigger("dndAccept",a)}),e.init(),g.promise()}},destroy:function(){this.dnd&&this.dnd.destroy()}})}),b("lib/filepaste",["base","mediator","runtime/client"],function(a,b,c){function d(a){a=this.options=e.extend({},a),a.container=e(a.container||document.body),c.call(this,"FilePaste")}var e=a.$;return a.inherits(c,{constructor:d,init:function(){var a=this;a.connectRuntime(a.options,function(){a.exec("init"),a.trigger("ready")})}}),b.installTo(d.prototype),d}),b("widgets/filepaste",["base","uploader","lib/filepaste","widgets/widget"],function(a,b,c){var d=a.$;return b.register({name:"paste",init:function(b){if(b.paste&&"html5"===this.request("predict-runtime-type")){var e,f=this,g=a.Deferred(),h=d.extend({},{container:b.paste,accept:b.accept});return this.paste=e=new c(h),e.once("ready",g.resolve),e.on("paste",function(a){f.owner.request("add-file",[a])}),e.init(),g.promise()}},destroy:function(){this.paste&&this.paste.destroy()}})}),b("lib/blob",["base","runtime/client"],function(a,b){function c(a,c){var d=this;d.source=c,d.ruid=a,this.size=c.size||0,this.type=!c.type&&this.ext&&~"jpg,jpeg,png,gif,bmp".indexOf(this.ext)?"image/"+("jpg"===this.ext?"jpeg":this.ext):c.type||"application/octet-stream",b.call(d,"Blob"),this.uid=c.uid||this.uid,a&&d.connectRuntime(a)}return a.inherits(b,{constructor:c,slice:function(a,b){return this.exec("slice",a,b)},getSource:function(){return this.source}}),c}),b("lib/file",["base","lib/blob"],function(a,b){function c(a,c){var f;this.name=c.name||"untitled"+d++,f=e.exec(c.name)?RegExp.$1.toLowerCase():"",!f&&c.type&&(f=/\/(jpg|jpeg|png|gif|bmp)$/i.exec(c.type)?RegExp.$1.toLowerCase():"",this.name+="."+f),this.ext=f,this.lastModifiedDate=c.lastModifiedDate||(new Date).toLocaleString(),b.apply(this,arguments)}var d=1,e=/\.([^.]+)$/;return a.inherits(b,c)}),b("lib/filepicker",["base","runtime/client","lib/file"],function(b,c,d){function e(a){if(a=this.options=f.extend({},e.options,a),a.container=f(a.id),!a.container.length)throw new Error("按钮指定错误");a.innerHTML=a.innerHTML||a.label||a.container.html()||"",a.button=f(a.button||document.createElement("div")),a.button.html(a.innerHTML),a.container.html(a.button),c.call(this,"FilePicker",!0)}var f=b.$;return e.options={button:null,container:null,label:null,innerHTML:null,multiple:!0,accept:null,name:"file"},b.inherits(c,{constructor:e,init:function(){var c=this,e=c.options,g=e.button;g.addClass("webuploader-pick"),c.on("all",function(a){var b;switch(a){case"mouseenter":g.addClass("webuploader-pick-hover");break;case"mouseleave":g.removeClass("webuploader-pick-hover");break;case"change":b=c.exec("getFiles"),c.trigger("select",f.map(b,function(a){return a=new d(c.getRuid(),a),a._refer=e.container,a}),e.container)}}),c.connectRuntime(e,function(){c.refresh(),c.exec("init",e),c.trigger("ready")}),this._resizeHandler=b.bindFn(this.refresh,this),f(a).on("resize",this._resizeHandler)},refresh:function(){var a=this.getRuntime().getContainer(),b=this.options.button,c=b.outerWidth?b.outerWidth():b.width(),d=b.outerHeight?b.outerHeight():b.height(),e=b.offset();c&&d&&a.css({bottom:"auto",right:"auto",width:c+"px",height:d+"px"}).offset(e)},enable:function(){var a=this.options.button;a.removeClass("webuploader-pick-disable"),this.refresh()},disable:function(){var a=this.options.button;this.getRuntime().getContainer().css({top:"-99999px"}),a.addClass("webuploader-pick-disable")},destroy:function(){var b=this.options.button;f(a).off("resize",this._resizeHandler),b.removeClass("webuploader-pick-disable webuploader-pick-hover webuploader-pick")}}),e}),b("widgets/filepicker",["base","uploader","lib/filepicker","widgets/widget"],function(a,b,c){var d=a.$;return d.extend(b.options,{pick:null,accept:null}),b.register({name:"picker",init:function(a){return this.pickers=[],a.pick&&this.addBtn(a.pick)},refresh:function(){d.each(this.pickers,function(){this.refresh()})},addBtn:function(b){var e=this,f=e.options,g=f.accept,h=[];if(b)return d.isPlainObject(b)||(b={id:b}),d(b.id).each(function(){var i,j,k;k=a.Deferred(),i=d.extend({},b,{accept:d.isPlainObject(g)?[g]:g,swf:f.swf,runtimeOrder:f.runtimeOrder,id:this}),j=new c(i),j.once("ready",k.resolve),j.on("select",function(a){e.owner.request("add-file",[a])}),j.init(),e.pickers.push(j),h.push(k.promise())}),a.when.apply(a,h)},disable:function(){d.each(this.pickers,function(){this.disable()})},enable:function(){d.each(this.pickers,function(){this.enable()})},destroy:function(){d.each(this.pickers,function(){this.destroy()}),this.pickers=null}})}),b("file",["base","mediator"],function(a,b){function c(){return f+g++}function d(a){this.name=a.name||"Untitled",this.size=a.size||0,this.type=a.type||"application/octet-stream",this.lastModifiedDate=a.lastModifiedDate||1*new Date,this.id=c(),this.ext=h.exec(this.name)?RegExp.$1:"",this.statusText="",i[this.id]=d.Status.INITED,this.source=a,this.loaded=0,this.on("error",function(a){this.setStatus(d.Status.ERROR,a)})}var e=a.$,f="WU_FILE_",g=0,h=/\.([^.]+)$/,i={};return e.extend(d.prototype,{setStatus:function(a,b){var c=i[this.id];"undefined"!=typeof b&&(this.statusText=b),a!==c&&(i[this.id]=a,this.trigger("statuschange",a,c))},getStatus:function(){return i[this.id]},getSource:function(){return this.source},destroy:function(){this.off(),delete i[this.id]}}),b.installTo(d.prototype),d.Status={INITED:"inited",QUEUED:"queued",PROGRESS:"progress",ERROR:"error",COMPLETE:"complete",CANCELLED:"cancelled",INTERRUPT:"interrupt",INVALID:"invalid"},d}),b("queue",["base","mediator","file"],function(a,b,c){function d(){this.stats={numOfQueue:0,numOfSuccess:0,numOfCancel:0,numOfProgress:0,numOfUploadFailed:0,numOfInvalid:0,numofDeleted:0,numofInterrupt:0},this._queue=[],this._map={}}var e=a.$,f=c.Status;return e.extend(d.prototype,{append:function(a){return this._queue.push(a),this._fileAdded(a),this},prepend:function(a){return this._queue.unshift(a),this._fileAdded(a),this},getFile:function(a){return"string"!=typeof a?a:this._map[a]},fetch:function(a){var b,c,d=this._queue.length;for(a=a||f.QUEUED,b=0;d>b;b++)if(c=this._queue[b],a===c.getStatus())return c;return null},sort:function(a){"function"==typeof a&&this._queue.sort(a)},getFiles:function(){for(var a,b=[].slice.call(arguments,0),c=[],d=0,f=this._queue.length;f>d;d++)a=this._queue[d],(!b.length||~e.inArray(a.getStatus(),b))&&c.push(a);return c},removeFile:function(a){var b=this._map[a.id];b&&(delete this._map[a.id],a.destroy(),this.stats.numofDeleted++)},_fileAdded:function(a){var b=this,c=this._map[a.id];c||(this._map[a.id]=a,a.on("statuschange",function(a,c){b._onFileStatusChange(a,c)}))},_onFileStatusChange:function(a,b){var c=this.stats;switch(b){case f.PROGRESS:c.numOfProgress--;break;case f.QUEUED:c.numOfQueue--;break;case f.ERROR:c.numOfUploadFailed--;break;case f.INVALID:c.numOfInvalid--;break;case f.INTERRUPT:c.numofInterrupt--}switch(a){case f.QUEUED:c.numOfQueue++;break;case f.PROGRESS:c.numOfProgress++;break;case f.ERROR:c.numOfUploadFailed++;break;case f.COMPLETE:c.numOfSuccess++;break;case f.CANCELLED:c.numOfCancel++;break;case f.INVALID:c.numOfInvalid++;break;case f.INTERRUPT:c.numofInterrupt++}}}),b.installTo(d.prototype),d}),b("widgets/queue",["base","uploader","queue","file","lib/file","runtime/client","widgets/widget"],function(a,b,c,d,e,f){var g=a.$,h=/\.\w+$/,i=d.Status;return b.register({name:"queue",init:function(b){var d,e,h,i,j,k,l,m=this;if(g.isPlainObject(b.accept)&&(b.accept=[b.accept]),b.accept){for(j=[],h=0,e=b.accept.length;e>h;h++)i=b.accept[h].extensions,i&&j.push(i);j.length&&(k="\\."+j.join(",").replace(/,/g,"$|\\.").replace(/\*/g,".*")+"$"),m.accept=new RegExp(k,"i")}return m.queue=new c,m.stats=m.queue.stats,"html5"===this.request("predict-runtime-type")?(d=a.Deferred(),this.placeholder=l=new f("Placeholder"),l.connectRuntime({runtimeOrder:"html5"},function(){m._ruid=l.getRuid(),d.resolve()}),d.promise()):void 0},_wrapFile:function(a){if(!(a instanceof d)){if(!(a instanceof e)){if(!this._ruid)throw new Error("Can't add external files.");a=new e(this._ruid,a)}a=new d(a)}return a},acceptFile:function(a){var b=!a||!a.size||this.accept&&h.exec(a.name)&&!this.accept.test(a.name);return!b},_addFile:function(a){var b=this;return a=b._wrapFile(a),b.owner.trigger("beforeFileQueued",a)?b.acceptFile(a)?(b.queue.append(a),b.owner.trigger("fileQueued",a),a):void b.owner.trigger("error","Q_TYPE_DENIED",a):void 0},getFile:function(a){return this.queue.getFile(a)},addFile:function(a){var b=this;a.length||(a=[a]),a=g.map(a,function(a){return b._addFile(a)}),b.owner.trigger("filesQueued",a),b.options.auto&&setTimeout(function(){b.request("start-upload")},20)},getStats:function(){return this.stats},removeFile:function(a,b){var c=this;a=a.id?a:c.queue.getFile(a),this.request("cancel-file",a),b&&this.queue.removeFile(a)},getFiles:function(){return this.queue.getFiles.apply(this.queue,arguments)},fetchFile:function(){return this.queue.fetch.apply(this.queue,arguments)},retry:function(a,b){var c,d,e,f=this;if(a)return a=a.id?a:f.queue.getFile(a),a.setStatus(i.QUEUED),void(b||f.request("start-upload"));for(c=f.queue.getFiles(i.ERROR),d=0,e=c.length;e>d;d++)a=c[d],a.setStatus(i.QUEUED);f.request("start-upload")},sortFiles:function(){return this.queue.sort.apply(this.queue,arguments)},reset:function(){this.owner.trigger("reset"),this.queue=new c,this.stats=this.queue.stats},destroy:function(){this.reset(),this.placeholder&&this.placeholder.destroy()}})}),b("widgets/runtime",["uploader","runtime/runtime","widgets/widget"],function(a,b){return a.support=function(){return b.hasRuntime.apply(b,arguments)},a.register({name:"runtime",init:function(){if(!this.predictRuntimeType())throw Error("Runtime Error")},predictRuntimeType:function(){var a,c,d=this.options.runtimeOrder||b.orders,e=this.type;if(!e)for(d=d.split(/\s*,\s*/g),a=0,c=d.length;c>a;a++)if(b.hasRuntime(d[a])){this.type=e=d[a];break}return e}})}),b("lib/transport",["base","runtime/client","mediator"],function(a,b,c){function d(a){var c=this;a=c.options=e.extend(!0,{},d.options,a||{}),b.call(this,"Transport"),this._blob=null,this._formData=a.formData||{},this._headers=a.headers||{},this.on("progress",this._timeout),this.on("load error",function(){c.trigger("progress",1),clearTimeout(c._timer)})}var e=a.$;return d.options={server:"",method:"POST",withCredentials:!1,fileVal:"file",timeout:12e4,formData:{},headers:{},sendAsBinary:!1},e.extend(d.prototype,{appendBlob:function(a,b,c){var d=this,e=d.options;d.getRuid()&&d.disconnectRuntime(),d.connectRuntime(b.ruid,function(){d.exec("init")}),d._blob=b,e.fileVal=a||e.fileVal,e.filename=c||e.filename},append:function(a,b){"object"==typeof a?e.extend(this._formData,a):this._formData[a]=b},setRequestHeader:function(a,b){"object"==typeof a?e.extend(this._headers,a):this._headers[a]=b},send:function(a){this.exec("send",a),this._timeout()},abort:function(){return clearTimeout(this._timer),this.exec("abort")},destroy:function(){this.trigger("destroy"),this.off(),this.exec("destroy"),this.disconnectRuntime()},getResponse:function(){return this.exec("getResponse")},getResponseAsJson:function(){return this.exec("getResponseAsJson")},getStatus:function(){return this.exec("getStatus")},_timeout:function(){var a=this,b=a.options.timeout;b&&(clearTimeout(a._timer),a._timer=setTimeout(function(){a.abort(),a.trigger("error","timeout")},b))}}),c.installTo(d.prototype),d}),b("widgets/upload",["base","uploader","file","lib/transport","widgets/widget"],function(a,b,c,d){function e(a,b){var c,d,e=[],f=a.source,g=f.size,h=b?Math.ceil(g/b):1,i=0,j=0;for(d={file:a,has:function(){return!!e.length},shift:function(){return e.shift()},unshift:function(a){e.unshift(a)}};h>j;)c=Math.min(b,g-i),e.push({file:a,start:i,end:b?i+c:g,total:g,chunks:h,chunk:j++,cuted:d}),i+=c;return a.blocks=e.concat(),a.remaning=e.length,d}var f=a.$,g=a.isPromise,h=c.Status;f.extend(b.options,{prepareNextFile:!1,chunked:!1,chunkSize:5242880,chunkRetry:2,threads:3,formData:{}}),b.register({name:"upload",init:function(){var b=this.owner,c=this;this.runing=!1,this.progress=!1,b.on("startUpload",function(){c.progress=!0}).on("uploadFinished",function(){c.progress=!1}),this.pool=[],this.stack=[],this.pending=[],this.remaning=0,this.__tick=a.bindFn(this._tick,this),b.on("uploadComplete",function(a){a.blocks&&f.each(a.blocks,function(a,b){b.transport&&(b.transport.abort(),b.transport.destroy()),delete b.transport}),delete a.blocks,delete a.remaning})},reset:function(){this.request("stop-upload",!0),this.runing=!1,this.pool=[],this.stack=[],this.pending=[],this.remaning=0,this._trigged=!1,this._promise=null},startUpload:function(b){var c=this;if(f.each(c.request("get-files",h.INVALID),function(){c.request("remove-file",this)}),b)if(b=b.id?b:c.request("get-file",b),b.getStatus()===h.INTERRUPT)f.each(c.pool,function(a,c){c.file===b&&c.transport&&c.transport.send()}),b.setStatus(h.QUEUED);else{if(b.getStatus()===h.PROGRESS)return;b.setStatus(h.QUEUED)}else f.each(c.request("get-files",[h.INITED]),function(){this.setStatus(h.QUEUED)});if(!c.runing){c.runing=!0;var d=[];f.each(c.pool,function(a,b){var e=b.file;e.getStatus()===h.INTERRUPT&&(d.push(e),c._trigged=!1,b.transport&&b.transport.send())});for(var b;b=d.shift();)b.setStatus(h.PROGRESS);b||f.each(c.request("get-files",h.INTERRUPT),function(){this.setStatus(h.PROGRESS)}),c._trigged=!1,a.nextTick(c.__tick),c.owner.trigger("startUpload")}},stopUpload:function(b,c){var d=this;if(b===!0&&(c=b,b=null),d.runing!==!1){if(b){if(b=b.id?b:d.request("get-file",b),b.getStatus()!==h.PROGRESS&&b.getStatus()!==h.QUEUED)return;return b.setStatus(h.INTERRUPT),f.each(d.pool,function(a,c){c.file===b&&(c.transport&&c.transport.abort(),d._putback(c),d._popBlock(c))}),a.nextTick(d.__tick)}d.runing=!1,this._promise&&this._promise.file&&this._promise.file.setStatus(h.INTERRUPT),c&&f.each(d.pool,function(a,b){b.transport&&b.transport.abort(),b.file.setStatus(h.INTERRUPT)}),d.owner.trigger("stopUpload")}},cancelFile:function(a){a=a.id?a:this.request("get-file",a),a.blocks&&f.each(a.blocks,function(a,b){var c=b.transport;c&&(c.abort(),c.destroy(),delete b.transport)}),a.setStatus(h.CANCELLED),this.owner.trigger("fileDequeued",a)},isInProgress:function(){return!!this.progress},_getStats:function(){return this.request("get-stats")},skipFile:function(a,b){a=a.id?a:this.request("get-file",a),a.setStatus(b||h.COMPLETE),a.skipped=!0,a.blocks&&f.each(a.blocks,function(a,b){var c=b.transport;c&&(c.abort(),c.destroy(),delete b.transport)}),this.owner.trigger("uploadSkip",a)},_tick:function(){var b,c,d=this,e=d.options;return d._promise?d._promise.always(d.__tick):void(d.pool.length1&&~"http,abort".indexOf(a)&&b.retried1&&f.extend(m,{chunks:b.chunks,chunk:b.chunk}),i.trigger("uploadBeforeSend",b,m,n),l.appendBlob(j.fileVal,b.blob,k.name),l.append(m),l.setRequestHeader(n),l.send()},_finishFile:function(a,b,c){var d=this.owner;return d.request("after-send-file",arguments,function(){a.setStatus(h.COMPLETE),d.trigger("uploadSuccess",a,b,c)}).fail(function(b){a.getStatus()===h.PROGRESS&&a.setStatus(h.ERROR,b),d.trigger("uploadError",a,b)}).always(function(){d.trigger("uploadComplete",a)})},updateFileProgress:function(a){var b=0,c=0;a.blocks&&(f.each(a.blocks,function(a,b){c+=(b.percentage||0)*(b.end-b.start)}),b=c/a.size,this.owner.trigger("uploadProgress",a,b||0))}})}),b("widgets/validator",["base","uploader","file","widgets/widget"],function(a,b,c){var d,e=a.$,f={};return d={addValidator:function(a,b){f[a]=b},removeValidator:function(a){delete f[a]}},b.register({name:"validator",init:function(){var b=this;a.nextTick(function(){e.each(f,function(){this.call(b.owner)})})}}),d.addValidator("fileNumLimit",function(){var a=this,b=a.options,c=0,d=parseInt(b.fileNumLimit,10),e=!0;d&&(a.on("beforeFileQueued",function(a){return c>=d&&e&&(e=!1,this.trigger("error","Q_EXCEED_NUM_LIMIT",d,a),setTimeout(function(){e=!0},1)),c>=d?!1:!0}),a.on("fileQueued",function(){c++}),a.on("fileDequeued",function(){c--}),a.on("reset",function(){c=0}))}),d.addValidator("fileSizeLimit",function(){var a=this,b=a.options,c=0,d=parseInt(b.fileSizeLimit,10),e=!0;d&&(a.on("beforeFileQueued",function(a){var b=c+a.size>d;return b&&e&&(e=!1,this.trigger("error","Q_EXCEED_SIZE_LIMIT",d,a),setTimeout(function(){e=!0},1)),b?!1:!0}),a.on("fileQueued",function(a){c+=a.size}),a.on("fileDequeued",function(a){c-=a.size}),a.on("reset",function(){c=0}))}),d.addValidator("fileSingleSizeLimit",function(){var a=this,b=a.options,d=b.fileSingleSizeLimit;d&&a.on("beforeFileQueued",function(a){return a.size>d?(a.setStatus(c.Status.INVALID,"exceed_size"),this.trigger("error","F_EXCEED_SIZE",d,a),!1):void 0})}),d.addValidator("duplicate",function(){function a(a){for(var b,c=0,d=0,e=a.length;e>d;d++)b=a.charCodeAt(d),c=b+(c<<6)+(c<<16)-c;return c}var b=this,c=b.options,d={};c.duplicate||(b.on("beforeFileQueued",function(b){var c=b.__hash||(b.__hash=a(b.name+b.size+b.lastModifiedDate));return d[c]?(this.trigger("error","F_DUPLICATE",b),!1):void 0}),b.on("fileQueued",function(a){var b=a.__hash;b&&(d[b]=!0)}),b.on("fileDequeued",function(a){var b=a.__hash;b&&delete d[b]}),b.on("reset",function(){d={}}))}),d}),b("runtime/compbase",[],function(){function a(a,b){this.owner=a,this.options=a.options,this.getRuntime=function(){return b},this.getRuid=function(){return b.uid},this.trigger=function(){return a.trigger.apply(a,arguments)}}return a}),b("runtime/html5/runtime",["base","runtime/runtime","runtime/compbase"],function(b,c,d){function e(){var a={},d=this,e=this.destroy;c.apply(d,arguments),d.type=f,d.exec=function(c,e){var f,h=this,i=h.uid,j=b.slice(arguments,2);return g[c]&&(f=a[i]=a[i]||new g[c](h,d),f[e])?f[e].apply(f,j):void 0},d.destroy=function(){return e&&e.apply(this,arguments)}}var f="html5",g={};return b.inherits(c,{constructor:e,init:function(){var a=this;setTimeout(function(){a.trigger("ready")},1)}}),e.register=function(a,c){var e=g[a]=b.inherits(d,c);return e},a.Blob&&a.FileReader&&a.DataView&&c.addRuntime(f,e),e}),b("runtime/html5/blob",["runtime/html5/runtime","lib/blob"],function(a,b){return a.register("Blob",{slice:function(a,c){var d=this.owner.source,e=d.slice||d.webkitSlice||d.mozSlice; -return d=e.call(d,a,c),new b(this.getRuid(),d)}})}),b("runtime/html5/dnd",["base","runtime/html5/runtime","lib/file"],function(a,b,c){var d=a.$,e="webuploader-dnd-";return b.register("DragAndDrop",{init:function(){var b=this.elem=this.options.container;this.dragEnterHandler=a.bindFn(this._dragEnterHandler,this),this.dragOverHandler=a.bindFn(this._dragOverHandler,this),this.dragLeaveHandler=a.bindFn(this._dragLeaveHandler,this),this.dropHandler=a.bindFn(this._dropHandler,this),this.dndOver=!1,b.on("dragenter",this.dragEnterHandler),b.on("dragover",this.dragOverHandler),b.on("dragleave",this.dragLeaveHandler),b.on("drop",this.dropHandler),this.options.disableGlobalDnd&&(d(document).on("dragover",this.dragOverHandler),d(document).on("drop",this.dropHandler))},_dragEnterHandler:function(a){var b,c=this,d=c._denied||!1;return a=a.originalEvent||a,c.dndOver||(c.dndOver=!0,b=a.dataTransfer.items,b&&b.length&&(c._denied=d=!c.trigger("accept",b)),c.elem.addClass(e+"over"),c.elem[d?"addClass":"removeClass"](e+"denied")),a.dataTransfer.dropEffect=d?"none":"copy",!1},_dragOverHandler:function(a){var b=this.elem.parent().get(0);return b&&!d.contains(b,a.currentTarget)?!1:(clearTimeout(this._leaveTimer),this._dragEnterHandler.call(this,a),!1)},_dragLeaveHandler:function(){var a,b=this;return a=function(){b.dndOver=!1,b.elem.removeClass(e+"over "+e+"denied")},clearTimeout(b._leaveTimer),b._leaveTimer=setTimeout(a,100),!1},_dropHandler:function(a){var b,f,g=this,h=g.getRuid(),i=g.elem.parent().get(0);if(i&&!d.contains(i,a.currentTarget))return!1;a=a.originalEvent||a,b=a.dataTransfer;try{f=b.getData("text/html")}catch(j){}return f?void 0:(g._getTansferFiles(b,function(a){g.trigger("drop",d.map(a,function(a){return new c(h,a)}))}),g.dndOver=!1,g.elem.removeClass(e+"over"),!1)},_getTansferFiles:function(b,c){var d,e,f,g,h,i,j,k=[],l=[];for(d=b.items,e=b.files,j=!(!d||!d[0].webkitGetAsEntry),h=0,i=e.length;i>h;h++)f=e[h],g=d&&d[h],j&&g.webkitGetAsEntry().isDirectory?l.push(this._traverseDirectoryTree(g.webkitGetAsEntry(),k)):k.push(f);a.when.apply(a,l).done(function(){k.length&&c(k)})},_traverseDirectoryTree:function(b,c){var d=a.Deferred(),e=this;return b.isFile?b.file(function(a){c.push(a),d.resolve()}):b.isDirectory&&b.createReader().readEntries(function(b){var f,g=b.length,h=[],i=[];for(f=0;g>f;f++)h.push(e._traverseDirectoryTree(b[f],i));a.when.apply(a,h).then(function(){c.push.apply(c,i),d.resolve()},d.reject)}),d.promise()},destroy:function(){var a=this.elem;a&&(a.off("dragenter",this.dragEnterHandler),a.off("dragover",this.dragOverHandler),a.off("dragleave",this.dragLeaveHandler),a.off("drop",this.dropHandler),this.options.disableGlobalDnd&&(d(document).off("dragover",this.dragOverHandler),d(document).off("drop",this.dropHandler)))}})}),b("runtime/html5/filepaste",["base","runtime/html5/runtime","lib/file"],function(a,b,c){return b.register("FilePaste",{init:function(){var b,c,d,e,f=this.options,g=this.elem=f.container,h=".*";if(f.accept){for(b=[],c=0,d=f.accept.length;d>c;c++)e=f.accept[c].mimeTypes,e&&b.push(e);b.length&&(h=b.join(","),h=h.replace(/,/g,"|").replace(/\*/g,".*"))}this.accept=h=new RegExp(h,"i"),this.hander=a.bindFn(this._pasteHander,this),g.on("paste",this.hander)},_pasteHander:function(a){var b,d,e,f,g,h=[],i=this.getRuid();for(a=a.originalEvent||a,b=a.clipboardData.items,f=0,g=b.length;g>f;f++)d=b[f],"file"===d.kind&&(e=d.getAsFile())&&h.push(new c(i,e));h.length&&(a.preventDefault(),a.stopPropagation(),this.trigger("paste",h))},destroy:function(){this.elem.off("paste",this.hander)}})}),b("runtime/html5/filepicker",["base","runtime/html5/runtime"],function(a,b){var c=a.$;return b.register("FilePicker",{init:function(){var a,b,d,e,f=this.getRuntime().getContainer(),g=this,h=g.owner,i=g.options,j=this.label=c(document.createElement("label")),k=this.input=c(document.createElement("input"));if(k.attr("type","file"),k.attr("name",i.name),k.addClass("webuploader-element-invisible"),j.on("click",function(){k.trigger("click")}),j.css({opacity:0,width:"100%",height:"100%",display:"block",cursor:"pointer",background:"#ffffff"}),i.multiple&&k.attr("multiple","multiple"),i.accept&&i.accept.length>0){for(a=[],b=0,d=i.accept.length;d>b;b++)a.push(i.accept[b].mimeTypes);k.attr("accept",a.join(","))}f.append(k),f.append(j),e=function(a){h.trigger(a.type)},k.on("change",function(a){var b,d=arguments.callee;g.files=a.target.files,b=this.cloneNode(!0),b.value=null,this.parentNode.replaceChild(b,this),k.off(),k=c(b).on("change",d).on("mouseenter mouseleave",e),h.trigger("change")}),j.on("mouseenter mouseleave",e)},getFiles:function(){return this.files},destroy:function(){this.input.off(),this.label.off()}})}),b("runtime/html5/transport",["base","runtime/html5/runtime"],function(a,b){var c=a.noop,d=a.$;return b.register("Transport",{init:function(){this._status=0,this._response=null},send:function(){var b,c,e,f=this.owner,g=this.options,h=this._initAjax(),i=f._blob,j=g.server;g.sendAsBinary?(j+=(/\?/.test(j)?"&":"?")+d.param(f._formData),c=i.getSource()):(b=new FormData,d.each(f._formData,function(a,c){b.append(a,c)}),b.append(g.fileVal,i.getSource(),g.filename||f._formData.name||"")),g.withCredentials&&"withCredentials"in h?(h.open(g.method,j,!0),h.withCredentials=!0):h.open(g.method,j),this._setRequestHeader(h,g.headers),c?(h.overrideMimeType&&h.overrideMimeType("application/octet-stream"),a.os.android?(e=new FileReader,e.onload=function(){h.send(this.result),e=e.onload=null},e.readAsArrayBuffer(c)):h.send(c)):h.send(b)},getResponse:function(){return this._response},getResponseAsJson:function(){return this._parseJson(this._response)},getStatus:function(){return this._status},abort:function(){var a=this._xhr;a&&(a.upload.onprogress=c,a.onreadystatechange=c,a.abort(),this._xhr=a=null)},destroy:function(){this.abort()},_initAjax:function(){var a=this,b=new XMLHttpRequest,d=this.options;return!d.withCredentials||"withCredentials"in b||"undefined"==typeof XDomainRequest||(b=new XDomainRequest),b.upload.onprogress=function(b){var c=0;return b.lengthComputable&&(c=b.loaded/b.total),a.trigger("progress",c)},b.onreadystatechange=function(){return 4===b.readyState?(b.upload.onprogress=c,b.onreadystatechange=c,a._xhr=null,a._status=b.status,b.status>=200&&b.status<300?(a._response=b.responseText,a.trigger("load")):b.status>=500&&b.status<600?(a._response=b.responseText,a.trigger("error","server")):a.trigger("error",a._status?"http":"abort")):void 0},a._xhr=b,b},_setRequestHeader:function(a,b){d.each(b,function(b,c){a.setRequestHeader(b,c)})},_parseJson:function(a){var b;try{b=JSON.parse(a)}catch(c){b={}}return b}})}),b("runtime/flash/runtime",["base","runtime/runtime","runtime/compbase"],function(b,c,d){function e(){var a;try{a=navigator.plugins["Shockwave Flash"],a=a.description}catch(b){try{a=new ActiveXObject("ShockwaveFlash.ShockwaveFlash").GetVariable("$version")}catch(c){a="0.0"}}return a=a.match(/\d+/g),parseFloat(a[0]+"."+a[1],10)}function f(){function d(a,b){var c,d,e=a.type||a;c=e.split("::"),d=c[0],e=c[1],"Ready"===e&&d===j.uid?j.trigger("ready"):f[d]&&f[d].trigger(e.toLowerCase(),a,b)}var e={},f={},g=this.destroy,j=this,k=b.guid("webuploader_");c.apply(j,arguments),j.type=h,j.exec=function(a,c){var d,g=this,h=g.uid,k=b.slice(arguments,2);return f[h]=g,i[a]&&(e[h]||(e[h]=new i[a](g,j)),d=e[h],d[c])?d[c].apply(d,k):j.flashExec.apply(g,arguments)},a[k]=function(){var a=arguments;setTimeout(function(){d.apply(null,a)},1)},this.jsreciver=k,this.destroy=function(){return g&&g.apply(this,arguments)},this.flashExec=function(a,c){var d=j.getFlash(),e=b.slice(arguments,2);return d.exec(this.uid,a,c,e)}}var g=b.$,h="flash",i={};return b.inherits(c,{constructor:f,init:function(){var a,c=this.getContainer(),d=this.options;c.css({position:"absolute",top:"-8px",left:"-8px",width:"9px",height:"9px",overflow:"hidden"}),a='',c.html(a)},getFlash:function(){return this._flash?this._flash:(this._flash=g("#"+this.uid).get(0),this._flash)}}),f.register=function(a,c){return c=i[a]=b.inherits(d,g.extend({flashExec:function(){var a=this.owner,b=this.getRuntime();return b.flashExec.apply(a,arguments)}},c))},e()>=11.4&&c.addRuntime(h,f),f}),b("runtime/flash/filepicker",["base","runtime/flash/runtime"],function(a,b){var c=a.$;return b.register("FilePicker",{init:function(a){var b,d,e=c.extend({},a);for(b=e.accept&&e.accept.length,d=0;b>d;d++)e.accept[d].title||(e.accept[d].title="Files");delete e.button,delete e.id,delete e.container,this.flashExec("FilePicker","init",e)},destroy:function(){this.flashExec("FilePicker","destroy")}})}),b("runtime/flash/transport",["base","runtime/flash/runtime","runtime/client"],function(b,c,d){var e=b.$;return c.register("Transport",{init:function(){this._status=0,this._response=null,this._responseJson=null},send:function(){var a,b=this.owner,c=this.options,d=this._initAjax(),f=b._blob,g=c.server;d.connectRuntime(f.ruid),c.sendAsBinary?(g+=(/\?/.test(g)?"&":"?")+e.param(b._formData),a=f.uid):(e.each(b._formData,function(a,b){d.exec("append",a,b)}),d.exec("appendBlob",c.fileVal,f.uid,c.filename||b._formData.name||"")),this._setRequestHeader(d,c.headers),d.exec("send",{method:c.method,url:g,forceURLStream:c.forceURLStream,mimeType:"application/octet-stream"},a)},getStatus:function(){return this._status},getResponse:function(){return this._response||""},getResponseAsJson:function(){return this._responseJson},abort:function(){var a=this._xhr;a&&(a.exec("abort"),a.destroy(),this._xhr=a=null)},destroy:function(){this.abort()},_initAjax:function(){var b=this,c=new d("XMLHttpRequest");return c.on("uploadprogress progress",function(a){var c=a.loaded/a.total;return c=Math.min(1,Math.max(0,c)),b.trigger("progress",c)}),c.on("load",function(){var d,e=c.exec("getStatus"),f=!1,g="";return c.off(),b._xhr=null,e>=200&&300>e?f=!0:e>=500&&600>e?(f=!0,g="server"):g="http",f&&(b._response=c.exec("getResponse"),b._response=decodeURIComponent(b._response),d=a.JSON&&a.JSON.parse||function(a){try{return new Function("return "+a).call()}catch(b){return{}}},b._responseJson=b._response?d(b._response):{}),c.destroy(),c=null,g?b.trigger("error",g):b.trigger("load")}),c.on("error",function(){c.off(),b._xhr=null,b.trigger("error","http")}),b._xhr=c,c},_setRequestHeader:function(a,b){e.each(b,function(b,c){a.exec("setRequestHeader",b,c)})}})}),b("runtime/flash/blob",["runtime/flash/runtime","lib/blob"],function(a,b){return a.register("Blob",{slice:function(a,c){var d=this.flashExec("Blob","slice",a,c);return new b(d.uid,d)}})}),b("preset/withoutimage",["base","widgets/filednd","widgets/filepaste","widgets/filepicker","widgets/queue","widgets/runtime","widgets/upload","widgets/validator","runtime/html5/blob","runtime/html5/dnd","runtime/html5/filepaste","runtime/html5/filepicker","runtime/html5/transport","runtime/flash/filepicker","runtime/flash/transport","runtime/flash/blob"],function(a){return a}),b("webuploader",["preset/withoutimage"],function(a){return a}),c("webuploader")}); \ No newline at end of file diff --git a/static/plugins/webuploader/webuploader.nolog.js b/static/plugins/webuploader/webuploader.nolog.js deleted file mode 100644 index 294e9e03..00000000 --- a/static/plugins/webuploader/webuploader.nolog.js +++ /dev/null @@ -1,8012 +0,0 @@ -/*! WebUploader 0.1.5 */ - - -/** - * @fileOverview 让内部各个部件的代码可以用[amd](https://github.com/amdjs/amdjs-api/wiki/AMD)模块定义方式组织起来。 - * - * AMD API 内部的简单不完全实现,请忽略。只有当WebUploader被合并成一个文件的时候才会引入。 - */ -(function( root, factory ) { - var modules = {}, - - // 内部require, 简单不完全实现。 - // https://github.com/amdjs/amdjs-api/wiki/require - _require = function( deps, callback ) { - var args, len, i; - - // 如果deps不是数组,则直接返回指定module - if ( typeof deps === 'string' ) { - return getModule( deps ); - } else { - args = []; - for( len = deps.length, i = 0; i < len; i++ ) { - args.push( getModule( deps[ i ] ) ); - } - - return callback.apply( null, args ); - } - }, - - // 内部define,暂时不支持不指定id. - _define = function( id, deps, factory ) { - if ( arguments.length === 2 ) { - factory = deps; - deps = null; - } - - _require( deps || [], function() { - setModule( id, factory, arguments ); - }); - }, - - // 设置module, 兼容CommonJs写法。 - setModule = function( id, factory, args ) { - var module = { - exports: factory - }, - returned; - - if ( typeof factory === 'function' ) { - args.length || (args = [ _require, module.exports, module ]); - returned = factory.apply( null, args ); - returned !== undefined && (module.exports = returned); - } - - modules[ id ] = module.exports; - }, - - // 根据id获取module - getModule = function( id ) { - var module = modules[ id ] || root[ id ]; - - if ( !module ) { - throw new Error( '`' + id + '` is undefined' ); - } - - return module; - }, - - // 将所有modules,将路径ids装换成对象。 - exportsTo = function( obj ) { - var key, host, parts, part, last, ucFirst; - - // make the first character upper case. - ucFirst = function( str ) { - return str && (str.charAt( 0 ).toUpperCase() + str.substr( 1 )); - }; - - for ( key in modules ) { - host = obj; - - if ( !modules.hasOwnProperty( key ) ) { - continue; - } - - parts = key.split('/'); - last = ucFirst( parts.pop() ); - - while( (part = ucFirst( parts.shift() )) ) { - host[ part ] = host[ part ] || {}; - host = host[ part ]; - } - - host[ last ] = modules[ key ]; - } - - return obj; - }, - - makeExport = function( dollar ) { - root.__dollar = dollar; - - // exports every module. - return exportsTo( factory( root, _define, _require ) ); - }, - - origin; - - if ( typeof module === 'object' && typeof module.exports === 'object' ) { - - // For CommonJS and CommonJS-like environments where a proper window is present, - module.exports = makeExport(); - } else if ( typeof define === 'function' && define.amd ) { - - // Allow using this built library as an AMD module - // in another project. That other project will only - // see this AMD call, not the internal modules in - // the closure below. - define([ 'jquery' ], makeExport ); - } else { - - // Browser globals case. Just assign the - // result to a property on the global. - origin = root.WebUploader; - root.WebUploader = makeExport(); - root.WebUploader.noConflict = function() { - root.WebUploader = origin; - }; - } -})( window, function( window, define, require ) { - - - /** - * @fileOverview jQuery or Zepto - */ - define('dollar-third',[],function() { - var $ = window.__dollar || window.jQuery || window.Zepto; - - if ( !$ ) { - throw new Error('jQuery or Zepto not found!'); - } - - return $; - }); - /** - * @fileOverview Dom 操作相关 - */ - define('dollar',[ - 'dollar-third' - ], function( _ ) { - return _; - }); - /** - * @fileOverview 使用jQuery的Promise - */ - define('promise-third',[ - 'dollar' - ], function( $ ) { - return { - Deferred: $.Deferred, - when: $.when, - - isPromise: function( anything ) { - return anything && typeof anything.then === 'function'; - } - }; - }); - /** - * @fileOverview Promise/A+ - */ - define('promise',[ - 'promise-third' - ], function( _ ) { - return _; - }); - /** - * @fileOverview 基础类方法。 - */ - - /** - * Web Uploader内部类的详细说明,以下提及的功能类,都可以在`WebUploader`这个变量中访问到。 - * - * As you know, Web Uploader的每个文件都是用过[AMD](https://github.com/amdjs/amdjs-api/wiki/AMD)规范中的`define`组织起来的, 每个Module都会有个module id. - * 默认module id为该文件的路径,而此路径将会转化成名字空间存放在WebUploader中。如: - * - * * module `base`:WebUploader.Base - * * module `file`: WebUploader.File - * * module `lib/dnd`: WebUploader.Lib.Dnd - * * module `runtime/html5/dnd`: WebUploader.Runtime.Html5.Dnd - * - * - * 以下文档中对类的使用可能省略掉了`WebUploader`前缀。 - * @module WebUploader - * @title WebUploader API文档 - */ - define('base',[ - 'dollar', - 'promise' - ], function( $, promise ) { - - var noop = function() {}, - call = Function.call; - - // http://jsperf.com/uncurrythis - // 反科里化 - function uncurryThis( fn ) { - return function() { - return call.apply( fn, arguments ); - }; - } - - function bindFn( fn, context ) { - return function() { - return fn.apply( context, arguments ); - }; - } - - function createObject( proto ) { - var f; - - if ( Object.create ) { - return Object.create( proto ); - } else { - f = function() {}; - f.prototype = proto; - return new f(); - } - } - - - /** - * 基础类,提供一些简单常用的方法。 - * @class Base - */ - return { - - /** - * @property {String} version 当前版本号。 - */ - version: '0.1.5', - - /** - * @property {jQuery|Zepto} $ 引用依赖的jQuery或者Zepto对象。 - */ - $: $, - - Deferred: promise.Deferred, - - isPromise: promise.isPromise, - - when: promise.when, - - /** - * @description 简单的浏览器检查结果。 - * - * * `webkit` webkit版本号,如果浏览器为非webkit内核,此属性为`undefined`。 - * * `chrome` chrome浏览器版本号,如果浏览器为chrome,此属性为`undefined`。 - * * `ie` ie浏览器版本号,如果浏览器为非ie,此属性为`undefined`。**暂不支持ie10+** - * * `firefox` firefox浏览器版本号,如果浏览器为非firefox,此属性为`undefined`。 - * * `safari` safari浏览器版本号,如果浏览器为非safari,此属性为`undefined`。 - * * `opera` opera浏览器版本号,如果浏览器为非opera,此属性为`undefined`。 - * - * @property {Object} [browser] - */ - browser: (function( ua ) { - var ret = {}, - webkit = ua.match( /WebKit\/([\d.]+)/ ), - chrome = ua.match( /Chrome\/([\d.]+)/ ) || - ua.match( /CriOS\/([\d.]+)/ ), - - ie = ua.match( /MSIE\s([\d\.]+)/ ) || - ua.match( /(?:trident)(?:.*rv:([\w.]+))?/i ), - firefox = ua.match( /Firefox\/([\d.]+)/ ), - safari = ua.match( /Safari\/([\d.]+)/ ), - opera = ua.match( /OPR\/([\d.]+)/ ); - - webkit && (ret.webkit = parseFloat( webkit[ 1 ] )); - chrome && (ret.chrome = parseFloat( chrome[ 1 ] )); - ie && (ret.ie = parseFloat( ie[ 1 ] )); - firefox && (ret.firefox = parseFloat( firefox[ 1 ] )); - safari && (ret.safari = parseFloat( safari[ 1 ] )); - opera && (ret.opera = parseFloat( opera[ 1 ] )); - - return ret; - })( navigator.userAgent ), - - /** - * @description 操作系统检查结果。 - * - * * `android` 如果在android浏览器环境下,此值为对应的android版本号,否则为`undefined`。 - * * `ios` 如果在ios浏览器环境下,此值为对应的ios版本号,否则为`undefined`。 - * @property {Object} [os] - */ - os: (function( ua ) { - var ret = {}, - - // osx = !!ua.match( /\(Macintosh\; Intel / ), - android = ua.match( /(?:Android);?[\s\/]+([\d.]+)?/ ), - ios = ua.match( /(?:iPad|iPod|iPhone).*OS\s([\d_]+)/ ); - - // osx && (ret.osx = true); - android && (ret.android = parseFloat( android[ 1 ] )); - ios && (ret.ios = parseFloat( ios[ 1 ].replace( /_/g, '.' ) )); - - return ret; - })( navigator.userAgent ), - - /** - * 实现类与类之间的继承。 - * @method inherits - * @grammar Base.inherits( super ) => child - * @grammar Base.inherits( super, protos ) => child - * @grammar Base.inherits( super, protos, statics ) => child - * @param {Class} super 父类 - * @param {Object | Function} [protos] 子类或者对象。如果对象中包含constructor,子类将是用此属性值。 - * @param {Function} [protos.constructor] 子类构造器,不指定的话将创建个临时的直接执行父类构造器的方法。 - * @param {Object} [statics] 静态属性或方法。 - * @return {Class} 返回子类。 - * @example - * function Person() { - * console.log( 'Super' ); - * } - * Person.prototype.hello = function() { - * console.log( 'hello' ); - * }; - * - * var Manager = Base.inherits( Person, { - * world: function() { - * console.log( 'World' ); - * } - * }); - * - * // 因为没有指定构造器,父类的构造器将会执行。 - * var instance = new Manager(); // => Super - * - * // 继承子父类的方法 - * instance.hello(); // => hello - * instance.world(); // => World - * - * // 子类的__super__属性指向父类 - * console.log( Manager.__super__ === Person ); // => true - */ - inherits: function( Super, protos, staticProtos ) { - var child; - - if ( typeof protos === 'function' ) { - child = protos; - protos = null; - } else if ( protos && protos.hasOwnProperty('constructor') ) { - child = protos.constructor; - } else { - child = function() { - return Super.apply( this, arguments ); - }; - } - - // 复制静态方法 - $.extend( true, child, Super, staticProtos || {} ); - - /* jshint camelcase: false */ - - // 让子类的__super__属性指向父类。 - child.__super__ = Super.prototype; - - // 构建原型,添加原型方法或属性。 - // 暂时用Object.create实现。 - child.prototype = createObject( Super.prototype ); - protos && $.extend( true, child.prototype, protos ); - - return child; - }, - - /** - * 一个不做任何事情的方法。可以用来赋值给默认的callback. - * @method noop - */ - noop: noop, - - /** - * 返回一个新的方法,此方法将已指定的`context`来执行。 - * @grammar Base.bindFn( fn, context ) => Function - * @method bindFn - * @example - * var doSomething = function() { - * console.log( this.name ); - * }, - * obj = { - * name: 'Object Name' - * }, - * aliasFn = Base.bind( doSomething, obj ); - * - * aliasFn(); // => Object Name - * - */ - bindFn: bindFn, - - /** - * 引用Console.log如果存在的话,否则引用一个[空函数noop](#WebUploader:Base.noop)。 - * @grammar Base.log( args... ) => undefined - * @method log - */ - log: (function() { - if ( window.console ) { - return bindFn( console.log, console ); - } - return noop; - })(), - - nextTick: (function() { - - return function( cb ) { - setTimeout( cb, 1 ); - }; - - // @bug 当浏览器不在当前窗口时就停了。 - // var next = window.requestAnimationFrame || - // window.webkitRequestAnimationFrame || - // window.mozRequestAnimationFrame || - // function( cb ) { - // window.setTimeout( cb, 1000 / 60 ); - // }; - - // // fix: Uncaught TypeError: Illegal invocation - // return bindFn( next, window ); - })(), - - /** - * 被[uncurrythis](http://www.2ality.com/2011/11/uncurrying-this.html)的数组slice方法。 - * 将用来将非数组对象转化成数组对象。 - * @grammar Base.slice( target, start[, end] ) => Array - * @method slice - * @example - * function doSomthing() { - * var args = Base.slice( arguments, 1 ); - * console.log( args ); - * } - * - * doSomthing( 'ignored', 'arg2', 'arg3' ); // => Array ["arg2", "arg3"] - */ - slice: uncurryThis( [].slice ), - - /** - * 生成唯一的ID - * @method guid - * @grammar Base.guid() => String - * @grammar Base.guid( prefx ) => String - */ - guid: (function() { - var counter = 0; - - return function( prefix ) { - var guid = (+new Date()).toString( 32 ), - i = 0; - - for ( ; i < 5; i++ ) { - guid += Math.floor( Math.random() * 65535 ).toString( 32 ); - } - - return (prefix || 'wu_') + guid + (counter++).toString( 32 ); - }; - })(), - - /** - * 格式化文件大小, 输出成带单位的字符串 - * @method formatSize - * @grammar Base.formatSize( size ) => String - * @grammar Base.formatSize( size, pointLength ) => String - * @grammar Base.formatSize( size, pointLength, units ) => String - * @param {Number} size 文件大小 - * @param {Number} [pointLength=2] 精确到的小数点数。 - * @param {Array} [units=[ 'B', 'K', 'M', 'G', 'TB' ]] 单位数组。从字节,到千字节,一直往上指定。如果单位数组里面只指定了到了K(千字节),同时文件大小大于M, 此方法的输出将还是显示成多少K. - * @example - * console.log( Base.formatSize( 100 ) ); // => 100B - * console.log( Base.formatSize( 1024 ) ); // => 1.00K - * console.log( Base.formatSize( 1024, 0 ) ); // => 1K - * console.log( Base.formatSize( 1024 * 1024 ) ); // => 1.00M - * console.log( Base.formatSize( 1024 * 1024 * 1024 ) ); // => 1.00G - * console.log( Base.formatSize( 1024 * 1024 * 1024, 0, ['B', 'KB', 'MB'] ) ); // => 1024MB - */ - formatSize: function( size, pointLength, units ) { - var unit; - - units = units || [ 'B', 'K', 'M', 'G', 'TB' ]; - - while ( (unit = units.shift()) && size > 1024 ) { - size = size / 1024; - } - - return (unit === 'B' ? size : size.toFixed( pointLength || 2 )) + - unit; - } - }; - }); - /** - * 事件处理类,可以独立使用,也可以扩展给对象使用。 - * @fileOverview Mediator - */ - define('mediator',[ - 'base' - ], function( Base ) { - var $ = Base.$, - slice = [].slice, - separator = /\s+/, - protos; - - // 根据条件过滤出事件handlers. - function findHandlers( arr, name, callback, context ) { - return $.grep( arr, function( handler ) { - return handler && - (!name || handler.e === name) && - (!callback || handler.cb === callback || - handler.cb._cb === callback) && - (!context || handler.ctx === context); - }); - } - - function eachEvent( events, callback, iterator ) { - // 不支持对象,只支持多个event用空格隔开 - $.each( (events || '').split( separator ), function( _, key ) { - iterator( key, callback ); - }); - } - - function triggerHanders( events, args ) { - var stoped = false, - i = -1, - len = events.length, - handler; - - while ( ++i < len ) { - handler = events[ i ]; - - if ( handler.cb.apply( handler.ctx2, args ) === false ) { - stoped = true; - break; - } - } - - return !stoped; - } - - protos = { - - /** - * 绑定事件。 - * - * `callback`方法在执行时,arguments将会来源于trigger的时候携带的参数。如 - * ```javascript - * var obj = {}; - * - * // 使得obj有事件行为 - * Mediator.installTo( obj ); - * - * obj.on( 'testa', function( arg1, arg2 ) { - * console.log( arg1, arg2 ); // => 'arg1', 'arg2' - * }); - * - * obj.trigger( 'testa', 'arg1', 'arg2' ); - * ``` - * - * 如果`callback`中,某一个方法`return false`了,则后续的其他`callback`都不会被执行到。 - * 切会影响到`trigger`方法的返回值,为`false`。 - * - * `on`还可以用来添加一个特殊事件`all`, 这样所有的事件触发都会响应到。同时此类`callback`中的arguments有一个不同处, - * 就是第一个参数为`type`,记录当前是什么事件在触发。此类`callback`的优先级比脚低,会再正常`callback`执行完后触发。 - * ```javascript - * obj.on( 'all', function( type, arg1, arg2 ) { - * console.log( type, arg1, arg2 ); // => 'testa', 'arg1', 'arg2' - * }); - * ``` - * - * @method on - * @grammar on( name, callback[, context] ) => self - * @param {String} name 事件名,支持多个事件用空格隔开 - * @param {Function} callback 事件处理器 - * @param {Object} [context] 事件处理器的上下文。 - * @return {self} 返回自身,方便链式 - * @chainable - * @class Mediator - */ - on: function( name, callback, context ) { - var me = this, - set; - - if ( !callback ) { - return this; - } - - set = this._events || (this._events = []); - - eachEvent( name, callback, function( name, callback ) { - var handler = { e: name }; - - handler.cb = callback; - handler.ctx = context; - handler.ctx2 = context || me; - handler.id = set.length; - - set.push( handler ); - }); - - return this; - }, - - /** - * 绑定事件,且当handler执行完后,自动解除绑定。 - * @method once - * @grammar once( name, callback[, context] ) => self - * @param {String} name 事件名 - * @param {Function} callback 事件处理器 - * @param {Object} [context] 事件处理器的上下文。 - * @return {self} 返回自身,方便链式 - * @chainable - */ - once: function( name, callback, context ) { - var me = this; - - if ( !callback ) { - return me; - } - - eachEvent( name, callback, function( name, callback ) { - var once = function() { - me.off( name, once ); - return callback.apply( context || me, arguments ); - }; - - once._cb = callback; - me.on( name, once, context ); - }); - - return me; - }, - - /** - * 解除事件绑定 - * @method off - * @grammar off( [name[, callback[, context] ] ] ) => self - * @param {String} [name] 事件名 - * @param {Function} [callback] 事件处理器 - * @param {Object} [context] 事件处理器的上下文。 - * @return {self} 返回自身,方便链式 - * @chainable - */ - off: function( name, cb, ctx ) { - var events = this._events; - - if ( !events ) { - return this; - } - - if ( !name && !cb && !ctx ) { - this._events = []; - return this; - } - - eachEvent( name, cb, function( name, cb ) { - $.each( findHandlers( events, name, cb, ctx ), function() { - delete events[ this.id ]; - }); - }); - - return this; - }, - - /** - * 触发事件 - * @method trigger - * @grammar trigger( name[, args...] ) => self - * @param {String} type 事件名 - * @param {*} [...] 任意参数 - * @return {Boolean} 如果handler中return false了,则返回false, 否则返回true - */ - trigger: function( type ) { - var args, events, allEvents; - - if ( !this._events || !type ) { - return this; - } - - args = slice.call( arguments, 1 ); - events = findHandlers( this._events, type ); - allEvents = findHandlers( this._events, 'all' ); - - return triggerHanders( events, args ) && - triggerHanders( allEvents, arguments ); - } - }; - - /** - * 中介者,它本身是个单例,但可以通过[installTo](#WebUploader:Mediator:installTo)方法,使任何对象具备事件行为。 - * 主要目的是负责模块与模块之间的合作,降低耦合度。 - * - * @class Mediator - */ - return $.extend({ - - /** - * 可以通过这个接口,使任何对象具备事件功能。 - * @method installTo - * @param {Object} obj 需要具备事件行为的对象。 - * @return {Object} 返回obj. - */ - installTo: function( obj ) { - return $.extend( obj, protos ); - } - - }, protos ); - }); - /** - * @fileOverview Uploader上传类 - */ - define('uploader',[ - 'base', - 'mediator' - ], function( Base, Mediator ) { - - var $ = Base.$; - - /** - * 上传入口类。 - * @class Uploader - * @constructor - * @grammar new Uploader( opts ) => Uploader - * @example - * var uploader = WebUploader.Uploader({ - * swf: 'path_of_swf/Uploader.swf', - * - * // 开起分片上传。 - * chunked: true - * }); - */ - function Uploader( opts ) { - this.options = $.extend( true, {}, Uploader.options, opts ); - this._init( this.options ); - } - - // default Options - // widgets中有相应扩展 - Uploader.options = {}; - Mediator.installTo( Uploader.prototype ); - - // 批量添加纯命令式方法。 - $.each({ - upload: 'start-upload', - stop: 'stop-upload', - getFile: 'get-file', - getFiles: 'get-files', - addFile: 'add-file', - addFiles: 'add-file', - sort: 'sort-files', - removeFile: 'remove-file', - cancelFile: 'cancel-file', - skipFile: 'skip-file', - retry: 'retry', - isInProgress: 'is-in-progress', - makeThumb: 'make-thumb', - md5File: 'md5-file', - getDimension: 'get-dimension', - addButton: 'add-btn', - predictRuntimeType: 'predict-runtime-type', - refresh: 'refresh', - disable: 'disable', - enable: 'enable', - reset: 'reset' - }, function( fn, command ) { - Uploader.prototype[ fn ] = function() { - return this.request( command, arguments ); - }; - }); - - $.extend( Uploader.prototype, { - state: 'pending', - - _init: function( opts ) { - var me = this; - - me.request( 'init', opts, function() { - me.state = 'ready'; - me.trigger('ready'); - }); - }, - - /** - * 获取或者设置Uploader配置项。 - * @method option - * @grammar option( key ) => * - * @grammar option( key, val ) => self - * @example - * - * // 初始状态图片上传前不会压缩 - * var uploader = new WebUploader.Uploader({ - * compress: null; - * }); - * - * // 修改后图片上传前,尝试将图片压缩到1600 * 1600 - * uploader.option( 'compress', { - * width: 1600, - * height: 1600 - * }); - */ - option: function( key, val ) { - var opts = this.options; - - // setter - if ( arguments.length > 1 ) { - - if ( $.isPlainObject( val ) && - $.isPlainObject( opts[ key ] ) ) { - $.extend( opts[ key ], val ); - } else { - opts[ key ] = val; - } - - } else { // getter - return key ? opts[ key ] : opts; - } - }, - - /** - * 获取文件统计信息。返回一个包含一下信息的对象。 - * * `successNum` 上传成功的文件数 - * * `progressNum` 上传中的文件数 - * * `cancelNum` 被删除的文件数 - * * `invalidNum` 无效的文件数 - * * `uploadFailNum` 上传失败的文件数 - * * `queueNum` 还在队列中的文件数 - * * `interruptNum` 被暂停的文件数 - * @method getStats - * @grammar getStats() => Object - */ - getStats: function() { - // return this._mgr.getStats.apply( this._mgr, arguments ); - var stats = this.request('get-stats'); - - return stats ? { - successNum: stats.numOfSuccess, - progressNum: stats.numOfProgress, - - // who care? - // queueFailNum: 0, - cancelNum: stats.numOfCancel, - invalidNum: stats.numOfInvalid, - uploadFailNum: stats.numOfUploadFailed, - queueNum: stats.numOfQueue, - interruptNum: stats.numofInterrupt - } : {}; - }, - - // 需要重写此方法来来支持opts.onEvent和instance.onEvent的处理器 - trigger: function( type/*, args...*/ ) { - var args = [].slice.call( arguments, 1 ), - opts = this.options, - name = 'on' + type.substring( 0, 1 ).toUpperCase() + - type.substring( 1 ); - - if ( - // 调用通过on方法注册的handler. - Mediator.trigger.apply( this, arguments ) === false || - - // 调用opts.onEvent - $.isFunction( opts[ name ] ) && - opts[ name ].apply( this, args ) === false || - - // 调用this.onEvent - $.isFunction( this[ name ] ) && - this[ name ].apply( this, args ) === false || - - // 广播所有uploader的事件。 - Mediator.trigger.apply( Mediator, - [ this, type ].concat( args ) ) === false ) { - - return false; - } - - return true; - }, - - /** - * 销毁 webuploader 实例 - * @method destroy - * @grammar destroy() => undefined - */ - destroy: function() { - this.request( 'destroy', arguments ); - this.off(); - }, - - // widgets/widget.js将补充此方法的详细文档。 - request: Base.noop - }); - - /** - * 创建Uploader实例,等同于new Uploader( opts ); - * @method create - * @class Base - * @static - * @grammar Base.create( opts ) => Uploader - */ - Base.create = Uploader.create = function( opts ) { - return new Uploader( opts ); - }; - - // 暴露Uploader,可以通过它来扩展业务逻辑。 - Base.Uploader = Uploader; - - return Uploader; - }); - /** - * @fileOverview Runtime管理器,负责Runtime的选择, 连接 - */ - define('runtime/runtime',[ - 'base', - 'mediator' - ], function( Base, Mediator ) { - - var $ = Base.$, - factories = {}, - - // 获取对象的第一个key - getFirstKey = function( obj ) { - for ( var key in obj ) { - if ( obj.hasOwnProperty( key ) ) { - return key; - } - } - return null; - }; - - // 接口类。 - function Runtime( options ) { - this.options = $.extend({ - container: document.body - }, options ); - this.uid = Base.guid('rt_'); - } - - $.extend( Runtime.prototype, { - - getContainer: function() { - var opts = this.options, - parent, container; - - if ( this._container ) { - return this._container; - } - - parent = $( opts.container || document.body ); - container = $( document.createElement('div') ); - - container.attr( 'id', 'rt_' + this.uid ); - container.css({ - position: 'absolute', - top: '0px', - left: '0px', - width: '1px', - height: '1px', - overflow: 'hidden' - }); - - parent.append( container ); - parent.addClass('webuploader-container'); - this._container = container; - this._parent = parent; - return container; - }, - - init: Base.noop, - exec: Base.noop, - - destroy: function() { - this._container && this._container.remove(); - this._parent && this._parent.removeClass('webuploader-container'); - this.off(); - } - }); - - Runtime.orders = 'html5,flash'; - - - /** - * 添加Runtime实现。 - * @param {String} type 类型 - * @param {Runtime} factory 具体Runtime实现。 - */ - Runtime.addRuntime = function( type, factory ) { - factories[ type ] = factory; - }; - - Runtime.hasRuntime = function( type ) { - return !!(type ? factories[ type ] : getFirstKey( factories )); - }; - - Runtime.create = function( opts, orders ) { - var type, runtime; - - orders = orders || Runtime.orders; - $.each( orders.split( /\s*,\s*/g ), function() { - if ( factories[ this ] ) { - type = this; - return false; - } - }); - - type = type || getFirstKey( factories ); - - if ( !type ) { - throw new Error('Runtime Error'); - } - - runtime = new factories[ type ]( opts ); - return runtime; - }; - - Mediator.installTo( Runtime.prototype ); - return Runtime; - }); - - /** - * @fileOverview Runtime管理器,负责Runtime的选择, 连接 - */ - define('runtime/client',[ - 'base', - 'mediator', - 'runtime/runtime' - ], function( Base, Mediator, Runtime ) { - - var cache; - - cache = (function() { - var obj = {}; - - return { - add: function( runtime ) { - obj[ runtime.uid ] = runtime; - }, - - get: function( ruid, standalone ) { - var i; - - if ( ruid ) { - return obj[ ruid ]; - } - - for ( i in obj ) { - // 有些类型不能重用,比如filepicker. - if ( standalone && obj[ i ].__standalone ) { - continue; - } - - return obj[ i ]; - } - - return null; - }, - - remove: function( runtime ) { - delete obj[ runtime.uid ]; - } - }; - })(); - - function RuntimeClient( component, standalone ) { - var deferred = Base.Deferred(), - runtime; - - this.uid = Base.guid('client_'); - - // 允许runtime没有初始化之前,注册一些方法在初始化后执行。 - this.runtimeReady = function( cb ) { - return deferred.done( cb ); - }; - - this.connectRuntime = function( opts, cb ) { - - // already connected. - if ( runtime ) { - throw new Error('already connected!'); - } - - deferred.done( cb ); - - if ( typeof opts === 'string' && cache.get( opts ) ) { - runtime = cache.get( opts ); - } - - // 像filePicker只能独立存在,不能公用。 - runtime = runtime || cache.get( null, standalone ); - - // 需要创建 - if ( !runtime ) { - runtime = Runtime.create( opts, opts.runtimeOrder ); - runtime.__promise = deferred.promise(); - runtime.once( 'ready', deferred.resolve ); - runtime.init(); - cache.add( runtime ); - runtime.__client = 1; - } else { - // 来自cache - Base.$.extend( runtime.options, opts ); - runtime.__promise.then( deferred.resolve ); - runtime.__client++; - } - - standalone && (runtime.__standalone = standalone); - return runtime; - }; - - this.getRuntime = function() { - return runtime; - }; - - this.disconnectRuntime = function() { - if ( !runtime ) { - return; - } - - runtime.__client--; - - if ( runtime.__client <= 0 ) { - cache.remove( runtime ); - delete runtime.__promise; - runtime.destroy(); - } - - runtime = null; - }; - - this.exec = function() { - if ( !runtime ) { - return; - } - - var args = Base.slice( arguments ); - component && args.unshift( component ); - - return runtime.exec.apply( this, args ); - }; - - this.getRuid = function() { - return runtime && runtime.uid; - }; - - this.destroy = (function( destroy ) { - return function() { - destroy && destroy.apply( this, arguments ); - this.trigger('destroy'); - this.off(); - this.exec('destroy'); - this.disconnectRuntime(); - }; - })( this.destroy ); - } - - Mediator.installTo( RuntimeClient.prototype ); - return RuntimeClient; - }); - /** - * @fileOverview 错误信息 - */ - define('lib/dnd',[ - 'base', - 'mediator', - 'runtime/client' - ], function( Base, Mediator, RuntimeClent ) { - - var $ = Base.$; - - function DragAndDrop( opts ) { - opts = this.options = $.extend({}, DragAndDrop.options, opts ); - - opts.container = $( opts.container ); - - if ( !opts.container.length ) { - return; - } - - RuntimeClent.call( this, 'DragAndDrop' ); - } - - DragAndDrop.options = { - accept: null, - disableGlobalDnd: false - }; - - Base.inherits( RuntimeClent, { - constructor: DragAndDrop, - - init: function() { - var me = this; - - me.connectRuntime( me.options, function() { - me.exec('init'); - me.trigger('ready'); - }); - } - }); - - Mediator.installTo( DragAndDrop.prototype ); - - return DragAndDrop; - }); - /** - * @fileOverview 组件基类。 - */ - define('widgets/widget',[ - 'base', - 'uploader' - ], function( Base, Uploader ) { - - var $ = Base.$, - _init = Uploader.prototype._init, - _destroy = Uploader.prototype.destroy, - IGNORE = {}, - widgetClass = []; - - function isArrayLike( obj ) { - if ( !obj ) { - return false; - } - - var length = obj.length, - type = $.type( obj ); - - if ( obj.nodeType === 1 && length ) { - return true; - } - - return type === 'array' || type !== 'function' && type !== 'string' && - (length === 0 || typeof length === 'number' && length > 0 && - (length - 1) in obj); - } - - function Widget( uploader ) { - this.owner = uploader; - this.options = uploader.options; - } - - $.extend( Widget.prototype, { - - init: Base.noop, - - // 类Backbone的事件监听声明,监听uploader实例上的事件 - // widget直接无法监听事件,事件只能通过uploader来传递 - invoke: function( apiName, args ) { - - /* - { - 'make-thumb': 'makeThumb' - } - */ - var map = this.responseMap; - - // 如果无API响应声明则忽略 - if ( !map || !(apiName in map) || !(map[ apiName ] in this) || - !$.isFunction( this[ map[ apiName ] ] ) ) { - - return IGNORE; - } - - return this[ map[ apiName ] ].apply( this, args ); - - }, - - /** - * 发送命令。当传入`callback`或者`handler`中返回`promise`时。返回一个当所有`handler`中的promise都完成后完成的新`promise`。 - * @method request - * @grammar request( command, args ) => * | Promise - * @grammar request( command, args, callback ) => Promise - * @for Uploader - */ - request: function() { - return this.owner.request.apply( this.owner, arguments ); - } - }); - - // 扩展Uploader. - $.extend( Uploader.prototype, { - - /** - * @property {String | Array} [disableWidgets=undefined] - * @namespace options - * @for Uploader - * @description 默认所有 Uploader.register 了的 widget 都会被加载,如果禁用某一部分,请通过此 option 指定黑名单。 - */ - - // 覆写_init用来初始化widgets - _init: function() { - var me = this, - widgets = me._widgets = [], - deactives = me.options.disableWidgets || ''; - - $.each( widgetClass, function( _, klass ) { - (!deactives || !~deactives.indexOf( klass._name )) && - widgets.push( new klass( me ) ); - }); - - return _init.apply( me, arguments ); - }, - - request: function( apiName, args, callback ) { - var i = 0, - widgets = this._widgets, - len = widgets && widgets.length, - rlts = [], - dfds = [], - widget, rlt, promise, key; - - args = isArrayLike( args ) ? args : [ args ]; - - for ( ; i < len; i++ ) { - widget = widgets[ i ]; - rlt = widget.invoke( apiName, args ); - - if ( rlt !== IGNORE ) { - - // Deferred对象 - if ( Base.isPromise( rlt ) ) { - dfds.push( rlt ); - } else { - rlts.push( rlt ); - } - } - } - - // 如果有callback,则用异步方式。 - if ( callback || dfds.length ) { - promise = Base.when.apply( Base, dfds ); - key = promise.pipe ? 'pipe' : 'then'; - - // 很重要不能删除。删除了会死循环。 - // 保证执行顺序。让callback总是在下一个 tick 中执行。 - return promise[ key ](function() { - var deferred = Base.Deferred(), - args = arguments; - - if ( args.length === 1 ) { - args = args[ 0 ]; - } - - setTimeout(function() { - deferred.resolve( args ); - }, 1 ); - - return deferred.promise(); - })[ callback ? key : 'done' ]( callback || Base.noop ); - } else { - return rlts[ 0 ]; - } - }, - - destroy: function() { - _destroy.apply( this, arguments ); - this._widgets = null; - } - }); - - /** - * 添加组件 - * @grammar Uploader.register(proto); - * @grammar Uploader.register(map, proto); - * @param {object} responseMap API 名称与函数实现的映射 - * @param {object} proto 组件原型,构造函数通过 constructor 属性定义 - * @method Uploader.register - * @for Uploader - * @example - * Uploader.register({ - * 'make-thumb': 'makeThumb' - * }, { - * init: function( options ) {}, - * makeThumb: function() {} - * }); - * - * Uploader.register({ - * 'make-thumb': function() { - * - * } - * }); - */ - Uploader.register = Widget.register = function( responseMap, widgetProto ) { - var map = { init: 'init', destroy: 'destroy', name: 'anonymous' }, - klass; - - if ( arguments.length === 1 ) { - widgetProto = responseMap; - - // 自动生成 map 表。 - $.each(widgetProto, function(key) { - if ( key[0] === '_' || key === 'name' ) { - key === 'name' && (map.name = widgetProto.name); - return; - } - - map[key.replace(/[A-Z]/g, '-$&').toLowerCase()] = key; - }); - - } else { - map = $.extend( map, responseMap ); - } - - widgetProto.responseMap = map; - klass = Base.inherits( Widget, widgetProto ); - klass._name = map.name; - widgetClass.push( klass ); - - return klass; - }; - - /** - * 删除插件,只有在注册时指定了名字的才能被删除。 - * @grammar Uploader.unRegister(name); - * @param {string} name 组件名字 - * @method Uploader.unRegister - * @for Uploader - * @example - * - * Uploader.register({ - * name: 'custom', - * - * 'make-thumb': function() { - * - * } - * }); - * - * Uploader.unRegister('custom'); - */ - Uploader.unRegister = Widget.unRegister = function( name ) { - if ( !name || name === 'anonymous' ) { - return; - } - - // 删除指定的插件。 - for ( var i = widgetClass.length; i--; ) { - if ( widgetClass[i]._name === name ) { - widgetClass.splice(i, 1) - } - } - }; - - return Widget; - }); - /** - * @fileOverview DragAndDrop Widget。 - */ - define('widgets/filednd',[ - 'base', - 'uploader', - 'lib/dnd', - 'widgets/widget' - ], function( Base, Uploader, Dnd ) { - var $ = Base.$; - - Uploader.options.dnd = ''; - - /** - * @property {Selector} [dnd=undefined] 指定Drag And Drop拖拽的容器,如果不指定,则不启动。 - * @namespace options - * @for Uploader - */ - - /** - * @property {Selector} [disableGlobalDnd=false] 是否禁掉整个页面的拖拽功能,如果不禁用,图片拖进来的时候会默认被浏览器打开。 - * @namespace options - * @for Uploader - */ - - /** - * @event dndAccept - * @param {DataTransferItemList} items DataTransferItem - * @description 阻止此事件可以拒绝某些类型的文件拖入进来。目前只有 chrome 提供这样的 API,且只能通过 mime-type 验证。 - * @for Uploader - */ - return Uploader.register({ - name: 'dnd', - - init: function( opts ) { - - if ( !opts.dnd || - this.request('predict-runtime-type') !== 'html5' ) { - return; - } - - var me = this, - deferred = Base.Deferred(), - options = $.extend({}, { - disableGlobalDnd: opts.disableGlobalDnd, - container: opts.dnd, - accept: opts.accept - }), - dnd; - - this.dnd = dnd = new Dnd( options ); - - dnd.once( 'ready', deferred.resolve ); - dnd.on( 'drop', function( files ) { - me.request( 'add-file', [ files ]); - }); - - // 检测文件是否全部允许添加。 - dnd.on( 'accept', function( items ) { - return me.owner.trigger( 'dndAccept', items ); - }); - - dnd.init(); - - return deferred.promise(); - }, - - destroy: function() { - this.dnd && this.dnd.destroy(); - } - }); - }); - - /** - * @fileOverview 错误信息 - */ - define('lib/filepaste',[ - 'base', - 'mediator', - 'runtime/client' - ], function( Base, Mediator, RuntimeClent ) { - - var $ = Base.$; - - function FilePaste( opts ) { - opts = this.options = $.extend({}, opts ); - opts.container = $( opts.container || document.body ); - RuntimeClent.call( this, 'FilePaste' ); - } - - Base.inherits( RuntimeClent, { - constructor: FilePaste, - - init: function() { - var me = this; - - me.connectRuntime( me.options, function() { - me.exec('init'); - me.trigger('ready'); - }); - } - }); - - Mediator.installTo( FilePaste.prototype ); - - return FilePaste; - }); - /** - * @fileOverview 组件基类。 - */ - define('widgets/filepaste',[ - 'base', - 'uploader', - 'lib/filepaste', - 'widgets/widget' - ], function( Base, Uploader, FilePaste ) { - var $ = Base.$; - - /** - * @property {Selector} [paste=undefined] 指定监听paste事件的容器,如果不指定,不启用此功能。此功能为通过粘贴来添加截屏的图片。建议设置为`document.body`. - * @namespace options - * @for Uploader - */ - return Uploader.register({ - name: 'paste', - - init: function( opts ) { - - if ( !opts.paste || - this.request('predict-runtime-type') !== 'html5' ) { - return; - } - - var me = this, - deferred = Base.Deferred(), - options = $.extend({}, { - container: opts.paste, - accept: opts.accept - }), - paste; - - this.paste = paste = new FilePaste( options ); - - paste.once( 'ready', deferred.resolve ); - paste.on( 'paste', function( files ) { - me.owner.request( 'add-file', [ files ]); - }); - paste.init(); - - return deferred.promise(); - }, - - destroy: function() { - this.paste && this.paste.destroy(); - } - }); - }); - /** - * @fileOverview Blob - */ - define('lib/blob',[ - 'base', - 'runtime/client' - ], function( Base, RuntimeClient ) { - - function Blob( ruid, source ) { - var me = this; - - me.source = source; - me.ruid = ruid; - this.size = source.size || 0; - - // 如果没有指定 mimetype, 但是知道文件后缀。 - if ( !source.type && this.ext && - ~'jpg,jpeg,png,gif,bmp'.indexOf( this.ext ) ) { - this.type = 'image/' + (this.ext === 'jpg' ? 'jpeg' : this.ext); - } else { - this.type = source.type || 'application/octet-stream'; - } - - RuntimeClient.call( me, 'Blob' ); - this.uid = source.uid || this.uid; - - if ( ruid ) { - me.connectRuntime( ruid ); - } - } - - Base.inherits( RuntimeClient, { - constructor: Blob, - - slice: function( start, end ) { - return this.exec( 'slice', start, end ); - }, - - getSource: function() { - return this.source; - } - }); - - return Blob; - }); - /** - * 为了统一化Flash的File和HTML5的File而存在。 - * 以至于要调用Flash里面的File,也可以像调用HTML5版本的File一下。 - * @fileOverview File - */ - define('lib/file',[ - 'base', - 'lib/blob' - ], function( Base, Blob ) { - - var uid = 1, - rExt = /\.([^.]+)$/; - - function File( ruid, file ) { - var ext; - - this.name = file.name || ('untitled' + uid++); - ext = rExt.exec( file.name ) ? RegExp.$1.toLowerCase() : ''; - - // todo 支持其他类型文件的转换。 - // 如果有 mimetype, 但是文件名里面没有找出后缀规律 - if ( !ext && file.type ) { - ext = /\/(jpg|jpeg|png|gif|bmp)$/i.exec( file.type ) ? - RegExp.$1.toLowerCase() : ''; - this.name += '.' + ext; - } - - this.ext = ext; - this.lastModifiedDate = file.lastModifiedDate || - (new Date()).toLocaleString(); - - Blob.apply( this, arguments ); - } - - return Base.inherits( Blob, File ); - }); - - /** - * @fileOverview 错误信息 - */ - define('lib/filepicker',[ - 'base', - 'runtime/client', - 'lib/file' - ], function( Base, RuntimeClent, File ) { - - var $ = Base.$; - - function FilePicker( opts ) { - opts = this.options = $.extend({}, FilePicker.options, opts ); - - opts.container = $( opts.id ); - - if ( !opts.container.length ) { - throw new Error('按钮指定错误'); - } - - opts.innerHTML = opts.innerHTML || opts.label || - opts.container.html() || ''; - - opts.button = $( opts.button || document.createElement('div') ); - opts.button.html( opts.innerHTML ); - opts.container.html( opts.button ); - - RuntimeClent.call( this, 'FilePicker', true ); - } - - FilePicker.options = { - button: null, - container: null, - label: null, - innerHTML: null, - multiple: true, - accept: null, - name: 'file' - }; - - Base.inherits( RuntimeClent, { - constructor: FilePicker, - - init: function() { - var me = this, - opts = me.options, - button = opts.button; - - button.addClass('webuploader-pick'); - - me.on( 'all', function( type ) { - var files; - - switch ( type ) { - case 'mouseenter': - button.addClass('webuploader-pick-hover'); - break; - - case 'mouseleave': - button.removeClass('webuploader-pick-hover'); - break; - - case 'change': - files = me.exec('getFiles'); - me.trigger( 'select', $.map( files, function( file ) { - file = new File( me.getRuid(), file ); - - // 记录来源。 - file._refer = opts.container; - return file; - }), opts.container ); - break; - } - }); - - me.connectRuntime( opts, function() { - me.refresh(); - me.exec( 'init', opts ); - me.trigger('ready'); - }); - - this._resizeHandler = Base.bindFn( this.refresh, this ); - $( window ).on( 'resize', this._resizeHandler ); - }, - - refresh: function() { - var shimContainer = this.getRuntime().getContainer(), - button = this.options.button, - width = button.outerWidth ? - button.outerWidth() : button.width(), - - height = button.outerHeight ? - button.outerHeight() : button.height(), - - pos = button.offset(); - - width && height && shimContainer.css({ - bottom: 'auto', - right: 'auto', - width: width + 'px', - height: height + 'px' - }).offset( pos ); - }, - - enable: function() { - var btn = this.options.button; - - btn.removeClass('webuploader-pick-disable'); - this.refresh(); - }, - - disable: function() { - var btn = this.options.button; - - this.getRuntime().getContainer().css({ - top: '-99999px' - }); - - btn.addClass('webuploader-pick-disable'); - }, - - destroy: function() { - var btn = this.options.button; - $( window ).off( 'resize', this._resizeHandler ); - btn.removeClass('webuploader-pick-disable webuploader-pick-hover ' + - 'webuploader-pick'); - } - }); - - return FilePicker; - }); - - /** - * @fileOverview 文件选择相关 - */ - define('widgets/filepicker',[ - 'base', - 'uploader', - 'lib/filepicker', - 'widgets/widget' - ], function( Base, Uploader, FilePicker ) { - var $ = Base.$; - - $.extend( Uploader.options, { - - /** - * @property {Selector | Object} [pick=undefined] - * @namespace options - * @for Uploader - * @description 指定选择文件的按钮容器,不指定则不创建按钮。 - * - * * `id` {Seletor|dom} 指定选择文件的按钮容器,不指定则不创建按钮。**注意** 这里虽然写的是 id, 但是不是只支持 id, 还支持 class, 或者 dom 节点。 - * * `label` {String} 请采用 `innerHTML` 代替 - * * `innerHTML` {String} 指定按钮文字。不指定时优先从指定的容器中看是否自带文字。 - * * `multiple` {Boolean} 是否开起同时选择多个文件能力。 - */ - pick: null, - - /** - * @property {Arroy} [accept=null] - * @namespace options - * @for Uploader - * @description 指定接受哪些类型的文件。 由于目前还有ext转mimeType表,所以这里需要分开指定。 - * - * * `title` {String} 文字描述 - * * `extensions` {String} 允许的文件后缀,不带点,多个用逗号分割。 - * * `mimeTypes` {String} 多个用逗号分割。 - * - * 如: - * - * ``` - * { - * title: 'Images', - * extensions: 'gif,jpg,jpeg,bmp,png', - * mimeTypes: 'image/*' - * } - * ``` - */ - accept: null/*{ - title: 'Images', - extensions: 'gif,jpg,jpeg,bmp,png', - mimeTypes: 'image/*' - }*/ - }); - - return Uploader.register({ - name: 'picker', - - init: function( opts ) { - this.pickers = []; - return opts.pick && this.addBtn( opts.pick ); - }, - - refresh: function() { - $.each( this.pickers, function() { - this.refresh(); - }); - }, - - /** - * @method addButton - * @for Uploader - * @grammar addButton( pick ) => Promise - * @description - * 添加文件选择按钮,如果一个按钮不够,需要调用此方法来添加。参数跟[options.pick](#WebUploader:Uploader:options)一致。 - * @example - * uploader.addButton({ - * id: '#btnContainer', - * innerHTML: '选择文件' - * }); - */ - addBtn: function( pick ) { - var me = this, - opts = me.options, - accept = opts.accept, - promises = []; - - if ( !pick ) { - return; - } - - $.isPlainObject( pick ) || (pick = { - id: pick - }); - - $( pick.id ).each(function() { - var options, picker, deferred; - - deferred = Base.Deferred(); - - options = $.extend({}, pick, { - accept: $.isPlainObject( accept ) ? [ accept ] : accept, - swf: opts.swf, - runtimeOrder: opts.runtimeOrder, - id: this - }); - - picker = new FilePicker( options ); - - picker.once( 'ready', deferred.resolve ); - picker.on( 'select', function( files ) { - me.owner.request( 'add-file', [ files ]); - }); - picker.init(); - - me.pickers.push( picker ); - - promises.push( deferred.promise() ); - }); - - return Base.when.apply( Base, promises ); - }, - - disable: function() { - $.each( this.pickers, function() { - this.disable(); - }); - }, - - enable: function() { - $.each( this.pickers, function() { - this.enable(); - }); - }, - - destroy: function() { - $.each( this.pickers, function() { - this.destroy(); - }); - this.pickers = null; - } - }); - }); - /** - * @fileOverview Image - */ - define('lib/image',[ - 'base', - 'runtime/client', - 'lib/blob' - ], function( Base, RuntimeClient, Blob ) { - var $ = Base.$; - - // 构造器。 - function Image( opts ) { - this.options = $.extend({}, Image.options, opts ); - RuntimeClient.call( this, 'Image' ); - - this.on( 'load', function() { - this._info = this.exec('info'); - this._meta = this.exec('meta'); - }); - } - - // 默认选项。 - Image.options = { - - // 默认的图片处理质量 - quality: 90, - - // 是否裁剪 - crop: false, - - // 是否保留头部信息 - preserveHeaders: false, - - // 是否允许放大。 - allowMagnify: false - }; - - // 继承RuntimeClient. - Base.inherits( RuntimeClient, { - constructor: Image, - - info: function( val ) { - - // setter - if ( val ) { - this._info = val; - return this; - } - - // getter - return this._info; - }, - - meta: function( val ) { - - // setter - if ( val ) { - this._meta = val; - return this; - } - - // getter - return this._meta; - }, - - loadFromBlob: function( blob ) { - var me = this, - ruid = blob.getRuid(); - - this.connectRuntime( ruid, function() { - me.exec( 'init', me.options ); - me.exec( 'loadFromBlob', blob ); - }); - }, - - resize: function() { - var args = Base.slice( arguments ); - return this.exec.apply( this, [ 'resize' ].concat( args ) ); - }, - - crop: function() { - var args = Base.slice( arguments ); - return this.exec.apply( this, [ 'crop' ].concat( args ) ); - }, - - getAsDataUrl: function( type ) { - return this.exec( 'getAsDataUrl', type ); - }, - - getAsBlob: function( type ) { - var blob = this.exec( 'getAsBlob', type ); - - return new Blob( this.getRuid(), blob ); - } - }); - - return Image; - }); - /** - * @fileOverview 图片操作, 负责预览图片和上传前压缩图片 - */ - define('widgets/image',[ - 'base', - 'uploader', - 'lib/image', - 'widgets/widget' - ], function( Base, Uploader, Image ) { - - var $ = Base.$, - throttle; - - // 根据要处理的文件大小来节流,一次不能处理太多,会卡。 - throttle = (function( max ) { - var occupied = 0, - waiting = [], - tick = function() { - var item; - - while ( waiting.length && occupied < max ) { - item = waiting.shift(); - occupied += item[ 0 ]; - item[ 1 ](); - } - }; - - return function( emiter, size, cb ) { - waiting.push([ size, cb ]); - emiter.once( 'destroy', function() { - occupied -= size; - setTimeout( tick, 1 ); - }); - setTimeout( tick, 1 ); - }; - })( 5 * 1024 * 1024 ); - - $.extend( Uploader.options, { - - /** - * @property {Object} [thumb] - * @namespace options - * @for Uploader - * @description 配置生成缩略图的选项。 - * - * 默认为: - * - * ```javascript - * { - * width: 110, - * height: 110, - * - * // 图片质量,只有type为`image/jpeg`的时候才有效。 - * quality: 70, - * - * // 是否允许放大,如果想要生成小图的时候不失真,此选项应该设置为false. - * allowMagnify: true, - * - * // 是否允许裁剪。 - * crop: true, - * - * // 为空的话则保留原有图片格式。 - * // 否则强制转换成指定的类型。 - * type: 'image/jpeg' - * } - * ``` - */ - thumb: { - width: 110, - height: 110, - quality: 70, - allowMagnify: true, - crop: true, - preserveHeaders: false, - - // 为空的话则保留原有图片格式。 - // 否则强制转换成指定的类型。 - // IE 8下面 base64 大小不能超过 32K 否则预览失败,而非 jpeg 编码的图片很可 - // 能会超过 32k, 所以这里设置成预览的时候都是 image/jpeg - type: 'image/jpeg' - }, - - /** - * @property {Object} [compress] - * @namespace options - * @for Uploader - * @description 配置压缩的图片的选项。如果此选项为`false`, 则图片在上传前不进行压缩。 - * - * 默认为: - * - * ```javascript - * { - * width: 1600, - * height: 1600, - * - * // 图片质量,只有type为`image/jpeg`的时候才有效。 - * quality: 90, - * - * // 是否允许放大,如果想要生成小图的时候不失真,此选项应该设置为false. - * allowMagnify: false, - * - * // 是否允许裁剪。 - * crop: false, - * - * // 是否保留头部meta信息。 - * preserveHeaders: true, - * - * // 如果发现压缩后文件大小比原来还大,则使用原来图片 - * // 此属性可能会影响图片自动纠正功能 - * noCompressIfLarger: false, - * - * // 单位字节,如果图片大小小于此值,不会采用压缩。 - * compressSize: 0 - * } - * ``` - */ - compress: { - width: 1600, - height: 1600, - quality: 90, - allowMagnify: false, - crop: false, - preserveHeaders: true - } - }); - - return Uploader.register({ - - name: 'image', - - - /** - * 生成缩略图,此过程为异步,所以需要传入`callback`。 - * 通常情况在图片加入队里后调用此方法来生成预览图以增强交互效果。 - * - * 当 width 或者 height 的值介于 0 - 1 时,被当成百分比使用。 - * - * `callback`中可以接收到两个参数。 - * * 第一个为error,如果生成缩略图有错误,此error将为真。 - * * 第二个为ret, 缩略图的Data URL值。 - * - * **注意** - * Date URL在IE6/7中不支持,所以不用调用此方法了,直接显示一张暂不支持预览图片好了。 - * 也可以借助服务端,将 base64 数据传给服务端,生成一个临时文件供预览。 - * - * @method makeThumb - * @grammar makeThumb( file, callback ) => undefined - * @grammar makeThumb( file, callback, width, height ) => undefined - * @for Uploader - * @example - * - * uploader.on( 'fileQueued', function( file ) { - * var $li = ...; - * - * uploader.makeThumb( file, function( error, ret ) { - * if ( error ) { - * $li.text('预览错误'); - * } else { - * $li.append(''); - * } - * }); - * - * }); - */ - makeThumb: function( file, cb, width, height ) { - var opts, image; - - file = this.request( 'get-file', file ); - - // 只预览图片格式。 - if ( !file.type.match( /^image/ ) ) { - cb( true ); - return; - } - - opts = $.extend({}, this.options.thumb ); - - // 如果传入的是object. - if ( $.isPlainObject( width ) ) { - opts = $.extend( opts, width ); - width = null; - } - - width = width || opts.width; - height = height || opts.height; - - image = new Image( opts ); - - image.once( 'load', function() { - file._info = file._info || image.info(); - file._meta = file._meta || image.meta(); - - // 如果 width 的值介于 0 - 1 - // 说明设置的是百分比。 - if ( width <= 1 && width > 0 ) { - width = file._info.width * width; - } - - // 同样的规则应用于 height - if ( height <= 1 && height > 0 ) { - height = file._info.height * height; - } - - image.resize( width, height ); - }); - - // 当 resize 完后 - image.once( 'complete', function() { - cb( false, image.getAsDataUrl( opts.type ) ); - image.destroy(); - }); - - image.once( 'error', function( reason ) { - cb( reason || true ); - image.destroy(); - }); - - throttle( image, file.source.size, function() { - file._info && image.info( file._info ); - file._meta && image.meta( file._meta ); - image.loadFromBlob( file.source ); - }); - }, - - beforeSendFile: function( file ) { - var opts = this.options.compress || this.options.resize, - compressSize = opts && opts.compressSize || 0, - noCompressIfLarger = opts && opts.noCompressIfLarger || false, - image, deferred; - - file = this.request( 'get-file', file ); - - // 只压缩 jpeg 图片格式。 - // gif 可能会丢失针 - // bmp png 基本上尺寸都不大,且压缩比比较小。 - if ( !opts || !~'image/jpeg,image/jpg'.indexOf( file.type ) || - file.size < compressSize || - file._compressed ) { - return; - } - - opts = $.extend({}, opts ); - deferred = Base.Deferred(); - - image = new Image( opts ); - - deferred.always(function() { - image.destroy(); - image = null; - }); - image.once( 'error', deferred.reject ); - image.once( 'load', function() { - var width = opts.width, - height = opts.height; - - file._info = file._info || image.info(); - file._meta = file._meta || image.meta(); - - // 如果 width 的值介于 0 - 1 - // 说明设置的是百分比。 - if ( width <= 1 && width > 0 ) { - width = file._info.width * width; - } - - // 同样的规则应用于 height - if ( height <= 1 && height > 0 ) { - height = file._info.height * height; - } - - image.resize( width, height ); - }); - - image.once( 'complete', function() { - var blob, size; - - // 移动端 UC / qq 浏览器的无图模式下 - // ctx.getImageData 处理大图的时候会报 Exception - // INDEX_SIZE_ERR: DOM Exception 1 - try { - blob = image.getAsBlob( opts.type ); - - size = file.size; - - // 如果压缩后,比原来还大则不用压缩后的。 - if ( !noCompressIfLarger || blob.size < size ) { - // file.source.destroy && file.source.destroy(); - file.source = blob; - file.size = blob.size; - - file.trigger( 'resize', blob.size, size ); - } - - // 标记,避免重复压缩。 - file._compressed = true; - deferred.resolve(); - } catch ( e ) { - // 出错了直接继续,让其上传原始图片 - deferred.resolve(); - } - }); - - file._info && image.info( file._info ); - file._meta && image.meta( file._meta ); - - image.loadFromBlob( file.source ); - return deferred.promise(); - } - }); - }); - /** - * @fileOverview 文件属性封装 - */ - define('file',[ - 'base', - 'mediator' - ], function( Base, Mediator ) { - - var $ = Base.$, - idPrefix = 'WU_FILE_', - idSuffix = 0, - rExt = /\.([^.]+)$/, - statusMap = {}; - - function gid() { - return idPrefix + idSuffix++; - } - - /** - * 文件类 - * @class File - * @constructor 构造函数 - * @grammar new File( source ) => File - * @param {Lib.File} source [lib.File](#Lib.File)实例, 此source对象是带有Runtime信息的。 - */ - function WUFile( source ) { - - /** - * 文件名,包括扩展名(后缀) - * @property name - * @type {string} - */ - this.name = source.name || 'Untitled'; - - /** - * 文件体积(字节) - * @property size - * @type {uint} - * @default 0 - */ - this.size = source.size || 0; - - /** - * 文件MIMETYPE类型,与文件类型的对应关系请参考[http://t.cn/z8ZnFny](http://t.cn/z8ZnFny) - * @property type - * @type {string} - * @default 'application/octet-stream' - */ - this.type = source.type || 'application/octet-stream'; - - /** - * 文件最后修改日期 - * @property lastModifiedDate - * @type {int} - * @default 当前时间戳 - */ - this.lastModifiedDate = source.lastModifiedDate || (new Date() * 1); - - /** - * 文件ID,每个对象具有唯一ID,与文件名无关 - * @property id - * @type {string} - */ - this.id = gid(); - - /** - * 文件扩展名,通过文件名获取,例如test.png的扩展名为png - * @property ext - * @type {string} - */ - this.ext = rExt.exec( this.name ) ? RegExp.$1 : ''; - - - /** - * 状态文字说明。在不同的status语境下有不同的用途。 - * @property statusText - * @type {string} - */ - this.statusText = ''; - - // 存储文件状态,防止通过属性直接修改 - statusMap[ this.id ] = WUFile.Status.INITED; - - this.source = source; - this.loaded = 0; - - this.on( 'error', function( msg ) { - this.setStatus( WUFile.Status.ERROR, msg ); - }); - } - - $.extend( WUFile.prototype, { - - /** - * 设置状态,状态变化时会触发`change`事件。 - * @method setStatus - * @grammar setStatus( status[, statusText] ); - * @param {File.Status|String} status [文件状态值](#WebUploader:File:File.Status) - * @param {String} [statusText=''] 状态说明,常在error时使用,用http, abort,server等来标记是由于什么原因导致文件错误。 - */ - setStatus: function( status, text ) { - - var prevStatus = statusMap[ this.id ]; - - typeof text !== 'undefined' && (this.statusText = text); - - if ( status !== prevStatus ) { - statusMap[ this.id ] = status; - /** - * 文件状态变化 - * @event statuschange - */ - this.trigger( 'statuschange', status, prevStatus ); - } - - }, - - /** - * 获取文件状态 - * @return {File.Status} - * @example - 文件状态具体包括以下几种类型: - { - // 初始化 - INITED: 0, - // 已入队列 - QUEUED: 1, - // 正在上传 - PROGRESS: 2, - // 上传出错 - ERROR: 3, - // 上传成功 - COMPLETE: 4, - // 上传取消 - CANCELLED: 5 - } - */ - getStatus: function() { - return statusMap[ this.id ]; - }, - - /** - * 获取文件原始信息。 - * @return {*} - */ - getSource: function() { - return this.source; - }, - - destroy: function() { - this.off(); - delete statusMap[ this.id ]; - } - }); - - Mediator.installTo( WUFile.prototype ); - - /** - * 文件状态值,具体包括以下几种类型: - * * `inited` 初始状态 - * * `queued` 已经进入队列, 等待上传 - * * `progress` 上传中 - * * `complete` 上传完成。 - * * `error` 上传出错,可重试 - * * `interrupt` 上传中断,可续传。 - * * `invalid` 文件不合格,不能重试上传。会自动从队列中移除。 - * * `cancelled` 文件被移除。 - * @property {Object} Status - * @namespace File - * @class File - * @static - */ - WUFile.Status = { - INITED: 'inited', // 初始状态 - QUEUED: 'queued', // 已经进入队列, 等待上传 - PROGRESS: 'progress', // 上传中 - ERROR: 'error', // 上传出错,可重试 - COMPLETE: 'complete', // 上传完成。 - CANCELLED: 'cancelled', // 上传取消。 - INTERRUPT: 'interrupt', // 上传中断,可续传。 - INVALID: 'invalid' // 文件不合格,不能重试上传。 - }; - - return WUFile; - }); - - /** - * @fileOverview 文件队列 - */ - define('queue',[ - 'base', - 'mediator', - 'file' - ], function( Base, Mediator, WUFile ) { - - var $ = Base.$, - STATUS = WUFile.Status; - - /** - * 文件队列, 用来存储各个状态中的文件。 - * @class Queue - * @extends Mediator - */ - function Queue() { - - /** - * 统计文件数。 - * * `numOfQueue` 队列中的文件数。 - * * `numOfSuccess` 上传成功的文件数 - * * `numOfCancel` 被取消的文件数 - * * `numOfProgress` 正在上传中的文件数 - * * `numOfUploadFailed` 上传错误的文件数。 - * * `numOfInvalid` 无效的文件数。 - * * `numofDeleted` 被移除的文件数。 - * @property {Object} stats - */ - this.stats = { - numOfQueue: 0, - numOfSuccess: 0, - numOfCancel: 0, - numOfProgress: 0, - numOfUploadFailed: 0, - numOfInvalid: 0, - numofDeleted: 0, - numofInterrupt: 0 - }; - - // 上传队列,仅包括等待上传的文件 - this._queue = []; - - // 存储所有文件 - this._map = {}; - } - - $.extend( Queue.prototype, { - - /** - * 将新文件加入对队列尾部 - * - * @method append - * @param {File} file 文件对象 - */ - append: function( file ) { - this._queue.push( file ); - this._fileAdded( file ); - return this; - }, - - /** - * 将新文件加入对队列头部 - * - * @method prepend - * @param {File} file 文件对象 - */ - prepend: function( file ) { - this._queue.unshift( file ); - this._fileAdded( file ); - return this; - }, - - /** - * 获取文件对象 - * - * @method getFile - * @param {String} fileId 文件ID - * @return {File} - */ - getFile: function( fileId ) { - if ( typeof fileId !== 'string' ) { - return fileId; - } - return this._map[ fileId ]; - }, - - /** - * 从队列中取出一个指定状态的文件。 - * @grammar fetch( status ) => File - * @method fetch - * @param {String} status [文件状态值](#WebUploader:File:File.Status) - * @return {File} [File](#WebUploader:File) - */ - fetch: function( status ) { - var len = this._queue.length, - i, file; - - status = status || STATUS.QUEUED; - - for ( i = 0; i < len; i++ ) { - file = this._queue[ i ]; - - if ( status === file.getStatus() ) { - return file; - } - } - - return null; - }, - - /** - * 对队列进行排序,能够控制文件上传顺序。 - * @grammar sort( fn ) => undefined - * @method sort - * @param {Function} fn 排序方法 - */ - sort: function( fn ) { - if ( typeof fn === 'function' ) { - this._queue.sort( fn ); - } - }, - - /** - * 获取指定类型的文件列表, 列表中每一个成员为[File](#WebUploader:File)对象。 - * @grammar getFiles( [status1[, status2 ...]] ) => Array - * @method getFiles - * @param {String} [status] [文件状态值](#WebUploader:File:File.Status) - */ - getFiles: function() { - var sts = [].slice.call( arguments, 0 ), - ret = [], - i = 0, - len = this._queue.length, - file; - - for ( ; i < len; i++ ) { - file = this._queue[ i ]; - - if ( sts.length && !~$.inArray( file.getStatus(), sts ) ) { - continue; - } - - ret.push( file ); - } - - return ret; - }, - - /** - * 在队列中删除文件。 - * @grammar removeFile( file ) => Array - * @method removeFile - * @param {File} 文件对象。 - */ - removeFile: function( file ) { - var me = this, - existing = this._map[ file.id ]; - - if ( existing ) { - delete this._map[ file.id ]; - file.destroy(); - this.stats.numofDeleted++; - } - }, - - _fileAdded: function( file ) { - var me = this, - existing = this._map[ file.id ]; - - if ( !existing ) { - this._map[ file.id ] = file; - - file.on( 'statuschange', function( cur, pre ) { - me._onFileStatusChange( cur, pre ); - }); - } - }, - - _onFileStatusChange: function( curStatus, preStatus ) { - var stats = this.stats; - - switch ( preStatus ) { - case STATUS.PROGRESS: - stats.numOfProgress--; - break; - - case STATUS.QUEUED: - stats.numOfQueue --; - break; - - case STATUS.ERROR: - stats.numOfUploadFailed--; - break; - - case STATUS.INVALID: - stats.numOfInvalid--; - break; - - case STATUS.INTERRUPT: - stats.numofInterrupt--; - break; - } - - switch ( curStatus ) { - case STATUS.QUEUED: - stats.numOfQueue++; - break; - - case STATUS.PROGRESS: - stats.numOfProgress++; - break; - - case STATUS.ERROR: - stats.numOfUploadFailed++; - break; - - case STATUS.COMPLETE: - stats.numOfSuccess++; - break; - - case STATUS.CANCELLED: - stats.numOfCancel++; - break; - - - case STATUS.INVALID: - stats.numOfInvalid++; - break; - - case STATUS.INTERRUPT: - stats.numofInterrupt++; - break; - } - } - - }); - - Mediator.installTo( Queue.prototype ); - - return Queue; - }); - /** - * @fileOverview 队列 - */ - define('widgets/queue',[ - 'base', - 'uploader', - 'queue', - 'file', - 'lib/file', - 'runtime/client', - 'widgets/widget' - ], function( Base, Uploader, Queue, WUFile, File, RuntimeClient ) { - - var $ = Base.$, - rExt = /\.\w+$/, - Status = WUFile.Status; - - return Uploader.register({ - name: 'queue', - - init: function( opts ) { - var me = this, - deferred, len, i, item, arr, accept, runtime; - - if ( $.isPlainObject( opts.accept ) ) { - opts.accept = [ opts.accept ]; - } - - // accept中的中生成匹配正则。 - if ( opts.accept ) { - arr = []; - - for ( i = 0, len = opts.accept.length; i < len; i++ ) { - item = opts.accept[ i ].extensions; - item && arr.push( item ); - } - - if ( arr.length ) { - accept = '\\.' + arr.join(',') - .replace( /,/g, '$|\\.' ) - .replace( /\*/g, '.*' ) + '$'; - } - - me.accept = new RegExp( accept, 'i' ); - } - - me.queue = new Queue(); - me.stats = me.queue.stats; - - // 如果当前不是html5运行时,那就算了。 - // 不执行后续操作 - if ( this.request('predict-runtime-type') !== 'html5' ) { - return; - } - - // 创建一个 html5 运行时的 placeholder - // 以至于外部添加原生 File 对象的时候能正确包裹一下供 webuploader 使用。 - deferred = Base.Deferred(); - this.placeholder = runtime = new RuntimeClient('Placeholder'); - runtime.connectRuntime({ - runtimeOrder: 'html5' - }, function() { - me._ruid = runtime.getRuid(); - deferred.resolve(); - }); - return deferred.promise(); - }, - - - // 为了支持外部直接添加一个原生File对象。 - _wrapFile: function( file ) { - if ( !(file instanceof WUFile) ) { - - if ( !(file instanceof File) ) { - if ( !this._ruid ) { - throw new Error('Can\'t add external files.'); - } - file = new File( this._ruid, file ); - } - - file = new WUFile( file ); - } - - return file; - }, - - // 判断文件是否可以被加入队列 - acceptFile: function( file ) { - var invalid = !file || !file.size || this.accept && - - // 如果名字中有后缀,才做后缀白名单处理。 - rExt.exec( file.name ) && !this.accept.test( file.name ); - - return !invalid; - }, - - - /** - * @event beforeFileQueued - * @param {File} file File对象 - * @description 当文件被加入队列之前触发,此事件的handler返回值为`false`,则此文件不会被添加进入队列。 - * @for Uploader - */ - - /** - * @event fileQueued - * @param {File} file File对象 - * @description 当文件被加入队列以后触发。 - * @for Uploader - */ - - _addFile: function( file ) { - var me = this; - - file = me._wrapFile( file ); - - // 不过类型判断允许不允许,先派送 `beforeFileQueued` - if ( !me.owner.trigger( 'beforeFileQueued', file ) ) { - return; - } - - // 类型不匹配,则派送错误事件,并返回。 - if ( !me.acceptFile( file ) ) { - me.owner.trigger( 'error', 'Q_TYPE_DENIED', file ); - return; - } - - me.queue.append( file ); - me.owner.trigger( 'fileQueued', file ); - return file; - }, - - getFile: function( fileId ) { - return this.queue.getFile( fileId ); - }, - - /** - * @event filesQueued - * @param {File} files 数组,内容为原始File(lib/File)对象。 - * @description 当一批文件添加进队列以后触发。 - * @for Uploader - */ - - /** - * @property {Boolean} [auto=false] - * @namespace options - * @for Uploader - * @description 设置为 true 后,不需要手动调用上传,有文件选择即开始上传。 - * - */ - - /** - * @method addFiles - * @grammar addFiles( file ) => undefined - * @grammar addFiles( [file1, file2 ...] ) => undefined - * @param {Array of File or File} [files] Files 对象 数组 - * @description 添加文件到队列 - * @for Uploader - */ - addFile: function( files ) { - var me = this; - - if ( !files.length ) { - files = [ files ]; - } - - files = $.map( files, function( file ) { - return me._addFile( file ); - }); - - me.owner.trigger( 'filesQueued', files ); - - if ( me.options.auto ) { - setTimeout(function() { - me.request('start-upload'); - }, 20 ); - } - }, - - getStats: function() { - return this.stats; - }, - - /** - * @event fileDequeued - * @param {File} file File对象 - * @description 当文件被移除队列后触发。 - * @for Uploader - */ - - /** - * @method removeFile - * @grammar removeFile( file ) => undefined - * @grammar removeFile( id ) => undefined - * @grammar removeFile( file, true ) => undefined - * @grammar removeFile( id, true ) => undefined - * @param {File|id} file File对象或这File对象的id - * @description 移除某一文件, 默认只会标记文件状态为已取消,如果第二个参数为 `true` 则会从 queue 中移除。 - * @for Uploader - * @example - * - * $li.on('click', '.remove-this', function() { - * uploader.removeFile( file ); - * }) - */ - removeFile: function( file, remove ) { - var me = this; - - file = file.id ? file : me.queue.getFile( file ); - - this.request( 'cancel-file', file ); - - if ( remove ) { - this.queue.removeFile( file ); - } - }, - - /** - * @method getFiles - * @grammar getFiles() => Array - * @grammar getFiles( status1, status2, status... ) => Array - * @description 返回指定状态的文件集合,不传参数将返回所有状态的文件。 - * @for Uploader - * @example - * console.log( uploader.getFiles() ); // => all files - * console.log( uploader.getFiles('error') ) // => all error files. - */ - getFiles: function() { - return this.queue.getFiles.apply( this.queue, arguments ); - }, - - fetchFile: function() { - return this.queue.fetch.apply( this.queue, arguments ); - }, - - /** - * @method retry - * @grammar retry() => undefined - * @grammar retry( file ) => undefined - * @description 重试上传,重试指定文件,或者从出错的文件开始重新上传。 - * @for Uploader - * @example - * function retry() { - * uploader.retry(); - * } - */ - retry: function( file, noForceStart ) { - var me = this, - files, i, len; - - if ( file ) { - file = file.id ? file : me.queue.getFile( file ); - file.setStatus( Status.QUEUED ); - noForceStart || me.request('start-upload'); - return; - } - - files = me.queue.getFiles( Status.ERROR ); - i = 0; - len = files.length; - - for ( ; i < len; i++ ) { - file = files[ i ]; - file.setStatus( Status.QUEUED ); - } - - me.request('start-upload'); - }, - - /** - * @method sort - * @grammar sort( fn ) => undefined - * @description 排序队列中的文件,在上传之前调整可以控制上传顺序。 - * @for Uploader - */ - sortFiles: function() { - return this.queue.sort.apply( this.queue, arguments ); - }, - - /** - * @event reset - * @description 当 uploader 被重置的时候触发。 - * @for Uploader - */ - - /** - * @method reset - * @grammar reset() => undefined - * @description 重置uploader。目前只重置了队列。 - * @for Uploader - * @example - * uploader.reset(); - */ - reset: function() { - this.owner.trigger('reset'); - this.queue = new Queue(); - this.stats = this.queue.stats; - }, - - destroy: function() { - this.reset(); - this.placeholder && this.placeholder.destroy(); - } - }); - - }); - /** - * @fileOverview 添加获取Runtime相关信息的方法。 - */ - define('widgets/runtime',[ - 'uploader', - 'runtime/runtime', - 'widgets/widget' - ], function( Uploader, Runtime ) { - - Uploader.support = function() { - return Runtime.hasRuntime.apply( Runtime, arguments ); - }; - - /** - * @property {Object} [runtimeOrder=html5,flash] - * @namespace options - * @for Uploader - * @description 指定运行时启动顺序。默认会想尝试 html5 是否支持,如果支持则使用 html5, 否则则使用 flash. - * - * 可以将此值设置成 `flash`,来强制使用 flash 运行时。 - */ - - return Uploader.register({ - name: 'runtime', - - init: function() { - if ( !this.predictRuntimeType() ) { - throw Error('Runtime Error'); - } - }, - - /** - * 预测Uploader将采用哪个`Runtime` - * @grammar predictRuntimeType() => String - * @method predictRuntimeType - * @for Uploader - */ - predictRuntimeType: function() { - var orders = this.options.runtimeOrder || Runtime.orders, - type = this.type, - i, len; - - if ( !type ) { - orders = orders.split( /\s*,\s*/g ); - - for ( i = 0, len = orders.length; i < len; i++ ) { - if ( Runtime.hasRuntime( orders[ i ] ) ) { - this.type = type = orders[ i ]; - break; - } - } - } - - return type; - } - }); - }); - /** - * @fileOverview Transport - */ - define('lib/transport',[ - 'base', - 'runtime/client', - 'mediator' - ], function( Base, RuntimeClient, Mediator ) { - - var $ = Base.$; - - function Transport( opts ) { - var me = this; - - opts = me.options = $.extend( true, {}, Transport.options, opts || {} ); - RuntimeClient.call( this, 'Transport' ); - - this._blob = null; - this._formData = opts.formData || {}; - this._headers = opts.headers || {}; - - this.on( 'progress', this._timeout ); - this.on( 'load error', function() { - me.trigger( 'progress', 1 ); - clearTimeout( me._timer ); - }); - } - - Transport.options = { - server: '', - method: 'POST', - - // 跨域时,是否允许携带cookie, 只有html5 runtime才有效 - withCredentials: false, - fileVal: 'file', - timeout: 2 * 60 * 1000, // 2分钟 - formData: {}, - headers: {}, - sendAsBinary: false - }; - - $.extend( Transport.prototype, { - - // 添加Blob, 只能添加一次,最后一次有效。 - appendBlob: function( key, blob, filename ) { - var me = this, - opts = me.options; - - if ( me.getRuid() ) { - me.disconnectRuntime(); - } - - // 连接到blob归属的同一个runtime. - me.connectRuntime( blob.ruid, function() { - me.exec('init'); - }); - - me._blob = blob; - opts.fileVal = key || opts.fileVal; - opts.filename = filename || opts.filename; - }, - - // 添加其他字段 - append: function( key, value ) { - if ( typeof key === 'object' ) { - $.extend( this._formData, key ); - } else { - this._formData[ key ] = value; - } - }, - - setRequestHeader: function( key, value ) { - if ( typeof key === 'object' ) { - $.extend( this._headers, key ); - } else { - this._headers[ key ] = value; - } - }, - - send: function( method ) { - this.exec( 'send', method ); - this._timeout(); - }, - - abort: function() { - clearTimeout( this._timer ); - return this.exec('abort'); - }, - - destroy: function() { - this.trigger('destroy'); - this.off(); - this.exec('destroy'); - this.disconnectRuntime(); - }, - - getResponse: function() { - return this.exec('getResponse'); - }, - - getResponseAsJson: function() { - return this.exec('getResponseAsJson'); - }, - - getStatus: function() { - return this.exec('getStatus'); - }, - - _timeout: function() { - var me = this, - duration = me.options.timeout; - - if ( !duration ) { - return; - } - - clearTimeout( me._timer ); - me._timer = setTimeout(function() { - me.abort(); - me.trigger( 'error', 'timeout' ); - }, duration ); - } - - }); - - // 让Transport具备事件功能。 - Mediator.installTo( Transport.prototype ); - - return Transport; - }); - /** - * @fileOverview 负责文件上传相关。 - */ - define('widgets/upload',[ - 'base', - 'uploader', - 'file', - 'lib/transport', - 'widgets/widget' - ], function( Base, Uploader, WUFile, Transport ) { - - var $ = Base.$, - isPromise = Base.isPromise, - Status = WUFile.Status; - - // 添加默认配置项 - $.extend( Uploader.options, { - - - /** - * @property {Boolean} [prepareNextFile=false] - * @namespace options - * @for Uploader - * @description 是否允许在文件传输时提前把下一个文件准备好。 - * 对于一个文件的准备工作比较耗时,比如图片压缩,md5序列化。 - * 如果能提前在当前文件传输期处理,可以节省总体耗时。 - */ - prepareNextFile: false, - - /** - * @property {Boolean} [chunked=false] - * @namespace options - * @for Uploader - * @description 是否要分片处理大文件上传。 - */ - chunked: false, - - /** - * @property {Boolean} [chunkSize=5242880] - * @namespace options - * @for Uploader - * @description 如果要分片,分多大一片? 默认大小为5M. - */ - chunkSize: 5 * 1024 * 1024, - - /** - * @property {Boolean} [chunkRetry=2] - * @namespace options - * @for Uploader - * @description 如果某个分片由于网络问题出错,允许自动重传多少次? - */ - chunkRetry: 2, - - /** - * @property {Boolean} [threads=3] - * @namespace options - * @for Uploader - * @description 上传并发数。允许同时最大上传进程数。 - */ - threads: 3, - - - /** - * @property {Object} [formData={}] - * @namespace options - * @for Uploader - * @description 文件上传请求的参数表,每次发送都会发送此对象中的参数。 - */ - formData: {} - - /** - * @property {Object} [fileVal='file'] - * @namespace options - * @for Uploader - * @description 设置文件上传域的name。 - */ - - /** - * @property {Object} [method='POST'] - * @namespace options - * @for Uploader - * @description 文件上传方式,`POST`或者`GET`。 - */ - - /** - * @property {Object} [sendAsBinary=false] - * @namespace options - * @for Uploader - * @description 是否已二进制的流的方式发送文件,这样整个上传内容`php://input`都为文件内容, - * 其他参数在$_GET数组中。 - */ - }); - - // 负责将文件切片。 - function CuteFile( file, chunkSize ) { - var pending = [], - blob = file.source, - total = blob.size, - chunks = chunkSize ? Math.ceil( total / chunkSize ) : 1, - start = 0, - index = 0, - len, api; - - api = { - file: file, - - has: function() { - return !!pending.length; - }, - - shift: function() { - return pending.shift(); - }, - - unshift: function( block ) { - pending.unshift( block ); - } - }; - - while ( index < chunks ) { - len = Math.min( chunkSize, total - start ); - - pending.push({ - file: file, - start: start, - end: chunkSize ? (start + len) : total, - total: total, - chunks: chunks, - chunk: index++, - cuted: api - }); - start += len; - } - - file.blocks = pending.concat(); - file.remaning = pending.length; - - return api; - } - - Uploader.register({ - name: 'upload', - - init: function() { - var owner = this.owner, - me = this; - - this.runing = false; - this.progress = false; - - owner - .on( 'startUpload', function() { - me.progress = true; - }) - .on( 'uploadFinished', function() { - me.progress = false; - }); - - // 记录当前正在传的数据,跟threads相关 - this.pool = []; - - // 缓存分好片的文件。 - this.stack = []; - - // 缓存即将上传的文件。 - this.pending = []; - - // 跟踪还有多少分片在上传中但是没有完成上传。 - this.remaning = 0; - this.__tick = Base.bindFn( this._tick, this ); - - owner.on( 'uploadComplete', function( file ) { - - // 把其他块取消了。 - file.blocks && $.each( file.blocks, function( _, v ) { - v.transport && (v.transport.abort(), v.transport.destroy()); - delete v.transport; - }); - - delete file.blocks; - delete file.remaning; - }); - }, - - reset: function() { - this.request( 'stop-upload', true ); - this.runing = false; - this.pool = []; - this.stack = []; - this.pending = []; - this.remaning = 0; - this._trigged = false; - this._promise = null; - }, - - /** - * @event startUpload - * @description 当开始上传流程时触发。 - * @for Uploader - */ - - /** - * 开始上传。此方法可以从初始状态调用开始上传流程,也可以从暂停状态调用,继续上传流程。 - * - * 可以指定开始某一个文件。 - * @grammar upload() => undefined - * @grammar upload( file | fileId) => undefined - * @method upload - * @for Uploader - */ - startUpload: function(file) { - var me = this; - - // 移出invalid的文件 - $.each( me.request( 'get-files', Status.INVALID ), function() { - me.request( 'remove-file', this ); - }); - - // 如果指定了开始某个文件,则只开始指定文件。 - if ( file ) { - file = file.id ? file : me.request( 'get-file', file ); - - if (file.getStatus() === Status.INTERRUPT) { - $.each( me.pool, function( _, v ) { - - // 之前暂停过。 - if (v.file !== file) { - return; - } - - v.transport && v.transport.send(); - }); - - file.setStatus( Status.QUEUED ); - } else if (file.getStatus() === Status.PROGRESS) { - return; - } else { - file.setStatus( Status.QUEUED ); - } - } else { - $.each( me.request( 'get-files', [ Status.INITED ] ), function() { - this.setStatus( Status.QUEUED ); - }); - } - - if ( me.runing ) { - return; - } - - me.runing = true; - - var files = []; - - // 如果有暂停的,则续传 - $.each( me.pool, function( _, v ) { - var file = v.file; - - if ( file.getStatus() === Status.INTERRUPT ) { - files.push(file); - me._trigged = false; - v.transport && v.transport.send(); - } - }); - - var file; - while ( (file = files.shift()) ) { - file.setStatus( Status.PROGRESS ); - } - - file || $.each( me.request( 'get-files', - Status.INTERRUPT ), function() { - this.setStatus( Status.PROGRESS ); - }); - - me._trigged = false; - Base.nextTick( me.__tick ); - me.owner.trigger('startUpload'); - }, - - /** - * @event stopUpload - * @description 当开始上传流程暂停时触发。 - * @for Uploader - */ - - /** - * 暂停上传。第一个参数为是否中断上传当前正在上传的文件。 - * - * 如果第一个参数是文件,则只暂停指定文件。 - * @grammar stop() => undefined - * @grammar stop( true ) => undefined - * @grammar stop( file ) => undefined - * @method stop - * @for Uploader - */ - stopUpload: function( file, interrupt ) { - var me = this; - - if (file === true) { - interrupt = file; - file = null; - } - - if ( me.runing === false ) { - return; - } - - // 如果只是暂停某个文件。 - if ( file ) { - file = file.id ? file : me.request( 'get-file', file ); - - if ( file.getStatus() !== Status.PROGRESS && - file.getStatus() !== Status.QUEUED ) { - return; - } - - file.setStatus( Status.INTERRUPT ); - $.each( me.pool, function( _, v ) { - - // 只 abort 指定的文件。 - if (v.file !== file) { - return; - } - - v.transport && v.transport.abort(); - me._putback(v); - me._popBlock(v); - }); - - return Base.nextTick( me.__tick ); - } - - me.runing = false; - - if (this._promise && this._promise.file) { - this._promise.file.setStatus( Status.INTERRUPT ); - } - - interrupt && $.each( me.pool, function( _, v ) { - v.transport && v.transport.abort(); - v.file.setStatus( Status.INTERRUPT ); - }); - - me.owner.trigger('stopUpload'); - }, - - /** - * @method cancelFile - * @grammar cancelFile( file ) => undefined - * @grammar cancelFile( id ) => undefined - * @param {File|id} file File对象或这File对象的id - * @description 标记文件状态为已取消, 同时将中断文件传输。 - * @for Uploader - * @example - * - * $li.on('click', '.remove-this', function() { - * uploader.cancelFile( file ); - * }) - */ - cancelFile: function( file ) { - file = file.id ? file : this.request( 'get-file', file ); - - // 如果正在上传。 - file.blocks && $.each( file.blocks, function( _, v ) { - var _tr = v.transport; - - if ( _tr ) { - _tr.abort(); - _tr.destroy(); - delete v.transport; - } - }); - - file.setStatus( Status.CANCELLED ); - this.owner.trigger( 'fileDequeued', file ); - }, - - /** - * 判断`Uplaode`r是否正在上传中。 - * @grammar isInProgress() => Boolean - * @method isInProgress - * @for Uploader - */ - isInProgress: function() { - return !!this.progress; - }, - - _getStats: function() { - return this.request('get-stats'); - }, - - /** - * 掉过一个文件上传,直接标记指定文件为已上传状态。 - * @grammar skipFile( file ) => undefined - * @method skipFile - * @for Uploader - */ - skipFile: function( file, status ) { - file = file.id ? file : this.request( 'get-file', file ); - - file.setStatus( status || Status.COMPLETE ); - file.skipped = true; - - // 如果正在上传。 - file.blocks && $.each( file.blocks, function( _, v ) { - var _tr = v.transport; - - if ( _tr ) { - _tr.abort(); - _tr.destroy(); - delete v.transport; - } - }); - - this.owner.trigger( 'uploadSkip', file ); - }, - - /** - * @event uploadFinished - * @description 当所有文件上传结束时触发。 - * @for Uploader - */ - _tick: function() { - var me = this, - opts = me.options, - fn, val; - - // 上一个promise还没有结束,则等待完成后再执行。 - if ( me._promise ) { - return me._promise.always( me.__tick ); - } - - // 还有位置,且还有文件要处理的话。 - if ( me.pool.length < opts.threads && (val = me._nextBlock()) ) { - me._trigged = false; - - fn = function( val ) { - me._promise = null; - - // 有可能是reject过来的,所以要检测val的类型。 - val && val.file && me._startSend( val ); - Base.nextTick( me.__tick ); - }; - - me._promise = isPromise( val ) ? val.always( fn ) : fn( val ); - - // 没有要上传的了,且没有正在传输的了。 - } else if ( !me.remaning && !me._getStats().numOfQueue && - !me._getStats().numofInterrupt ) { - me.runing = false; - - me._trigged || Base.nextTick(function() { - me.owner.trigger('uploadFinished'); - }); - me._trigged = true; - } - }, - - _putback: function(block) { - var idx; - - block.cuted.unshift(block); - idx = this.stack.indexOf(block.cuted); - - if (!~idx) { - this.stack.unshift(block.cuted); - } - }, - - _getStack: function() { - var i = 0, - act; - - while ( (act = this.stack[ i++ ]) ) { - if ( act.has() && act.file.getStatus() === Status.PROGRESS ) { - return act; - } else if (!act.has() || - act.file.getStatus() !== Status.PROGRESS && - act.file.getStatus() !== Status.INTERRUPT ) { - - // 把已经处理完了的,或者,状态为非 progress(上传中)、 - // interupt(暂停中) 的移除。 - this.stack.splice( --i, 1 ); - } - } - - return null; - }, - - _nextBlock: function() { - var me = this, - opts = me.options, - act, next, done, preparing; - - // 如果当前文件还有没有需要传输的,则直接返回剩下的。 - if ( (act = this._getStack()) ) { - - // 是否提前准备下一个文件 - if ( opts.prepareNextFile && !me.pending.length ) { - me._prepareNextFile(); - } - - return act.shift(); - - // 否则,如果正在运行,则准备下一个文件,并等待完成后返回下个分片。 - } else if ( me.runing ) { - - // 如果缓存中有,则直接在缓存中取,没有则去queue中取。 - if ( !me.pending.length && me._getStats().numOfQueue ) { - me._prepareNextFile(); - } - - next = me.pending.shift(); - done = function( file ) { - if ( !file ) { - return null; - } - - act = CuteFile( file, opts.chunked ? opts.chunkSize : 0 ); - me.stack.push(act); - return act.shift(); - }; - - // 文件可能还在prepare中,也有可能已经完全准备好了。 - if ( isPromise( next) ) { - preparing = next.file; - next = next[ next.pipe ? 'pipe' : 'then' ]( done ); - next.file = preparing; - return next; - } - - return done( next ); - } - }, - - - /** - * @event uploadStart - * @param {File} file File对象 - * @description 某个文件开始上传前触发,一个文件只会触发一次。 - * @for Uploader - */ - _prepareNextFile: function() { - var me = this, - file = me.request('fetch-file'), - pending = me.pending, - promise; - - if ( file ) { - promise = me.request( 'before-send-file', file, function() { - - // 有可能文件被skip掉了。文件被skip掉后,状态坑定不是Queued. - if ( file.getStatus() === Status.PROGRESS || - file.getStatus() === Status.INTERRUPT ) { - return file; - } - - return me._finishFile( file ); - }); - - me.owner.trigger( 'uploadStart', file ); - file.setStatus( Status.PROGRESS ); - - promise.file = file; - - // 如果还在pending中,则替换成文件本身。 - promise.done(function() { - var idx = $.inArray( promise, pending ); - - ~idx && pending.splice( idx, 1, file ); - }); - - // befeore-send-file的钩子就有错误发生。 - promise.fail(function( reason ) { - file.setStatus( Status.ERROR, reason ); - me.owner.trigger( 'uploadError', file, reason ); - me.owner.trigger( 'uploadComplete', file ); - }); - - pending.push( promise ); - } - }, - - // 让出位置了,可以让其他分片开始上传 - _popBlock: function( block ) { - var idx = $.inArray( block, this.pool ); - - this.pool.splice( idx, 1 ); - block.file.remaning--; - this.remaning--; - }, - - // 开始上传,可以被掉过。如果promise被reject了,则表示跳过此分片。 - _startSend: function( block ) { - var me = this, - file = block.file, - promise; - - // 有可能在 before-send-file 的 promise 期间改变了文件状态。 - // 如:暂停,取消 - // 我们不能中断 promise, 但是可以在 promise 完后,不做上传操作。 - if ( file.getStatus() !== Status.PROGRESS ) { - - // 如果是中断,则还需要放回去。 - if (file.getStatus() === Status.INTERRUPT) { - me._putback(block); - } - - return; - } - - me.pool.push( block ); - me.remaning++; - - // 如果没有分片,则直接使用原始的。 - // 不会丢失content-type信息。 - block.blob = block.chunks === 1 ? file.source : - file.source.slice( block.start, block.end ); - - // hook, 每个分片发送之前可能要做些异步的事情。 - promise = me.request( 'before-send', block, function() { - - // 有可能文件已经上传出错了,所以不需要再传输了。 - if ( file.getStatus() === Status.PROGRESS ) { - me._doSend( block ); - } else { - me._popBlock( block ); - Base.nextTick( me.__tick ); - } - }); - - // 如果为fail了,则跳过此分片。 - promise.fail(function() { - if ( file.remaning === 1 ) { - me._finishFile( file ).always(function() { - block.percentage = 1; - me._popBlock( block ); - me.owner.trigger( 'uploadComplete', file ); - Base.nextTick( me.__tick ); - }); - } else { - block.percentage = 1; - me.updateFileProgress( file ); - me._popBlock( block ); - Base.nextTick( me.__tick ); - } - }); - }, - - - /** - * @event uploadBeforeSend - * @param {Object} object - * @param {Object} data 默认的上传参数,可以扩展此对象来控制上传参数。 - * @param {Object} headers 可以扩展此对象来控制上传头部。 - * @description 当某个文件的分块在发送前触发,主要用来询问是否要添加附带参数,大文件在开起分片上传的前提下此事件可能会触发多次。 - * @for Uploader - */ - - /** - * @event uploadAccept - * @param {Object} object - * @param {Object} ret 服务端的返回数据,json格式,如果服务端不是json格式,从ret._raw中取数据,自行解析。 - * @description 当某个文件上传到服务端响应后,会派送此事件来询问服务端响应是否有效。如果此事件handler返回值为`false`, 则此文件将派送`server`类型的`uploadError`事件。 - * @for Uploader - */ - - /** - * @event uploadProgress - * @param {File} file File对象 - * @param {Number} percentage 上传进度 - * @description 上传过程中触发,携带上传进度。 - * @for Uploader - */ - - - /** - * @event uploadError - * @param {File} file File对象 - * @param {String} reason 出错的code - * @description 当文件上传出错时触发。 - * @for Uploader - */ - - /** - * @event uploadSuccess - * @param {File} file File对象 - * @param {Object} response 服务端返回的数据 - * @description 当文件上传成功时触发。 - * @for Uploader - */ - - /** - * @event uploadComplete - * @param {File} [file] File对象 - * @description 不管成功或者失败,文件上传完成时触发。 - * @for Uploader - */ - - // 做上传操作。 - _doSend: function( block ) { - var me = this, - owner = me.owner, - opts = me.options, - file = block.file, - tr = new Transport( opts ), - data = $.extend({}, opts.formData ), - headers = $.extend({}, opts.headers ), - requestAccept, ret; - - block.transport = tr; - - tr.on( 'destroy', function() { - delete block.transport; - me._popBlock( block ); - Base.nextTick( me.__tick ); - }); - - // 广播上传进度。以文件为单位。 - tr.on( 'progress', function( percentage ) { - block.percentage = percentage; - me.updateFileProgress( file ); - }); - - // 用来询问,是否返回的结果是有错误的。 - requestAccept = function( reject ) { - var fn; - - ret = tr.getResponseAsJson() || {}; - ret._raw = tr.getResponse(); - fn = function( value ) { - reject = value; - }; - - // 服务端响应了,不代表成功了,询问是否响应正确。 - if ( !owner.trigger( 'uploadAccept', block, ret, fn ) ) { - reject = reject || 'server'; - } - - return reject; - }; - - // 尝试重试,然后广播文件上传出错。 - tr.on( 'error', function( type, flag ) { - block.retried = block.retried || 0; - - // 自动重试 - if ( block.chunks > 1 && ~'http,abort'.indexOf( type ) && - block.retried < opts.chunkRetry ) { - - block.retried++; - tr.send(); - - } else { - - // http status 500 ~ 600 - if ( !flag && type === 'server' ) { - type = requestAccept( type ); - } - - file.setStatus( Status.ERROR, type ); - owner.trigger( 'uploadError', file, type ); - owner.trigger( 'uploadComplete', file ); - } - }); - - // 上传成功 - tr.on( 'load', function() { - var reason; - - // 如果非预期,转向上传出错。 - if ( (reason = requestAccept()) ) { - tr.trigger( 'error', reason, true ); - return; - } - - // 全部上传完成。 - if ( file.remaning === 1 ) { - me._finishFile( file, ret ); - } else { - tr.destroy(); - } - }); - - // 配置默认的上传字段。 - data = $.extend( data, { - id: file.id, - name: file.name, - type: file.type, - lastModifiedDate: file.lastModifiedDate, - size: file.size - }); - - block.chunks > 1 && $.extend( data, { - chunks: block.chunks, - chunk: block.chunk - }); - - // 在发送之间可以添加字段什么的。。。 - // 如果默认的字段不够使用,可以通过监听此事件来扩展 - owner.trigger( 'uploadBeforeSend', block, data, headers ); - - // 开始发送。 - tr.appendBlob( opts.fileVal, block.blob, file.name ); - tr.append( data ); - tr.setRequestHeader( headers ); - tr.send(); - }, - - // 完成上传。 - _finishFile: function( file, ret, hds ) { - var owner = this.owner; - - return owner - .request( 'after-send-file', arguments, function() { - file.setStatus( Status.COMPLETE ); - owner.trigger( 'uploadSuccess', file, ret, hds ); - }) - .fail(function( reason ) { - - // 如果外部已经标记为invalid什么的,不再改状态。 - if ( file.getStatus() === Status.PROGRESS ) { - file.setStatus( Status.ERROR, reason ); - } - - owner.trigger( 'uploadError', file, reason ); - }) - .always(function() { - owner.trigger( 'uploadComplete', file ); - }); - }, - - updateFileProgress: function(file) { - var totalPercent = 0, - uploaded = 0; - - if (!file.blocks) { - return; - } - - $.each( file.blocks, function( _, v ) { - uploaded += (v.percentage || 0) * (v.end - v.start); - }); - - totalPercent = uploaded / file.size; - this.owner.trigger( 'uploadProgress', file, totalPercent || 0 ); - } - - }); - }); - /** - * @fileOverview 各种验证,包括文件总大小是否超出、单文件是否超出和文件是否重复。 - */ - - define('widgets/validator',[ - 'base', - 'uploader', - 'file', - 'widgets/widget' - ], function( Base, Uploader, WUFile ) { - - var $ = Base.$, - validators = {}, - api; - - /** - * @event error - * @param {String} type 错误类型。 - * @description 当validate不通过时,会以派送错误事件的形式通知调用者。通过`upload.on('error', handler)`可以捕获到此类错误,目前有以下错误会在特定的情况下派送错来。 - * - * * `Q_EXCEED_NUM_LIMIT` 在设置了`fileNumLimit`且尝试给`uploader`添加的文件数量超出这个值时派送。 - * * `Q_EXCEED_SIZE_LIMIT` 在设置了`Q_EXCEED_SIZE_LIMIT`且尝试给`uploader`添加的文件总大小超出这个值时派送。 - * * `Q_TYPE_DENIED` 当文件类型不满足时触发。。 - * @for Uploader - */ - - // 暴露给外面的api - api = { - - // 添加验证器 - addValidator: function( type, cb ) { - validators[ type ] = cb; - }, - - // 移除验证器 - removeValidator: function( type ) { - delete validators[ type ]; - } - }; - - // 在Uploader初始化的时候启动Validators的初始化 - Uploader.register({ - name: 'validator', - - init: function() { - var me = this; - Base.nextTick(function() { - $.each( validators, function() { - this.call( me.owner ); - }); - }); - } - }); - - /** - * @property {int} [fileNumLimit=undefined] - * @namespace options - * @for Uploader - * @description 验证文件总数量, 超出则不允许加入队列。 - */ - api.addValidator( 'fileNumLimit', function() { - var uploader = this, - opts = uploader.options, - count = 0, - max = parseInt( opts.fileNumLimit, 10 ), - flag = true; - - if ( !max ) { - return; - } - - uploader.on( 'beforeFileQueued', function( file ) { - - if ( count >= max && flag ) { - flag = false; - this.trigger( 'error', 'Q_EXCEED_NUM_LIMIT', max, file ); - setTimeout(function() { - flag = true; - }, 1 ); - } - - return count >= max ? false : true; - }); - - uploader.on( 'fileQueued', function() { - count++; - }); - - uploader.on( 'fileDequeued', function() { - count--; - }); - - uploader.on( 'reset', function() { - count = 0; - }); - }); - - - /** - * @property {int} [fileSizeLimit=undefined] - * @namespace options - * @for Uploader - * @description 验证文件总大小是否超出限制, 超出则不允许加入队列。 - */ - api.addValidator( 'fileSizeLimit', function() { - var uploader = this, - opts = uploader.options, - count = 0, - max = parseInt( opts.fileSizeLimit, 10 ), - flag = true; - - if ( !max ) { - return; - } - - uploader.on( 'beforeFileQueued', function( file ) { - var invalid = count + file.size > max; - - if ( invalid && flag ) { - flag = false; - this.trigger( 'error', 'Q_EXCEED_SIZE_LIMIT', max, file ); - setTimeout(function() { - flag = true; - }, 1 ); - } - - return invalid ? false : true; - }); - - uploader.on( 'fileQueued', function( file ) { - count += file.size; - }); - - uploader.on( 'fileDequeued', function( file ) { - count -= file.size; - }); - - uploader.on( 'reset', function() { - count = 0; - }); - }); - - /** - * @property {int} [fileSingleSizeLimit=undefined] - * @namespace options - * @for Uploader - * @description 验证单个文件大小是否超出限制, 超出则不允许加入队列。 - */ - api.addValidator( 'fileSingleSizeLimit', function() { - var uploader = this, - opts = uploader.options, - max = opts.fileSingleSizeLimit; - - if ( !max ) { - return; - } - - uploader.on( 'beforeFileQueued', function( file ) { - - if ( file.size > max ) { - file.setStatus( WUFile.Status.INVALID, 'exceed_size' ); - this.trigger( 'error', 'F_EXCEED_SIZE', max, file ); - return false; - } - - }); - - }); - - /** - * @property {Boolean} [duplicate=undefined] - * @namespace options - * @for Uploader - * @description 去重, 根据文件名字、文件大小和最后修改时间来生成hash Key. - */ - api.addValidator( 'duplicate', function() { - var uploader = this, - opts = uploader.options, - mapping = {}; - - if ( opts.duplicate ) { - return; - } - - function hashString( str ) { - var hash = 0, - i = 0, - len = str.length, - _char; - - for ( ; i < len; i++ ) { - _char = str.charCodeAt( i ); - hash = _char + (hash << 6) + (hash << 16) - hash; - } - - return hash; - } - - uploader.on( 'beforeFileQueued', function( file ) { - var hash = file.__hash || (file.__hash = hashString( file.name + - file.size + file.lastModifiedDate )); - - // 已经重复了 - if ( mapping[ hash ] ) { - this.trigger( 'error', 'F_DUPLICATE', file ); - return false; - } - }); - - uploader.on( 'fileQueued', function( file ) { - var hash = file.__hash; - - hash && (mapping[ hash ] = true); - }); - - uploader.on( 'fileDequeued', function( file ) { - var hash = file.__hash; - - hash && (delete mapping[ hash ]); - }); - - uploader.on( 'reset', function() { - mapping = {}; - }); - }); - - return api; - }); - - /** - * @fileOverview Md5 - */ - define('lib/md5',[ - 'runtime/client', - 'mediator' - ], function( RuntimeClient, Mediator ) { - - function Md5() { - RuntimeClient.call( this, 'Md5' ); - } - - // 让 Md5 具备事件功能。 - Mediator.installTo( Md5.prototype ); - - Md5.prototype.loadFromBlob = function( blob ) { - var me = this; - - if ( me.getRuid() ) { - me.disconnectRuntime(); - } - - // 连接到blob归属的同一个runtime. - me.connectRuntime( blob.ruid, function() { - me.exec('init'); - me.exec( 'loadFromBlob', blob ); - }); - }; - - Md5.prototype.getResult = function() { - return this.exec('getResult'); - }; - - return Md5; - }); - /** - * @fileOverview 图片操作, 负责预览图片和上传前压缩图片 - */ - define('widgets/md5',[ - 'base', - 'uploader', - 'lib/md5', - 'lib/blob', - 'widgets/widget' - ], function( Base, Uploader, Md5, Blob ) { - - return Uploader.register({ - name: 'md5', - - - /** - * 计算文件 md5 值,返回一个 promise 对象,可以监听 progress 进度。 - * - * - * @method md5File - * @grammar md5File( file[, start[, end]] ) => promise - * @for Uploader - * @example - * - * uploader.on( 'fileQueued', function( file ) { - * var $li = ...; - * - * uploader.md5File( file ) - * - * // 及时显示进度 - * .progress(function(percentage) { - * console.log('Percentage:', percentage); - * }) - * - * // 完成 - * .then(function(val) { - * console.log('md5 result:', val); - * }); - * - * }); - */ - md5File: function( file, start, end ) { - var md5 = new Md5(), - deferred = Base.Deferred(), - blob = (file instanceof Blob) ? file : - this.request( 'get-file', file ).source; - - md5.on( 'progress load', function( e ) { - e = e || {}; - deferred.notify( e.total ? e.loaded / e.total : 1 ); - }); - - md5.on( 'complete', function() { - deferred.resolve( md5.getResult() ); - }); - - md5.on( 'error', function( reason ) { - deferred.reject( reason ); - }); - - if ( arguments.length > 1 ) { - start = start || 0; - end = end || 0; - start < 0 && (start = blob.size + start); - end < 0 && (end = blob.size + end); - end = Math.min( end, blob.size ); - blob = blob.slice( start, end ); - } - - md5.loadFromBlob( blob ); - - return deferred.promise(); - } - }); - }); - /** - * @fileOverview Runtime管理器,负责Runtime的选择, 连接 - */ - define('runtime/compbase',[],function() { - - function CompBase( owner, runtime ) { - - this.owner = owner; - this.options = owner.options; - - this.getRuntime = function() { - return runtime; - }; - - this.getRuid = function() { - return runtime.uid; - }; - - this.trigger = function() { - return owner.trigger.apply( owner, arguments ); - }; - } - - return CompBase; - }); - /** - * @fileOverview Html5Runtime - */ - define('runtime/html5/runtime',[ - 'base', - 'runtime/runtime', - 'runtime/compbase' - ], function( Base, Runtime, CompBase ) { - - var type = 'html5', - components = {}; - - function Html5Runtime() { - var pool = {}, - me = this, - destroy = this.destroy; - - Runtime.apply( me, arguments ); - me.type = type; - - - // 这个方法的调用者,实际上是RuntimeClient - me.exec = function( comp, fn/*, args...*/) { - var client = this, - uid = client.uid, - args = Base.slice( arguments, 2 ), - instance; - - if ( components[ comp ] ) { - instance = pool[ uid ] = pool[ uid ] || - new components[ comp ]( client, me ); - - if ( instance[ fn ] ) { - return instance[ fn ].apply( instance, args ); - } - } - }; - - me.destroy = function() { - // @todo 删除池子中的所有实例 - return destroy && destroy.apply( this, arguments ); - }; - } - - Base.inherits( Runtime, { - constructor: Html5Runtime, - - // 不需要连接其他程序,直接执行callback - init: function() { - var me = this; - setTimeout(function() { - me.trigger('ready'); - }, 1 ); - } - - }); - - // 注册Components - Html5Runtime.register = function( name, component ) { - var klass = components[ name ] = Base.inherits( CompBase, component ); - return klass; - }; - - // 注册html5运行时。 - // 只有在支持的前提下注册。 - if ( window.Blob && window.FileReader && window.DataView ) { - Runtime.addRuntime( type, Html5Runtime ); - } - - return Html5Runtime; - }); - /** - * @fileOverview Blob Html实现 - */ - define('runtime/html5/blob',[ - 'runtime/html5/runtime', - 'lib/blob' - ], function( Html5Runtime, Blob ) { - - return Html5Runtime.register( 'Blob', { - slice: function( start, end ) { - var blob = this.owner.source, - slice = blob.slice || blob.webkitSlice || blob.mozSlice; - - blob = slice.call( blob, start, end ); - - return new Blob( this.getRuid(), blob ); - } - }); - }); - /** - * @fileOverview FilePaste - */ - define('runtime/html5/dnd',[ - 'base', - 'runtime/html5/runtime', - 'lib/file' - ], function( Base, Html5Runtime, File ) { - - var $ = Base.$, - prefix = 'webuploader-dnd-'; - - return Html5Runtime.register( 'DragAndDrop', { - init: function() { - var elem = this.elem = this.options.container; - - this.dragEnterHandler = Base.bindFn( this._dragEnterHandler, this ); - this.dragOverHandler = Base.bindFn( this._dragOverHandler, this ); - this.dragLeaveHandler = Base.bindFn( this._dragLeaveHandler, this ); - this.dropHandler = Base.bindFn( this._dropHandler, this ); - this.dndOver = false; - - elem.on( 'dragenter', this.dragEnterHandler ); - elem.on( 'dragover', this.dragOverHandler ); - elem.on( 'dragleave', this.dragLeaveHandler ); - elem.on( 'drop', this.dropHandler ); - - if ( this.options.disableGlobalDnd ) { - $( document ).on( 'dragover', this.dragOverHandler ); - $( document ).on( 'drop', this.dropHandler ); - } - }, - - _dragEnterHandler: function( e ) { - var me = this, - denied = me._denied || false, - items; - - e = e.originalEvent || e; - - if ( !me.dndOver ) { - me.dndOver = true; - - // 注意只有 chrome 支持。 - items = e.dataTransfer.items; - - if ( items && items.length ) { - me._denied = denied = !me.trigger( 'accept', items ); - } - - me.elem.addClass( prefix + 'over' ); - me.elem[ denied ? 'addClass' : - 'removeClass' ]( prefix + 'denied' ); - } - - e.dataTransfer.dropEffect = denied ? 'none' : 'copy'; - - return false; - }, - - _dragOverHandler: function( e ) { - // 只处理框内的。 - var parentElem = this.elem.parent().get( 0 ); - if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) { - return false; - } - - clearTimeout( this._leaveTimer ); - this._dragEnterHandler.call( this, e ); - - return false; - }, - - _dragLeaveHandler: function() { - var me = this, - handler; - - handler = function() { - me.dndOver = false; - me.elem.removeClass( prefix + 'over ' + prefix + 'denied' ); - }; - - clearTimeout( me._leaveTimer ); - me._leaveTimer = setTimeout( handler, 100 ); - return false; - }, - - _dropHandler: function( e ) { - var me = this, - ruid = me.getRuid(), - parentElem = me.elem.parent().get( 0 ), - dataTransfer, data; - - // 只处理框内的。 - if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) { - return false; - } - - e = e.originalEvent || e; - dataTransfer = e.dataTransfer; - - // 如果是页面内拖拽,还不能处理,不阻止事件。 - // 此处 ie11 下会报参数错误, - try { - data = dataTransfer.getData('text/html'); - } catch( err ) { - } - - if ( data ) { - return; - } - - me._getTansferFiles( dataTransfer, function( results ) { - me.trigger( 'drop', $.map( results, function( file ) { - return new File( ruid, file ); - }) ); - }); - - me.dndOver = false; - me.elem.removeClass( prefix + 'over' ); - return false; - }, - - // 如果传入 callback 则去查看文件夹,否则只管当前文件夹。 - _getTansferFiles: function( dataTransfer, callback ) { - var results = [], - promises = [], - items, files, file, item, i, len, canAccessFolder; - - items = dataTransfer.items; - files = dataTransfer.files; - - canAccessFolder = !!(items && items[ 0 ].webkitGetAsEntry); - - for ( i = 0, len = files.length; i < len; i++ ) { - file = files[ i ]; - item = items && items[ i ]; - - if ( canAccessFolder && item.webkitGetAsEntry().isDirectory ) { - - promises.push( this._traverseDirectoryTree( - item.webkitGetAsEntry(), results ) ); - } else { - results.push( file ); - } - } - - Base.when.apply( Base, promises ).done(function() { - - if ( !results.length ) { - return; - } - - callback( results ); - }); - }, - - _traverseDirectoryTree: function( entry, results ) { - var deferred = Base.Deferred(), - me = this; - - if ( entry.isFile ) { - entry.file(function( file ) { - results.push( file ); - deferred.resolve(); - }); - } else if ( entry.isDirectory ) { - entry.createReader().readEntries(function( entries ) { - var len = entries.length, - promises = [], - arr = [], // 为了保证顺序。 - i; - - for ( i = 0; i < len; i++ ) { - promises.push( me._traverseDirectoryTree( - entries[ i ], arr ) ); - } - - Base.when.apply( Base, promises ).then(function() { - results.push.apply( results, arr ); - deferred.resolve(); - }, deferred.reject ); - }); - } - - return deferred.promise(); - }, - - destroy: function() { - var elem = this.elem; - - // 还没 init 就调用 destroy - if (!elem) { - return; - } - - elem.off( 'dragenter', this.dragEnterHandler ); - elem.off( 'dragover', this.dragOverHandler ); - elem.off( 'dragleave', this.dragLeaveHandler ); - elem.off( 'drop', this.dropHandler ); - - if ( this.options.disableGlobalDnd ) { - $( document ).off( 'dragover', this.dragOverHandler ); - $( document ).off( 'drop', this.dropHandler ); - } - } - }); - }); - - /** - * @fileOverview FilePaste - */ - define('runtime/html5/filepaste',[ - 'base', - 'runtime/html5/runtime', - 'lib/file' - ], function( Base, Html5Runtime, File ) { - - return Html5Runtime.register( 'FilePaste', { - init: function() { - var opts = this.options, - elem = this.elem = opts.container, - accept = '.*', - arr, i, len, item; - - // accetp的mimeTypes中生成匹配正则。 - if ( opts.accept ) { - arr = []; - - for ( i = 0, len = opts.accept.length; i < len; i++ ) { - item = opts.accept[ i ].mimeTypes; - item && arr.push( item ); - } - - if ( arr.length ) { - accept = arr.join(','); - accept = accept.replace( /,/g, '|' ).replace( /\*/g, '.*' ); - } - } - this.accept = accept = new RegExp( accept, 'i' ); - this.hander = Base.bindFn( this._pasteHander, this ); - elem.on( 'paste', this.hander ); - }, - - _pasteHander: function( e ) { - var allowed = [], - ruid = this.getRuid(), - items, item, blob, i, len; - - e = e.originalEvent || e; - items = e.clipboardData.items; - - for ( i = 0, len = items.length; i < len; i++ ) { - item = items[ i ]; - - if ( item.kind !== 'file' || !(blob = item.getAsFile()) ) { - continue; - } - - allowed.push( new File( ruid, blob ) ); - } - - if ( allowed.length ) { - // 不阻止非文件粘贴(文字粘贴)的事件冒泡 - e.preventDefault(); - e.stopPropagation(); - this.trigger( 'paste', allowed ); - } - }, - - destroy: function() { - this.elem.off( 'paste', this.hander ); - } - }); - }); - - /** - * @fileOverview FilePicker - */ - define('runtime/html5/filepicker',[ - 'base', - 'runtime/html5/runtime' - ], function( Base, Html5Runtime ) { - - var $ = Base.$; - - return Html5Runtime.register( 'FilePicker', { - init: function() { - var container = this.getRuntime().getContainer(), - me = this, - owner = me.owner, - opts = me.options, - label = this.label = $( document.createElement('label') ), - input = this.input = $( document.createElement('input') ), - arr, i, len, mouseHandler; - - input.attr( 'type', 'file' ); - input.attr( 'name', opts.name ); - input.addClass('webuploader-element-invisible'); - - label.on( 'click', function() { - input.trigger('click'); - }); - - label.css({ - opacity: 0, - width: '100%', - height: '100%', - display: 'block', - cursor: 'pointer', - background: '#ffffff' - }); - - if ( opts.multiple ) { - input.attr( 'multiple', 'multiple' ); - } - - // @todo Firefox不支持单独指定后缀 - if ( opts.accept && opts.accept.length > 0 ) { - arr = []; - - for ( i = 0, len = opts.accept.length; i < len; i++ ) { - arr.push( opts.accept[ i ].mimeTypes ); - } - - input.attr( 'accept', arr.join(',') ); - } - - container.append( input ); - container.append( label ); - - mouseHandler = function( e ) { - owner.trigger( e.type ); - }; - - input.on( 'change', function( e ) { - var fn = arguments.callee, - clone; - - me.files = e.target.files; - - // reset input - clone = this.cloneNode( true ); - clone.value = null; - this.parentNode.replaceChild( clone, this ); - - input.off(); - input = $( clone ).on( 'change', fn ) - .on( 'mouseenter mouseleave', mouseHandler ); - - owner.trigger('change'); - }); - - label.on( 'mouseenter mouseleave', mouseHandler ); - - }, - - - getFiles: function() { - return this.files; - }, - - destroy: function() { - this.input.off(); - this.label.off(); - } - }); - }); - /** - * Terms: - * - * Uint8Array, FileReader, BlobBuilder, atob, ArrayBuffer - * @fileOverview Image控件 - */ - define('runtime/html5/util',[ - 'base' - ], function( Base ) { - - var urlAPI = window.createObjectURL && window || - window.URL && URL.revokeObjectURL && URL || - window.webkitURL, - createObjectURL = Base.noop, - revokeObjectURL = createObjectURL; - - if ( urlAPI ) { - - // 更安全的方式调用,比如android里面就能把context改成其他的对象。 - createObjectURL = function() { - return urlAPI.createObjectURL.apply( urlAPI, arguments ); - }; - - revokeObjectURL = function() { - return urlAPI.revokeObjectURL.apply( urlAPI, arguments ); - }; - } - - return { - createObjectURL: createObjectURL, - revokeObjectURL: revokeObjectURL, - - dataURL2Blob: function( dataURI ) { - var byteStr, intArray, ab, i, mimetype, parts; - - parts = dataURI.split(','); - - if ( ~parts[ 0 ].indexOf('base64') ) { - byteStr = atob( parts[ 1 ] ); - } else { - byteStr = decodeURIComponent( parts[ 1 ] ); - } - - ab = new ArrayBuffer( byteStr.length ); - intArray = new Uint8Array( ab ); - - for ( i = 0; i < byteStr.length; i++ ) { - intArray[ i ] = byteStr.charCodeAt( i ); - } - - mimetype = parts[ 0 ].split(':')[ 1 ].split(';')[ 0 ]; - - return this.arrayBufferToBlob( ab, mimetype ); - }, - - dataURL2ArrayBuffer: function( dataURI ) { - var byteStr, intArray, i, parts; - - parts = dataURI.split(','); - - if ( ~parts[ 0 ].indexOf('base64') ) { - byteStr = atob( parts[ 1 ] ); - } else { - byteStr = decodeURIComponent( parts[ 1 ] ); - } - - intArray = new Uint8Array( byteStr.length ); - - for ( i = 0; i < byteStr.length; i++ ) { - intArray[ i ] = byteStr.charCodeAt( i ); - } - - return intArray.buffer; - }, - - arrayBufferToBlob: function( buffer, type ) { - var builder = window.BlobBuilder || window.WebKitBlobBuilder, - bb; - - // android不支持直接new Blob, 只能借助blobbuilder. - if ( builder ) { - bb = new builder(); - bb.append( buffer ); - return bb.getBlob( type ); - } - - return new Blob([ buffer ], type ? { type: type } : {} ); - }, - - // 抽出来主要是为了解决android下面canvas.toDataUrl不支持jpeg. - // 你得到的结果是png. - canvasToDataUrl: function( canvas, type, quality ) { - return canvas.toDataURL( type, quality / 100 ); - }, - - // imagemeat会复写这个方法,如果用户选择加载那个文件了的话。 - parseMeta: function( blob, callback ) { - callback( false, {}); - }, - - // imagemeat会复写这个方法,如果用户选择加载那个文件了的话。 - updateImageHead: function( data ) { - return data; - } - }; - }); - /** - * Terms: - * - * Uint8Array, FileReader, BlobBuilder, atob, ArrayBuffer - * @fileOverview Image控件 - */ - define('runtime/html5/imagemeta',[ - 'runtime/html5/util' - ], function( Util ) { - - var api; - - api = { - parsers: { - 0xffe1: [] - }, - - maxMetaDataSize: 262144, - - parse: function( blob, cb ) { - var me = this, - fr = new FileReader(); - - fr.onload = function() { - cb( false, me._parse( this.result ) ); - fr = fr.onload = fr.onerror = null; - }; - - fr.onerror = function( e ) { - cb( e.message ); - fr = fr.onload = fr.onerror = null; - }; - - blob = blob.slice( 0, me.maxMetaDataSize ); - fr.readAsArrayBuffer( blob.getSource() ); - }, - - _parse: function( buffer, noParse ) { - if ( buffer.byteLength < 6 ) { - return; - } - - var dataview = new DataView( buffer ), - offset = 2, - maxOffset = dataview.byteLength - 4, - headLength = offset, - ret = {}, - markerBytes, markerLength, parsers, i; - - if ( dataview.getUint16( 0 ) === 0xffd8 ) { - - while ( offset < maxOffset ) { - markerBytes = dataview.getUint16( offset ); - - if ( markerBytes >= 0xffe0 && markerBytes <= 0xffef || - markerBytes === 0xfffe ) { - - markerLength = dataview.getUint16( offset + 2 ) + 2; - - if ( offset + markerLength > dataview.byteLength ) { - break; - } - - parsers = api.parsers[ markerBytes ]; - - if ( !noParse && parsers ) { - for ( i = 0; i < parsers.length; i += 1 ) { - parsers[ i ].call( api, dataview, offset, - markerLength, ret ); - } - } - - offset += markerLength; - headLength = offset; - } else { - break; - } - } - - if ( headLength > 6 ) { - if ( buffer.slice ) { - ret.imageHead = buffer.slice( 2, headLength ); - } else { - // Workaround for IE10, which does not yet - // support ArrayBuffer.slice: - ret.imageHead = new Uint8Array( buffer ) - .subarray( 2, headLength ); - } - } - } - - return ret; - }, - - updateImageHead: function( buffer, head ) { - var data = this._parse( buffer, true ), - buf1, buf2, bodyoffset; - - - bodyoffset = 2; - if ( data.imageHead ) { - bodyoffset = 2 + data.imageHead.byteLength; - } - - if ( buffer.slice ) { - buf2 = buffer.slice( bodyoffset ); - } else { - buf2 = new Uint8Array( buffer ).subarray( bodyoffset ); - } - - buf1 = new Uint8Array( head.byteLength + 2 + buf2.byteLength ); - - buf1[ 0 ] = 0xFF; - buf1[ 1 ] = 0xD8; - buf1.set( new Uint8Array( head ), 2 ); - buf1.set( new Uint8Array( buf2 ), head.byteLength + 2 ); - - return buf1.buffer; - } - }; - - Util.parseMeta = function() { - return api.parse.apply( api, arguments ); - }; - - Util.updateImageHead = function() { - return api.updateImageHead.apply( api, arguments ); - }; - - return api; - }); - /** - * 代码来自于:https://github.com/blueimp/JavaScript-Load-Image - * 暂时项目中只用了orientation. - * - * 去除了 Exif Sub IFD Pointer, GPS Info IFD Pointer, Exif Thumbnail. - * @fileOverview EXIF解析 - */ - - // Sample - // ==================================== - // Make : Apple - // Model : iPhone 4S - // Orientation : 1 - // XResolution : 72 [72/1] - // YResolution : 72 [72/1] - // ResolutionUnit : 2 - // Software : QuickTime 7.7.1 - // DateTime : 2013:09:01 22:53:55 - // ExifIFDPointer : 190 - // ExposureTime : 0.058823529411764705 [1/17] - // FNumber : 2.4 [12/5] - // ExposureProgram : Normal program - // ISOSpeedRatings : 800 - // ExifVersion : 0220 - // DateTimeOriginal : 2013:09:01 22:52:51 - // DateTimeDigitized : 2013:09:01 22:52:51 - // ComponentsConfiguration : YCbCr - // ShutterSpeedValue : 4.058893515764426 - // ApertureValue : 2.5260688216892597 [4845/1918] - // BrightnessValue : -0.3126686601998395 - // MeteringMode : Pattern - // Flash : Flash did not fire, compulsory flash mode - // FocalLength : 4.28 [107/25] - // SubjectArea : [4 values] - // FlashpixVersion : 0100 - // ColorSpace : 1 - // PixelXDimension : 2448 - // PixelYDimension : 3264 - // SensingMethod : One-chip color area sensor - // ExposureMode : 0 - // WhiteBalance : Auto white balance - // FocalLengthIn35mmFilm : 35 - // SceneCaptureType : Standard - define('runtime/html5/imagemeta/exif',[ - 'base', - 'runtime/html5/imagemeta' - ], function( Base, ImageMeta ) { - - var EXIF = {}; - - EXIF.ExifMap = function() { - return this; - }; - - EXIF.ExifMap.prototype.map = { - 'Orientation': 0x0112 - }; - - EXIF.ExifMap.prototype.get = function( id ) { - return this[ id ] || this[ this.map[ id ] ]; - }; - - EXIF.exifTagTypes = { - // byte, 8-bit unsigned int: - 1: { - getValue: function( dataView, dataOffset ) { - return dataView.getUint8( dataOffset ); - }, - size: 1 - }, - - // ascii, 8-bit byte: - 2: { - getValue: function( dataView, dataOffset ) { - return String.fromCharCode( dataView.getUint8( dataOffset ) ); - }, - size: 1, - ascii: true - }, - - // short, 16 bit int: - 3: { - getValue: function( dataView, dataOffset, littleEndian ) { - return dataView.getUint16( dataOffset, littleEndian ); - }, - size: 2 - }, - - // long, 32 bit int: - 4: { - getValue: function( dataView, dataOffset, littleEndian ) { - return dataView.getUint32( dataOffset, littleEndian ); - }, - size: 4 - }, - - // rational = two long values, - // first is numerator, second is denominator: - 5: { - getValue: function( dataView, dataOffset, littleEndian ) { - return dataView.getUint32( dataOffset, littleEndian ) / - dataView.getUint32( dataOffset + 4, littleEndian ); - }, - size: 8 - }, - - // slong, 32 bit signed int: - 9: { - getValue: function( dataView, dataOffset, littleEndian ) { - return dataView.getInt32( dataOffset, littleEndian ); - }, - size: 4 - }, - - // srational, two slongs, first is numerator, second is denominator: - 10: { - getValue: function( dataView, dataOffset, littleEndian ) { - return dataView.getInt32( dataOffset, littleEndian ) / - dataView.getInt32( dataOffset + 4, littleEndian ); - }, - size: 8 - } - }; - - // undefined, 8-bit byte, value depending on field: - EXIF.exifTagTypes[ 7 ] = EXIF.exifTagTypes[ 1 ]; - - EXIF.getExifValue = function( dataView, tiffOffset, offset, type, length, - littleEndian ) { - - var tagType = EXIF.exifTagTypes[ type ], - tagSize, dataOffset, values, i, str, c; - - if ( !tagType ) { - Base.log('Invalid Exif data: Invalid tag type.'); - return; - } - - tagSize = tagType.size * length; - - // Determine if the value is contained in the dataOffset bytes, - // or if the value at the dataOffset is a pointer to the actual data: - dataOffset = tagSize > 4 ? tiffOffset + dataView.getUint32( offset + 8, - littleEndian ) : (offset + 8); - - if ( dataOffset + tagSize > dataView.byteLength ) { - Base.log('Invalid Exif data: Invalid data offset.'); - return; - } - - if ( length === 1 ) { - return tagType.getValue( dataView, dataOffset, littleEndian ); - } - - values = []; - - for ( i = 0; i < length; i += 1 ) { - values[ i ] = tagType.getValue( dataView, - dataOffset + i * tagType.size, littleEndian ); - } - - if ( tagType.ascii ) { - str = ''; - - // Concatenate the chars: - for ( i = 0; i < values.length; i += 1 ) { - c = values[ i ]; - - // Ignore the terminating NULL byte(s): - if ( c === '\u0000' ) { - break; - } - str += c; - } - - return str; - } - return values; - }; - - EXIF.parseExifTag = function( dataView, tiffOffset, offset, littleEndian, - data ) { - - var tag = dataView.getUint16( offset, littleEndian ); - data.exif[ tag ] = EXIF.getExifValue( dataView, tiffOffset, offset, - dataView.getUint16( offset + 2, littleEndian ), // tag type - dataView.getUint32( offset + 4, littleEndian ), // tag length - littleEndian ); - }; - - EXIF.parseExifTags = function( dataView, tiffOffset, dirOffset, - littleEndian, data ) { - - var tagsNumber, dirEndOffset, i; - - if ( dirOffset + 6 > dataView.byteLength ) { - Base.log('Invalid Exif data: Invalid directory offset.'); - return; - } - - tagsNumber = dataView.getUint16( dirOffset, littleEndian ); - dirEndOffset = dirOffset + 2 + 12 * tagsNumber; - - if ( dirEndOffset + 4 > dataView.byteLength ) { - Base.log('Invalid Exif data: Invalid directory size.'); - return; - } - - for ( i = 0; i < tagsNumber; i += 1 ) { - this.parseExifTag( dataView, tiffOffset, - dirOffset + 2 + 12 * i, // tag offset - littleEndian, data ); - } - - // Return the offset to the next directory: - return dataView.getUint32( dirEndOffset, littleEndian ); - }; - - // EXIF.getExifThumbnail = function(dataView, offset, length) { - // var hexData, - // i, - // b; - // if (!length || offset + length > dataView.byteLength) { - // Base.log('Invalid Exif data: Invalid thumbnail data.'); - // return; - // } - // hexData = []; - // for (i = 0; i < length; i += 1) { - // b = dataView.getUint8(offset + i); - // hexData.push((b < 16 ? '0' : '') + b.toString(16)); - // } - // return 'data:image/jpeg,%' + hexData.join('%'); - // }; - - EXIF.parseExifData = function( dataView, offset, length, data ) { - - var tiffOffset = offset + 10, - littleEndian, dirOffset; - - // Check for the ASCII code for "Exif" (0x45786966): - if ( dataView.getUint32( offset + 4 ) !== 0x45786966 ) { - // No Exif data, might be XMP data instead - return; - } - if ( tiffOffset + 8 > dataView.byteLength ) { - Base.log('Invalid Exif data: Invalid segment size.'); - return; - } - - // Check for the two null bytes: - if ( dataView.getUint16( offset + 8 ) !== 0x0000 ) { - Base.log('Invalid Exif data: Missing byte alignment offset.'); - return; - } - - // Check the byte alignment: - switch ( dataView.getUint16( tiffOffset ) ) { - case 0x4949: - littleEndian = true; - break; - - case 0x4D4D: - littleEndian = false; - break; - - default: - Base.log('Invalid Exif data: Invalid byte alignment marker.'); - return; - } - - // Check for the TIFF tag marker (0x002A): - if ( dataView.getUint16( tiffOffset + 2, littleEndian ) !== 0x002A ) { - Base.log('Invalid Exif data: Missing TIFF marker.'); - return; - } - - // Retrieve the directory offset bytes, usually 0x00000008 or 8 decimal: - dirOffset = dataView.getUint32( tiffOffset + 4, littleEndian ); - // Create the exif object to store the tags: - data.exif = new EXIF.ExifMap(); - // Parse the tags of the main image directory and retrieve the - // offset to the next directory, usually the thumbnail directory: - dirOffset = EXIF.parseExifTags( dataView, tiffOffset, - tiffOffset + dirOffset, littleEndian, data ); - - // 尝试读取缩略图 - // if ( dirOffset ) { - // thumbnailData = {exif: {}}; - // dirOffset = EXIF.parseExifTags( - // dataView, - // tiffOffset, - // tiffOffset + dirOffset, - // littleEndian, - // thumbnailData - // ); - - // // Check for JPEG Thumbnail offset: - // if (thumbnailData.exif[0x0201]) { - // data.exif.Thumbnail = EXIF.getExifThumbnail( - // dataView, - // tiffOffset + thumbnailData.exif[0x0201], - // thumbnailData.exif[0x0202] // Thumbnail data length - // ); - // } - // } - }; - - ImageMeta.parsers[ 0xffe1 ].push( EXIF.parseExifData ); - return EXIF; - }); - /** - * 这个方式性能不行,但是可以解决android里面的toDataUrl的bug - * android里面toDataUrl('image/jpege')得到的结果却是png. - * - * 所以这里没辙,只能借助这个工具 - * @fileOverview jpeg encoder - */ - define('runtime/html5/jpegencoder',[], function( require, exports, module ) { - - /* - Copyright (c) 2008, Adobe Systems Incorporated - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are - met: - - * Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the name of Adobe Systems Incorporated nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS - IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - /* - JPEG encoder ported to JavaScript and optimized by Andreas Ritter, www.bytestrom.eu, 11/2009 - - Basic GUI blocking jpeg encoder - */ - - function JPEGEncoder(quality) { - var self = this; - var fround = Math.round; - var ffloor = Math.floor; - var YTable = new Array(64); - var UVTable = new Array(64); - var fdtbl_Y = new Array(64); - var fdtbl_UV = new Array(64); - var YDC_HT; - var UVDC_HT; - var YAC_HT; - var UVAC_HT; - - var bitcode = new Array(65535); - var category = new Array(65535); - var outputfDCTQuant = new Array(64); - var DU = new Array(64); - var byteout = []; - var bytenew = 0; - var bytepos = 7; - - var YDU = new Array(64); - var UDU = new Array(64); - var VDU = new Array(64); - var clt = new Array(256); - var RGB_YUV_TABLE = new Array(2048); - var currentQuality; - - var ZigZag = [ - 0, 1, 5, 6,14,15,27,28, - 2, 4, 7,13,16,26,29,42, - 3, 8,12,17,25,30,41,43, - 9,11,18,24,31,40,44,53, - 10,19,23,32,39,45,52,54, - 20,22,33,38,46,51,55,60, - 21,34,37,47,50,56,59,61, - 35,36,48,49,57,58,62,63 - ]; - - var std_dc_luminance_nrcodes = [0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0]; - var std_dc_luminance_values = [0,1,2,3,4,5,6,7,8,9,10,11]; - var std_ac_luminance_nrcodes = [0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d]; - var std_ac_luminance_values = [ - 0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12, - 0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07, - 0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08, - 0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0, - 0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16, - 0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28, - 0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39, - 0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49, - 0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59, - 0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69, - 0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79, - 0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89, - 0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98, - 0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7, - 0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6, - 0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5, - 0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4, - 0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2, - 0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea, - 0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8, - 0xf9,0xfa - ]; - - var std_dc_chrominance_nrcodes = [0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0]; - var std_dc_chrominance_values = [0,1,2,3,4,5,6,7,8,9,10,11]; - var std_ac_chrominance_nrcodes = [0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77]; - var std_ac_chrominance_values = [ - 0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21, - 0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71, - 0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91, - 0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0, - 0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34, - 0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26, - 0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38, - 0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48, - 0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58, - 0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68, - 0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78, - 0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87, - 0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96, - 0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5, - 0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4, - 0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3, - 0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2, - 0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda, - 0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9, - 0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8, - 0xf9,0xfa - ]; - - function initQuantTables(sf){ - var YQT = [ - 16, 11, 10, 16, 24, 40, 51, 61, - 12, 12, 14, 19, 26, 58, 60, 55, - 14, 13, 16, 24, 40, 57, 69, 56, - 14, 17, 22, 29, 51, 87, 80, 62, - 18, 22, 37, 56, 68,109,103, 77, - 24, 35, 55, 64, 81,104,113, 92, - 49, 64, 78, 87,103,121,120,101, - 72, 92, 95, 98,112,100,103, 99 - ]; - - for (var i = 0; i < 64; i++) { - var t = ffloor((YQT[i]*sf+50)/100); - if (t < 1) { - t = 1; - } else if (t > 255) { - t = 255; - } - YTable[ZigZag[i]] = t; - } - var UVQT = [ - 17, 18, 24, 47, 99, 99, 99, 99, - 18, 21, 26, 66, 99, 99, 99, 99, - 24, 26, 56, 99, 99, 99, 99, 99, - 47, 66, 99, 99, 99, 99, 99, 99, - 99, 99, 99, 99, 99, 99, 99, 99, - 99, 99, 99, 99, 99, 99, 99, 99, - 99, 99, 99, 99, 99, 99, 99, 99, - 99, 99, 99, 99, 99, 99, 99, 99 - ]; - for (var j = 0; j < 64; j++) { - var u = ffloor((UVQT[j]*sf+50)/100); - if (u < 1) { - u = 1; - } else if (u > 255) { - u = 255; - } - UVTable[ZigZag[j]] = u; - } - var aasf = [ - 1.0, 1.387039845, 1.306562965, 1.175875602, - 1.0, 0.785694958, 0.541196100, 0.275899379 - ]; - var k = 0; - for (var row = 0; row < 8; row++) - { - for (var col = 0; col < 8; col++) - { - fdtbl_Y[k] = (1.0 / (YTable [ZigZag[k]] * aasf[row] * aasf[col] * 8.0)); - fdtbl_UV[k] = (1.0 / (UVTable[ZigZag[k]] * aasf[row] * aasf[col] * 8.0)); - k++; - } - } - } - - function computeHuffmanTbl(nrcodes, std_table){ - var codevalue = 0; - var pos_in_table = 0; - var HT = new Array(); - for (var k = 1; k <= 16; k++) { - for (var j = 1; j <= nrcodes[k]; j++) { - HT[std_table[pos_in_table]] = []; - HT[std_table[pos_in_table]][0] = codevalue; - HT[std_table[pos_in_table]][1] = k; - pos_in_table++; - codevalue++; - } - codevalue*=2; - } - return HT; - } - - function initHuffmanTbl() - { - YDC_HT = computeHuffmanTbl(std_dc_luminance_nrcodes,std_dc_luminance_values); - UVDC_HT = computeHuffmanTbl(std_dc_chrominance_nrcodes,std_dc_chrominance_values); - YAC_HT = computeHuffmanTbl(std_ac_luminance_nrcodes,std_ac_luminance_values); - UVAC_HT = computeHuffmanTbl(std_ac_chrominance_nrcodes,std_ac_chrominance_values); - } - - function initCategoryNumber() - { - var nrlower = 1; - var nrupper = 2; - for (var cat = 1; cat <= 15; cat++) { - //Positive numbers - for (var nr = nrlower; nr>0] = 38470 * i; - RGB_YUV_TABLE[(i+ 512)>>0] = 7471 * i + 0x8000; - RGB_YUV_TABLE[(i+ 768)>>0] = -11059 * i; - RGB_YUV_TABLE[(i+1024)>>0] = -21709 * i; - RGB_YUV_TABLE[(i+1280)>>0] = 32768 * i + 0x807FFF; - RGB_YUV_TABLE[(i+1536)>>0] = -27439 * i; - RGB_YUV_TABLE[(i+1792)>>0] = - 5329 * i; - } - } - - // IO functions - function writeBits(bs) - { - var value = bs[0]; - var posval = bs[1]-1; - while ( posval >= 0 ) { - if (value & (1 << posval) ) { - bytenew |= (1 << bytepos); - } - posval--; - bytepos--; - if (bytepos < 0) { - if (bytenew == 0xFF) { - writeByte(0xFF); - writeByte(0); - } - else { - writeByte(bytenew); - } - bytepos=7; - bytenew=0; - } - } - } - - function writeByte(value) - { - byteout.push(clt[value]); // write char directly instead of converting later - } - - function writeWord(value) - { - writeByte((value>>8)&0xFF); - writeByte((value )&0xFF); - } - - // DCT & quantization core - function fDCTQuant(data, fdtbl) - { - var d0, d1, d2, d3, d4, d5, d6, d7; - /* Pass 1: process rows. */ - var dataOff=0; - var i; - var I8 = 8; - var I64 = 64; - for (i=0; i 0.0) ? ((fDCTQuant + 0.5)|0) : ((fDCTQuant - 0.5)|0); - //outputfDCTQuant[i] = fround(fDCTQuant); - - } - return outputfDCTQuant; - } - - function writeAPP0() - { - writeWord(0xFFE0); // marker - writeWord(16); // length - writeByte(0x4A); // J - writeByte(0x46); // F - writeByte(0x49); // I - writeByte(0x46); // F - writeByte(0); // = "JFIF",'\0' - writeByte(1); // versionhi - writeByte(1); // versionlo - writeByte(0); // xyunits - writeWord(1); // xdensity - writeWord(1); // ydensity - writeByte(0); // thumbnwidth - writeByte(0); // thumbnheight - } - - function writeSOF0(width, height) - { - writeWord(0xFFC0); // marker - writeWord(17); // length, truecolor YUV JPG - writeByte(8); // precision - writeWord(height); - writeWord(width); - writeByte(3); // nrofcomponents - writeByte(1); // IdY - writeByte(0x11); // HVY - writeByte(0); // QTY - writeByte(2); // IdU - writeByte(0x11); // HVU - writeByte(1); // QTU - writeByte(3); // IdV - writeByte(0x11); // HVV - writeByte(1); // QTV - } - - function writeDQT() - { - writeWord(0xFFDB); // marker - writeWord(132); // length - writeByte(0); - for (var i=0; i<64; i++) { - writeByte(YTable[i]); - } - writeByte(1); - for (var j=0; j<64; j++) { - writeByte(UVTable[j]); - } - } - - function writeDHT() - { - writeWord(0xFFC4); // marker - writeWord(0x01A2); // length - - writeByte(0); // HTYDCinfo - for (var i=0; i<16; i++) { - writeByte(std_dc_luminance_nrcodes[i+1]); - } - for (var j=0; j<=11; j++) { - writeByte(std_dc_luminance_values[j]); - } - - writeByte(0x10); // HTYACinfo - for (var k=0; k<16; k++) { - writeByte(std_ac_luminance_nrcodes[k+1]); - } - for (var l=0; l<=161; l++) { - writeByte(std_ac_luminance_values[l]); - } - - writeByte(1); // HTUDCinfo - for (var m=0; m<16; m++) { - writeByte(std_dc_chrominance_nrcodes[m+1]); - } - for (var n=0; n<=11; n++) { - writeByte(std_dc_chrominance_values[n]); - } - - writeByte(0x11); // HTUACinfo - for (var o=0; o<16; o++) { - writeByte(std_ac_chrominance_nrcodes[o+1]); - } - for (var p=0; p<=161; p++) { - writeByte(std_ac_chrominance_values[p]); - } - } - - function writeSOS() - { - writeWord(0xFFDA); // marker - writeWord(12); // length - writeByte(3); // nrofcomponents - writeByte(1); // IdY - writeByte(0); // HTY - writeByte(2); // IdU - writeByte(0x11); // HTU - writeByte(3); // IdV - writeByte(0x11); // HTV - writeByte(0); // Ss - writeByte(0x3f); // Se - writeByte(0); // Bf - } - - function processDU(CDU, fdtbl, DC, HTDC, HTAC){ - var EOB = HTAC[0x00]; - var M16zeroes = HTAC[0xF0]; - var pos; - var I16 = 16; - var I63 = 63; - var I64 = 64; - var DU_DCT = fDCTQuant(CDU, fdtbl); - //ZigZag reorder - for (var j=0;j0)&&(DU[end0pos]==0); end0pos--) {}; - //end0pos = first element in reverse order !=0 - if ( end0pos == 0) { - writeBits(EOB); - return DC; - } - var i = 1; - var lng; - while ( i <= end0pos ) { - var startpos = i; - for (; (DU[i]==0) && (i<=end0pos); ++i) {} - var nrzeroes = i-startpos; - if ( nrzeroes >= I16 ) { - lng = nrzeroes>>4; - for (var nrmarker=1; nrmarker <= lng; ++nrmarker) - writeBits(M16zeroes); - nrzeroes = nrzeroes&0xF; - } - pos = 32767+DU[i]; - writeBits(HTAC[(nrzeroes<<4)+category[pos]]); - writeBits(bitcode[pos]); - i++; - } - if ( end0pos != I63 ) { - writeBits(EOB); - } - return DC; - } - - function initCharLookupTable(){ - var sfcc = String.fromCharCode; - for(var i=0; i < 256; i++){ ///// ACHTUNG // 255 - clt[i] = sfcc(i); - } - } - - this.encode = function(image,quality) // image data object - { - // var time_start = new Date().getTime(); - - if(quality) setQuality(quality); - - // Initialize bit writer - byteout = new Array(); - bytenew=0; - bytepos=7; - - // Add JPEG headers - writeWord(0xFFD8); // SOI - writeAPP0(); - writeDQT(); - writeSOF0(image.width,image.height); - writeDHT(); - writeSOS(); - - - // Encode 8x8 macroblocks - var DCY=0; - var DCU=0; - var DCV=0; - - bytenew=0; - bytepos=7; - - - this.encode.displayName = "_encode_"; - - var imageData = image.data; - var width = image.width; - var height = image.height; - - var quadWidth = width*4; - var tripleWidth = width*3; - - var x, y = 0; - var r, g, b; - var start,p, col,row,pos; - while(y < height){ - x = 0; - while(x < quadWidth){ - start = quadWidth * y + x; - p = start; - col = -1; - row = 0; - - for(pos=0; pos < 64; pos++){ - row = pos >> 3;// /8 - col = ( pos & 7 ) * 4; // %8 - p = start + ( row * quadWidth ) + col; - - if(y+row >= height){ // padding bottom - p-= (quadWidth*(y+1+row-height)); - } - - if(x+col >= quadWidth){ // padding right - p-= ((x+col) - quadWidth +4) - } - - r = imageData[ p++ ]; - g = imageData[ p++ ]; - b = imageData[ p++ ]; - - - /* // calculate YUV values dynamically - YDU[pos]=((( 0.29900)*r+( 0.58700)*g+( 0.11400)*b))-128; //-0x80 - UDU[pos]=(((-0.16874)*r+(-0.33126)*g+( 0.50000)*b)); - VDU[pos]=((( 0.50000)*r+(-0.41869)*g+(-0.08131)*b)); - */ - - // use lookup table (slightly faster) - YDU[pos] = ((RGB_YUV_TABLE[r] + RGB_YUV_TABLE[(g + 256)>>0] + RGB_YUV_TABLE[(b + 512)>>0]) >> 16)-128; - UDU[pos] = ((RGB_YUV_TABLE[(r + 768)>>0] + RGB_YUV_TABLE[(g + 1024)>>0] + RGB_YUV_TABLE[(b + 1280)>>0]) >> 16)-128; - VDU[pos] = ((RGB_YUV_TABLE[(r + 1280)>>0] + RGB_YUV_TABLE[(g + 1536)>>0] + RGB_YUV_TABLE[(b + 1792)>>0]) >> 16)-128; - - } - - DCY = processDU(YDU, fdtbl_Y, DCY, YDC_HT, YAC_HT); - DCU = processDU(UDU, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); - DCV = processDU(VDU, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); - x+=32; - } - y+=8; - } - - - //////////////////////////////////////////////////////////////// - - // Do the bit alignment of the EOI marker - if ( bytepos >= 0 ) { - var fillbits = []; - fillbits[1] = bytepos+1; - fillbits[0] = (1<<(bytepos+1))-1; - writeBits(fillbits); - } - - writeWord(0xFFD9); //EOI - - var jpegDataUri = 'data:image/jpeg;base64,' + btoa(byteout.join('')); - - byteout = []; - - // benchmarking - // var duration = new Date().getTime() - time_start; - // console.log('Encoding time: '+ currentQuality + 'ms'); - // - - return jpegDataUri - } - - function setQuality(quality){ - if (quality <= 0) { - quality = 1; - } - if (quality > 100) { - quality = 100; - } - - if(currentQuality == quality) return // don't recalc if unchanged - - var sf = 0; - if (quality < 50) { - sf = Math.floor(5000 / quality); - } else { - sf = Math.floor(200 - quality*2); - } - - initQuantTables(sf); - currentQuality = quality; - // console.log('Quality set to: '+quality +'%'); - } - - function init(){ - // var time_start = new Date().getTime(); - if(!quality) quality = 50; - // Create tables - initCharLookupTable() - initHuffmanTbl(); - initCategoryNumber(); - initRGBYUVTable(); - - setQuality(quality); - // var duration = new Date().getTime() - time_start; - // console.log('Initialization '+ duration + 'ms'); - } - - init(); - - }; - - JPEGEncoder.encode = function( data, quality ) { - var encoder = new JPEGEncoder( quality ); - - return encoder.encode( data ); - } - - return JPEGEncoder; - }); - /** - * @fileOverview Fix android canvas.toDataUrl bug. - */ - define('runtime/html5/androidpatch',[ - 'runtime/html5/util', - 'runtime/html5/jpegencoder', - 'base' - ], function( Util, encoder, Base ) { - var origin = Util.canvasToDataUrl, - supportJpeg; - - Util.canvasToDataUrl = function( canvas, type, quality ) { - var ctx, w, h, fragement, parts; - - // 非android手机直接跳过。 - if ( !Base.os.android ) { - return origin.apply( null, arguments ); - } - - // 检测是否canvas支持jpeg导出,根据数据格式来判断。 - // JPEG 前两位分别是:255, 216 - if ( type === 'image/jpeg' && typeof supportJpeg === 'undefined' ) { - fragement = origin.apply( null, arguments ); - - parts = fragement.split(','); - - if ( ~parts[ 0 ].indexOf('base64') ) { - fragement = atob( parts[ 1 ] ); - } else { - fragement = decodeURIComponent( parts[ 1 ] ); - } - - fragement = fragement.substring( 0, 2 ); - - supportJpeg = fragement.charCodeAt( 0 ) === 255 && - fragement.charCodeAt( 1 ) === 216; - } - - // 只有在android环境下才修复 - if ( type === 'image/jpeg' && !supportJpeg ) { - w = canvas.width; - h = canvas.height; - ctx = canvas.getContext('2d'); - - return encoder.encode( ctx.getImageData( 0, 0, w, h ), quality ); - } - - return origin.apply( null, arguments ); - }; - }); - /** - * @fileOverview Image - */ - define('runtime/html5/image',[ - 'base', - 'runtime/html5/runtime', - 'runtime/html5/util' - ], function( Base, Html5Runtime, Util ) { - - var BLANK = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs%3D'; - - return Html5Runtime.register( 'Image', { - - // flag: 标记是否被修改过。 - modified: false, - - init: function() { - var me = this, - img = new Image(); - - img.onload = function() { - - me._info = { - type: me.type, - width: this.width, - height: this.height - }; - - // 读取meta信息。 - if ( !me._metas && 'image/jpeg' === me.type ) { - Util.parseMeta( me._blob, function( error, ret ) { - me._metas = ret; - me.owner.trigger('load'); - }); - } else { - me.owner.trigger('load'); - } - }; - - img.onerror = function() { - me.owner.trigger('error'); - }; - - me._img = img; - }, - - loadFromBlob: function( blob ) { - var me = this, - img = me._img; - - me._blob = blob; - me.type = blob.type; - img.src = Util.createObjectURL( blob.getSource() ); - me.owner.once( 'load', function() { - Util.revokeObjectURL( img.src ); - }); - }, - - resize: function( width, height ) { - var canvas = this._canvas || - (this._canvas = document.createElement('canvas')); - - this._resize( this._img, canvas, width, height ); - this._blob = null; // 没用了,可以删掉了。 - this.modified = true; - this.owner.trigger( 'complete', 'resize' ); - }, - - crop: function( x, y, w, h, s ) { - var cvs = this._canvas || - (this._canvas = document.createElement('canvas')), - opts = this.options, - img = this._img, - iw = img.naturalWidth, - ih = img.naturalHeight, - orientation = this.getOrientation(); - - s = s || 1; - - // todo 解决 orientation 的问题。 - // values that require 90 degree rotation - // if ( ~[ 5, 6, 7, 8 ].indexOf( orientation ) ) { - - // switch ( orientation ) { - // case 6: - // tmp = x; - // x = y; - // y = iw * s - tmp - w; - // console.log(ih * s, tmp, w) - // break; - // } - - // (w ^= h, h ^= w, w ^= h); - // } - - cvs.width = w; - cvs.height = h; - - opts.preserveHeaders || this._rotate2Orientaion( cvs, orientation ); - this._renderImageToCanvas( cvs, img, -x, -y, iw * s, ih * s ); - - this._blob = null; // 没用了,可以删掉了。 - this.modified = true; - this.owner.trigger( 'complete', 'crop' ); - }, - - getAsBlob: function( type ) { - var blob = this._blob, - opts = this.options, - canvas; - - type = type || this.type; - - // blob需要重新生成。 - if ( this.modified || this.type !== type ) { - canvas = this._canvas; - - if ( type === 'image/jpeg' ) { - - blob = Util.canvasToDataUrl( canvas, type, opts.quality ); - - if ( opts.preserveHeaders && this._metas && - this._metas.imageHead ) { - - blob = Util.dataURL2ArrayBuffer( blob ); - blob = Util.updateImageHead( blob, - this._metas.imageHead ); - blob = Util.arrayBufferToBlob( blob, type ); - return blob; - } - } else { - blob = Util.canvasToDataUrl( canvas, type ); - } - - blob = Util.dataURL2Blob( blob ); - } - - return blob; - }, - - getAsDataUrl: function( type ) { - var opts = this.options; - - type = type || this.type; - - if ( type === 'image/jpeg' ) { - return Util.canvasToDataUrl( this._canvas, type, opts.quality ); - } else { - return this._canvas.toDataURL( type ); - } - }, - - getOrientation: function() { - return this._metas && this._metas.exif && - this._metas.exif.get('Orientation') || 1; - }, - - info: function( val ) { - - // setter - if ( val ) { - this._info = val; - return this; - } - - // getter - return this._info; - }, - - meta: function( val ) { - - // setter - if ( val ) { - this._meta = val; - return this; - } - - // getter - return this._meta; - }, - - destroy: function() { - var canvas = this._canvas; - this._img.onload = null; - - if ( canvas ) { - canvas.getContext('2d') - .clearRect( 0, 0, canvas.width, canvas.height ); - canvas.width = canvas.height = 0; - this._canvas = null; - } - - // 释放内存。非常重要,否则释放不了image的内存。 - this._img.src = BLANK; - this._img = this._blob = null; - }, - - _resize: function( img, cvs, width, height ) { - var opts = this.options, - naturalWidth = img.width, - naturalHeight = img.height, - orientation = this.getOrientation(), - scale, w, h, x, y; - - // values that require 90 degree rotation - if ( ~[ 5, 6, 7, 8 ].indexOf( orientation ) ) { - - // 交换width, height的值。 - width ^= height; - height ^= width; - width ^= height; - } - - scale = Math[ opts.crop ? 'max' : 'min' ]( width / naturalWidth, - height / naturalHeight ); - - // 不允许放大。 - opts.allowMagnify || (scale = Math.min( 1, scale )); - - w = naturalWidth * scale; - h = naturalHeight * scale; - - if ( opts.crop ) { - cvs.width = width; - cvs.height = height; - } else { - cvs.width = w; - cvs.height = h; - } - - x = (cvs.width - w) / 2; - y = (cvs.height - h) / 2; - - opts.preserveHeaders || this._rotate2Orientaion( cvs, orientation ); - - this._renderImageToCanvas( cvs, img, x, y, w, h ); - }, - - _rotate2Orientaion: function( canvas, orientation ) { - var width = canvas.width, - height = canvas.height, - ctx = canvas.getContext('2d'); - - switch ( orientation ) { - case 5: - case 6: - case 7: - case 8: - canvas.width = height; - canvas.height = width; - break; - } - - switch ( orientation ) { - case 2: // horizontal flip - ctx.translate( width, 0 ); - ctx.scale( -1, 1 ); - break; - - case 3: // 180 rotate left - ctx.translate( width, height ); - ctx.rotate( Math.PI ); - break; - - case 4: // vertical flip - ctx.translate( 0, height ); - ctx.scale( 1, -1 ); - break; - - case 5: // vertical flip + 90 rotate right - ctx.rotate( 0.5 * Math.PI ); - ctx.scale( 1, -1 ); - break; - - case 6: // 90 rotate right - ctx.rotate( 0.5 * Math.PI ); - ctx.translate( 0, -height ); - break; - - case 7: // horizontal flip + 90 rotate right - ctx.rotate( 0.5 * Math.PI ); - ctx.translate( width, -height ); - ctx.scale( -1, 1 ); - break; - - case 8: // 90 rotate left - ctx.rotate( -0.5 * Math.PI ); - ctx.translate( -width, 0 ); - break; - } - }, - - // https://github.com/stomita/ios-imagefile-megapixel/ - // blob/master/src/megapix-image.js - _renderImageToCanvas: (function() { - - // 如果不是ios, 不需要这么复杂! - if ( !Base.os.ios ) { - return function( canvas ) { - var args = Base.slice( arguments, 1 ), - ctx = canvas.getContext('2d'); - - ctx.drawImage.apply( ctx, args ); - }; - } - - /** - * Detecting vertical squash in loaded image. - * Fixes a bug which squash image vertically while drawing into - * canvas for some images. - */ - function detectVerticalSquash( img, iw, ih ) { - var canvas = document.createElement('canvas'), - ctx = canvas.getContext('2d'), - sy = 0, - ey = ih, - py = ih, - data, alpha, ratio; - - - canvas.width = 1; - canvas.height = ih; - ctx.drawImage( img, 0, 0 ); - data = ctx.getImageData( 0, 0, 1, ih ).data; - - // search image edge pixel position in case - // it is squashed vertically. - while ( py > sy ) { - alpha = data[ (py - 1) * 4 + 3 ]; - - if ( alpha === 0 ) { - ey = py; - } else { - sy = py; - } - - py = (ey + sy) >> 1; - } - - ratio = (py / ih); - return (ratio === 0) ? 1 : ratio; - } - - // fix ie7 bug - // http://stackoverflow.com/questions/11929099/ - // html5-canvas-drawimage-ratio-bug-ios - if ( Base.os.ios >= 7 ) { - return function( canvas, img, x, y, w, h ) { - var iw = img.naturalWidth, - ih = img.naturalHeight, - vertSquashRatio = detectVerticalSquash( img, iw, ih ); - - return canvas.getContext('2d').drawImage( img, 0, 0, - iw * vertSquashRatio, ih * vertSquashRatio, - x, y, w, h ); - }; - } - - /** - * Detect subsampling in loaded image. - * In iOS, larger images than 2M pixels may be - * subsampled in rendering. - */ - function detectSubsampling( img ) { - var iw = img.naturalWidth, - ih = img.naturalHeight, - canvas, ctx; - - // subsampling may happen overmegapixel image - if ( iw * ih > 1024 * 1024 ) { - canvas = document.createElement('canvas'); - canvas.width = canvas.height = 1; - ctx = canvas.getContext('2d'); - ctx.drawImage( img, -iw + 1, 0 ); - - // subsampled image becomes half smaller in rendering size. - // check alpha channel value to confirm image is covering - // edge pixel or not. if alpha value is 0 - // image is not covering, hence subsampled. - return ctx.getImageData( 0, 0, 1, 1 ).data[ 3 ] === 0; - } else { - return false; - } - } - - - return function( canvas, img, x, y, width, height ) { - var iw = img.naturalWidth, - ih = img.naturalHeight, - ctx = canvas.getContext('2d'), - subsampled = detectSubsampling( img ), - doSquash = this.type === 'image/jpeg', - d = 1024, - sy = 0, - dy = 0, - tmpCanvas, tmpCtx, vertSquashRatio, dw, dh, sx, dx; - - if ( subsampled ) { - iw /= 2; - ih /= 2; - } - - ctx.save(); - tmpCanvas = document.createElement('canvas'); - tmpCanvas.width = tmpCanvas.height = d; - - tmpCtx = tmpCanvas.getContext('2d'); - vertSquashRatio = doSquash ? - detectVerticalSquash( img, iw, ih ) : 1; - - dw = Math.ceil( d * width / iw ); - dh = Math.ceil( d * height / ih / vertSquashRatio ); - - while ( sy < ih ) { - sx = 0; - dx = 0; - while ( sx < iw ) { - tmpCtx.clearRect( 0, 0, d, d ); - tmpCtx.drawImage( img, -sx, -sy ); - ctx.drawImage( tmpCanvas, 0, 0, d, d, - x + dx, y + dy, dw, dh ); - sx += d; - dx += dw; - } - sy += d; - dy += dh; - } - ctx.restore(); - tmpCanvas = tmpCtx = null; - }; - })() - }); - }); - /** - * @fileOverview Transport - * @todo 支持chunked传输,优势: - * 可以将大文件分成小块,挨个传输,可以提高大文件成功率,当失败的时候,也只需要重传那小部分, - * 而不需要重头再传一次。另外断点续传也需要用chunked方式。 - */ - define('runtime/html5/transport',[ - 'base', - 'runtime/html5/runtime' - ], function( Base, Html5Runtime ) { - - var noop = Base.noop, - $ = Base.$; - - return Html5Runtime.register( 'Transport', { - init: function() { - this._status = 0; - this._response = null; - }, - - send: function() { - var owner = this.owner, - opts = this.options, - xhr = this._initAjax(), - blob = owner._blob, - server = opts.server, - formData, binary, fr; - - if ( opts.sendAsBinary ) { - server += (/\?/.test( server ) ? '&' : '?') + - $.param( owner._formData ); - - binary = blob.getSource(); - } else { - formData = new FormData(); - $.each( owner._formData, function( k, v ) { - formData.append( k, v ); - }); - - formData.append( opts.fileVal, blob.getSource(), - opts.filename || owner._formData.name || '' ); - } - - if ( opts.withCredentials && 'withCredentials' in xhr ) { - xhr.open( opts.method, server, true ); - xhr.withCredentials = true; - } else { - xhr.open( opts.method, server ); - } - - this._setRequestHeader( xhr, opts.headers ); - - if ( binary ) { - // 强制设置成 content-type 为文件流。 - xhr.overrideMimeType && - xhr.overrideMimeType('application/octet-stream'); - - // android直接发送blob会导致服务端接收到的是空文件。 - // bug详情。 - // https://code.google.com/p/android/issues/detail?id=39882 - // 所以先用fileReader读取出来再通过arraybuffer的方式发送。 - if ( Base.os.android ) { - fr = new FileReader(); - - fr.onload = function() { - xhr.send( this.result ); - fr = fr.onload = null; - }; - - fr.readAsArrayBuffer( binary ); - } else { - xhr.send( binary ); - } - } else { - xhr.send( formData ); - } - }, - - getResponse: function() { - return this._response; - }, - - getResponseAsJson: function() { - return this._parseJson( this._response ); - }, - - getStatus: function() { - return this._status; - }, - - abort: function() { - var xhr = this._xhr; - - if ( xhr ) { - xhr.upload.onprogress = noop; - xhr.onreadystatechange = noop; - xhr.abort(); - - this._xhr = xhr = null; - } - }, - - destroy: function() { - this.abort(); - }, - - _initAjax: function() { - var me = this, - xhr = new XMLHttpRequest(), - opts = this.options; - - if ( opts.withCredentials && !('withCredentials' in xhr) && - typeof XDomainRequest !== 'undefined' ) { - xhr = new XDomainRequest(); - } - - xhr.upload.onprogress = function( e ) { - var percentage = 0; - - if ( e.lengthComputable ) { - percentage = e.loaded / e.total; - } - - return me.trigger( 'progress', percentage ); - }; - - xhr.onreadystatechange = function() { - - if ( xhr.readyState !== 4 ) { - return; - } - - xhr.upload.onprogress = noop; - xhr.onreadystatechange = noop; - me._xhr = null; - me._status = xhr.status; - - if ( xhr.status >= 200 && xhr.status < 300 ) { - me._response = xhr.responseText; - return me.trigger('load'); - } else if ( xhr.status >= 500 && xhr.status < 600 ) { - me._response = xhr.responseText; - return me.trigger( 'error', 'server' ); - } - - - return me.trigger( 'error', me._status ? 'http' : 'abort' ); - }; - - me._xhr = xhr; - return xhr; - }, - - _setRequestHeader: function( xhr, headers ) { - $.each( headers, function( key, val ) { - xhr.setRequestHeader( key, val ); - }); - }, - - _parseJson: function( str ) { - var json; - - try { - json = JSON.parse( str ); - } catch ( ex ) { - json = {}; - } - - return json; - } - }); - }); - /** - * @fileOverview Transport flash实现 - */ - define('runtime/html5/md5',[ - 'runtime/html5/runtime' - ], function( FlashRuntime ) { - - /* - * Fastest md5 implementation around (JKM md5) - * Credits: Joseph Myers - * - * @see http://www.myersdaily.org/joseph/javascript/md5-text.html - * @see http://jsperf.com/md5-shootout/7 - */ - - /* this function is much faster, - so if possible we use it. Some IEs - are the only ones I know of that - need the idiotic second function, - generated by an if clause. */ - var add32 = function (a, b) { - return (a + b) & 0xFFFFFFFF; - }, - - cmn = function (q, a, b, x, s, t) { - a = add32(add32(a, q), add32(x, t)); - return add32((a << s) | (a >>> (32 - s)), b); - }, - - ff = function (a, b, c, d, x, s, t) { - return cmn((b & c) | ((~b) & d), a, b, x, s, t); - }, - - gg = function (a, b, c, d, x, s, t) { - return cmn((b & d) | (c & (~d)), a, b, x, s, t); - }, - - hh = function (a, b, c, d, x, s, t) { - return cmn(b ^ c ^ d, a, b, x, s, t); - }, - - ii = function (a, b, c, d, x, s, t) { - return cmn(c ^ (b | (~d)), a, b, x, s, t); - }, - - md5cycle = function (x, k) { - var a = x[0], - b = x[1], - c = x[2], - d = x[3]; - - a = ff(a, b, c, d, k[0], 7, -680876936); - d = ff(d, a, b, c, k[1], 12, -389564586); - c = ff(c, d, a, b, k[2], 17, 606105819); - b = ff(b, c, d, a, k[3], 22, -1044525330); - a = ff(a, b, c, d, k[4], 7, -176418897); - d = ff(d, a, b, c, k[5], 12, 1200080426); - c = ff(c, d, a, b, k[6], 17, -1473231341); - b = ff(b, c, d, a, k[7], 22, -45705983); - a = ff(a, b, c, d, k[8], 7, 1770035416); - d = ff(d, a, b, c, k[9], 12, -1958414417); - c = ff(c, d, a, b, k[10], 17, -42063); - b = ff(b, c, d, a, k[11], 22, -1990404162); - a = ff(a, b, c, d, k[12], 7, 1804603682); - d = ff(d, a, b, c, k[13], 12, -40341101); - c = ff(c, d, a, b, k[14], 17, -1502002290); - b = ff(b, c, d, a, k[15], 22, 1236535329); - - a = gg(a, b, c, d, k[1], 5, -165796510); - d = gg(d, a, b, c, k[6], 9, -1069501632); - c = gg(c, d, a, b, k[11], 14, 643717713); - b = gg(b, c, d, a, k[0], 20, -373897302); - a = gg(a, b, c, d, k[5], 5, -701558691); - d = gg(d, a, b, c, k[10], 9, 38016083); - c = gg(c, d, a, b, k[15], 14, -660478335); - b = gg(b, c, d, a, k[4], 20, -405537848); - a = gg(a, b, c, d, k[9], 5, 568446438); - d = gg(d, a, b, c, k[14], 9, -1019803690); - c = gg(c, d, a, b, k[3], 14, -187363961); - b = gg(b, c, d, a, k[8], 20, 1163531501); - a = gg(a, b, c, d, k[13], 5, -1444681467); - d = gg(d, a, b, c, k[2], 9, -51403784); - c = gg(c, d, a, b, k[7], 14, 1735328473); - b = gg(b, c, d, a, k[12], 20, -1926607734); - - a = hh(a, b, c, d, k[5], 4, -378558); - d = hh(d, a, b, c, k[8], 11, -2022574463); - c = hh(c, d, a, b, k[11], 16, 1839030562); - b = hh(b, c, d, a, k[14], 23, -35309556); - a = hh(a, b, c, d, k[1], 4, -1530992060); - d = hh(d, a, b, c, k[4], 11, 1272893353); - c = hh(c, d, a, b, k[7], 16, -155497632); - b = hh(b, c, d, a, k[10], 23, -1094730640); - a = hh(a, b, c, d, k[13], 4, 681279174); - d = hh(d, a, b, c, k[0], 11, -358537222); - c = hh(c, d, a, b, k[3], 16, -722521979); - b = hh(b, c, d, a, k[6], 23, 76029189); - a = hh(a, b, c, d, k[9], 4, -640364487); - d = hh(d, a, b, c, k[12], 11, -421815835); - c = hh(c, d, a, b, k[15], 16, 530742520); - b = hh(b, c, d, a, k[2], 23, -995338651); - - a = ii(a, b, c, d, k[0], 6, -198630844); - d = ii(d, a, b, c, k[7], 10, 1126891415); - c = ii(c, d, a, b, k[14], 15, -1416354905); - b = ii(b, c, d, a, k[5], 21, -57434055); - a = ii(a, b, c, d, k[12], 6, 1700485571); - d = ii(d, a, b, c, k[3], 10, -1894986606); - c = ii(c, d, a, b, k[10], 15, -1051523); - b = ii(b, c, d, a, k[1], 21, -2054922799); - a = ii(a, b, c, d, k[8], 6, 1873313359); - d = ii(d, a, b, c, k[15], 10, -30611744); - c = ii(c, d, a, b, k[6], 15, -1560198380); - b = ii(b, c, d, a, k[13], 21, 1309151649); - a = ii(a, b, c, d, k[4], 6, -145523070); - d = ii(d, a, b, c, k[11], 10, -1120210379); - c = ii(c, d, a, b, k[2], 15, 718787259); - b = ii(b, c, d, a, k[9], 21, -343485551); - - x[0] = add32(a, x[0]); - x[1] = add32(b, x[1]); - x[2] = add32(c, x[2]); - x[3] = add32(d, x[3]); - }, - - /* there needs to be support for Unicode here, - * unless we pretend that we can redefine the MD-5 - * algorithm for multi-byte characters (perhaps - * by adding every four 16-bit characters and - * shortening the sum to 32 bits). Otherwise - * I suggest performing MD-5 as if every character - * was two bytes--e.g., 0040 0025 = @%--but then - * how will an ordinary MD-5 sum be matched? - * There is no way to standardize text to something - * like UTF-8 before transformation; speed cost is - * utterly prohibitive. The JavaScript standard - * itself needs to look at this: it should start - * providing access to strings as preformed UTF-8 - * 8-bit unsigned value arrays. - */ - md5blk = function (s) { - var md5blks = [], - i; /* Andy King said do it this way. */ - - for (i = 0; i < 64; i += 4) { - md5blks[i >> 2] = s.charCodeAt(i) + (s.charCodeAt(i + 1) << 8) + (s.charCodeAt(i + 2) << 16) + (s.charCodeAt(i + 3) << 24); - } - return md5blks; - }, - - md5blk_array = function (a) { - var md5blks = [], - i; /* Andy King said do it this way. */ - - for (i = 0; i < 64; i += 4) { - md5blks[i >> 2] = a[i] + (a[i + 1] << 8) + (a[i + 2] << 16) + (a[i + 3] << 24); - } - return md5blks; - }, - - md51 = function (s) { - var n = s.length, - state = [1732584193, -271733879, -1732584194, 271733878], - i, - length, - tail, - tmp, - lo, - hi; - - for (i = 64; i <= n; i += 64) { - md5cycle(state, md5blk(s.substring(i - 64, i))); - } - s = s.substring(i - 64); - length = s.length; - tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - for (i = 0; i < length; i += 1) { - tail[i >> 2] |= s.charCodeAt(i) << ((i % 4) << 3); - } - tail[i >> 2] |= 0x80 << ((i % 4) << 3); - if (i > 55) { - md5cycle(state, tail); - for (i = 0; i < 16; i += 1) { - tail[i] = 0; - } - } - - // Beware that the final length might not fit in 32 bits so we take care of that - tmp = n * 8; - tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/); - lo = parseInt(tmp[2], 16); - hi = parseInt(tmp[1], 16) || 0; - - tail[14] = lo; - tail[15] = hi; - - md5cycle(state, tail); - return state; - }, - - md51_array = function (a) { - var n = a.length, - state = [1732584193, -271733879, -1732584194, 271733878], - i, - length, - tail, - tmp, - lo, - hi; - - for (i = 64; i <= n; i += 64) { - md5cycle(state, md5blk_array(a.subarray(i - 64, i))); - } - - // Not sure if it is a bug, however IE10 will always produce a sub array of length 1 - // containing the last element of the parent array if the sub array specified starts - // beyond the length of the parent array - weird. - // https://connect.microsoft.com/IE/feedback/details/771452/typed-array-subarray-issue - a = (i - 64) < n ? a.subarray(i - 64) : new Uint8Array(0); - - length = a.length; - tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - for (i = 0; i < length; i += 1) { - tail[i >> 2] |= a[i] << ((i % 4) << 3); - } - - tail[i >> 2] |= 0x80 << ((i % 4) << 3); - if (i > 55) { - md5cycle(state, tail); - for (i = 0; i < 16; i += 1) { - tail[i] = 0; - } - } - - // Beware that the final length might not fit in 32 bits so we take care of that - tmp = n * 8; - tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/); - lo = parseInt(tmp[2], 16); - hi = parseInt(tmp[1], 16) || 0; - - tail[14] = lo; - tail[15] = hi; - - md5cycle(state, tail); - - return state; - }, - - hex_chr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'], - - rhex = function (n) { - var s = '', - j; - for (j = 0; j < 4; j += 1) { - s += hex_chr[(n >> (j * 8 + 4)) & 0x0F] + hex_chr[(n >> (j * 8)) & 0x0F]; - } - return s; - }, - - hex = function (x) { - var i; - for (i = 0; i < x.length; i += 1) { - x[i] = rhex(x[i]); - } - return x.join(''); - }, - - md5 = function (s) { - return hex(md51(s)); - }, - - - - //////////////////////////////////////////////////////////////////////////// - - /** - * SparkMD5 OOP implementation. - * - * Use this class to perform an incremental md5, otherwise use the - * static methods instead. - */ - SparkMD5 = function () { - // call reset to init the instance - this.reset(); - }; - - - // In some cases the fast add32 function cannot be used.. - if (md5('hello') !== '5d41402abc4b2a76b9719d911017c592') { - add32 = function (x, y) { - var lsw = (x & 0xFFFF) + (y & 0xFFFF), - msw = (x >> 16) + (y >> 16) + (lsw >> 16); - return (msw << 16) | (lsw & 0xFFFF); - }; - } - - - /** - * Appends a string. - * A conversion will be applied if an utf8 string is detected. - * - * @param {String} str The string to be appended - * - * @return {SparkMD5} The instance itself - */ - SparkMD5.prototype.append = function (str) { - // converts the string to utf8 bytes if necessary - if (/[\u0080-\uFFFF]/.test(str)) { - str = unescape(encodeURIComponent(str)); - } - - // then append as binary - this.appendBinary(str); - - return this; - }; - - /** - * Appends a binary string. - * - * @param {String} contents The binary string to be appended - * - * @return {SparkMD5} The instance itself - */ - SparkMD5.prototype.appendBinary = function (contents) { - this._buff += contents; - this._length += contents.length; - - var length = this._buff.length, - i; - - for (i = 64; i <= length; i += 64) { - md5cycle(this._state, md5blk(this._buff.substring(i - 64, i))); - } - - this._buff = this._buff.substr(i - 64); - - return this; - }; - - /** - * Finishes the incremental computation, reseting the internal state and - * returning the result. - * Use the raw parameter to obtain the raw result instead of the hex one. - * - * @param {Boolean} raw True to get the raw result, false to get the hex result - * - * @return {String|Array} The result - */ - SparkMD5.prototype.end = function (raw) { - var buff = this._buff, - length = buff.length, - i, - tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ret; - - for (i = 0; i < length; i += 1) { - tail[i >> 2] |= buff.charCodeAt(i) << ((i % 4) << 3); - } - - this._finish(tail, length); - ret = !!raw ? this._state : hex(this._state); - - this.reset(); - - return ret; - }; - - /** - * Finish the final calculation based on the tail. - * - * @param {Array} tail The tail (will be modified) - * @param {Number} length The length of the remaining buffer - */ - SparkMD5.prototype._finish = function (tail, length) { - var i = length, - tmp, - lo, - hi; - - tail[i >> 2] |= 0x80 << ((i % 4) << 3); - if (i > 55) { - md5cycle(this._state, tail); - for (i = 0; i < 16; i += 1) { - tail[i] = 0; - } - } - - // Do the final computation based on the tail and length - // Beware that the final length may not fit in 32 bits so we take care of that - tmp = this._length * 8; - tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/); - lo = parseInt(tmp[2], 16); - hi = parseInt(tmp[1], 16) || 0; - - tail[14] = lo; - tail[15] = hi; - md5cycle(this._state, tail); - }; - - /** - * Resets the internal state of the computation. - * - * @return {SparkMD5} The instance itself - */ - SparkMD5.prototype.reset = function () { - this._buff = ""; - this._length = 0; - this._state = [1732584193, -271733879, -1732584194, 271733878]; - - return this; - }; - - /** - * Releases memory used by the incremental buffer and other aditional - * resources. If you plan to use the instance again, use reset instead. - */ - SparkMD5.prototype.destroy = function () { - delete this._state; - delete this._buff; - delete this._length; - }; - - - /** - * Performs the md5 hash on a string. - * A conversion will be applied if utf8 string is detected. - * - * @param {String} str The string - * @param {Boolean} raw True to get the raw result, false to get the hex result - * - * @return {String|Array} The result - */ - SparkMD5.hash = function (str, raw) { - // converts the string to utf8 bytes if necessary - if (/[\u0080-\uFFFF]/.test(str)) { - str = unescape(encodeURIComponent(str)); - } - - var hash = md51(str); - - return !!raw ? hash : hex(hash); - }; - - /** - * Performs the md5 hash on a binary string. - * - * @param {String} content The binary string - * @param {Boolean} raw True to get the raw result, false to get the hex result - * - * @return {String|Array} The result - */ - SparkMD5.hashBinary = function (content, raw) { - var hash = md51(content); - - return !!raw ? hash : hex(hash); - }; - - /** - * SparkMD5 OOP implementation for array buffers. - * - * Use this class to perform an incremental md5 ONLY for array buffers. - */ - SparkMD5.ArrayBuffer = function () { - // call reset to init the instance - this.reset(); - }; - - //////////////////////////////////////////////////////////////////////////// - - /** - * Appends an array buffer. - * - * @param {ArrayBuffer} arr The array to be appended - * - * @return {SparkMD5.ArrayBuffer} The instance itself - */ - SparkMD5.ArrayBuffer.prototype.append = function (arr) { - // TODO: we could avoid the concatenation here but the algorithm would be more complex - // if you find yourself needing extra performance, please make a PR. - var buff = this._concatArrayBuffer(this._buff, arr), - length = buff.length, - i; - - this._length += arr.byteLength; - - for (i = 64; i <= length; i += 64) { - md5cycle(this._state, md5blk_array(buff.subarray(i - 64, i))); - } - - // Avoids IE10 weirdness (documented above) - this._buff = (i - 64) < length ? buff.subarray(i - 64) : new Uint8Array(0); - - return this; - }; - - /** - * Finishes the incremental computation, reseting the internal state and - * returning the result. - * Use the raw parameter to obtain the raw result instead of the hex one. - * - * @param {Boolean} raw True to get the raw result, false to get the hex result - * - * @return {String|Array} The result - */ - SparkMD5.ArrayBuffer.prototype.end = function (raw) { - var buff = this._buff, - length = buff.length, - tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - i, - ret; - - for (i = 0; i < length; i += 1) { - tail[i >> 2] |= buff[i] << ((i % 4) << 3); - } - - this._finish(tail, length); - ret = !!raw ? this._state : hex(this._state); - - this.reset(); - - return ret; - }; - - SparkMD5.ArrayBuffer.prototype._finish = SparkMD5.prototype._finish; - - /** - * Resets the internal state of the computation. - * - * @return {SparkMD5.ArrayBuffer} The instance itself - */ - SparkMD5.ArrayBuffer.prototype.reset = function () { - this._buff = new Uint8Array(0); - this._length = 0; - this._state = [1732584193, -271733879, -1732584194, 271733878]; - - return this; - }; - - /** - * Releases memory used by the incremental buffer and other aditional - * resources. If you plan to use the instance again, use reset instead. - */ - SparkMD5.ArrayBuffer.prototype.destroy = SparkMD5.prototype.destroy; - - /** - * Concats two array buffers, returning a new one. - * - * @param {ArrayBuffer} first The first array buffer - * @param {ArrayBuffer} second The second array buffer - * - * @return {ArrayBuffer} The new array buffer - */ - SparkMD5.ArrayBuffer.prototype._concatArrayBuffer = function (first, second) { - var firstLength = first.length, - result = new Uint8Array(firstLength + second.byteLength); - - result.set(first); - result.set(new Uint8Array(second), firstLength); - - return result; - }; - - /** - * Performs the md5 hash on an array buffer. - * - * @param {ArrayBuffer} arr The array buffer - * @param {Boolean} raw True to get the raw result, false to get the hex result - * - * @return {String|Array} The result - */ - SparkMD5.ArrayBuffer.hash = function (arr, raw) { - var hash = md51_array(new Uint8Array(arr)); - - return !!raw ? hash : hex(hash); - }; - - return FlashRuntime.register( 'Md5', { - init: function() { - // do nothing. - }, - - loadFromBlob: function( file ) { - var blob = file.getSource(), - chunkSize = 2 * 1024 * 1024, - chunks = Math.ceil( blob.size / chunkSize ), - chunk = 0, - owner = this.owner, - spark = new SparkMD5.ArrayBuffer(), - me = this, - blobSlice = blob.mozSlice || blob.webkitSlice || blob.slice, - loadNext, fr; - - fr = new FileReader(); - - loadNext = function() { - var start, end; - - start = chunk * chunkSize; - end = Math.min( start + chunkSize, blob.size ); - - fr.onload = function( e ) { - spark.append( e.target.result ); - owner.trigger( 'progress', { - total: file.size, - loaded: end - }); - }; - - fr.onloadend = function() { - fr.onloadend = fr.onload = null; - - if ( ++chunk < chunks ) { - setTimeout( loadNext, 1 ); - } else { - setTimeout(function(){ - owner.trigger('load'); - me.result = spark.end(); - loadNext = file = blob = spark = null; - owner.trigger('complete'); - }, 50 ); - } - }; - - fr.readAsArrayBuffer( blobSlice.call( blob, start, end ) ); - }; - - loadNext(); - }, - - getResult: function() { - return this.result; - } - }); - }); - /** - * @fileOverview FlashRuntime - */ - define('runtime/flash/runtime',[ - 'base', - 'runtime/runtime', - 'runtime/compbase' - ], function( Base, Runtime, CompBase ) { - - var $ = Base.$, - type = 'flash', - components = {}; - - - function getFlashVersion() { - var version; - - try { - version = navigator.plugins[ 'Shockwave Flash' ]; - version = version.description; - } catch ( ex ) { - try { - version = new ActiveXObject('ShockwaveFlash.ShockwaveFlash') - .GetVariable('$version'); - } catch ( ex2 ) { - version = '0.0'; - } - } - version = version.match( /\d+/g ); - return parseFloat( version[ 0 ] + '.' + version[ 1 ], 10 ); - } - - function FlashRuntime() { - var pool = {}, - clients = {}, - destroy = this.destroy, - me = this, - jsreciver = Base.guid('webuploader_'); - - Runtime.apply( me, arguments ); - me.type = type; - - - // 这个方法的调用者,实际上是RuntimeClient - me.exec = function( comp, fn/*, args...*/ ) { - var client = this, - uid = client.uid, - args = Base.slice( arguments, 2 ), - instance; - - clients[ uid ] = client; - - if ( components[ comp ] ) { - if ( !pool[ uid ] ) { - pool[ uid ] = new components[ comp ]( client, me ); - } - - instance = pool[ uid ]; - - if ( instance[ fn ] ) { - return instance[ fn ].apply( instance, args ); - } - } - - return me.flashExec.apply( client, arguments ); - }; - - function handler( evt, obj ) { - var type = evt.type || evt, - parts, uid; - - parts = type.split('::'); - uid = parts[ 0 ]; - type = parts[ 1 ]; - - // console.log.apply( console, arguments ); - - if ( type === 'Ready' && uid === me.uid ) { - me.trigger('ready'); - } else if ( clients[ uid ] ) { - clients[ uid ].trigger( type.toLowerCase(), evt, obj ); - } - - // Base.log( evt, obj ); - } - - // flash的接受器。 - window[ jsreciver ] = function() { - var args = arguments; - - // 为了能捕获得到。 - setTimeout(function() { - handler.apply( null, args ); - }, 1 ); - }; - - this.jsreciver = jsreciver; - - this.destroy = function() { - // @todo 删除池子中的所有实例 - return destroy && destroy.apply( this, arguments ); - }; - - this.flashExec = function( comp, fn ) { - var flash = me.getFlash(), - args = Base.slice( arguments, 2 ); - - return flash.exec( this.uid, comp, fn, args ); - }; - - // @todo - } - - Base.inherits( Runtime, { - constructor: FlashRuntime, - - init: function() { - var container = this.getContainer(), - opts = this.options, - html; - - // if not the minimal height, shims are not initialized - // in older browsers (e.g FF3.6, IE6,7,8, Safari 4.0,5.0, etc) - container.css({ - position: 'absolute', - top: '-8px', - left: '-8px', - width: '9px', - height: '9px', - overflow: 'hidden' - }); - - // insert flash object - html = '' + - '' + - '' + - '' + - ''; - - container.html( html ); - }, - - getFlash: function() { - if ( this._flash ) { - return this._flash; - } - - this._flash = $( '#' + this.uid ).get( 0 ); - return this._flash; - } - - }); - - FlashRuntime.register = function( name, component ) { - component = components[ name ] = Base.inherits( CompBase, $.extend({ - - // @todo fix this later - flashExec: function() { - var owner = this.owner, - runtime = this.getRuntime(); - - return runtime.flashExec.apply( owner, arguments ); - } - }, component ) ); - - return component; - }; - - if ( getFlashVersion() >= 11.4 ) { - Runtime.addRuntime( type, FlashRuntime ); - } - - return FlashRuntime; - }); - /** - * @fileOverview FilePicker - */ - define('runtime/flash/filepicker',[ - 'base', - 'runtime/flash/runtime' - ], function( Base, FlashRuntime ) { - var $ = Base.$; - - return FlashRuntime.register( 'FilePicker', { - init: function( opts ) { - var copy = $.extend({}, opts ), - len, i; - - // 修复Flash再没有设置title的情况下无法弹出flash文件选择框的bug. - len = copy.accept && copy.accept.length; - for ( i = 0; i < len; i++ ) { - if ( !copy.accept[ i ].title ) { - copy.accept[ i ].title = 'Files'; - } - } - - delete copy.button; - delete copy.id; - delete copy.container; - - this.flashExec( 'FilePicker', 'init', copy ); - }, - - destroy: function() { - this.flashExec( 'FilePicker', 'destroy' ); - } - }); - }); - /** - * @fileOverview 图片压缩 - */ - define('runtime/flash/image',[ - 'runtime/flash/runtime' - ], function( FlashRuntime ) { - - return FlashRuntime.register( 'Image', { - // init: function( options ) { - // var owner = this.owner; - - // this.flashExec( 'Image', 'init', options ); - // owner.on( 'load', function() { - // debugger; - // }); - // }, - - loadFromBlob: function( blob ) { - var owner = this.owner; - - owner.info() && this.flashExec( 'Image', 'info', owner.info() ); - owner.meta() && this.flashExec( 'Image', 'meta', owner.meta() ); - - this.flashExec( 'Image', 'loadFromBlob', blob.uid ); - } - }); - }); - /** - * @fileOverview Transport flash实现 - */ - define('runtime/flash/transport',[ - 'base', - 'runtime/flash/runtime', - 'runtime/client' - ], function( Base, FlashRuntime, RuntimeClient ) { - var $ = Base.$; - - return FlashRuntime.register( 'Transport', { - init: function() { - this._status = 0; - this._response = null; - this._responseJson = null; - }, - - send: function() { - var owner = this.owner, - opts = this.options, - xhr = this._initAjax(), - blob = owner._blob, - server = opts.server, - binary; - - xhr.connectRuntime( blob.ruid ); - - if ( opts.sendAsBinary ) { - server += (/\?/.test( server ) ? '&' : '?') + - $.param( owner._formData ); - - binary = blob.uid; - } else { - $.each( owner._formData, function( k, v ) { - xhr.exec( 'append', k, v ); - }); - - xhr.exec( 'appendBlob', opts.fileVal, blob.uid, - opts.filename || owner._formData.name || '' ); - } - - this._setRequestHeader( xhr, opts.headers ); - xhr.exec( 'send', { - method: opts.method, - url: server, - forceURLStream: opts.forceURLStream, - mimeType: 'application/octet-stream' - }, binary ); - }, - - getStatus: function() { - return this._status; - }, - - getResponse: function() { - return this._response || ''; - }, - - getResponseAsJson: function() { - return this._responseJson; - }, - - abort: function() { - var xhr = this._xhr; - - if ( xhr ) { - xhr.exec('abort'); - xhr.destroy(); - this._xhr = xhr = null; - } - }, - - destroy: function() { - this.abort(); - }, - - _initAjax: function() { - var me = this, - xhr = new RuntimeClient('XMLHttpRequest'); - - xhr.on( 'uploadprogress progress', function( e ) { - var percent = e.loaded / e.total; - percent = Math.min( 1, Math.max( 0, percent ) ); - return me.trigger( 'progress', percent ); - }); - - xhr.on( 'load', function() { - var status = xhr.exec('getStatus'), - readBody = false, - err = '', - p; - - xhr.off(); - me._xhr = null; - - if ( status >= 200 && status < 300 ) { - readBody = true; - } else if ( status >= 500 && status < 600 ) { - readBody = true; - err = 'server'; - } else { - err = 'http'; - } - - if ( readBody ) { - me._response = xhr.exec('getResponse'); - me._response = decodeURIComponent( me._response ); - - // flash 处理可能存在 bug, 没辙只能靠 js 了 - // try { - // me._responseJson = xhr.exec('getResponseAsJson'); - // } catch ( error ) { - - p = window.JSON && window.JSON.parse || function( s ) { - try { - return new Function('return ' + s).call(); - } catch ( err ) { - return {}; - } - }; - me._responseJson = me._response ? p(me._response) : {}; - - // } - } - - xhr.destroy(); - xhr = null; - - return err ? me.trigger( 'error', err ) : me.trigger('load'); - }); - - xhr.on( 'error', function() { - xhr.off(); - me._xhr = null; - me.trigger( 'error', 'http' ); - }); - - me._xhr = xhr; - return xhr; - }, - - _setRequestHeader: function( xhr, headers ) { - $.each( headers, function( key, val ) { - xhr.exec( 'setRequestHeader', key, val ); - }); - } - }); - }); - /** - * @fileOverview Blob Html实现 - */ - define('runtime/flash/blob',[ - 'runtime/flash/runtime', - 'lib/blob' - ], function( FlashRuntime, Blob ) { - - return FlashRuntime.register( 'Blob', { - slice: function( start, end ) { - var blob = this.flashExec( 'Blob', 'slice', start, end ); - - return new Blob( blob.uid, blob ); - } - }); - }); - /** - * @fileOverview Md5 flash实现 - */ - define('runtime/flash/md5',[ - 'runtime/flash/runtime' - ], function( FlashRuntime ) { - - return FlashRuntime.register( 'Md5', { - init: function() { - // do nothing. - }, - - loadFromBlob: function( blob ) { - return this.flashExec( 'Md5', 'loadFromBlob', blob.uid ); - } - }); - }); - /** - * @fileOverview 完全版本。 - */ - define('preset/all',[ - 'base', - - // widgets - 'widgets/filednd', - 'widgets/filepaste', - 'widgets/filepicker', - 'widgets/image', - 'widgets/queue', - 'widgets/runtime', - 'widgets/upload', - 'widgets/validator', - 'widgets/md5', - - // runtimes - // html5 - 'runtime/html5/blob', - 'runtime/html5/dnd', - 'runtime/html5/filepaste', - 'runtime/html5/filepicker', - 'runtime/html5/imagemeta/exif', - 'runtime/html5/androidpatch', - 'runtime/html5/image', - 'runtime/html5/transport', - 'runtime/html5/md5', - - // flash - 'runtime/flash/filepicker', - 'runtime/flash/image', - 'runtime/flash/transport', - 'runtime/flash/blob', - 'runtime/flash/md5' - ], function( Base ) { - return Base; - }); - define('webuploader',[ - 'preset/all' - ], function( preset ) { - return preset; - }); - return require('webuploader'); -}); diff --git a/static/plugins/webuploader/webuploader.nolog.min.js b/static/plugins/webuploader/webuploader.nolog.min.js deleted file mode 100644 index a4ee44b3..00000000 --- a/static/plugins/webuploader/webuploader.nolog.min.js +++ /dev/null @@ -1,3 +0,0 @@ -/* WebUploader 0.1.5 */!function(a,b){var c,d={},e=function(a,b){var c,d,e;if("string"==typeof a)return h(a);for(c=[],d=a.length,e=0;d>e;e++)c.push(h(a[e]));return b.apply(null,c)},f=function(a,b,c){2===arguments.length&&(c=b,b=null),e(b||[],function(){g(a,c,arguments)})},g=function(a,b,c){var f,g={exports:b};"function"==typeof b&&(c.length||(c=[e,g.exports,g]),f=b.apply(null,c),void 0!==f&&(g.exports=f)),d[a]=g.exports},h=function(b){var c=d[b]||a[b];if(!c)throw new Error("`"+b+"` is undefined");return c},i=function(a){var b,c,e,f,g,h;h=function(a){return a&&a.charAt(0).toUpperCase()+a.substr(1)};for(b in d)if(c=a,d.hasOwnProperty(b)){for(e=b.split("/"),g=h(e.pop());f=h(e.shift());)c[f]=c[f]||{},c=c[f];c[g]=d[b]}return a},j=function(c){return a.__dollar=c,i(b(a,f,e))};"object"==typeof module&&"object"==typeof module.exports?module.exports=j():"function"==typeof define&&define.amd?define(["jquery"],j):(c=a.WebUploader,a.WebUploader=j(),a.WebUploader.noConflict=function(){a.WebUploader=c})}(window,function(a,b,c){return b("dollar-third",[],function(){var b=a.__dollar||a.jQuery||a.Zepto;if(!b)throw new Error("jQuery or Zepto not found!");return b}),b("dollar",["dollar-third"],function(a){return a}),b("promise-third",["dollar"],function(a){return{Deferred:a.Deferred,when:a.when,isPromise:function(a){return a&&"function"==typeof a.then}}}),b("promise",["promise-third"],function(a){return a}),b("base",["dollar","promise"],function(b,c){function d(a){return function(){return h.apply(a,arguments)}}function e(a,b){return function(){return a.apply(b,arguments)}}function f(a){var b;return Object.create?Object.create(a):(b=function(){},b.prototype=a,new b)}var g=function(){},h=Function.call;return{version:"0.1.5",$:b,Deferred:c.Deferred,isPromise:c.isPromise,when:c.when,browser:function(a){var b={},c=a.match(/WebKit\/([\d.]+)/),d=a.match(/Chrome\/([\d.]+)/)||a.match(/CriOS\/([\d.]+)/),e=a.match(/MSIE\s([\d\.]+)/)||a.match(/(?:trident)(?:.*rv:([\w.]+))?/i),f=a.match(/Firefox\/([\d.]+)/),g=a.match(/Safari\/([\d.]+)/),h=a.match(/OPR\/([\d.]+)/);return c&&(b.webkit=parseFloat(c[1])),d&&(b.chrome=parseFloat(d[1])),e&&(b.ie=parseFloat(e[1])),f&&(b.firefox=parseFloat(f[1])),g&&(b.safari=parseFloat(g[1])),h&&(b.opera=parseFloat(h[1])),b}(navigator.userAgent),os:function(a){var b={},c=a.match(/(?:Android);?[\s\/]+([\d.]+)?/),d=a.match(/(?:iPad|iPod|iPhone).*OS\s([\d_]+)/);return c&&(b.android=parseFloat(c[1])),d&&(b.ios=parseFloat(d[1].replace(/_/g,"."))),b}(navigator.userAgent),inherits:function(a,c,d){var e;return"function"==typeof c?(e=c,c=null):e=c&&c.hasOwnProperty("constructor")?c.constructor:function(){return a.apply(this,arguments)},b.extend(!0,e,a,d||{}),e.__super__=a.prototype,e.prototype=f(a.prototype),c&&b.extend(!0,e.prototype,c),e},noop:g,bindFn:e,log:function(){return a.console?e(console.log,console):g}(),nextTick:function(){return function(a){setTimeout(a,1)}}(),slice:d([].slice),guid:function(){var a=0;return function(b){for(var c=(+new Date).toString(32),d=0;5>d;d++)c+=Math.floor(65535*Math.random()).toString(32);return(b||"wu_")+c+(a++).toString(32)}}(),formatSize:function(a,b,c){var d;for(c=c||["B","K","M","G","TB"];(d=c.shift())&&a>1024;)a/=1024;return("B"===d?a:a.toFixed(b||2))+d}}}),b("mediator",["base"],function(a){function b(a,b,c,d){return f.grep(a,function(a){return!(!a||b&&a.e!==b||c&&a.cb!==c&&a.cb._cb!==c||d&&a.ctx!==d)})}function c(a,b,c){f.each((a||"").split(h),function(a,d){c(d,b)})}function d(a,b){for(var c,d=!1,e=-1,f=a.length;++e1?void(d.isPlainObject(b)&&d.isPlainObject(c[a])?d.extend(c[a],b):c[a]=b):a?c[a]:c},getStats:function(){var a=this.request("get-stats");return a?{successNum:a.numOfSuccess,progressNum:a.numOfProgress,cancelNum:a.numOfCancel,invalidNum:a.numOfInvalid,uploadFailNum:a.numOfUploadFailed,queueNum:a.numOfQueue,interruptNum:a.numofInterrupt}:{}},trigger:function(a){var c=[].slice.call(arguments,1),e=this.options,f="on"+a.substring(0,1).toUpperCase()+a.substring(1);return b.trigger.apply(this,arguments)===!1||d.isFunction(e[f])&&e[f].apply(this,c)===!1||d.isFunction(this[f])&&this[f].apply(this,c)===!1||b.trigger.apply(b,[this,a].concat(c))===!1?!1:!0},destroy:function(){this.request("destroy",arguments),this.off()},request:a.noop}),a.create=c.create=function(a){return new c(a)},a.Uploader=c,c}),b("runtime/runtime",["base","mediator"],function(a,b){function c(b){this.options=d.extend({container:document.body},b),this.uid=a.guid("rt_")}var d=a.$,e={},f=function(a){for(var b in a)if(a.hasOwnProperty(b))return b;return null};return d.extend(c.prototype,{getContainer:function(){var a,b,c=this.options;return this._container?this._container:(a=d(c.container||document.body),b=d(document.createElement("div")),b.attr("id","rt_"+this.uid),b.css({position:"absolute",top:"0px",left:"0px",width:"1px",height:"1px",overflow:"hidden"}),a.append(b),a.addClass("webuploader-container"),this._container=b,this._parent=a,b)},init:a.noop,exec:a.noop,destroy:function(){this._container&&this._container.remove(),this._parent&&this._parent.removeClass("webuploader-container"),this.off()}}),c.orders="html5,flash",c.addRuntime=function(a,b){e[a]=b},c.hasRuntime=function(a){return!!(a?e[a]:f(e))},c.create=function(a,b){var g,h;if(b=b||c.orders,d.each(b.split(/\s*,\s*/g),function(){return e[this]?(g=this,!1):void 0}),g=g||f(e),!g)throw new Error("Runtime Error");return h=new e[g](a)},b.installTo(c.prototype),c}),b("runtime/client",["base","mediator","runtime/runtime"],function(a,b,c){function d(b,d){var f,g=a.Deferred();this.uid=a.guid("client_"),this.runtimeReady=function(a){return g.done(a)},this.connectRuntime=function(b,h){if(f)throw new Error("already connected!");return g.done(h),"string"==typeof b&&e.get(b)&&(f=e.get(b)),f=f||e.get(null,d),f?(a.$.extend(f.options,b),f.__promise.then(g.resolve),f.__client++):(f=c.create(b,b.runtimeOrder),f.__promise=g.promise(),f.once("ready",g.resolve),f.init(),e.add(f),f.__client=1),d&&(f.__standalone=d),f},this.getRuntime=function(){return f},this.disconnectRuntime=function(){f&&(f.__client--,f.__client<=0&&(e.remove(f),delete f.__promise,f.destroy()),f=null)},this.exec=function(){if(f){var c=a.slice(arguments);return b&&c.unshift(b),f.exec.apply(this,c)}},this.getRuid=function(){return f&&f.uid},this.destroy=function(a){return function(){a&&a.apply(this,arguments),this.trigger("destroy"),this.off(),this.exec("destroy"),this.disconnectRuntime()}}(this.destroy)}var e;return e=function(){var a={};return{add:function(b){a[b.uid]=b},get:function(b,c){var d;if(b)return a[b];for(d in a)if(!c||!a[d].__standalone)return a[d];return null},remove:function(b){delete a[b.uid]}}}(),b.installTo(d.prototype),d}),b("lib/dnd",["base","mediator","runtime/client"],function(a,b,c){function d(a){a=this.options=e.extend({},d.options,a),a.container=e(a.container),a.container.length&&c.call(this,"DragAndDrop")}var e=a.$;return d.options={accept:null,disableGlobalDnd:!1},a.inherits(c,{constructor:d,init:function(){var a=this;a.connectRuntime(a.options,function(){a.exec("init"),a.trigger("ready")})}}),b.installTo(d.prototype),d}),b("widgets/widget",["base","uploader"],function(a,b){function c(a){if(!a)return!1;var b=a.length,c=e.type(a);return 1===a.nodeType&&b?!0:"array"===c||"function"!==c&&"string"!==c&&(0===b||"number"==typeof b&&b>0&&b-1 in a)}function d(a){this.owner=a,this.options=a.options}var e=a.$,f=b.prototype._init,g=b.prototype.destroy,h={},i=[];return e.extend(d.prototype,{init:a.noop,invoke:function(a,b){var c=this.responseMap;return c&&a in c&&c[a]in this&&e.isFunction(this[c[a]])?this[c[a]].apply(this,b):h},request:function(){return this.owner.request.apply(this.owner,arguments)}}),e.extend(b.prototype,{_init:function(){var a=this,b=a._widgets=[],c=a.options.disableWidgets||"";return e.each(i,function(d,e){(!c||!~c.indexOf(e._name))&&b.push(new e(a))}),f.apply(a,arguments)},request:function(b,d,e){var f,g,i,j,k=0,l=this._widgets,m=l&&l.length,n=[],o=[];for(d=c(d)?d:[d];m>k;k++)f=l[k],g=f.invoke(b,d),g!==h&&(a.isPromise(g)?o.push(g):n.push(g));return e||o.length?(i=a.when.apply(a,o),j=i.pipe?"pipe":"then",i[j](function(){var b=a.Deferred(),c=arguments;return 1===c.length&&(c=c[0]),setTimeout(function(){b.resolve(c)},1),b.promise()})[e?j:"done"](e||a.noop)):n[0]},destroy:function(){g.apply(this,arguments),this._widgets=null}}),b.register=d.register=function(b,c){var f,g={init:"init",destroy:"destroy",name:"anonymous"};return 1===arguments.length?(c=b,e.each(c,function(a){return"_"===a[0]||"name"===a?void("name"===a&&(g.name=c.name)):void(g[a.replace(/[A-Z]/g,"-$&").toLowerCase()]=a)})):g=e.extend(g,b),c.responseMap=g,f=a.inherits(d,c),f._name=g.name,i.push(f),f},b.unRegister=d.unRegister=function(a){if(a&&"anonymous"!==a)for(var b=i.length;b--;)i[b]._name===a&&i.splice(b,1)},d}),b("widgets/filednd",["base","uploader","lib/dnd","widgets/widget"],function(a,b,c){var d=a.$;return b.options.dnd="",b.register({name:"dnd",init:function(b){if(b.dnd&&"html5"===this.request("predict-runtime-type")){var e,f=this,g=a.Deferred(),h=d.extend({},{disableGlobalDnd:b.disableGlobalDnd,container:b.dnd,accept:b.accept});return this.dnd=e=new c(h),e.once("ready",g.resolve),e.on("drop",function(a){f.request("add-file",[a])}),e.on("accept",function(a){return f.owner.trigger("dndAccept",a)}),e.init(),g.promise()}},destroy:function(){this.dnd&&this.dnd.destroy()}})}),b("lib/filepaste",["base","mediator","runtime/client"],function(a,b,c){function d(a){a=this.options=e.extend({},a),a.container=e(a.container||document.body),c.call(this,"FilePaste")}var e=a.$;return a.inherits(c,{constructor:d,init:function(){var a=this;a.connectRuntime(a.options,function(){a.exec("init"),a.trigger("ready")})}}),b.installTo(d.prototype),d}),b("widgets/filepaste",["base","uploader","lib/filepaste","widgets/widget"],function(a,b,c){var d=a.$;return b.register({name:"paste",init:function(b){if(b.paste&&"html5"===this.request("predict-runtime-type")){var e,f=this,g=a.Deferred(),h=d.extend({},{container:b.paste,accept:b.accept});return this.paste=e=new c(h),e.once("ready",g.resolve),e.on("paste",function(a){f.owner.request("add-file",[a])}),e.init(),g.promise()}},destroy:function(){this.paste&&this.paste.destroy()}})}),b("lib/blob",["base","runtime/client"],function(a,b){function c(a,c){var d=this;d.source=c,d.ruid=a,this.size=c.size||0,this.type=!c.type&&this.ext&&~"jpg,jpeg,png,gif,bmp".indexOf(this.ext)?"image/"+("jpg"===this.ext?"jpeg":this.ext):c.type||"application/octet-stream",b.call(d,"Blob"),this.uid=c.uid||this.uid,a&&d.connectRuntime(a)}return a.inherits(b,{constructor:c,slice:function(a,b){return this.exec("slice",a,b)},getSource:function(){return this.source}}),c}),b("lib/file",["base","lib/blob"],function(a,b){function c(a,c){var f;this.name=c.name||"untitled"+d++,f=e.exec(c.name)?RegExp.$1.toLowerCase():"",!f&&c.type&&(f=/\/(jpg|jpeg|png|gif|bmp)$/i.exec(c.type)?RegExp.$1.toLowerCase():"",this.name+="."+f),this.ext=f,this.lastModifiedDate=c.lastModifiedDate||(new Date).toLocaleString(),b.apply(this,arguments)}var d=1,e=/\.([^.]+)$/;return a.inherits(b,c)}),b("lib/filepicker",["base","runtime/client","lib/file"],function(b,c,d){function e(a){if(a=this.options=f.extend({},e.options,a),a.container=f(a.id),!a.container.length)throw new Error("按钮指定错误");a.innerHTML=a.innerHTML||a.label||a.container.html()||"",a.button=f(a.button||document.createElement("div")),a.button.html(a.innerHTML),a.container.html(a.button),c.call(this,"FilePicker",!0)}var f=b.$;return e.options={button:null,container:null,label:null,innerHTML:null,multiple:!0,accept:null,name:"file"},b.inherits(c,{constructor:e,init:function(){var c=this,e=c.options,g=e.button;g.addClass("webuploader-pick"),c.on("all",function(a){var b;switch(a){case"mouseenter":g.addClass("webuploader-pick-hover");break;case"mouseleave":g.removeClass("webuploader-pick-hover");break;case"change":b=c.exec("getFiles"),c.trigger("select",f.map(b,function(a){return a=new d(c.getRuid(),a),a._refer=e.container,a}),e.container)}}),c.connectRuntime(e,function(){c.refresh(),c.exec("init",e),c.trigger("ready")}),this._resizeHandler=b.bindFn(this.refresh,this),f(a).on("resize",this._resizeHandler)},refresh:function(){var a=this.getRuntime().getContainer(),b=this.options.button,c=b.outerWidth?b.outerWidth():b.width(),d=b.outerHeight?b.outerHeight():b.height(),e=b.offset();c&&d&&a.css({bottom:"auto",right:"auto",width:c+"px",height:d+"px"}).offset(e)},enable:function(){var a=this.options.button;a.removeClass("webuploader-pick-disable"),this.refresh()},disable:function(){var a=this.options.button;this.getRuntime().getContainer().css({top:"-99999px"}),a.addClass("webuploader-pick-disable")},destroy:function(){var b=this.options.button;f(a).off("resize",this._resizeHandler),b.removeClass("webuploader-pick-disable webuploader-pick-hover webuploader-pick")}}),e}),b("widgets/filepicker",["base","uploader","lib/filepicker","widgets/widget"],function(a,b,c){var d=a.$;return d.extend(b.options,{pick:null,accept:null}),b.register({name:"picker",init:function(a){return this.pickers=[],a.pick&&this.addBtn(a.pick)},refresh:function(){d.each(this.pickers,function(){this.refresh()})},addBtn:function(b){var e=this,f=e.options,g=f.accept,h=[];if(b)return d.isPlainObject(b)||(b={id:b}),d(b.id).each(function(){var i,j,k;k=a.Deferred(),i=d.extend({},b,{accept:d.isPlainObject(g)?[g]:g,swf:f.swf,runtimeOrder:f.runtimeOrder,id:this}),j=new c(i),j.once("ready",k.resolve),j.on("select",function(a){e.owner.request("add-file",[a])}),j.init(),e.pickers.push(j),h.push(k.promise())}),a.when.apply(a,h)},disable:function(){d.each(this.pickers,function(){this.disable()})},enable:function(){d.each(this.pickers,function(){this.enable()})},destroy:function(){d.each(this.pickers,function(){this.destroy()}),this.pickers=null}})}),b("lib/image",["base","runtime/client","lib/blob"],function(a,b,c){function d(a){this.options=e.extend({},d.options,a),b.call(this,"Image"),this.on("load",function(){this._info=this.exec("info"),this._meta=this.exec("meta")})}var e=a.$;return d.options={quality:90,crop:!1,preserveHeaders:!1,allowMagnify:!1},a.inherits(b,{constructor:d,info:function(a){return a?(this._info=a,this):this._info},meta:function(a){return a?(this._meta=a,this):this._meta},loadFromBlob:function(a){var b=this,c=a.getRuid();this.connectRuntime(c,function(){b.exec("init",b.options),b.exec("loadFromBlob",a)})},resize:function(){var b=a.slice(arguments);return this.exec.apply(this,["resize"].concat(b))},crop:function(){var b=a.slice(arguments);return this.exec.apply(this,["crop"].concat(b))},getAsDataUrl:function(a){return this.exec("getAsDataUrl",a)},getAsBlob:function(a){var b=this.exec("getAsBlob",a);return new c(this.getRuid(),b)}}),d}),b("widgets/image",["base","uploader","lib/image","widgets/widget"],function(a,b,c){var d,e=a.$;return d=function(a){var b=0,c=[],d=function(){for(var d;c.length&&a>b;)d=c.shift(),b+=d[0],d[1]()};return function(a,e,f){c.push([e,f]),a.once("destroy",function(){b-=e,setTimeout(d,1)}),setTimeout(d,1)}}(5242880),e.extend(b.options,{thumb:{width:110,height:110,quality:70,allowMagnify:!0,crop:!0,preserveHeaders:!1,type:"image/jpeg"},compress:{width:1600,height:1600,quality:90,allowMagnify:!1,crop:!1,preserveHeaders:!0}}),b.register({name:"image",makeThumb:function(a,b,f,g){var h,i;return a=this.request("get-file",a),a.type.match(/^image/)?(h=e.extend({},this.options.thumb),e.isPlainObject(f)&&(h=e.extend(h,f),f=null),f=f||h.width,g=g||h.height,i=new c(h),i.once("load",function(){a._info=a._info||i.info(),a._meta=a._meta||i.meta(),1>=f&&f>0&&(f=a._info.width*f),1>=g&&g>0&&(g=a._info.height*g),i.resize(f,g)}),i.once("complete",function(){b(!1,i.getAsDataUrl(h.type)),i.destroy()}),i.once("error",function(a){b(a||!0),i.destroy()}),void d(i,a.source.size,function(){a._info&&i.info(a._info),a._meta&&i.meta(a._meta),i.loadFromBlob(a.source)})):void b(!0)},beforeSendFile:function(b){var d,f,g=this.options.compress||this.options.resize,h=g&&g.compressSize||0,i=g&&g.noCompressIfLarger||!1;return b=this.request("get-file",b),!g||!~"image/jpeg,image/jpg".indexOf(b.type)||b.size=a&&a>0&&(a=b._info.width*a),1>=c&&c>0&&(c=b._info.height*c),d.resize(a,c)}),d.once("complete",function(){var a,c;try{a=d.getAsBlob(g.type),c=b.size,(!i||a.sizeb;b++)if(c=this._queue[b],a===c.getStatus())return c;return null},sort:function(a){"function"==typeof a&&this._queue.sort(a)},getFiles:function(){for(var a,b=[].slice.call(arguments,0),c=[],d=0,f=this._queue.length;f>d;d++)a=this._queue[d],(!b.length||~e.inArray(a.getStatus(),b))&&c.push(a);return c},removeFile:function(a){var b=this._map[a.id];b&&(delete this._map[a.id],a.destroy(),this.stats.numofDeleted++)},_fileAdded:function(a){var b=this,c=this._map[a.id];c||(this._map[a.id]=a,a.on("statuschange",function(a,c){b._onFileStatusChange(a,c)}))},_onFileStatusChange:function(a,b){var c=this.stats;switch(b){case f.PROGRESS:c.numOfProgress--;break;case f.QUEUED:c.numOfQueue--;break;case f.ERROR:c.numOfUploadFailed--;break;case f.INVALID:c.numOfInvalid--;break;case f.INTERRUPT:c.numofInterrupt--}switch(a){case f.QUEUED:c.numOfQueue++;break;case f.PROGRESS:c.numOfProgress++;break;case f.ERROR:c.numOfUploadFailed++;break;case f.COMPLETE:c.numOfSuccess++;break;case f.CANCELLED:c.numOfCancel++;break;case f.INVALID:c.numOfInvalid++;break;case f.INTERRUPT:c.numofInterrupt++}}}),b.installTo(d.prototype),d}),b("widgets/queue",["base","uploader","queue","file","lib/file","runtime/client","widgets/widget"],function(a,b,c,d,e,f){var g=a.$,h=/\.\w+$/,i=d.Status;return b.register({name:"queue",init:function(b){var d,e,h,i,j,k,l,m=this;if(g.isPlainObject(b.accept)&&(b.accept=[b.accept]),b.accept){for(j=[],h=0,e=b.accept.length;e>h;h++)i=b.accept[h].extensions,i&&j.push(i);j.length&&(k="\\."+j.join(",").replace(/,/g,"$|\\.").replace(/\*/g,".*")+"$"),m.accept=new RegExp(k,"i")}return m.queue=new c,m.stats=m.queue.stats,"html5"===this.request("predict-runtime-type")?(d=a.Deferred(),this.placeholder=l=new f("Placeholder"),l.connectRuntime({runtimeOrder:"html5"},function(){m._ruid=l.getRuid(),d.resolve()}),d.promise()):void 0},_wrapFile:function(a){if(!(a instanceof d)){if(!(a instanceof e)){if(!this._ruid)throw new Error("Can't add external files.");a=new e(this._ruid,a)}a=new d(a)}return a},acceptFile:function(a){var b=!a||!a.size||this.accept&&h.exec(a.name)&&!this.accept.test(a.name);return!b},_addFile:function(a){var b=this;return a=b._wrapFile(a),b.owner.trigger("beforeFileQueued",a)?b.acceptFile(a)?(b.queue.append(a),b.owner.trigger("fileQueued",a),a):void b.owner.trigger("error","Q_TYPE_DENIED",a):void 0},getFile:function(a){return this.queue.getFile(a)},addFile:function(a){var b=this;a.length||(a=[a]),a=g.map(a,function(a){return b._addFile(a)}),b.owner.trigger("filesQueued",a),b.options.auto&&setTimeout(function(){b.request("start-upload")},20)},getStats:function(){return this.stats},removeFile:function(a,b){var c=this;a=a.id?a:c.queue.getFile(a),this.request("cancel-file",a),b&&this.queue.removeFile(a)},getFiles:function(){return this.queue.getFiles.apply(this.queue,arguments)},fetchFile:function(){return this.queue.fetch.apply(this.queue,arguments)},retry:function(a,b){var c,d,e,f=this;if(a)return a=a.id?a:f.queue.getFile(a),a.setStatus(i.QUEUED),void(b||f.request("start-upload"));for(c=f.queue.getFiles(i.ERROR),d=0,e=c.length;e>d;d++)a=c[d],a.setStatus(i.QUEUED);f.request("start-upload")},sortFiles:function(){return this.queue.sort.apply(this.queue,arguments)},reset:function(){this.owner.trigger("reset"),this.queue=new c,this.stats=this.queue.stats},destroy:function(){this.reset(),this.placeholder&&this.placeholder.destroy()}})}),b("widgets/runtime",["uploader","runtime/runtime","widgets/widget"],function(a,b){return a.support=function(){return b.hasRuntime.apply(b,arguments)},a.register({name:"runtime",init:function(){if(!this.predictRuntimeType())throw Error("Runtime Error")},predictRuntimeType:function(){var a,c,d=this.options.runtimeOrder||b.orders,e=this.type;if(!e)for(d=d.split(/\s*,\s*/g),a=0,c=d.length;c>a;a++)if(b.hasRuntime(d[a])){this.type=e=d[a];break}return e}})}),b("lib/transport",["base","runtime/client","mediator"],function(a,b,c){function d(a){var c=this;a=c.options=e.extend(!0,{},d.options,a||{}),b.call(this,"Transport"),this._blob=null,this._formData=a.formData||{},this._headers=a.headers||{},this.on("progress",this._timeout),this.on("load error",function(){c.trigger("progress",1),clearTimeout(c._timer)})}var e=a.$;return d.options={server:"",method:"POST",withCredentials:!1,fileVal:"file",timeout:12e4,formData:{},headers:{},sendAsBinary:!1},e.extend(d.prototype,{appendBlob:function(a,b,c){var d=this,e=d.options;d.getRuid()&&d.disconnectRuntime(),d.connectRuntime(b.ruid,function(){d.exec("init")}),d._blob=b,e.fileVal=a||e.fileVal,e.filename=c||e.filename},append:function(a,b){"object"==typeof a?e.extend(this._formData,a):this._formData[a]=b},setRequestHeader:function(a,b){"object"==typeof a?e.extend(this._headers,a):this._headers[a]=b},send:function(a){this.exec("send",a),this._timeout()},abort:function(){return clearTimeout(this._timer),this.exec("abort")},destroy:function(){this.trigger("destroy"),this.off(),this.exec("destroy"),this.disconnectRuntime()},getResponse:function(){return this.exec("getResponse")},getResponseAsJson:function(){return this.exec("getResponseAsJson")},getStatus:function(){return this.exec("getStatus")},_timeout:function(){var a=this,b=a.options.timeout;b&&(clearTimeout(a._timer),a._timer=setTimeout(function(){a.abort(),a.trigger("error","timeout")},b))}}),c.installTo(d.prototype),d}),b("widgets/upload",["base","uploader","file","lib/transport","widgets/widget"],function(a,b,c,d){function e(a,b){var c,d,e=[],f=a.source,g=f.size,h=b?Math.ceil(g/b):1,i=0,j=0;for(d={file:a,has:function(){return!!e.length},shift:function(){return e.shift()},unshift:function(a){e.unshift(a)}};h>j;)c=Math.min(b,g-i),e.push({file:a,start:i,end:b?i+c:g,total:g,chunks:h,chunk:j++,cuted:d}),i+=c;return a.blocks=e.concat(),a.remaning=e.length,d}var f=a.$,g=a.isPromise,h=c.Status;f.extend(b.options,{prepareNextFile:!1,chunked:!1,chunkSize:5242880,chunkRetry:2,threads:3,formData:{}}),b.register({name:"upload",init:function(){var b=this.owner,c=this;this.runing=!1,this.progress=!1,b.on("startUpload",function(){c.progress=!0}).on("uploadFinished",function(){c.progress=!1}),this.pool=[],this.stack=[],this.pending=[],this.remaning=0,this.__tick=a.bindFn(this._tick,this),b.on("uploadComplete",function(a){a.blocks&&f.each(a.blocks,function(a,b){b.transport&&(b.transport.abort(),b.transport.destroy()),delete b.transport}),delete a.blocks,delete a.remaning})},reset:function(){this.request("stop-upload",!0),this.runing=!1,this.pool=[],this.stack=[],this.pending=[],this.remaning=0,this._trigged=!1,this._promise=null},startUpload:function(b){var c=this;if(f.each(c.request("get-files",h.INVALID),function(){c.request("remove-file",this)}),b)if(b=b.id?b:c.request("get-file",b),b.getStatus()===h.INTERRUPT)f.each(c.pool,function(a,c){c.file===b&&c.transport&&c.transport.send()}),b.setStatus(h.QUEUED);else{if(b.getStatus()===h.PROGRESS)return;b.setStatus(h.QUEUED)}else f.each(c.request("get-files",[h.INITED]),function(){this.setStatus(h.QUEUED)});if(!c.runing){c.runing=!0;var d=[];f.each(c.pool,function(a,b){var e=b.file;e.getStatus()===h.INTERRUPT&&(d.push(e),c._trigged=!1,b.transport&&b.transport.send())});for(var b;b=d.shift();)b.setStatus(h.PROGRESS);b||f.each(c.request("get-files",h.INTERRUPT),function(){this.setStatus(h.PROGRESS)}),c._trigged=!1,a.nextTick(c.__tick),c.owner.trigger("startUpload")}},stopUpload:function(b,c){var d=this;if(b===!0&&(c=b,b=null),d.runing!==!1){if(b){if(b=b.id?b:d.request("get-file",b),b.getStatus()!==h.PROGRESS&&b.getStatus()!==h.QUEUED)return;return b.setStatus(h.INTERRUPT),f.each(d.pool,function(a,c){c.file===b&&(c.transport&&c.transport.abort(),d._putback(c),d._popBlock(c))}),a.nextTick(d.__tick)}d.runing=!1,this._promise&&this._promise.file&&this._promise.file.setStatus(h.INTERRUPT),c&&f.each(d.pool,function(a,b){b.transport&&b.transport.abort(),b.file.setStatus(h.INTERRUPT)}),d.owner.trigger("stopUpload")}},cancelFile:function(a){a=a.id?a:this.request("get-file",a),a.blocks&&f.each(a.blocks,function(a,b){var c=b.transport;c&&(c.abort(),c.destroy(),delete b.transport)}),a.setStatus(h.CANCELLED),this.owner.trigger("fileDequeued",a)},isInProgress:function(){return!!this.progress},_getStats:function(){return this.request("get-stats")},skipFile:function(a,b){a=a.id?a:this.request("get-file",a),a.setStatus(b||h.COMPLETE),a.skipped=!0,a.blocks&&f.each(a.blocks,function(a,b){var c=b.transport;c&&(c.abort(),c.destroy(),delete b.transport)}),this.owner.trigger("uploadSkip",a)},_tick:function(){var b,c,d=this,e=d.options;return d._promise?d._promise.always(d.__tick):void(d.pool.length1&&~"http,abort".indexOf(a)&&b.retried1&&f.extend(m,{chunks:b.chunks,chunk:b.chunk}),i.trigger("uploadBeforeSend",b,m,n),l.appendBlob(j.fileVal,b.blob,k.name),l.append(m),l.setRequestHeader(n),l.send()},_finishFile:function(a,b,c){var d=this.owner;return d.request("after-send-file",arguments,function(){a.setStatus(h.COMPLETE),d.trigger("uploadSuccess",a,b,c)}).fail(function(b){a.getStatus()===h.PROGRESS&&a.setStatus(h.ERROR,b),d.trigger("uploadError",a,b) -}).always(function(){d.trigger("uploadComplete",a)})},updateFileProgress:function(a){var b=0,c=0;a.blocks&&(f.each(a.blocks,function(a,b){c+=(b.percentage||0)*(b.end-b.start)}),b=c/a.size,this.owner.trigger("uploadProgress",a,b||0))}})}),b("widgets/validator",["base","uploader","file","widgets/widget"],function(a,b,c){var d,e=a.$,f={};return d={addValidator:function(a,b){f[a]=b},removeValidator:function(a){delete f[a]}},b.register({name:"validator",init:function(){var b=this;a.nextTick(function(){e.each(f,function(){this.call(b.owner)})})}}),d.addValidator("fileNumLimit",function(){var a=this,b=a.options,c=0,d=parseInt(b.fileNumLimit,10),e=!0;d&&(a.on("beforeFileQueued",function(a){return c>=d&&e&&(e=!1,this.trigger("error","Q_EXCEED_NUM_LIMIT",d,a),setTimeout(function(){e=!0},1)),c>=d?!1:!0}),a.on("fileQueued",function(){c++}),a.on("fileDequeued",function(){c--}),a.on("reset",function(){c=0}))}),d.addValidator("fileSizeLimit",function(){var a=this,b=a.options,c=0,d=parseInt(b.fileSizeLimit,10),e=!0;d&&(a.on("beforeFileQueued",function(a){var b=c+a.size>d;return b&&e&&(e=!1,this.trigger("error","Q_EXCEED_SIZE_LIMIT",d,a),setTimeout(function(){e=!0},1)),b?!1:!0}),a.on("fileQueued",function(a){c+=a.size}),a.on("fileDequeued",function(a){c-=a.size}),a.on("reset",function(){c=0}))}),d.addValidator("fileSingleSizeLimit",function(){var a=this,b=a.options,d=b.fileSingleSizeLimit;d&&a.on("beforeFileQueued",function(a){return a.size>d?(a.setStatus(c.Status.INVALID,"exceed_size"),this.trigger("error","F_EXCEED_SIZE",d,a),!1):void 0})}),d.addValidator("duplicate",function(){function a(a){for(var b,c=0,d=0,e=a.length;e>d;d++)b=a.charCodeAt(d),c=b+(c<<6)+(c<<16)-c;return c}var b=this,c=b.options,d={};c.duplicate||(b.on("beforeFileQueued",function(b){var c=b.__hash||(b.__hash=a(b.name+b.size+b.lastModifiedDate));return d[c]?(this.trigger("error","F_DUPLICATE",b),!1):void 0}),b.on("fileQueued",function(a){var b=a.__hash;b&&(d[b]=!0)}),b.on("fileDequeued",function(a){var b=a.__hash;b&&delete d[b]}),b.on("reset",function(){d={}}))}),d}),b("lib/md5",["runtime/client","mediator"],function(a,b){function c(){a.call(this,"Md5")}return b.installTo(c.prototype),c.prototype.loadFromBlob=function(a){var b=this;b.getRuid()&&b.disconnectRuntime(),b.connectRuntime(a.ruid,function(){b.exec("init"),b.exec("loadFromBlob",a)})},c.prototype.getResult=function(){return this.exec("getResult")},c}),b("widgets/md5",["base","uploader","lib/md5","lib/blob","widgets/widget"],function(a,b,c,d){return b.register({name:"md5",md5File:function(b,e,f){var g=new c,h=a.Deferred(),i=b instanceof d?b:this.request("get-file",b).source;return g.on("progress load",function(a){a=a||{},h.notify(a.total?a.loaded/a.total:1)}),g.on("complete",function(){h.resolve(g.getResult())}),g.on("error",function(a){h.reject(a)}),arguments.length>1&&(e=e||0,f=f||0,0>e&&(e=i.size+e),0>f&&(f=i.size+f),f=Math.min(f,i.size),i=i.slice(e,f)),g.loadFromBlob(i),h.promise()}})}),b("runtime/compbase",[],function(){function a(a,b){this.owner=a,this.options=a.options,this.getRuntime=function(){return b},this.getRuid=function(){return b.uid},this.trigger=function(){return a.trigger.apply(a,arguments)}}return a}),b("runtime/html5/runtime",["base","runtime/runtime","runtime/compbase"],function(b,c,d){function e(){var a={},d=this,e=this.destroy;c.apply(d,arguments),d.type=f,d.exec=function(c,e){var f,h=this,i=h.uid,j=b.slice(arguments,2);return g[c]&&(f=a[i]=a[i]||new g[c](h,d),f[e])?f[e].apply(f,j):void 0},d.destroy=function(){return e&&e.apply(this,arguments)}}var f="html5",g={};return b.inherits(c,{constructor:e,init:function(){var a=this;setTimeout(function(){a.trigger("ready")},1)}}),e.register=function(a,c){var e=g[a]=b.inherits(d,c);return e},a.Blob&&a.FileReader&&a.DataView&&c.addRuntime(f,e),e}),b("runtime/html5/blob",["runtime/html5/runtime","lib/blob"],function(a,b){return a.register("Blob",{slice:function(a,c){var d=this.owner.source,e=d.slice||d.webkitSlice||d.mozSlice;return d=e.call(d,a,c),new b(this.getRuid(),d)}})}),b("runtime/html5/dnd",["base","runtime/html5/runtime","lib/file"],function(a,b,c){var d=a.$,e="webuploader-dnd-";return b.register("DragAndDrop",{init:function(){var b=this.elem=this.options.container;this.dragEnterHandler=a.bindFn(this._dragEnterHandler,this),this.dragOverHandler=a.bindFn(this._dragOverHandler,this),this.dragLeaveHandler=a.bindFn(this._dragLeaveHandler,this),this.dropHandler=a.bindFn(this._dropHandler,this),this.dndOver=!1,b.on("dragenter",this.dragEnterHandler),b.on("dragover",this.dragOverHandler),b.on("dragleave",this.dragLeaveHandler),b.on("drop",this.dropHandler),this.options.disableGlobalDnd&&(d(document).on("dragover",this.dragOverHandler),d(document).on("drop",this.dropHandler))},_dragEnterHandler:function(a){var b,c=this,d=c._denied||!1;return a=a.originalEvent||a,c.dndOver||(c.dndOver=!0,b=a.dataTransfer.items,b&&b.length&&(c._denied=d=!c.trigger("accept",b)),c.elem.addClass(e+"over"),c.elem[d?"addClass":"removeClass"](e+"denied")),a.dataTransfer.dropEffect=d?"none":"copy",!1},_dragOverHandler:function(a){var b=this.elem.parent().get(0);return b&&!d.contains(b,a.currentTarget)?!1:(clearTimeout(this._leaveTimer),this._dragEnterHandler.call(this,a),!1)},_dragLeaveHandler:function(){var a,b=this;return a=function(){b.dndOver=!1,b.elem.removeClass(e+"over "+e+"denied")},clearTimeout(b._leaveTimer),b._leaveTimer=setTimeout(a,100),!1},_dropHandler:function(a){var b,f,g=this,h=g.getRuid(),i=g.elem.parent().get(0);if(i&&!d.contains(i,a.currentTarget))return!1;a=a.originalEvent||a,b=a.dataTransfer;try{f=b.getData("text/html")}catch(j){}return f?void 0:(g._getTansferFiles(b,function(a){g.trigger("drop",d.map(a,function(a){return new c(h,a)}))}),g.dndOver=!1,g.elem.removeClass(e+"over"),!1)},_getTansferFiles:function(b,c){var d,e,f,g,h,i,j,k=[],l=[];for(d=b.items,e=b.files,j=!(!d||!d[0].webkitGetAsEntry),h=0,i=e.length;i>h;h++)f=e[h],g=d&&d[h],j&&g.webkitGetAsEntry().isDirectory?l.push(this._traverseDirectoryTree(g.webkitGetAsEntry(),k)):k.push(f);a.when.apply(a,l).done(function(){k.length&&c(k)})},_traverseDirectoryTree:function(b,c){var d=a.Deferred(),e=this;return b.isFile?b.file(function(a){c.push(a),d.resolve()}):b.isDirectory&&b.createReader().readEntries(function(b){var f,g=b.length,h=[],i=[];for(f=0;g>f;f++)h.push(e._traverseDirectoryTree(b[f],i));a.when.apply(a,h).then(function(){c.push.apply(c,i),d.resolve()},d.reject)}),d.promise()},destroy:function(){var a=this.elem;a&&(a.off("dragenter",this.dragEnterHandler),a.off("dragover",this.dragOverHandler),a.off("dragleave",this.dragLeaveHandler),a.off("drop",this.dropHandler),this.options.disableGlobalDnd&&(d(document).off("dragover",this.dragOverHandler),d(document).off("drop",this.dropHandler)))}})}),b("runtime/html5/filepaste",["base","runtime/html5/runtime","lib/file"],function(a,b,c){return b.register("FilePaste",{init:function(){var b,c,d,e,f=this.options,g=this.elem=f.container,h=".*";if(f.accept){for(b=[],c=0,d=f.accept.length;d>c;c++)e=f.accept[c].mimeTypes,e&&b.push(e);b.length&&(h=b.join(","),h=h.replace(/,/g,"|").replace(/\*/g,".*"))}this.accept=h=new RegExp(h,"i"),this.hander=a.bindFn(this._pasteHander,this),g.on("paste",this.hander)},_pasteHander:function(a){var b,d,e,f,g,h=[],i=this.getRuid();for(a=a.originalEvent||a,b=a.clipboardData.items,f=0,g=b.length;g>f;f++)d=b[f],"file"===d.kind&&(e=d.getAsFile())&&h.push(new c(i,e));h.length&&(a.preventDefault(),a.stopPropagation(),this.trigger("paste",h))},destroy:function(){this.elem.off("paste",this.hander)}})}),b("runtime/html5/filepicker",["base","runtime/html5/runtime"],function(a,b){var c=a.$;return b.register("FilePicker",{init:function(){var a,b,d,e,f=this.getRuntime().getContainer(),g=this,h=g.owner,i=g.options,j=this.label=c(document.createElement("label")),k=this.input=c(document.createElement("input"));if(k.attr("type","file"),k.attr("name",i.name),k.addClass("webuploader-element-invisible"),j.on("click",function(){k.trigger("click")}),j.css({opacity:0,width:"100%",height:"100%",display:"block",cursor:"pointer",background:"#ffffff"}),i.multiple&&k.attr("multiple","multiple"),i.accept&&i.accept.length>0){for(a=[],b=0,d=i.accept.length;d>b;b++)a.push(i.accept[b].mimeTypes);k.attr("accept",a.join(","))}f.append(k),f.append(j),e=function(a){h.trigger(a.type)},k.on("change",function(a){var b,d=arguments.callee;g.files=a.target.files,b=this.cloneNode(!0),b.value=null,this.parentNode.replaceChild(b,this),k.off(),k=c(b).on("change",d).on("mouseenter mouseleave",e),h.trigger("change")}),j.on("mouseenter mouseleave",e)},getFiles:function(){return this.files},destroy:function(){this.input.off(),this.label.off()}})}),b("runtime/html5/util",["base"],function(b){var c=a.createObjectURL&&a||a.URL&&URL.revokeObjectURL&&URL||a.webkitURL,d=b.noop,e=d;return c&&(d=function(){return c.createObjectURL.apply(c,arguments)},e=function(){return c.revokeObjectURL.apply(c,arguments)}),{createObjectURL:d,revokeObjectURL:e,dataURL2Blob:function(a){var b,c,d,e,f,g;for(g=a.split(","),b=~g[0].indexOf("base64")?atob(g[1]):decodeURIComponent(g[1]),d=new ArrayBuffer(b.length),c=new Uint8Array(d),e=0;ei&&(d=h.getUint16(i),d>=65504&&65519>=d||65534===d)&&(e=h.getUint16(i+2)+2,!(i+e>h.byteLength));){if(f=b.parsers[d],!c&&f)for(g=0;g6&&(l.imageHead=a.slice?a.slice(2,k):new Uint8Array(a).subarray(2,k))}return l}},updateImageHead:function(a,b){var c,d,e,f=this._parse(a,!0);return e=2,f.imageHead&&(e=2+f.imageHead.byteLength),d=a.slice?a.slice(e):new Uint8Array(a).subarray(e),c=new Uint8Array(b.byteLength+2+d.byteLength),c[0]=255,c[1]=216,c.set(new Uint8Array(b),2),c.set(new Uint8Array(d),b.byteLength+2),c.buffer}},a.parseMeta=function(){return b.parse.apply(b,arguments)},a.updateImageHead=function(){return b.updateImageHead.apply(b,arguments)},b}),b("runtime/html5/imagemeta/exif",["base","runtime/html5/imagemeta"],function(a,b){var c={};return c.ExifMap=function(){return this},c.ExifMap.prototype.map={Orientation:274},c.ExifMap.prototype.get=function(a){return this[a]||this[this.map[a]]},c.exifTagTypes={1:{getValue:function(a,b){return a.getUint8(b)},size:1},2:{getValue:function(a,b){return String.fromCharCode(a.getUint8(b))},size:1,ascii:!0},3:{getValue:function(a,b,c){return a.getUint16(b,c)},size:2},4:{getValue:function(a,b,c){return a.getUint32(b,c)},size:4},5:{getValue:function(a,b,c){return a.getUint32(b,c)/a.getUint32(b+4,c)},size:8},9:{getValue:function(a,b,c){return a.getInt32(b,c)},size:4},10:{getValue:function(a,b,c){return a.getInt32(b,c)/a.getInt32(b+4,c)},size:8}},c.exifTagTypes[7]=c.exifTagTypes[1],c.getExifValue=function(b,d,e,f,g,h){var i,j,k,l,m,n,o=c.exifTagTypes[f];if(!o)return void a.log("Invalid Exif data: Invalid tag type.");if(i=o.size*g,j=i>4?d+b.getUint32(e+8,h):e+8,j+i>b.byteLength)return void a.log("Invalid Exif data: Invalid data offset.");if(1===g)return o.getValue(b,j,h);for(k=[],l=0;g>l;l+=1)k[l]=o.getValue(b,j+l*o.size,h);if(o.ascii){for(m="",l=0;lb.byteLength)return void a.log("Invalid Exif data: Invalid directory offset.");if(g=b.getUint16(d,e),h=d+2+12*g,h+4>b.byteLength)return void a.log("Invalid Exif data: Invalid directory size.");for(i=0;g>i;i+=1)this.parseExifTag(b,c,d+2+12*i,e,f);return b.getUint32(h,e)},c.parseExifData=function(b,d,e,f){var g,h,i=d+10;if(1165519206===b.getUint32(d+4)){if(i+8>b.byteLength)return void a.log("Invalid Exif data: Invalid segment size.");if(0!==b.getUint16(d+8))return void a.log("Invalid Exif data: Missing byte alignment offset.");switch(b.getUint16(i)){case 18761:g=!0;break;case 19789:g=!1;break;default:return void a.log("Invalid Exif data: Invalid byte alignment marker.")}if(42!==b.getUint16(i+2,g))return void a.log("Invalid Exif data: Missing TIFF marker.");h=b.getUint32(i+4,g),f.exif=new c.ExifMap,h=c.parseExifTags(b,i,i+h,g,f)}},b.parsers[65505].push(c.parseExifData),c}),b("runtime/html5/jpegencoder",[],function(){function a(a){function b(a){for(var b=[16,11,10,16,24,40,51,61,12,12,14,19,26,58,60,55,14,13,16,24,40,57,69,56,14,17,22,29,51,87,80,62,18,22,37,56,68,109,103,77,24,35,55,64,81,104,113,92,49,64,78,87,103,121,120,101,72,92,95,98,112,100,103,99],c=0;64>c;c++){var d=y((b[c]*a+50)/100);1>d?d=1:d>255&&(d=255),z[P[c]]=d}for(var e=[17,18,24,47,99,99,99,99,18,21,26,66,99,99,99,99,24,26,56,99,99,99,99,99,47,66,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99],f=0;64>f;f++){var g=y((e[f]*a+50)/100);1>g?g=1:g>255&&(g=255),A[P[f]]=g}for(var h=[1,1.387039845,1.306562965,1.175875602,1,.785694958,.5411961,.275899379],i=0,j=0;8>j;j++)for(var k=0;8>k;k++)B[i]=1/(z[P[i]]*h[j]*h[k]*8),C[i]=1/(A[P[i]]*h[j]*h[k]*8),i++}function c(a,b){for(var c=0,d=0,e=new Array,f=1;16>=f;f++){for(var g=1;g<=a[f];g++)e[b[d]]=[],e[b[d]][0]=c,e[b[d]][1]=f,d++,c++;c*=2}return e}function d(){t=c(Q,R),u=c(U,V),v=c(S,T),w=c(W,X)}function e(){for(var a=1,b=2,c=1;15>=c;c++){for(var d=a;b>d;d++)E[32767+d]=c,D[32767+d]=[],D[32767+d][1]=c,D[32767+d][0]=d;for(var e=-(b-1);-a>=e;e++)E[32767+e]=c,D[32767+e]=[],D[32767+e][1]=c,D[32767+e][0]=b-1+e;a<<=1,b<<=1}}function f(){for(var a=0;256>a;a++)O[a]=19595*a,O[a+256>>0]=38470*a,O[a+512>>0]=7471*a+32768,O[a+768>>0]=-11059*a,O[a+1024>>0]=-21709*a,O[a+1280>>0]=32768*a+8421375,O[a+1536>>0]=-27439*a,O[a+1792>>0]=-5329*a}function g(a){for(var b=a[0],c=a[1]-1;c>=0;)b&1<J&&(255==I?(h(255),h(0)):h(I),J=7,I=0)}function h(a){H.push(N[a])}function i(a){h(a>>8&255),h(255&a)}function j(a,b){var c,d,e,f,g,h,i,j,k,l=0,m=8,n=64;for(k=0;m>k;++k){c=a[l],d=a[l+1],e=a[l+2],f=a[l+3],g=a[l+4],h=a[l+5],i=a[l+6],j=a[l+7];var o=c+j,p=c-j,q=d+i,r=d-i,s=e+h,t=e-h,u=f+g,v=f-g,w=o+u,x=o-u,y=q+s,z=q-s;a[l]=w+y,a[l+4]=w-y;var A=.707106781*(z+x);a[l+2]=x+A,a[l+6]=x-A,w=v+t,y=t+r,z=r+p;var B=.382683433*(w-z),C=.5411961*w+B,D=1.306562965*z+B,E=.707106781*y,G=p+E,H=p-E;a[l+5]=H+C,a[l+3]=H-C,a[l+1]=G+D,a[l+7]=G-D,l+=8}for(l=0,k=0;m>k;++k){c=a[l],d=a[l+8],e=a[l+16],f=a[l+24],g=a[l+32],h=a[l+40],i=a[l+48],j=a[l+56];var I=c+j,J=c-j,K=d+i,L=d-i,M=e+h,N=e-h,O=f+g,P=f-g,Q=I+O,R=I-O,S=K+M,T=K-M;a[l]=Q+S,a[l+32]=Q-S;var U=.707106781*(T+R);a[l+16]=R+U,a[l+48]=R-U,Q=P+N,S=N+L,T=L+J;var V=.382683433*(Q-T),W=.5411961*Q+V,X=1.306562965*T+V,Y=.707106781*S,Z=J+Y,$=J-Y;a[l+40]=$+W,a[l+24]=$-W,a[l+8]=Z+X,a[l+56]=Z-X,l++}var _;for(k=0;n>k;++k)_=a[k]*b[k],F[k]=_>0?_+.5|0:_-.5|0;return F}function k(){i(65504),i(16),h(74),h(70),h(73),h(70),h(0),h(1),h(1),h(0),i(1),i(1),h(0),h(0)}function l(a,b){i(65472),i(17),h(8),i(b),i(a),h(3),h(1),h(17),h(0),h(2),h(17),h(1),h(3),h(17),h(1)}function m(){i(65499),i(132),h(0);for(var a=0;64>a;a++)h(z[a]);h(1);for(var b=0;64>b;b++)h(A[b])}function n(){i(65476),i(418),h(0);for(var a=0;16>a;a++)h(Q[a+1]);for(var b=0;11>=b;b++)h(R[b]);h(16);for(var c=0;16>c;c++)h(S[c+1]);for(var d=0;161>=d;d++)h(T[d]);h(1);for(var e=0;16>e;e++)h(U[e+1]);for(var f=0;11>=f;f++)h(V[f]);h(17);for(var g=0;16>g;g++)h(W[g+1]);for(var j=0;161>=j;j++)h(X[j])}function o(){i(65498),i(12),h(3),h(1),h(0),h(2),h(17),h(3),h(17),h(0),h(63),h(0)}function p(a,b,c,d,e){for(var f,h=e[0],i=e[240],k=16,l=63,m=64,n=j(a,b),o=0;m>o;++o)G[P[o]]=n[o];var p=G[0]-c;c=G[0],0==p?g(d[0]):(f=32767+p,g(d[E[f]]),g(D[f]));for(var q=63;q>0&&0==G[q];q--);if(0==q)return g(h),c;for(var r,s=1;q>=s;){for(var t=s;0==G[s]&&q>=s;++s);var u=s-t;if(u>=k){r=u>>4;for(var v=1;r>=v;++v)g(i);u=15&u}f=32767+G[s],g(e[(u<<4)+E[f]]),g(D[f]),s++}return q!=l&&g(h),c}function q(){for(var a=String.fromCharCode,b=0;256>b;b++)N[b]=a(b)}function r(a){if(0>=a&&(a=1),a>100&&(a=100),x!=a){var c=0;c=Math.floor(50>a?5e3/a:200-2*a),b(c),x=a}}function s(){a||(a=50),q(),d(),e(),f(),r(a)}var t,u,v,w,x,y=(Math.round,Math.floor),z=new Array(64),A=new Array(64),B=new Array(64),C=new Array(64),D=new Array(65535),E=new Array(65535),F=new Array(64),G=new Array(64),H=[],I=0,J=7,K=new Array(64),L=new Array(64),M=new Array(64),N=new Array(256),O=new Array(2048),P=[0,1,5,6,14,15,27,28,2,4,7,13,16,26,29,42,3,8,12,17,25,30,41,43,9,11,18,24,31,40,44,53,10,19,23,32,39,45,52,54,20,22,33,38,46,51,55,60,21,34,37,47,50,56,59,61,35,36,48,49,57,58,62,63],Q=[0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0],R=[0,1,2,3,4,5,6,7,8,9,10,11],S=[0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,125],T=[1,2,3,0,4,17,5,18,33,49,65,6,19,81,97,7,34,113,20,50,129,145,161,8,35,66,177,193,21,82,209,240,36,51,98,114,130,9,10,22,23,24,25,26,37,38,39,40,41,42,52,53,54,55,56,57,58,67,68,69,70,71,72,73,74,83,84,85,86,87,88,89,90,99,100,101,102,103,104,105,106,115,116,117,118,119,120,121,122,131,132,133,134,135,136,137,138,146,147,148,149,150,151,152,153,154,162,163,164,165,166,167,168,169,170,178,179,180,181,182,183,184,185,186,194,195,196,197,198,199,200,201,202,210,211,212,213,214,215,216,217,218,225,226,227,228,229,230,231,232,233,234,241,242,243,244,245,246,247,248,249,250],U=[0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0],V=[0,1,2,3,4,5,6,7,8,9,10,11],W=[0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,119],X=[0,1,2,3,17,4,5,33,49,6,18,65,81,7,97,113,19,34,50,129,8,20,66,145,161,177,193,9,35,51,82,240,21,98,114,209,10,22,36,52,225,37,241,23,24,25,26,38,39,40,41,42,53,54,55,56,57,58,67,68,69,70,71,72,73,74,83,84,85,86,87,88,89,90,99,100,101,102,103,104,105,106,115,116,117,118,119,120,121,122,130,131,132,133,134,135,136,137,138,146,147,148,149,150,151,152,153,154,162,163,164,165,166,167,168,169,170,178,179,180,181,182,183,184,185,186,194,195,196,197,198,199,200,201,202,210,211,212,213,214,215,216,217,218,226,227,228,229,230,231,232,233,234,242,243,244,245,246,247,248,249,250];this.encode=function(a,b){b&&r(b),H=new Array,I=0,J=7,i(65496),k(),m(),l(a.width,a.height),n(),o();var c=0,d=0,e=0;I=0,J=7,this.encode.displayName="_encode_";for(var f,h,j,q,s,x,y,z,A,D=a.data,E=a.width,F=a.height,G=4*E,N=0;F>N;){for(f=0;G>f;){for(s=G*N+f,x=s,y=-1,z=0,A=0;64>A;A++)z=A>>3,y=4*(7&A),x=s+z*G+y,N+z>=F&&(x-=G*(N+1+z-F)),f+y>=G&&(x-=f+y-G+4),h=D[x++],j=D[x++],q=D[x++],K[A]=(O[h]+O[j+256>>0]+O[q+512>>0]>>16)-128,L[A]=(O[h+768>>0]+O[j+1024>>0]+O[q+1280>>0]>>16)-128,M[A]=(O[h+1280>>0]+O[j+1536>>0]+O[q+1792>>0]>>16)-128;c=p(K,B,c,t,v),d=p(L,C,d,u,w),e=p(M,C,e,u,w),f+=32}N+=8}if(J>=0){var P=[];P[1]=J+1,P[0]=(1<i;)e=d[4*(k-1)+3],0===e?j=k:i=k,k=j+i>>1;return f=k/c,0===f?1:f}function c(a){var b,c,d=a.naturalWidth,e=a.naturalHeight;return d*e>1048576?(b=document.createElement("canvas"),b.width=b.height=1,c=b.getContext("2d"),c.drawImage(a,-d+1,0),0===c.getImageData(0,0,1,1).data[3]):!1}return a.os.ios?a.os.ios>=7?function(a,c,d,e,f,g){var h=c.naturalWidth,i=c.naturalHeight,j=b(c,h,i);return a.getContext("2d").drawImage(c,0,0,h*j,i*j,d,e,f,g)}:function(a,d,e,f,g,h){var i,j,k,l,m,n,o,p=d.naturalWidth,q=d.naturalHeight,r=a.getContext("2d"),s=c(d),t="image/jpeg"===this.type,u=1024,v=0,w=0;for(s&&(p/=2,q/=2),r.save(),i=document.createElement("canvas"),i.width=i.height=u,j=i.getContext("2d"),k=t?b(d,p,q):1,l=Math.ceil(u*g/p),m=Math.ceil(u*h/q/k);q>v;){for(n=0,o=0;p>n;)j.clearRect(0,0,u,u),j.drawImage(d,-n,-v),r.drawImage(i,0,0,u,u,e+o,f+w,l,m),n+=u,o+=l;v+=u,w+=m}r.restore(),i=j=null}:function(b){var c=a.slice(arguments,1),d=b.getContext("2d");d.drawImage.apply(d,c)}}()})}),b("runtime/html5/transport",["base","runtime/html5/runtime"],function(a,b){var c=a.noop,d=a.$;return b.register("Transport",{init:function(){this._status=0,this._response=null},send:function(){var b,c,e,f=this.owner,g=this.options,h=this._initAjax(),i=f._blob,j=g.server;g.sendAsBinary?(j+=(/\?/.test(j)?"&":"?")+d.param(f._formData),c=i.getSource()):(b=new FormData,d.each(f._formData,function(a,c){b.append(a,c)}),b.append(g.fileVal,i.getSource(),g.filename||f._formData.name||"")),g.withCredentials&&"withCredentials"in h?(h.open(g.method,j,!0),h.withCredentials=!0):h.open(g.method,j),this._setRequestHeader(h,g.headers),c?(h.overrideMimeType&&h.overrideMimeType("application/octet-stream"),a.os.android?(e=new FileReader,e.onload=function(){h.send(this.result),e=e.onload=null},e.readAsArrayBuffer(c)):h.send(c)):h.send(b)},getResponse:function(){return this._response},getResponseAsJson:function(){return this._parseJson(this._response)},getStatus:function(){return this._status},abort:function(){var a=this._xhr;a&&(a.upload.onprogress=c,a.onreadystatechange=c,a.abort(),this._xhr=a=null)},destroy:function(){this.abort()},_initAjax:function(){var a=this,b=new XMLHttpRequest,d=this.options;return!d.withCredentials||"withCredentials"in b||"undefined"==typeof XDomainRequest||(b=new XDomainRequest),b.upload.onprogress=function(b){var c=0;return b.lengthComputable&&(c=b.loaded/b.total),a.trigger("progress",c)},b.onreadystatechange=function(){return 4===b.readyState?(b.upload.onprogress=c,b.onreadystatechange=c,a._xhr=null,a._status=b.status,b.status>=200&&b.status<300?(a._response=b.responseText,a.trigger("load")):b.status>=500&&b.status<600?(a._response=b.responseText,a.trigger("error","server")):a.trigger("error",a._status?"http":"abort")):void 0},a._xhr=b,b},_setRequestHeader:function(a,b){d.each(b,function(b,c){a.setRequestHeader(b,c)})},_parseJson:function(a){var b;try{b=JSON.parse(a)}catch(c){b={}}return b}})}),b("runtime/html5/md5",["runtime/html5/runtime"],function(a){var b=function(a,b){return a+b&4294967295},c=function(a,c,d,e,f,g){return c=b(b(c,a),b(e,g)),b(c<>>32-f,d)},d=function(a,b,d,e,f,g,h){return c(b&d|~b&e,a,b,f,g,h)},e=function(a,b,d,e,f,g,h){return c(b&e|d&~e,a,b,f,g,h)},f=function(a,b,d,e,f,g,h){return c(b^d^e,a,b,f,g,h)},g=function(a,b,d,e,f,g,h){return c(d^(b|~e),a,b,f,g,h)},h=function(a,c){var h=a[0],i=a[1],j=a[2],k=a[3];h=d(h,i,j,k,c[0],7,-680876936),k=d(k,h,i,j,c[1],12,-389564586),j=d(j,k,h,i,c[2],17,606105819),i=d(i,j,k,h,c[3],22,-1044525330),h=d(h,i,j,k,c[4],7,-176418897),k=d(k,h,i,j,c[5],12,1200080426),j=d(j,k,h,i,c[6],17,-1473231341),i=d(i,j,k,h,c[7],22,-45705983),h=d(h,i,j,k,c[8],7,1770035416),k=d(k,h,i,j,c[9],12,-1958414417),j=d(j,k,h,i,c[10],17,-42063),i=d(i,j,k,h,c[11],22,-1990404162),h=d(h,i,j,k,c[12],7,1804603682),k=d(k,h,i,j,c[13],12,-40341101),j=d(j,k,h,i,c[14],17,-1502002290),i=d(i,j,k,h,c[15],22,1236535329),h=e(h,i,j,k,c[1],5,-165796510),k=e(k,h,i,j,c[6],9,-1069501632),j=e(j,k,h,i,c[11],14,643717713),i=e(i,j,k,h,c[0],20,-373897302),h=e(h,i,j,k,c[5],5,-701558691),k=e(k,h,i,j,c[10],9,38016083),j=e(j,k,h,i,c[15],14,-660478335),i=e(i,j,k,h,c[4],20,-405537848),h=e(h,i,j,k,c[9],5,568446438),k=e(k,h,i,j,c[14],9,-1019803690),j=e(j,k,h,i,c[3],14,-187363961),i=e(i,j,k,h,c[8],20,1163531501),h=e(h,i,j,k,c[13],5,-1444681467),k=e(k,h,i,j,c[2],9,-51403784),j=e(j,k,h,i,c[7],14,1735328473),i=e(i,j,k,h,c[12],20,-1926607734),h=f(h,i,j,k,c[5],4,-378558),k=f(k,h,i,j,c[8],11,-2022574463),j=f(j,k,h,i,c[11],16,1839030562),i=f(i,j,k,h,c[14],23,-35309556),h=f(h,i,j,k,c[1],4,-1530992060),k=f(k,h,i,j,c[4],11,1272893353),j=f(j,k,h,i,c[7],16,-155497632),i=f(i,j,k,h,c[10],23,-1094730640),h=f(h,i,j,k,c[13],4,681279174),k=f(k,h,i,j,c[0],11,-358537222),j=f(j,k,h,i,c[3],16,-722521979),i=f(i,j,k,h,c[6],23,76029189),h=f(h,i,j,k,c[9],4,-640364487),k=f(k,h,i,j,c[12],11,-421815835),j=f(j,k,h,i,c[15],16,530742520),i=f(i,j,k,h,c[2],23,-995338651),h=g(h,i,j,k,c[0],6,-198630844),k=g(k,h,i,j,c[7],10,1126891415),j=g(j,k,h,i,c[14],15,-1416354905),i=g(i,j,k,h,c[5],21,-57434055),h=g(h,i,j,k,c[12],6,1700485571),k=g(k,h,i,j,c[3],10,-1894986606),j=g(j,k,h,i,c[10],15,-1051523),i=g(i,j,k,h,c[1],21,-2054922799),h=g(h,i,j,k,c[8],6,1873313359),k=g(k,h,i,j,c[15],10,-30611744),j=g(j,k,h,i,c[6],15,-1560198380),i=g(i,j,k,h,c[13],21,1309151649),h=g(h,i,j,k,c[4],6,-145523070),k=g(k,h,i,j,c[11],10,-1120210379),j=g(j,k,h,i,c[2],15,718787259),i=g(i,j,k,h,c[9],21,-343485551),a[0]=b(h,a[0]),a[1]=b(i,a[1]),a[2]=b(j,a[2]),a[3]=b(k,a[3])},i=function(a){var b,c=[];for(b=0;64>b;b+=4)c[b>>2]=a.charCodeAt(b)+(a.charCodeAt(b+1)<<8)+(a.charCodeAt(b+2)<<16)+(a.charCodeAt(b+3)<<24);return c},j=function(a){var b,c=[];for(b=0;64>b;b+=4)c[b>>2]=a[b]+(a[b+1]<<8)+(a[b+2]<<16)+(a[b+3]<<24);return c},k=function(a){var b,c,d,e,f,g,j=a.length,k=[1732584193,-271733879,-1732584194,271733878];for(b=64;j>=b;b+=64)h(k,i(a.substring(b-64,b)));for(a=a.substring(b-64),c=a.length,d=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],b=0;c>b;b+=1)d[b>>2]|=a.charCodeAt(b)<<(b%4<<3);if(d[b>>2]|=128<<(b%4<<3),b>55)for(h(k,d),b=0;16>b;b+=1)d[b]=0;return e=8*j,e=e.toString(16).match(/(.*?)(.{0,8})$/),f=parseInt(e[2],16),g=parseInt(e[1],16)||0,d[14]=f,d[15]=g,h(k,d),k},l=function(a){var b,c,d,e,f,g,i=a.length,k=[1732584193,-271733879,-1732584194,271733878];for(b=64;i>=b;b+=64)h(k,j(a.subarray(b-64,b)));for(a=i>b-64?a.subarray(b-64):new Uint8Array(0),c=a.length,d=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],b=0;c>b;b+=1)d[b>>2]|=a[b]<<(b%4<<3);if(d[b>>2]|=128<<(b%4<<3),b>55)for(h(k,d),b=0;16>b;b+=1)d[b]=0;return e=8*i,e=e.toString(16).match(/(.*?)(.{0,8})$/),f=parseInt(e[2],16),g=parseInt(e[1],16)||0,d[14]=f,d[15]=g,h(k,d),k},m=["0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"],n=function(a){var b,c="";for(b=0;4>b;b+=1)c+=m[a>>8*b+4&15]+m[a>>8*b&15];return c},o=function(a){var b;for(b=0;b>16)+(b>>16)+(c>>16);return d<<16|65535&c}),q.prototype.append=function(a){return/[\u0080-\uFFFF]/.test(a)&&(a=unescape(encodeURIComponent(a))),this.appendBinary(a),this},q.prototype.appendBinary=function(a){this._buff+=a,this._length+=a.length;var b,c=this._buff.length;for(b=64;c>=b;b+=64)h(this._state,i(this._buff.substring(b-64,b)));return this._buff=this._buff.substr(b-64),this},q.prototype.end=function(a){var b,c,d=this._buff,e=d.length,f=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];for(b=0;e>b;b+=1)f[b>>2]|=d.charCodeAt(b)<<(b%4<<3);return this._finish(f,e),c=a?this._state:o(this._state),this.reset(),c},q.prototype._finish=function(a,b){var c,d,e,f=b;if(a[f>>2]|=128<<(f%4<<3),f>55)for(h(this._state,a),f=0;16>f;f+=1)a[f]=0;c=8*this._length,c=c.toString(16).match(/(.*?)(.{0,8})$/),d=parseInt(c[2],16),e=parseInt(c[1],16)||0,a[14]=d,a[15]=e,h(this._state,a)},q.prototype.reset=function(){return this._buff="",this._length=0,this._state=[1732584193,-271733879,-1732584194,271733878],this},q.prototype.destroy=function(){delete this._state,delete this._buff,delete this._length},q.hash=function(a,b){/[\u0080-\uFFFF]/.test(a)&&(a=unescape(encodeURIComponent(a)));var c=k(a);return b?c:o(c)},q.hashBinary=function(a,b){var c=k(a);return b?c:o(c)},q.ArrayBuffer=function(){this.reset()},q.ArrayBuffer.prototype.append=function(a){var b,c=this._concatArrayBuffer(this._buff,a),d=c.length;for(this._length+=a.byteLength,b=64;d>=b;b+=64)h(this._state,j(c.subarray(b-64,b)));return this._buff=d>b-64?c.subarray(b-64):new Uint8Array(0),this},q.ArrayBuffer.prototype.end=function(a){var b,c,d=this._buff,e=d.length,f=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]; -for(b=0;e>b;b+=1)f[b>>2]|=d[b]<<(b%4<<3);return this._finish(f,e),c=a?this._state:o(this._state),this.reset(),c},q.ArrayBuffer.prototype._finish=q.prototype._finish,q.ArrayBuffer.prototype.reset=function(){return this._buff=new Uint8Array(0),this._length=0,this._state=[1732584193,-271733879,-1732584194,271733878],this},q.ArrayBuffer.prototype.destroy=q.prototype.destroy,q.ArrayBuffer.prototype._concatArrayBuffer=function(a,b){var c=a.length,d=new Uint8Array(c+b.byteLength);return d.set(a),d.set(new Uint8Array(b),c),d},q.ArrayBuffer.hash=function(a,b){var c=l(new Uint8Array(a));return b?c:o(c)},a.register("Md5",{init:function(){},loadFromBlob:function(a){var b,c,d=a.getSource(),e=2097152,f=Math.ceil(d.size/e),g=0,h=this.owner,i=new q.ArrayBuffer,j=this,k=d.mozSlice||d.webkitSlice||d.slice;c=new FileReader,(b=function(){var l,m;l=g*e,m=Math.min(l+e,d.size),c.onload=function(b){i.append(b.target.result),h.trigger("progress",{total:a.size,loaded:m})},c.onloadend=function(){c.onloadend=c.onload=null,++g',c.html(a)},getFlash:function(){return this._flash?this._flash:(this._flash=g("#"+this.uid).get(0),this._flash)}}),f.register=function(a,c){return c=i[a]=b.inherits(d,g.extend({flashExec:function(){var a=this.owner,b=this.getRuntime();return b.flashExec.apply(a,arguments)}},c))},e()>=11.4&&c.addRuntime(h,f),f}),b("runtime/flash/filepicker",["base","runtime/flash/runtime"],function(a,b){var c=a.$;return b.register("FilePicker",{init:function(a){var b,d,e=c.extend({},a);for(b=e.accept&&e.accept.length,d=0;b>d;d++)e.accept[d].title||(e.accept[d].title="Files");delete e.button,delete e.id,delete e.container,this.flashExec("FilePicker","init",e)},destroy:function(){this.flashExec("FilePicker","destroy")}})}),b("runtime/flash/image",["runtime/flash/runtime"],function(a){return a.register("Image",{loadFromBlob:function(a){var b=this.owner;b.info()&&this.flashExec("Image","info",b.info()),b.meta()&&this.flashExec("Image","meta",b.meta()),this.flashExec("Image","loadFromBlob",a.uid)}})}),b("runtime/flash/transport",["base","runtime/flash/runtime","runtime/client"],function(b,c,d){var e=b.$;return c.register("Transport",{init:function(){this._status=0,this._response=null,this._responseJson=null},send:function(){var a,b=this.owner,c=this.options,d=this._initAjax(),f=b._blob,g=c.server;d.connectRuntime(f.ruid),c.sendAsBinary?(g+=(/\?/.test(g)?"&":"?")+e.param(b._formData),a=f.uid):(e.each(b._formData,function(a,b){d.exec("append",a,b)}),d.exec("appendBlob",c.fileVal,f.uid,c.filename||b._formData.name||"")),this._setRequestHeader(d,c.headers),d.exec("send",{method:c.method,url:g,forceURLStream:c.forceURLStream,mimeType:"application/octet-stream"},a)},getStatus:function(){return this._status},getResponse:function(){return this._response||""},getResponseAsJson:function(){return this._responseJson},abort:function(){var a=this._xhr;a&&(a.exec("abort"),a.destroy(),this._xhr=a=null)},destroy:function(){this.abort()},_initAjax:function(){var b=this,c=new d("XMLHttpRequest");return c.on("uploadprogress progress",function(a){var c=a.loaded/a.total;return c=Math.min(1,Math.max(0,c)),b.trigger("progress",c)}),c.on("load",function(){var d,e=c.exec("getStatus"),f=!1,g="";return c.off(),b._xhr=null,e>=200&&300>e?f=!0:e>=500&&600>e?(f=!0,g="server"):g="http",f&&(b._response=c.exec("getResponse"),b._response=decodeURIComponent(b._response),d=a.JSON&&a.JSON.parse||function(a){try{return new Function("return "+a).call()}catch(b){return{}}},b._responseJson=b._response?d(b._response):{}),c.destroy(),c=null,g?b.trigger("error",g):b.trigger("load")}),c.on("error",function(){c.off(),b._xhr=null,b.trigger("error","http")}),b._xhr=c,c},_setRequestHeader:function(a,b){e.each(b,function(b,c){a.exec("setRequestHeader",b,c)})}})}),b("runtime/flash/blob",["runtime/flash/runtime","lib/blob"],function(a,b){return a.register("Blob",{slice:function(a,c){var d=this.flashExec("Blob","slice",a,c);return new b(d.uid,d)}})}),b("runtime/flash/md5",["runtime/flash/runtime"],function(a){return a.register("Md5",{init:function(){},loadFromBlob:function(a){return this.flashExec("Md5","loadFromBlob",a.uid)}})}),b("preset/all",["base","widgets/filednd","widgets/filepaste","widgets/filepicker","widgets/image","widgets/queue","widgets/runtime","widgets/upload","widgets/validator","widgets/md5","runtime/html5/blob","runtime/html5/dnd","runtime/html5/filepaste","runtime/html5/filepicker","runtime/html5/imagemeta/exif","runtime/html5/androidpatch","runtime/html5/image","runtime/html5/transport","runtime/html5/md5","runtime/flash/filepicker","runtime/flash/image","runtime/flash/transport","runtime/flash/blob","runtime/flash/md5"],function(a){return a}),b("webuploader",["preset/all"],function(a){return a}),c("webuploader")}); \ No newline at end of file diff --git a/static/plugins/webuploader/webuploader.withoutimage.js b/static/plugins/webuploader/webuploader.withoutimage.js deleted file mode 100644 index 9e6e3a38..00000000 --- a/static/plugins/webuploader/webuploader.withoutimage.js +++ /dev/null @@ -1,4993 +0,0 @@ -/*! WebUploader 0.1.5 */ - - -/** - * @fileOverview 让内部各个部件的代码可以用[amd](https://github.com/amdjs/amdjs-api/wiki/AMD)模块定义方式组织起来。 - * - * AMD API 内部的简单不完全实现,请忽略。只有当WebUploader被合并成一个文件的时候才会引入。 - */ -(function( root, factory ) { - var modules = {}, - - // 内部require, 简单不完全实现。 - // https://github.com/amdjs/amdjs-api/wiki/require - _require = function( deps, callback ) { - var args, len, i; - - // 如果deps不是数组,则直接返回指定module - if ( typeof deps === 'string' ) { - return getModule( deps ); - } else { - args = []; - for( len = deps.length, i = 0; i < len; i++ ) { - args.push( getModule( deps[ i ] ) ); - } - - return callback.apply( null, args ); - } - }, - - // 内部define,暂时不支持不指定id. - _define = function( id, deps, factory ) { - if ( arguments.length === 2 ) { - factory = deps; - deps = null; - } - - _require( deps || [], function() { - setModule( id, factory, arguments ); - }); - }, - - // 设置module, 兼容CommonJs写法。 - setModule = function( id, factory, args ) { - var module = { - exports: factory - }, - returned; - - if ( typeof factory === 'function' ) { - args.length || (args = [ _require, module.exports, module ]); - returned = factory.apply( null, args ); - returned !== undefined && (module.exports = returned); - } - - modules[ id ] = module.exports; - }, - - // 根据id获取module - getModule = function( id ) { - var module = modules[ id ] || root[ id ]; - - if ( !module ) { - throw new Error( '`' + id + '` is undefined' ); - } - - return module; - }, - - // 将所有modules,将路径ids装换成对象。 - exportsTo = function( obj ) { - var key, host, parts, part, last, ucFirst; - - // make the first character upper case. - ucFirst = function( str ) { - return str && (str.charAt( 0 ).toUpperCase() + str.substr( 1 )); - }; - - for ( key in modules ) { - host = obj; - - if ( !modules.hasOwnProperty( key ) ) { - continue; - } - - parts = key.split('/'); - last = ucFirst( parts.pop() ); - - while( (part = ucFirst( parts.shift() )) ) { - host[ part ] = host[ part ] || {}; - host = host[ part ]; - } - - host[ last ] = modules[ key ]; - } - - return obj; - }, - - makeExport = function( dollar ) { - root.__dollar = dollar; - - // exports every module. - return exportsTo( factory( root, _define, _require ) ); - }, - - origin; - - if ( typeof module === 'object' && typeof module.exports === 'object' ) { - - // For CommonJS and CommonJS-like environments where a proper window is present, - module.exports = makeExport(); - } else if ( typeof define === 'function' && define.amd ) { - - // Allow using this built library as an AMD module - // in another project. That other project will only - // see this AMD call, not the internal modules in - // the closure below. - define([ 'jquery' ], makeExport ); - } else { - - // Browser globals case. Just assign the - // result to a property on the global. - origin = root.WebUploader; - root.WebUploader = makeExport(); - root.WebUploader.noConflict = function() { - root.WebUploader = origin; - }; - } -})( window, function( window, define, require ) { - - - /** - * @fileOverview jQuery or Zepto - */ - define('dollar-third',[],function() { - var $ = window.__dollar || window.jQuery || window.Zepto; - - if ( !$ ) { - throw new Error('jQuery or Zepto not found!'); - } - - return $; - }); - /** - * @fileOverview Dom 操作相关 - */ - define('dollar',[ - 'dollar-third' - ], function( _ ) { - return _; - }); - /** - * @fileOverview 使用jQuery的Promise - */ - define('promise-third',[ - 'dollar' - ], function( $ ) { - return { - Deferred: $.Deferred, - when: $.when, - - isPromise: function( anything ) { - return anything && typeof anything.then === 'function'; - } - }; - }); - /** - * @fileOverview Promise/A+ - */ - define('promise',[ - 'promise-third' - ], function( _ ) { - return _; - }); - /** - * @fileOverview 基础类方法。 - */ - - /** - * Web Uploader内部类的详细说明,以下提及的功能类,都可以在`WebUploader`这个变量中访问到。 - * - * As you know, Web Uploader的每个文件都是用过[AMD](https://github.com/amdjs/amdjs-api/wiki/AMD)规范中的`define`组织起来的, 每个Module都会有个module id. - * 默认module id为该文件的路径,而此路径将会转化成名字空间存放在WebUploader中。如: - * - * * module `base`:WebUploader.Base - * * module `file`: WebUploader.File - * * module `lib/dnd`: WebUploader.Lib.Dnd - * * module `runtime/html5/dnd`: WebUploader.Runtime.Html5.Dnd - * - * - * 以下文档中对类的使用可能省略掉了`WebUploader`前缀。 - * @module WebUploader - * @title WebUploader API文档 - */ - define('base',[ - 'dollar', - 'promise' - ], function( $, promise ) { - - var noop = function() {}, - call = Function.call; - - // http://jsperf.com/uncurrythis - // 反科里化 - function uncurryThis( fn ) { - return function() { - return call.apply( fn, arguments ); - }; - } - - function bindFn( fn, context ) { - return function() { - return fn.apply( context, arguments ); - }; - } - - function createObject( proto ) { - var f; - - if ( Object.create ) { - return Object.create( proto ); - } else { - f = function() {}; - f.prototype = proto; - return new f(); - } - } - - - /** - * 基础类,提供一些简单常用的方法。 - * @class Base - */ - return { - - /** - * @property {String} version 当前版本号。 - */ - version: '0.1.5', - - /** - * @property {jQuery|Zepto} $ 引用依赖的jQuery或者Zepto对象。 - */ - $: $, - - Deferred: promise.Deferred, - - isPromise: promise.isPromise, - - when: promise.when, - - /** - * @description 简单的浏览器检查结果。 - * - * * `webkit` webkit版本号,如果浏览器为非webkit内核,此属性为`undefined`。 - * * `chrome` chrome浏览器版本号,如果浏览器为chrome,此属性为`undefined`。 - * * `ie` ie浏览器版本号,如果浏览器为非ie,此属性为`undefined`。**暂不支持ie10+** - * * `firefox` firefox浏览器版本号,如果浏览器为非firefox,此属性为`undefined`。 - * * `safari` safari浏览器版本号,如果浏览器为非safari,此属性为`undefined`。 - * * `opera` opera浏览器版本号,如果浏览器为非opera,此属性为`undefined`。 - * - * @property {Object} [browser] - */ - browser: (function( ua ) { - var ret = {}, - webkit = ua.match( /WebKit\/([\d.]+)/ ), - chrome = ua.match( /Chrome\/([\d.]+)/ ) || - ua.match( /CriOS\/([\d.]+)/ ), - - ie = ua.match( /MSIE\s([\d\.]+)/ ) || - ua.match( /(?:trident)(?:.*rv:([\w.]+))?/i ), - firefox = ua.match( /Firefox\/([\d.]+)/ ), - safari = ua.match( /Safari\/([\d.]+)/ ), - opera = ua.match( /OPR\/([\d.]+)/ ); - - webkit && (ret.webkit = parseFloat( webkit[ 1 ] )); - chrome && (ret.chrome = parseFloat( chrome[ 1 ] )); - ie && (ret.ie = parseFloat( ie[ 1 ] )); - firefox && (ret.firefox = parseFloat( firefox[ 1 ] )); - safari && (ret.safari = parseFloat( safari[ 1 ] )); - opera && (ret.opera = parseFloat( opera[ 1 ] )); - - return ret; - })( navigator.userAgent ), - - /** - * @description 操作系统检查结果。 - * - * * `android` 如果在android浏览器环境下,此值为对应的android版本号,否则为`undefined`。 - * * `ios` 如果在ios浏览器环境下,此值为对应的ios版本号,否则为`undefined`。 - * @property {Object} [os] - */ - os: (function( ua ) { - var ret = {}, - - // osx = !!ua.match( /\(Macintosh\; Intel / ), - android = ua.match( /(?:Android);?[\s\/]+([\d.]+)?/ ), - ios = ua.match( /(?:iPad|iPod|iPhone).*OS\s([\d_]+)/ ); - - // osx && (ret.osx = true); - android && (ret.android = parseFloat( android[ 1 ] )); - ios && (ret.ios = parseFloat( ios[ 1 ].replace( /_/g, '.' ) )); - - return ret; - })( navigator.userAgent ), - - /** - * 实现类与类之间的继承。 - * @method inherits - * @grammar Base.inherits( super ) => child - * @grammar Base.inherits( super, protos ) => child - * @grammar Base.inherits( super, protos, statics ) => child - * @param {Class} super 父类 - * @param {Object | Function} [protos] 子类或者对象。如果对象中包含constructor,子类将是用此属性值。 - * @param {Function} [protos.constructor] 子类构造器,不指定的话将创建个临时的直接执行父类构造器的方法。 - * @param {Object} [statics] 静态属性或方法。 - * @return {Class} 返回子类。 - * @example - * function Person() { - * console.log( 'Super' ); - * } - * Person.prototype.hello = function() { - * console.log( 'hello' ); - * }; - * - * var Manager = Base.inherits( Person, { - * world: function() { - * console.log( 'World' ); - * } - * }); - * - * // 因为没有指定构造器,父类的构造器将会执行。 - * var instance = new Manager(); // => Super - * - * // 继承子父类的方法 - * instance.hello(); // => hello - * instance.world(); // => World - * - * // 子类的__super__属性指向父类 - * console.log( Manager.__super__ === Person ); // => true - */ - inherits: function( Super, protos, staticProtos ) { - var child; - - if ( typeof protos === 'function' ) { - child = protos; - protos = null; - } else if ( protos && protos.hasOwnProperty('constructor') ) { - child = protos.constructor; - } else { - child = function() { - return Super.apply( this, arguments ); - }; - } - - // 复制静态方法 - $.extend( true, child, Super, staticProtos || {} ); - - /* jshint camelcase: false */ - - // 让子类的__super__属性指向父类。 - child.__super__ = Super.prototype; - - // 构建原型,添加原型方法或属性。 - // 暂时用Object.create实现。 - child.prototype = createObject( Super.prototype ); - protos && $.extend( true, child.prototype, protos ); - - return child; - }, - - /** - * 一个不做任何事情的方法。可以用来赋值给默认的callback. - * @method noop - */ - noop: noop, - - /** - * 返回一个新的方法,此方法将已指定的`context`来执行。 - * @grammar Base.bindFn( fn, context ) => Function - * @method bindFn - * @example - * var doSomething = function() { - * console.log( this.name ); - * }, - * obj = { - * name: 'Object Name' - * }, - * aliasFn = Base.bind( doSomething, obj ); - * - * aliasFn(); // => Object Name - * - */ - bindFn: bindFn, - - /** - * 引用Console.log如果存在的话,否则引用一个[空函数noop](#WebUploader:Base.noop)。 - * @grammar Base.log( args... ) => undefined - * @method log - */ - log: (function() { - if ( window.console ) { - return bindFn( console.log, console ); - } - return noop; - })(), - - nextTick: (function() { - - return function( cb ) { - setTimeout( cb, 1 ); - }; - - // @bug 当浏览器不在当前窗口时就停了。 - // var next = window.requestAnimationFrame || - // window.webkitRequestAnimationFrame || - // window.mozRequestAnimationFrame || - // function( cb ) { - // window.setTimeout( cb, 1000 / 60 ); - // }; - - // // fix: Uncaught TypeError: Illegal invocation - // return bindFn( next, window ); - })(), - - /** - * 被[uncurrythis](http://www.2ality.com/2011/11/uncurrying-this.html)的数组slice方法。 - * 将用来将非数组对象转化成数组对象。 - * @grammar Base.slice( target, start[, end] ) => Array - * @method slice - * @example - * function doSomthing() { - * var args = Base.slice( arguments, 1 ); - * console.log( args ); - * } - * - * doSomthing( 'ignored', 'arg2', 'arg3' ); // => Array ["arg2", "arg3"] - */ - slice: uncurryThis( [].slice ), - - /** - * 生成唯一的ID - * @method guid - * @grammar Base.guid() => String - * @grammar Base.guid( prefx ) => String - */ - guid: (function() { - var counter = 0; - - return function( prefix ) { - var guid = (+new Date()).toString( 32 ), - i = 0; - - for ( ; i < 5; i++ ) { - guid += Math.floor( Math.random() * 65535 ).toString( 32 ); - } - - return (prefix || 'wu_') + guid + (counter++).toString( 32 ); - }; - })(), - - /** - * 格式化文件大小, 输出成带单位的字符串 - * @method formatSize - * @grammar Base.formatSize( size ) => String - * @grammar Base.formatSize( size, pointLength ) => String - * @grammar Base.formatSize( size, pointLength, units ) => String - * @param {Number} size 文件大小 - * @param {Number} [pointLength=2] 精确到的小数点数。 - * @param {Array} [units=[ 'B', 'K', 'M', 'G', 'TB' ]] 单位数组。从字节,到千字节,一直往上指定。如果单位数组里面只指定了到了K(千字节),同时文件大小大于M, 此方法的输出将还是显示成多少K. - * @example - * console.log( Base.formatSize( 100 ) ); // => 100B - * console.log( Base.formatSize( 1024 ) ); // => 1.00K - * console.log( Base.formatSize( 1024, 0 ) ); // => 1K - * console.log( Base.formatSize( 1024 * 1024 ) ); // => 1.00M - * console.log( Base.formatSize( 1024 * 1024 * 1024 ) ); // => 1.00G - * console.log( Base.formatSize( 1024 * 1024 * 1024, 0, ['B', 'KB', 'MB'] ) ); // => 1024MB - */ - formatSize: function( size, pointLength, units ) { - var unit; - - units = units || [ 'B', 'K', 'M', 'G', 'TB' ]; - - while ( (unit = units.shift()) && size > 1024 ) { - size = size / 1024; - } - - return (unit === 'B' ? size : size.toFixed( pointLength || 2 )) + - unit; - } - }; - }); - /** - * 事件处理类,可以独立使用,也可以扩展给对象使用。 - * @fileOverview Mediator - */ - define('mediator',[ - 'base' - ], function( Base ) { - var $ = Base.$, - slice = [].slice, - separator = /\s+/, - protos; - - // 根据条件过滤出事件handlers. - function findHandlers( arr, name, callback, context ) { - return $.grep( arr, function( handler ) { - return handler && - (!name || handler.e === name) && - (!callback || handler.cb === callback || - handler.cb._cb === callback) && - (!context || handler.ctx === context); - }); - } - - function eachEvent( events, callback, iterator ) { - // 不支持对象,只支持多个event用空格隔开 - $.each( (events || '').split( separator ), function( _, key ) { - iterator( key, callback ); - }); - } - - function triggerHanders( events, args ) { - var stoped = false, - i = -1, - len = events.length, - handler; - - while ( ++i < len ) { - handler = events[ i ]; - - if ( handler.cb.apply( handler.ctx2, args ) === false ) { - stoped = true; - break; - } - } - - return !stoped; - } - - protos = { - - /** - * 绑定事件。 - * - * `callback`方法在执行时,arguments将会来源于trigger的时候携带的参数。如 - * ```javascript - * var obj = {}; - * - * // 使得obj有事件行为 - * Mediator.installTo( obj ); - * - * obj.on( 'testa', function( arg1, arg2 ) { - * console.log( arg1, arg2 ); // => 'arg1', 'arg2' - * }); - * - * obj.trigger( 'testa', 'arg1', 'arg2' ); - * ``` - * - * 如果`callback`中,某一个方法`return false`了,则后续的其他`callback`都不会被执行到。 - * 切会影响到`trigger`方法的返回值,为`false`。 - * - * `on`还可以用来添加一个特殊事件`all`, 这样所有的事件触发都会响应到。同时此类`callback`中的arguments有一个不同处, - * 就是第一个参数为`type`,记录当前是什么事件在触发。此类`callback`的优先级比脚低,会再正常`callback`执行完后触发。 - * ```javascript - * obj.on( 'all', function( type, arg1, arg2 ) { - * console.log( type, arg1, arg2 ); // => 'testa', 'arg1', 'arg2' - * }); - * ``` - * - * @method on - * @grammar on( name, callback[, context] ) => self - * @param {String} name 事件名,支持多个事件用空格隔开 - * @param {Function} callback 事件处理器 - * @param {Object} [context] 事件处理器的上下文。 - * @return {self} 返回自身,方便链式 - * @chainable - * @class Mediator - */ - on: function( name, callback, context ) { - var me = this, - set; - - if ( !callback ) { - return this; - } - - set = this._events || (this._events = []); - - eachEvent( name, callback, function( name, callback ) { - var handler = { e: name }; - - handler.cb = callback; - handler.ctx = context; - handler.ctx2 = context || me; - handler.id = set.length; - - set.push( handler ); - }); - - return this; - }, - - /** - * 绑定事件,且当handler执行完后,自动解除绑定。 - * @method once - * @grammar once( name, callback[, context] ) => self - * @param {String} name 事件名 - * @param {Function} callback 事件处理器 - * @param {Object} [context] 事件处理器的上下文。 - * @return {self} 返回自身,方便链式 - * @chainable - */ - once: function( name, callback, context ) { - var me = this; - - if ( !callback ) { - return me; - } - - eachEvent( name, callback, function( name, callback ) { - var once = function() { - me.off( name, once ); - return callback.apply( context || me, arguments ); - }; - - once._cb = callback; - me.on( name, once, context ); - }); - - return me; - }, - - /** - * 解除事件绑定 - * @method off - * @grammar off( [name[, callback[, context] ] ] ) => self - * @param {String} [name] 事件名 - * @param {Function} [callback] 事件处理器 - * @param {Object} [context] 事件处理器的上下文。 - * @return {self} 返回自身,方便链式 - * @chainable - */ - off: function( name, cb, ctx ) { - var events = this._events; - - if ( !events ) { - return this; - } - - if ( !name && !cb && !ctx ) { - this._events = []; - return this; - } - - eachEvent( name, cb, function( name, cb ) { - $.each( findHandlers( events, name, cb, ctx ), function() { - delete events[ this.id ]; - }); - }); - - return this; - }, - - /** - * 触发事件 - * @method trigger - * @grammar trigger( name[, args...] ) => self - * @param {String} type 事件名 - * @param {*} [...] 任意参数 - * @return {Boolean} 如果handler中return false了,则返回false, 否则返回true - */ - trigger: function( type ) { - var args, events, allEvents; - - if ( !this._events || !type ) { - return this; - } - - args = slice.call( arguments, 1 ); - events = findHandlers( this._events, type ); - allEvents = findHandlers( this._events, 'all' ); - - return triggerHanders( events, args ) && - triggerHanders( allEvents, arguments ); - } - }; - - /** - * 中介者,它本身是个单例,但可以通过[installTo](#WebUploader:Mediator:installTo)方法,使任何对象具备事件行为。 - * 主要目的是负责模块与模块之间的合作,降低耦合度。 - * - * @class Mediator - */ - return $.extend({ - - /** - * 可以通过这个接口,使任何对象具备事件功能。 - * @method installTo - * @param {Object} obj 需要具备事件行为的对象。 - * @return {Object} 返回obj. - */ - installTo: function( obj ) { - return $.extend( obj, protos ); - } - - }, protos ); - }); - /** - * @fileOverview Uploader上传类 - */ - define('uploader',[ - 'base', - 'mediator' - ], function( Base, Mediator ) { - - var $ = Base.$; - - /** - * 上传入口类。 - * @class Uploader - * @constructor - * @grammar new Uploader( opts ) => Uploader - * @example - * var uploader = WebUploader.Uploader({ - * swf: 'path_of_swf/Uploader.swf', - * - * // 开起分片上传。 - * chunked: true - * }); - */ - function Uploader( opts ) { - this.options = $.extend( true, {}, Uploader.options, opts ); - this._init( this.options ); - } - - // default Options - // widgets中有相应扩展 - Uploader.options = {}; - Mediator.installTo( Uploader.prototype ); - - // 批量添加纯命令式方法。 - $.each({ - upload: 'start-upload', - stop: 'stop-upload', - getFile: 'get-file', - getFiles: 'get-files', - addFile: 'add-file', - addFiles: 'add-file', - sort: 'sort-files', - removeFile: 'remove-file', - cancelFile: 'cancel-file', - skipFile: 'skip-file', - retry: 'retry', - isInProgress: 'is-in-progress', - makeThumb: 'make-thumb', - md5File: 'md5-file', - getDimension: 'get-dimension', - addButton: 'add-btn', - predictRuntimeType: 'predict-runtime-type', - refresh: 'refresh', - disable: 'disable', - enable: 'enable', - reset: 'reset' - }, function( fn, command ) { - Uploader.prototype[ fn ] = function() { - return this.request( command, arguments ); - }; - }); - - $.extend( Uploader.prototype, { - state: 'pending', - - _init: function( opts ) { - var me = this; - - me.request( 'init', opts, function() { - me.state = 'ready'; - me.trigger('ready'); - }); - }, - - /** - * 获取或者设置Uploader配置项。 - * @method option - * @grammar option( key ) => * - * @grammar option( key, val ) => self - * @example - * - * // 初始状态图片上传前不会压缩 - * var uploader = new WebUploader.Uploader({ - * compress: null; - * }); - * - * // 修改后图片上传前,尝试将图片压缩到1600 * 1600 - * uploader.option( 'compress', { - * width: 1600, - * height: 1600 - * }); - */ - option: function( key, val ) { - var opts = this.options; - - // setter - if ( arguments.length > 1 ) { - - if ( $.isPlainObject( val ) && - $.isPlainObject( opts[ key ] ) ) { - $.extend( opts[ key ], val ); - } else { - opts[ key ] = val; - } - - } else { // getter - return key ? opts[ key ] : opts; - } - }, - - /** - * 获取文件统计信息。返回一个包含一下信息的对象。 - * * `successNum` 上传成功的文件数 - * * `progressNum` 上传中的文件数 - * * `cancelNum` 被删除的文件数 - * * `invalidNum` 无效的文件数 - * * `uploadFailNum` 上传失败的文件数 - * * `queueNum` 还在队列中的文件数 - * * `interruptNum` 被暂停的文件数 - * @method getStats - * @grammar getStats() => Object - */ - getStats: function() { - // return this._mgr.getStats.apply( this._mgr, arguments ); - var stats = this.request('get-stats'); - - return stats ? { - successNum: stats.numOfSuccess, - progressNum: stats.numOfProgress, - - // who care? - // queueFailNum: 0, - cancelNum: stats.numOfCancel, - invalidNum: stats.numOfInvalid, - uploadFailNum: stats.numOfUploadFailed, - queueNum: stats.numOfQueue, - interruptNum: stats.numofInterrupt - } : {}; - }, - - // 需要重写此方法来来支持opts.onEvent和instance.onEvent的处理器 - trigger: function( type/*, args...*/ ) { - var args = [].slice.call( arguments, 1 ), - opts = this.options, - name = 'on' + type.substring( 0, 1 ).toUpperCase() + - type.substring( 1 ); - - if ( - // 调用通过on方法注册的handler. - Mediator.trigger.apply( this, arguments ) === false || - - // 调用opts.onEvent - $.isFunction( opts[ name ] ) && - opts[ name ].apply( this, args ) === false || - - // 调用this.onEvent - $.isFunction( this[ name ] ) && - this[ name ].apply( this, args ) === false || - - // 广播所有uploader的事件。 - Mediator.trigger.apply( Mediator, - [ this, type ].concat( args ) ) === false ) { - - return false; - } - - return true; - }, - - /** - * 销毁 webuploader 实例 - * @method destroy - * @grammar destroy() => undefined - */ - destroy: function() { - this.request( 'destroy', arguments ); - this.off(); - }, - - // widgets/widget.js将补充此方法的详细文档。 - request: Base.noop - }); - - /** - * 创建Uploader实例,等同于new Uploader( opts ); - * @method create - * @class Base - * @static - * @grammar Base.create( opts ) => Uploader - */ - Base.create = Uploader.create = function( opts ) { - return new Uploader( opts ); - }; - - // 暴露Uploader,可以通过它来扩展业务逻辑。 - Base.Uploader = Uploader; - - return Uploader; - }); - /** - * @fileOverview Runtime管理器,负责Runtime的选择, 连接 - */ - define('runtime/runtime',[ - 'base', - 'mediator' - ], function( Base, Mediator ) { - - var $ = Base.$, - factories = {}, - - // 获取对象的第一个key - getFirstKey = function( obj ) { - for ( var key in obj ) { - if ( obj.hasOwnProperty( key ) ) { - return key; - } - } - return null; - }; - - // 接口类。 - function Runtime( options ) { - this.options = $.extend({ - container: document.body - }, options ); - this.uid = Base.guid('rt_'); - } - - $.extend( Runtime.prototype, { - - getContainer: function() { - var opts = this.options, - parent, container; - - if ( this._container ) { - return this._container; - } - - parent = $( opts.container || document.body ); - container = $( document.createElement('div') ); - - container.attr( 'id', 'rt_' + this.uid ); - container.css({ - position: 'absolute', - top: '0px', - left: '0px', - width: '1px', - height: '1px', - overflow: 'hidden' - }); - - parent.append( container ); - parent.addClass('webuploader-container'); - this._container = container; - this._parent = parent; - return container; - }, - - init: Base.noop, - exec: Base.noop, - - destroy: function() { - this._container && this._container.remove(); - this._parent && this._parent.removeClass('webuploader-container'); - this.off(); - } - }); - - Runtime.orders = 'html5,flash'; - - - /** - * 添加Runtime实现。 - * @param {String} type 类型 - * @param {Runtime} factory 具体Runtime实现。 - */ - Runtime.addRuntime = function( type, factory ) { - factories[ type ] = factory; - }; - - Runtime.hasRuntime = function( type ) { - return !!(type ? factories[ type ] : getFirstKey( factories )); - }; - - Runtime.create = function( opts, orders ) { - var type, runtime; - - orders = orders || Runtime.orders; - $.each( orders.split( /\s*,\s*/g ), function() { - if ( factories[ this ] ) { - type = this; - return false; - } - }); - - type = type || getFirstKey( factories ); - - if ( !type ) { - throw new Error('Runtime Error'); - } - - runtime = new factories[ type ]( opts ); - return runtime; - }; - - Mediator.installTo( Runtime.prototype ); - return Runtime; - }); - - /** - * @fileOverview Runtime管理器,负责Runtime的选择, 连接 - */ - define('runtime/client',[ - 'base', - 'mediator', - 'runtime/runtime' - ], function( Base, Mediator, Runtime ) { - - var cache; - - cache = (function() { - var obj = {}; - - return { - add: function( runtime ) { - obj[ runtime.uid ] = runtime; - }, - - get: function( ruid, standalone ) { - var i; - - if ( ruid ) { - return obj[ ruid ]; - } - - for ( i in obj ) { - // 有些类型不能重用,比如filepicker. - if ( standalone && obj[ i ].__standalone ) { - continue; - } - - return obj[ i ]; - } - - return null; - }, - - remove: function( runtime ) { - delete obj[ runtime.uid ]; - } - }; - })(); - - function RuntimeClient( component, standalone ) { - var deferred = Base.Deferred(), - runtime; - - this.uid = Base.guid('client_'); - - // 允许runtime没有初始化之前,注册一些方法在初始化后执行。 - this.runtimeReady = function( cb ) { - return deferred.done( cb ); - }; - - this.connectRuntime = function( opts, cb ) { - - // already connected. - if ( runtime ) { - throw new Error('already connected!'); - } - - deferred.done( cb ); - - if ( typeof opts === 'string' && cache.get( opts ) ) { - runtime = cache.get( opts ); - } - - // 像filePicker只能独立存在,不能公用。 - runtime = runtime || cache.get( null, standalone ); - - // 需要创建 - if ( !runtime ) { - runtime = Runtime.create( opts, opts.runtimeOrder ); - runtime.__promise = deferred.promise(); - runtime.once( 'ready', deferred.resolve ); - runtime.init(); - cache.add( runtime ); - runtime.__client = 1; - } else { - // 来自cache - Base.$.extend( runtime.options, opts ); - runtime.__promise.then( deferred.resolve ); - runtime.__client++; - } - - standalone && (runtime.__standalone = standalone); - return runtime; - }; - - this.getRuntime = function() { - return runtime; - }; - - this.disconnectRuntime = function() { - if ( !runtime ) { - return; - } - - runtime.__client--; - - if ( runtime.__client <= 0 ) { - cache.remove( runtime ); - delete runtime.__promise; - runtime.destroy(); - } - - runtime = null; - }; - - this.exec = function() { - if ( !runtime ) { - return; - } - - var args = Base.slice( arguments ); - component && args.unshift( component ); - - return runtime.exec.apply( this, args ); - }; - - this.getRuid = function() { - return runtime && runtime.uid; - }; - - this.destroy = (function( destroy ) { - return function() { - destroy && destroy.apply( this, arguments ); - this.trigger('destroy'); - this.off(); - this.exec('destroy'); - this.disconnectRuntime(); - }; - })( this.destroy ); - } - - Mediator.installTo( RuntimeClient.prototype ); - return RuntimeClient; - }); - /** - * @fileOverview 错误信息 - */ - define('lib/dnd',[ - 'base', - 'mediator', - 'runtime/client' - ], function( Base, Mediator, RuntimeClent ) { - - var $ = Base.$; - - function DragAndDrop( opts ) { - opts = this.options = $.extend({}, DragAndDrop.options, opts ); - - opts.container = $( opts.container ); - - if ( !opts.container.length ) { - return; - } - - RuntimeClent.call( this, 'DragAndDrop' ); - } - - DragAndDrop.options = { - accept: null, - disableGlobalDnd: false - }; - - Base.inherits( RuntimeClent, { - constructor: DragAndDrop, - - init: function() { - var me = this; - - me.connectRuntime( me.options, function() { - me.exec('init'); - me.trigger('ready'); - }); - } - }); - - Mediator.installTo( DragAndDrop.prototype ); - - return DragAndDrop; - }); - /** - * @fileOverview 组件基类。 - */ - define('widgets/widget',[ - 'base', - 'uploader' - ], function( Base, Uploader ) { - - var $ = Base.$, - _init = Uploader.prototype._init, - _destroy = Uploader.prototype.destroy, - IGNORE = {}, - widgetClass = []; - - function isArrayLike( obj ) { - if ( !obj ) { - return false; - } - - var length = obj.length, - type = $.type( obj ); - - if ( obj.nodeType === 1 && length ) { - return true; - } - - return type === 'array' || type !== 'function' && type !== 'string' && - (length === 0 || typeof length === 'number' && length > 0 && - (length - 1) in obj); - } - - function Widget( uploader ) { - this.owner = uploader; - this.options = uploader.options; - } - - $.extend( Widget.prototype, { - - init: Base.noop, - - // 类Backbone的事件监听声明,监听uploader实例上的事件 - // widget直接无法监听事件,事件只能通过uploader来传递 - invoke: function( apiName, args ) { - - /* - { - 'make-thumb': 'makeThumb' - } - */ - var map = this.responseMap; - - // 如果无API响应声明则忽略 - if ( !map || !(apiName in map) || !(map[ apiName ] in this) || - !$.isFunction( this[ map[ apiName ] ] ) ) { - - return IGNORE; - } - - return this[ map[ apiName ] ].apply( this, args ); - - }, - - /** - * 发送命令。当传入`callback`或者`handler`中返回`promise`时。返回一个当所有`handler`中的promise都完成后完成的新`promise`。 - * @method request - * @grammar request( command, args ) => * | Promise - * @grammar request( command, args, callback ) => Promise - * @for Uploader - */ - request: function() { - return this.owner.request.apply( this.owner, arguments ); - } - }); - - // 扩展Uploader. - $.extend( Uploader.prototype, { - - /** - * @property {String | Array} [disableWidgets=undefined] - * @namespace options - * @for Uploader - * @description 默认所有 Uploader.register 了的 widget 都会被加载,如果禁用某一部分,请通过此 option 指定黑名单。 - */ - - // 覆写_init用来初始化widgets - _init: function() { - var me = this, - widgets = me._widgets = [], - deactives = me.options.disableWidgets || ''; - - $.each( widgetClass, function( _, klass ) { - (!deactives || !~deactives.indexOf( klass._name )) && - widgets.push( new klass( me ) ); - }); - - return _init.apply( me, arguments ); - }, - - request: function( apiName, args, callback ) { - var i = 0, - widgets = this._widgets, - len = widgets && widgets.length, - rlts = [], - dfds = [], - widget, rlt, promise, key; - - args = isArrayLike( args ) ? args : [ args ]; - - for ( ; i < len; i++ ) { - widget = widgets[ i ]; - rlt = widget.invoke( apiName, args ); - - if ( rlt !== IGNORE ) { - - // Deferred对象 - if ( Base.isPromise( rlt ) ) { - dfds.push( rlt ); - } else { - rlts.push( rlt ); - } - } - } - - // 如果有callback,则用异步方式。 - if ( callback || dfds.length ) { - promise = Base.when.apply( Base, dfds ); - key = promise.pipe ? 'pipe' : 'then'; - - // 很重要不能删除。删除了会死循环。 - // 保证执行顺序。让callback总是在下一个 tick 中执行。 - return promise[ key ](function() { - var deferred = Base.Deferred(), - args = arguments; - - if ( args.length === 1 ) { - args = args[ 0 ]; - } - - setTimeout(function() { - deferred.resolve( args ); - }, 1 ); - - return deferred.promise(); - })[ callback ? key : 'done' ]( callback || Base.noop ); - } else { - return rlts[ 0 ]; - } - }, - - destroy: function() { - _destroy.apply( this, arguments ); - this._widgets = null; - } - }); - - /** - * 添加组件 - * @grammar Uploader.register(proto); - * @grammar Uploader.register(map, proto); - * @param {object} responseMap API 名称与函数实现的映射 - * @param {object} proto 组件原型,构造函数通过 constructor 属性定义 - * @method Uploader.register - * @for Uploader - * @example - * Uploader.register({ - * 'make-thumb': 'makeThumb' - * }, { - * init: function( options ) {}, - * makeThumb: function() {} - * }); - * - * Uploader.register({ - * 'make-thumb': function() { - * - * } - * }); - */ - Uploader.register = Widget.register = function( responseMap, widgetProto ) { - var map = { init: 'init', destroy: 'destroy', name: 'anonymous' }, - klass; - - if ( arguments.length === 1 ) { - widgetProto = responseMap; - - // 自动生成 map 表。 - $.each(widgetProto, function(key) { - if ( key[0] === '_' || key === 'name' ) { - key === 'name' && (map.name = widgetProto.name); - return; - } - - map[key.replace(/[A-Z]/g, '-$&').toLowerCase()] = key; - }); - - } else { - map = $.extend( map, responseMap ); - } - - widgetProto.responseMap = map; - klass = Base.inherits( Widget, widgetProto ); - klass._name = map.name; - widgetClass.push( klass ); - - return klass; - }; - - /** - * 删除插件,只有在注册时指定了名字的才能被删除。 - * @grammar Uploader.unRegister(name); - * @param {string} name 组件名字 - * @method Uploader.unRegister - * @for Uploader - * @example - * - * Uploader.register({ - * name: 'custom', - * - * 'make-thumb': function() { - * - * } - * }); - * - * Uploader.unRegister('custom'); - */ - Uploader.unRegister = Widget.unRegister = function( name ) { - if ( !name || name === 'anonymous' ) { - return; - } - - // 删除指定的插件。 - for ( var i = widgetClass.length; i--; ) { - if ( widgetClass[i]._name === name ) { - widgetClass.splice(i, 1) - } - } - }; - - return Widget; - }); - /** - * @fileOverview DragAndDrop Widget。 - */ - define('widgets/filednd',[ - 'base', - 'uploader', - 'lib/dnd', - 'widgets/widget' - ], function( Base, Uploader, Dnd ) { - var $ = Base.$; - - Uploader.options.dnd = ''; - - /** - * @property {Selector} [dnd=undefined] 指定Drag And Drop拖拽的容器,如果不指定,则不启动。 - * @namespace options - * @for Uploader - */ - - /** - * @property {Selector} [disableGlobalDnd=false] 是否禁掉整个页面的拖拽功能,如果不禁用,图片拖进来的时候会默认被浏览器打开。 - * @namespace options - * @for Uploader - */ - - /** - * @event dndAccept - * @param {DataTransferItemList} items DataTransferItem - * @description 阻止此事件可以拒绝某些类型的文件拖入进来。目前只有 chrome 提供这样的 API,且只能通过 mime-type 验证。 - * @for Uploader - */ - return Uploader.register({ - name: 'dnd', - - init: function( opts ) { - - if ( !opts.dnd || - this.request('predict-runtime-type') !== 'html5' ) { - return; - } - - var me = this, - deferred = Base.Deferred(), - options = $.extend({}, { - disableGlobalDnd: opts.disableGlobalDnd, - container: opts.dnd, - accept: opts.accept - }), - dnd; - - this.dnd = dnd = new Dnd( options ); - - dnd.once( 'ready', deferred.resolve ); - dnd.on( 'drop', function( files ) { - me.request( 'add-file', [ files ]); - }); - - // 检测文件是否全部允许添加。 - dnd.on( 'accept', function( items ) { - return me.owner.trigger( 'dndAccept', items ); - }); - - dnd.init(); - - return deferred.promise(); - }, - - destroy: function() { - this.dnd && this.dnd.destroy(); - } - }); - }); - - /** - * @fileOverview 错误信息 - */ - define('lib/filepaste',[ - 'base', - 'mediator', - 'runtime/client' - ], function( Base, Mediator, RuntimeClent ) { - - var $ = Base.$; - - function FilePaste( opts ) { - opts = this.options = $.extend({}, opts ); - opts.container = $( opts.container || document.body ); - RuntimeClent.call( this, 'FilePaste' ); - } - - Base.inherits( RuntimeClent, { - constructor: FilePaste, - - init: function() { - var me = this; - - me.connectRuntime( me.options, function() { - me.exec('init'); - me.trigger('ready'); - }); - } - }); - - Mediator.installTo( FilePaste.prototype ); - - return FilePaste; - }); - /** - * @fileOverview 组件基类。 - */ - define('widgets/filepaste',[ - 'base', - 'uploader', - 'lib/filepaste', - 'widgets/widget' - ], function( Base, Uploader, FilePaste ) { - var $ = Base.$; - - /** - * @property {Selector} [paste=undefined] 指定监听paste事件的容器,如果不指定,不启用此功能。此功能为通过粘贴来添加截屏的图片。建议设置为`document.body`. - * @namespace options - * @for Uploader - */ - return Uploader.register({ - name: 'paste', - - init: function( opts ) { - - if ( !opts.paste || - this.request('predict-runtime-type') !== 'html5' ) { - return; - } - - var me = this, - deferred = Base.Deferred(), - options = $.extend({}, { - container: opts.paste, - accept: opts.accept - }), - paste; - - this.paste = paste = new FilePaste( options ); - - paste.once( 'ready', deferred.resolve ); - paste.on( 'paste', function( files ) { - me.owner.request( 'add-file', [ files ]); - }); - paste.init(); - - return deferred.promise(); - }, - - destroy: function() { - this.paste && this.paste.destroy(); - } - }); - }); - /** - * @fileOverview Blob - */ - define('lib/blob',[ - 'base', - 'runtime/client' - ], function( Base, RuntimeClient ) { - - function Blob( ruid, source ) { - var me = this; - - me.source = source; - me.ruid = ruid; - this.size = source.size || 0; - - // 如果没有指定 mimetype, 但是知道文件后缀。 - if ( !source.type && this.ext && - ~'jpg,jpeg,png,gif,bmp'.indexOf( this.ext ) ) { - this.type = 'image/' + (this.ext === 'jpg' ? 'jpeg' : this.ext); - } else { - this.type = source.type || 'application/octet-stream'; - } - - RuntimeClient.call( me, 'Blob' ); - this.uid = source.uid || this.uid; - - if ( ruid ) { - me.connectRuntime( ruid ); - } - } - - Base.inherits( RuntimeClient, { - constructor: Blob, - - slice: function( start, end ) { - return this.exec( 'slice', start, end ); - }, - - getSource: function() { - return this.source; - } - }); - - return Blob; - }); - /** - * 为了统一化Flash的File和HTML5的File而存在。 - * 以至于要调用Flash里面的File,也可以像调用HTML5版本的File一下。 - * @fileOverview File - */ - define('lib/file',[ - 'base', - 'lib/blob' - ], function( Base, Blob ) { - - var uid = 1, - rExt = /\.([^.]+)$/; - - function File( ruid, file ) { - var ext; - - this.name = file.name || ('untitled' + uid++); - ext = rExt.exec( file.name ) ? RegExp.$1.toLowerCase() : ''; - - // todo 支持其他类型文件的转换。 - // 如果有 mimetype, 但是文件名里面没有找出后缀规律 - if ( !ext && file.type ) { - ext = /\/(jpg|jpeg|png|gif|bmp)$/i.exec( file.type ) ? - RegExp.$1.toLowerCase() : ''; - this.name += '.' + ext; - } - - this.ext = ext; - this.lastModifiedDate = file.lastModifiedDate || - (new Date()).toLocaleString(); - - Blob.apply( this, arguments ); - } - - return Base.inherits( Blob, File ); - }); - - /** - * @fileOverview 错误信息 - */ - define('lib/filepicker',[ - 'base', - 'runtime/client', - 'lib/file' - ], function( Base, RuntimeClent, File ) { - - var $ = Base.$; - - function FilePicker( opts ) { - opts = this.options = $.extend({}, FilePicker.options, opts ); - - opts.container = $( opts.id ); - - if ( !opts.container.length ) { - throw new Error('按钮指定错误'); - } - - opts.innerHTML = opts.innerHTML || opts.label || - opts.container.html() || ''; - - opts.button = $( opts.button || document.createElement('div') ); - opts.button.html( opts.innerHTML ); - opts.container.html( opts.button ); - - RuntimeClent.call( this, 'FilePicker', true ); - } - - FilePicker.options = { - button: null, - container: null, - label: null, - innerHTML: null, - multiple: true, - accept: null, - name: 'file' - }; - - Base.inherits( RuntimeClent, { - constructor: FilePicker, - - init: function() { - var me = this, - opts = me.options, - button = opts.button; - - button.addClass('webuploader-pick'); - - me.on( 'all', function( type ) { - var files; - - switch ( type ) { - case 'mouseenter': - button.addClass('webuploader-pick-hover'); - break; - - case 'mouseleave': - button.removeClass('webuploader-pick-hover'); - break; - - case 'change': - files = me.exec('getFiles'); - me.trigger( 'select', $.map( files, function( file ) { - file = new File( me.getRuid(), file ); - - // 记录来源。 - file._refer = opts.container; - return file; - }), opts.container ); - break; - } - }); - - me.connectRuntime( opts, function() { - me.refresh(); - me.exec( 'init', opts ); - me.trigger('ready'); - }); - - this._resizeHandler = Base.bindFn( this.refresh, this ); - $( window ).on( 'resize', this._resizeHandler ); - }, - - refresh: function() { - var shimContainer = this.getRuntime().getContainer(), - button = this.options.button, - width = button.outerWidth ? - button.outerWidth() : button.width(), - - height = button.outerHeight ? - button.outerHeight() : button.height(), - - pos = button.offset(); - - width && height && shimContainer.css({ - bottom: 'auto', - right: 'auto', - width: width + 'px', - height: height + 'px' - }).offset( pos ); - }, - - enable: function() { - var btn = this.options.button; - - btn.removeClass('webuploader-pick-disable'); - this.refresh(); - }, - - disable: function() { - var btn = this.options.button; - - this.getRuntime().getContainer().css({ - top: '-99999px' - }); - - btn.addClass('webuploader-pick-disable'); - }, - - destroy: function() { - var btn = this.options.button; - $( window ).off( 'resize', this._resizeHandler ); - btn.removeClass('webuploader-pick-disable webuploader-pick-hover ' + - 'webuploader-pick'); - } - }); - - return FilePicker; - }); - - /** - * @fileOverview 文件选择相关 - */ - define('widgets/filepicker',[ - 'base', - 'uploader', - 'lib/filepicker', - 'widgets/widget' - ], function( Base, Uploader, FilePicker ) { - var $ = Base.$; - - $.extend( Uploader.options, { - - /** - * @property {Selector | Object} [pick=undefined] - * @namespace options - * @for Uploader - * @description 指定选择文件的按钮容器,不指定则不创建按钮。 - * - * * `id` {Seletor} 指定选择文件的按钮容器,不指定则不创建按钮。 - * * `label` {String} 请采用 `innerHTML` 代替 - * * `innerHTML` {String} 指定按钮文字。不指定时优先从指定的容器中看是否自带文字。 - * * `multiple` {Boolean} 是否开起同时选择多个文件能力。 - */ - pick: null, - - /** - * @property {Arroy} [accept=null] - * @namespace options - * @for Uploader - * @description 指定接受哪些类型的文件。 由于目前还有ext转mimeType表,所以这里需要分开指定。 - * - * * `title` {String} 文字描述 - * * `extensions` {String} 允许的文件后缀,不带点,多个用逗号分割。 - * * `mimeTypes` {String} 多个用逗号分割。 - * - * 如: - * - * ``` - * { - * title: 'Images', - * extensions: 'gif,jpg,jpeg,bmp,png', - * mimeTypes: 'image/*' - * } - * ``` - */ - accept: null/*{ - title: 'Images', - extensions: 'gif,jpg,jpeg,bmp,png', - mimeTypes: 'image/*' - }*/ - }); - - return Uploader.register({ - name: 'picker', - - init: function( opts ) { - this.pickers = []; - return opts.pick && this.addBtn( opts.pick ); - }, - - refresh: function() { - $.each( this.pickers, function() { - this.refresh(); - }); - }, - - /** - * @method addButton - * @for Uploader - * @grammar addButton( pick ) => Promise - * @description - * 添加文件选择按钮,如果一个按钮不够,需要调用此方法来添加。参数跟[options.pick](#WebUploader:Uploader:options)一致。 - * @example - * uploader.addButton({ - * id: '#btnContainer', - * innerHTML: '选择文件' - * }); - */ - addBtn: function( pick ) { - var me = this, - opts = me.options, - accept = opts.accept, - promises = []; - - if ( !pick ) { - return; - } - - $.isPlainObject( pick ) || (pick = { - id: pick - }); - - $( pick.id ).each(function() { - var options, picker, deferred; - - deferred = Base.Deferred(); - - options = $.extend({}, pick, { - accept: $.isPlainObject( accept ) ? [ accept ] : accept, - swf: opts.swf, - runtimeOrder: opts.runtimeOrder, - id: this - }); - - picker = new FilePicker( options ); - - picker.once( 'ready', deferred.resolve ); - picker.on( 'select', function( files ) { - me.owner.request( 'add-file', [ files ]); - }); - picker.init(); - - me.pickers.push( picker ); - - promises.push( deferred.promise() ); - }); - - return Base.when.apply( Base, promises ); - }, - - disable: function() { - $.each( this.pickers, function() { - this.disable(); - }); - }, - - enable: function() { - $.each( this.pickers, function() { - this.enable(); - }); - }, - - destroy: function() { - $.each( this.pickers, function() { - this.destroy(); - }); - this.pickers = null; - } - }); - }); - /** - * @fileOverview 文件属性封装 - */ - define('file',[ - 'base', - 'mediator' - ], function( Base, Mediator ) { - - var $ = Base.$, - idPrefix = 'WU_FILE_', - idSuffix = 0, - rExt = /\.([^.]+)$/, - statusMap = {}; - - function gid() { - return idPrefix + idSuffix++; - } - - /** - * 文件类 - * @class File - * @constructor 构造函数 - * @grammar new File( source ) => File - * @param {Lib.File} source [lib.File](#Lib.File)实例, 此source对象是带有Runtime信息的。 - */ - function WUFile( source ) { - - /** - * 文件名,包括扩展名(后缀) - * @property name - * @type {string} - */ - this.name = source.name || 'Untitled'; - - /** - * 文件体积(字节) - * @property size - * @type {uint} - * @default 0 - */ - this.size = source.size || 0; - - /** - * 文件MIMETYPE类型,与文件类型的对应关系请参考[http://t.cn/z8ZnFny](http://t.cn/z8ZnFny) - * @property type - * @type {string} - * @default 'application/octet-stream' - */ - this.type = source.type || 'application/octet-stream'; - - /** - * 文件最后修改日期 - * @property lastModifiedDate - * @type {int} - * @default 当前时间戳 - */ - this.lastModifiedDate = source.lastModifiedDate || (new Date() * 1); - - /** - * 文件ID,每个对象具有唯一ID,与文件名无关 - * @property id - * @type {string} - */ - this.id = gid(); - - /** - * 文件扩展名,通过文件名获取,例如test.png的扩展名为png - * @property ext - * @type {string} - */ - this.ext = rExt.exec( this.name ) ? RegExp.$1 : ''; - - - /** - * 状态文字说明。在不同的status语境下有不同的用途。 - * @property statusText - * @type {string} - */ - this.statusText = ''; - - // 存储文件状态,防止通过属性直接修改 - statusMap[ this.id ] = WUFile.Status.INITED; - - this.source = source; - this.loaded = 0; - - this.on( 'error', function( msg ) { - this.setStatus( WUFile.Status.ERROR, msg ); - }); - } - - $.extend( WUFile.prototype, { - - /** - * 设置状态,状态变化时会触发`change`事件。 - * @method setStatus - * @grammar setStatus( status[, statusText] ); - * @param {File.Status|String} status [文件状态值](#WebUploader:File:File.Status) - * @param {String} [statusText=''] 状态说明,常在error时使用,用http, abort,server等来标记是由于什么原因导致文件错误。 - */ - setStatus: function( status, text ) { - - var prevStatus = statusMap[ this.id ]; - - typeof text !== 'undefined' && (this.statusText = text); - - if ( status !== prevStatus ) { - statusMap[ this.id ] = status; - /** - * 文件状态变化 - * @event statuschange - */ - this.trigger( 'statuschange', status, prevStatus ); - } - - }, - - /** - * 获取文件状态 - * @return {File.Status} - * @example - 文件状态具体包括以下几种类型: - { - // 初始化 - INITED: 0, - // 已入队列 - QUEUED: 1, - // 正在上传 - PROGRESS: 2, - // 上传出错 - ERROR: 3, - // 上传成功 - COMPLETE: 4, - // 上传取消 - CANCELLED: 5 - } - */ - getStatus: function() { - return statusMap[ this.id ]; - }, - - /** - * 获取文件原始信息。 - * @return {*} - */ - getSource: function() { - return this.source; - }, - - destroy: function() { - this.off(); - delete statusMap[ this.id ]; - } - }); - - Mediator.installTo( WUFile.prototype ); - - /** - * 文件状态值,具体包括以下几种类型: - * * `inited` 初始状态 - * * `queued` 已经进入队列, 等待上传 - * * `progress` 上传中 - * * `complete` 上传完成。 - * * `error` 上传出错,可重试 - * * `interrupt` 上传中断,可续传。 - * * `invalid` 文件不合格,不能重试上传。会自动从队列中移除。 - * * `cancelled` 文件被移除。 - * @property {Object} Status - * @namespace File - * @class File - * @static - */ - WUFile.Status = { - INITED: 'inited', // 初始状态 - QUEUED: 'queued', // 已经进入队列, 等待上传 - PROGRESS: 'progress', // 上传中 - ERROR: 'error', // 上传出错,可重试 - COMPLETE: 'complete', // 上传完成。 - CANCELLED: 'cancelled', // 上传取消。 - INTERRUPT: 'interrupt', // 上传中断,可续传。 - INVALID: 'invalid' // 文件不合格,不能重试上传。 - }; - - return WUFile; - }); - - /** - * @fileOverview 文件队列 - */ - define('queue',[ - 'base', - 'mediator', - 'file' - ], function( Base, Mediator, WUFile ) { - - var $ = Base.$, - STATUS = WUFile.Status; - - /** - * 文件队列, 用来存储各个状态中的文件。 - * @class Queue - * @extends Mediator - */ - function Queue() { - - /** - * 统计文件数。 - * * `numOfQueue` 队列中的文件数。 - * * `numOfSuccess` 上传成功的文件数 - * * `numOfCancel` 被取消的文件数 - * * `numOfProgress` 正在上传中的文件数 - * * `numOfUploadFailed` 上传错误的文件数。 - * * `numOfInvalid` 无效的文件数。 - * * `numofDeleted` 被移除的文件数。 - * @property {Object} stats - */ - this.stats = { - numOfQueue: 0, - numOfSuccess: 0, - numOfCancel: 0, - numOfProgress: 0, - numOfUploadFailed: 0, - numOfInvalid: 0, - numofDeleted: 0, - numofInterrupt: 0, - }; - - // 上传队列,仅包括等待上传的文件 - this._queue = []; - - // 存储所有文件 - this._map = {}; - } - - $.extend( Queue.prototype, { - - /** - * 将新文件加入对队列尾部 - * - * @method append - * @param {File} file 文件对象 - */ - append: function( file ) { - this._queue.push( file ); - this._fileAdded( file ); - return this; - }, - - /** - * 将新文件加入对队列头部 - * - * @method prepend - * @param {File} file 文件对象 - */ - prepend: function( file ) { - this._queue.unshift( file ); - this._fileAdded( file ); - return this; - }, - - /** - * 获取文件对象 - * - * @method getFile - * @param {String} fileId 文件ID - * @return {File} - */ - getFile: function( fileId ) { - if ( typeof fileId !== 'string' ) { - return fileId; - } - return this._map[ fileId ]; - }, - - /** - * 从队列中取出一个指定状态的文件。 - * @grammar fetch( status ) => File - * @method fetch - * @param {String} status [文件状态值](#WebUploader:File:File.Status) - * @return {File} [File](#WebUploader:File) - */ - fetch: function( status ) { - var len = this._queue.length, - i, file; - - status = status || STATUS.QUEUED; - - for ( i = 0; i < len; i++ ) { - file = this._queue[ i ]; - - if ( status === file.getStatus() ) { - return file; - } - } - - return null; - }, - - /** - * 对队列进行排序,能够控制文件上传顺序。 - * @grammar sort( fn ) => undefined - * @method sort - * @param {Function} fn 排序方法 - */ - sort: function( fn ) { - if ( typeof fn === 'function' ) { - this._queue.sort( fn ); - } - }, - - /** - * 获取指定类型的文件列表, 列表中每一个成员为[File](#WebUploader:File)对象。 - * @grammar getFiles( [status1[, status2 ...]] ) => Array - * @method getFiles - * @param {String} [status] [文件状态值](#WebUploader:File:File.Status) - */ - getFiles: function() { - var sts = [].slice.call( arguments, 0 ), - ret = [], - i = 0, - len = this._queue.length, - file; - - for ( ; i < len; i++ ) { - file = this._queue[ i ]; - - if ( sts.length && !~$.inArray( file.getStatus(), sts ) ) { - continue; - } - - ret.push( file ); - } - - return ret; - }, - - /** - * 在队列中删除文件。 - * @grammar removeFile( file ) => Array - * @method removeFile - * @param {File} 文件对象。 - */ - removeFile: function( file ) { - var me = this, - existing = this._map[ file.id ]; - - if ( existing ) { - delete this._map[ file.id ]; - file.destroy(); - this.stats.numofDeleted++; - } - }, - - _fileAdded: function( file ) { - var me = this, - existing = this._map[ file.id ]; - - if ( !existing ) { - this._map[ file.id ] = file; - - file.on( 'statuschange', function( cur, pre ) { - me._onFileStatusChange( cur, pre ); - }); - } - }, - - _onFileStatusChange: function( curStatus, preStatus ) { - var stats = this.stats; - - switch ( preStatus ) { - case STATUS.PROGRESS: - stats.numOfProgress--; - break; - - case STATUS.QUEUED: - stats.numOfQueue --; - break; - - case STATUS.ERROR: - stats.numOfUploadFailed--; - break; - - case STATUS.INVALID: - stats.numOfInvalid--; - break; - - case STATUS.INTERRUPT: - stats.numofInterrupt--; - break; - } - - switch ( curStatus ) { - case STATUS.QUEUED: - stats.numOfQueue++; - break; - - case STATUS.PROGRESS: - stats.numOfProgress++; - break; - - case STATUS.ERROR: - stats.numOfUploadFailed++; - break; - - case STATUS.COMPLETE: - stats.numOfSuccess++; - break; - - case STATUS.CANCELLED: - stats.numOfCancel++; - break; - - - case STATUS.INVALID: - stats.numOfInvalid++; - break; - - case STATUS.INTERRUPT: - stats.numofInterrupt++; - break; - } - } - - }); - - Mediator.installTo( Queue.prototype ); - - return Queue; - }); - /** - * @fileOverview 队列 - */ - define('widgets/queue',[ - 'base', - 'uploader', - 'queue', - 'file', - 'lib/file', - 'runtime/client', - 'widgets/widget' - ], function( Base, Uploader, Queue, WUFile, File, RuntimeClient ) { - - var $ = Base.$, - rExt = /\.\w+$/, - Status = WUFile.Status; - - return Uploader.register({ - name: 'queue', - - init: function( opts ) { - var me = this, - deferred, len, i, item, arr, accept, runtime; - - if ( $.isPlainObject( opts.accept ) ) { - opts.accept = [ opts.accept ]; - } - - // accept中的中生成匹配正则。 - if ( opts.accept ) { - arr = []; - - for ( i = 0, len = opts.accept.length; i < len; i++ ) { - item = opts.accept[ i ].extensions; - item && arr.push( item ); - } - - if ( arr.length ) { - accept = '\\.' + arr.join(',') - .replace( /,/g, '$|\\.' ) - .replace( /\*/g, '.*' ) + '$'; - } - - me.accept = new RegExp( accept, 'i' ); - } - - me.queue = new Queue(); - me.stats = me.queue.stats; - - // 如果当前不是html5运行时,那就算了。 - // 不执行后续操作 - if ( this.request('predict-runtime-type') !== 'html5' ) { - return; - } - - // 创建一个 html5 运行时的 placeholder - // 以至于外部添加原生 File 对象的时候能正确包裹一下供 webuploader 使用。 - deferred = Base.Deferred(); - this.placeholder = runtime = new RuntimeClient('Placeholder'); - runtime.connectRuntime({ - runtimeOrder: 'html5' - }, function() { - me._ruid = runtime.getRuid(); - deferred.resolve(); - }); - return deferred.promise(); - }, - - - // 为了支持外部直接添加一个原生File对象。 - _wrapFile: function( file ) { - if ( !(file instanceof WUFile) ) { - - if ( !(file instanceof File) ) { - if ( !this._ruid ) { - throw new Error('Can\'t add external files.'); - } - file = new File( this._ruid, file ); - } - - file = new WUFile( file ); - } - - return file; - }, - - // 判断文件是否可以被加入队列 - acceptFile: function( file ) { - var invalid = !file || !file.size || this.accept && - - // 如果名字中有后缀,才做后缀白名单处理。 - rExt.exec( file.name ) && !this.accept.test( file.name ); - - return !invalid; - }, - - - /** - * @event beforeFileQueued - * @param {File} file File对象 - * @description 当文件被加入队列之前触发,此事件的handler返回值为`false`,则此文件不会被添加进入队列。 - * @for Uploader - */ - - /** - * @event fileQueued - * @param {File} file File对象 - * @description 当文件被加入队列以后触发。 - * @for Uploader - */ - - _addFile: function( file ) { - var me = this; - - file = me._wrapFile( file ); - - // 不过类型判断允许不允许,先派送 `beforeFileQueued` - if ( !me.owner.trigger( 'beforeFileQueued', file ) ) { - return; - } - - // 类型不匹配,则派送错误事件,并返回。 - if ( !me.acceptFile( file ) ) { - me.owner.trigger( 'error', 'Q_TYPE_DENIED', file ); - return; - } - - me.queue.append( file ); - me.owner.trigger( 'fileQueued', file ); - return file; - }, - - getFile: function( fileId ) { - return this.queue.getFile( fileId ); - }, - - /** - * @event filesQueued - * @param {File} files 数组,内容为原始File(lib/File)对象。 - * @description 当一批文件添加进队列以后触发。 - * @for Uploader - */ - - /** - * @property {Boolean} [auto=false] - * @namespace options - * @for Uploader - * @description 设置为 true 后,不需要手动调用上传,有文件选择即开始上传。 - * - */ - - /** - * @method addFiles - * @grammar addFiles( file ) => undefined - * @grammar addFiles( [file1, file2 ...] ) => undefined - * @param {Array of File or File} [files] Files 对象 数组 - * @description 添加文件到队列 - * @for Uploader - */ - addFile: function( files ) { - var me = this; - - if ( !files.length ) { - files = [ files ]; - } - - files = $.map( files, function( file ) { - return me._addFile( file ); - }); - - me.owner.trigger( 'filesQueued', files ); - - if ( me.options.auto ) { - setTimeout(function() { - me.request('start-upload'); - }, 20 ); - } - }, - - getStats: function() { - return this.stats; - }, - - /** - * @event fileDequeued - * @param {File} file File对象 - * @description 当文件被移除队列后触发。 - * @for Uploader - */ - - /** - * @method removeFile - * @grammar removeFile( file ) => undefined - * @grammar removeFile( id ) => undefined - * @grammar removeFile( file, true ) => undefined - * @grammar removeFile( id, true ) => undefined - * @param {File|id} file File对象或这File对象的id - * @description 移除某一文件, 默认只会标记文件状态为已取消,如果第二个参数为 `true` 则会从 queue 中移除。 - * @for Uploader - * @example - * - * $li.on('click', '.remove-this', function() { - * uploader.removeFile( file ); - * }) - */ - removeFile: function( file, remove ) { - var me = this; - - file = file.id ? file : me.queue.getFile( file ); - - this.request( 'cancel-file', file ); - - if ( remove ) { - this.queue.removeFile( file ); - } - }, - - /** - * @method getFiles - * @grammar getFiles() => Array - * @grammar getFiles( status1, status2, status... ) => Array - * @description 返回指定状态的文件集合,不传参数将返回所有状态的文件。 - * @for Uploader - * @example - * console.log( uploader.getFiles() ); // => all files - * console.log( uploader.getFiles('error') ) // => all error files. - */ - getFiles: function() { - return this.queue.getFiles.apply( this.queue, arguments ); - }, - - fetchFile: function() { - return this.queue.fetch.apply( this.queue, arguments ); - }, - - /** - * @method retry - * @grammar retry() => undefined - * @grammar retry( file ) => undefined - * @description 重试上传,重试指定文件,或者从出错的文件开始重新上传。 - * @for Uploader - * @example - * function retry() { - * uploader.retry(); - * } - */ - retry: function( file, noForceStart ) { - var me = this, - files, i, len; - - if ( file ) { - file = file.id ? file : me.queue.getFile( file ); - file.setStatus( Status.QUEUED ); - noForceStart || me.request('start-upload'); - return; - } - - files = me.queue.getFiles( Status.ERROR ); - i = 0; - len = files.length; - - for ( ; i < len; i++ ) { - file = files[ i ]; - file.setStatus( Status.QUEUED ); - } - - me.request('start-upload'); - }, - - /** - * @method sort - * @grammar sort( fn ) => undefined - * @description 排序队列中的文件,在上传之前调整可以控制上传顺序。 - * @for Uploader - */ - sortFiles: function() { - return this.queue.sort.apply( this.queue, arguments ); - }, - - /** - * @event reset - * @description 当 uploader 被重置的时候触发。 - * @for Uploader - */ - - /** - * @method reset - * @grammar reset() => undefined - * @description 重置uploader。目前只重置了队列。 - * @for Uploader - * @example - * uploader.reset(); - */ - reset: function() { - this.owner.trigger('reset'); - this.queue = new Queue(); - this.stats = this.queue.stats; - }, - - destroy: function() { - this.reset(); - this.placeholder && this.placeholder.destroy(); - } - }); - - }); - /** - * @fileOverview 添加获取Runtime相关信息的方法。 - */ - define('widgets/runtime',[ - 'uploader', - 'runtime/runtime', - 'widgets/widget' - ], function( Uploader, Runtime ) { - - Uploader.support = function() { - return Runtime.hasRuntime.apply( Runtime, arguments ); - }; - - return Uploader.register({ - name: 'runtime', - - init: function() { - if ( !this.predictRuntimeType() ) { - throw Error('Runtime Error'); - } - }, - - /** - * 预测Uploader将采用哪个`Runtime` - * @grammar predictRuntimeType() => String - * @method predictRuntimeType - * @for Uploader - */ - predictRuntimeType: function() { - var orders = this.options.runtimeOrder || Runtime.orders, - type = this.type, - i, len; - - if ( !type ) { - orders = orders.split( /\s*,\s*/g ); - - for ( i = 0, len = orders.length; i < len; i++ ) { - if ( Runtime.hasRuntime( orders[ i ] ) ) { - this.type = type = orders[ i ]; - break; - } - } - } - - return type; - } - }); - }); - /** - * @fileOverview Transport - */ - define('lib/transport',[ - 'base', - 'runtime/client', - 'mediator' - ], function( Base, RuntimeClient, Mediator ) { - - var $ = Base.$; - - function Transport( opts ) { - var me = this; - - opts = me.options = $.extend( true, {}, Transport.options, opts || {} ); - RuntimeClient.call( this, 'Transport' ); - - this._blob = null; - this._formData = opts.formData || {}; - this._headers = opts.headers || {}; - - this.on( 'progress', this._timeout ); - this.on( 'load error', function() { - me.trigger( 'progress', 1 ); - clearTimeout( me._timer ); - }); - } - - Transport.options = { - server: '', - method: 'POST', - - // 跨域时,是否允许携带cookie, 只有html5 runtime才有效 - withCredentials: false, - fileVal: 'file', - timeout: 2 * 60 * 1000, // 2分钟 - formData: {}, - headers: {}, - sendAsBinary: false - }; - - $.extend( Transport.prototype, { - - // 添加Blob, 只能添加一次,最后一次有效。 - appendBlob: function( key, blob, filename ) { - var me = this, - opts = me.options; - - if ( me.getRuid() ) { - me.disconnectRuntime(); - } - - // 连接到blob归属的同一个runtime. - me.connectRuntime( blob.ruid, function() { - me.exec('init'); - }); - - me._blob = blob; - opts.fileVal = key || opts.fileVal; - opts.filename = filename || opts.filename; - }, - - // 添加其他字段 - append: function( key, value ) { - if ( typeof key === 'object' ) { - $.extend( this._formData, key ); - } else { - this._formData[ key ] = value; - } - }, - - setRequestHeader: function( key, value ) { - if ( typeof key === 'object' ) { - $.extend( this._headers, key ); - } else { - this._headers[ key ] = value; - } - }, - - send: function( method ) { - this.exec( 'send', method ); - this._timeout(); - }, - - abort: function() { - clearTimeout( this._timer ); - return this.exec('abort'); - }, - - destroy: function() { - this.trigger('destroy'); - this.off(); - this.exec('destroy'); - this.disconnectRuntime(); - }, - - getResponse: function() { - return this.exec('getResponse'); - }, - - getResponseAsJson: function() { - return this.exec('getResponseAsJson'); - }, - - getStatus: function() { - return this.exec('getStatus'); - }, - - _timeout: function() { - var me = this, - duration = me.options.timeout; - - if ( !duration ) { - return; - } - - clearTimeout( me._timer ); - me._timer = setTimeout(function() { - me.abort(); - me.trigger( 'error', 'timeout' ); - }, duration ); - } - - }); - - // 让Transport具备事件功能。 - Mediator.installTo( Transport.prototype ); - - return Transport; - }); - /** - * @fileOverview 负责文件上传相关。 - */ - define('widgets/upload',[ - 'base', - 'uploader', - 'file', - 'lib/transport', - 'widgets/widget' - ], function( Base, Uploader, WUFile, Transport ) { - - var $ = Base.$, - isPromise = Base.isPromise, - Status = WUFile.Status; - - // 添加默认配置项 - $.extend( Uploader.options, { - - - /** - * @property {Boolean} [prepareNextFile=false] - * @namespace options - * @for Uploader - * @description 是否允许在文件传输时提前把下一个文件准备好。 - * 对于一个文件的准备工作比较耗时,比如图片压缩,md5序列化。 - * 如果能提前在当前文件传输期处理,可以节省总体耗时。 - */ - prepareNextFile: false, - - /** - * @property {Boolean} [chunked=false] - * @namespace options - * @for Uploader - * @description 是否要分片处理大文件上传。 - */ - chunked: false, - - /** - * @property {Boolean} [chunkSize=5242880] - * @namespace options - * @for Uploader - * @description 如果要分片,分多大一片? 默认大小为5M. - */ - chunkSize: 5 * 1024 * 1024, - - /** - * @property {Boolean} [chunkRetry=2] - * @namespace options - * @for Uploader - * @description 如果某个分片由于网络问题出错,允许自动重传多少次? - */ - chunkRetry: 2, - - /** - * @property {Boolean} [threads=3] - * @namespace options - * @for Uploader - * @description 上传并发数。允许同时最大上传进程数。 - */ - threads: 3, - - - /** - * @property {Object} [formData={}] - * @namespace options - * @for Uploader - * @description 文件上传请求的参数表,每次发送都会发送此对象中的参数。 - */ - formData: {} - - /** - * @property {Object} [fileVal='file'] - * @namespace options - * @for Uploader - * @description 设置文件上传域的name。 - */ - - /** - * @property {Object} [method='POST'] - * @namespace options - * @for Uploader - * @description 文件上传方式,`POST`或者`GET`。 - */ - - /** - * @property {Object} [sendAsBinary=false] - * @namespace options - * @for Uploader - * @description 是否已二进制的流的方式发送文件,这样整个上传内容`php://input`都为文件内容, - * 其他参数在$_GET数组中。 - */ - }); - - // 负责将文件切片。 - function CuteFile( file, chunkSize ) { - var pending = [], - blob = file.source, - total = blob.size, - chunks = chunkSize ? Math.ceil( total / chunkSize ) : 1, - start = 0, - index = 0, - len, api; - - api = { - file: file, - - has: function() { - return !!pending.length; - }, - - shift: function() { - return pending.shift(); - }, - - unshift: function( block ) { - pending.unshift( block ); - } - }; - - while ( index < chunks ) { - len = Math.min( chunkSize, total - start ); - - pending.push({ - file: file, - start: start, - end: chunkSize ? (start + len) : total, - total: total, - chunks: chunks, - chunk: index++, - cuted: api - }); - start += len; - } - - file.blocks = pending.concat(); - file.remaning = pending.length; - - return api; - } - - Uploader.register({ - name: 'upload', - - init: function() { - var owner = this.owner, - me = this; - - this.runing = false; - this.progress = false; - - owner - .on( 'startUpload', function() { - me.progress = true; - }) - .on( 'uploadFinished', function() { - me.progress = false; - }); - - // 记录当前正在传的数据,跟threads相关 - this.pool = []; - - // 缓存分好片的文件。 - this.stack = []; - - // 缓存即将上传的文件。 - this.pending = []; - - // 跟踪还有多少分片在上传中但是没有完成上传。 - this.remaning = 0; - this.__tick = Base.bindFn( this._tick, this ); - - owner.on( 'uploadComplete', function( file ) { - - // 把其他块取消了。 - file.blocks && $.each( file.blocks, function( _, v ) { - v.transport && (v.transport.abort(), v.transport.destroy()); - delete v.transport; - }); - - delete file.blocks; - delete file.remaning; - }); - }, - - reset: function() { - this.request( 'stop-upload', true ); - this.runing = false; - this.pool = []; - this.stack = []; - this.pending = []; - this.remaning = 0; - this._trigged = false; - this._promise = null; - }, - - /** - * @event startUpload - * @description 当开始上传流程时触发。 - * @for Uploader - */ - - /** - * 开始上传。此方法可以从初始状态调用开始上传流程,也可以从暂停状态调用,继续上传流程。 - * - * 可以指定开始某一个文件。 - * @grammar upload() => undefined - * @grammar upload( file | fileId) => undefined - * @method upload - * @for Uploader - */ - startUpload: function(file) { - var me = this; - - // 移出invalid的文件 - $.each( me.request( 'get-files', Status.INVALID ), function() { - me.request( 'remove-file', this ); - }); - - // 如果指定了开始某个文件,则只开始指定文件。 - if ( file ) { - file = file.id ? file : me.request( 'get-file', file ); - - if (file.getStatus() === Status.INTERRUPT) { - $.each( me.pool, function( _, v ) { - - // 之前暂停过。 - if (v.file !== file) { - return; - } - - v.transport && v.transport.send(); - }); - - file.setStatus( Status.QUEUED ); - } else if (file.getStatus() === Status.PROGRESS) { - return; - } else { - file.setStatus( Status.QUEUED ); - } - } else { - $.each( me.request( 'get-files', [ Status.INITED ] ), function() { - this.setStatus( Status.QUEUED ); - }); - } - - if ( me.runing ) { - return; - } - - me.runing = true; - - // 如果有暂停的,则续传 - $.each( me.pool, function( _, v ) { - var file = v.file; - - if ( file.getStatus() === Status.INTERRUPT ) { - file.setStatus( Status.PROGRESS ); - me._trigged = false; - v.transport && v.transport.send(); - } - }); - - file || $.each( me.request( 'get-files', - Status.INTERRUPT ), function() { - this.setStatus( Status.PROGRESS ); - }); - - me._trigged = false; - Base.nextTick( me.__tick ); - me.owner.trigger('startUpload'); - }, - - /** - * @event stopUpload - * @description 当开始上传流程暂停时触发。 - * @for Uploader - */ - - /** - * 暂停上传。第一个参数为是否中断上传当前正在上传的文件。 - * - * 如果第一个参数是文件,则只暂停指定文件。 - * @grammar stop() => undefined - * @grammar stop( true ) => undefined - * @grammar stop( file ) => undefined - * @method stop - * @for Uploader - */ - stopUpload: function( file, interrupt ) { - var me = this; - - if (file === true) { - interrupt = file; - file = null; - } - - if ( me.runing === false ) { - return; - } - - // 如果只是暂停某个文件。 - if ( file ) { - file = file.id ? file : me.request( 'get-file', file ); - - if ( file.getStatus() !== Status.PROGRESS && - file.getStatus() !== Status.QUEUED ) { - return; - } - - file.setStatus( Status.INTERRUPT ); - $.each( me.pool, function( _, v ) { - - // 只 abort 指定的文件。 - if (v.file !== file) { - return; - } - - v.transport && v.transport.abort(); - me._putback(v); - me._popBlock(v); - }); - - return Base.nextTick( me.__tick ); - } - - me.runing = false; - - if (this._promise && this._promise.file) { - this._promise.file.setStatus( Status.INTERRUPT ); - } - - interrupt && $.each( me.pool, function( _, v ) { - v.transport && v.transport.abort(); - v.file.setStatus( Status.INTERRUPT ); - }); - - me.owner.trigger('stopUpload'); - }, - - /** - * @method cancelFile - * @grammar cancelFile( file ) => undefined - * @grammar cancelFile( id ) => undefined - * @param {File|id} file File对象或这File对象的id - * @description 标记文件状态为已取消, 同时将中断文件传输。 - * @for Uploader - * @example - * - * $li.on('click', '.remove-this', function() { - * uploader.cancelFile( file ); - * }) - */ - cancelFile: function( file ) { - file = file.id ? file : this.request( 'get-file', file ); - - // 如果正在上传。 - file.blocks && $.each( file.blocks, function( _, v ) { - var _tr = v.transport; - - if ( _tr ) { - _tr.abort(); - _tr.destroy(); - delete v.transport; - } - }); - - file.setStatus( Status.CANCELLED ); - this.owner.trigger( 'fileDequeued', file ); - }, - - /** - * 判断`Uplaode`r是否正在上传中。 - * @grammar isInProgress() => Boolean - * @method isInProgress - * @for Uploader - */ - isInProgress: function() { - return !!this.progress; - }, - - _getStats: function() { - return this.request('get-stats'); - }, - - /** - * 掉过一个文件上传,直接标记指定文件为已上传状态。 - * @grammar skipFile( file ) => undefined - * @method skipFile - * @for Uploader - */ - skipFile: function( file, status ) { - file = file.id ? file : this.request( 'get-file', file ); - - file.setStatus( status || Status.COMPLETE ); - file.skipped = true; - - // 如果正在上传。 - file.blocks && $.each( file.blocks, function( _, v ) { - var _tr = v.transport; - - if ( _tr ) { - _tr.abort(); - _tr.destroy(); - delete v.transport; - } - }); - - this.owner.trigger( 'uploadSkip', file ); - }, - - /** - * @event uploadFinished - * @description 当所有文件上传结束时触发。 - * @for Uploader - */ - _tick: function() { - var me = this, - opts = me.options, - fn, val; - - // 上一个promise还没有结束,则等待完成后再执行。 - if ( me._promise ) { - return me._promise.always( me.__tick ); - } - - // 还有位置,且还有文件要处理的话。 - if ( me.pool.length < opts.threads && (val = me._nextBlock()) ) { - me._trigged = false; - - fn = function( val ) { - me._promise = null; - - // 有可能是reject过来的,所以要检测val的类型。 - val && val.file && me._startSend( val ); - Base.nextTick( me.__tick ); - }; - - me._promise = isPromise( val ) ? val.always( fn ) : fn( val ); - - // 没有要上传的了,且没有正在传输的了。 - } else if ( !me.remaning && !me._getStats().numOfQueue && - !me._getStats().numofInterrupt ) { - me.runing = false; - - me._trigged || Base.nextTick(function() { - me.owner.trigger('uploadFinished'); - }); - me._trigged = true; - } - }, - - _putback: function(block) { - var idx; - - block.cuted.unshift(block); - idx = this.stack.indexOf(block.cuted); - - if (!~idx) { - this.stack.unshift(block.cuted); - } - }, - - _getStack: function() { - var i = 0, - act; - - while ( (act = this.stack[ i++ ]) ) { - if ( act.has() && act.file.getStatus() === Status.PROGRESS ) { - return act; - } else if (!act.has() || - act.file.getStatus() !== Status.PROGRESS && - act.file.getStatus() !== Status.INTERRUPT ) { - - // 把已经处理完了的,或者,状态为非 progress(上传中)、 - // interupt(暂停中) 的移除。 - this.stack.splice( --i, 1 ); - } - } - - return null; - }, - - _nextBlock: function() { - var me = this, - opts = me.options, - act, next, done, preparing; - - // 如果当前文件还有没有需要传输的,则直接返回剩下的。 - if ( (act = this._getStack()) ) { - - // 是否提前准备下一个文件 - if ( opts.prepareNextFile && !me.pending.length ) { - me._prepareNextFile(); - } - - return act.shift(); - - // 否则,如果正在运行,则准备下一个文件,并等待完成后返回下个分片。 - } else if ( me.runing ) { - - // 如果缓存中有,则直接在缓存中取,没有则去queue中取。 - if ( !me.pending.length && me._getStats().numOfQueue ) { - me._prepareNextFile(); - } - - next = me.pending.shift(); - done = function( file ) { - if ( !file ) { - return null; - } - - act = CuteFile( file, opts.chunked ? opts.chunkSize : 0 ); - me.stack.push(act); - return act.shift(); - }; - - // 文件可能还在prepare中,也有可能已经完全准备好了。 - if ( isPromise( next) ) { - preparing = next.file; - next = next[ next.pipe ? 'pipe' : 'then' ]( done ); - next.file = preparing; - return next; - } - - return done( next ); - } - }, - - - /** - * @event uploadStart - * @param {File} file File对象 - * @description 某个文件开始上传前触发,一个文件只会触发一次。 - * @for Uploader - */ - _prepareNextFile: function() { - var me = this, - file = me.request('fetch-file'), - pending = me.pending, - promise; - - if ( file ) { - promise = me.request( 'before-send-file', file, function() { - - // 有可能文件被skip掉了。文件被skip掉后,状态坑定不是Queued. - if ( file.getStatus() === Status.PROGRESS || - file.getStatus() === Status.INTERRUPT ) { - return file; - } - - return me._finishFile( file ); - }); - - me.owner.trigger( 'uploadStart', file ); - file.setStatus( Status.PROGRESS ); - - promise.file = file; - - // 如果还在pending中,则替换成文件本身。 - promise.done(function() { - var idx = $.inArray( promise, pending ); - - ~idx && pending.splice( idx, 1, file ); - }); - - // befeore-send-file的钩子就有错误发生。 - promise.fail(function( reason ) { - file.setStatus( Status.ERROR, reason ); - me.owner.trigger( 'uploadError', file, reason ); - me.owner.trigger( 'uploadComplete', file ); - }); - - pending.push( promise ); - } - }, - - // 让出位置了,可以让其他分片开始上传 - _popBlock: function( block ) { - var idx = $.inArray( block, this.pool ); - - this.pool.splice( idx, 1 ); - block.file.remaning--; - this.remaning--; - }, - - // 开始上传,可以被掉过。如果promise被reject了,则表示跳过此分片。 - _startSend: function( block ) { - var me = this, - file = block.file, - promise; - - // 有可能在 before-send-file 的 promise 期间改变了文件状态。 - // 如:暂停,取消 - // 我们不能中断 promise, 但是可以在 promise 完后,不做上传操作。 - if ( file.getStatus() !== Status.PROGRESS ) { - - // 如果是中断,则还需要放回去。 - if (file.getStatus() === Status.INTERRUPT) { - me._putback(block); - } - - return; - } - - me.pool.push( block ); - me.remaning++; - - // 如果没有分片,则直接使用原始的。 - // 不会丢失content-type信息。 - block.blob = block.chunks === 1 ? file.source : - file.source.slice( block.start, block.end ); - - // hook, 每个分片发送之前可能要做些异步的事情。 - promise = me.request( 'before-send', block, function() { - - // 有可能文件已经上传出错了,所以不需要再传输了。 - if ( file.getStatus() === Status.PROGRESS ) { - me._doSend( block ); - } else { - me._popBlock( block ); - Base.nextTick( me.__tick ); - } - }); - - // 如果为fail了,则跳过此分片。 - promise.fail(function() { - if ( file.remaning === 1 ) { - me._finishFile( file ).always(function() { - block.percentage = 1; - me._popBlock( block ); - me.owner.trigger( 'uploadComplete', file ); - Base.nextTick( me.__tick ); - }); - } else { - block.percentage = 1; - me._popBlock( block ); - Base.nextTick( me.__tick ); - } - }); - }, - - - /** - * @event uploadBeforeSend - * @param {Object} object - * @param {Object} data 默认的上传参数,可以扩展此对象来控制上传参数。 - * @param {Object} headers 可以扩展此对象来控制上传头部。 - * @description 当某个文件的分块在发送前触发,主要用来询问是否要添加附带参数,大文件在开起分片上传的前提下此事件可能会触发多次。 - * @for Uploader - */ - - /** - * @event uploadAccept - * @param {Object} object - * @param {Object} ret 服务端的返回数据,json格式,如果服务端不是json格式,从ret._raw中取数据,自行解析。 - * @description 当某个文件上传到服务端响应后,会派送此事件来询问服务端响应是否有效。如果此事件handler返回值为`false`, 则此文件将派送`server`类型的`uploadError`事件。 - * @for Uploader - */ - - /** - * @event uploadProgress - * @param {File} file File对象 - * @param {Number} percentage 上传进度 - * @description 上传过程中触发,携带上传进度。 - * @for Uploader - */ - - - /** - * @event uploadError - * @param {File} file File对象 - * @param {String} reason 出错的code - * @description 当文件上传出错时触发。 - * @for Uploader - */ - - /** - * @event uploadSuccess - * @param {File} file File对象 - * @param {Object} response 服务端返回的数据 - * @description 当文件上传成功时触发。 - * @for Uploader - */ - - /** - * @event uploadComplete - * @param {File} [file] File对象 - * @description 不管成功或者失败,文件上传完成时触发。 - * @for Uploader - */ - - // 做上传操作。 - _doSend: function( block ) { - var me = this, - owner = me.owner, - opts = me.options, - file = block.file, - tr = new Transport( opts ), - data = $.extend({}, opts.formData ), - headers = $.extend({}, opts.headers ), - requestAccept, ret; - - block.transport = tr; - - tr.on( 'destroy', function() { - delete block.transport; - me._popBlock( block ); - Base.nextTick( me.__tick ); - }); - - // 广播上传进度。以文件为单位。 - tr.on( 'progress', function( percentage ) { - var totalPercent = 0, - uploaded = 0; - - // 可能没有abort掉,progress还是执行进来了。 - // if ( !file.blocks ) { - // return; - // } - - totalPercent = block.percentage = percentage; - - if ( block.chunks > 1 ) { // 计算文件的整体速度。 - $.each( file.blocks, function( _, v ) { - uploaded += (v.percentage || 0) * (v.end - v.start); - }); - - totalPercent = uploaded / file.size; - } - - owner.trigger( 'uploadProgress', file, totalPercent || 0 ); - }); - - // 用来询问,是否返回的结果是有错误的。 - requestAccept = function( reject ) { - var fn; - - ret = tr.getResponseAsJson() || {}; - ret._raw = tr.getResponse(); - fn = function( value ) { - reject = value; - }; - - // 服务端响应了,不代表成功了,询问是否响应正确。 - if ( !owner.trigger( 'uploadAccept', block, ret, fn ) ) { - reject = reject || 'server'; - } - - return reject; - }; - - // 尝试重试,然后广播文件上传出错。 - tr.on( 'error', function( type, flag ) { - block.retried = block.retried || 0; - - // 自动重试 - if ( block.chunks > 1 && ~'http,abort'.indexOf( type ) && - block.retried < opts.chunkRetry ) { - - block.retried++; - tr.send(); - - } else { - - // http status 500 ~ 600 - if ( !flag && type === 'server' ) { - type = requestAccept( type ); - } - - file.setStatus( Status.ERROR, type ); - owner.trigger( 'uploadError', file, type ); - owner.trigger( 'uploadComplete', file ); - } - }); - - // 上传成功 - tr.on( 'load', function() { - var reason; - - // 如果非预期,转向上传出错。 - if ( (reason = requestAccept()) ) { - tr.trigger( 'error', reason, true ); - return; - } - - // 全部上传完成。 - if ( file.remaning === 1 ) { - me._finishFile( file, ret ); - } else { - tr.destroy(); - } - }); - - // 配置默认的上传字段。 - data = $.extend( data, { - id: file.id, - name: file.name, - type: file.type, - lastModifiedDate: file.lastModifiedDate, - size: file.size - }); - - block.chunks > 1 && $.extend( data, { - chunks: block.chunks, - chunk: block.chunk - }); - - // 在发送之间可以添加字段什么的。。。 - // 如果默认的字段不够使用,可以通过监听此事件来扩展 - owner.trigger( 'uploadBeforeSend', block, data, headers ); - - // 开始发送。 - tr.appendBlob( opts.fileVal, block.blob, file.name ); - tr.append( data ); - tr.setRequestHeader( headers ); - tr.send(); - }, - - // 完成上传。 - _finishFile: function( file, ret, hds ) { - var owner = this.owner; - - return owner - .request( 'after-send-file', arguments, function() { - file.setStatus( Status.COMPLETE ); - owner.trigger( 'uploadSuccess', file, ret, hds ); - }) - .fail(function( reason ) { - - // 如果外部已经标记为invalid什么的,不再改状态。 - if ( file.getStatus() === Status.PROGRESS ) { - file.setStatus( Status.ERROR, reason ); - } - - owner.trigger( 'uploadError', file, reason ); - }) - .always(function() { - owner.trigger( 'uploadComplete', file ); - }); - } - - }); - }); - /** - * @fileOverview 各种验证,包括文件总大小是否超出、单文件是否超出和文件是否重复。 - */ - - define('widgets/validator',[ - 'base', - 'uploader', - 'file', - 'widgets/widget' - ], function( Base, Uploader, WUFile ) { - - var $ = Base.$, - validators = {}, - api; - - /** - * @event error - * @param {String} type 错误类型。 - * @description 当validate不通过时,会以派送错误事件的形式通知调用者。通过`upload.on('error', handler)`可以捕获到此类错误,目前有以下错误会在特定的情况下派送错来。 - * - * * `Q_EXCEED_NUM_LIMIT` 在设置了`fileNumLimit`且尝试给`uploader`添加的文件数量超出这个值时派送。 - * * `Q_EXCEED_SIZE_LIMIT` 在设置了`Q_EXCEED_SIZE_LIMIT`且尝试给`uploader`添加的文件总大小超出这个值时派送。 - * * `Q_TYPE_DENIED` 当文件类型不满足时触发。。 - * @for Uploader - */ - - // 暴露给外面的api - api = { - - // 添加验证器 - addValidator: function( type, cb ) { - validators[ type ] = cb; - }, - - // 移除验证器 - removeValidator: function( type ) { - delete validators[ type ]; - } - }; - - // 在Uploader初始化的时候启动Validators的初始化 - Uploader.register({ - name: 'validator', - - init: function() { - var me = this; - Base.nextTick(function() { - $.each( validators, function() { - this.call( me.owner ); - }); - }); - } - }); - - /** - * @property {int} [fileNumLimit=undefined] - * @namespace options - * @for Uploader - * @description 验证文件总数量, 超出则不允许加入队列。 - */ - api.addValidator( 'fileNumLimit', function() { - var uploader = this, - opts = uploader.options, - count = 0, - max = parseInt( opts.fileNumLimit, 10 ), - flag = true; - - if ( !max ) { - return; - } - - uploader.on( 'beforeFileQueued', function( file ) { - - if ( count >= max && flag ) { - flag = false; - this.trigger( 'error', 'Q_EXCEED_NUM_LIMIT', max, file ); - setTimeout(function() { - flag = true; - }, 1 ); - } - - return count >= max ? false : true; - }); - - uploader.on( 'fileQueued', function() { - count++; - }); - - uploader.on( 'fileDequeued', function() { - count--; - }); - - uploader.on( 'reset', function() { - count = 0; - }); - }); - - - /** - * @property {int} [fileSizeLimit=undefined] - * @namespace options - * @for Uploader - * @description 验证文件总大小是否超出限制, 超出则不允许加入队列。 - */ - api.addValidator( 'fileSizeLimit', function() { - var uploader = this, - opts = uploader.options, - count = 0, - max = parseInt( opts.fileSizeLimit, 10 ), - flag = true; - - if ( !max ) { - return; - } - - uploader.on( 'beforeFileQueued', function( file ) { - var invalid = count + file.size > max; - - if ( invalid && flag ) { - flag = false; - this.trigger( 'error', 'Q_EXCEED_SIZE_LIMIT', max, file ); - setTimeout(function() { - flag = true; - }, 1 ); - } - - return invalid ? false : true; - }); - - uploader.on( 'fileQueued', function( file ) { - count += file.size; - }); - - uploader.on( 'fileDequeued', function( file ) { - count -= file.size; - }); - - uploader.on( 'reset', function() { - count = 0; - }); - }); - - /** - * @property {int} [fileSingleSizeLimit=undefined] - * @namespace options - * @for Uploader - * @description 验证单个文件大小是否超出限制, 超出则不允许加入队列。 - */ - api.addValidator( 'fileSingleSizeLimit', function() { - var uploader = this, - opts = uploader.options, - max = opts.fileSingleSizeLimit; - - if ( !max ) { - return; - } - - uploader.on( 'beforeFileQueued', function( file ) { - - if ( file.size > max ) { - file.setStatus( WUFile.Status.INVALID, 'exceed_size' ); - this.trigger( 'error', 'F_EXCEED_SIZE', max, file ); - return false; - } - - }); - - }); - - /** - * @property {Boolean} [duplicate=undefined] - * @namespace options - * @for Uploader - * @description 去重, 根据文件名字、文件大小和最后修改时间来生成hash Key. - */ - api.addValidator( 'duplicate', function() { - var uploader = this, - opts = uploader.options, - mapping = {}; - - if ( opts.duplicate ) { - return; - } - - function hashString( str ) { - var hash = 0, - i = 0, - len = str.length, - _char; - - for ( ; i < len; i++ ) { - _char = str.charCodeAt( i ); - hash = _char + (hash << 6) + (hash << 16) - hash; - } - - return hash; - } - - uploader.on( 'beforeFileQueued', function( file ) { - var hash = file.__hash || (file.__hash = hashString( file.name + - file.size + file.lastModifiedDate )); - - // 已经重复了 - if ( mapping[ hash ] ) { - this.trigger( 'error', 'F_DUPLICATE', file ); - return false; - } - }); - - uploader.on( 'fileQueued', function( file ) { - var hash = file.__hash; - - hash && (mapping[ hash ] = true); - }); - - uploader.on( 'fileDequeued', function( file ) { - var hash = file.__hash; - - hash && (delete mapping[ hash ]); - }); - - uploader.on( 'reset', function() { - mapping = {}; - }); - }); - - return api; - }); - - /** - * @fileOverview Runtime管理器,负责Runtime的选择, 连接 - */ - define('runtime/compbase',[],function() { - - function CompBase( owner, runtime ) { - - this.owner = owner; - this.options = owner.options; - - this.getRuntime = function() { - return runtime; - }; - - this.getRuid = function() { - return runtime.uid; - }; - - this.trigger = function() { - return owner.trigger.apply( owner, arguments ); - }; - } - - return CompBase; - }); - /** - * @fileOverview Html5Runtime - */ - define('runtime/html5/runtime',[ - 'base', - 'runtime/runtime', - 'runtime/compbase' - ], function( Base, Runtime, CompBase ) { - - var type = 'html5', - components = {}; - - function Html5Runtime() { - var pool = {}, - me = this, - destroy = this.destroy; - - Runtime.apply( me, arguments ); - me.type = type; - - - // 这个方法的调用者,实际上是RuntimeClient - me.exec = function( comp, fn/*, args...*/) { - var client = this, - uid = client.uid, - args = Base.slice( arguments, 2 ), - instance; - - if ( components[ comp ] ) { - instance = pool[ uid ] = pool[ uid ] || - new components[ comp ]( client, me ); - - if ( instance[ fn ] ) { - return instance[ fn ].apply( instance, args ); - } - } - }; - - me.destroy = function() { - // @todo 删除池子中的所有实例 - return destroy && destroy.apply( this, arguments ); - }; - } - - Base.inherits( Runtime, { - constructor: Html5Runtime, - - // 不需要连接其他程序,直接执行callback - init: function() { - var me = this; - setTimeout(function() { - me.trigger('ready'); - }, 1 ); - } - - }); - - // 注册Components - Html5Runtime.register = function( name, component ) { - var klass = components[ name ] = Base.inherits( CompBase, component ); - return klass; - }; - - // 注册html5运行时。 - // 只有在支持的前提下注册。 - if ( window.Blob && window.FileReader && window.DataView ) { - Runtime.addRuntime( type, Html5Runtime ); - } - - return Html5Runtime; - }); - /** - * @fileOverview Blob Html实现 - */ - define('runtime/html5/blob',[ - 'runtime/html5/runtime', - 'lib/blob' - ], function( Html5Runtime, Blob ) { - - return Html5Runtime.register( 'Blob', { - slice: function( start, end ) { - var blob = this.owner.source, - slice = blob.slice || blob.webkitSlice || blob.mozSlice; - - blob = slice.call( blob, start, end ); - - return new Blob( this.getRuid(), blob ); - } - }); - }); - /** - * @fileOverview FilePaste - */ - define('runtime/html5/dnd',[ - 'base', - 'runtime/html5/runtime', - 'lib/file' - ], function( Base, Html5Runtime, File ) { - - var $ = Base.$, - prefix = 'webuploader-dnd-'; - - return Html5Runtime.register( 'DragAndDrop', { - init: function() { - var elem = this.elem = this.options.container; - - this.dragEnterHandler = Base.bindFn( this._dragEnterHandler, this ); - this.dragOverHandler = Base.bindFn( this._dragOverHandler, this ); - this.dragLeaveHandler = Base.bindFn( this._dragLeaveHandler, this ); - this.dropHandler = Base.bindFn( this._dropHandler, this ); - this.dndOver = false; - - elem.on( 'dragenter', this.dragEnterHandler ); - elem.on( 'dragover', this.dragOverHandler ); - elem.on( 'dragleave', this.dragLeaveHandler ); - elem.on( 'drop', this.dropHandler ); - - if ( this.options.disableGlobalDnd ) { - $( document ).on( 'dragover', this.dragOverHandler ); - $( document ).on( 'drop', this.dropHandler ); - } - }, - - _dragEnterHandler: function( e ) { - var me = this, - denied = me._denied || false, - items; - - e = e.originalEvent || e; - - if ( !me.dndOver ) { - me.dndOver = true; - - // 注意只有 chrome 支持。 - items = e.dataTransfer.items; - - if ( items && items.length ) { - me._denied = denied = !me.trigger( 'accept', items ); - } - - me.elem.addClass( prefix + 'over' ); - me.elem[ denied ? 'addClass' : - 'removeClass' ]( prefix + 'denied' ); - } - - e.dataTransfer.dropEffect = denied ? 'none' : 'copy'; - - return false; - }, - - _dragOverHandler: function( e ) { - // 只处理框内的。 - var parentElem = this.elem.parent().get( 0 ); - if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) { - return false; - } - - clearTimeout( this._leaveTimer ); - this._dragEnterHandler.call( this, e ); - - return false; - }, - - _dragLeaveHandler: function() { - var me = this, - handler; - - handler = function() { - me.dndOver = false; - me.elem.removeClass( prefix + 'over ' + prefix + 'denied' ); - }; - - clearTimeout( me._leaveTimer ); - me._leaveTimer = setTimeout( handler, 100 ); - return false; - }, - - _dropHandler: function( e ) { - var me = this, - ruid = me.getRuid(), - parentElem = me.elem.parent().get( 0 ), - dataTransfer, data; - - // 只处理框内的。 - if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) { - return false; - } - - e = e.originalEvent || e; - dataTransfer = e.dataTransfer; - - // 如果是页面内拖拽,还不能处理,不阻止事件。 - // 此处 ie11 下会报参数错误, - try { - data = dataTransfer.getData('text/html'); - } catch( err ) { - } - - if ( data ) { - return; - } - - me._getTansferFiles( dataTransfer, function( results ) { - me.trigger( 'drop', $.map( results, function( file ) { - return new File( ruid, file ); - }) ); - }); - - me.dndOver = false; - me.elem.removeClass( prefix + 'over' ); - return false; - }, - - // 如果传入 callback 则去查看文件夹,否则只管当前文件夹。 - _getTansferFiles: function( dataTransfer, callback ) { - var results = [], - promises = [], - items, files, file, item, i, len, canAccessFolder; - - items = dataTransfer.items; - files = dataTransfer.files; - - canAccessFolder = !!(items && items[ 0 ].webkitGetAsEntry); - - for ( i = 0, len = files.length; i < len; i++ ) { - file = files[ i ]; - item = items && items[ i ]; - - if ( canAccessFolder && item.webkitGetAsEntry().isDirectory ) { - - promises.push( this._traverseDirectoryTree( - item.webkitGetAsEntry(), results ) ); - } else { - results.push( file ); - } - } - - Base.when.apply( Base, promises ).done(function() { - - if ( !results.length ) { - return; - } - - callback( results ); - }); - }, - - _traverseDirectoryTree: function( entry, results ) { - var deferred = Base.Deferred(), - me = this; - - if ( entry.isFile ) { - entry.file(function( file ) { - results.push( file ); - deferred.resolve(); - }); - } else if ( entry.isDirectory ) { - entry.createReader().readEntries(function( entries ) { - var len = entries.length, - promises = [], - arr = [], // 为了保证顺序。 - i; - - for ( i = 0; i < len; i++ ) { - promises.push( me._traverseDirectoryTree( - entries[ i ], arr ) ); - } - - Base.when.apply( Base, promises ).then(function() { - results.push.apply( results, arr ); - deferred.resolve(); - }, deferred.reject ); - }); - } - - return deferred.promise(); - }, - - destroy: function() { - var elem = this.elem; - - // 还没 init 就调用 destroy - if (!elem) { - return; - } - - elem.off( 'dragenter', this.dragEnterHandler ); - elem.off( 'dragover', this.dragOverHandler ); - elem.off( 'dragleave', this.dragLeaveHandler ); - elem.off( 'drop', this.dropHandler ); - - if ( this.options.disableGlobalDnd ) { - $( document ).off( 'dragover', this.dragOverHandler ); - $( document ).off( 'drop', this.dropHandler ); - } - } - }); - }); - - /** - * @fileOverview FilePaste - */ - define('runtime/html5/filepaste',[ - 'base', - 'runtime/html5/runtime', - 'lib/file' - ], function( Base, Html5Runtime, File ) { - - return Html5Runtime.register( 'FilePaste', { - init: function() { - var opts = this.options, - elem = this.elem = opts.container, - accept = '.*', - arr, i, len, item; - - // accetp的mimeTypes中生成匹配正则。 - if ( opts.accept ) { - arr = []; - - for ( i = 0, len = opts.accept.length; i < len; i++ ) { - item = opts.accept[ i ].mimeTypes; - item && arr.push( item ); - } - - if ( arr.length ) { - accept = arr.join(','); - accept = accept.replace( /,/g, '|' ).replace( /\*/g, '.*' ); - } - } - this.accept = accept = new RegExp( accept, 'i' ); - this.hander = Base.bindFn( this._pasteHander, this ); - elem.on( 'paste', this.hander ); - }, - - _pasteHander: function( e ) { - var allowed = [], - ruid = this.getRuid(), - items, item, blob, i, len; - - e = e.originalEvent || e; - items = e.clipboardData.items; - - for ( i = 0, len = items.length; i < len; i++ ) { - item = items[ i ]; - - if ( item.kind !== 'file' || !(blob = item.getAsFile()) ) { - continue; - } - - allowed.push( new File( ruid, blob ) ); - } - - if ( allowed.length ) { - // 不阻止非文件粘贴(文字粘贴)的事件冒泡 - e.preventDefault(); - e.stopPropagation(); - this.trigger( 'paste', allowed ); - } - }, - - destroy: function() { - this.elem.off( 'paste', this.hander ); - } - }); - }); - - /** - * @fileOverview FilePicker - */ - define('runtime/html5/filepicker',[ - 'base', - 'runtime/html5/runtime' - ], function( Base, Html5Runtime ) { - - var $ = Base.$; - - return Html5Runtime.register( 'FilePicker', { - init: function() { - var container = this.getRuntime().getContainer(), - me = this, - owner = me.owner, - opts = me.options, - label = this.label = $( document.createElement('label') ), - input = this.input = $( document.createElement('input') ), - arr, i, len, mouseHandler; - - input.attr( 'type', 'file' ); - input.attr( 'name', opts.name ); - input.addClass('webuploader-element-invisible'); - - label.on( 'click', function() { - input.trigger('click'); - }); - - label.css({ - opacity: 0, - width: '100%', - height: '100%', - display: 'block', - cursor: 'pointer', - background: '#ffffff' - }); - - if ( opts.multiple ) { - input.attr( 'multiple', 'multiple' ); - } - - // @todo Firefox不支持单独指定后缀 - if ( opts.accept && opts.accept.length > 0 ) { - arr = []; - - for ( i = 0, len = opts.accept.length; i < len; i++ ) { - arr.push( opts.accept[ i ].mimeTypes ); - } - - input.attr( 'accept', arr.join(',') ); - } - - container.append( input ); - container.append( label ); - - mouseHandler = function( e ) { - owner.trigger( e.type ); - }; - - input.on( 'change', function( e ) { - var fn = arguments.callee, - clone; - - me.files = e.target.files; - - // reset input - clone = this.cloneNode( true ); - clone.value = null; - this.parentNode.replaceChild( clone, this ); - - input.off(); - input = $( clone ).on( 'change', fn ) - .on( 'mouseenter mouseleave', mouseHandler ); - - owner.trigger('change'); - }); - - label.on( 'mouseenter mouseleave', mouseHandler ); - - }, - - - getFiles: function() { - return this.files; - }, - - destroy: function() { - this.input.off(); - this.label.off(); - } - }); - }); - /** - * @fileOverview Transport - * @todo 支持chunked传输,优势: - * 可以将大文件分成小块,挨个传输,可以提高大文件成功率,当失败的时候,也只需要重传那小部分, - * 而不需要重头再传一次。另外断点续传也需要用chunked方式。 - */ - define('runtime/html5/transport',[ - 'base', - 'runtime/html5/runtime' - ], function( Base, Html5Runtime ) { - - var noop = Base.noop, - $ = Base.$; - - return Html5Runtime.register( 'Transport', { - init: function() { - this._status = 0; - this._response = null; - }, - - send: function() { - var owner = this.owner, - opts = this.options, - xhr = this._initAjax(), - blob = owner._blob, - server = opts.server, - formData, binary, fr; - - if ( opts.sendAsBinary ) { - server += (/\?/.test( server ) ? '&' : '?') + - $.param( owner._formData ); - - binary = blob.getSource(); - } else { - formData = new FormData(); - $.each( owner._formData, function( k, v ) { - formData.append( k, v ); - }); - - formData.append( opts.fileVal, blob.getSource(), - opts.filename || owner._formData.name || '' ); - } - - if ( opts.withCredentials && 'withCredentials' in xhr ) { - xhr.open( opts.method, server, true ); - xhr.withCredentials = true; - } else { - xhr.open( opts.method, server ); - } - - this._setRequestHeader( xhr, opts.headers ); - - if ( binary ) { - // 强制设置成 content-type 为文件流。 - xhr.overrideMimeType && - xhr.overrideMimeType('application/octet-stream'); - - // android直接发送blob会导致服务端接收到的是空文件。 - // bug详情。 - // https://code.google.com/p/android/issues/detail?id=39882 - // 所以先用fileReader读取出来再通过arraybuffer的方式发送。 - if ( Base.os.android ) { - fr = new FileReader(); - - fr.onload = function() { - xhr.send( this.result ); - fr = fr.onload = null; - }; - - fr.readAsArrayBuffer( binary ); - } else { - xhr.send( binary ); - } - } else { - xhr.send( formData ); - } - }, - - getResponse: function() { - return this._response; - }, - - getResponseAsJson: function() { - return this._parseJson( this._response ); - }, - - getStatus: function() { - return this._status; - }, - - abort: function() { - var xhr = this._xhr; - - if ( xhr ) { - xhr.upload.onprogress = noop; - xhr.onreadystatechange = noop; - xhr.abort(); - - this._xhr = xhr = null; - } - }, - - destroy: function() { - this.abort(); - }, - - _initAjax: function() { - var me = this, - xhr = new XMLHttpRequest(), - opts = this.options; - - if ( opts.withCredentials && !('withCredentials' in xhr) && - typeof XDomainRequest !== 'undefined' ) { - xhr = new XDomainRequest(); - } - - xhr.upload.onprogress = function( e ) { - var percentage = 0; - - if ( e.lengthComputable ) { - percentage = e.loaded / e.total; - } - - return me.trigger( 'progress', percentage ); - }; - - xhr.onreadystatechange = function() { - - if ( xhr.readyState !== 4 ) { - return; - } - - xhr.upload.onprogress = noop; - xhr.onreadystatechange = noop; - me._xhr = null; - me._status = xhr.status; - - if ( xhr.status >= 200 && xhr.status < 300 ) { - me._response = xhr.responseText; - return me.trigger('load'); - } else if ( xhr.status >= 500 && xhr.status < 600 ) { - me._response = xhr.responseText; - return me.trigger( 'error', 'server' ); - } - - - return me.trigger( 'error', me._status ? 'http' : 'abort' ); - }; - - me._xhr = xhr; - return xhr; - }, - - _setRequestHeader: function( xhr, headers ) { - $.each( headers, function( key, val ) { - xhr.setRequestHeader( key, val ); - }); - }, - - _parseJson: function( str ) { - var json; - - try { - json = JSON.parse( str ); - } catch ( ex ) { - json = {}; - } - - return json; - } - }); - }); - /** - * @fileOverview FlashRuntime - */ - define('runtime/flash/runtime',[ - 'base', - 'runtime/runtime', - 'runtime/compbase' - ], function( Base, Runtime, CompBase ) { - - var $ = Base.$, - type = 'flash', - components = {}; - - - function getFlashVersion() { - var version; - - try { - version = navigator.plugins[ 'Shockwave Flash' ]; - version = version.description; - } catch ( ex ) { - try { - version = new ActiveXObject('ShockwaveFlash.ShockwaveFlash') - .GetVariable('$version'); - } catch ( ex2 ) { - version = '0.0'; - } - } - version = version.match( /\d+/g ); - return parseFloat( version[ 0 ] + '.' + version[ 1 ], 10 ); - } - - function FlashRuntime() { - var pool = {}, - clients = {}, - destroy = this.destroy, - me = this, - jsreciver = Base.guid('webuploader_'); - - Runtime.apply( me, arguments ); - me.type = type; - - - // 这个方法的调用者,实际上是RuntimeClient - me.exec = function( comp, fn/*, args...*/ ) { - var client = this, - uid = client.uid, - args = Base.slice( arguments, 2 ), - instance; - - clients[ uid ] = client; - - if ( components[ comp ] ) { - if ( !pool[ uid ] ) { - pool[ uid ] = new components[ comp ]( client, me ); - } - - instance = pool[ uid ]; - - if ( instance[ fn ] ) { - return instance[ fn ].apply( instance, args ); - } - } - - return me.flashExec.apply( client, arguments ); - }; - - function handler( evt, obj ) { - var type = evt.type || evt, - parts, uid; - - parts = type.split('::'); - uid = parts[ 0 ]; - type = parts[ 1 ]; - - // console.log.apply( console, arguments ); - - if ( type === 'Ready' && uid === me.uid ) { - me.trigger('ready'); - } else if ( clients[ uid ] ) { - clients[ uid ].trigger( type.toLowerCase(), evt, obj ); - } - - // Base.log( evt, obj ); - } - - // flash的接受器。 - window[ jsreciver ] = function() { - var args = arguments; - - // 为了能捕获得到。 - setTimeout(function() { - handler.apply( null, args ); - }, 1 ); - }; - - this.jsreciver = jsreciver; - - this.destroy = function() { - // @todo 删除池子中的所有实例 - return destroy && destroy.apply( this, arguments ); - }; - - this.flashExec = function( comp, fn ) { - var flash = me.getFlash(), - args = Base.slice( arguments, 2 ); - - return flash.exec( this.uid, comp, fn, args ); - }; - - // @todo - } - - Base.inherits( Runtime, { - constructor: FlashRuntime, - - init: function() { - var container = this.getContainer(), - opts = this.options, - html; - - // if not the minimal height, shims are not initialized - // in older browsers (e.g FF3.6, IE6,7,8, Safari 4.0,5.0, etc) - container.css({ - position: 'absolute', - top: '-8px', - left: '-8px', - width: '9px', - height: '9px', - overflow: 'hidden' - }); - - // insert flash object - html = '' + - '' + - '' + - '' + - ''; - - container.html( html ); - }, - - getFlash: function() { - if ( this._flash ) { - return this._flash; - } - - this._flash = $( '#' + this.uid ).get( 0 ); - return this._flash; - } - - }); - - FlashRuntime.register = function( name, component ) { - component = components[ name ] = Base.inherits( CompBase, $.extend({ - - // @todo fix this later - flashExec: function() { - var owner = this.owner, - runtime = this.getRuntime(); - - return runtime.flashExec.apply( owner, arguments ); - } - }, component ) ); - - return component; - }; - - if ( getFlashVersion() >= 11.4 ) { - Runtime.addRuntime( type, FlashRuntime ); - } - - return FlashRuntime; - }); - /** - * @fileOverview FilePicker - */ - define('runtime/flash/filepicker',[ - 'base', - 'runtime/flash/runtime' - ], function( Base, FlashRuntime ) { - var $ = Base.$; - - return FlashRuntime.register( 'FilePicker', { - init: function( opts ) { - var copy = $.extend({}, opts ), - len, i; - - // 修复Flash再没有设置title的情况下无法弹出flash文件选择框的bug. - len = copy.accept && copy.accept.length; - for ( i = 0; i < len; i++ ) { - if ( !copy.accept[ i ].title ) { - copy.accept[ i ].title = 'Files'; - } - } - - delete copy.button; - delete copy.id; - delete copy.container; - - this.flashExec( 'FilePicker', 'init', copy ); - }, - - destroy: function() { - this.flashExec( 'FilePicker', 'destroy' ); - } - }); - }); - /** - * @fileOverview Transport flash实现 - */ - define('runtime/flash/transport',[ - 'base', - 'runtime/flash/runtime', - 'runtime/client' - ], function( Base, FlashRuntime, RuntimeClient ) { - var $ = Base.$; - - return FlashRuntime.register( 'Transport', { - init: function() { - this._status = 0; - this._response = null; - this._responseJson = null; - }, - - send: function() { - var owner = this.owner, - opts = this.options, - xhr = this._initAjax(), - blob = owner._blob, - server = opts.server, - binary; - - xhr.connectRuntime( blob.ruid ); - - if ( opts.sendAsBinary ) { - server += (/\?/.test( server ) ? '&' : '?') + - $.param( owner._formData ); - - binary = blob.uid; - } else { - $.each( owner._formData, function( k, v ) { - xhr.exec( 'append', k, v ); - }); - - xhr.exec( 'appendBlob', opts.fileVal, blob.uid, - opts.filename || owner._formData.name || '' ); - } - - this._setRequestHeader( xhr, opts.headers ); - xhr.exec( 'send', { - method: opts.method, - url: server, - forceURLStream: opts.forceURLStream, - mimeType: 'application/octet-stream' - }, binary ); - }, - - getStatus: function() { - return this._status; - }, - - getResponse: function() { - return this._response || ''; - }, - - getResponseAsJson: function() { - return this._responseJson; - }, - - abort: function() { - var xhr = this._xhr; - - if ( xhr ) { - xhr.exec('abort'); - xhr.destroy(); - this._xhr = xhr = null; - } - }, - - destroy: function() { - this.abort(); - }, - - _initAjax: function() { - var me = this, - xhr = new RuntimeClient('XMLHttpRequest'); - - xhr.on( 'uploadprogress progress', function( e ) { - var percent = e.loaded / e.total; - percent = Math.min( 1, Math.max( 0, percent ) ); - return me.trigger( 'progress', percent ); - }); - - xhr.on( 'load', function() { - var status = xhr.exec('getStatus'), - readBody = false, - err = '', - p; - - xhr.off(); - me._xhr = null; - - if ( status >= 200 && status < 300 ) { - readBody = true; - } else if ( status >= 500 && status < 600 ) { - readBody = true; - err = 'server'; - } else { - err = 'http'; - } - - if ( readBody ) { - me._response = xhr.exec('getResponse'); - me._response = decodeURIComponent( me._response ); - - // flash 处理可能存在 bug, 没辙只能靠 js 了 - // try { - // me._responseJson = xhr.exec('getResponseAsJson'); - // } catch ( error ) { - - p = window.JSON && window.JSON.parse || function( s ) { - try { - return new Function('return ' + s).call(); - } catch ( err ) { - return {}; - } - }; - me._responseJson = me._response ? p(me._response) : {}; - - // } - } - - xhr.destroy(); - xhr = null; - - return err ? me.trigger( 'error', err ) : me.trigger('load'); - }); - - xhr.on( 'error', function() { - xhr.off(); - me._xhr = null; - me.trigger( 'error', 'http' ); - }); - - me._xhr = xhr; - return xhr; - }, - - _setRequestHeader: function( xhr, headers ) { - $.each( headers, function( key, val ) { - xhr.exec( 'setRequestHeader', key, val ); - }); - } - }); - }); - /** - * @fileOverview 没有图像处理的版本。 - */ - define('preset/withoutimage',[ - 'base', - - // widgets - 'widgets/filednd', - 'widgets/filepaste', - 'widgets/filepicker', - 'widgets/queue', - 'widgets/runtime', - 'widgets/upload', - 'widgets/validator', - - // runtimes - // html5 - 'runtime/html5/blob', - 'runtime/html5/dnd', - 'runtime/html5/filepaste', - 'runtime/html5/filepicker', - 'runtime/html5/transport', - - // flash - 'runtime/flash/filepicker', - 'runtime/flash/transport' - ], function( Base ) { - return Base; - }); - define('webuploader',[ - 'preset/withoutimage' - ], function( preset ) { - return preset; - }); - return require('webuploader'); -}); diff --git a/static/plugins/webuploader/webuploader.withoutimage.min.js b/static/plugins/webuploader/webuploader.withoutimage.min.js deleted file mode 100644 index 84ba1987..00000000 --- a/static/plugins/webuploader/webuploader.withoutimage.min.js +++ /dev/null @@ -1,2 +0,0 @@ -/* WebUploader 0.1.5 */!function(a,b){var c,d={},e=function(a,b){var c,d,e;if("string"==typeof a)return h(a);for(c=[],d=a.length,e=0;d>e;e++)c.push(h(a[e]));return b.apply(null,c)},f=function(a,b,c){2===arguments.length&&(c=b,b=null),e(b||[],function(){g(a,c,arguments)})},g=function(a,b,c){var f,g={exports:b};"function"==typeof b&&(c.length||(c=[e,g.exports,g]),f=b.apply(null,c),void 0!==f&&(g.exports=f)),d[a]=g.exports},h=function(b){var c=d[b]||a[b];if(!c)throw new Error("`"+b+"` is undefined");return c},i=function(a){var b,c,e,f,g,h;h=function(a){return a&&a.charAt(0).toUpperCase()+a.substr(1)};for(b in d)if(c=a,d.hasOwnProperty(b)){for(e=b.split("/"),g=h(e.pop());f=h(e.shift());)c[f]=c[f]||{},c=c[f];c[g]=d[b]}return a},j=function(c){return a.__dollar=c,i(b(a,f,e))};"object"==typeof module&&"object"==typeof module.exports?module.exports=j():"function"==typeof define&&define.amd?define(["jquery"],j):(c=a.WebUploader,a.WebUploader=j(),a.WebUploader.noConflict=function(){a.WebUploader=c})}(window,function(a,b,c){return b("dollar-third",[],function(){var b=a.__dollar||a.jQuery||a.Zepto;if(!b)throw new Error("jQuery or Zepto not found!");return b}),b("dollar",["dollar-third"],function(a){return a}),b("promise-third",["dollar"],function(a){return{Deferred:a.Deferred,when:a.when,isPromise:function(a){return a&&"function"==typeof a.then}}}),b("promise",["promise-third"],function(a){return a}),b("base",["dollar","promise"],function(b,c){function d(a){return function(){return h.apply(a,arguments)}}function e(a,b){return function(){return a.apply(b,arguments)}}function f(a){var b;return Object.create?Object.create(a):(b=function(){},b.prototype=a,new b)}var g=function(){},h=Function.call;return{version:"0.1.5",$:b,Deferred:c.Deferred,isPromise:c.isPromise,when:c.when,browser:function(a){var b={},c=a.match(/WebKit\/([\d.]+)/),d=a.match(/Chrome\/([\d.]+)/)||a.match(/CriOS\/([\d.]+)/),e=a.match(/MSIE\s([\d\.]+)/)||a.match(/(?:trident)(?:.*rv:([\w.]+))?/i),f=a.match(/Firefox\/([\d.]+)/),g=a.match(/Safari\/([\d.]+)/),h=a.match(/OPR\/([\d.]+)/);return c&&(b.webkit=parseFloat(c[1])),d&&(b.chrome=parseFloat(d[1])),e&&(b.ie=parseFloat(e[1])),f&&(b.firefox=parseFloat(f[1])),g&&(b.safari=parseFloat(g[1])),h&&(b.opera=parseFloat(h[1])),b}(navigator.userAgent),os:function(a){var b={},c=a.match(/(?:Android);?[\s\/]+([\d.]+)?/),d=a.match(/(?:iPad|iPod|iPhone).*OS\s([\d_]+)/);return c&&(b.android=parseFloat(c[1])),d&&(b.ios=parseFloat(d[1].replace(/_/g,"."))),b}(navigator.userAgent),inherits:function(a,c,d){var e;return"function"==typeof c?(e=c,c=null):e=c&&c.hasOwnProperty("constructor")?c.constructor:function(){return a.apply(this,arguments)},b.extend(!0,e,a,d||{}),e.__super__=a.prototype,e.prototype=f(a.prototype),c&&b.extend(!0,e.prototype,c),e},noop:g,bindFn:e,log:function(){return a.console?e(console.log,console):g}(),nextTick:function(){return function(a){setTimeout(a,1)}}(),slice:d([].slice),guid:function(){var a=0;return function(b){for(var c=(+new Date).toString(32),d=0;5>d;d++)c+=Math.floor(65535*Math.random()).toString(32);return(b||"wu_")+c+(a++).toString(32)}}(),formatSize:function(a,b,c){var d;for(c=c||["B","K","M","G","TB"];(d=c.shift())&&a>1024;)a/=1024;return("B"===d?a:a.toFixed(b||2))+d}}}),b("mediator",["base"],function(a){function b(a,b,c,d){return f.grep(a,function(a){return!(!a||b&&a.e!==b||c&&a.cb!==c&&a.cb._cb!==c||d&&a.ctx!==d)})}function c(a,b,c){f.each((a||"").split(h),function(a,d){c(d,b)})}function d(a,b){for(var c,d=!1,e=-1,f=a.length;++e1?void(d.isPlainObject(b)&&d.isPlainObject(c[a])?d.extend(c[a],b):c[a]=b):a?c[a]:c},getStats:function(){var a=this.request("get-stats");return a?{successNum:a.numOfSuccess,progressNum:a.numOfProgress,cancelNum:a.numOfCancel,invalidNum:a.numOfInvalid,uploadFailNum:a.numOfUploadFailed,queueNum:a.numOfQueue,interruptNum:a.numofInterrupt}:{}},trigger:function(a){var c=[].slice.call(arguments,1),e=this.options,f="on"+a.substring(0,1).toUpperCase()+a.substring(1);return b.trigger.apply(this,arguments)===!1||d.isFunction(e[f])&&e[f].apply(this,c)===!1||d.isFunction(this[f])&&this[f].apply(this,c)===!1||b.trigger.apply(b,[this,a].concat(c))===!1?!1:!0},destroy:function(){this.request("destroy",arguments),this.off()},request:a.noop}),a.create=c.create=function(a){return new c(a)},a.Uploader=c,c}),b("runtime/runtime",["base","mediator"],function(a,b){function c(b){this.options=d.extend({container:document.body},b),this.uid=a.guid("rt_")}var d=a.$,e={},f=function(a){for(var b in a)if(a.hasOwnProperty(b))return b;return null};return d.extend(c.prototype,{getContainer:function(){var a,b,c=this.options;return this._container?this._container:(a=d(c.container||document.body),b=d(document.createElement("div")),b.attr("id","rt_"+this.uid),b.css({position:"absolute",top:"0px",left:"0px",width:"1px",height:"1px",overflow:"hidden"}),a.append(b),a.addClass("webuploader-container"),this._container=b,this._parent=a,b)},init:a.noop,exec:a.noop,destroy:function(){this._container&&this._container.remove(),this._parent&&this._parent.removeClass("webuploader-container"),this.off()}}),c.orders="html5,flash",c.addRuntime=function(a,b){e[a]=b},c.hasRuntime=function(a){return!!(a?e[a]:f(e))},c.create=function(a,b){var g,h;if(b=b||c.orders,d.each(b.split(/\s*,\s*/g),function(){return e[this]?(g=this,!1):void 0}),g=g||f(e),!g)throw new Error("Runtime Error");return h=new e[g](a)},b.installTo(c.prototype),c}),b("runtime/client",["base","mediator","runtime/runtime"],function(a,b,c){function d(b,d){var f,g=a.Deferred();this.uid=a.guid("client_"),this.runtimeReady=function(a){return g.done(a)},this.connectRuntime=function(b,h){if(f)throw new Error("already connected!");return g.done(h),"string"==typeof b&&e.get(b)&&(f=e.get(b)),f=f||e.get(null,d),f?(a.$.extend(f.options,b),f.__promise.then(g.resolve),f.__client++):(f=c.create(b,b.runtimeOrder),f.__promise=g.promise(),f.once("ready",g.resolve),f.init(),e.add(f),f.__client=1),d&&(f.__standalone=d),f},this.getRuntime=function(){return f},this.disconnectRuntime=function(){f&&(f.__client--,f.__client<=0&&(e.remove(f),delete f.__promise,f.destroy()),f=null)},this.exec=function(){if(f){var c=a.slice(arguments);return b&&c.unshift(b),f.exec.apply(this,c)}},this.getRuid=function(){return f&&f.uid},this.destroy=function(a){return function(){a&&a.apply(this,arguments),this.trigger("destroy"),this.off(),this.exec("destroy"),this.disconnectRuntime()}}(this.destroy)}var e;return e=function(){var a={};return{add:function(b){a[b.uid]=b},get:function(b,c){var d;if(b)return a[b];for(d in a)if(!c||!a[d].__standalone)return a[d];return null},remove:function(b){delete a[b.uid]}}}(),b.installTo(d.prototype),d}),b("lib/dnd",["base","mediator","runtime/client"],function(a,b,c){function d(a){a=this.options=e.extend({},d.options,a),a.container=e(a.container),a.container.length&&c.call(this,"DragAndDrop")}var e=a.$;return d.options={accept:null,disableGlobalDnd:!1},a.inherits(c,{constructor:d,init:function(){var a=this;a.connectRuntime(a.options,function(){a.exec("init"),a.trigger("ready")})}}),b.installTo(d.prototype),d}),b("widgets/widget",["base","uploader"],function(a,b){function c(a){if(!a)return!1;var b=a.length,c=e.type(a);return 1===a.nodeType&&b?!0:"array"===c||"function"!==c&&"string"!==c&&(0===b||"number"==typeof b&&b>0&&b-1 in a)}function d(a){this.owner=a,this.options=a.options}var e=a.$,f=b.prototype._init,g=b.prototype.destroy,h={},i=[];return e.extend(d.prototype,{init:a.noop,invoke:function(a,b){var c=this.responseMap;return c&&a in c&&c[a]in this&&e.isFunction(this[c[a]])?this[c[a]].apply(this,b):h},request:function(){return this.owner.request.apply(this.owner,arguments)}}),e.extend(b.prototype,{_init:function(){var a=this,b=a._widgets=[],c=a.options.disableWidgets||"";return e.each(i,function(d,e){(!c||!~c.indexOf(e._name))&&b.push(new e(a))}),f.apply(a,arguments)},request:function(b,d,e){var f,g,i,j,k=0,l=this._widgets,m=l&&l.length,n=[],o=[];for(d=c(d)?d:[d];m>k;k++)f=l[k],g=f.invoke(b,d),g!==h&&(a.isPromise(g)?o.push(g):n.push(g));return e||o.length?(i=a.when.apply(a,o),j=i.pipe?"pipe":"then",i[j](function(){var b=a.Deferred(),c=arguments;return 1===c.length&&(c=c[0]),setTimeout(function(){b.resolve(c)},1),b.promise()})[e?j:"done"](e||a.noop)):n[0]},destroy:function(){g.apply(this,arguments),this._widgets=null}}),b.register=d.register=function(b,c){var f,g={init:"init",destroy:"destroy",name:"anonymous"};return 1===arguments.length?(c=b,e.each(c,function(a){return"_"===a[0]||"name"===a?void("name"===a&&(g.name=c.name)):void(g[a.replace(/[A-Z]/g,"-$&").toLowerCase()]=a)})):g=e.extend(g,b),c.responseMap=g,f=a.inherits(d,c),f._name=g.name,i.push(f),f},b.unRegister=d.unRegister=function(a){if(a&&"anonymous"!==a)for(var b=i.length;b--;)i[b]._name===a&&i.splice(b,1)},d}),b("widgets/filednd",["base","uploader","lib/dnd","widgets/widget"],function(a,b,c){var d=a.$;return b.options.dnd="",b.register({name:"dnd",init:function(b){if(b.dnd&&"html5"===this.request("predict-runtime-type")){var e,f=this,g=a.Deferred(),h=d.extend({},{disableGlobalDnd:b.disableGlobalDnd,container:b.dnd,accept:b.accept});return this.dnd=e=new c(h),e.once("ready",g.resolve),e.on("drop",function(a){f.request("add-file",[a])}),e.on("accept",function(a){return f.owner.trigger("dndAccept",a)}),e.init(),g.promise()}},destroy:function(){this.dnd&&this.dnd.destroy()}})}),b("lib/filepaste",["base","mediator","runtime/client"],function(a,b,c){function d(a){a=this.options=e.extend({},a),a.container=e(a.container||document.body),c.call(this,"FilePaste")}var e=a.$;return a.inherits(c,{constructor:d,init:function(){var a=this;a.connectRuntime(a.options,function(){a.exec("init"),a.trigger("ready")})}}),b.installTo(d.prototype),d}),b("widgets/filepaste",["base","uploader","lib/filepaste","widgets/widget"],function(a,b,c){var d=a.$;return b.register({name:"paste",init:function(b){if(b.paste&&"html5"===this.request("predict-runtime-type")){var e,f=this,g=a.Deferred(),h=d.extend({},{container:b.paste,accept:b.accept});return this.paste=e=new c(h),e.once("ready",g.resolve),e.on("paste",function(a){f.owner.request("add-file",[a])}),e.init(),g.promise()}},destroy:function(){this.paste&&this.paste.destroy()}})}),b("lib/blob",["base","runtime/client"],function(a,b){function c(a,c){var d=this;d.source=c,d.ruid=a,this.size=c.size||0,this.type=!c.type&&this.ext&&~"jpg,jpeg,png,gif,bmp".indexOf(this.ext)?"image/"+("jpg"===this.ext?"jpeg":this.ext):c.type||"application/octet-stream",b.call(d,"Blob"),this.uid=c.uid||this.uid,a&&d.connectRuntime(a)}return a.inherits(b,{constructor:c,slice:function(a,b){return this.exec("slice",a,b)},getSource:function(){return this.source}}),c}),b("lib/file",["base","lib/blob"],function(a,b){function c(a,c){var f;this.name=c.name||"untitled"+d++,f=e.exec(c.name)?RegExp.$1.toLowerCase():"",!f&&c.type&&(f=/\/(jpg|jpeg|png|gif|bmp)$/i.exec(c.type)?RegExp.$1.toLowerCase():"",this.name+="."+f),this.ext=f,this.lastModifiedDate=c.lastModifiedDate||(new Date).toLocaleString(),b.apply(this,arguments)}var d=1,e=/\.([^.]+)$/;return a.inherits(b,c)}),b("lib/filepicker",["base","runtime/client","lib/file"],function(b,c,d){function e(a){if(a=this.options=f.extend({},e.options,a),a.container=f(a.id),!a.container.length)throw new Error("按钮指定错误");a.innerHTML=a.innerHTML||a.label||a.container.html()||"",a.button=f(a.button||document.createElement("div")),a.button.html(a.innerHTML),a.container.html(a.button),c.call(this,"FilePicker",!0)}var f=b.$;return e.options={button:null,container:null,label:null,innerHTML:null,multiple:!0,accept:null,name:"file"},b.inherits(c,{constructor:e,init:function(){var c=this,e=c.options,g=e.button;g.addClass("webuploader-pick"),c.on("all",function(a){var b;switch(a){case"mouseenter":g.addClass("webuploader-pick-hover");break;case"mouseleave":g.removeClass("webuploader-pick-hover");break;case"change":b=c.exec("getFiles"),c.trigger("select",f.map(b,function(a){return a=new d(c.getRuid(),a),a._refer=e.container,a}),e.container)}}),c.connectRuntime(e,function(){c.refresh(),c.exec("init",e),c.trigger("ready")}),this._resizeHandler=b.bindFn(this.refresh,this),f(a).on("resize",this._resizeHandler)},refresh:function(){var a=this.getRuntime().getContainer(),b=this.options.button,c=b.outerWidth?b.outerWidth():b.width(),d=b.outerHeight?b.outerHeight():b.height(),e=b.offset();c&&d&&a.css({bottom:"auto",right:"auto",width:c+"px",height:d+"px"}).offset(e)},enable:function(){var a=this.options.button;a.removeClass("webuploader-pick-disable"),this.refresh()},disable:function(){var a=this.options.button;this.getRuntime().getContainer().css({top:"-99999px"}),a.addClass("webuploader-pick-disable")},destroy:function(){var b=this.options.button;f(a).off("resize",this._resizeHandler),b.removeClass("webuploader-pick-disable webuploader-pick-hover webuploader-pick")}}),e}),b("widgets/filepicker",["base","uploader","lib/filepicker","widgets/widget"],function(a,b,c){var d=a.$;return d.extend(b.options,{pick:null,accept:null}),b.register({name:"picker",init:function(a){return this.pickers=[],a.pick&&this.addBtn(a.pick)},refresh:function(){d.each(this.pickers,function(){this.refresh()})},addBtn:function(b){var e=this,f=e.options,g=f.accept,h=[];if(b)return d.isPlainObject(b)||(b={id:b}),d(b.id).each(function(){var i,j,k;k=a.Deferred(),i=d.extend({},b,{accept:d.isPlainObject(g)?[g]:g,swf:f.swf,runtimeOrder:f.runtimeOrder,id:this}),j=new c(i),j.once("ready",k.resolve),j.on("select",function(a){e.owner.request("add-file",[a])}),j.init(),e.pickers.push(j),h.push(k.promise())}),a.when.apply(a,h)},disable:function(){d.each(this.pickers,function(){this.disable()})},enable:function(){d.each(this.pickers,function(){this.enable()})},destroy:function(){d.each(this.pickers,function(){this.destroy()}),this.pickers=null}})}),b("file",["base","mediator"],function(a,b){function c(){return f+g++}function d(a){this.name=a.name||"Untitled",this.size=a.size||0,this.type=a.type||"application/octet-stream",this.lastModifiedDate=a.lastModifiedDate||1*new Date,this.id=c(),this.ext=h.exec(this.name)?RegExp.$1:"",this.statusText="",i[this.id]=d.Status.INITED,this.source=a,this.loaded=0,this.on("error",function(a){this.setStatus(d.Status.ERROR,a)})}var e=a.$,f="WU_FILE_",g=0,h=/\.([^.]+)$/,i={};return e.extend(d.prototype,{setStatus:function(a,b){var c=i[this.id];"undefined"!=typeof b&&(this.statusText=b),a!==c&&(i[this.id]=a,this.trigger("statuschange",a,c))},getStatus:function(){return i[this.id]},getSource:function(){return this.source},destroy:function(){this.off(),delete i[this.id]}}),b.installTo(d.prototype),d.Status={INITED:"inited",QUEUED:"queued",PROGRESS:"progress",ERROR:"error",COMPLETE:"complete",CANCELLED:"cancelled",INTERRUPT:"interrupt",INVALID:"invalid"},d}),b("queue",["base","mediator","file"],function(a,b,c){function d(){this.stats={numOfQueue:0,numOfSuccess:0,numOfCancel:0,numOfProgress:0,numOfUploadFailed:0,numOfInvalid:0,numofDeleted:0,numofInterrupt:0},this._queue=[],this._map={}}var e=a.$,f=c.Status;return e.extend(d.prototype,{append:function(a){return this._queue.push(a),this._fileAdded(a),this},prepend:function(a){return this._queue.unshift(a),this._fileAdded(a),this},getFile:function(a){return"string"!=typeof a?a:this._map[a]},fetch:function(a){var b,c,d=this._queue.length;for(a=a||f.QUEUED,b=0;d>b;b++)if(c=this._queue[b],a===c.getStatus())return c;return null},sort:function(a){"function"==typeof a&&this._queue.sort(a)},getFiles:function(){for(var a,b=[].slice.call(arguments,0),c=[],d=0,f=this._queue.length;f>d;d++)a=this._queue[d],(!b.length||~e.inArray(a.getStatus(),b))&&c.push(a);return c},removeFile:function(a){var b=this._map[a.id];b&&(delete this._map[a.id],a.destroy(),this.stats.numofDeleted++)},_fileAdded:function(a){var b=this,c=this._map[a.id];c||(this._map[a.id]=a,a.on("statuschange",function(a,c){b._onFileStatusChange(a,c)}))},_onFileStatusChange:function(a,b){var c=this.stats;switch(b){case f.PROGRESS:c.numOfProgress--;break;case f.QUEUED:c.numOfQueue--;break;case f.ERROR:c.numOfUploadFailed--;break;case f.INVALID:c.numOfInvalid--;break;case f.INTERRUPT:c.numofInterrupt--}switch(a){case f.QUEUED:c.numOfQueue++;break;case f.PROGRESS:c.numOfProgress++;break;case f.ERROR:c.numOfUploadFailed++;break;case f.COMPLETE:c.numOfSuccess++;break;case f.CANCELLED:c.numOfCancel++;break;case f.INVALID:c.numOfInvalid++;break;case f.INTERRUPT:c.numofInterrupt++}}}),b.installTo(d.prototype),d}),b("widgets/queue",["base","uploader","queue","file","lib/file","runtime/client","widgets/widget"],function(a,b,c,d,e,f){var g=a.$,h=/\.\w+$/,i=d.Status;return b.register({name:"queue",init:function(b){var d,e,h,i,j,k,l,m=this;if(g.isPlainObject(b.accept)&&(b.accept=[b.accept]),b.accept){for(j=[],h=0,e=b.accept.length;e>h;h++)i=b.accept[h].extensions,i&&j.push(i);j.length&&(k="\\."+j.join(",").replace(/,/g,"$|\\.").replace(/\*/g,".*")+"$"),m.accept=new RegExp(k,"i")}return m.queue=new c,m.stats=m.queue.stats,"html5"===this.request("predict-runtime-type")?(d=a.Deferred(),this.placeholder=l=new f("Placeholder"),l.connectRuntime({runtimeOrder:"html5"},function(){m._ruid=l.getRuid(),d.resolve()}),d.promise()):void 0},_wrapFile:function(a){if(!(a instanceof d)){if(!(a instanceof e)){if(!this._ruid)throw new Error("Can't add external files.");a=new e(this._ruid,a)}a=new d(a)}return a},acceptFile:function(a){var b=!a||!a.size||this.accept&&h.exec(a.name)&&!this.accept.test(a.name);return!b},_addFile:function(a){var b=this;return a=b._wrapFile(a),b.owner.trigger("beforeFileQueued",a)?b.acceptFile(a)?(b.queue.append(a),b.owner.trigger("fileQueued",a),a):void b.owner.trigger("error","Q_TYPE_DENIED",a):void 0},getFile:function(a){return this.queue.getFile(a)},addFile:function(a){var b=this;a.length||(a=[a]),a=g.map(a,function(a){return b._addFile(a)}),b.owner.trigger("filesQueued",a),b.options.auto&&setTimeout(function(){b.request("start-upload")},20)},getStats:function(){return this.stats},removeFile:function(a,b){var c=this;a=a.id?a:c.queue.getFile(a),this.request("cancel-file",a),b&&this.queue.removeFile(a)},getFiles:function(){return this.queue.getFiles.apply(this.queue,arguments)},fetchFile:function(){return this.queue.fetch.apply(this.queue,arguments)},retry:function(a,b){var c,d,e,f=this;if(a)return a=a.id?a:f.queue.getFile(a),a.setStatus(i.QUEUED),void(b||f.request("start-upload"));for(c=f.queue.getFiles(i.ERROR),d=0,e=c.length;e>d;d++)a=c[d],a.setStatus(i.QUEUED);f.request("start-upload")},sortFiles:function(){return this.queue.sort.apply(this.queue,arguments)},reset:function(){this.owner.trigger("reset"),this.queue=new c,this.stats=this.queue.stats},destroy:function(){this.reset(),this.placeholder&&this.placeholder.destroy()}})}),b("widgets/runtime",["uploader","runtime/runtime","widgets/widget"],function(a,b){return a.support=function(){return b.hasRuntime.apply(b,arguments)},a.register({name:"runtime",init:function(){if(!this.predictRuntimeType())throw Error("Runtime Error")},predictRuntimeType:function(){var a,c,d=this.options.runtimeOrder||b.orders,e=this.type;if(!e)for(d=d.split(/\s*,\s*/g),a=0,c=d.length;c>a;a++)if(b.hasRuntime(d[a])){this.type=e=d[a];break}return e}})}),b("lib/transport",["base","runtime/client","mediator"],function(a,b,c){function d(a){var c=this;a=c.options=e.extend(!0,{},d.options,a||{}),b.call(this,"Transport"),this._blob=null,this._formData=a.formData||{},this._headers=a.headers||{},this.on("progress",this._timeout),this.on("load error",function(){c.trigger("progress",1),clearTimeout(c._timer)})}var e=a.$;return d.options={server:"",method:"POST",withCredentials:!1,fileVal:"file",timeout:12e4,formData:{},headers:{},sendAsBinary:!1},e.extend(d.prototype,{appendBlob:function(a,b,c){var d=this,e=d.options;d.getRuid()&&d.disconnectRuntime(),d.connectRuntime(b.ruid,function(){d.exec("init")}),d._blob=b,e.fileVal=a||e.fileVal,e.filename=c||e.filename},append:function(a,b){"object"==typeof a?e.extend(this._formData,a):this._formData[a]=b},setRequestHeader:function(a,b){"object"==typeof a?e.extend(this._headers,a):this._headers[a]=b},send:function(a){this.exec("send",a),this._timeout()},abort:function(){return clearTimeout(this._timer),this.exec("abort")},destroy:function(){this.trigger("destroy"),this.off(),this.exec("destroy"),this.disconnectRuntime()},getResponse:function(){return this.exec("getResponse")},getResponseAsJson:function(){return this.exec("getResponseAsJson")},getStatus:function(){return this.exec("getStatus")},_timeout:function(){var a=this,b=a.options.timeout;b&&(clearTimeout(a._timer),a._timer=setTimeout(function(){a.abort(),a.trigger("error","timeout")},b))}}),c.installTo(d.prototype),d}),b("widgets/upload",["base","uploader","file","lib/transport","widgets/widget"],function(a,b,c,d){function e(a,b){var c,d,e=[],f=a.source,g=f.size,h=b?Math.ceil(g/b):1,i=0,j=0;for(d={file:a,has:function(){return!!e.length},shift:function(){return e.shift()},unshift:function(a){e.unshift(a)}};h>j;)c=Math.min(b,g-i),e.push({file:a,start:i,end:b?i+c:g,total:g,chunks:h,chunk:j++,cuted:d}),i+=c;return a.blocks=e.concat(),a.remaning=e.length,d}var f=a.$,g=a.isPromise,h=c.Status;f.extend(b.options,{prepareNextFile:!1,chunked:!1,chunkSize:5242880,chunkRetry:2,threads:3,formData:{}}),b.register({name:"upload",init:function(){var b=this.owner,c=this;this.runing=!1,this.progress=!1,b.on("startUpload",function(){c.progress=!0}).on("uploadFinished",function(){c.progress=!1}),this.pool=[],this.stack=[],this.pending=[],this.remaning=0,this.__tick=a.bindFn(this._tick,this),b.on("uploadComplete",function(a){a.blocks&&f.each(a.blocks,function(a,b){b.transport&&(b.transport.abort(),b.transport.destroy()),delete b.transport}),delete a.blocks,delete a.remaning})},reset:function(){this.request("stop-upload",!0),this.runing=!1,this.pool=[],this.stack=[],this.pending=[],this.remaning=0,this._trigged=!1,this._promise=null},startUpload:function(b){var c=this;if(f.each(c.request("get-files",h.INVALID),function(){c.request("remove-file",this)}),b)if(b=b.id?b:c.request("get-file",b),b.getStatus()===h.INTERRUPT)f.each(c.pool,function(a,c){c.file===b&&c.transport&&c.transport.send()}),b.setStatus(h.QUEUED);else{if(b.getStatus()===h.PROGRESS)return;b.setStatus(h.QUEUED)}else f.each(c.request("get-files",[h.INITED]),function(){this.setStatus(h.QUEUED)});c.runing||(c.runing=!0,f.each(c.pool,function(a,b){var d=b.file;d.getStatus()===h.INTERRUPT&&(d.setStatus(h.PROGRESS),c._trigged=!1,b.transport&&b.transport.send())}),b||f.each(c.request("get-files",h.INTERRUPT),function(){this.setStatus(h.PROGRESS)}),c._trigged=!1,a.nextTick(c.__tick),c.owner.trigger("startUpload"))},stopUpload:function(b,c){var d=this;if(b===!0&&(c=b,b=null),d.runing!==!1){if(b){if(b=b.id?b:d.request("get-file",b),b.getStatus()!==h.PROGRESS&&b.getStatus()!==h.QUEUED)return;return b.setStatus(h.INTERRUPT),f.each(d.pool,function(a,c){c.file===b&&(c.transport&&c.transport.abort(),d._putback(c),d._popBlock(c))}),a.nextTick(d.__tick)}d.runing=!1,this._promise&&this._promise.file&&this._promise.file.setStatus(h.INTERRUPT),c&&f.each(d.pool,function(a,b){b.transport&&b.transport.abort(),b.file.setStatus(h.INTERRUPT)}),d.owner.trigger("stopUpload")}},cancelFile:function(a){a=a.id?a:this.request("get-file",a),a.blocks&&f.each(a.blocks,function(a,b){var c=b.transport;c&&(c.abort(),c.destroy(),delete b.transport)}),a.setStatus(h.CANCELLED),this.owner.trigger("fileDequeued",a)},isInProgress:function(){return!!this.progress},_getStats:function(){return this.request("get-stats")},skipFile:function(a,b){a=a.id?a:this.request("get-file",a),a.setStatus(b||h.COMPLETE),a.skipped=!0,a.blocks&&f.each(a.blocks,function(a,b){var c=b.transport;c&&(c.abort(),c.destroy(),delete b.transport)}),this.owner.trigger("uploadSkip",a)},_tick:function(){var b,c,d=this,e=d.options;return d._promise?d._promise.always(d.__tick):void(d.pool.length1&&(f.each(k.blocks,function(a,b){d+=(b.percentage||0)*(b.end-b.start)}),c=d/k.size),i.trigger("uploadProgress",k,c||0)}),c=function(a){var c;return e=l.getResponseAsJson()||{},e._raw=l.getResponse(),c=function(b){a=b},i.trigger("uploadAccept",b,e,c)||(a=a||"server"),a},l.on("error",function(a,d){b.retried=b.retried||0,b.chunks>1&&~"http,abort".indexOf(a)&&b.retried1&&f.extend(m,{chunks:b.chunks,chunk:b.chunk}),i.trigger("uploadBeforeSend",b,m,n),l.appendBlob(j.fileVal,b.blob,k.name),l.append(m),l.setRequestHeader(n),l.send()},_finishFile:function(a,b,c){var d=this.owner;return d.request("after-send-file",arguments,function(){a.setStatus(h.COMPLETE),d.trigger("uploadSuccess",a,b,c)}).fail(function(b){a.getStatus()===h.PROGRESS&&a.setStatus(h.ERROR,b),d.trigger("uploadError",a,b)}).always(function(){d.trigger("uploadComplete",a)})}})}),b("widgets/validator",["base","uploader","file","widgets/widget"],function(a,b,c){var d,e=a.$,f={};return d={addValidator:function(a,b){f[a]=b},removeValidator:function(a){delete f[a]}},b.register({name:"validator",init:function(){var b=this;a.nextTick(function(){e.each(f,function(){this.call(b.owner)})})}}),d.addValidator("fileNumLimit",function(){var a=this,b=a.options,c=0,d=parseInt(b.fileNumLimit,10),e=!0;d&&(a.on("beforeFileQueued",function(a){return c>=d&&e&&(e=!1,this.trigger("error","Q_EXCEED_NUM_LIMIT",d,a),setTimeout(function(){e=!0},1)),c>=d?!1:!0}),a.on("fileQueued",function(){c++}),a.on("fileDequeued",function(){c--}),a.on("reset",function(){c=0}))}),d.addValidator("fileSizeLimit",function(){var a=this,b=a.options,c=0,d=parseInt(b.fileSizeLimit,10),e=!0;d&&(a.on("beforeFileQueued",function(a){var b=c+a.size>d;return b&&e&&(e=!1,this.trigger("error","Q_EXCEED_SIZE_LIMIT",d,a),setTimeout(function(){e=!0},1)),b?!1:!0}),a.on("fileQueued",function(a){c+=a.size}),a.on("fileDequeued",function(a){c-=a.size}),a.on("reset",function(){c=0}))}),d.addValidator("fileSingleSizeLimit",function(){var a=this,b=a.options,d=b.fileSingleSizeLimit;d&&a.on("beforeFileQueued",function(a){return a.size>d?(a.setStatus(c.Status.INVALID,"exceed_size"),this.trigger("error","F_EXCEED_SIZE",d,a),!1):void 0})}),d.addValidator("duplicate",function(){function a(a){for(var b,c=0,d=0,e=a.length;e>d;d++)b=a.charCodeAt(d),c=b+(c<<6)+(c<<16)-c;return c}var b=this,c=b.options,d={};c.duplicate||(b.on("beforeFileQueued",function(b){var c=b.__hash||(b.__hash=a(b.name+b.size+b.lastModifiedDate));return d[c]?(this.trigger("error","F_DUPLICATE",b),!1):void 0}),b.on("fileQueued",function(a){var b=a.__hash;b&&(d[b]=!0)}),b.on("fileDequeued",function(a){var b=a.__hash;b&&delete d[b]}),b.on("reset",function(){d={}}))}),d}),b("runtime/compbase",[],function(){function a(a,b){this.owner=a,this.options=a.options,this.getRuntime=function(){return b},this.getRuid=function(){return b.uid},this.trigger=function(){return a.trigger.apply(a,arguments)}}return a}),b("runtime/html5/runtime",["base","runtime/runtime","runtime/compbase"],function(b,c,d){function e(){var a={},d=this,e=this.destroy;c.apply(d,arguments),d.type=f,d.exec=function(c,e){var f,h=this,i=h.uid,j=b.slice(arguments,2);return g[c]&&(f=a[i]=a[i]||new g[c](h,d),f[e])?f[e].apply(f,j):void 0},d.destroy=function(){return e&&e.apply(this,arguments)}}var f="html5",g={};return b.inherits(c,{constructor:e,init:function(){var a=this;setTimeout(function(){a.trigger("ready")},1)}}),e.register=function(a,c){var e=g[a]=b.inherits(d,c);return e},a.Blob&&a.FileReader&&a.DataView&&c.addRuntime(f,e),e}),b("runtime/html5/blob",["runtime/html5/runtime","lib/blob"],function(a,b){return a.register("Blob",{slice:function(a,c){var d=this.owner.source,e=d.slice||d.webkitSlice||d.mozSlice; -return d=e.call(d,a,c),new b(this.getRuid(),d)}})}),b("runtime/html5/dnd",["base","runtime/html5/runtime","lib/file"],function(a,b,c){var d=a.$,e="webuploader-dnd-";return b.register("DragAndDrop",{init:function(){var b=this.elem=this.options.container;this.dragEnterHandler=a.bindFn(this._dragEnterHandler,this),this.dragOverHandler=a.bindFn(this._dragOverHandler,this),this.dragLeaveHandler=a.bindFn(this._dragLeaveHandler,this),this.dropHandler=a.bindFn(this._dropHandler,this),this.dndOver=!1,b.on("dragenter",this.dragEnterHandler),b.on("dragover",this.dragOverHandler),b.on("dragleave",this.dragLeaveHandler),b.on("drop",this.dropHandler),this.options.disableGlobalDnd&&(d(document).on("dragover",this.dragOverHandler),d(document).on("drop",this.dropHandler))},_dragEnterHandler:function(a){var b,c=this,d=c._denied||!1;return a=a.originalEvent||a,c.dndOver||(c.dndOver=!0,b=a.dataTransfer.items,b&&b.length&&(c._denied=d=!c.trigger("accept",b)),c.elem.addClass(e+"over"),c.elem[d?"addClass":"removeClass"](e+"denied")),a.dataTransfer.dropEffect=d?"none":"copy",!1},_dragOverHandler:function(a){var b=this.elem.parent().get(0);return b&&!d.contains(b,a.currentTarget)?!1:(clearTimeout(this._leaveTimer),this._dragEnterHandler.call(this,a),!1)},_dragLeaveHandler:function(){var a,b=this;return a=function(){b.dndOver=!1,b.elem.removeClass(e+"over "+e+"denied")},clearTimeout(b._leaveTimer),b._leaveTimer=setTimeout(a,100),!1},_dropHandler:function(a){var b,f,g=this,h=g.getRuid(),i=g.elem.parent().get(0);if(i&&!d.contains(i,a.currentTarget))return!1;a=a.originalEvent||a,b=a.dataTransfer;try{f=b.getData("text/html")}catch(j){}return f?void 0:(g._getTansferFiles(b,function(a){g.trigger("drop",d.map(a,function(a){return new c(h,a)}))}),g.dndOver=!1,g.elem.removeClass(e+"over"),!1)},_getTansferFiles:function(b,c){var d,e,f,g,h,i,j,k=[],l=[];for(d=b.items,e=b.files,j=!(!d||!d[0].webkitGetAsEntry),h=0,i=e.length;i>h;h++)f=e[h],g=d&&d[h],j&&g.webkitGetAsEntry().isDirectory?l.push(this._traverseDirectoryTree(g.webkitGetAsEntry(),k)):k.push(f);a.when.apply(a,l).done(function(){k.length&&c(k)})},_traverseDirectoryTree:function(b,c){var d=a.Deferred(),e=this;return b.isFile?b.file(function(a){c.push(a),d.resolve()}):b.isDirectory&&b.createReader().readEntries(function(b){var f,g=b.length,h=[],i=[];for(f=0;g>f;f++)h.push(e._traverseDirectoryTree(b[f],i));a.when.apply(a,h).then(function(){c.push.apply(c,i),d.resolve()},d.reject)}),d.promise()},destroy:function(){var a=this.elem;a&&(a.off("dragenter",this.dragEnterHandler),a.off("dragover",this.dragOverHandler),a.off("dragleave",this.dragLeaveHandler),a.off("drop",this.dropHandler),this.options.disableGlobalDnd&&(d(document).off("dragover",this.dragOverHandler),d(document).off("drop",this.dropHandler)))}})}),b("runtime/html5/filepaste",["base","runtime/html5/runtime","lib/file"],function(a,b,c){return b.register("FilePaste",{init:function(){var b,c,d,e,f=this.options,g=this.elem=f.container,h=".*";if(f.accept){for(b=[],c=0,d=f.accept.length;d>c;c++)e=f.accept[c].mimeTypes,e&&b.push(e);b.length&&(h=b.join(","),h=h.replace(/,/g,"|").replace(/\*/g,".*"))}this.accept=h=new RegExp(h,"i"),this.hander=a.bindFn(this._pasteHander,this),g.on("paste",this.hander)},_pasteHander:function(a){var b,d,e,f,g,h=[],i=this.getRuid();for(a=a.originalEvent||a,b=a.clipboardData.items,f=0,g=b.length;g>f;f++)d=b[f],"file"===d.kind&&(e=d.getAsFile())&&h.push(new c(i,e));h.length&&(a.preventDefault(),a.stopPropagation(),this.trigger("paste",h))},destroy:function(){this.elem.off("paste",this.hander)}})}),b("runtime/html5/filepicker",["base","runtime/html5/runtime"],function(a,b){var c=a.$;return b.register("FilePicker",{init:function(){var a,b,d,e,f=this.getRuntime().getContainer(),g=this,h=g.owner,i=g.options,j=this.label=c(document.createElement("label")),k=this.input=c(document.createElement("input"));if(k.attr("type","file"),k.attr("name",i.name),k.addClass("webuploader-element-invisible"),j.on("click",function(){k.trigger("click")}),j.css({opacity:0,width:"100%",height:"100%",display:"block",cursor:"pointer",background:"#ffffff"}),i.multiple&&k.attr("multiple","multiple"),i.accept&&i.accept.length>0){for(a=[],b=0,d=i.accept.length;d>b;b++)a.push(i.accept[b].mimeTypes);k.attr("accept",a.join(","))}f.append(k),f.append(j),e=function(a){h.trigger(a.type)},k.on("change",function(a){var b,d=arguments.callee;g.files=a.target.files,b=this.cloneNode(!0),b.value=null,this.parentNode.replaceChild(b,this),k.off(),k=c(b).on("change",d).on("mouseenter mouseleave",e),h.trigger("change")}),j.on("mouseenter mouseleave",e)},getFiles:function(){return this.files},destroy:function(){this.input.off(),this.label.off()}})}),b("runtime/html5/transport",["base","runtime/html5/runtime"],function(a,b){var c=a.noop,d=a.$;return b.register("Transport",{init:function(){this._status=0,this._response=null},send:function(){var b,c,e,f=this.owner,g=this.options,h=this._initAjax(),i=f._blob,j=g.server;g.sendAsBinary?(j+=(/\?/.test(j)?"&":"?")+d.param(f._formData),c=i.getSource()):(b=new FormData,d.each(f._formData,function(a,c){b.append(a,c)}),b.append(g.fileVal,i.getSource(),g.filename||f._formData.name||"")),g.withCredentials&&"withCredentials"in h?(h.open(g.method,j,!0),h.withCredentials=!0):h.open(g.method,j),this._setRequestHeader(h,g.headers),c?(h.overrideMimeType&&h.overrideMimeType("application/octet-stream"),a.os.android?(e=new FileReader,e.onload=function(){h.send(this.result),e=e.onload=null},e.readAsArrayBuffer(c)):h.send(c)):h.send(b)},getResponse:function(){return this._response},getResponseAsJson:function(){return this._parseJson(this._response)},getStatus:function(){return this._status},abort:function(){var a=this._xhr;a&&(a.upload.onprogress=c,a.onreadystatechange=c,a.abort(),this._xhr=a=null)},destroy:function(){this.abort()},_initAjax:function(){var a=this,b=new XMLHttpRequest,d=this.options;return!d.withCredentials||"withCredentials"in b||"undefined"==typeof XDomainRequest||(b=new XDomainRequest),b.upload.onprogress=function(b){var c=0;return b.lengthComputable&&(c=b.loaded/b.total),a.trigger("progress",c)},b.onreadystatechange=function(){return 4===b.readyState?(b.upload.onprogress=c,b.onreadystatechange=c,a._xhr=null,a._status=b.status,b.status>=200&&b.status<300?(a._response=b.responseText,a.trigger("load")):b.status>=500&&b.status<600?(a._response=b.responseText,a.trigger("error","server")):a.trigger("error",a._status?"http":"abort")):void 0},a._xhr=b,b},_setRequestHeader:function(a,b){d.each(b,function(b,c){a.setRequestHeader(b,c)})},_parseJson:function(a){var b;try{b=JSON.parse(a)}catch(c){b={}}return b}})}),b("runtime/flash/runtime",["base","runtime/runtime","runtime/compbase"],function(b,c,d){function e(){var a;try{a=navigator.plugins["Shockwave Flash"],a=a.description}catch(b){try{a=new ActiveXObject("ShockwaveFlash.ShockwaveFlash").GetVariable("$version")}catch(c){a="0.0"}}return a=a.match(/\d+/g),parseFloat(a[0]+"."+a[1],10)}function f(){function d(a,b){var c,d,e=a.type||a;c=e.split("::"),d=c[0],e=c[1],"Ready"===e&&d===j.uid?j.trigger("ready"):f[d]&&f[d].trigger(e.toLowerCase(),a,b)}var e={},f={},g=this.destroy,j=this,k=b.guid("webuploader_");c.apply(j,arguments),j.type=h,j.exec=function(a,c){var d,g=this,h=g.uid,k=b.slice(arguments,2);return f[h]=g,i[a]&&(e[h]||(e[h]=new i[a](g,j)),d=e[h],d[c])?d[c].apply(d,k):j.flashExec.apply(g,arguments)},a[k]=function(){var a=arguments;setTimeout(function(){d.apply(null,a)},1)},this.jsreciver=k,this.destroy=function(){return g&&g.apply(this,arguments)},this.flashExec=function(a,c){var d=j.getFlash(),e=b.slice(arguments,2);return d.exec(this.uid,a,c,e)}}var g=b.$,h="flash",i={};return b.inherits(c,{constructor:f,init:function(){var a,c=this.getContainer(),d=this.options;c.css({position:"absolute",top:"-8px",left:"-8px",width:"9px",height:"9px",overflow:"hidden"}),a='',c.html(a)},getFlash:function(){return this._flash?this._flash:(this._flash=g("#"+this.uid).get(0),this._flash)}}),f.register=function(a,c){return c=i[a]=b.inherits(d,g.extend({flashExec:function(){var a=this.owner,b=this.getRuntime();return b.flashExec.apply(a,arguments)}},c))},e()>=11.4&&c.addRuntime(h,f),f}),b("runtime/flash/filepicker",["base","runtime/flash/runtime"],function(a,b){var c=a.$;return b.register("FilePicker",{init:function(a){var b,d,e=c.extend({},a);for(b=e.accept&&e.accept.length,d=0;b>d;d++)e.accept[d].title||(e.accept[d].title="Files");delete e.button,delete e.id,delete e.container,this.flashExec("FilePicker","init",e)},destroy:function(){this.flashExec("FilePicker","destroy")}})}),b("runtime/flash/transport",["base","runtime/flash/runtime","runtime/client"],function(b,c,d){var e=b.$;return c.register("Transport",{init:function(){this._status=0,this._response=null,this._responseJson=null},send:function(){var a,b=this.owner,c=this.options,d=this._initAjax(),f=b._blob,g=c.server;d.connectRuntime(f.ruid),c.sendAsBinary?(g+=(/\?/.test(g)?"&":"?")+e.param(b._formData),a=f.uid):(e.each(b._formData,function(a,b){d.exec("append",a,b)}),d.exec("appendBlob",c.fileVal,f.uid,c.filename||b._formData.name||"")),this._setRequestHeader(d,c.headers),d.exec("send",{method:c.method,url:g,forceURLStream:c.forceURLStream,mimeType:"application/octet-stream"},a)},getStatus:function(){return this._status},getResponse:function(){return this._response||""},getResponseAsJson:function(){return this._responseJson},abort:function(){var a=this._xhr;a&&(a.exec("abort"),a.destroy(),this._xhr=a=null)},destroy:function(){this.abort()},_initAjax:function(){var b=this,c=new d("XMLHttpRequest");return c.on("uploadprogress progress",function(a){var c=a.loaded/a.total;return c=Math.min(1,Math.max(0,c)),b.trigger("progress",c)}),c.on("load",function(){var d,e=c.exec("getStatus"),f=!1,g="";return c.off(),b._xhr=null,e>=200&&300>e?f=!0:e>=500&&600>e?(f=!0,g="server"):g="http",f&&(b._response=c.exec("getResponse"),b._response=decodeURIComponent(b._response),d=a.JSON&&a.JSON.parse||function(a){try{return new Function("return "+a).call()}catch(b){return{}}},b._responseJson=b._response?d(b._response):{}),c.destroy(),c=null,g?b.trigger("error",g):b.trigger("load")}),c.on("error",function(){c.off(),b._xhr=null,b.trigger("error","http")}),b._xhr=c,c},_setRequestHeader:function(a,b){e.each(b,function(b,c){a.exec("setRequestHeader",b,c)})}})}),b("preset/withoutimage",["base","widgets/filednd","widgets/filepaste","widgets/filepicker","widgets/queue","widgets/runtime","widgets/upload","widgets/validator","runtime/html5/blob","runtime/html5/dnd","runtime/html5/filepaste","runtime/html5/filepicker","runtime/html5/transport","runtime/flash/filepicker","runtime/flash/transport"],function(a){return a}),b("webuploader",["preset/withoutimage"],function(a){return a}),c("webuploader")}); \ No newline at end of file diff --git a/views/attachment/page.html b/views/attachment/page.html index 7509e8e4..64f19041 100644 --- a/views/attachment/page.html +++ b/views/attachment/page.html @@ -1,42 +1,87 @@
    -
    -
    -
    -
    - - {{/*

    或将文件拖到这里,单次最多可上传10个

    */}} -
    -
    -
    +
    +
    -
    -
    - - - - - - - - - - - - - - - - - - -
    文件名131267.89 KB
    文件名Jacob31123 KB
    文件名1Larry123 KB
    -
    -
    -
    - -
    -
    \ No newline at end of file +
    + \ No newline at end of file diff --git a/views/layouts/attachment.html b/views/layouts/attachment.html index 26445f74..59ba0b0c 100644 --- a/views/layouts/attachment.html +++ b/views/layouts/attachment.html @@ -10,23 +10,26 @@ - - - + + + - + - + - + - + - - + + + + + {{/**/}} From 0ad8053f09f5230ffbb542e3e68047a3933df8a6 Mon Sep 17 00:00:00 2001 From: phachon Date: Tue, 14 May 2019 14:48:19 +0800 Subject: [PATCH 03/14] fix --- app/controllers/page.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/page.go b/app/controllers/page.go index 51d6745e..f24fccab 100644 --- a/app/controllers/page.go +++ b/app/controllers/page.go @@ -331,7 +331,7 @@ func (this *PageController) Display() { // get document content documentContent, err := utils.Document.GetContentByPageFile(pageFile) if err != nil { - this.ErrorLog("查找文档 " + documentId + " 失败:" + err.Error()) + this.ErrorLog("查找文档 " + documentId + " 内容失败:" + err.Error()) this.ViewError("文档不存在!") } @@ -394,7 +394,7 @@ func (this *PageController) Export() { // check space document privilege isVisit, _, _ := this.GetDocumentPrivilege(space) if !isVisit { - this.jsonError("您没有权限导出该空间下文档!") + this.ViewError("您没有权限导出该空间下文档!") } // check space is allow export From f074b73e270f33ad874e585272f864e39d3094ba Mon Sep 17 00:00:00 2001 From: phachon Date: Wed, 15 May 2019 20:23:27 +0800 Subject: [PATCH 04/14] fix --- static/css/common.css | 13 +++++++++++++ views/page/edit.html | 3 ++- views/paginator/default.html | 10 ++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/static/css/common.css b/static/css/common.css index a58177a4..d2af2427 100644 --- a/static/css/common.css +++ b/static/css/common.css @@ -455,4 +455,17 @@ ul.countdown li p { .editormd { border-radius: 4px; border-bottom: 0; +} + +/** + * 分页选择框 + */ +.pagination-select { + padding: 2px; +} + +#page_select { + /*-webkit-appearance: none;*/ + /*border-width: 0;*/ + border-radius: 0; } \ No newline at end of file diff --git a/views/page/edit.html b/views/page/edit.html index b308d581..84d535ec 100644 --- a/views/page/edit.html +++ b/views/page/edit.html @@ -65,7 +65,8 @@ saveHTMLToTextarea: false, // 保存 HTML 到 Textarea searchReplace: true, //watch : false, // 关闭实时预览 - htmlDecode : "style,script,iframe|on*", // 开启 HTML 标签解析,为了安全性,默认不开启 + // htmlDecode : "style,script,iframe|on*,alert", // 开启 HTML 标签解析,为了安全性,默认不开启 + htmlDecode : "style,script,iframe,sub,sup,embed,img|on*", // 开启 HTML 标签解析,为了安全性,默认不开启 //toolbar : false, //关闭工具栏 //previewCodeHighlight : false, // 关闭预览 HTML 的代码块高亮,默认开启 emoji : false, diff --git a/views/paginator/default.html b/views/paginator/default.html index cd46149a..6b006d06 100644 --- a/views/paginator/default.html +++ b/views/paginator/default.html @@ -1,6 +1,16 @@ {{if gt .paginator.PageNums 1 }}
    • 共 {{.paginator.Nums}} 条数据
    • +{{/*
    • */}} +{{/* */}} +{{/* */}} +{{/*
    • */}} {{if .paginator.HasPrev}}
    • 首页
    • From 1a318deea3b62d3c4b9c9a6f2b6c8899917783f7 Mon Sep 17 00:00:00 2001 From: phachon Date: Thu, 16 May 2019 00:10:33 +0800 Subject: [PATCH 05/14] pagination add prePageNum select --- app/modules/system/controllers/log.go | 4 +-- app/utils/paginator.go | 36 +++++++++++++++++++++------ static/css/common.css | 18 ++++++++++---- views/paginator/default.html | 23 ++++++++--------- 4 files changed, 54 insertions(+), 27 deletions(-) diff --git a/app/modules/system/controllers/log.go b/app/modules/system/controllers/log.go index 44275ceb..aaae150a 100644 --- a/app/modules/system/controllers/log.go +++ b/app/modules/system/controllers/log.go @@ -12,11 +12,11 @@ type LogController struct { func (this *LogController) System() { page, _ := this.GetInt("page", 1) + number, _ := this.GetInt("number", 15) level := strings.TrimSpace(this.GetString("level", "")) message := strings.TrimSpace(this.GetString("message", "")) username := strings.TrimSpace(this.GetString("username", "")) - number := 15 limit := (page - 1) * number var err error var count int64 @@ -60,7 +60,7 @@ func (this *LogController) Info() { func (this *LogController) Document() { page, _ := this.GetInt("page", 1) - number := 12 + number, _ := this.GetInt("number", 15) limit := (page - 1) * number keyword := strings.TrimSpace(this.GetString("keyword", "")) userId := strings.TrimSpace(this.GetString("user_id", "")) diff --git a/app/utils/paginator.go b/app/utils/paginator.go index 1578879d..b2ca0918 100644 --- a/app/utils/paginator.go +++ b/app/utils/paginator.go @@ -21,15 +21,20 @@ import ( "strconv" ) +// 默认的每页条数的选择范围 +var defaultPerPageNumsSelect = []int{10,15,20,25,30,35,40,45,50,66,60,65,70,75,80,85,90,100} + type Paginator struct { - Request *http.Request - PerPageNums int - MaxPages int - nums int64 - pageRange []int - pageNums int - page int - pageParamName string + Request *http.Request + PerPageNums int + PerPageNumsSelect []int + MaxPages int + nums int64 + pageRange []int + pageNums int + page int + pageParamName string + prePageNumParamName string } func (p *Paginator) PageNums() int { @@ -52,6 +57,10 @@ func (p *Paginator) SetNums(nums interface{}) { p.nums, _ = NewConvert().ToInt64(nums) } +func (p *Paginator) SetPrePageNumsSelect(selectNums []int) { + p.PerPageNumsSelect = selectNums +} + func (p *Paginator) Page() int { if p.page != 0 { return p.page @@ -106,6 +115,14 @@ func (p *Paginator) PageLink(page int) string { } else { values.Set(p.pageParamName, strconv.Itoa(page)) } + + if p.PerPageNums < p.PerPageNumsSelect[0] { + p.PerPageNums = p.PerPageNumsSelect[0] + } + if p.PerPageNums > p.PerPageNumsSelect[len(p.PerPageNumsSelect) - 1] { + p.PerPageNums = p.PerPageNumsSelect[len(p.PerPageNumsSelect) - 1] + } + values.Set(p.prePageNumParamName, strconv.Itoa(p.PerPageNums)) link.RawQuery = values.Encode() return link.String() } @@ -157,10 +174,13 @@ func NewPaginator(req *http.Request, per int, nums interface{}) *Paginator { p.Request = req // 翻页参数名,默认为 page p.pageParamName = "page" + // 每页条数参数名,默认为 "number" + p.prePageNumParamName = "number" if per <= 0 { per = 10 } p.PerPageNums = per p.SetNums(nums) + p.SetPrePageNumsSelect(defaultPerPageNumsSelect) return &p } diff --git a/static/css/common.css b/static/css/common.css index d2af2427..64371d65 100644 --- a/static/css/common.css +++ b/static/css/common.css @@ -460,12 +460,20 @@ ul.countdown li p { /** * 分页选择框 */ -.pagination-select { - padding: 2px; +.pagination > .pagination-select > span { + padding: 0; } -#page_select { - /*-webkit-appearance: none;*/ - /*border-width: 0;*/ +select.pagination-select-xs { + height: 28px; + line-height: 28px; + border-width: 0; border-radius: 0; + background-color: #fff; + border-color: #fff; +} + +select.pagination-select-xs:hover { + color: #23527c; + background-color: #eee; } \ No newline at end of file diff --git a/views/paginator/default.html b/views/paginator/default.html index 6b006d06..4a673ab3 100644 --- a/views/paginator/default.html +++ b/views/paginator/default.html @@ -1,16 +1,15 @@ -{{if gt .paginator.PageNums 1 }} +{{/*{{if gt .paginator.PageNums 1 }}*/}}
      • 共 {{.paginator.Nums}} 条数据
      • -{{/*
      • */}} -{{/* */}} -{{/* */}} -{{/*
      • */}} +
      • + + + +
      • {{if .paginator.HasPrev}}
      • 首页
      • @@ -31,4 +30,4 @@
      • 尾页
      • {{end}}
      -{{end}} \ No newline at end of file +{{/*{{end}}*/}} \ No newline at end of file From 39e541f13c136e19537b854490b4760c9698f341 Mon Sep 17 00:00:00 2001 From: phachon Date: Thu, 16 May 2019 14:10:25 +0800 Subject: [PATCH 06/14] add table perPageNum --- app/controllers/document.go | 2 +- app/controllers/main.go | 3 +- app/controllers/space.go | 4 +- app/controllers/template.go | 14 ++++++ app/controllers/user.go | 2 +- app/modules/system/controllers/auth.go | 4 +- app/modules/system/controllers/link.go | 4 +- app/modules/system/controllers/log.go | 6 +-- app/modules/system/controllers/profile.go | 4 +- app/modules/system/controllers/role.go | 6 +-- app/modules/system/controllers/space.go | 6 +-- app/modules/system/controllers/user.go | 3 +- app/utils/paginator.go | 60 ++++++++++++++++------- views/paginator/default.html | 8 +-- views/system/link/list.html | 1 + views/system/log/system.html | 2 +- 16 files changed, 85 insertions(+), 44 deletions(-) diff --git a/app/controllers/document.go b/app/controllers/document.go index 8ee31907..d3236494 100644 --- a/app/controllers/document.go +++ b/app/controllers/document.go @@ -221,7 +221,7 @@ func (this *DocumentController) History() { page, _ := this.GetInt("page", 1) documentId := this.GetString("document_id", "0") - number := 8 + number, _ := this.GetRangeInt("number", 10, 10, 100) limit := (page - 1) * number if documentId == "0" { diff --git a/app/controllers/main.go b/app/controllers/main.go index d411c13d..9965c9d3 100644 --- a/app/controllers/main.go +++ b/app/controllers/main.go @@ -140,8 +140,7 @@ func (this *MainController) Search() { page, _ := this.GetInt("page", 1) documentName := this.GetString("document_name", "") - - number := 15 + number, _ := this.GetRangeInt("number", 20, 10, 100) limit := (page - 1) * number var documents = []map[string]string{} diff --git a/app/controllers/space.go b/app/controllers/space.go index af2d78f6..c62481b6 100644 --- a/app/controllers/space.go +++ b/app/controllers/space.go @@ -37,8 +37,8 @@ func (this *SpaceController) List() { page, _ := this.GetInt("page", 1) keyword := strings.TrimSpace(this.GetString("keyword", "")) + number, _ := this.GetRangeInt("number", 20, 10, 100) - number := 20 limit := (page - 1) * number var err error var count int64 @@ -83,6 +83,7 @@ func (this *SpaceController) Member() { page, _ := this.GetInt("page", 1) spaceId := strings.TrimSpace(this.GetString("space_id", "")) + number, _ := this.GetRangeInt("number", 20, 10, 100) if spaceId == "" { this.ViewError("没有选择空间!") @@ -96,7 +97,6 @@ func (this *SpaceController) Member() { this.ViewError("空间不存在!") } - number := 20 limit := (page - 1) * number count, err := models.SpaceUserModel.CountSpaceUsersBySpaceId(spaceId) diff --git a/app/controllers/template.go b/app/controllers/template.go index d2f7713c..7dff9af7 100644 --- a/app/controllers/template.go +++ b/app/controllers/template.go @@ -269,6 +269,20 @@ func (this *TemplateController) IsRoot() bool { return this.User["role_id"] == fmt.Sprintf("%d", models.Role_Root_Id) } +func (this *TemplateController) GetRangeInt(key string, def int, min int, max int) (n int, err error) { + n, err = this.GetInt(key, def) + if err != nil { + return + } + if n < min { + n = min + } + if n > max { + n = max + } + return n, nil +} + func (this *TemplateController) GetDocumentPrivilege(space map[string]string) (isVisit, isEditor, isManager bool) { if this.IsRoot() { diff --git a/app/controllers/user.go b/app/controllers/user.go index de68ab43..0f64e3ba 100644 --- a/app/controllers/user.go +++ b/app/controllers/user.go @@ -18,12 +18,12 @@ func (this *UserController) List() { keywords := map[string]string{} page, _ := this.GetInt("page", 1) + number, _ := this.GetRangeInt("number", 20, 10, 100) username := strings.TrimSpace(this.GetString("username", "")) if username != "" { keywords["username"] = username } - number := 20 limit := (page - 1) * number var err error var count int64 diff --git a/app/modules/system/controllers/auth.go b/app/modules/system/controllers/auth.go index 2c2f05b0..cc8d7b63 100644 --- a/app/modules/system/controllers/auth.go +++ b/app/modules/system/controllers/auth.go @@ -20,9 +20,9 @@ func (this *AuthController) List() { page, _ := this.GetInt("page", 1) keyword := strings.TrimSpace(this.GetString("keyword", "")) - - number := 20 + number, _ := this.GetRangeInt("number", 20, 10, 100) limit := (page - 1) * number + var err error var count int64 var auths []map[string]string diff --git a/app/modules/system/controllers/link.go b/app/modules/system/controllers/link.go index 0769e330..1009876c 100644 --- a/app/modules/system/controllers/link.go +++ b/app/modules/system/controllers/link.go @@ -62,9 +62,9 @@ func (this *LinkController) List() { page, _ := this.GetInt("page", 1) keyword := strings.TrimSpace(this.GetString("keyword", "")) - - number := 20 + number, _ := this.GetRangeInt("number", 20, 10, 100) limit := (page - 1) * number + var err error var count int64 var links []map[string]string diff --git a/app/modules/system/controllers/log.go b/app/modules/system/controllers/log.go index aaae150a..3aedb0e0 100644 --- a/app/modules/system/controllers/log.go +++ b/app/modules/system/controllers/log.go @@ -12,7 +12,7 @@ type LogController struct { func (this *LogController) System() { page, _ := this.GetInt("page", 1) - number, _ := this.GetInt("number", 15) + number, _ := this.GetRangeInt("number", 20, 10, 100) level := strings.TrimSpace(this.GetString("level", "")) message := strings.TrimSpace(this.GetString("message", "")) username := strings.TrimSpace(this.GetString("username", "")) @@ -60,11 +60,11 @@ func (this *LogController) Info() { func (this *LogController) Document() { page, _ := this.GetInt("page", 1) - number, _ := this.GetInt("number", 15) - limit := (page - 1) * number + number, _ := this.GetRangeInt("number", 20, 10, 100) keyword := strings.TrimSpace(this.GetString("keyword", "")) userId := strings.TrimSpace(this.GetString("user_id", "")) + limit := (page - 1) * number var logDocuments = []map[string]string{} var err error var count int64 diff --git a/app/modules/system/controllers/profile.go b/app/modules/system/controllers/profile.go index 93f2bbff..84e6c87f 100644 --- a/app/modules/system/controllers/profile.go +++ b/app/modules/system/controllers/profile.go @@ -169,7 +169,7 @@ func (this *ProfileController) FollowUser() { func (this *ProfileController) FollowDoc() { page, _ := this.GetInt("page", 1) - number := 10 + number, _ := this.GetRangeInt("number", 10, 10, 100) limit := (page - 1) * number // follow docs limit @@ -221,7 +221,7 @@ func (this *ProfileController) FollowDoc() { func (this *ProfileController) Activity() { page, _ := this.GetInt("page", 1) - number := 12 + number, _ := this.GetRangeInt("number", 15, 10, 100) limit := (page - 1) * number keyword := strings.TrimSpace(this.GetString("keyword", "")) diff --git a/app/modules/system/controllers/role.go b/app/modules/system/controllers/role.go index 5fa477aa..d4d64b15 100644 --- a/app/modules/system/controllers/role.go +++ b/app/modules/system/controllers/role.go @@ -50,9 +50,9 @@ func (this *RoleController) List() { page, _ := this.GetInt("page", 1) keyword := strings.TrimSpace(this.GetString("keyword", "")) - - number := 20 + number, _ := this.GetRangeInt("number", 20, 10, 100) limit := (page - 1) * number + var err error var count int64 var roles []map[string]string @@ -154,7 +154,7 @@ func (this *RoleController) User() { } keywords["role_id"] = roleId - number := 20 + number, _ := this.GetRangeInt("number", 15, 10, 100) limit := (page - 1) * number var err error var count int64 diff --git a/app/modules/system/controllers/space.go b/app/modules/system/controllers/space.go index 42df63e0..2d3baf99 100644 --- a/app/modules/system/controllers/space.go +++ b/app/modules/system/controllers/space.go @@ -102,9 +102,9 @@ func (this *SpaceController) List() { page, _ := this.GetInt("page", 1) keyword := strings.TrimSpace(this.GetString("keyword", "")) - - number := 20 + number, _ := this.GetRangeInt("number", 20, 10, 100) limit := (page - 1) * number + var err error var count int64 var spaces []map[string]string @@ -207,12 +207,12 @@ func (this *SpaceController) Member() { page, _ := this.GetInt("page", 1) spaceId := strings.TrimSpace(this.GetString("space_id", "")) + number, _ := this.GetRangeInt("number", 15, 10, 100) if spaceId == "" { this.ViewError("没有选择空间!") } - number := 20 limit := (page - 1) * number count, err := models.SpaceUserModel.CountSpaceUsersBySpaceId(spaceId) diff --git a/app/modules/system/controllers/user.go b/app/modules/system/controllers/user.go index be4185b6..014adb03 100644 --- a/app/modules/system/controllers/user.go +++ b/app/modules/system/controllers/user.go @@ -121,6 +121,8 @@ func (this *UserController) List() { page, _ := this.GetInt("page", 1) username := strings.TrimSpace(this.GetString("username", "")) roleId := strings.TrimSpace(this.GetString("role_id", "")) + number, _ := this.GetRangeInt("number", 20, 10, 100) + if username != "" { keywords["username"] = username } @@ -128,7 +130,6 @@ func (this *UserController) List() { keywords["role_id"] = roleId } - number := 20 limit := (page - 1) * number var err error var count int64 diff --git a/app/utils/paginator.go b/app/utils/paginator.go index b2ca0918..0e2d7de0 100644 --- a/app/utils/paginator.go +++ b/app/utils/paginator.go @@ -22,19 +22,19 @@ import ( ) // 默认的每页条数的选择范围 -var defaultPerPageNumsSelect = []int{10,15,20,25,30,35,40,45,50,66,60,65,70,75,80,85,90,100} +var defaultPerPageNumsSelect = []int{10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 100} type Paginator struct { - Request *http.Request - PerPageNums int - PerPageNumsSelect []int - MaxPages int - nums int64 - pageRange []int - pageNums int - page int - pageParamName string - prePageNumParamName string + Request *http.Request + PerPageNums int + PerPageNumsSelect []int + MaxPages int + nums int64 + pageRange []int + pageNums int + page int + pageParamName string + perPageNumParamName string } func (p *Paginator) PageNums() int { @@ -61,6 +61,17 @@ func (p *Paginator) SetPrePageNumsSelect(selectNums []int) { p.PerPageNumsSelect = selectNums } +func (p *Paginator) SetPerPageNums(perPageNums int) { + + if perPageNums < p.PerPageNumsSelect[0] { + perPageNums = p.PerPageNumsSelect[0] + } + if perPageNums > p.PerPageNumsSelect[len(p.PerPageNumsSelect)-1] { + perPageNums = p.PerPageNumsSelect[len(p.PerPageNumsSelect)-1] + } + p.PerPageNums = perPageNums +} + func (p *Paginator) Page() int { if p.page != 0 { return p.page @@ -119,10 +130,25 @@ func (p *Paginator) PageLink(page int) string { if p.PerPageNums < p.PerPageNumsSelect[0] { p.PerPageNums = p.PerPageNumsSelect[0] } - if p.PerPageNums > p.PerPageNumsSelect[len(p.PerPageNumsSelect) - 1] { - p.PerPageNums = p.PerPageNumsSelect[len(p.PerPageNumsSelect) - 1] + if p.PerPageNums > p.PerPageNumsSelect[len(p.PerPageNumsSelect)-1] { + p.PerPageNums = p.PerPageNumsSelect[len(p.PerPageNumsSelect)-1] + } + values.Set(p.perPageNumParamName, strconv.Itoa(p.PerPageNums)) + link.RawQuery = values.Encode() + return link.String() +} + +func (p *Paginator) PrePageNumLink(perPageNum int) string { + link, _ := url.ParseRequestURI(p.Request.RequestURI) + values := link.Query() + + if perPageNum < p.PerPageNumsSelect[0] { + perPageNum = p.PerPageNumsSelect[0] + } + if perPageNum > p.PerPageNumsSelect[len(p.PerPageNumsSelect)-1] { + perPageNum = p.PerPageNumsSelect[len(p.PerPageNumsSelect)-1] } - values.Set(p.prePageNumParamName, strconv.Itoa(p.PerPageNums)) + values.Set(p.perPageNumParamName, strconv.Itoa(perPageNum)) link.RawQuery = values.Encode() return link.String() } @@ -175,12 +201,12 @@ func NewPaginator(req *http.Request, per int, nums interface{}) *Paginator { // 翻页参数名,默认为 page p.pageParamName = "page" // 每页条数参数名,默认为 "number" - p.prePageNumParamName = "number" + p.perPageNumParamName = "number" if per <= 0 { per = 10 } - p.PerPageNums = per - p.SetNums(nums) p.SetPrePageNumsSelect(defaultPerPageNumsSelect) + p.SetPerPageNums(per) + p.SetNums(nums) return &p } diff --git a/views/paginator/default.html b/views/paginator/default.html index 4a673ab3..39b10233 100644 --- a/views/paginator/default.html +++ b/views/paginator/default.html @@ -1,11 +1,11 @@ -{{/*{{if gt .paginator.PageNums 1 }}*/}} +{{if gt .paginator.Nums 0 }}
      • 共 {{.paginator.Nums}} 条数据
      • - {{range $index, $numSelect := .paginator.PerPageNumsSelect}} - + {{end}} @@ -30,4 +30,4 @@
      • 尾页
      • {{end}}
      -{{/*{{end}}*/}} \ No newline at end of file +{{end}} \ No newline at end of file diff --git a/views/system/link/list.html b/views/system/link/list.html index 533e2b9e..a70961e5 100644 --- a/views/system/link/list.html +++ b/views/system/link/list.html @@ -46,6 +46,7 @@ diff --git a/views/system/log/system.html b/views/system/log/system.html index 3119ae44..ff4ed044 100644 --- a/views/system/log/system.html +++ b/views/system/log/system.html @@ -79,6 +79,6 @@ \ No newline at end of file From 5f0e912da0581413d28fe58191628c65ea5fadaf Mon Sep 17 00:00:00 2001 From: phachon Date: Sun, 26 May 2019 20:12:05 +0800 Subject: [PATCH 07/14] add attachment feature --- app/bootstrap.go | 20 ++- app/controllers/attachment.go | 268 +++++++++++++++++++++++++++++++++- app/controllers/base.go | 42 ++++++ app/models/attachment.go | 247 +++++++++++++++++++++++++++++++ docs/databases/table.sql | 2 +- static/css/common.css | 11 +- static/js/modules/page.js | 24 ++- views/attachment/page.html | 153 +++++++++---------- views/layouts/attachment.html | 5 +- views/page/edit.html | 2 +- views/page/view.html | 5 +- 11 files changed, 683 insertions(+), 96 deletions(-) create mode 100644 app/models/attachment.go diff --git a/app/bootstrap.go b/app/bootstrap.go index 25ac4f6f..b8764676 100644 --- a/app/bootstrap.go +++ b/app/bootstrap.go @@ -15,7 +15,6 @@ import ( "github.com/astaxie/beego" "github.com/fatih/color" "github.com/snail007/go-activerecord/mysql" - ) var ( @@ -36,6 +35,8 @@ var ( RootDir = "" ImageAbsDir = "" + + AttachmentAbsDir = "" ) func init() { @@ -162,9 +163,14 @@ func initDocumentDir() { os.Exit(1) } + // markdown save dir markDownAbsDir := path.Join(rootAbsDir, "markdowns") + // image save dir imagesAbsDir := path.Join(rootAbsDir, "images") + // attachment save dir + attachmentAbsDir := path.Join(rootAbsDir, "attachment") + // create markdown dir ok, _ = utils.File.PathIsExists(markDownAbsDir) if !ok { err := os.Mkdir(markDownAbsDir, 0777) @@ -173,7 +179,7 @@ func initDocumentDir() { os.Exit(1) } } - + // create image dir ok, _ = utils.File.PathIsExists(imagesAbsDir) if !ok { err := os.Mkdir(imagesAbsDir, 0777) @@ -182,9 +188,19 @@ func initDocumentDir() { os.Exit(1) } } + // create attachment dir + ok, _ = utils.File.PathIsExists(attachmentAbsDir) + if !ok { + err := os.Mkdir(attachmentAbsDir, 0777) + if err != nil { + beego.Error("create document attachment dir " + attachmentAbsDir + " error!") + os.Exit(1) + } + } utils.Document.RootAbsDir = markDownAbsDir ImageAbsDir = imagesAbsDir + AttachmentAbsDir = attachmentAbsDir beego.SetStaticPath("/images/", ImageAbsDir) } diff --git a/app/controllers/attachment.go b/app/controllers/attachment.go index d2e44d5b..cfdfa80c 100644 --- a/app/controllers/attachment.go +++ b/app/controllers/attachment.go @@ -1,15 +1,279 @@ package controllers +import ( + "fmt" + "mm-wiki/app" + "mm-wiki/app/models" + "mm-wiki/app/utils" + "os" + "path" +) type AttachmentController struct { BaseController } - func (this *AttachmentController) Page() { + + documentId := this.GetString("document_id", "") + if documentId == "" { + this.ViewError("页面参数错误!", "/space/index") + } + + document, err := models.DocumentModel.GetDocumentByDocumentId(documentId) + if err != nil { + this.ErrorLog("查找空间文档 " + documentId + " 失败:" + err.Error()) + this.ViewError("查找文档失败!") + } + if len(document) == 0 { + this.ViewError("文档不存在!") + } + spaceId := document["space_id"] + space, err := models.SpaceModel.GetSpaceBySpaceId(spaceId) + if err != nil { + this.ErrorLog("查找文档 " + documentId + " 所在空间失败:" + err.Error()) + this.ViewError("查找文档失败!") + } + if len(space) == 0 { + this.ViewError("文档所在空间不存在!") + } + // check space visit_level + isVisit, isEditor, isManager := this.GetDocumentPrivilege(space) + if !isVisit { + this.ViewError("您没有权限访问该空间下的文档!") + } + + // get document attachments + attachments, err := models.AttachmentModel.GetAttachmentsByDocumentId(documentId) + if err != nil { + this.ErrorLog("查找文档 " + documentId + " 附件失败:" + err.Error()) + this.ViewError("查找文档附件失败!") + } + + // get username + userIds := []string{} + for _, attachment := range attachments { + userIds = append(userIds, attachment["user_id"]) + } + users, err := models.UserModel.GetUsersByUserIds(userIds) + if err != nil { + this.ErrorLog("查找文档 " + documentId + " 附件失败:" + err.Error()) + this.ViewError("查找文档附件失败!") + } + usernameMap := make(map[string]string) + for _, user := range users { + usernameMap[user["user_id"]] = user["username"] + } + for _, attachment := range attachments { + attachment["username"] = usernameMap[attachment["user_id"]] + } + + this.Data["attachments"] = attachments + this.Data["document_id"] = documentId + this.Data["is_upload"] = isEditor + this.Data["is_delete"] = isManager this.viewLayout("attachment/page", "attachment") } func (this *AttachmentController) Upload() { -} \ No newline at end of file + if !this.IsPost() { + this.ViewError("请求方式有误!", "/space/index") + } + documentId := this.GetString("document_id", "") + if documentId == "" { + this.uploadJsonError("参数错误!", "/space/index") + } + + // handle document + document, err := models.DocumentModel.GetDocumentByDocumentId(documentId) + if err != nil { + this.ErrorLog("查找空间文档 " + documentId + " 失败:" + err.Error()) + this.uploadJsonError("查找文档失败!") + } + if len(document) == 0 { + this.uploadJsonError("文档不存在!") + } + + spaceId := document["space_id"] + space, err := models.SpaceModel.GetSpaceBySpaceId(spaceId) + if err != nil { + this.ErrorLog("查找文档 " + documentId + " 所在空间失败:" + err.Error()) + this.uploadJsonError("查找文档失败!") + } + if len(space) == 0 { + this.uploadJsonError("文档所在空间不存在!") + } + // check space visit_level + _, isEditor, _ := this.GetDocumentPrivilege(space) + if !isEditor { + this.uploadJsonError("您没有权限操作该空间下的文档!") + } + + // handle upload + f, h, err := this.GetFile("attachment") + if err != nil { + this.ErrorLog("上传附件数据错误: " + err.Error()) + this.uploadJsonError("上传附件数据错误") + return + } + if h == nil || f == nil { + this.ErrorLog("上传附件错误") + this.uploadJsonError("上传附件错误") + return + } + _ = f.Close() + + // file save dir + saveDir := fmt.Sprintf("%s/%s/%s", app.AttachmentAbsDir, spaceId, documentId) + ok, _ := utils.File.PathIsExists(saveDir) + if !ok { + err := os.MkdirAll(saveDir, 0777) + if err != nil { + this.ErrorLog("上传附件错误: " + err.Error()) + this.uploadJsonError("上传附件失败") + return + } + } + // check file is exists + attachmentFile := path.Join(saveDir, h.Filename) + ok, _ = utils.File.PathIsExists(attachmentFile) + if ok { + this.uploadJsonError("该附件已经存在!") + } + // save file + err = this.SaveToFile("attachment", attachmentFile) + if err != nil { + this.ErrorLog("附件保存失败: " + err.Error()) + this.uploadJsonError("附件保存失败") + } + + // insert db + attachment := map[string]interface{}{ + "user_id": this.UserId, + "document_id": documentId, + "name": h.Filename, + "path": attachmentFile, + } + _, err = models.AttachmentModel.Insert(attachment) + if err != nil { + _ = os.Remove(attachmentFile) + this.ErrorLog("上传附件错误: " + err.Error()) + this.uploadJsonError("附件信息保存失败") + } + + this.jsonSuccess("附件上传成功", "", "/attachment/page?document_id="+documentId) +} + +func (this *AttachmentController) Delete() { + + if !this.IsPost() { + this.ViewError("请求方式有误!", "/space/index") + } + attachmentId := this.GetString("attachment_id", "") + if attachmentId == "" { + this.jsonError("没有选择附件!") + } + + attachment, err := models.AttachmentModel.GetAttachmentByAttachmentId(attachmentId) + if err != nil { + this.ErrorLog("删除附件 " + attachmentId + " 失败: " + err.Error()) + this.jsonError("删除附件失败") + } + if len(attachment) == 0 { + this.jsonError("附件不存在") + } + + documentId := attachment["document_id"] + document, err := models.DocumentModel.GetDocumentByDocumentId(documentId) + if err != nil { + this.ErrorLog("查找附件所属空间文档 " + documentId + " 失败:" + err.Error()) + this.jsonError("查找附件所属文档失败!") + } + if len(document) == 0 { + this.jsonError("附件所属文档不存在!") + } + + spaceId := document["space_id"] + space, err := models.SpaceModel.GetSpaceBySpaceId(spaceId) + if err != nil { + this.ErrorLog("查找附件所属文档 " + documentId + " 所在空间失败:" + err.Error()) + this.jsonError("查找附件所属文档空间失败!") + } + if len(space) == 0 { + this.jsonError("附件所属文档所在空间不存在!") + } + // check space visit_level + _, _, isManager := this.GetDocumentPrivilege(space) + if !isManager { + this.jsonError("您没有权限删除该空间下的文档!") + } + attachmentFilePath := attachment["path"] + attachmentName := attachment["name"] + + // delete db + err = models.AttachmentModel.Delete(attachmentId) + if err != nil { + this.ErrorLog("删除附件 " + attachmentId + " 失败: " + err.Error()) + this.jsonError("删除附件失败") + } + // delete file + err = os.Remove(attachmentFilePath) + if err != nil { + this.WarningLog("删除附件 " + attachmentFilePath + " 失败: " + err.Error()) + } + + // update document log + go func() { + _, _ = models.LogDocumentModel.UpdateAction(this.UserId, documentId, "删除了附件 "+attachmentName) + }() + + this.InfoLog("删除附件 " + attachmentId + " 成功") + this.jsonSuccess("删除附件成功", nil, "/attachment/page?document_id="+documentId) +} + +func (this *AttachmentController) Download() { + + attachmentId := this.GetString("attachment_id", "") + if attachmentId == "" { + this.ViewError("没有选择附件!") + } + + attachment, err := models.AttachmentModel.GetAttachmentByAttachmentId(attachmentId) + if err != nil { + this.ErrorLog("下载附件 " + attachmentId + " 失败: " + err.Error()) + this.ViewError("下载附件失败") + } + if len(attachment) == 0 { + this.ViewError("附件不存在") + } + + documentId := attachment["document_id"] + document, err := models.DocumentModel.GetDocumentByDocumentId(documentId) + if err != nil { + this.ErrorLog("查找附件所属空间文档 " + documentId + " 失败:" + err.Error()) + this.ViewError("查找附件所属文档失败!") + } + if len(document) == 0 { + this.ViewError("附件所属文档不存在!") + } + + spaceId := document["space_id"] + space, err := models.SpaceModel.GetSpaceBySpaceId(spaceId) + if err != nil { + this.ErrorLog("查找附件所属文档 " + documentId + " 所在空间失败:" + err.Error()) + this.ViewError("查找附件所属文档空间失败!") + } + if len(space) == 0 { + this.ViewError("附件所属文档所在空间不存在!") + } + // check space visit_level + isVisit, _, _ := this.GetDocumentPrivilege(space) + if !isVisit { + this.ViewError("您没有权限访问或下载该空间下的资料!") + } + attachmentFilePath := attachment["path"] + attachmentName := attachment["name"] + + this.Ctx.Output.Download(attachmentFilePath, attachmentName) +} diff --git a/app/controllers/base.go b/app/controllers/base.go index e58b3674..ec606b7b 100644 --- a/app/controllers/base.go +++ b/app/controllers/base.go @@ -1,5 +1,7 @@ package controllers +import "encoding/json" + type BaseController struct { TemplateController } @@ -19,3 +21,43 @@ func (this *BaseController) jsonSuccess(message interface{}, data ...interface{} func (this *BaseController) jsonError(message interface{}, data ...interface{}) { this.JsonError(message, data...) } + +type UploadJsonResponse struct { + Code int `json:"code"` + Message interface{} `json:"message"` + Data interface{} `json:"data"` + Redirect map[string]interface{} `json:"redirect"` + Error interface{} `json:"error"` +} + +func (this *BaseController) uploadJsonError(message interface{}, data ...interface{}) { + url := "" + sleep := 2000 + var _data interface{} + if len(data) > 0 { + _data = data[0] + } + if len(data) > 1 { + url = data[1].(string) + } + if len(data) > 2 { + sleep = data[2].(int) + } + this.Data["json"] = UploadJsonResponse{ + Code: 0, + Message: message, + Data: _data, + Redirect: map[string]interface{}{ + "url": url, + "sleep": sleep, + }, + Error: message, + } + j, err := json.MarshalIndent(this.Data["json"], "", " \t") + if err != nil { + this.Abort(err.Error()) + } else { + this.Ctx.Output.Header("Content-Type", "application/json; charset=utf-8") + this.Abort(string(j)) + } +} diff --git a/app/models/attachment.go b/app/models/attachment.go new file mode 100644 index 00000000..b617258d --- /dev/null +++ b/app/models/attachment.go @@ -0,0 +1,247 @@ +package models + +import ( + "github.com/snail007/go-activerecord/mysql" + "mm-wiki/app/utils" + "time" +) + +const Table_Attachment_Name = "attachment" + +type Attachment struct { +} + +var AttachmentModel = Attachment{} + +// get attachment by attachment_id +func (a *Attachment) GetAttachmentByAttachmentId(attachmentId string) (attachment map[string]string, err error) { + db := G.DB() + var rs *mysql.ResultSet + rs, err = db.Query(db.AR().From(Table_Attachment_Name).Where(map[string]interface{}{ + "attachment_id": attachmentId, + })) + if err != nil { + return + } + attachment = rs.Row() + return +} + +// attachment_id and name is exists +func (a *Attachment) HasSameName(attachmentId, name string) (has bool, err error) { + db := G.DB() + var rs *mysql.ResultSet + rs, err = db.Query(db.AR().From(Table_Attachment_Name).Where(map[string]interface{}{ + "attachment_id <>": attachmentId, + "name": name, + }).Limit(0, 1)) + if err != nil { + return + } + if rs.Len() > 0 { + has = true + } + return +} + +// name is exists +func (a *Attachment) HasAttachmentName(name string) (has bool, err error) { + db := G.DB() + var rs *mysql.ResultSet + rs, err = db.Query(db.AR().From(Table_Attachment_Name).Where(map[string]interface{}{ + "name": name, + }).Limit(0, 1)) + if err != nil { + return + } + if rs.Len() > 0 { + has = true + } + return +} + +// get attachment by name +func (a *Attachment) GetAttachmentByName(name string) (attachment map[string]string, err error) { + db := G.DB() + var rs *mysql.ResultSet + rs, err = db.Query(db.AR().From(Table_Attachment_Name).Where(map[string]interface{}{ + "name": name, + }).Limit(0, 1)) + if err != nil { + return + } + attachment = rs.Row() + return +} + +// get attachments by document +func (a *Attachment) GetAttachmentsByDocumentId(documentId string) (attachments []map[string]string, err error) { + db := G.DB() + var rs *mysql.ResultSet + rs, err = db.Query(db.AR().From(Table_Attachment_Name).Where(map[string]interface{}{ + "document_id": documentId, + }).OrderBy("attachment_id", "desc")) + if err != nil { + return + } + attachments = rs.Rows() + return +} + +// delete attachment by attachment_id +func (a *Attachment) Delete(attachmentId string) (err error) { + db := G.DB() + _, err = db.Exec(db.AR().Delete(Table_Attachment_Name, map[string]interface{}{ + "attachment_id": attachmentId, + })) + if err != nil { + return + } + + return +} + +// insert attachment +func (a *Attachment) Insert(attachmentValue map[string]interface{}) (id int64, err error) { + + attachmentValue["create_time"] = time.Now().Unix() + attachmentValue["update_time"] = time.Now().Unix() + db := G.DB() + var rs *mysql.ResultSet + rs, err = db.Exec(db.AR().Insert(Table_Attachment_Name, attachmentValue)) + if err != nil { + return + } + id = rs.LastInsertId + + // create document log + go func() { + _, _ = LogDocumentModel.UpdateAction(attachmentValue["user_id"].(string), + attachmentValue["document_id"].(string), "上传了附件 "+attachmentValue["name"].(string)) + }() + + return +} + +// update attachment by attachment_id +func (a *Attachment) Update(attachmentId string, attachmentValue map[string]interface{}) (id int64, err error) { + db := G.DB() + var rs *mysql.ResultSet + attachmentValue["update_time"] = time.Now().Unix() + rs, err = db.Exec(db.AR().Update(Table_Attachment_Name, attachmentValue, map[string]interface{}{ + "attachment_id": attachmentId, + })) + if err != nil { + return + } + id = rs.LastInsertId + return +} + +// get limit attachments +func (a *Attachment) GetAttachmentsByLimit(limit int, number int) (attachments []map[string]string, err error) { + + db := G.DB() + var rs *mysql.ResultSet + rs, err = db.Query( + db.AR(). + From(Table_Attachment_Name). + Limit(limit, number). + OrderBy("attachment_id", "DESC")) + if err != nil { + return + } + attachments = rs.Rows() + + return +} + +// get all attachments +func (a *Attachment) GetAttachments() (attachments []map[string]string, err error) { + + db := G.DB() + var rs *mysql.ResultSet + rs, err = db.Query( + db.AR().From(Table_Attachment_Name)) + if err != nil { + return + } + attachments = rs.Rows() + return +} + +// get all attachments by sequence +func (a *Attachment) GetAttachmentsOrderBySequence() (attachments []map[string]string, err error) { + + db := G.DB() + var rs *mysql.ResultSet + rs, err = db.Query( + db.AR().From(Table_Attachment_Name).OrderBy("sequence", "ASC")) + if err != nil { + return + } + attachments = rs.Rows() + return +} + +// get attachment count +func (a *Attachment) CountAttachments() (count int64, err error) { + + db := G.DB() + var rs *mysql.ResultSet + rs, err = db.Query( + db.AR(). + Select("count(*) as total"). + From(Table_Attachment_Name)) + if err != nil { + return + } + count = utils.NewConvert().StringToInt64(rs.Value("total")) + return +} + +// get attachment count by keyword +func (a *Attachment) CountAttachmentsByKeyword(keyword string) (count int64, err error) { + + db := G.DB() + var rs *mysql.ResultSet + rs, err = db.Query(db.AR(). + Select("count(*) as total"). + From(Table_Attachment_Name). + Where(map[string]interface{}{ + "name LIKE": "%" + keyword + "%", + })) + if err != nil { + return + } + count = utils.NewConvert().StringToInt64(rs.Value("total")) + return +} + +// get attachments by like name +func (a *Attachment) GetAttachmentsByLikeName(name string) (attachments []map[string]string, err error) { + db := G.DB() + var rs *mysql.ResultSet + rs, err = db.Query(db.AR().From(Table_Attachment_Name).Where(map[string]interface{}{ + "name Like": "%" + name + "%", + }).Limit(0, 1)) + if err != nil { + return + } + attachments = rs.Rows() + return +} + +// get attachment by many attachment_id +func (a *Attachment) GetAttachmentByAttachmentIds(attachmentIds []string) (attachments []map[string]string, err error) { + db := G.DB() + var rs *mysql.ResultSet + rs, err = db.Query(db.AR().From(Table_Attachment_Name).Where(map[string]interface{}{ + "attachment_id": attachmentIds, + })) + if err != nil { + return + } + attachments = rs.Rows() + return +} diff --git a/docs/databases/table.sql b/docs/databases/table.sql index 5b867310..ccb0fbb3 100644 --- a/docs/databases/table.sql +++ b/docs/databases/table.sql @@ -291,7 +291,7 @@ CREATE TABLE `mw_attachment` ( `user_id` int(10) NOT NULL DEFAULT '0' COMMENT '创建用户id', `document_id` int(10) NOT NULL DEFAULT '0' COMMENT '所属文档id', `name` varchar(50) NOT NULL DEFAULT '' COMMENT '附件名称', - `url` varchar(100) NOT NULL DEFAULT '' COMMENT '附件地址或路径', + `path` varchar(100) NOT NULL DEFAULT '' COMMENT '附件路径', `create_time` int(11) NOT NULL DEFAULT '0' COMMENT '创建时间', `update_time` int(11) NOT NULL DEFAULT '0' COMMENT '更新时间', PRIMARY KEY (`attachment_id`), diff --git a/static/css/common.css b/static/css/common.css index ce6be905..f25d5c81 100644 --- a/static/css/common.css +++ b/static/css/common.css @@ -478,10 +478,13 @@ select.pagination-select-xs:hover { background-color: #eee; } -#attach_row .file-drop-zone { - margin: 3px; +#attach_section .progress { + height: 14px; + /*margin-bottom: 15px;*/ + margin-top: 0; } -#attach_row .file-other-icon { - font-size: 1.4em; +#attach_section .progress-bar { + font-size:10px; + line-height:14px; } \ No newline at end of file diff --git a/static/js/modules/page.js b/static/js/modules/page.js index a1c025d5..af4ed699 100644 --- a/static/js/modules/page.js +++ b/static/js/modules/page.js @@ -155,25 +155,33 @@ var Page = { /** * upload attachment */ - attachment: function () { + attachment: function (documentId) { layer.open({ type: 2, skin: Layers.skin, - title: '上传附件', + title: '附件', shadeClose: true, shade : 0.1, resize: false, maxmin: false, - area: ["800px", "500px"], - content: "/attachment/page", + area: ["900px", "500px"], + content: "/attachment/page?document_id="+documentId, padding:"10px" }); }, /** - * uploader + * 错误提示 + * @param element + * @param message */ - attachUpload: function () { - - } + uploadErrorBox: function (element, message) { + $(element).html(''); + $(element).removeClass('hide'); + $(element).addClass('alert alert-danger'); + $(element).append('×'); + $(element).append(' 上传失败:'); + $(element).append(message); + $(element).show(); + }, }; \ No newline at end of file diff --git a/views/attachment/page.html b/views/attachment/page.html index 64f19041..1686fcd9 100644 --- a/views/attachment/page.html +++ b/views/attachment/page.html @@ -1,87 +1,90 @@ -
      -
      +
      {{$attachments := .attachments}}{{$documentId := .document_id}} +
      +
      {{$isDelete := .is_delete}}{{$isUpload := .is_upload}} + {{if eq $isUpload true}}
      -
      - +
      +
      + +
      +
      + {{end}} +
      + + + + + + + + + + + {{range $attachment := $attachments}} + + + + + + + {{end}} + +
      文件名上传用户上传时间操作
      {{$attachment.name}}{{$attachment.username}}{{dateFormat $attachment.create_time "Y-m-d H:i:s"}} + {{if eq $isDelete true}} + + {{end}} + +
      +
      \ No newline at end of file diff --git a/views/layouts/attachment.html b/views/layouts/attachment.html index 59ba0b0c..1b4b44f2 100644 --- a/views/layouts/attachment.html +++ b/views/layouts/attachment.html @@ -26,7 +26,8 @@ - +{{/* */}} + {{/**/}} @@ -36,6 +37,8 @@ + + {{.LayoutContent}} diff --git a/views/page/edit.html b/views/page/edit.html index d3567625..9983832b 100644 --- a/views/page/edit.html +++ b/views/page/edit.html @@ -56,7 +56,7 @@ toolbarCustomIcons : { sidebar : '', save : "", - attachment : '' + attachment : '' }, theme : "default", previewTheme : "default", diff --git a/views/page/view.html b/views/page/view.html index f933294e..1257730d 100644 --- a/views/page/view.html +++ b/views/page/view.html @@ -30,7 +30,8 @@

      {{$document.name}}

      {{$editUser.username}}({{$editUser.given_name}}) {{end}} 最后更新于 {{dateFormat $document.update_time "Y/m/d H:i:s"}} - (查看修改历史) +  查看修改历史 +   查看附件

      @@ -60,7 +61,7 @@

      {{$document.name}}


      -
      +
      From f55324767ce89f205fc80709b67591c0bbf9f05f Mon Sep 17 00:00:00 2001 From: phachon Date: Mon, 27 May 2019 00:31:35 +0800 Subject: [PATCH 08/14] update image upload --- app/bootstrap.go | 9 +++ app/controllers/attachment.go | 71 +++++++++++++++++++++-- app/controllers/image.go | 102 ++++++++++++++++++++++++++++------ app/models/attachment.go | 97 +++++--------------------------- docs/databases/table.sql | 17 ++++-- views/attachment/image.html | 43 ++++++++++++++ views/attachment/page.html | 15 +++-- views/page/edit.html | 2 +- 8 files changed, 241 insertions(+), 115 deletions(-) create mode 100644 views/attachment/image.html diff --git a/app/bootstrap.go b/app/bootstrap.go index b8764676..e6720939 100644 --- a/app/bootstrap.go +++ b/app/bootstrap.go @@ -34,6 +34,10 @@ var ( RootDir = "" + DocumentAbsDir = "" + + MarkdownAbsDir = "" + ImageAbsDir = "" AttachmentAbsDir = "" @@ -163,6 +167,8 @@ func initDocumentDir() { os.Exit(1) } + DocumentAbsDir = rootAbsDir + // markdown save dir markDownAbsDir := path.Join(rootAbsDir, "markdowns") // image save dir @@ -199,10 +205,13 @@ func initDocumentDir() { } utils.Document.RootAbsDir = markDownAbsDir + MarkdownAbsDir = markDownAbsDir ImageAbsDir = imagesAbsDir AttachmentAbsDir = attachmentAbsDir beego.SetStaticPath("/images/", ImageAbsDir) + // todo + beego.SetStaticPath("/images/:space_id/:document_id/", ImageAbsDir) } // check upgrade diff --git a/app/controllers/attachment.go b/app/controllers/attachment.go index cfdfa80c..a04da134 100644 --- a/app/controllers/attachment.go +++ b/app/controllers/attachment.go @@ -44,7 +44,7 @@ func (this *AttachmentController) Page() { } // get document attachments - attachments, err := models.AttachmentModel.GetAttachmentsByDocumentId(documentId) + attachments, err := models.AttachmentModel.GetAttachmentsByDocumentIdAndSource(documentId, models.Attachment_Source_Default) if err != nil { this.ErrorLog("查找文档 " + documentId + " 附件失败:" + err.Error()) this.ViewError("查找文档附件失败!") @@ -153,7 +153,8 @@ func (this *AttachmentController) Upload() { "user_id": this.UserId, "document_id": documentId, "name": h.Filename, - "path": attachmentFile, + "path": fmt.Sprintf("attachment/%s/%s/%s", spaceId, documentId, h.Filename), + "source": models.Attachment_Source_Default, } _, err = models.AttachmentModel.Insert(attachment) if err != nil { @@ -162,6 +163,7 @@ func (this *AttachmentController) Upload() { this.uploadJsonError("附件信息保存失败") } + this.InfoLog(fmt.Sprintf("文档 %s 上传附件 %s 成功", documentId, h.Filename)) this.jsonSuccess("附件上传成功", "", "/attachment/page?document_id="+documentId) } @@ -208,7 +210,7 @@ func (this *AttachmentController) Delete() { if !isManager { this.jsonError("您没有权限删除该空间下的文档!") } - attachmentFilePath := attachment["path"] + attachmentFilePath := path.Join(app.DocumentAbsDir, attachment["path"]) attachmentName := attachment["name"] // delete db @@ -272,8 +274,69 @@ func (this *AttachmentController) Download() { if !isVisit { this.ViewError("您没有权限访问或下载该空间下的资料!") } - attachmentFilePath := attachment["path"] + attachmentFilePath := path.Join(app.DocumentAbsDir, attachment["path"]) attachmentName := attachment["name"] this.Ctx.Output.Download(attachmentFilePath, attachmentName) } + +func (this *AttachmentController) Image() { + + documentId := this.GetString("document_id", "") + if documentId == "" { + this.ViewError("页面参数错误!", "/space/index") + } + + document, err := models.DocumentModel.GetDocumentByDocumentId(documentId) + if err != nil { + this.ErrorLog("查找空间文档 " + documentId + " 失败:" + err.Error()) + this.ViewError("查找文档失败!") + } + if len(document) == 0 { + this.ViewError("文档不存在!") + } + spaceId := document["space_id"] + space, err := models.SpaceModel.GetSpaceBySpaceId(spaceId) + if err != nil { + this.ErrorLog("查找文档 " + documentId + " 所在空间失败:" + err.Error()) + this.ViewError("查找文档失败!") + } + if len(space) == 0 { + this.ViewError("文档所在空间不存在!") + } + // check space visit_level + isVisit, isEditor, _ := this.GetDocumentPrivilege(space) + if !isVisit { + this.ViewError("您没有权限访问该空间下的文档!") + } + + // get document attachment images + attachments, err := models.AttachmentModel.GetAttachmentsByDocumentIdAndSource(documentId, models.Attachment_Source_Image) + if err != nil { + this.ErrorLog("查找文档 " + documentId + " 图片失败:" + err.Error()) + this.ViewError("查找文档图片失败!") + } + + // get username + userIds := []string{} + for _, attachment := range attachments { + userIds = append(userIds, attachment["user_id"]) + } + users, err := models.UserModel.GetUsersByUserIds(userIds) + if err != nil { + this.ErrorLog("查找文档 " + documentId + " 图片失败:" + err.Error()) + this.ViewError("查找文档图片失败!") + } + usernameMap := make(map[string]string) + for _, user := range users { + usernameMap[user["user_id"]] = user["username"] + } + for _, attachment := range attachments { + attachment["username"] = usernameMap[attachment["user_id"]] + } + + this.Data["attachments"] = attachments + this.Data["document_id"] = documentId + this.Data["is_delete"] = isEditor + this.viewLayout("attachment/image", "attachment") +} \ No newline at end of file diff --git a/app/controllers/image.go b/app/controllers/image.go index 63741d3d..5bcf9e69 100644 --- a/app/controllers/image.go +++ b/app/controllers/image.go @@ -2,12 +2,12 @@ package controllers import ( "encoding/json" - "path" - "strings" - + "fmt" "mm-wiki/app" - - "github.com/nu7hatch/gouuid" + "mm-wiki/app/models" + "mm-wiki/app/utils" + "os" + "path" ) type UploadResponse struct { @@ -22,24 +22,94 @@ type ImageController struct { func (this *ImageController) Upload() { - f, h, err := this.GetFile("editormd-image-file") + if !this.IsPost() { + this.ViewError("请求方式有误!", "/space/index") + } + documentId := this.GetString("document_id", "") + if documentId == "" { + this.jsonError("参数错误!") + } + + // handle document + document, err := models.DocumentModel.GetDocumentByDocumentId(documentId) if err != nil { - this.ErrorLog("上传图片错误: " + err.Error()) - this.jsonError("上传出错") + this.ErrorLog("查找空间文档 " + documentId + " 失败:" + err.Error()) + this.jsonError("查找文档失败!") + } + if len(document) == 0 { + this.jsonError("文档不存在!") } - f.Close() - ext := h.Filename[strings.LastIndex(h.Filename, "."):] + spaceId := document["space_id"] + space, err := models.SpaceModel.GetSpaceBySpaceId(spaceId) + if err != nil { + this.ErrorLog("查找文档 " + documentId + " 所在空间失败:" + err.Error()) + this.jsonError("查找文档失败!") + } + if len(space) == 0 { + this.jsonError("文档所在空间不存在!") + } + // check space visit_level + _, isEditor, _ := this.GetDocumentPrivilege(space) + if !isEditor { + this.jsonError("您没有权限操作该空间下的文档!") + } - uuId, _ := uuid.NewV4() - uploadFile := path.Join(app.ImageAbsDir, uuId.String()+ext) - err = this.SaveToFile("editormd-image-file", uploadFile) + // handle upload + f, h, err := this.GetFile("editormd-image-file") + if err != nil { + this.ErrorLog("上传图片数据错误: " + err.Error()) + this.jsonError("上传图片数据错误") + return + } + if h == nil || f == nil { + this.ErrorLog("上传图片错误") + this.jsonError("上传图片错误") + return + } + _ = f.Close() + + // file save dir + saveDir := fmt.Sprintf("%s/%s/%s", app.ImageAbsDir, spaceId, documentId) + ok, _ := utils.File.PathIsExists(saveDir) + if !ok { + err := os.MkdirAll(saveDir, 0777) + if err != nil { + this.ErrorLog("上传图片错误: " + err.Error()) + this.jsonError("上传图片失败") + return + } + } + // check file is exists + imageFile := path.Join(saveDir, h.Filename) + ok, _ = utils.File.PathIsExists(imageFile) + if ok { + this.jsonError("该图片已经上传过!") + } + // save file + err = this.SaveToFile("editormd-image-file", imageFile) + if err != nil { + this.ErrorLog("图片保存失败: " + err.Error()) + this.jsonError("图片保存失败") + } + + // insert db + attachment := map[string]interface{}{ + "user_id": this.UserId, + "document_id": documentId, + "name": h.Filename, + "path": fmt.Sprintf("images/%s/%s/%s", spaceId, documentId, h.Filename), + "source": models.Attachment_Source_Image, + } + _, err = models.AttachmentModel.Insert(attachment) if err != nil { - this.ErrorLog("上传图片错误: " + err.Error()) - this.jsonError("上传出错") + _ = os.Remove(imageFile) + this.ErrorLog("上传图片保存信息错误: " + err.Error()) + this.jsonError("图片信息保存失败") } - this.jsonSuccess("上传成功", "/images/"+uuId.String()+ext) + this.InfoLog(fmt.Sprintf("文档 %s 上传图片 %s 成功", documentId, h.Filename)) + this.jsonSuccess("上传成功", fmt.Sprintf("/%s", attachment["path"])) } func (this *ImageController) jsonError(message string) { diff --git a/app/models/attachment.go b/app/models/attachment.go index b617258d..9a0adea6 100644 --- a/app/models/attachment.go +++ b/app/models/attachment.go @@ -1,13 +1,18 @@ package models import ( + "fmt" "github.com/snail007/go-activerecord/mysql" - "mm-wiki/app/utils" "time" ) const Table_Attachment_Name = "attachment" +const ( + Attachment_Source_Default = 0 + Attachment_Source_Image = 1 +) + type Attachment struct { } @@ -75,11 +80,12 @@ func (a *Attachment) GetAttachmentByName(name string) (attachment map[string]str } // get attachments by document -func (a *Attachment) GetAttachmentsByDocumentId(documentId string) (attachments []map[string]string, err error) { +func (a *Attachment) GetAttachmentsByDocumentIdAndSource(documentId string, source int) (attachments []map[string]string, err error) { db := G.DB() var rs *mysql.ResultSet rs, err = db.Query(db.AR().From(Table_Attachment_Name).Where(map[string]interface{}{ "document_id": documentId, + "source": source, }).OrderBy("attachment_id", "desc")) if err != nil { return @@ -116,46 +122,17 @@ func (a *Attachment) Insert(attachmentValue map[string]interface{}) (id int64, e // create document log go func() { + comment := fmt.Sprintf("上传了附件 %s", attachmentValue["name"].(string)) + if attachmentValue["source"].(int) == Attachment_Source_Image { + comment = fmt.Sprintf("上传了图片 %s", attachmentValue["name"].(string)) + } _, _ = LogDocumentModel.UpdateAction(attachmentValue["user_id"].(string), - attachmentValue["document_id"].(string), "上传了附件 "+attachmentValue["name"].(string)) + attachmentValue["document_id"].(string), comment) }() return } -// update attachment by attachment_id -func (a *Attachment) Update(attachmentId string, attachmentValue map[string]interface{}) (id int64, err error) { - db := G.DB() - var rs *mysql.ResultSet - attachmentValue["update_time"] = time.Now().Unix() - rs, err = db.Exec(db.AR().Update(Table_Attachment_Name, attachmentValue, map[string]interface{}{ - "attachment_id": attachmentId, - })) - if err != nil { - return - } - id = rs.LastInsertId - return -} - -// get limit attachments -func (a *Attachment) GetAttachmentsByLimit(limit int, number int) (attachments []map[string]string, err error) { - - db := G.DB() - var rs *mysql.ResultSet - rs, err = db.Query( - db.AR(). - From(Table_Attachment_Name). - Limit(limit, number). - OrderBy("attachment_id", "DESC")) - if err != nil { - return - } - attachments = rs.Rows() - - return -} - // get all attachments func (a *Attachment) GetAttachments() (attachments []map[string]string, err error) { @@ -170,54 +147,6 @@ func (a *Attachment) GetAttachments() (attachments []map[string]string, err erro return } -// get all attachments by sequence -func (a *Attachment) GetAttachmentsOrderBySequence() (attachments []map[string]string, err error) { - - db := G.DB() - var rs *mysql.ResultSet - rs, err = db.Query( - db.AR().From(Table_Attachment_Name).OrderBy("sequence", "ASC")) - if err != nil { - return - } - attachments = rs.Rows() - return -} - -// get attachment count -func (a *Attachment) CountAttachments() (count int64, err error) { - - db := G.DB() - var rs *mysql.ResultSet - rs, err = db.Query( - db.AR(). - Select("count(*) as total"). - From(Table_Attachment_Name)) - if err != nil { - return - } - count = utils.NewConvert().StringToInt64(rs.Value("total")) - return -} - -// get attachment count by keyword -func (a *Attachment) CountAttachmentsByKeyword(keyword string) (count int64, err error) { - - db := G.DB() - var rs *mysql.ResultSet - rs, err = db.Query(db.AR(). - Select("count(*) as total"). - From(Table_Attachment_Name). - Where(map[string]interface{}{ - "name LIKE": "%" + keyword + "%", - })) - if err != nil { - return - } - count = utils.NewConvert().StringToInt64(rs.Value("total")) - return -} - // get attachments by like name func (a *Attachment) GetAttachmentsByLikeName(name string) (attachments []map[string]string, err error) { db := G.DB() diff --git a/docs/databases/table.sql b/docs/databases/table.sql index ccb0fbb3..75571e2e 100644 --- a/docs/databases/table.sql +++ b/docs/databases/table.sql @@ -75,7 +75,9 @@ CREATE TABLE `mw_role_privilege` ( `role_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '角色id', `privilege_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '权限id', `create_time` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', - PRIMARY KEY (`role_privilege_id`) + PRIMARY KEY (`role_privilege_id`), + KEY (`role_id`), + KEY (`privilege_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='系统角色权限对应关系表'; -- -------------------------------- @@ -177,7 +179,8 @@ CREATE TABLE `mw_log_document` ( `action` tinyint(3) NOT NULL DEFAULT '1' COMMENT '动作 1 创建 2 修改 3 删除', `comment` varchar(255) NOT NULL DEFAULT '' COMMENT '备注信息', `create_time` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', - PRIMARY KEY (`log_document_id`) + PRIMARY KEY (`log_document_id`), + KEY (`document_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='文档日志表'; -- -------------------------------- @@ -197,7 +200,8 @@ CREATE TABLE `mw_log` ( `user_id` int(10) NOT NULL DEFAULT '0' COMMENT '用户id', `username` char(100) NOT NULL DEFAULT '' COMMENT '用户名', `create_time` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', - PRIMARY KEY (`log_id`) + PRIMARY KEY (`log_id`), + KEY (`level`, `username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='系统操作日志表'; -- -------------------------------- @@ -283,7 +287,7 @@ CREATE TABLE `mw_contact` ( ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='联系人表'; -- -------------------------------- --- 附件表 +-- 附件信息表 -- -------------------------------- DROP TABLE IF EXISTS `mw_attachment`; CREATE TABLE `mw_attachment` ( @@ -292,8 +296,9 @@ CREATE TABLE `mw_attachment` ( `document_id` int(10) NOT NULL DEFAULT '0' COMMENT '所属文档id', `name` varchar(50) NOT NULL DEFAULT '' COMMENT '附件名称', `path` varchar(100) NOT NULL DEFAULT '' COMMENT '附件路径', + `source` tinyint(1) NOT NULL DEFAULT '0' COMMENT '附件来源, 0 默认是附件 1 图片', `create_time` int(11) NOT NULL DEFAULT '0' COMMENT '创建时间', `update_time` int(11) NOT NULL DEFAULT '0' COMMENT '更新时间', PRIMARY KEY (`attachment_id`), - KEY (`document_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='附件表'; \ No newline at end of file + KEY (`document_id`, `source`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='附件信息表'; \ No newline at end of file diff --git a/views/attachment/image.html b/views/attachment/image.html new file mode 100644 index 00000000..2f25094f --- /dev/null +++ b/views/attachment/image.html @@ -0,0 +1,43 @@ +
      {{$attachments := .attachments}}{{$documentId := .document_id}} +
      {{$isDelete := .is_delete}} +
      +
      +
      + +
      +
      +
      + + + + + + + + + + + {{range $attachment := $attachments}} + + + + + + + {{end}} + +
      图片上传用户上传时间操作
      + + + + {{$attachment.username}}{{dateFormat $attachment.create_time "Y-m-d H:i:s"}} + {{if eq $isDelete true}} + + {{end}} +
      +
      +
      +
      \ No newline at end of file diff --git a/views/attachment/page.html b/views/attachment/page.html index 1686fcd9..72485a8a 100644 --- a/views/attachment/page.html +++ b/views/attachment/page.html @@ -1,15 +1,22 @@
      {{$attachments := .attachments}}{{$documentId := .document_id}} -
      -
      {{$isDelete := .is_delete}}{{$isUpload := .is_upload}} - {{if eq $isUpload true}} +
      {{$isDelete := .is_delete}}{{$isUpload := .is_upload}} +
      +
      + +
      +
      + {{if eq $isUpload true}} +
      -
      {{end}}
      diff --git a/views/page/edit.html b/views/page/edit.html index 9983832b..18920f6e 100644 --- a/views/page/edit.html +++ b/views/page/edit.html @@ -85,7 +85,7 @@ //dialogMaskBgColor : "#000", // 设置透明遮罩层的背景颜色,全局通用,默认为#fff imageUpload : true, imageFormats : ["jpg", "jpeg", "gif", "png", "bmp", "webp"], - imageUploadURL : "/image/upload", + imageUploadURL : "/image/upload?document_id={{$document.document_id}}", onload : function() { var docStorage = Storage.get(storageId); if (docStorage !== "") { From 18fc65e1a98e06025125585771bd3db42910daa6 Mon Sep 17 00:00:00 2001 From: phachon Date: Sun, 2 Jun 2019 14:32:49 +0800 Subject: [PATCH 09/14] delete image;export file and attach; --- app/bootstrap.go | 23 ++--- app/controllers/attachment.go | 18 ++-- app/controllers/document.go | 6 ++ app/controllers/page.go | 44 ++++++++-- app/models/attachment.go | 63 +++++++++++++- app/modules/system/controllers/space.go | 1 - app/utils/document.go | 31 +++++-- app/utils/zipx.go | 110 +++++++++++++++++++++++- app/utils/zipx_test.go | 30 +++++++ docs/databases/table.sql | 2 +- 10 files changed, 294 insertions(+), 34 deletions(-) create mode 100644 app/utils/zipx_test.go diff --git a/app/bootstrap.go b/app/bootstrap.go index e6720939..07ad2ac1 100644 --- a/app/bootstrap.go +++ b/app/bootstrap.go @@ -32,8 +32,6 @@ var ( StartTime = int64(0) - RootDir = "" - DocumentAbsDir = "" MarkdownAbsDir = "" @@ -161,20 +159,24 @@ func initDocumentDir() { os.Exit(1) } - rootAbsDir, err := filepath.Abs(docRootDir) + documentAbsDir, err := filepath.Abs(docRootDir) if err != nil { beego.Error("document root dir " + docRootDir + " is error!") os.Exit(1) } - DocumentAbsDir = rootAbsDir + DocumentAbsDir = documentAbsDir // markdown save dir - markDownAbsDir := path.Join(rootAbsDir, "markdowns") + markDownAbsDir := path.Join(documentAbsDir, "markdowns") // image save dir - imagesAbsDir := path.Join(rootAbsDir, "images") + imagesAbsDir := path.Join(documentAbsDir, "images") // attachment save dir - attachmentAbsDir := path.Join(rootAbsDir, "attachment") + attachmentAbsDir := path.Join(documentAbsDir, "attachment") + + MarkdownAbsDir = markDownAbsDir + ImageAbsDir = imagesAbsDir + AttachmentAbsDir = attachmentAbsDir // create markdown dir ok, _ = utils.File.PathIsExists(markDownAbsDir) @@ -204,10 +206,9 @@ func initDocumentDir() { } } - utils.Document.RootAbsDir = markDownAbsDir - MarkdownAbsDir = markDownAbsDir - ImageAbsDir = imagesAbsDir - AttachmentAbsDir = attachmentAbsDir + // utils document + utils.Document.MarkdownAbsDir = markDownAbsDir + utils.Document.DocumentAbsDir = documentAbsDir beego.SetStaticPath("/images/", ImageAbsDir) // todo diff --git a/app/controllers/attachment.go b/app/controllers/attachment.go index a04da134..d6d2c8c9 100644 --- a/app/controllers/attachment.go +++ b/app/controllers/attachment.go @@ -210,28 +210,28 @@ func (this *AttachmentController) Delete() { if !isManager { this.jsonError("您没有权限删除该空间下的文档!") } - attachmentFilePath := path.Join(app.DocumentAbsDir, attachment["path"]) attachmentName := attachment["name"] + attachmentSource := attachment["source"] // delete db - err = models.AttachmentModel.Delete(attachmentId) + err = models.AttachmentModel.DeleteAttachmentDBFile(attachmentId) if err != nil { this.ErrorLog("删除附件 " + attachmentId + " 失败: " + err.Error()) this.jsonError("删除附件失败") } - // delete file - err = os.Remove(attachmentFilePath) - if err != nil { - this.WarningLog("删除附件 " + attachmentFilePath + " 失败: " + err.Error()) - } // update document log go func() { _, _ = models.LogDocumentModel.UpdateAction(this.UserId, documentId, "删除了附件 "+attachmentName) }() - this.InfoLog("删除附件 " + attachmentId + " 成功") - this.jsonSuccess("删除附件成功", nil, "/attachment/page?document_id="+documentId) + redirect := fmt.Sprintf("/attachment/page?document_id=%s", documentId) + if attachmentSource == fmt.Sprintf("%d", models.Attachment_Source_Image) { + redirect = fmt.Sprintf("/attachment/image?document_id=%s", documentId) + } + + this.InfoLog("删除文档 "+documentId+" 附件 " + attachmentName + " 成功") + this.jsonSuccess("删除成功", nil, redirect) } func (this *AttachmentController) Download() { diff --git a/app/controllers/document.go b/app/controllers/document.go index d3236494..be314d3a 100644 --- a/app/controllers/document.go +++ b/app/controllers/document.go @@ -429,6 +429,12 @@ func (this *DocumentController) Delete() { this.jsonError("删除文档失败!") } + // delete attachment + err = models.AttachmentModel.DeleteAttachmentsDBFileByDocumentId(documentId) + if err != nil { + this.ErrorLog("删除文档 " + documentId + " 附件失败:" + err.Error()) + } + this.InfoLog("删除文档 " + documentId + " 成功") this.jsonSuccess("删除文档成功", "", "/document/index?document_id="+document["parent_id"]) } diff --git a/app/controllers/page.go b/app/controllers/page.go index f24fccab..61073d43 100644 --- a/app/controllers/page.go +++ b/app/controllers/page.go @@ -3,11 +3,13 @@ package controllers import ( "errors" "fmt" - "regexp" - "strings" - + "mm-wiki/app" "mm-wiki/app/models" "mm-wiki/app/utils" + "os" + "path/filepath" + "regexp" + "strings" "github.com/astaxie/beego" ) @@ -412,9 +414,41 @@ func (this *PageController) Export() { this.ViewError("父文档不存在!") } - // get document file + packFiles := []*utils.CompressFileInfo{} + absPageFile := utils.Document.GetAbsPageFileByPageFile(pageFile) - this.Ctx.Output.Download(absPageFile, document["name"]+utils.Document_Page_Suffix) + // pack document file + packFiles = append(packFiles, &utils.CompressFileInfo{ + File: absPageFile, + PrefixPath: "", + }) + + // get document attachments + attachments, err := models.AttachmentModel.GetAttachmentsByDocumentId(documentId) + if err != nil { + this.ErrorLog("查找文档附件失败:" + err.Error()) + this.ViewError("查找文档附件失败!") + } + for _, attachment := range attachments { + if attachment["path"] == "" { + continue + } + path := attachment["path"] + attachmentFile := filepath.Join(app.DocumentAbsDir, path) + packFile := &utils.CompressFileInfo{ + File: attachmentFile, + PrefixPath: filepath.Dir(path), + } + packFiles = append(packFiles, packFile) + } + var dest = fmt.Sprintf("%s/mm_wiki/%s.zip", os.TempDir(), document["name"]) + err = utils.Zipx.PackFile(packFiles, dest) + if err != nil { + this.ErrorLog("导出文档附件失败:" + err.Error()) + this.ViewError("导出文档失败!") + } + + this.Ctx.Output.Download(dest, document["name"] + ".zip") } func sendEmail(documentId string, username string, comment string, url string) error { diff --git a/app/models/attachment.go b/app/models/attachment.go index 9a0adea6..a66e63e7 100644 --- a/app/models/attachment.go +++ b/app/models/attachment.go @@ -3,6 +3,7 @@ package models import ( "fmt" "github.com/snail007/go-activerecord/mysql" + "mm-wiki/app/utils" "time" ) @@ -79,7 +80,7 @@ func (a *Attachment) GetAttachmentByName(name string) (attachment map[string]str return } -// get attachments by document +// get attachments by document and source func (a *Attachment) GetAttachmentsByDocumentIdAndSource(documentId string, source int) (attachments []map[string]string, err error) { db := G.DB() var rs *mysql.ResultSet @@ -94,6 +95,20 @@ func (a *Attachment) GetAttachmentsByDocumentIdAndSource(documentId string, sour return } +// get attachments by document_id +func (a *Attachment) GetAttachmentsByDocumentId(documentId string) (attachments []map[string]string, err error) { + db := G.DB() + var rs *mysql.ResultSet + rs, err = db.Query(db.AR().From(Table_Attachment_Name).Where(map[string]interface{}{ + "document_id": documentId, + })) + if err != nil { + return + } + attachments = rs.Rows() + return +} + // delete attachment by attachment_id func (a *Attachment) Delete(attachmentId string) (err error) { db := G.DB() @@ -174,3 +189,49 @@ func (a *Attachment) GetAttachmentByAttachmentIds(attachmentIds []string) (attac attachments = rs.Rows() return } + +func (a *Attachment) DeleteAttachmentsDBFileByDocumentId(documentId string) (err error) { + db := G.DB() + attachments, err := a.GetAttachmentsByDocumentId(documentId) + if err != nil { + return + } + + // delete attachment file + err = utils.Document.DeleteAttachment(attachments) + if err != nil { + return err + } + + // delete db attachment + _, err = db.Exec(db.AR().Delete(Table_Attachment_Name, map[string]interface{}{ + "document_id": documentId, + })) + if err != nil { + return + } + return nil +} + +func (a *Attachment) DeleteAttachmentDBFile(attachmentId string) (err error) { + db := G.DB() + attachment, err := a.GetAttachmentByAttachmentId(attachmentId) + if err != nil { + return + } + + // delete attachment file + err = utils.Document.DeleteAttachment([]map[string]string{attachment}) + if err != nil { + return err + } + + // delete db attachment + _, err = db.Exec(db.AR().Delete(Table_Attachment_Name, map[string]interface{}{ + "attachment_id": attachmentId, + })) + if err != nil { + return + } + return nil +} \ No newline at end of file diff --git a/app/modules/system/controllers/space.go b/app/modules/system/controllers/space.go index 2d3baf99..23699bae 100644 --- a/app/modules/system/controllers/space.go +++ b/app/modules/system/controllers/space.go @@ -359,7 +359,6 @@ func (this *SpaceController) Download() { os.Mkdir(tempDir, 0777) var dest = tempDir + "/" + spaceName + ".zip" - fmt.Println(dest) err = utils.Zipx.Compress(files, dest) if err != nil { this.ErrorLog("下载空间 " + spaceId + " 压缩文档失败:" + err.Error()) diff --git a/app/utils/document.go b/app/utils/document.go index be109d48..410ed33b 100644 --- a/app/utils/document.go +++ b/app/utils/document.go @@ -8,7 +8,7 @@ import ( "sync" ) -var Document = NewDocument("./data") +var Document = NewDocument("./data", "./data/markdowns") const ( Document_Default_FileName = "README" @@ -20,14 +20,16 @@ const ( Document_Type_Dir = 2 ) -func NewDocument(rootAbsDir string) *document { +func NewDocument(documentAbsDir string, markdownAbsDir string) *document { return &document{ - RootAbsDir: rootAbsDir, + DocumentAbsDir: documentAbsDir, + MarkdownAbsDir: markdownAbsDir, } } type document struct { - RootAbsDir string + DocumentAbsDir string + MarkdownAbsDir string lock sync.Mutex } @@ -48,7 +50,7 @@ func (d *document) GetDefaultPageFileBySpaceName(name string) string { // get document abs pageFile func (d *document) GetAbsPageFileByPageFile(pageFile string) string { - return d.RootAbsDir + "/" + pageFile + return d.MarkdownAbsDir + "/" + pageFile } // get document content by pageFile @@ -177,3 +179,22 @@ func (d *document) Move(movePath string, targetPath string, docType int) error { } return os.Rename(filepath.Dir(absOldPageFile), filepath.Dir(absTargetPageFile)) } + +// delete document attachment +func (d *document) DeleteAttachment(attachments []map[string]string) error { + d.lock.Lock() + defer d.lock.Unlock() + + if len(attachments) == 0 { + return nil + } + // delete attachment file + for _, attachment := range attachments { + if len(attachment) == 0 || attachment["path"] == "" { + continue + } + file := filepath.Join(d.DocumentAbsDir, attachment["path"]) + _ = os.Remove(file) + } + return nil +} \ No newline at end of file diff --git a/app/utils/zipx.go b/app/utils/zipx.go index ef88a7bd..7db83d32 100644 --- a/app/utils/zipx.go +++ b/app/utils/zipx.go @@ -4,25 +4,84 @@ import ( "archive/zip" "io" "os" + "path/filepath" "strings" + "sync" ) var Zipx = NewZipx() type zipx struct { + lock sync.Mutex } func NewZipx() *zipx { return &zipx{} } +type CompressFileInfo struct { + File string + PrefixPath string + osFile *os.File +} + +func (z *zipx) PackFile(files []*CompressFileInfo, dest string) error { + + z.lock.Lock() + defer z.lock.Unlock() + + // create dest file + destDir := filepath.Dir(dest) + err := os.RemoveAll(destDir) + if err != nil { + return err + } + err = os.MkdirAll(destDir, 0777) + if err != nil { + return err + } + d, err := os.Create(dest) + if err != nil { + return err + } + defer d.Close() + + // file handle + for _, f := range files { + f3, err := os.Open(f.File) + if err != nil { + continue + } + f.osFile = f3 + } + defer func() { + for _, f := range files { + if f.osFile == nil { + continue + } + _ = f.osFile.Close() + } + }() + + // zip writer + w := zip.NewWriter(d) + defer w.Close() + for _, file := range files { + err := z.compress(file.osFile, file.PrefixPath, w) + if err != nil { + return err + } + } + return nil +} + func (z *zipx) Compress(files []*os.File, dest string) error { d, _ := os.Create(dest) defer d.Close() w := zip.NewWriter(d) defer w.Close() for _, file := range files { - err := z.compress(file, "", w) + err := z.compress(file, "images/1/4", w) if err != nil { return err } @@ -121,3 +180,52 @@ func (z *zipx) subString(str string, start, end int) string { return string(rs[start:end]) } + +// srcFile could be a single file or a directory +func (z *zipx)Zip(srcFile string, destZip string) error { + zipfile, err := os.Create(destZip) + if err != nil { + return err + } + defer zipfile.Close() + + archive := zip.NewWriter(zipfile) + defer archive.Close() + + _ = filepath.Walk(srcFile, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + header, err := zip.FileInfoHeader(info) + if err != nil { + return err + } + + + header.Name = strings.TrimPrefix(path, filepath.Dir(srcFile) + "/") + // header.Name = path + if info.IsDir() { + header.Name += "/" + } else { + header.Method = zip.Deflate + } + + writer, err := archive.CreateHeader(header) + if err != nil { + return err + } + + if !info.IsDir() { + file, err := os.Open(path) + if err != nil { + return err + } + defer file.Close() + _, err = io.Copy(writer, file) + } + return err + }) + + return err +} \ No newline at end of file diff --git a/app/utils/zipx_test.go b/app/utils/zipx_test.go new file mode 100644 index 00000000..82ca81f6 --- /dev/null +++ b/app/utils/zipx_test.go @@ -0,0 +1,30 @@ +package utils + +import ( + "os" + "testing" +) + +func TestZipx_Zip(t *testing.T) { + + err := Zipx.Zip("~/Code/data/mm_wiki/attachment/1/4/", "/Users/phachon/Downloads/test.zip") + if err != nil { + t.Error(err) + } +} + +func TestZipx_Compress(t *testing.T) { + + osFiles := []*os.File{} + f3, err := os.Open("/Users/phachon/Code/data/mm_wiki/images/1/4/redis2.jpeg") + if err != nil { + t.Error(err) + } + defer f3.Close() + osFiles = append(osFiles, f3) + + err = Zipx.Compress(osFiles, "/Users/phachon/Downloads/demo.zip") + if err != nil { + t.Error(err) + } +} \ No newline at end of file diff --git a/docs/databases/table.sql b/docs/databases/table.sql index 75571e2e..3e8531a8 100644 --- a/docs/databases/table.sql +++ b/docs/databases/table.sql @@ -188,7 +188,7 @@ CREATE TABLE `mw_log_document` ( -- -------------------------------- DROP TABLE IF EXISTS `mw_log`; CREATE TABLE `mw_log` ( - `log_id` int(10) NOT NULL AUTO_INCREMENT COMMENT '系统操作日志 id', + `log_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '系统操作日志 id', `level` tinyint(3) NOT NULL DEFAULT '6' COMMENT '日志级别', `path` char(100) NOT NULL DEFAULT '' COMMENT '请求路径', `get` text NOT NULL COMMENT 'get参数', From 90c9f45fbc72e8fb6eba876f33b73abfd99e9c8b Mon Sep 17 00:00:00 2001 From: phachon Date: Sun, 2 Jun 2019 14:55:16 +0800 Subject: [PATCH 10/14] delete space attachment; download space attachment --- app/models/attachment.go | 28 +++++++++++++++ app/modules/system/controllers/space.go | 48 +++++++++++++++++-------- 2 files changed, 61 insertions(+), 15 deletions(-) diff --git a/app/models/attachment.go b/app/models/attachment.go index a66e63e7..be2b8cb5 100644 --- a/app/models/attachment.go +++ b/app/models/attachment.go @@ -109,6 +109,34 @@ func (a *Attachment) GetAttachmentsByDocumentId(documentId string) (attachments return } +// get attachments by document_id +func (a *Attachment) GetAttachmentsByDocumentIds(documentIds []string) (attachments []map[string]string, err error) { + db := G.DB() + var rs *mysql.ResultSet + rs, err = db.Query(db.AR().From(Table_Attachment_Name).Where(map[string]interface{}{ + "document_id": documentIds, + })) + if err != nil { + return + } + attachments = rs.Rows() + return +} + +// get attachments by space_id +func (a *Attachment) GetAttachmentsBySpaceId(spaceId string) (attachments []map[string]string, err error) { + documents, err := DocumentModel.GetDocumentsBySpaceId(spaceId) + if err != nil { + return + } + documentIds := []string{} + for _, document := range documents { + documentIds = append(documentIds, document["document_id"]) + } + + return a.GetAttachmentsByDocumentIds(documentIds) +} + // delete attachment by attachment_id func (a *Attachment) Delete(attachmentId string) (err error) { db := G.DB() diff --git a/app/modules/system/controllers/space.go b/app/modules/system/controllers/space.go index 23699bae..79a1bc7d 100644 --- a/app/modules/system/controllers/space.go +++ b/app/modules/system/controllers/space.go @@ -2,9 +2,11 @@ package controllers import ( "fmt" + "mm-wiki/app" "mm-wiki/app/models" "mm-wiki/app/utils" "os" + "path/filepath" "regexp" "strings" ) @@ -296,10 +298,12 @@ func (this *SpaceController) Delete() { // delete space dir and documentId _, pageFile, err := models.DocumentModel.GetParentDocumentsByDocument(documents[0]) if err != nil { - this.ErrorLog("删除空间 " + spaceId + " 失败: " + err.Error()) + this.ErrorLog("删除空间 " + spaceId + " 获取空间文件失败: " + err.Error()) this.jsonError("删除空间失败") } err = models.DocumentModel.DeleteDBAndFile(documents[0]["document_id"], this.UserId, pageFile, fmt.Sprintf("%d", models.Document_Type_Dir)) + // delete space document attachments + _ = models.AttachmentModel.DeleteAttachmentsDBFileByDocumentId(documents[0]["document_id"]) } } else { // delete space dir @@ -346,23 +350,37 @@ func (this *SpaceController) Download() { spaceName := space["name"] spacePath := utils.Document.GetAbsPageFileByPageFile(spaceName) - f3, err := os.Open(spacePath) - if err != nil { - this.ErrorLog("下载空间 " + spaceId + " 打开空间目录失败:" + err.Error()) - this.ViewError("下载空间文档失败") - } - defer f3.Close() - var files = []*os.File{f3} + packFiles := []*utils.CompressFileInfo{} - tempDir := os.TempDir() + "/" + "mmwiki" - os.RemoveAll(tempDir) - os.Mkdir(tempDir, 0777) - var dest = tempDir + "/" + spaceName + ".zip" + // pack space all markdown file + packFiles = append(packFiles, &utils.CompressFileInfo{ + File: spacePath, + PrefixPath: "", + }) - err = utils.Zipx.Compress(files, dest) + // get space all document attachments + attachments, err := models.AttachmentModel.GetAttachmentsBySpaceId(spaceId) + if err != nil { + this.ErrorLog("查找空间文档附件失败:" + err.Error()) + this.ViewError("查找空间文档附件失败!") + } + for _, attachment := range attachments { + if attachment["path"] == "" { + continue + } + path := attachment["path"] + attachmentFile := filepath.Join(app.DocumentAbsDir, path) + packFile := &utils.CompressFileInfo{ + File: attachmentFile, + PrefixPath: filepath.Dir(path), + } + packFiles = append(packFiles, packFile) + } + var dest = fmt.Sprintf("%s/mm_wiki/%s.zip", os.TempDir(), spaceName) + err = utils.Zipx.PackFile(packFiles, dest) if err != nil { - this.ErrorLog("下载空间 " + spaceId + " 压缩文档失败:" + err.Error()) - this.ViewError("空间文档压缩失败") + this.ErrorLog("下载空间文档失败:" + err.Error()) + this.ViewError("下载空间文档失败!") } this.Ctx.Output.Download(dest, spaceName+".zip") From 9ad1813d6cbde88f213b8a606d51485db73f3b3f Mon Sep 17 00:00:00 2001 From: phachon Date: Sun, 2 Jun 2019 15:45:32 +0800 Subject: [PATCH 11/14] fix #53 --- app/models/document.go | 17 ++++++++ app/models/space.go | 56 +++++++++++++++++++++++++ app/modules/system/controllers/space.go | 8 ++-- app/utils/document.go | 11 +++++ 4 files changed, 89 insertions(+), 3 deletions(-) diff --git a/app/models/document.go b/app/models/document.go index bf931c7a..1257ac15 100644 --- a/app/models/document.go +++ b/app/models/document.go @@ -102,6 +102,23 @@ func (d *Document) GetDocumentByNameParentIdAndSpaceId(name string, parentId str return } +// get document by name and spaceId +func (d *Document) GetDocumentByParentIdAndSpaceId(parentId string, spaceId string, docType int) (document map[string]string, err error) { + db := G.DB() + var rs *mysql.ResultSet + rs, err = db.Query(db.AR().From(Table_Document_Name).Where(map[string]interface{}{ + "space_id": spaceId, + "parent_id": parentId, + "type": docType, + "is_delete": Document_Delete_False, + }).Limit(0, 1)) + if err != nil { + return + } + document = rs.Row() + return +} + // delete document by document_id func (d *Document) DeleteDBAndFile(documentId string, userId string, pageFile string, docType string) (err error) { db := G.DB() diff --git a/app/models/space.go b/app/models/space.go index 1ba2c5ea..f9f2ed85 100644 --- a/app/models/space.go +++ b/app/models/space.go @@ -149,6 +149,62 @@ func (s *Space) Update(spaceId string, spaceValue map[string]interface{}) (id in return } +// update space db and file name by space_id +func (s *Space) UpdateDBAndSpaceFileName(spaceId string, spaceValue map[string]interface{}, oldName string) (id int64, err error) { + // begin update + db := G.DB() + tx, err := db.Begin(db.Config) + if err != nil { + return + } + var rs *mysql.ResultSet + + // get real old space name (v0.1.2 #53 bug) + defaultDocument, err := DocumentModel.GetDocumentByParentIdAndSpaceId("0", spaceId, Document_Type_Dir) + if err != nil { + return + } + if oldName != defaultDocument["name"] { + oldName = defaultDocument["name"] + } + + // update space db + spaceValue["update_time"] = time.Now().Unix() + rs, err = db.ExecTx(db.AR().Update(Table_Space_Name, spaceValue, map[string]interface{}{ + "space_id": spaceId, + "is_delete": Space_Delete_False, + }),tx) + if err != nil { + tx.Rollback() + return + } + id = rs.LastInsertId + + documentValue := map[string]interface{}{ + "name": spaceValue["name"], + "update_time": time.Now().Unix(), + } + // update space document name + _, err = db.ExecTx(db.AR().Update(Table_Document_Name, documentValue, map[string]interface{}{ + "space_id": spaceId, + "parent_id": 0, + "type": Document_Type_Dir, + }), tx) + if err != nil { + tx.Rollback() + return + } + // update space name + err = utils.Document.UpdateSpaceName(oldName, spaceValue["name"].(string)) + if err != nil { + tx.Rollback() + return + } + err = tx.Commit() + + return +} + // get limit spaces by search keyword func (s *Space) GetSpacesByKeywordAndLimit(keyword string, limit int, number int) (spaces []map[string]string, err error) { diff --git a/app/modules/system/controllers/space.go b/app/modules/system/controllers/space.go index 79a1bc7d..6402e6c2 100644 --- a/app/modules/system/controllers/space.go +++ b/app/modules/system/controllers/space.go @@ -188,15 +188,17 @@ func (this *SpaceController) Modify() { if ok { this.jsonError("空间名已经存在!") } - _, err = models.SpaceModel.Update(spaceId, map[string]interface{}{ + + spaceValue := map[string]interface{}{ "name": name, "description": description, "tags": tags, "visit_level": visitLevel, "is_share": isShare, "is_export": isExport, - }) - + } + // update space document dir name if name update + _, err = models.SpaceModel.UpdateDBAndSpaceFileName(spaceId, spaceValue, space["name"]) if err != nil { this.ErrorLog("修改空间 " + spaceId + " 失败:" + err.Error()) this.jsonError("修改空间失败") diff --git a/app/utils/document.go b/app/utils/document.go index 410ed33b..3f69f532 100644 --- a/app/utils/document.go +++ b/app/utils/document.go @@ -135,6 +135,17 @@ func (d *document) Update(oldPageFile string, name string, content string, docTy return nil } +func (d *document) UpdateSpaceName(oldSpaceName string, newName string) error { + + d.lock.Lock() + defer d.lock.Unlock() + + spaceOldDir := d.GetAbsPageFileByPageFile(oldSpaceName) + spaceNewDir := d.GetAbsPageFileByPageFile(newName) + err := os.Rename(spaceOldDir, spaceNewDir) + return err +} + // delete document func (d *document) Delete(path string, docType int) error { d.lock.Lock() From 438c25623c9cd25bf2e1870ce714d76d8c05a789 Mon Sep 17 00:00:00 2001 From: phachon Date: Sun, 2 Jun 2019 15:49:35 +0800 Subject: [PATCH 12/14] fmt file --- app/controllers/attachment.go | 4 ++-- app/models/attachment.go | 6 +++--- app/models/space.go | 8 ++++---- app/modules/system/controllers/space.go | 4 ++-- app/utils/document.go | 4 ++-- app/utils/zipx.go | 11 +++++------ 6 files changed, 18 insertions(+), 19 deletions(-) diff --git a/app/controllers/attachment.go b/app/controllers/attachment.go index d6d2c8c9..36a23cec 100644 --- a/app/controllers/attachment.go +++ b/app/controllers/attachment.go @@ -230,7 +230,7 @@ func (this *AttachmentController) Delete() { redirect = fmt.Sprintf("/attachment/image?document_id=%s", documentId) } - this.InfoLog("删除文档 "+documentId+" 附件 " + attachmentName + " 成功") + this.InfoLog("删除文档 " + documentId + " 附件 " + attachmentName + " 成功") this.jsonSuccess("删除成功", nil, redirect) } @@ -339,4 +339,4 @@ func (this *AttachmentController) Image() { this.Data["document_id"] = documentId this.Data["is_delete"] = isEditor this.viewLayout("attachment/image", "attachment") -} \ No newline at end of file +} diff --git a/app/models/attachment.go b/app/models/attachment.go index be2b8cb5..61ebe35e 100644 --- a/app/models/attachment.go +++ b/app/models/attachment.go @@ -11,7 +11,7 @@ const Table_Attachment_Name = "attachment" const ( Attachment_Source_Default = 0 - Attachment_Source_Image = 1 + Attachment_Source_Image = 1 ) type Attachment struct { @@ -86,7 +86,7 @@ func (a *Attachment) GetAttachmentsByDocumentIdAndSource(documentId string, sour var rs *mysql.ResultSet rs, err = db.Query(db.AR().From(Table_Attachment_Name).Where(map[string]interface{}{ "document_id": documentId, - "source": source, + "source": source, }).OrderBy("attachment_id", "desc")) if err != nil { return @@ -262,4 +262,4 @@ func (a *Attachment) DeleteAttachmentDBFile(attachmentId string) (err error) { return } return nil -} \ No newline at end of file +} diff --git a/app/models/space.go b/app/models/space.go index f9f2ed85..65bd90cc 100644 --- a/app/models/space.go +++ b/app/models/space.go @@ -173,7 +173,7 @@ func (s *Space) UpdateDBAndSpaceFileName(spaceId string, spaceValue map[string]i rs, err = db.ExecTx(db.AR().Update(Table_Space_Name, spaceValue, map[string]interface{}{ "space_id": spaceId, "is_delete": Space_Delete_False, - }),tx) + }), tx) if err != nil { tx.Rollback() return @@ -181,14 +181,14 @@ func (s *Space) UpdateDBAndSpaceFileName(spaceId string, spaceValue map[string]i id = rs.LastInsertId documentValue := map[string]interface{}{ - "name": spaceValue["name"], + "name": spaceValue["name"], "update_time": time.Now().Unix(), } // update space document name _, err = db.ExecTx(db.AR().Update(Table_Document_Name, documentValue, map[string]interface{}{ - "space_id": spaceId, + "space_id": spaceId, "parent_id": 0, - "type": Document_Type_Dir, + "type": Document_Type_Dir, }), tx) if err != nil { tx.Rollback() diff --git a/app/modules/system/controllers/space.go b/app/modules/system/controllers/space.go index 6402e6c2..cfed2875 100644 --- a/app/modules/system/controllers/space.go +++ b/app/modules/system/controllers/space.go @@ -356,7 +356,7 @@ func (this *SpaceController) Download() { // pack space all markdown file packFiles = append(packFiles, &utils.CompressFileInfo{ - File: spacePath, + File: spacePath, PrefixPath: "", }) @@ -373,7 +373,7 @@ func (this *SpaceController) Download() { path := attachment["path"] attachmentFile := filepath.Join(app.DocumentAbsDir, path) packFile := &utils.CompressFileInfo{ - File: attachmentFile, + File: attachmentFile, PrefixPath: filepath.Dir(path), } packFiles = append(packFiles, packFile) diff --git a/app/utils/document.go b/app/utils/document.go index 3f69f532..b37256c2 100644 --- a/app/utils/document.go +++ b/app/utils/document.go @@ -30,7 +30,7 @@ func NewDocument(documentAbsDir string, markdownAbsDir string) *document { type document struct { DocumentAbsDir string MarkdownAbsDir string - lock sync.Mutex + lock sync.Mutex } // get document page file by parentPath @@ -208,4 +208,4 @@ func (d *document) DeleteAttachment(attachments []map[string]string) error { _ = os.Remove(file) } return nil -} \ No newline at end of file +} diff --git a/app/utils/zipx.go b/app/utils/zipx.go index 7db83d32..80c779a0 100644 --- a/app/utils/zipx.go +++ b/app/utils/zipx.go @@ -20,9 +20,9 @@ func NewZipx() *zipx { } type CompressFileInfo struct { - File string + File string PrefixPath string - osFile *os.File + osFile *os.File } func (z *zipx) PackFile(files []*CompressFileInfo, dest string) error { @@ -182,7 +182,7 @@ func (z *zipx) subString(str string, start, end int) string { } // srcFile could be a single file or a directory -func (z *zipx)Zip(srcFile string, destZip string) error { +func (z *zipx) Zip(srcFile string, destZip string) error { zipfile, err := os.Create(destZip) if err != nil { return err @@ -202,8 +202,7 @@ func (z *zipx)Zip(srcFile string, destZip string) error { return err } - - header.Name = strings.TrimPrefix(path, filepath.Dir(srcFile) + "/") + header.Name = strings.TrimPrefix(path, filepath.Dir(srcFile)+"/") // header.Name = path if info.IsDir() { header.Name += "/" @@ -228,4 +227,4 @@ func (z *zipx)Zip(srcFile string, destZip string) error { }) return err -} \ No newline at end of file +} From 7b8cac1cc83acaa0c8b42e195b729c8693bd242c Mon Sep 17 00:00:00 2001 From: phachon Date: Sun, 2 Jun 2019 16:06:17 +0800 Subject: [PATCH 13/14] fix --- app/controllers/main.go | 3 ++- views/main/default.html | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/controllers/main.go b/app/controllers/main.go index 9965c9d3..dc5def3e 100644 --- a/app/controllers/main.go +++ b/app/controllers/main.go @@ -34,11 +34,12 @@ func (this *MainController) Index() { func (this *MainController) Default() { page, _ := this.GetInt("page", 1) + number, _ := this.GetInt("number", 10) maxPage := 10 if page >= maxPage { page = maxPage } - number := 8 + //number := 8 limit := (page - 1) * number logDocuments, err := models.LogDocumentModel.GetLogDocumentsByLimit(limit, number) diff --git a/views/main/default.html b/views/main/default.html index 0dfd5d5f..47b008af 100644 --- a/views/main/default.html +++ b/views/main/default.html @@ -46,7 +46,7 @@

      {{.panel_title}}

      - {{template "paginator/document_update.html" .}} + {{template "paginator/default.html" .}}
      From e4fab15a09ae5d98156d35597d7c6ddce43d0911 Mon Sep 17 00:00:00 2001 From: phachon Date: Sun, 2 Jun 2019 18:09:48 +0800 Subject: [PATCH 14/14] add v0.1.3 changelog and upgrade --- CHANGELOG.md | 17 ++++++++++++++++ app/bootstrap.go | 8 +++++++- app/models/upgrade.go | 46 ++++++++++++++++++++++++++++++++++++++----- 3 files changed, 65 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a5ad8db..2405a279 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,26 @@ ## v0.1.3 (2018-12) ### Fix Bug & Add Feature +#### 修复bug 1. 修复启动命令不支持绝对路径问题 2. 安装向导页面优化 3. 修复首页快捷链接没有按排序号排序问题 +4. 代码 go fmt +5. 更新 copyright +6. 修改系统权限 #55 +7. 修复私有文档未授权访问漏洞 #55 +8. 修复文档导出漏洞 #66 +9. 新建文档可回车提交 #43 +10. 修复文档编辑窗口过短问题 #46 +11. 安装的最小环境限制 #45 +11. 空间名修改后没有更新 #53 + +#### 新增功能 +1. 新增附件上传,附件列表查看,附件删除功能 +2. 图片上传分目录存储,可查看图片列表并删除 +3. 分页增加每一页数量控制功能 +4. 导出文件同时导出附件和图片 +5. 下载文件同时下载所有的图片和附件 ### Upgrade 1. 下载新版本到部署该项目的根目录 diff --git a/app/bootstrap.go b/app/bootstrap.go index 07ad2ac1..dca31a8f 100644 --- a/app/bootstrap.go +++ b/app/bootstrap.go @@ -32,6 +32,8 @@ var ( StartTime = int64(0) + RootDir = "" + DocumentAbsDir = "" MarkdownAbsDir = "" @@ -97,7 +99,11 @@ func initConfig() { beego.BConfig.AppName = beego.AppConfig.String("sys.name") beego.BConfig.ServerName = beego.AppConfig.String("sys.name") - RootDir, _ := filepath.Abs(filepath.Dir(os.Args[0])) + RootDir, err := filepath.Abs(filepath.Dir(os.Args[0])) + if err != nil { + log.Println("init config error: "+err.Error()) + os.Exit(1) + } // set static path beego.SetStaticPath("/static/", filepath.Join(RootDir, "./static")) diff --git a/app/models/upgrade.go b/app/models/upgrade.go index 40e794bb..60dc55bf 100644 --- a/app/models/upgrade.go +++ b/app/models/upgrade.go @@ -1,7 +1,9 @@ package models import ( + "database/sql" "errors" + "fmt" "github.com/astaxie/beego" "mm-wiki/app/utils" ) @@ -26,8 +28,8 @@ var ( func (up *Upgrade) initHandleFunc() { // v0 ~ v0.1.2 upgradeMap = append(upgradeMap, &upgradeHandle{Version: "v0.1.2", Func: up.v0ToV012}) - // v0.1.2 ~ v0.1.8 - //upgradeMap = append(upgradeMap, &upgradeHandle{Version: "v0.1.8", Func: up.v012ToV018}) + // v0.1.2 ~ v0.1.3 + upgradeMap = append(upgradeMap, &upgradeHandle{Version: "v0.1.3", Func: up.v012ToV013}) // v0.1.8 ~ v0.2.1 //upgradeMap = append(upgradeMap, &upgradeHandle{Version: "v0.2.1", Func: up.v018ToV021}) // v0.2.1 ~ v0.2.7 @@ -98,9 +100,25 @@ func (up *Upgrade) v0ToV012() (err error) { return } -// upgrade v0.1.2 ~ v0.1.8 -func (up *Upgrade) v012ToV018() error { - return nil +// upgrade v0.1.2 ~ v0.1.3 +func (up *Upgrade) v012ToV013() error { + + // create attachment table + sql := "DROP TABLE IF EXISTS `mw_attachment`;" + + "CREATE TABLE `mw_attachment` (" + + "`attachment_id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '附件 id'," + + "`user_id` int(10) NOT NULL DEFAULT '0' COMMENT '创建用户id'," + + "`document_id` int(10) NOT NULL DEFAULT '0' COMMENT '所属文档id'," + + "`name` varchar(50) NOT NULL DEFAULT '' COMMENT '附件名称'," + + "`path` varchar(100) NOT NULL DEFAULT '' COMMENT '附件路径'," + + "`source` tinyint(1) NOT NULL DEFAULT '0' COMMENT '附件来源, 0 默认是附件 1 图片'," + + "`create_time` int(11) NOT NULL DEFAULT '0' COMMENT '创建时间'," + + "`update_time` int(11) NOT NULL DEFAULT '0' COMMENT '更新时间'," + + "PRIMARY KEY (`attachment_id`)," + + "KEY (`document_id`, `source`)" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='附件信息表';" + + return up.createTable(sql) } // upgrade v0.1.8 ~ v0.2.1 @@ -118,6 +136,24 @@ func (up *Upgrade) v027ToV033() error { return nil } +func (up *Upgrade) createTable(sqlTable string) error { + host := beego.AppConfig.String("db::host") + port, _ := beego.AppConfig.Int("db::port") + user := beego.AppConfig.String("db::user") + pass := beego.AppConfig.String("db::pass") + name := beego.AppConfig.String("db::name") + db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8&multiStatements=true", user, pass, host, port, name)) + if err != nil { + return err + } + defer db.Close() + _, err = db.Exec(sqlTable) + if err != nil { + return err + } + return nil +} + func (up *Upgrade) upgradeAfter(version string) (err error) { // update system version config, err := ConfigModel.GetConfigByKey(Config_Key_SystemVersion)