webdav.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791
  1. (function($) {
  2. if (!('from' in Array)) {
  3. Array.from = function(arrayLike) {
  4. return [].slice.call(arrayLike);
  5. };
  6. }
  7. if (!('keys' in Object)) {
  8. Object.keys = function(obj) {
  9. var keys = [];
  10. for (var key in obj) {
  11. if (obj.hasOwnProperty(key)) {
  12. keys.push(key);
  13. }
  14. }
  15. return keys;
  16. };
  17. }
  18. var WebDAV = (function() {
  19. // internal methods
  20. var _bindEvents = function(file) {
  21. if (file.directory) {
  22. file.item.find('.title').on('click', function() {
  23. history.pushState(history.state, file.path + file.name, file.path + file.name);
  24. WebDAV.list(file.path + file.name);
  25. return false;
  26. });
  27. }
  28. else {
  29. file.item.find('.title').on('click', function(event) {
  30. event.stopPropagation();
  31. if (file.type === 'video') {
  32. $.featherlight('<video autoplay controls><source src="' + file.path + file.name + '"/></video>');
  33. event.preventDefault();
  34. }
  35. else if (file.type === 'audio') {
  36. $.featherlight('<audio autoplay controls><source src="' + file.path + file.name + '"/></audio>');
  37. event.preventDefault();
  38. }
  39. else if (file.type === 'image') {
  40. $.featherlight({
  41. image: file.path + file.name
  42. });
  43. event.preventDefault();
  44. }
  45. else if (file.type === 'font') {
  46. var formats = {
  47. eot: 'embedded-opentype',
  48. otf: 'opentype',
  49. ttf: 'truetype'
  50. },
  51. extension = file.name.replace(/^.+\.([^\.]+)$/, '$1').toLowerCase(),
  52. fontName = (file.path + file.name).replace(/\W+/g, '_'),
  53. demoText = 'The quick brown fox jumps over the lazy dog. 0123456789<br/>Aa Bb Cc Dd Ee Ff Gg Hh Ii Jj Kk Ll Mm Nn Oo Pp Qq Rr Ss Tt Uu Vv Ww Xx Yy Zz';
  54. if (!$('[data-path="' + (file.path + file.name) + '"]').is('style')) {
  55. $('body').appendChild('<style type="text/css" data-path="' + (file.path + file.name) + '">@font-face{font-family:"' + fontName + '";src:url("' + file.path + file.name + '") format("' + (formats[extension] || extension) + '")}</style>');
  56. }
  57. $.featherlight('<h1 style="font-family:"' + fontName + '">' + file.name + '</h1><p style="font-family:\'' + fontName + '\';font-size:1.5em">' + demoText + '</p><p style="font-family:\'' + fontName + '\'">' + a + '</p><p style="font-family:\'' + fontName + '\'"><strong>' + demoText + '</strong></p><p style="font-family:\'' + fontName + '\'"><em>' + demoText + '</em></p><p><a href="' + file.path + file.name + '" style="display:inline-block;padding:.5em;background:#000;font-family:sans-serif;border-radius:.5em;color:#fff">Download</a></p>');
  58. event.preventDefault();
  59. }
  60. else if (file.type === 'text') {
  61. if (!('code' in $.featherlight.contentFilters)) {
  62. $.extend($.featherlight.contentFilters, {
  63. code: {
  64. process: function(url) {
  65. var deferred = $.Deferred(),
  66. $container = $('<pre class="prettyprint"></pre>');
  67. $.ajax(url, {
  68. complete: function(response, status) {
  69. if ( status !== "error" ) {
  70. $container.text(response.responseText);
  71. deferred.resolve($container);
  72. // prettify the code
  73. PR.prettyPrint();
  74. }
  75. deferred.fail();
  76. }
  77. });
  78. return deferred.promise();
  79. }
  80. }
  81. });
  82. }
  83. $.featherlight({
  84. code: file.path + file.name
  85. });
  86. event.preventDefault();
  87. }
  88. });
  89. }
  90. if (file['delete']) {
  91. file.item.find('.delete').on('click', function() {
  92. if (confirm('Are you sure you want to delete "' + file.name + '"?')) {
  93. WebDAV.del(file);
  94. }
  95. return false;
  96. });
  97. file.item.find('.rename').on('click', function() {
  98. var to = prompt('Please enter the new name for "' + file.name + '":', decodeURIComponent(file.name));
  99. if (!to) {
  100. return false;
  101. }
  102. if (!_validateFileName(to)) {
  103. _message('Bad file name.');
  104. return false;
  105. }
  106. WebDAV.rename(file, file.path + to);
  107. return false;
  108. });
  109. file.item.find('.copy').on('click', function() {
  110. _message('Currently not implemented.');
  111. return false;
  112. });
  113. file.item.find('.move').on('click', function() {
  114. _message('Currently not implemented.');
  115. return false;
  116. });
  117. file.item.find('.download').on('click', function(event) {
  118. event.stopPropagation();
  119. return true;
  120. });
  121. }
  122. file.item.on('click', function() {
  123. file.item.find('a.title').click();
  124. return false;
  125. });
  126. return file.item;
  127. },
  128. _checkFile = function(file) {
  129. var foundFile = false;
  130. $.each(_files, function() {
  131. if (decodeURIComponent(this.name) === decodeURIComponent(file.name)) {
  132. foundFile = this;
  133. return false;
  134. }
  135. });
  136. return foundFile;
  137. },
  138. _createListItem = function(file) {
  139. file.item = $('<li/>').data('file', file);
  140. if (file.directory) {
  141. file.item.addClass('directory');
  142. }
  143. else {
  144. file.item.addClass('file');
  145. if (file.type) {
  146. file.item.addClass(file.type);
  147. }
  148. else {
  149. file.item.addClass('unknown');
  150. }
  151. }
  152. if (!file.directory) {
  153. file.item.addClass(file.name.replace(/^.+\.([^\.]+)$/, '$1'));
  154. }
  155. file.item.append('<a href="' + file.path + file.name + '" target="_blank" class="title">' + file.title + '</a>');
  156. if (!file.directory) {
  157. file.item.append('<span class="size">' + _showSize(file.size) + '</span>');
  158. }
  159. // parent folder doesn't have a 'name'
  160. if (file.name) {
  161. if (file['delete']) {
  162. file.item.append('<a href="#delete" title="Delete" class="delete">delete</a>');
  163. file.item.append('<a href="#move" title="Move" class="move">move</a>');
  164. }
  165. file.item.append('<a href="#rename" title="Rename" class="rename">rename</a>');
  166. file.item.append('<a href="#copy" title="Copy" class="copy">copy</a>');
  167. if (!file.directory) {
  168. file.item.append('<a href="' + file.path + file.name + '" download="' + file.name + '" class="download">download</a>');
  169. }
  170. }
  171. _bindEvents(file);
  172. return file;
  173. },
  174. _validateFileName = function(filename) {
  175. if (!filename) {
  176. return false;
  177. }
  178. else if (!filename.match(/^[\w \-\.]+$/)) {
  179. return false;
  180. }
  181. else if (filename.match(/^\.\.?$/)) {
  182. return false;
  183. }
  184. return true;
  185. },
  186. _makeSafePath = function(path) {
  187. return decodeURIComponent(path).replace(/[^\w\/\-\.]/g, function(char) {
  188. return encodeURIComponent(char);
  189. });
  190. },
  191. _getFileName = function(path) {
  192. path = path.replace(/\/$/, '');
  193. return path.split('/').pop();
  194. },
  195. _getTag = function(doc, tag) {
  196. if (doc.querySelector) {
  197. return doc.querySelector(tag);
  198. }
  199. return doc.getElementsByTagName(tag)[0];
  200. },
  201. _getTagContent = function(doc, tag) {
  202. var node = _getTag(doc, tag);
  203. return node ? node.textContent : '';
  204. },
  205. _getTags = function(doc, tag) {
  206. if (doc.querySelectorAll) {
  207. return Array.from(doc.querySelectorAll(tag));
  208. }
  209. return Array.from(doc.getElementsByTagName(tag));
  210. },
  211. _getType = function(file) {
  212. if (file.mimeType && file.mimeType.split('/').shift()) {
  213. return file.mimeType.split('/').shift();
  214. }
  215. var types = {
  216. // displayed in an iframe, using google prettify
  217. text: /\.(?:te?xt|i?nfo|php|pl|cgi|faq|ini|htaccess|log|md|sql|sfv|conf|sh|pl|pm|py|rb|(?:s?c|sa)ss|js|java|coffee|[sx]?html?|xml)$/i,
  218. // displayed in fancybox as an image
  219. image: /\.(?:jpe?g|gif|a?png|svg)/i,
  220. video: /\.(?:mp(?:e?g)?4|mov|avi|webm|ogv)/i,
  221. audio: /\.(?:mp3|wav|ogg)/i,
  222. font: /\.(?:woff2?|eot|[ot]tf)/i
  223. },
  224. // pushed to browser
  225. type = 'unknown';
  226. $.each(types, function(key, value) {
  227. if (file.match(value)) {
  228. type = key;
  229. return false;
  230. }
  231. });
  232. return type;
  233. },
  234. _listContents = function(path, events) {
  235. var req = _request('PROPFIND', path, {
  236. Depth: 1
  237. });
  238. Object.keys(events).forEach(function(event) {
  239. req.addEventListener(event, events[event], true);
  240. });
  241. req.send(null);
  242. return req;
  243. },
  244. _message = function(message, type) {
  245. if ('notify' in $) {
  246. $.notify(message, {
  247. className: (type || 'error')
  248. });
  249. }
  250. else {
  251. console.log(message);
  252. }
  253. },
  254. _refreshDisplay = function(forceRefresh) {
  255. return WebDAV.list(_path, forceRefresh);
  256. },
  257. _renderFiles = function() {
  258. _sortFiles();
  259. _list.empty();
  260. $.each(_files, function(i, file) {
  261. if (!file) {
  262. return;
  263. }
  264. _list.append(file.item);
  265. });
  266. return _list;
  267. },
  268. _request = function(type, url, headers, allowCache) {
  269. // could add support for other versions here. lazy
  270. var xhr = new XMLHttpRequest();
  271. // bust some cache
  272. if (!allowCache) {
  273. url += (url.indexOf('?') > -1 ? '&' : '?') + '_=' + Date.now();
  274. }
  275. xhr.addEventListener('loadstart', function() {
  276. _busy = true;
  277. });
  278. xhr.addEventListener('loadend', function() {
  279. _busy = true;
  280. });
  281. xhr.open(type, url, true);
  282. if (headers) {
  283. Object.keys(headers).forEach(function(header) {
  284. xhr.setRequestHeader(header, headers[header]);
  285. });
  286. }
  287. return xhr;
  288. },
  289. _showSize = function(i) {
  290. var size = '';
  291. ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB'].forEach(function(text, index) {
  292. if (!size && (i < Math.pow(1024, index + 1))) {
  293. size += (i / Math.pow(1024, index)).toFixed((index > 0) ? 1 : 0) + ' ' + ((i === 1) ? 'byte' : text);
  294. }
  295. });
  296. return size;
  297. },
  298. _sortFiles = function() {
  299. if (_files.length) {
  300. _files.sort(function(a, b) {
  301. if (a.directory === b.directory) {
  302. return a.name.replace(/\/$/, '') < b.name.replace(/\/$/, '') ? -1 : 1;
  303. }
  304. else {
  305. return a.directory ? -1 : 1;
  306. }
  307. });
  308. }
  309. $.each(_files, function(i) {
  310. this.index = i;
  311. });
  312. return _files;
  313. },
  314. _updateDisplay = function() {
  315. document.title = decodeURIComponent(_path) + ' - ' + window.location.host;
  316. _sortFiles();
  317. _renderFiles();
  318. },
  319. // private vars
  320. _busy = false,
  321. _cache = {},
  322. _dropper,
  323. _files = [],
  324. _list = $('<ul class="list"/>'),
  325. _path = window.location.pathname,
  326. // exposed API
  327. WebDAV = {
  328. init: function() {
  329. $('<div class="content"></div><div class="upload">Drop files here to upload or <a href="#createDirectory" class="create-directory">create a new directory</a></div>').appendTo($('body').empty());
  330. $('div.content').append(_list);
  331. _dropper = $('div.upload');
  332. WebDAV.list(_path);
  333. // render the nice list
  334. _renderFiles();
  335. // drag and drop area
  336. _dropper.on('dragover', function() {
  337. _dropper.addClass('active');
  338. return false;
  339. });
  340. _dropper.on('dragend dragleave', function(event) {
  341. _dropper.removeClass('active');
  342. return false;
  343. });
  344. _dropper.on('drop', function(event) {
  345. var newFiles = event.originalEvent.target.files || event.originalEvent.dataTransfer.files;
  346. _dropper.removeClass('active');
  347. $.each(newFiles, function(i, fileObject) {
  348. if (existingFile = _checkFile(fileObject)) {
  349. if (!confirm('A file called "' + existingFile.name + '" already exists, would you like to overwrite it?')) {
  350. return false;
  351. }
  352. else {
  353. delete _files[existingFile.index];
  354. }
  355. }
  356. if (typeof FileReader != 'undefined') {
  357. var fileReader = new FileReader();
  358. fileReader.addEventListener('load', function(event) {
  359. fileObject.data = event.target.result;
  360. WebDAV.upload(fileObject);
  361. }, false);
  362. fileReader.context = WebDAV;
  363. fileReader.filename = fileObject.name;
  364. fileReader.readAsArrayBuffer(fileObject);
  365. }
  366. else {
  367. // TODO: support other browsers - flash fallback?
  368. _message('Sorry, your browser isn\'t currently suppored.');
  369. }
  370. });
  371. return false;
  372. });
  373. // TODO: if drag/drop unsupported, regular file upload box - also needed for flash fallback of FileReader
  374. // create directory
  375. $('a.create-directory').on('click', function() {
  376. var name = prompt('New folder name:'),
  377. file = _checkFile(name);
  378. if (!name) {
  379. return false;
  380. }
  381. if (!_validateFileName(name)) {
  382. alert('Name contains unsupported characters, aborting.');
  383. return false;
  384. }
  385. if (file) {
  386. if (file.directory) {
  387. alert('Directory "' + file.name + '" already exists.');
  388. }
  389. else {
  390. alert('A file called "' + file.name + '" exists, unable to create folder.');
  391. }
  392. return false;
  393. }
  394. file = {
  395. directory: true,
  396. name: _makeSafePath(name),
  397. title: name,
  398. path: _path,
  399. modified: Date.now(),
  400. size: false,
  401. type: _getType(name),
  402. mimeType: '',
  403. request: null,
  404. item: null,
  405. delete: true
  406. };
  407. file.request = _request('MKCOL', file.path + file.name);
  408. file.request.addEventListener('loadstart', function(event) {
  409. file.item.addClass('loading');
  410. }, false);
  411. file.request.addEventListener('load', function(event) {
  412. file.item.removeClass('loading');
  413. }, false);
  414. file.request.addEventListener('error', function(event) {
  415. delete _files[file.index];
  416. _updateDisplay();
  417. _message('Error creating directory ' + file.name + '.');
  418. }, false);
  419. file.request.addEventListener('abort', function(event) {
  420. delete _files[file.index];
  421. _updateDisplay();
  422. _message('Aborted as requested.', 'success');
  423. }, false);
  424. _files.push(_createListItem(file));
  425. _updateDisplay();
  426. file.request.send(null);
  427. return false;
  428. });
  429. $(window).on("popstate", function(e) {
  430. WebDAV.list(window.location.pathname);
  431. });
  432. // replace refresh key with force reload
  433. $(document).on('keydown', function(e) {
  434. var keyCode = e.which || e.keyCode;
  435. if ((keyCode === 116) || ((keyCode === 82) && (e.metaKey || e.ctrlKey))) {
  436. e.preventDefault();
  437. WebDAV.list(_path, true);
  438. return false;
  439. }
  440. return true;
  441. });
  442. },
  443. list: function(path, refresh) {
  444. path = path.match(/\/$/) ? path : path + '/'; // ensure we have a trailing slash for some platforms
  445. if ((path in _cache) && !refresh) {
  446. _files = [];
  447. _cache[_path = path].forEach(function(file) {
  448. // events need to be re-bound
  449. _files.push(_createListItem(file));
  450. });
  451. return _updateDisplay();
  452. }
  453. _listContents(path, {
  454. loadstart: function() {
  455. $('div.content').addClass('loading');
  456. },
  457. loadend: function() {
  458. $('div.content').removeClass('loading');
  459. },
  460. load: function(event) {
  461. var list = event.target,
  462. parser = new DOMParser(),
  463. xml = parser.parseFromString(list.responseText, 'application/xml');
  464. _path = path;
  465. _files = [];
  466. _getTags(xml, 'response').forEach(function(entry, i) {
  467. var file = _getTagContent(entry, 'href'),
  468. name = _getFileName(file);
  469. if (!i) {
  470. if (path != '/') {
  471. _files.push(_createListItem({
  472. directory: true,
  473. name: '',
  474. title: '&larr;',
  475. path: path.replace(/[^\/]+\/?$/, ''),
  476. modified: '',
  477. size: '',
  478. type: '',
  479. mimeType: '',
  480. request: null,
  481. item: null,
  482. delete: false
  483. }));
  484. }
  485. return;
  486. }
  487. _files.push(_createListItem({
  488. directory: !!_getTag(entry, 'collection'),
  489. name: name,
  490. title: decodeURIComponent(name),
  491. path: _path,
  492. modified: new Date(_getTagContent(entry, 'getlastmodified')),
  493. size: _getTagContent(entry, 'getcontentlength'),
  494. type: _getType(name),
  495. mimeType: _getTagContent(entry, 'getcontenttype'),
  496. request: null,
  497. item: null,
  498. delete: true
  499. }));
  500. });
  501. _files.timestamp = Date.now();
  502. _cache[_path] = _files;
  503. _updateDisplay();
  504. },
  505. error: function() {
  506. _message('There was an error getting details for ' + path + '.');
  507. },
  508. abort: function() {
  509. _message('Aborted as requested. ' + path, 'success');
  510. }
  511. });
  512. },
  513. upload: function(fileObject) {
  514. if (!fileObject.name) {
  515. return false;
  516. }
  517. var file = {
  518. directory: false,
  519. name: fileObject.name,
  520. title: decodeURIComponent(fileObject.name),
  521. path: _path,
  522. modified: new Date(),
  523. size: fileObject.data.byteLength,
  524. type: _getType(fileObject.name),
  525. mineType: fileObject.type,
  526. request: null,
  527. item: null,
  528. delete: true
  529. };
  530. file.request = _request('PUT', file.path + file.name, {
  531. 'Content-Type': file.type
  532. });
  533. file.request.addEventListener('loadstart', function(event) {
  534. file.item.addClass('loading');
  535. file.item.find('span.size').after('<span class="uploading"><span class="progress"><span class="meter"></span></span><span class="cancel-upload">&times;</span></span>');
  536. file.item.find('span.cancel-upload').on('click', function() {
  537. file.request.abort();
  538. return false;
  539. });
  540. }, false);
  541. file.request.addEventListener('progress', function(event) {
  542. file.item.find('span.meter').width('' + ((event.position / event.total) * 100) + '%');
  543. }, false);
  544. file.request.addEventListener('load', function(event) {
  545. _refreshDisplay();
  546. _message(file.name + ' uploaded successfully.', 'sucess');
  547. }, false);
  548. file.request.addEventListener('error', function(event) {
  549. delete _files[file.index];
  550. _updateDisplay();
  551. _message('Error uploading file.');
  552. }, false);
  553. file.request.addEventListener('abort', function(event) {
  554. delete _files[file.index];
  555. _updateDisplay();
  556. _message('Aborted as requested.', 'sucess');
  557. }, false);
  558. _files.push(_createListItem(file));
  559. _updateDisplay();
  560. file.request.send(fileObject.data);
  561. return true;
  562. },
  563. del: function(file) {
  564. if (!file.name) {
  565. return false;
  566. }
  567. if (!('path' in file)) {
  568. file.path = _path;
  569. }
  570. file.request = _request('DELETE', file.path + file.name);
  571. file.request.addEventListener('load', function(event) {
  572. _refreshDisplay(true);
  573. }, false);
  574. file.request.addEventListener('error', function(event) {
  575. _message('Error deleting file ' + file.name + '.');
  576. }, false);
  577. file.request.addEventListener('abort', function(event) {
  578. _message('Aborted as requested.', 'success');
  579. }, false);
  580. file.request.send(null);
  581. return true;
  582. },
  583. copy: function(from, to) {
  584. // TODO
  585. from.request = _request('COPY', from.path + from.name, {
  586. Destination: to
  587. });
  588. from.request.addEventListener('load', function(event) {
  589. _refreshDisplay();
  590. }, false);
  591. from.request.addEventListener('error', function(event) {
  592. _message('Error copying file ' + file.name + '.');
  593. }, false);
  594. from.request.addEventListener('abort', function(event) {
  595. _message('Aborted as requested.', 'success');
  596. }, false);
  597. from.request.send(null);
  598. return true;
  599. },
  600. move: function(from, to) {
  601. // TODO
  602. from.request = _request('MOVE', from.path + from.name, {
  603. Destination: window.location.protocol + '//' + window.location.host + _makeSafePath(to)
  604. });
  605. from.request.addEventListener('load', function(event) {
  606. _refreshDisplay(true);
  607. }, false);
  608. from.request.addEventListener('error', function(event) {
  609. _message('Error moving file ' + file.name + '.');
  610. }, false);
  611. from.request.addEventListener('abort', function(event) {
  612. _message('Aborted as requested.', 'success');
  613. }, false);
  614. from.request.send(null);
  615. return true;
  616. },
  617. rename: function(from, to) {
  618. return this.move(from, to);
  619. }
  620. };
  621. return WebDAV;
  622. })();
  623. $(function() {
  624. WebDAV.init();
  625. });
  626. })(jQuery);