editors.default.js 81 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644
  1. (function(editors, elFinder) {
  2. if (typeof define === 'function' && define.amd) {
  3. define(['elfinder'], editors);
  4. } else if (elFinder) {
  5. var optEditors = elFinder.prototype._options.commandsOptions.edit.editors;
  6. elFinder.prototype._options.commandsOptions.edit.editors = optEditors.concat(editors(elFinder));
  7. }
  8. }(function(elFinder) {
  9. "use strict";
  10. var apps = {},
  11. // get query of getfile
  12. getfile = window.location.search.match(/getfile=([a-z]+)/),
  13. useRequire = elFinder.prototype.hasRequire,
  14. ext2mime = {
  15. bmp: 'image/x-ms-bmp',
  16. dng: 'image/x-adobe-dng',
  17. gif: 'image/gif',
  18. jpeg: 'image/jpeg',
  19. jpg: 'image/jpeg',
  20. pdf: 'application/pdf',
  21. png: 'image/png',
  22. ppm: 'image/x-portable-pixmap',
  23. psd: 'image/vnd.adobe.photoshop',
  24. pxd: 'image/x-pixlr-data',
  25. svg: 'image/svg+xml',
  26. tiff: 'image/tiff',
  27. webp: 'image/webp',
  28. xcf: 'image/x-xcf',
  29. sketch: 'application/x-sketch',
  30. ico: 'image/x-icon',
  31. dds: 'image/vnd-ms.dds',
  32. emf: 'application/x-msmetafile'
  33. },
  34. mime2ext,
  35. getExtention = function(mime, fm, jpeg) {
  36. if (!mime2ext) {
  37. mime2ext = fm.arrayFlip(ext2mime);
  38. }
  39. var ext = mime2ext[mime] || fm.mimeTypes[mime];
  40. if (!jpeg) {
  41. if (ext === 'jpeg') {
  42. ext = 'jpg';
  43. }
  44. } else {
  45. if (ext === 'jpg') {
  46. ext = 'jpeg';
  47. }
  48. }
  49. return ext;
  50. },
  51. changeImageType = function(src, toMime) {
  52. var dfd = $.Deferred();
  53. try {
  54. var canvas = document.createElement('canvas'),
  55. ctx = canvas.getContext('2d'),
  56. img = new Image(),
  57. conv = function() {
  58. var url = canvas.toDataURL(toMime),
  59. mime, m;
  60. if (m = url.match(/^data:([a-z0-9]+\/[a-z0-9.+-]+)/i)) {
  61. mime = m[1];
  62. } else {
  63. mime = '';
  64. }
  65. if (mime.toLowerCase() === toMime.toLowerCase()) {
  66. dfd.resolve(canvas.toDataURL(toMime), canvas);
  67. } else {
  68. dfd.reject();
  69. }
  70. };
  71. img.src = src;
  72. $(img).on('load', function() {
  73. try {
  74. canvas.width = img.width;
  75. canvas.height = img.height;
  76. ctx.drawImage(img, 0, 0);
  77. conv();
  78. } catch(e) {
  79. dfd.reject();
  80. }
  81. }).on('error', function () {
  82. dfd.reject();
  83. });
  84. return dfd;
  85. } catch(e) {
  86. return dfd.reject();
  87. }
  88. },
  89. initImgTag = function(id, file, content, fm) {
  90. var node = $(this).children('img:first').data('ext', getExtention(file.mime, fm)),
  91. spnr = $('<div class="elfinder-edit-spinner elfinder-edit-image"></div>')
  92. .html('<span class="elfinder-spinner-text">' + fm.i18n('ntfloadimg') + '</span><span class="elfinder-spinner"></span>')
  93. .hide()
  94. .appendTo(this),
  95. setup = function() {
  96. node.attr('id', id+'-img')
  97. .attr('src', url || content)
  98. .css({'height':'', 'max-width':'100%', 'max-height':'100%', 'cursor':'pointer'})
  99. .data('loading', function(done) {
  100. var btns = node.closest('.elfinder-dialog').find('button,.elfinder-titlebar-button');
  101. btns.prop('disabled', !done)[done? 'removeClass' : 'addClass']('ui-state-disabled');
  102. node.css('opacity', done? '' : '0.3');
  103. spnr[done? 'hide' : 'show']();
  104. return node;
  105. });
  106. },
  107. url;
  108. if (!content.match(/^data:/)) {
  109. fm.openUrl(file.hash, false, function(v) {
  110. url = v;
  111. node.attr('_src', content);
  112. setup();
  113. });
  114. } else {
  115. setup();
  116. }
  117. },
  118. imgBase64 = function(node, mime) {
  119. var style = node.attr('style'),
  120. img, canvas, ctx, data;
  121. try {
  122. // reset css for getting image size
  123. node.attr('style', '');
  124. // img node
  125. img = node.get(0);
  126. // New Canvas
  127. canvas = document.createElement('canvas');
  128. canvas.width = img.width;
  129. canvas.height = img.height;
  130. // restore css
  131. node.attr('style', style);
  132. // Draw Image
  133. canvas.getContext('2d').drawImage(img, 0, 0);
  134. // To Base64
  135. data = canvas.toDataURL(mime);
  136. } catch(e) {
  137. data = node.attr('src');
  138. }
  139. return data;
  140. },
  141. iframeClose = function(ifm) {
  142. var $ifm = $(ifm),
  143. dfd = $.Deferred().always(function() {
  144. $ifm.off('load', load);
  145. }),
  146. ab = 'about:blank',
  147. chk = function() {
  148. tm = setTimeout(function() {
  149. var src;
  150. try {
  151. src = base.contentWindow.location.href;
  152. } catch(e) {
  153. src = null;
  154. }
  155. if (src === ab) {
  156. dfd.resolve();
  157. } else if (--cnt > 0){
  158. chk();
  159. } else {
  160. dfd.reject();
  161. }
  162. }, 500);
  163. },
  164. load = function() {
  165. tm && clearTimeout(tm);
  166. dfd.resolve();
  167. },
  168. cnt = 20, // 500ms * 20 = 10sec wait
  169. tm;
  170. $ifm.one('load', load);
  171. ifm.src = ab;
  172. chk();
  173. return dfd;
  174. };
  175. // check getfile callback function
  176. if (getfile) {
  177. getfile = getfile[1];
  178. if (getfile === 'ckeditor') {
  179. elFinder.prototype._options.getFileCallback = function(file, fm) {
  180. window.opener.CKEDITOR.tools.callFunction((function() {
  181. var reParam = new RegExp('(?:[\?&]|&amp;)CKEditorFuncNum=([^&]+)', 'i'),
  182. match = window.location.search.match(reParam);
  183. return (match && match.length > 1) ? match[1] : '';
  184. })(), fm.convAbsUrl(file.url));
  185. fm.destroy();
  186. window.close();
  187. };
  188. }
  189. }
  190. // return editors Array
  191. return [
  192. {
  193. // tui.image-editor - https://github.com/nhnent/tui.image-editor
  194. info : {
  195. id: 'tuiimgedit',
  196. name: 'TUI Image Editor',
  197. iconImg: 'img/editor-icons.png 0 -48',
  198. dataScheme: true,
  199. schemeContent: true,
  200. openMaximized: true,
  201. canMakeEmpty: false,
  202. integrate: {
  203. title: 'TOAST UI Image Editor',
  204. link: 'http://ui.toast.com/tui-image-editor/'
  205. }
  206. },
  207. // MIME types to accept
  208. mimes : ['image/jpeg', 'image/png', 'image/gif', 'image/svg+xml', 'image/x-ms-bmp'],
  209. // HTML of this editor
  210. html : '<div class="elfinder-edit-imageeditor"><canvas></canvas></div>',
  211. // called on initialization of elFinder cmd edit (this: this editor's config object)
  212. setup : function(opts, fm) {
  213. if (fm.UA.ltIE8 || fm.UA.Mobile) {
  214. this.disabled = true;
  215. } else {
  216. this.opts = Object.assign({
  217. version: 'v3.9.0'
  218. }, opts.extraOptions.tuiImgEditOpts || {}, {
  219. iconsPath : fm.baseUrl + 'img/tui-',
  220. theme : {}
  221. });
  222. if (!fm.isSameOrigin(this.opts.iconsPath)) {
  223. this.disabled = true;
  224. fm.debug('warning', 'Setting `commandOptions.edit.extraOptions.tuiImgEditOpts.iconsPath` MUST follow the same origin policy.');
  225. }
  226. }
  227. },
  228. // Initialization of editing node (this: this editors HTML node)
  229. init : function(id, file, content, fm) {
  230. this.data('url', content);
  231. },
  232. load : function(base) {
  233. var self = this,
  234. fm = this.fm,
  235. dfrd = $.Deferred(),
  236. cdns = fm.options.cdns,
  237. ver = self.confObj.opts.version,
  238. init = function(editor) {
  239. var $base = $(base),
  240. bParent = $base.parent(),
  241. opts = self.confObj.opts,
  242. iconsPath = opts.iconsPath,
  243. tmpContainer = $('<div class="tui-image-editor-container">').appendTo(bParent),
  244. tmpDiv = [
  245. $('<div class="tui-image-editor-submenu"></div>').appendTo(tmpContainer),
  246. $('<div class="tui-image-editor-controls"></div>').appendTo(tmpContainer)
  247. ],
  248. iEditor = new editor(base, {
  249. includeUI: {
  250. loadImage: {
  251. path: $base.data('url'),
  252. name: self.file.name
  253. },
  254. theme: Object.assign(opts.theme, {
  255. 'menu.normalIcon.path': iconsPath + 'icon-d.svg',
  256. 'menu.normalIcon.name': 'icon-d',
  257. 'menu.activeIcon.path': iconsPath + 'icon-b.svg',
  258. 'menu.activeIcon.name': 'icon-b',
  259. 'menu.disabledIcon.path': iconsPath + 'icon-a.svg',
  260. 'menu.disabledIcon.name': 'icon-a',
  261. 'menu.hoverIcon.path': iconsPath + 'icon-c.svg',
  262. 'menu.hoverIcon.name': 'icon-c',
  263. 'submenu.normalIcon.path': iconsPath + 'icon-d.svg',
  264. 'submenu.normalIcon.name': 'icon-d',
  265. 'submenu.activeIcon.path': iconsPath + 'icon-c.svg',
  266. 'submenu.activeIcon.name': 'icon-c'
  267. }),
  268. initMenu: 'filter',
  269. menuBarPosition: 'bottom'
  270. },
  271. cssMaxWidth: Math.max(300, bParent.width()),
  272. cssMaxHeight: Math.max(200, bParent.height() - (tmpDiv[0].height() + tmpDiv[1].height() + 3 /*margin*/)),
  273. usageStatistics: false
  274. }),
  275. canvas = $base.find('canvas:first').get(0),
  276. zoom = function(v) {
  277. if (typeof v !== 'undefined') {
  278. var c = $(canvas),
  279. w = parseInt(c.attr('width')),
  280. h = parseInt(c.attr('height')),
  281. a = w / h,
  282. mw, mh;
  283. if (v === 0) {
  284. mw = w;
  285. mh = h;
  286. } else {
  287. mw = parseInt(c.css('max-width')) + Number(v);
  288. mh = mw / a;
  289. if (mw > w && mh > h) {
  290. mw = w;
  291. mh = h;
  292. }
  293. }
  294. per.text(Math.round(mw / w * 100) + '%');
  295. iEditor.resizeCanvasDimension({width: mw, height: mh});
  296. // continually change more
  297. if (zoomMore) {
  298. setTimeout(function() {
  299. zoomMore && zoom(v);
  300. }, 50);
  301. }
  302. }
  303. },
  304. zup = $('<span class="ui-icon ui-icon-plusthick"></span>').data('val', 10),
  305. zdown = $('<span class="ui-icon ui-icon-minusthick"></span>').data('val', -10),
  306. per = $('<button></button>').css('width', '4em').text('%').attr('title', '100%').data('val', 0),
  307. quty, qutyTm, zoomTm, zoomMore;
  308. tmpContainer.remove();
  309. $base.removeData('url').data('mime', self.file.mime);
  310. // jpeg quality controls
  311. if (self.file.mime === 'image/jpeg') {
  312. $base.data('quality', fm.storage('jpgQuality') || fm.option('jpgQuality'));
  313. quty = $('<input type="number" class="ui-corner-all elfinder-resize-quality elfinder-tabstop"/>')
  314. .attr('min', '1')
  315. .attr('max', '100')
  316. .attr('title', '1 - 100')
  317. .on('change', function() {
  318. var q = quty.val();
  319. $base.data('quality', q);
  320. qutyTm && cancelAnimationFrame(qutyTm);
  321. qutyTm = requestAnimationFrame(function() {
  322. canvas.toBlob(function(blob) {
  323. blob && quty.next('span').text(' (' + fm.formatSize(blob.size) + ')');
  324. }, 'image/jpeg', Math.max(Math.min(q, 100), 1) / 100);
  325. });
  326. })
  327. .val($base.data('quality'));
  328. $('<div class="ui-dialog-buttonset elfinder-edit-extras elfinder-edit-extras-quality"></div>')
  329. .append(
  330. $('<span>').html(fm.i18n('quality') + ' : '), quty, $('<span></span>')
  331. )
  332. .prependTo($base.parent().next());
  333. } else if (self.file.mime === 'image/svg+xml') {
  334. $base.closest('.ui-dialog').trigger('changeType', {
  335. extention: 'png',
  336. mime : 'image/png',
  337. keepEditor: true
  338. });
  339. }
  340. // zoom scale controls
  341. $('<div class="ui-dialog-buttonset elfinder-edit-extras"></div>')
  342. .append(
  343. zdown, per, zup
  344. )
  345. .attr('title', fm.i18n('scale'))
  346. .on('click', 'span,button', function() {
  347. zoom($(this).data('val'));
  348. })
  349. .on('mousedown mouseup mouseleave', 'span', function(e) {
  350. zoomMore = false;
  351. zoomTm && clearTimeout(zoomTm);
  352. if (e.type === 'mousedown') {
  353. zoomTm = setTimeout(function() {
  354. zoomMore = true;
  355. zoom($(e.target).data('val'));
  356. }, 500);
  357. }
  358. })
  359. .prependTo($base.parent().next());
  360. // wait canvas ready
  361. setTimeout(function() {
  362. dfrd.resolve(iEditor);
  363. if (quty) {
  364. quty.trigger('change');
  365. iEditor.on('redoStackChanged undoStackChanged', function() {
  366. quty.trigger('change');
  367. });
  368. }
  369. // show initial scale
  370. zoom(null);
  371. }, 100);
  372. // show color slider (maybe TUI-Image-Editor's bug)
  373. // see https://github.com/nhn/tui.image-editor/issues/153
  374. $base.find('.tui-colorpicker-palette-container').on('click', '.tui-colorpicker-palette-preview', function() {
  375. $(this).closest('.color-picker-control').height('auto').find('.tui-colorpicker-slider-container').toggle();
  376. });
  377. $base.on('click', function() {
  378. $base.find('.tui-colorpicker-slider-container').hide();
  379. });
  380. },
  381. loader;
  382. if (!self.confObj.editor) {
  383. loader = $.Deferred();
  384. fm.loadCss([
  385. cdns.tui + '/tui-color-picker/latest/tui-color-picker.css',
  386. cdns.tui + '/tui-image-editor/'+ver+'/tui-image-editor.css'
  387. ]);
  388. if (fm.hasRequire) {
  389. require.config({
  390. paths : {
  391. 'fabric/dist/fabric.require' : cdns.fabric + '/fabric.require.min', // for fabric < 2.0.1
  392. 'fabric' : cdns.fabric + '/fabric.min', // for fabric >= 2.0.1
  393. 'tui-code-snippet' : cdns.tui + '/tui.code-snippet/latest/tui-code-snippet.min',
  394. 'tui-color-picker' : cdns.tui + '/tui-color-picker/latest/tui-color-picker.min',
  395. 'tui-image-editor' : cdns.tui + '/tui-image-editor/'+ver+'/tui-image-editor.min'
  396. }
  397. });
  398. require(['tui-image-editor'], function(ImageEditor) {
  399. loader.resolve(ImageEditor);
  400. });
  401. } else {
  402. fm.loadScript([
  403. cdns.fabric + '/fabric.min.js',
  404. cdns.tui + '/tui.code-snippet/latest/tui-code-snippet.min.js'
  405. ], function() {
  406. fm.loadScript([
  407. cdns.tui + '/tui-color-picker/latest/tui-color-picker.min.js'
  408. ], function() {
  409. fm.loadScript([
  410. cdns.tui + '/tui-image-editor/'+ver+'/tui-image-editor.min.js'
  411. ], function() {
  412. loader.resolve(window.tui.ImageEditor);
  413. }, {
  414. loadType: 'tag'
  415. });
  416. }, {
  417. loadType: 'tag'
  418. });
  419. }, {
  420. loadType: 'tag'
  421. });
  422. }
  423. loader.done(function(editor) {
  424. self.confObj.editor = editor;
  425. init(editor);
  426. });
  427. } else {
  428. init(self.confObj.editor);
  429. }
  430. return dfrd;
  431. },
  432. getContent : function(base) {
  433. var editor = this.editor,
  434. fm = editor.fm,
  435. $base = $(base),
  436. quality = $base.data('quality');
  437. if (editor.instance) {
  438. if ($base.data('mime') === 'image/jpeg') {
  439. quality = quality || fm.storage('jpgQuality') || fm.option('jpgQuality');
  440. quality = Math.max(0.1, Math.min(1, quality / 100));
  441. }
  442. return editor.instance.toDataURL({
  443. format: getExtention($base.data('mime'), fm, true),
  444. quality: quality
  445. });
  446. }
  447. },
  448. save : function(base) {
  449. var $base = $(base),
  450. quality = $base.data('quality'),
  451. hash = $base.data('hash'),
  452. file;
  453. this.instance.deactivateAll();
  454. if (typeof quality !== 'undefined') {
  455. this.fm.storage('jpgQuality', quality);
  456. }
  457. if (hash) {
  458. file = this.fm.file(hash);
  459. $base.data('mime', file.mime);
  460. }
  461. }
  462. },
  463. {
  464. // Photopea advanced image editor
  465. info : {
  466. id : 'photopea',
  467. name : 'Photopea',
  468. iconImg : 'img/editor-icons.png 0 -160',
  469. single: true,
  470. noContent: true,
  471. arrayBufferContent: true,
  472. openMaximized: true,
  473. // Disable file types that cannot be saved on Photopea.
  474. canMakeEmpty: ['image/jpeg', 'image/png', 'image/gif', 'image/svg+xml', 'image/x-ms-bmp', 'image/tiff', /*'image/x-adobe-dng',*/ 'image/webp', /*'image/x-xcf',*/ 'image/vnd.adobe.photoshop', 'application/pdf', 'image/x-portable-pixmap', 'image/x-sketch', 'image/x-icon', 'image/vnd-ms.dds', /*'application/x-msmetafile'*/],
  475. integrate: {
  476. title: 'Photopea',
  477. link: 'https://www.photopea.com/learn/'
  478. }
  479. },
  480. mimes : ['image/jpeg', 'image/png', 'image/gif', 'image/svg+xml', 'image/x-ms-bmp', 'image/tiff', 'image/x-adobe-dng', 'image/webp', 'image/x-xcf', 'image/vnd.adobe.photoshop', 'application/pdf', 'image/x-portable-pixmap', 'image/x-sketch', 'image/x-icon', 'image/vnd-ms.dds', 'application/x-msmetafile'],
  481. html : '<iframe style="width:100%;height:100%;border:none;"></iframe>',
  482. // setup on elFinder bootup
  483. setup : function(opts, fm) {
  484. if (fm.UA.IE || fm.UA.Mobile) {
  485. this.disabled = true;
  486. }
  487. },
  488. // Initialization of editing node (this: this editors HTML node)
  489. init : function(id, file, dum, fm) {
  490. var orig = 'https://www.photopea.com',
  491. ifm = $(this).hide()
  492. //.css('box-sizing', 'border-box')
  493. .on('load', function() {
  494. //spnr.remove();
  495. ifm.show();
  496. })
  497. .on('error', function() {
  498. spnr.remove();
  499. ifm.show();
  500. }),
  501. editor = this.editor,
  502. confObj = editor.confObj,
  503. spnr = $('<div class="elfinder-edit-spinner elfinder-edit-photopea"></div>')
  504. .html('<span class="elfinder-spinner-text">' + fm.i18n('nowLoading') + '</span><span class="elfinder-spinner"></span>')
  505. .appendTo(ifm.parent()),
  506. saveMimes = fm.arrayFlip(confObj.info.canMakeEmpty),
  507. getType = function(mime) {
  508. var ext = getExtention(mime, fm),
  509. extmime = ext2mime[ext];
  510. if (!confObj.mimesFlip[extmime]) {
  511. ext = '';
  512. } else if (ext === 'jpeg') {
  513. ext = 'jpg';
  514. }
  515. if (!ext || !saveMimes[extmime]) {
  516. ext = 'psd';
  517. extmime = ext2mime[ext];
  518. ifm.closest('.ui-dialog').trigger('changeType', {
  519. extention: ext,
  520. mime : extmime,
  521. keepEditor: true
  522. });
  523. }
  524. return ext;
  525. },
  526. mime = file.mime,
  527. liveMsg, type, quty;
  528. if (!confObj.mimesFlip) {
  529. confObj.mimesFlip = fm.arrayFlip(confObj.mimes, true);
  530. }
  531. if (!confObj.liveMsg) {
  532. confObj.liveMsg = function(ifm, spnr, file) {
  533. var wnd = ifm.get(0).contentWindow,
  534. phase = 0,
  535. data = null,
  536. dfdIni = $.Deferred().done(function() {
  537. spnr.remove();
  538. phase = 1;
  539. wnd.postMessage(data, orig);
  540. }),
  541. dfdGet;
  542. this.load = function() {
  543. return fm.getContents(file.hash, 'arraybuffer').done(function(d) {
  544. data = d;
  545. });
  546. };
  547. this.receive = function(e) {
  548. var ev = e.originalEvent,
  549. state;
  550. if (ev.origin === orig && ev.source === wnd) {
  551. if (ev.data === 'done') {
  552. if (phase === 0) {
  553. dfdIni.resolve();
  554. } else if (phase === 1) {
  555. phase = 2;
  556. ifm.trigger('contentsloaded');
  557. } else {
  558. if (dfdGet && dfdGet.state() === 'pending') {
  559. dfdGet.reject('errDataEmpty');
  560. }
  561. }
  562. } else if (ev.data === 'Save') {
  563. editor.doSave();
  564. } else {
  565. if (dfdGet && dfdGet.state() === 'pending') {
  566. if (typeof ev.data === 'object') {
  567. dfdGet.resolve('data:' + mime + ';base64,' + fm.arrayBufferToBase64(ev.data));
  568. } else {
  569. dfdGet.reject('errDataEmpty');
  570. }
  571. }
  572. }
  573. }
  574. };
  575. this.getContent = function() {
  576. var type, q;
  577. if (phase > 1) {
  578. dfdGet && dfdGet.state() === 'pending' && dfdGet.reject();
  579. dfdGet = null;
  580. dfdGet = $.Deferred();
  581. if (phase === 2) {
  582. phase = 3;
  583. dfdGet.resolve('data:' + mime + ';base64,' + fm.arrayBufferToBase64(data));
  584. data = null;
  585. return dfdGet;
  586. }
  587. if (ifm.data('mime')) {
  588. mime = ifm.data('mime');
  589. type = getType(mime);
  590. }
  591. if (q = ifm.data('quality')) {
  592. type += ':' + (q / 100);
  593. }
  594. wnd.postMessage('app.activeDocument.saveToOE("' + type + '")', orig);
  595. return dfdGet;
  596. }
  597. };
  598. };
  599. }
  600. ifm.parent().css('padding', 0);
  601. type = getType(file.mime);
  602. liveMsg = editor.liveMsg = new confObj.liveMsg(ifm, spnr, file);
  603. $(window).on('message.' + fm.namespace, liveMsg.receive);
  604. liveMsg.load().done(function() {
  605. var d = JSON.stringify({
  606. files : [],
  607. environment : {
  608. lang: fm.lang.replace(/_/g, '-'),
  609. customIO: {"save": "app.echoToOE(\"Save\");"}
  610. }
  611. });
  612. ifm.attr('src', orig + '/#' + encodeURI(d));
  613. }).fail(function(err) {
  614. err && fm.error(err);
  615. editor.initFail = true;
  616. });
  617. // jpeg quality controls
  618. if (file.mime === 'image/jpeg' || file.mime === 'image/webp') {
  619. ifm.data('quality', fm.storage('jpgQuality') || fm.option('jpgQuality'));
  620. quty = $('<input type="number" class="ui-corner-all elfinder-resize-quality elfinder-tabstop"/>')
  621. .attr('min', '1')
  622. .attr('max', '100')
  623. .attr('title', '1 - 100')
  624. .on('change', function() {
  625. var q = quty.val();
  626. ifm.data('quality', q);
  627. })
  628. .val(ifm.data('quality'));
  629. $('<div class="ui-dialog-buttonset elfinder-edit-extras elfinder-edit-extras-quality"></div>')
  630. .append(
  631. $('<span>').html(fm.i18n('quality') + ' : '), quty, $('<span></span>')
  632. )
  633. .prependTo(ifm.parent().next());
  634. }
  635. },
  636. load : function(base) {
  637. var dfd = $.Deferred(),
  638. self = this,
  639. fm = this.fm,
  640. $base = $(base);
  641. if (self.initFail) {
  642. dfd.reject();
  643. } else {
  644. $base.on('contentsloaded', function() {
  645. dfd.resolve(self.liveMsg);
  646. });
  647. }
  648. return dfd;
  649. },
  650. getContent : function() {
  651. return this.editor.liveMsg? this.editor.liveMsg.getContent() : void(0);
  652. },
  653. save : function(base, liveMsg) {
  654. var $base = $(base),
  655. quality = $base.data('quality'),
  656. hash = $base.data('hash'),
  657. file;
  658. if (typeof quality !== 'undefined') {
  659. this.fm.storage('jpgQuality', quality);
  660. }
  661. if (hash) {
  662. file = this.fm.file(hash);
  663. $base.data('mime', file.mime);
  664. } else {
  665. $base.removeData('mime');
  666. }
  667. },
  668. // On dialog closed
  669. close : function(base, liveMsg) {
  670. $(base).attr('src', '');
  671. liveMsg && $(window).off('message.' + this.fm.namespace, liveMsg.receive);
  672. }
  673. },
  674. {
  675. // Pixo is cross-platform image editor
  676. info : {
  677. id : 'pixo',
  678. name : 'Pixo Editor',
  679. iconImg : 'img/editor-icons.png 0 -208',
  680. dataScheme: true,
  681. schemeContent: true,
  682. single: true,
  683. canMakeEmpty: false,
  684. integrate: {
  685. title: 'Pixo Editor',
  686. link: 'https://pixoeditor.com/privacy-policy/'
  687. }
  688. },
  689. // MIME types to accept
  690. mimes : ['image/jpeg', 'image/png', 'image/gif', 'image/svg+xml', 'image/x-ms-bmp'],
  691. // HTML of this editor
  692. html : '<div class="elfinder-edit-imageeditor"><img/></div>',
  693. // called on initialization of elFinder cmd edit (this: this editor's config object)
  694. setup : function(opts, fm) {
  695. if (fm.UA.ltIE8 || !opts.extraOptions || !opts.extraOptions.pixo || !opts.extraOptions.pixo.apikey) {
  696. this.disabled = true;
  697. } else {
  698. this.editorOpts = opts.extraOptions.pixo;
  699. }
  700. },
  701. // Initialization of editing node (this: this editors HTML node)
  702. init : function(id, file, content, fm) {
  703. initImgTag.call(this, id, file, content, fm);
  704. },
  705. // Get data uri scheme (this: this editors HTML node)
  706. getContent : function() {
  707. return $(this).children('img:first').attr('src');
  708. },
  709. // Launch Pixo editor when dialog open
  710. load : function(base) {
  711. var self = this,
  712. fm = this.fm,
  713. $base = $(base),
  714. node = $base.children('img:first'),
  715. dialog = $base.closest('.ui-dialog'),
  716. elfNode = fm.getUI(),
  717. dfrd = $.Deferred(),
  718. container = $('#elfinder-pixo-container'),
  719. init = function(onload) {
  720. var opts;
  721. if (!container.length) {
  722. container = $('<div id="elfinder-pixo-container" class="ui-front"></div>').css({
  723. position: 'fixed',
  724. top: 0,
  725. right: 0,
  726. width: '100%',
  727. height: $(window).height(),
  728. overflow: 'hidden'
  729. }).hide().appendTo(elfNode.hasClass('elfinder-fullscreen')? elfNode : 'body');
  730. // bind switch fullscreen event
  731. elfNode.on('resize.'+fm.namespace, function(e, data) {
  732. e.preventDefault();
  733. e.stopPropagation();
  734. data && data.fullscreen && container.appendTo(data.fullscreen === 'on'? elfNode : 'body');
  735. });
  736. fm.bind('destroy', function() {
  737. editor && editor.cancelEditing();
  738. container.remove();
  739. });
  740. } else {
  741. // always moves to last
  742. container.appendTo(container.parent());
  743. }
  744. node.on('click', launch);
  745. // Constructor options
  746. opts = Object.assign({
  747. type: 'child',
  748. parent: container.get(0),
  749. output: {format: 'png'},
  750. onSave: function(arg) {
  751. // Check current file.hash, all callbacks are called on multiple instances
  752. var mime = arg.toBlob().type,
  753. ext = getExtention(mime, fm),
  754. draw = function(url) {
  755. node.one('load error', function() {
  756. node.data('loading') && node.data('loading')(true);
  757. })
  758. .attr('crossorigin', 'anonymous')
  759. .attr('src', url);
  760. },
  761. url = arg.toDataURL();
  762. node.data('loading')();
  763. delete base._canvas;
  764. if (node.data('ext') !== ext) {
  765. changeImageType(url, self.file.mime).done(function(res, cv) {
  766. if (cv) {
  767. base._canvas = canvas = cv;
  768. quty.trigger('change');
  769. qBase && qBase.show();
  770. }
  771. draw(res);
  772. }).fail(function() {
  773. dialog.trigger('changeType', {
  774. extention: ext,
  775. mime : mime
  776. });
  777. draw(url);
  778. });
  779. } else {
  780. draw(url);
  781. }
  782. },
  783. onClose: function() {
  784. dialog.removeClass(fm.res('class', 'preventback'));
  785. fm.toggleMaximize(container, false);
  786. container.hide();
  787. fm.toFront(dialog);
  788. }
  789. }, self.confObj.editorOpts);
  790. // trigger event 'editEditorPrepare'
  791. self.trigger('Prepare', {
  792. node: base,
  793. editorObj: Pixo,
  794. instance: void(0),
  795. opts: opts
  796. });
  797. // make editor instance
  798. editor = new Pixo.Bridge(opts);
  799. dfrd.resolve(editor);
  800. $base.on('saveAsFail', launch);
  801. if (onload) {
  802. onload();
  803. }
  804. },
  805. launch = function() {
  806. dialog.addClass(fm.res('class', 'preventback'));
  807. fm.toggleMaximize(container, true);
  808. fm.toFront(container);
  809. container.show().data('curhash', self.file.hash);
  810. editor.edit(node.get(0));
  811. node.data('loading')(true);
  812. },
  813. qBase, quty, qutyTm, canvas, editor;
  814. node.data('loading')();
  815. // jpeg quality controls
  816. if (self.file.mime === 'image/jpeg') {
  817. quty = $('<input type="number" class="ui-corner-all elfinder-resize-quality elfinder-tabstop"/>')
  818. .attr('min', '1')
  819. .attr('max', '100')
  820. .attr('title', '1 - 100')
  821. .on('change', function() {
  822. var q = quty.val();
  823. qutyTm && cancelAnimationFrame(qutyTm);
  824. qutyTm = requestAnimationFrame(function() {
  825. if (canvas) {
  826. canvas.toBlob(function(blob) {
  827. blob && quty.next('span').text(' (' + fm.formatSize(blob.size) + ')');
  828. }, 'image/jpeg', Math.max(Math.min(q, 100), 1) / 100);
  829. }
  830. });
  831. })
  832. .val(fm.storage('jpgQuality') || fm.option('jpgQuality'));
  833. qBase = $('<div class="ui-dialog-buttonset elfinder-edit-extras elfinder-edit-extras-quality"></div>')
  834. .hide()
  835. .append(
  836. $('<span>').html(fm.i18n('quality') + ' : '), quty, $('<span></span>')
  837. )
  838. .prependTo($base.parent().next());
  839. $base.data('quty', quty);
  840. }
  841. // load script then init
  842. if (typeof Pixo === 'undefined') {
  843. fm.loadScript(['https://pixoeditor.com:8443/editor/scripts/bridge.m.js'], function() {
  844. init(launch);
  845. }, {loadType: 'tag'});
  846. } else {
  847. init();
  848. launch();
  849. }
  850. return dfrd;
  851. },
  852. // Convert content url to data uri scheme to save content
  853. save : function(base) {
  854. var self = this,
  855. $base = $(base),
  856. node = $base.children('img:first'),
  857. q;
  858. if (base._canvas) {
  859. if ($base.data('quty')) {
  860. q = $base.data('quty').val();
  861. q && this.fm.storage('jpgQuality', q);
  862. }
  863. node.attr('src', base._canvas.toDataURL(self.file.mime, q? Math.max(Math.min(q, 100), 1) / 100 : void(0)));
  864. } else if (node.attr('src').substr(0, 5) !== 'data:') {
  865. node.attr('src', imgBase64(node, this.file.mime));
  866. }
  867. },
  868. close : function(base, editor) {
  869. editor && editor.destroy();
  870. }
  871. },
  872. {
  873. // ACE Editor
  874. // called on initialization of elFinder cmd edit (this: this editor's config object)
  875. setup : function(opts, fm) {
  876. if (fm.UA.ltIE8 || !fm.options.cdns.ace) {
  877. this.disabled = true;
  878. }
  879. },
  880. // `mimes` is not set for support everything kind of text file
  881. info : {
  882. id : 'aceeditor',
  883. name : 'ACE Editor',
  884. iconImg : 'img/editor-icons.png 0 -96'
  885. },
  886. load : function(textarea) {
  887. var self = this,
  888. fm = this.fm,
  889. dfrd = $.Deferred(),
  890. cdn = fm.options.cdns.ace,
  891. start = function() {
  892. var editor, editorBase, mode,
  893. ta = $(textarea),
  894. taBase = ta.parent(),
  895. dialog = taBase.parent(),
  896. id = textarea.id + '_ace',
  897. ext = self.file.name.replace(/^.+\.([^.]+)|(.+)$/, '$1$2').toLowerCase(),
  898. // MIME/mode map
  899. mimeMode = {
  900. 'text/x-php' : 'php',
  901. 'application/x-php' : 'php',
  902. 'text/html' : 'html',
  903. 'application/xhtml+xml' : 'html',
  904. 'text/javascript' : 'javascript',
  905. 'application/javascript' : 'javascript',
  906. 'text/css' : 'css',
  907. 'text/x-c' : 'c_cpp',
  908. 'text/x-csrc' : 'c_cpp',
  909. 'text/x-chdr' : 'c_cpp',
  910. 'text/x-c++' : 'c_cpp',
  911. 'text/x-c++src' : 'c_cpp',
  912. 'text/x-c++hdr' : 'c_cpp',
  913. 'text/x-shellscript' : 'sh',
  914. 'application/x-csh' : 'sh',
  915. 'text/x-python' : 'python',
  916. 'text/x-java' : 'java',
  917. 'text/x-java-source' : 'java',
  918. 'text/x-ruby' : 'ruby',
  919. 'text/x-perl' : 'perl',
  920. 'application/x-perl' : 'perl',
  921. 'text/x-sql' : 'sql',
  922. 'text/xml' : 'xml',
  923. 'application/docbook+xml' : 'xml',
  924. 'application/xml' : 'xml'
  925. };
  926. // set base height
  927. taBase.height(taBase.height());
  928. // set basePath of ace
  929. ace.config.set('basePath', cdn);
  930. // Base node of Ace editor
  931. editorBase = $('<div id="'+id+'" style="width:100%; height:100%;"></div>').text(ta.val()).insertBefore(ta.hide());
  932. // Editor flag
  933. ta.data('ace', true);
  934. // Aceeditor instance
  935. editor = ace.edit(id);
  936. // Ace editor configure
  937. editor.$blockScrolling = Infinity;
  938. editor.setOptions({
  939. theme: 'ace/theme/monokai',
  940. fontSize: '14px',
  941. wrap: true,
  942. });
  943. ace.config.loadModule('ace/ext/modelist', function() {
  944. // detect mode
  945. mode = ace.require('ace/ext/modelist').getModeForPath('/' + self.file.name).name;
  946. if (mode === 'text') {
  947. if (mimeMode[self.file.mime]) {
  948. mode = mimeMode[self.file.mime];
  949. }
  950. }
  951. // show MIME:mode in title bar
  952. taBase.prev().children('.elfinder-dialog-title').append(' (' + self.file.mime + ' : ' + mode.split(/[\/\\]/).pop() + ')');
  953. editor.setOptions({
  954. mode: 'ace/mode/' + mode
  955. });
  956. if (dfrd.state() === 'resolved') {
  957. dialog.trigger('resize');
  958. }
  959. });
  960. ace.config.loadModule('ace/ext/language_tools', function() {
  961. ace.require('ace/ext/language_tools');
  962. editor.setOptions({
  963. enableBasicAutocompletion: true,
  964. enableSnippets: true,
  965. enableLiveAutocompletion: false
  966. });
  967. });
  968. ace.config.loadModule('ace/ext/settings_menu', function() {
  969. ace.require('ace/ext/settings_menu').init(editor);
  970. });
  971. // Short cuts
  972. editor.commands.addCommand({
  973. name : "saveFile",
  974. bindKey: {
  975. win : 'Ctrl-s',
  976. mac : 'Command-s'
  977. },
  978. exec: function(editor) {
  979. self.doSave();
  980. }
  981. });
  982. editor.commands.addCommand({
  983. name : "closeEditor",
  984. bindKey: {
  985. win : 'Ctrl-w|Ctrl-q',
  986. mac : 'Command-w|Command-q'
  987. },
  988. exec: function(editor) {
  989. self.doCancel();
  990. }
  991. });
  992. editor.resize();
  993. // TextArea button and Setting button
  994. $('<div class="ui-dialog-buttonset"></div>').css('float', 'left')
  995. .append(
  996. $('<button></button>').html(self.fm.i18n('TextArea'))
  997. .button()
  998. .on('click', function(){
  999. if (ta.data('ace')) {
  1000. ta.removeData('ace');
  1001. editorBase.hide();
  1002. ta.val(editor.session.getValue()).show().trigger('focus');
  1003. $(this).text('AceEditor');
  1004. } else {
  1005. ta.data('ace', true);
  1006. editorBase.show();
  1007. editor.setValue(ta.hide().val(), -1);
  1008. editor.focus();
  1009. $(this).html(self.fm.i18n('TextArea'));
  1010. }
  1011. })
  1012. )
  1013. .append(
  1014. $('<button>Ace editor setting</button>')
  1015. .button({
  1016. icons: {
  1017. primary: 'ui-icon-gear',
  1018. secondary: 'ui-icon-triangle-1-e'
  1019. },
  1020. text: false
  1021. })
  1022. .on('click', function(){
  1023. editor.showSettingsMenu();
  1024. $('#ace_settingsmenu')
  1025. .css('font-size', '80%')
  1026. .find('div[contains="setOptions"]').hide().end()
  1027. .parent().appendTo($('#elfinder'));
  1028. })
  1029. )
  1030. .prependTo(taBase.next());
  1031. // trigger event 'editEditorPrepare'
  1032. self.trigger('Prepare', {
  1033. node: textarea,
  1034. editorObj: ace,
  1035. instance: editor,
  1036. opts: {}
  1037. });
  1038. //dialog.trigger('resize');
  1039. dfrd.resolve(editor);
  1040. };
  1041. // check ace & start
  1042. if (!self.confObj.loader) {
  1043. self.confObj.loader = $.Deferred();
  1044. self.fm.loadScript([ cdn+'/ace.js' ], function() {
  1045. self.confObj.loader.resolve();
  1046. }, void 0, {obj: window, name: 'ace'});
  1047. }
  1048. self.confObj.loader.done(start);
  1049. return dfrd;
  1050. },
  1051. close : function(textarea, instance) {
  1052. instance && instance.destroy();
  1053. },
  1054. save : function(textarea, instance) {
  1055. instance && $(textarea).data('ace') && (textarea.value = instance.session.getValue());
  1056. },
  1057. focus : function(textarea, instance) {
  1058. instance && $(textarea).data('ace') && instance.focus();
  1059. },
  1060. resize : function(textarea, instance, e, data) {
  1061. instance && instance.resize();
  1062. }
  1063. },
  1064. {
  1065. // CodeMirror
  1066. // called on initialization of elFinder cmd edit (this: this editor's config object)
  1067. setup : function(opts, fm) {
  1068. if (fm.UA.ltIE10 || !fm.options.cdns.codemirror) {
  1069. this.disabled = true;
  1070. }
  1071. },
  1072. // `mimes` is not set for support everything kind of text file
  1073. info : {
  1074. id : 'codemirror',
  1075. name : 'CodeMirror',
  1076. iconImg : 'img/editor-icons.png 0 -176'
  1077. },
  1078. load : function(textarea) {
  1079. var fm = this.fm,
  1080. cmUrl = fm.convAbsUrl(fm.options.cdns.codemirror),
  1081. dfrd = $.Deferred(),
  1082. self = this,
  1083. start = function(CodeMirror) {
  1084. var ta = $(textarea),
  1085. base = ta.parent(),
  1086. editor, editorBase, opts;
  1087. // set base height
  1088. base.height(base.height());
  1089. // CodeMirror configure options
  1090. opts = {
  1091. lineNumbers: true,
  1092. lineWrapping: true,
  1093. extraKeys : {
  1094. 'Ctrl-S': function() { self.doSave(); },
  1095. 'Ctrl-Q': function() { self.doCancel(); },
  1096. 'Ctrl-W': function() { self.doCancel(); }
  1097. }
  1098. };
  1099. // trigger event 'editEditorPrepare'
  1100. self.trigger('Prepare', {
  1101. node: textarea,
  1102. editorObj: CodeMirror,
  1103. instance: void(0),
  1104. opts: opts
  1105. });
  1106. // CodeMirror configure
  1107. editor = CodeMirror.fromTextArea(textarea, opts);
  1108. // return editor instance
  1109. dfrd.resolve(editor);
  1110. // Auto mode set
  1111. var info, m, mode, spec;
  1112. if (! info) {
  1113. info = CodeMirror.findModeByMIME(self.file.mime);
  1114. }
  1115. if (! info && (m = self.file.name.match(/.+\.([^.]+)$/))) {
  1116. info = CodeMirror.findModeByExtension(m[1]);
  1117. }
  1118. if (info) {
  1119. CodeMirror.modeURL = useRequire? 'codemirror/mode/%N/%N.min' : cmUrl + '/mode/%N/%N.min.js';
  1120. mode = info.mode;
  1121. spec = info.mime;
  1122. editor.setOption('mode', spec);
  1123. CodeMirror.autoLoadMode(editor, mode);
  1124. // show MIME:mode in title bar
  1125. base.prev().children('.elfinder-dialog-title').append(' (' + spec + (mode != 'null'? ' : ' + mode : '') + ')');
  1126. }
  1127. // editor base node
  1128. editorBase = $(editor.getWrapperElement()).css({
  1129. // fix CSS conflict to SimpleMDE
  1130. padding: 0,
  1131. border: 'none'
  1132. });
  1133. ta.data('cm', true);
  1134. // fit height to base
  1135. editorBase.height('100%');
  1136. // TextArea button and Setting button
  1137. $('<div class="ui-dialog-buttonset"></div>').css('float', 'left')
  1138. .append(
  1139. $('<button></button>').html(self.fm.i18n('TextArea'))
  1140. .button()
  1141. .on('click', function(){
  1142. if (ta.data('cm')) {
  1143. ta.removeData('cm');
  1144. editorBase.hide();
  1145. ta.val(editor.getValue()).show().trigger('focus');
  1146. $(this).text('CodeMirror');
  1147. } else {
  1148. ta.data('cm', true);
  1149. editorBase.show();
  1150. editor.setValue(ta.hide().val());
  1151. editor.refresh();
  1152. editor.focus();
  1153. $(this).html(self.fm.i18n('TextArea'));
  1154. }
  1155. })
  1156. )
  1157. .prependTo(base.next());
  1158. };
  1159. // load script then start
  1160. if (!self.confObj.loader) {
  1161. self.confObj.loader = $.Deferred();
  1162. if (useRequire) {
  1163. require.config({
  1164. packages: [{
  1165. name: 'codemirror',
  1166. location: cmUrl,
  1167. main: 'codemirror.min'
  1168. }],
  1169. map: {
  1170. 'codemirror': {
  1171. 'codemirror/lib/codemirror': 'codemirror'
  1172. }
  1173. }
  1174. });
  1175. require([
  1176. 'codemirror',
  1177. 'codemirror/addon/mode/loadmode.min',
  1178. 'codemirror/mode/meta.min'
  1179. ], function(CodeMirror) {
  1180. self.confObj.loader.resolve(CodeMirror);
  1181. });
  1182. } else {
  1183. self.fm.loadScript([
  1184. cmUrl + '/codemirror.min.js'
  1185. ], function() {
  1186. self.fm.loadScript([
  1187. cmUrl + '/addon/mode/loadmode.min.js',
  1188. cmUrl + '/mode/meta.min.js'
  1189. ], function() {
  1190. self.confObj.loader.resolve(CodeMirror);
  1191. });
  1192. }, {loadType: 'tag'});
  1193. }
  1194. self.fm.loadCss(cmUrl + '/codemirror.css');
  1195. }
  1196. self.confObj.loader.done(start);
  1197. return dfrd;
  1198. },
  1199. close : function(textarea, instance) {
  1200. instance && instance.toTextArea();
  1201. },
  1202. save : function(textarea, instance) {
  1203. instance && $(textarea).data('cm') && (textarea.value = instance.getValue());
  1204. },
  1205. focus : function(textarea, instance) {
  1206. instance && $(textarea).data('cm') && instance.focus();
  1207. },
  1208. resize : function(textarea, instance, e, data) {
  1209. instance && instance.refresh();
  1210. }
  1211. },
  1212. {
  1213. // SimpleMDE
  1214. // called on initialization of elFinder cmd edit (this: this editor's config object)
  1215. setup : function(opts, fm) {
  1216. if (fm.UA.ltIE10 || !fm.options.cdns.simplemde) {
  1217. this.disabled = true;
  1218. }
  1219. },
  1220. info : {
  1221. id : 'simplemde',
  1222. name : 'SimpleMDE',
  1223. iconImg : 'img/editor-icons.png 0 -80'
  1224. },
  1225. exts : ['md'],
  1226. load : function(textarea) {
  1227. var self = this,
  1228. fm = this.fm,
  1229. base = $(textarea).parent(),
  1230. dfrd = $.Deferred(),
  1231. cdn = fm.options.cdns.simplemde,
  1232. start = function(SimpleMDE) {
  1233. var h = base.height(),
  1234. delta = base.outerHeight(true) - h + 14,
  1235. editor, editorBase, opts;
  1236. // fit height function
  1237. textarea._setHeight = function(height) {
  1238. var h = height || base.height(),
  1239. ctrH = 0,
  1240. areaH;
  1241. base.children('.editor-toolbar,.editor-statusbar').each(function() {
  1242. ctrH += $(this).outerHeight(true);
  1243. });
  1244. areaH = h - ctrH - delta;
  1245. editorBase.height(areaH);
  1246. editor.codemirror.refresh();
  1247. return areaH;
  1248. };
  1249. // set base height
  1250. base.height(h);
  1251. opts = {
  1252. element: textarea,
  1253. autofocus: true
  1254. };
  1255. // trigger event 'editEditorPrepare'
  1256. self.trigger('Prepare', {
  1257. node: textarea,
  1258. editorObj: SimpleMDE,
  1259. instance: void(0),
  1260. opts: opts
  1261. });
  1262. // make editor
  1263. editor = new SimpleMDE(opts);
  1264. dfrd.resolve(editor);
  1265. // editor base node
  1266. editorBase = $(editor.codemirror.getWrapperElement());
  1267. // fit height to base
  1268. editorBase.css('min-height', '50px')
  1269. .children('.CodeMirror-scroll').css('min-height', '50px');
  1270. textarea._setHeight(h);
  1271. };
  1272. // check SimpleMDE & start
  1273. if (!self.confObj.loader) {
  1274. self.confObj.loader = $.Deferred();
  1275. self.fm.loadCss(cdn+'/simplemde.min.css');
  1276. if (useRequire) {
  1277. require([
  1278. cdn+'/simplemde.min.js'
  1279. ], function(SimpleMDE) {
  1280. self.confObj.loader.resolve(SimpleMDE);
  1281. });
  1282. } else {
  1283. self.fm.loadScript([cdn+'/simplemde.min.js'], function() {
  1284. self.confObj.loader.resolve(SimpleMDE);
  1285. }, {loadType: 'tag'});
  1286. }
  1287. }
  1288. self.confObj.loader.done(start);
  1289. return dfrd;
  1290. },
  1291. close : function(textarea, instance) {
  1292. instance && instance.toTextArea();
  1293. instance = null;
  1294. },
  1295. save : function(textarea, instance) {
  1296. instance && (textarea.value = instance.value());
  1297. },
  1298. focus : function(textarea, instance) {
  1299. instance && instance.codemirror.focus();
  1300. },
  1301. resize : function(textarea, instance, e, data) {
  1302. instance && textarea._setHeight();
  1303. }
  1304. },
  1305. {
  1306. // CKEditor for html file
  1307. info : {
  1308. id : 'ckeditor',
  1309. name : 'CKEditor',
  1310. iconImg : 'img/editor-icons.png 0 0'
  1311. },
  1312. exts : ['htm', 'html', 'xhtml'],
  1313. setup : function(opts, fm) {
  1314. var confObj = this;
  1315. if (!fm.options.cdns.ckeditor) {
  1316. confObj.disabled = true;
  1317. } else {
  1318. confObj.ckeOpts = {};
  1319. if (opts.extraOptions) {
  1320. confObj.ckeOpts = Object.assign({}, opts.extraOptions.ckeditor || {});
  1321. if (opts.extraOptions.managerUrl) {
  1322. confObj.managerUrl = opts.extraOptions.managerUrl;
  1323. }
  1324. }
  1325. }
  1326. },
  1327. load : function(textarea) {
  1328. var self = this,
  1329. fm = this.fm,
  1330. dfrd = $.Deferred(),
  1331. init = function() {
  1332. var base = $(textarea).parent(),
  1333. dlg = base.closest('.elfinder-dialog'),
  1334. h = base.height(),
  1335. reg = /([&?]getfile=)[^&]+/,
  1336. loc = self.confObj.managerUrl || window.location.href.replace(/#.*$/, ''),
  1337. name = 'ckeditor',
  1338. opts;
  1339. // make manager location
  1340. if (reg.test(loc)) {
  1341. loc = loc.replace(reg, '$1' + name);
  1342. } else {
  1343. loc += '?getfile=' + name;
  1344. }
  1345. // set base height
  1346. base.height(h);
  1347. // CKEditor configure options
  1348. opts = {
  1349. startupFocus : true,
  1350. fullPage: true,
  1351. allowedContent: true,
  1352. filebrowserBrowseUrl : loc,
  1353. toolbarCanCollapse: true,
  1354. toolbarStartupExpanded: !fm.UA.Mobile,
  1355. removePlugins: 'resize',
  1356. extraPlugins: 'colorbutton,justify,docprops',
  1357. on: {
  1358. 'instanceReady' : function(e) {
  1359. var editor = e.editor;
  1360. editor.resize('100%', h);
  1361. // re-build on dom move
  1362. dlg.one('beforedommove.'+fm.namespace, function() {
  1363. editor.destroy();
  1364. }).one('dommove.'+fm.namespace, function() {
  1365. self.load(textarea).done(function(editor) {
  1366. self.instance = editor;
  1367. });
  1368. });
  1369. // return editor instance
  1370. dfrd.resolve(e.editor);
  1371. }
  1372. }
  1373. };
  1374. // trigger event 'editEditorPrepare'
  1375. self.trigger('Prepare', {
  1376. node: textarea,
  1377. editorObj: CKEDITOR,
  1378. instance: void(0),
  1379. opts: opts
  1380. });
  1381. // CKEditor configure
  1382. CKEDITOR.replace(textarea.id, Object.assign(opts, self.confObj.ckeOpts));
  1383. CKEDITOR.on('dialogDefinition', function(e) {
  1384. var dlg = e.data.definition.dialog;
  1385. dlg.on('show', function(e) {
  1386. fm.getUI().append($('.cke_dialog_background_cover')).append(this.getElement().$);
  1387. });
  1388. dlg.on('hide', function(e) {
  1389. $('body:first').append($('.cke_dialog_background_cover')).append(this.getElement().$);
  1390. });
  1391. });
  1392. };
  1393. if (!self.confObj.loader) {
  1394. self.confObj.loader = $.Deferred();
  1395. window.CKEDITOR_BASEPATH = fm.options.cdns.ckeditor + '/';
  1396. $.getScript(fm.options.cdns.ckeditor + '/ckeditor.js', function() {
  1397. self.confObj.loader.resolve();
  1398. });
  1399. }
  1400. self.confObj.loader.done(init);
  1401. return dfrd;
  1402. },
  1403. close : function(textarea, instance) {
  1404. instance && instance.destroy();
  1405. },
  1406. save : function(textarea, instance) {
  1407. instance && (textarea.value = instance.getData());
  1408. },
  1409. focus : function(textarea, instance) {
  1410. instance && instance.focus();
  1411. },
  1412. resize : function(textarea, instance, e, data) {
  1413. var self;
  1414. if (instance) {
  1415. if (instance.status === 'ready') {
  1416. instance.resize('100%', $(textarea).parent().height());
  1417. }
  1418. }
  1419. }
  1420. },
  1421. {
  1422. // CKEditor5 balloon mode for html file
  1423. info : {
  1424. id : 'ckeditor5',
  1425. name : 'CKEditor5',
  1426. iconImg : 'img/editor-icons.png 0 -16'
  1427. },
  1428. exts : ['htm', 'html', 'xhtml'],
  1429. html : '<div class="edit-editor-ckeditor5"></div>',
  1430. setup : function(opts, fm) {
  1431. var confObj = this;
  1432. // check cdn and ES6 support
  1433. if (!fm.options.cdns.ckeditor5 || typeof window.Symbol !== 'function' || typeof Symbol() !== 'symbol') {
  1434. confObj.disabled = true;
  1435. } else {
  1436. confObj.ckeOpts = {};
  1437. if (opts.extraOptions) {
  1438. // @deprecated option extraOptions.ckeditor5Mode
  1439. if (opts.extraOptions.ckeditor5Mode) {
  1440. confObj.ckeditor5Mode = opts.extraOptions.ckeditor5Mode;
  1441. }
  1442. confObj.ckeOpts = Object.assign({}, opts.extraOptions.ckeditor5 || {});
  1443. if (confObj.ckeOpts.mode) {
  1444. confObj.ckeditor5Mode = confObj.ckeOpts.mode;
  1445. delete confObj.ckeOpts.mode;
  1446. }
  1447. if (opts.extraOptions.managerUrl) {
  1448. confObj.managerUrl = opts.extraOptions.managerUrl;
  1449. }
  1450. }
  1451. }
  1452. fm.bind('destroy', function() {
  1453. confObj.editor = null;
  1454. });
  1455. },
  1456. // Prepare on before show dialog
  1457. prepare : function(base, dialogOpts, file) {
  1458. $(base).height(base.editor.fm.getUI().height() - 100);
  1459. },
  1460. init : function(id, file, data, fm) {
  1461. var m = data.match(/^([\s\S]*<body[^>]*>)([\s\S]+)(<\/body>[\s\S]*)$/i),
  1462. header = '',
  1463. body = '',
  1464. footer ='';
  1465. this.css({
  1466. width: '100%',
  1467. height: '100%',
  1468. 'box-sizing': 'border-box'
  1469. });
  1470. if (m) {
  1471. header = m[1];
  1472. body = m[2];
  1473. footer = m[3];
  1474. } else {
  1475. body = data;
  1476. }
  1477. this.data('data', {
  1478. header: header,
  1479. body: body,
  1480. footer: footer
  1481. });
  1482. this._setupSelEncoding(data);
  1483. },
  1484. load : function(editnode) {
  1485. var self = this,
  1486. fm = this.fm,
  1487. dfrd = $.Deferred(),
  1488. mode = self.confObj.ckeditor5Mode || 'decoupled-document',
  1489. lang = (function() {
  1490. var l = fm.lang.toLowerCase().replace('_', '-');
  1491. if (l.substr(0, 2) === 'zh' && l !== 'zh-cn') {
  1492. l = 'zh';
  1493. }
  1494. return l;
  1495. })(),
  1496. init = function(cEditor) {
  1497. var base = $(editnode).parent(),
  1498. opts;
  1499. // set base height
  1500. base.height(fm.getUI().height() - 100);
  1501. // CKEditor5 configure options
  1502. opts = Object.assign({
  1503. toolbar: ["heading", "|", "fontSize", "fontFamily", "|", "bold", "italic", "underline", "strikethrough", "highlight", "|", "alignment", "|", "numberedList", "bulletedList", "blockQuote", "indent", "outdent", "|", "ckfinder", "link", "imageUpload", "insertTable", "mediaEmbed", "|", "undo", "redo"],
  1504. language: lang
  1505. }, self.confObj.ckeOpts);
  1506. // trigger event 'editEditorPrepare'
  1507. self.trigger('Prepare', {
  1508. node: editnode,
  1509. editorObj: cEditor,
  1510. instance: void(0),
  1511. opts: opts
  1512. });
  1513. cEditor
  1514. .create(editnode, opts)
  1515. .then(function(editor) {
  1516. var ckf = editor.commands.get('ckfinder'),
  1517. fileRepo = editor.plugins.get('FileRepository'),
  1518. prevVars = {}, isImage, insertImages;
  1519. if (editor.ui.view.toolbar && (mode === 'classic' || mode === 'decoupled-document')) {
  1520. $(editnode).closest('.elfinder-dialog').children('.ui-widget-header').append($(editor.ui.view.toolbar.element).css({marginRight:'-1em',marginLeft:'-1em'}));
  1521. }
  1522. if (mode === 'classic') {
  1523. $(editnode).closest('.elfinder-edit-editor').css('overflow', 'auto');
  1524. }
  1525. // Set up this elFinder instead of CKFinder
  1526. if (ckf) {
  1527. isImage = function(f) {
  1528. return f && f.mime.match(/^image\//i);
  1529. };
  1530. insertImages = function(urls) {
  1531. var imgCmd = editor.commands.get('imageUpload');
  1532. if (!imgCmd.isEnabled) {
  1533. var ntf = editor.plugins.get('Notification'),
  1534. i18 = editor.locale.t;
  1535. ntf.showWarning(i18('Could not insert image at the current position.'), {
  1536. title: i18('Inserting image failed'),
  1537. namespace: 'ckfinder'
  1538. });
  1539. return;
  1540. }
  1541. editor.execute('imageInsert', { source: urls });
  1542. };
  1543. // Take over ckfinder execute()
  1544. ckf.execute = function() {
  1545. var dlg = base.closest('.elfinder-dialog'),
  1546. gf = fm.getCommand('getfile'),
  1547. rever = function() {
  1548. if (prevVars.hasVar) {
  1549. dlg.off('resize close', rever);
  1550. gf.callback = prevVars.callback;
  1551. gf.options.folders = prevVars.folders;
  1552. gf.options.multiple = prevVars.multi;
  1553. fm.commandMap.open = prevVars.open;
  1554. prevVars.hasVar = false;
  1555. }
  1556. };
  1557. dlg.trigger('togleminimize').one('resize close', rever);
  1558. prevVars.callback = gf.callback;
  1559. prevVars.folders = gf.options.folders;
  1560. prevVars.multi = gf.options.multiple;
  1561. prevVars.open = fm.commandMap.open;
  1562. prevVars.hasVar = true;
  1563. gf.callback = function(files) {
  1564. var imgs = [];
  1565. if (files.length === 1 && files[0].mime === 'directory') {
  1566. fm.one('open', function() {
  1567. fm.commandMap.open = 'getfile';
  1568. }).getCommand('open').exec(files[0].hash);
  1569. return;
  1570. }
  1571. fm.getUI('cwd').trigger('unselectall');
  1572. $.each(files, function(i, f) {
  1573. if (isImage(f)) {
  1574. imgs.push(fm.convAbsUrl(f.url));
  1575. } else {
  1576. editor.execute('link', fm.convAbsUrl(f.url));
  1577. }
  1578. });
  1579. if (imgs.length) {
  1580. insertImages(imgs);
  1581. }
  1582. dlg.trigger('togleminimize');
  1583. };
  1584. gf.options.folders = true;
  1585. gf.options.multiple = true;
  1586. fm.commandMap.open = 'getfile';
  1587. fm.toast({
  1588. mode: 'info',
  1589. msg: fm.i18n('dblclickToSelect')
  1590. });
  1591. };
  1592. }
  1593. // Set up image uploader
  1594. fileRepo.createUploadAdapter = function(loader) {
  1595. return new uploder(loader);
  1596. };
  1597. editor.setData($(editnode).data('data').body);
  1598. // move .ck-body to elFinder node for fullscreen mode
  1599. fm.getUI().append($('body > div.ck-body'));
  1600. $('div.ck-balloon-panel').css({
  1601. 'z-index': fm.getMaximizeCss().zIndex + 1
  1602. });
  1603. dfrd.resolve(editor);
  1604. /*fm.log({
  1605. defaultConfig: cEditor.defaultConfig,
  1606. plugins: cEditor.builtinPlugins.map(function(p) { return p.pluginName; }),
  1607. toolbars: Array.from(editor.ui.componentFactory.names())
  1608. });*/
  1609. })
  1610. ['catch'](function(error) { // ['cache'] instead .cache for fix error on ie8
  1611. fm.error(error);
  1612. });
  1613. },
  1614. uploder = function(loader) {
  1615. var upload = function(file, resolve, reject) {
  1616. fm.exec('upload', {files: [file]}, void(0), fm.cwd().hash)
  1617. .done(function(data){
  1618. if (data.added && data.added.length) {
  1619. fm.url(data.added[0].hash, { async: true }).done(function(url) {
  1620. resolve({
  1621. 'default': fm.convAbsUrl(url)
  1622. });
  1623. }).fail(function() {
  1624. reject('errFileNotFound');
  1625. });
  1626. } else {
  1627. reject(fm.i18n(data.error? data.error : 'errUpload'));
  1628. }
  1629. })
  1630. .fail(function(err) {
  1631. var error = fm.parseError(err);
  1632. reject(fm.i18n(error? (error === 'userabort'? 'errAbort' : error) : 'errUploadNoFiles'));
  1633. })
  1634. .progress(function(data) {
  1635. loader.uploadTotal = data.total;
  1636. loader.uploaded = data.progress;
  1637. });
  1638. };
  1639. this.upload = function() {
  1640. return new Promise(function(resolve, reject) {
  1641. if (loader.file instanceof Promise || (loader.file && typeof loader.file.then === 'function')) {
  1642. loader.file.then(function(file) {
  1643. upload(file, resolve, reject);
  1644. });
  1645. } else {
  1646. upload(loader.file, resolve, reject);
  1647. }
  1648. });
  1649. };
  1650. this.abort = function() {
  1651. fm.getUI().trigger('uploadabort');
  1652. };
  1653. }, loader;
  1654. if (!self.confObj.editor) {
  1655. loader = $.Deferred();
  1656. self.fm.loadScript([
  1657. fm.options.cdns.ckeditor5 + '/' + mode + '/ckeditor.js'
  1658. ], function(editor) {
  1659. if (!editor) {
  1660. editor = window.BalloonEditor || window.InlineEditor || window.ClassicEditor || window.DecoupledEditor;
  1661. }
  1662. if (fm.lang !== 'en') {
  1663. self.fm.loadScript([
  1664. fm.options.cdns.ckeditor5 + '/' + mode + '/translations/' + lang + '.js'
  1665. ], function(obj) {
  1666. loader.resolve(editor);
  1667. }, {
  1668. tryRequire: true,
  1669. loadType: 'tag',
  1670. error: function(obj) {
  1671. lang = 'en';
  1672. loader.resolve(editor);
  1673. }
  1674. });
  1675. } else {
  1676. loader.resolve(editor);
  1677. }
  1678. }, {
  1679. tryRequire: true,
  1680. loadType: 'tag'
  1681. });
  1682. loader.done(function(editor) {
  1683. self.confObj.editor = editor;
  1684. init(editor);
  1685. });
  1686. } else {
  1687. init(self.confObj.editor);
  1688. }
  1689. return dfrd;
  1690. },
  1691. getContent : function() {
  1692. var data = $(this).data('data');
  1693. return data.header + data.body + data.footer;
  1694. },
  1695. close : function(editnode, instance) {
  1696. instance && instance.destroy();
  1697. },
  1698. save : function(editnode, instance) {
  1699. var elm = $(editnode),
  1700. data = elm.data('data');
  1701. if (instance) {
  1702. data.body = instance.getData();
  1703. elm.data('data', data);
  1704. }
  1705. },
  1706. focus : function(editnode, instance) {
  1707. $(editnode).trigger('focus');
  1708. }
  1709. },
  1710. {
  1711. // TinyMCE for html file
  1712. info : {
  1713. id : 'tinymce',
  1714. name : 'TinyMCE',
  1715. iconImg : 'img/editor-icons.png 0 -64'
  1716. },
  1717. exts : ['htm', 'html', 'xhtml'],
  1718. setup : function(opts, fm) {
  1719. var confObj = this;
  1720. if (!fm.options.cdns.tinymce) {
  1721. confObj.disabled = true;
  1722. } else {
  1723. confObj.mceOpts = {};
  1724. if (opts.extraOptions) {
  1725. confObj.uploadOpts = Object.assign({}, opts.extraOptions.uploadOpts || {});
  1726. confObj.mceOpts = Object.assign({}, opts.extraOptions.tinymce || {});
  1727. } else {
  1728. confObj.uploadOpts = {};
  1729. }
  1730. }
  1731. },
  1732. load : function(textarea) {
  1733. var self = this,
  1734. fm = this.fm,
  1735. dfrd = $.Deferred(),
  1736. init = function() {
  1737. var base = $(textarea).show().parent(),
  1738. dlg = base.closest('.elfinder-dialog'),
  1739. h = base.height(),
  1740. delta = base.outerHeight(true) - h,
  1741. // hide MCE dialog and modal block
  1742. hideMceDlg = function() {
  1743. var mceW;
  1744. if (tinymce.activeEditor.windowManager.windows) {
  1745. mceW = tinymce.activeEditor.windowManager.windows[0];
  1746. mceDlg = $(mceW? mceW.getEl() : void(0)).hide();
  1747. mceCv = $('#mce-modal-block').hide();
  1748. } else {
  1749. mceDlg = $('.tox-dialog-wrap').hide();
  1750. }
  1751. },
  1752. // Show MCE dialog and modal block
  1753. showMceDlg = function() {
  1754. mceCv && mceCv.show();
  1755. mceDlg && mceDlg.show();
  1756. },
  1757. tVer = tinymce.majorVersion,
  1758. opts, mceDlg, mceCv;
  1759. // set base height
  1760. base.height(h);
  1761. // fit height function
  1762. textarea._setHeight = function(height) {
  1763. if (tVer < 5) {
  1764. var base = $(this).parent(),
  1765. h = height || base.innerHeight(),
  1766. ctrH = 0,
  1767. areaH;
  1768. base.find('.mce-container-body:first').children('.mce-top-part,.mce-statusbar').each(function() {
  1769. ctrH += $(this).outerHeight(true);
  1770. });
  1771. areaH = h - ctrH - delta;
  1772. base.find('.mce-edit-area iframe:first').height(areaH);
  1773. }
  1774. };
  1775. // TinyMCE configure options
  1776. opts = {
  1777. selector: '#' + textarea.id,
  1778. resize: false,
  1779. plugins: 'print preview fullpage searchreplace autolink directionality visualblocks visualchars fullscreen image link media template codesample table charmap hr pagebreak nonbreaking anchor toc insertdatetime advlist lists wordcount imagetools textpattern help',
  1780. toolbar: 'formatselect | bold italic strikethrough forecolor backcolor | link image media | alignleft aligncenter alignright alignjustify | numlist bullist outdent indent | removeformat',
  1781. image_advtab: true,
  1782. init_instance_callback : function(editor) {
  1783. // fit height on init
  1784. textarea._setHeight(h);
  1785. // re-build on dom move
  1786. dlg.one('beforedommove.'+fm.namespace, function() {
  1787. tinymce.execCommand('mceRemoveEditor', false, textarea.id);
  1788. }).one('dommove.'+fm.namespace, function() {
  1789. self.load(textarea).done(function(editor) {
  1790. self.instance = editor;
  1791. });
  1792. });
  1793. // return editor instance
  1794. dfrd.resolve(editor);
  1795. },
  1796. file_picker_callback : function (callback, value, meta) {
  1797. var gf = fm.getCommand('getfile'),
  1798. revar = function() {
  1799. if (prevVars.hasVar) {
  1800. gf.callback = prevVars.callback;
  1801. gf.options.folders = prevVars.folders;
  1802. gf.options.multiple = prevVars.multi;
  1803. fm.commandMap.open = prevVars.open;
  1804. prevVars.hasVar = false;
  1805. }
  1806. dlg.off('resize close', revar);
  1807. showMceDlg();
  1808. },
  1809. prevVars = {};
  1810. prevVars.callback = gf.callback;
  1811. prevVars.folders = gf.options.folders;
  1812. prevVars.multi = gf.options.multiple;
  1813. prevVars.open = fm.commandMap.open;
  1814. prevVars.hasVar = true;
  1815. gf.callback = function(file) {
  1816. var url, info;
  1817. if (file.mime === 'directory') {
  1818. fm.one('open', function() {
  1819. fm.commandMap.open = 'getfile';
  1820. }).getCommand('open').exec(file.hash);
  1821. return;
  1822. }
  1823. // URL normalization
  1824. url = fm.convAbsUrl(file.url);
  1825. // Make file info
  1826. info = file.name + ' (' + fm.formatSize(file.size) + ')';
  1827. // Provide file and text for the link dialog
  1828. if (meta.filetype == 'file') {
  1829. callback(url, {text: info, title: info});
  1830. }
  1831. // Provide image and alt text for the image dialog
  1832. if (meta.filetype == 'image') {
  1833. callback(url, {alt: info});
  1834. }
  1835. // Provide alternative source and posted for the media dialog
  1836. if (meta.filetype == 'media') {
  1837. callback(url);
  1838. }
  1839. dlg.trigger('togleminimize');
  1840. };
  1841. gf.options.folders = true;
  1842. gf.options.multiple = false;
  1843. fm.commandMap.open = 'getfile';
  1844. hideMceDlg();
  1845. dlg.trigger('togleminimize').one('resize close', revar);
  1846. fm.toast({
  1847. mode: 'info',
  1848. msg: fm.i18n('dblclickToSelect')
  1849. });
  1850. return false;
  1851. },
  1852. images_upload_handler : function (blobInfo, success, failure) {
  1853. var file = blobInfo.blob(),
  1854. err = function(e) {
  1855. var dlg = e.data.dialog || {};
  1856. if (dlg.hasClass('elfinder-dialog-error') || dlg.hasClass('elfinder-confirm-upload')) {
  1857. hideMceDlg();
  1858. dlg.trigger('togleminimize').one('resize close', revert);
  1859. fm.unbind('dialogopened', err);
  1860. }
  1861. },
  1862. revert = function() {
  1863. dlg.off('resize close', revert);
  1864. showMceDlg();
  1865. },
  1866. clipdata = true;
  1867. // check file object
  1868. if (file.name) {
  1869. // file blob of client side file object
  1870. clipdata = void(0);
  1871. }
  1872. fm.bind('dialogopened', err).exec('upload', Object.assign({
  1873. files: [file],
  1874. clipdata: clipdata // to get unique name on connector
  1875. }, self.confObj.uploadOpts), void(0), fm.cwd().hash).done(function(data) {
  1876. if (data.added && data.added.length) {
  1877. fm.url(data.added[0].hash, { async: true }).done(function(url) {
  1878. showMceDlg();
  1879. success(fm.convAbsUrl(url));
  1880. }).fail(function() {
  1881. failure(fm.i18n('errFileNotFound'));
  1882. });
  1883. } else {
  1884. failure(fm.i18n(data.error? data.error : 'errUpload'));
  1885. }
  1886. }).fail(function(err) {
  1887. var error = fm.parseError(err);
  1888. if (error) {
  1889. if (error === 'errUnknownCmd') {
  1890. error = 'errPerm';
  1891. } else if (error === 'userabort') {
  1892. error = 'errAbort';
  1893. }
  1894. }
  1895. failure(fm.i18n(error? error : 'errUploadNoFiles'));
  1896. });
  1897. }
  1898. };
  1899. // TinyMCE 5 supports "height: 100%"
  1900. if (tVer >= 5) {
  1901. opts.height = '100%';
  1902. }
  1903. // trigger event 'editEditorPrepare'
  1904. self.trigger('Prepare', {
  1905. node: textarea,
  1906. editorObj: tinymce,
  1907. instance: void(0),
  1908. opts: opts
  1909. });
  1910. // TinyMCE configure
  1911. tinymce.init(Object.assign(opts, self.confObj.mceOpts));
  1912. };
  1913. if (!self.confObj.loader) {
  1914. self.confObj.loader = $.Deferred();
  1915. self.fm.loadScript([fm.options.cdns.tinymce + (fm.options.cdns.tinymce.match(/\.js/)? '' : '/tinymce.min.js')], function() {
  1916. self.confObj.loader.resolve();
  1917. }, {
  1918. loadType: 'tag'
  1919. });
  1920. }
  1921. self.confObj.loader.done(init);
  1922. return dfrd;
  1923. },
  1924. close : function(textarea, instance) {
  1925. instance && tinymce.execCommand('mceRemoveEditor', false, textarea.id);
  1926. },
  1927. save : function(textarea, instance) {
  1928. instance && instance.save();
  1929. },
  1930. focus : function(textarea, instance) {
  1931. instance && instance.focus();
  1932. },
  1933. resize : function(textarea, instance, e, data) {
  1934. // fit height to base node on dialog resize
  1935. instance && textarea._setHeight();
  1936. }
  1937. },
  1938. {
  1939. info : {
  1940. id : 'zohoeditor',
  1941. name : 'Zoho Editor',
  1942. iconImg : 'img/editor-icons.png 0 -32',
  1943. cmdCheck : 'ZohoOffice',
  1944. preventGet: true,
  1945. hideButtons: true,
  1946. syncInterval : 15000,
  1947. canMakeEmpty: true,
  1948. integrate: {
  1949. title: 'Zoho Office API',
  1950. link: 'https://www.zoho.com/officeapi/'
  1951. }
  1952. },
  1953. mimes : [
  1954. 'application/msword',
  1955. 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
  1956. //'application/pdf',
  1957. 'application/vnd.oasis.opendocument.text',
  1958. 'application/rtf',
  1959. 'text/html',
  1960. 'application/vnd.ms-excel',
  1961. 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
  1962. 'application/vnd.oasis.opendocument.spreadsheet',
  1963. 'application/vnd.sun.xml.calc',
  1964. 'text/csv',
  1965. 'text/tab-separated-values',
  1966. 'application/vnd.ms-powerpoint',
  1967. 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
  1968. 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
  1969. 'application/vnd.oasis.opendocument.presentation',
  1970. 'application/vnd.sun.xml.impress'
  1971. ],
  1972. html : '<iframe style="width:100%;max-height:100%;border:none;"></iframe>',
  1973. // setup on elFinder bootup
  1974. setup : function(opts, fm) {
  1975. if (fm.UA.Mobile || fm.UA.ltIE8) {
  1976. this.disabled = true;
  1977. }
  1978. },
  1979. // Prepare on before show dialog
  1980. prepare : function(base, dialogOpts, file) {
  1981. var elfNode = base.editor.fm.getUI();
  1982. $(base).height(elfNode.height());
  1983. dialogOpts.width = Math.max(dialogOpts.width || 0, elfNode.width() * 0.8);
  1984. },
  1985. // Initialization of editing node (this: this editors HTML node)
  1986. init : function(id, file, dum, fm) {
  1987. var ta = this,
  1988. ifm = $(this).hide(),
  1989. uiToast = fm.getUI('toast'),
  1990. spnr = $('<div class="elfinder-edit-spinner elfinder-edit-zohoeditor"></div>')
  1991. .html('<span class="elfinder-spinner-text">' + fm.i18n('nowLoading') + '</span><span class="elfinder-spinner"></span>')
  1992. .appendTo(ifm.parent()),
  1993. cdata = function() {
  1994. var data = '';
  1995. $.each(fm.customData, function(key, val) {
  1996. data += '&' + encodeURIComponent(key) + '=' + encodeURIComponent(val);
  1997. });
  1998. return data;
  1999. };
  2000. $(ta).data('xhr', fm.request({
  2001. data: {
  2002. cmd: 'editor',
  2003. name: ta.editor.confObj.info.cmdCheck,
  2004. method: 'init',
  2005. 'args[target]': file.hash,
  2006. 'args[lang]' : fm.lang,
  2007. 'args[cdata]' : cdata()
  2008. },
  2009. preventDefault : true
  2010. }).done(function(data) {
  2011. var opts;
  2012. if (data.zohourl) {
  2013. opts = {
  2014. css: {
  2015. height: '100%'
  2016. }
  2017. };
  2018. // trigger event 'editEditorPrepare'
  2019. ta.editor.trigger('Prepare', {
  2020. node: ta,
  2021. editorObj: void(0),
  2022. instance: ifm,
  2023. opts: opts
  2024. });
  2025. ifm.attr('src', data.zohourl).show().css(opts.css);
  2026. if (data.warning) {
  2027. uiToast.appendTo(ta.closest('.ui-dialog'));
  2028. fm.toast({
  2029. msg: fm.i18n(data.warning),
  2030. mode: 'warning',
  2031. timeOut: 0,
  2032. onHidden: function() {
  2033. uiToast.children().length === 1 && uiToast.appendTo(fm.getUI());
  2034. },
  2035. button: {
  2036. text: 'btnYes'
  2037. }
  2038. });
  2039. }
  2040. } else {
  2041. data.error && fm.error(data.error);
  2042. ta.elfinderdialog('destroy');
  2043. }
  2044. }).fail(function(error) {
  2045. error && fm.error(error);
  2046. ta.elfinderdialog('destroy');
  2047. }).always(function() {
  2048. spnr.remove();
  2049. }));
  2050. },
  2051. load : function() {},
  2052. getContent : function() {},
  2053. save : function() {},
  2054. // Before dialog close
  2055. beforeclose : iframeClose,
  2056. // On dialog closed
  2057. close : function(ta) {
  2058. var fm = this.fm,
  2059. xhr = $(ta).data('xhr');
  2060. if (xhr.state() === 'pending') {
  2061. xhr.reject();
  2062. }
  2063. }
  2064. },
  2065. {
  2066. // Zip Archive with FlySystem
  2067. info : {
  2068. id : 'ziparchive',
  2069. name : 'btnMount',
  2070. iconImg : 'img/toolbar.png 0 -416',
  2071. cmdCheck : 'ZipArchive',
  2072. edit : function(file, editor) {
  2073. var fm = this,
  2074. dfrd = $.Deferred();
  2075. fm.request({
  2076. data:{
  2077. cmd: 'netmount',
  2078. protocol: 'ziparchive',
  2079. host: file.hash,
  2080. path: file.phash
  2081. },
  2082. preventFail: true,
  2083. notify : {type : 'netmount', cnt : 1, hideCnt : true}
  2084. }).done(function(data) {
  2085. var pdir;
  2086. if (data.added && data.added.length) {
  2087. if (data.added[0].phash) {
  2088. if (pdir = fm.file(data.added[0].phash)) {
  2089. if (! pdir.dirs) {
  2090. pdir.dirs = 1;
  2091. fm.change({ changed: [ pdir ] });
  2092. }
  2093. }
  2094. }
  2095. fm.one('netmountdone', function() {
  2096. fm.exec('open', data.added[0].hash);
  2097. fm.one('opendone', function() {
  2098. data.toast && fm.toast(data.toast);
  2099. });
  2100. });
  2101. }
  2102. dfrd.resolve();
  2103. })
  2104. .fail(function(error) {
  2105. dfrd.reject(error);
  2106. });
  2107. return dfrd;
  2108. }
  2109. },
  2110. mimes : ['application/zip'],
  2111. load : function() {},
  2112. save : function(){}
  2113. },
  2114. {
  2115. // Simple Text (basic textarea editor)
  2116. info : {
  2117. id : 'textarea',
  2118. name : 'TextArea',
  2119. useTextAreaEvent : true
  2120. },
  2121. load : function(textarea) {
  2122. // trigger event 'editEditorPrepare'
  2123. this.trigger('Prepare', {
  2124. node: textarea,
  2125. editorObj: void(0),
  2126. instance: void(0),
  2127. opts: {}
  2128. });
  2129. textarea.setSelectionRange && textarea.setSelectionRange(0, 0);
  2130. $(textarea).trigger('focus').show();
  2131. },
  2132. save : function(){}
  2133. },
  2134. {
  2135. // File converter with online-convert.com
  2136. info : {
  2137. id : 'onlineconvert',
  2138. name : 'Online Convert',
  2139. iconImg : 'img/editor-icons.png 0 -144',
  2140. cmdCheck : 'OnlineConvert',
  2141. preventGet: true,
  2142. hideButtons: true,
  2143. single: true,
  2144. converter: true,
  2145. canMakeEmpty: false,
  2146. integrate: {
  2147. title: 'ONLINE-CONVERT.COM',
  2148. link: 'https://online-convert.com'
  2149. }
  2150. },
  2151. mimes : ['*'],
  2152. html : '<div style="width:100%;max-height:100%;"></div>',
  2153. // setup on elFinder bootup
  2154. setup : function(opts, fm) {
  2155. var mOpts = opts.extraOptions.onlineConvert || {maxSize:100,showLink:true};
  2156. if (mOpts.maxSize) {
  2157. this.info.maxSize = mOpts.maxSize * 1048576;
  2158. }
  2159. this.set = Object.assign({
  2160. url : 'https://%s.online-convert.com%s?external_url=',
  2161. conv : {
  2162. Archive: {'7Z':{}, 'BZ2':{ext:'bz'}, 'GZ':{}, 'ZIP':{}},
  2163. Audio: {'MP3':{}, 'OGG':{ext:'oga'}, 'WAV':{}, 'WMA':{}, 'AAC':{}, 'AIFF':{ext:'aif'}, 'FLAC':{}, 'M4A':{}, 'MMF':{}, 'OPUS':{ext:'oga'}},
  2164. Document: {'DOC':{}, 'DOCX':{}, 'HTML':{}, 'ODT':{}, 'PDF':{}, 'PPT':{}, 'PPTX':{}, 'RTF':{}, 'SWF':{}, 'TXT':{}},
  2165. eBook: {'AZW3':{ext:'azw'}, 'ePub':{}, 'FB2':{ext:'xml'}, 'LIT':{}, 'LRF':{}, 'MOBI':{}, 'PDB':{}, 'PDF':{},'PDF-eBook':{ext:'pdf'}, 'TCR':{}},
  2166. Hash: {'Adler32':{}, 'Apache-htpasswd':{}, 'Blowfish':{}, 'CRC32':{}, 'CRC32B':{}, 'Gost':{}, 'Haval128':{},'MD4':{}, 'MD5':{}, 'RIPEMD128':{}, 'RIPEMD160':{}, 'SHA1':{}, 'SHA256':{}, 'SHA384':{}, 'SHA512':{}, 'Snefru':{}, 'Std-DES':{}, 'Tiger128':{}, 'Tiger128-calculator':{}, 'Tiger128-converter':{}, 'Tiger160':{}, 'Tiger192':{}, 'Whirlpool':{}},
  2167. Image: {'BMP':{}, 'EPS':{ext:'ai'}, 'GIF':{}, 'EXR':{}, 'ICO':{}, 'JPG':{}, 'PNG':{}, 'SVG':{}, 'TGA':{}, 'TIFF':{ext:'tif'}, 'WBMP':{}, 'WebP':{}},
  2168. Video: {'3G2':{}, '3GP':{}, 'AVI':{}, 'FLV':{}, 'HLS':{ext:'m3u8'}, 'MKV':{}, 'MOV':{}, 'MP4':{}, 'MPEG-1':{ext:'mpeg'}, 'MPEG-2':{ext:'mpeg'}, 'OGG':{ext:'ogv'}, 'OGV':{}, 'WebM':{}, 'WMV':{}, 'Android':{link:'/convert-video-for-%s',ext:'mp4'}, 'Blackberry':{link:'/convert-video-for-%s',ext:'mp4'}, 'DPG':{link:'/convert-video-for-%s',ext:'avi'}, 'iPad':{link:'/convert-video-for-%s',ext:'mp4'}, 'iPhone':{link:'/convert-video-for-%s',ext:'mp4'}, 'iPod':{link:'/convert-video-for-%s',ext:'mp4'}, 'Nintendo-3DS':{link:'/convert-video-for-%s',ext:'avi'}, 'Nintendo-DS':{link:'/convert-video-for-%s',ext:'avi'}, 'PS3':{link:'/convert-video-for-%s',ext:'mp4'}, 'Wii':{link:'/convert-video-for-%s',ext:'avi'}, 'Xbox':{link:'/convert-video-for-%s',ext:'wmv'}}
  2169. },
  2170. catExts : {
  2171. Hash: 'txt'
  2172. },
  2173. link : '<div class="elfinder-edit-onlineconvert-link"><a href="https://www.online-convert.com" target="_blank"><span class="elfinder-button-icon"></span>ONLINE-CONVERT.COM</a></div>',
  2174. useTabs : ($.fn.tabs && !fm.UA.iOS)? true : false // Can't work on iOS, I don't know why.
  2175. }, mOpts);
  2176. },
  2177. // Prepare on before show dialog
  2178. prepare : function(base, dialogOpts, file) {
  2179. var elfNode = base.editor.fm.getUI();
  2180. $(base).height(elfNode.height());
  2181. dialogOpts.width = Math.max(dialogOpts.width || 0, elfNode.width() * 0.8);
  2182. },
  2183. // Initialization of editing node (this: this editors HTML node)
  2184. init : function(id, file, dum, fm) {
  2185. var ta = this,
  2186. confObj = ta.editor.confObj,
  2187. set = confObj.set,
  2188. uiToast = fm.getUI('toast'),
  2189. idxs = {},
  2190. allowZip = fm.uploadMimeCheck('application/zip', file.phash),
  2191. selfUrl = $('base').length? document.location.href.replace(/#.*$/, '') : '',
  2192. getExt = function(cat, con) {
  2193. var c;
  2194. if (set.catExts[cat]) {
  2195. return set.catExts[cat];
  2196. }
  2197. if (set.conv[cat] && (c = set.conv[cat][con])) {
  2198. return (c.ext || con).toLowerCase();
  2199. }
  2200. return con.toLowerCase();
  2201. },
  2202. setOptions = function(cat, done) {
  2203. var type, dfdInit, dfd;
  2204. if (typeof confObj.api === 'undefined') {
  2205. dfdInit = fm.request({
  2206. data: {
  2207. cmd: 'editor',
  2208. name: 'OnlineConvert',
  2209. method: 'init'
  2210. },
  2211. preventDefault : true
  2212. });
  2213. } else {
  2214. dfdInit = $.Deferred().resolve({api: confObj.api});
  2215. }
  2216. cat = cat.toLowerCase();
  2217. dfdInit.done(function(data) {
  2218. confObj.api = data.api;
  2219. if (confObj.api) {
  2220. if (cat) {
  2221. type = '?category=' + cat;
  2222. } else {
  2223. type = '';
  2224. cat = 'all';
  2225. }
  2226. if (!confObj.conversions) {
  2227. confObj.conversions = {};
  2228. }
  2229. if (!confObj.conversions[cat]) {
  2230. dfd = $.getJSON('https://api2.online-convert.com/conversions' + type);
  2231. } else {
  2232. dfd = $.Deferred().resolve(confObj.conversions[cat]);
  2233. }
  2234. dfd.done(function(d) {
  2235. confObj.conversions[cat] = d;
  2236. $.each(d, function(i, o) {
  2237. btns[set.useTabs? 'children' : 'find']('.onlineconvert-category-' + o.category).children('.onlineconvert-' + o.target).trigger('makeoption', o);
  2238. });
  2239. done && done();
  2240. });
  2241. }
  2242. });
  2243. },
  2244. btns = (function() {
  2245. var btns = $('<div></div>').on('click', 'button', function() {
  2246. var b = $(this),
  2247. opts = b.data('opts') || null,
  2248. cat = b.closest('.onlineconvert-category').data('cname'),
  2249. con = b.data('conv');
  2250. if (confObj.api === true) {
  2251. api({
  2252. category: cat,
  2253. convert: con,
  2254. options: opts
  2255. });
  2256. }
  2257. }).on('change', function(e) {
  2258. var t = $(e.target),
  2259. p = t.parent(),
  2260. b = t.closest('.elfinder-edit-onlineconvert-button').children('button:first'),
  2261. o = b.data('opts') || {},
  2262. v = p.data('type') === 'boolean'? t.is(':checked') : t.val();
  2263. e.stopPropagation();
  2264. if (v) {
  2265. if (p.data('type') === 'integer') {
  2266. v = parseInt(v);
  2267. }
  2268. if (p.data('pattern')) {
  2269. var reg = new RegExp(p.data('pattern'));
  2270. if (!reg.test(v)) {
  2271. requestAnimationFrame(function() {
  2272. fm.error('"' + fm.escape(v) + '" is not match to "/' + fm.escape(p.data('pattern')) + '/"');
  2273. });
  2274. v = null;
  2275. }
  2276. }
  2277. }
  2278. if (v) {
  2279. o[t.parent().data('optkey')] = v;
  2280. } else {
  2281. delete o[p.data('optkey')];
  2282. }
  2283. b.data('opts', o);
  2284. }),
  2285. ul = $('<ul></ul>'),
  2286. oform = function(n, o) {
  2287. var f = $('<p></p>').data('optkey', n).data('type', o.type),
  2288. checked = '',
  2289. disabled = '',
  2290. nozip = false,
  2291. opts, btn, elm;
  2292. if (o.description) {
  2293. f.attr('title', fm.i18n(o.description));
  2294. }
  2295. if (o.pattern) {
  2296. f.data('pattern', o.pattern);
  2297. }
  2298. f.append($('<span></span>').text(fm.i18n(n) + ' : '));
  2299. if (o.type === 'boolean') {
  2300. if (o['default'] || (nozip = (n === 'allow_multiple_outputs' && !allowZip))) {
  2301. checked = ' checked';
  2302. if (nozip) {
  2303. disabled = ' disabled';
  2304. }
  2305. btn = this.children('button:first');
  2306. opts = btn.data('opts') || {};
  2307. opts[n] = true;
  2308. btn.data('opts', opts);
  2309. }
  2310. f.append($('<input type="checkbox" value="true"'+checked+disabled+'/>'));
  2311. } else if (o['enum']){
  2312. elm = $('<select></select>').append($('<option value=""></option>').text('Select...'));
  2313. $.each(o['enum'], function(i, v) {
  2314. elm.append($('<option value="'+v+'"></option>').text(v));
  2315. });
  2316. f.append(elm);
  2317. } else {
  2318. f.append($('<input type="text" value=""/>'));
  2319. }
  2320. return f;
  2321. },
  2322. makeOption = function(o) {
  2323. var elm = this,
  2324. b = $('<span class="elfinder-button-icon elfinder-button-icon-preference"></span>').on('click', function() {
  2325. f.toggle();
  2326. }),
  2327. f = $('<div class="elfinder-edit-onlinconvert-options"></div>').hide();
  2328. if (o.options) {
  2329. $.each(o.options, function(k, v) {
  2330. k !== 'download_password' && f.append(oform.call(elm, k, v));
  2331. });
  2332. }
  2333. elm.append(b, f);
  2334. },
  2335. ts = (+new Date()),
  2336. i = 0;
  2337. if (!confObj.ext2mime) {
  2338. confObj.ext2mime = Object.assign(fm.arrayFlip(fm.mimeTypes), ext2mime);
  2339. }
  2340. $.each(set.conv, function(t, c) {
  2341. var cname = t.toLowerCase(),
  2342. id = 'elfinder-edit-onlineconvert-' + cname + ts,
  2343. type = $('<div id="' + id + '" class="onlineconvert-category onlineconvert-category-'+cname+'"></div>').data('cname', t),
  2344. cext;
  2345. $.each(c, function(n, o) {
  2346. var nl = n.toLowerCase(),
  2347. ext = getExt(t, n);
  2348. if (!confObj.ext2mime[ext]) {
  2349. if (cname === 'audio' || cname === 'image' || cname === 'video') {
  2350. confObj.ext2mime[ext] = cname + '/x-' + nl;
  2351. } else {
  2352. confObj.ext2mime[ext] = 'application/octet-stream';
  2353. }
  2354. }
  2355. if (fm.uploadMimeCheck(confObj.ext2mime[ext], file.phash)) {
  2356. type.append($('<div class="elfinder-edit-onlineconvert-button onlineconvert-'+nl+'"></div>').on('makeoption', function(e, data) {
  2357. var elm = $(this);
  2358. if (!elm.children('.elfinder-button-icon-preference').length) {
  2359. makeOption.call(elm, data);
  2360. }
  2361. }).append($('<button></button>').text(n).data('conv', n)));
  2362. }
  2363. });
  2364. if (type.children().length) {
  2365. ul.append($('<li></li>').append($('<a></a>').attr('href', selfUrl + '#' + id).text(t)));
  2366. btns.append(type);
  2367. idxs[cname] = i++;
  2368. }
  2369. });
  2370. if (set.useTabs) {
  2371. btns.prepend(ul).tabs({
  2372. beforeActivate: function(e, ui) {
  2373. setOptions(ui.newPanel.data('cname'));
  2374. }
  2375. });
  2376. } else {
  2377. $.each(set.conv, function(t) {
  2378. var tl = t.toLowerCase();
  2379. btns.append($('<fieldset class="onlineconvert-fieldset-' + tl + '"></fieldset>').append($('<legend></legend>').text(t)).append(btns.children('.onlineconvert-category-' + tl)));
  2380. });
  2381. }
  2382. return btns;
  2383. })(),
  2384. select = $(this)
  2385. .append(
  2386. btns,
  2387. (set.showLink? $(set.link) : null)
  2388. ),
  2389. spnr = $('<div class="elfinder-edit-spinner elfinder-edit-onlineconvert"></div>')
  2390. .hide()
  2391. .html('<span class="elfinder-spinner-text">' + fm.i18n('nowLoading') + '</span><span class="elfinder-spinner"></span>')
  2392. .appendTo(select.parent()),
  2393. prog = $('<div class="elfinder-quicklook-info-progress"></div>').appendTo(spnr),
  2394. _url = null,
  2395. url = function() {
  2396. var onetime;
  2397. if (_url) {
  2398. return $.Deferred().resolve(_url);
  2399. } else {
  2400. spnr.show();
  2401. return fm.forExternalUrl(file.hash, { progressBar: prog }).done(function(url) {
  2402. _url = url;
  2403. }).fail(function(error) {
  2404. error && fm.error(error);
  2405. ta.elfinderdialog('destroy');
  2406. }).always(function() {
  2407. spnr.hide();
  2408. });
  2409. }
  2410. },
  2411. api = function(opts) {
  2412. $(ta).data('dfrd', url().done(function(url) {
  2413. select.fadeOut();
  2414. setStatus({info: 'Start conversion request.'});
  2415. fm.request({
  2416. data: {
  2417. cmd: 'editor',
  2418. name: 'OnlineConvert',
  2419. method: 'api',
  2420. 'args[category]' : opts.category.toLowerCase(),
  2421. 'args[convert]' : opts.convert.toLowerCase(),
  2422. 'args[options]' : JSON.stringify(opts.options),
  2423. 'args[source]' : fm.convAbsUrl(url),
  2424. 'args[filename]' : fm.splitFileExtention(file.name)[0] + '.' + getExt(opts.category, opts.convert),
  2425. 'args[mime]' : file.mime
  2426. },
  2427. preventDefault : true
  2428. }).done(function(data) {
  2429. checkRes(data.apires, opts.category, opts.convert);
  2430. }).fail(function(error) {
  2431. error && fm.error(error);
  2432. ta.elfinderdialog('destroy');
  2433. });
  2434. }));
  2435. },
  2436. checkRes = function(res, cat, con) {
  2437. var status, err = [];
  2438. if (res && res.id) {
  2439. status = res.status;
  2440. if (status.code === 'failed') {
  2441. spnr.hide();
  2442. if (res.errors && res.errors.length) {
  2443. $.each(res.errors, function(i, o) {
  2444. o.message && err.push(o.message);
  2445. });
  2446. }
  2447. fm.error(err.length? err : status.info);
  2448. select.fadeIn();
  2449. } else if (status.code === 'completed') {
  2450. upload(res);
  2451. } else {
  2452. setStatus(status);
  2453. setTimeout(function() {
  2454. polling(res.id);
  2455. }, 1000);
  2456. }
  2457. } else {
  2458. uiToast.appendTo(ta.closest('.ui-dialog'));
  2459. if (res.message) {
  2460. fm.toast({
  2461. msg: fm.i18n(res.message),
  2462. mode: 'error',
  2463. timeOut: 5000,
  2464. onHidden: function() {
  2465. uiToast.children().length === 1 && uiToast.appendTo(fm.getUI());
  2466. }
  2467. });
  2468. }
  2469. fm.toast({
  2470. msg: fm.i18n('editorConvNoApi'),
  2471. mode: 'error',
  2472. timeOut: 3000,
  2473. onHidden: function() {
  2474. uiToast.children().length === 1 && uiToast.appendTo(fm.getUI());
  2475. }
  2476. });
  2477. spnr.hide();
  2478. select.show();
  2479. }
  2480. },
  2481. setStatus = function(status) {
  2482. spnr.show().children('.elfinder-spinner-text').text(status.info);
  2483. },
  2484. polling = function(jobid) {
  2485. fm.request({
  2486. data: {
  2487. cmd: 'editor',
  2488. name: 'OnlineConvert',
  2489. method: 'api',
  2490. 'args[jobid]': jobid
  2491. },
  2492. preventDefault : true
  2493. }).done(function(data) {
  2494. checkRes(data.apires);
  2495. }).fail(function(error) {
  2496. error && fm.error(error);
  2497. ta.elfinderdialog('destroy');
  2498. });
  2499. },
  2500. upload = function(res) {
  2501. var output = res.output,
  2502. id = res.id,
  2503. url = '';
  2504. spnr.hide();
  2505. if (output && output.length) {
  2506. ta.elfinderdialog('destroy');
  2507. $.each(output, function(i, o) {
  2508. if (o.uri) {
  2509. url += o.uri + '\n';
  2510. }
  2511. });
  2512. fm.upload({
  2513. target: file.phash,
  2514. files: [url],
  2515. type: 'text',
  2516. extraData: {
  2517. contentSaveId: 'OnlineConvert-' + res.id
  2518. }
  2519. });
  2520. }
  2521. },
  2522. mode = 'document',
  2523. cl, m;
  2524. select.parent().css({overflow: 'auto'}).addClass('overflow-scrolling-touch');
  2525. if (m = file.mime.match(/^(audio|image|video)/)) {
  2526. mode = m[1];
  2527. }
  2528. if (set.useTabs) {
  2529. if (idxs[mode]) {
  2530. btns.tabs('option', 'active', idxs[mode]);
  2531. }
  2532. } else {
  2533. cl = Object.keys(set.conv).length;
  2534. $.each(set.conv, function(t) {
  2535. if (t.toLowerCase() === mode) {
  2536. setOptions(t, function() {
  2537. $.each(set.conv, function(t0) {
  2538. t0.toLowerCase() !== mode && setOptions(t0);
  2539. });
  2540. });
  2541. return false;
  2542. }
  2543. cl--;
  2544. });
  2545. if (!cl) {
  2546. $.each(set.conv, function(t) {
  2547. setOptions(t);
  2548. });
  2549. }
  2550. select.parent().scrollTop(btns.children('.onlineconvert-fieldset-' + mode).offset().top);
  2551. }
  2552. },
  2553. load : function() {},
  2554. getContent : function() {},
  2555. save : function() {},
  2556. // On dialog closed
  2557. close : function(ta) {
  2558. var fm = this.fm,
  2559. dfrd = $(ta).data('dfrd');
  2560. if (dfrd && dfrd.state() === 'pending') {
  2561. dfrd.reject();
  2562. }
  2563. }
  2564. }
  2565. ];
  2566. }, window.elFinder));