You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1642 lines
46 KiB

12 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
13 years ago
13 years ago
13 years ago
13 years ago
  1. /*
  2. * Copyright (c) 2014
  3. *
  4. * This file is licensed under the Affero General Public License version 3
  5. * or later.
  6. *
  7. * See the COPYING-README file.
  8. *
  9. */
  10. /* global OC, t, n, FileList, FileActions, Files, FileSummary, BreadCrumb */
  11. /* global dragOptions, folderDropOptions */
  12. window.FileList = {
  13. SORT_INDICATOR_ASC_CLASS: 'icon-triangle-s',
  14. SORT_INDICATOR_DESC_CLASS: 'icon-triangle-n',
  15. appName: t('files', 'Files'),
  16. isEmpty: true,
  17. useUndo:true,
  18. $el: $('#filestable'),
  19. $fileList: $('#fileList'),
  20. breadcrumb: null,
  21. /**
  22. * Instance of FileSummary
  23. */
  24. fileSummary: null,
  25. initialized: false,
  26. // number of files per page
  27. pageSize: 20,
  28. /**
  29. * Array of files in the current folder.
  30. * The entries are of file data.
  31. */
  32. files: [],
  33. /**
  34. * Map of file id to file data
  35. */
  36. _selectedFiles: {},
  37. /**
  38. * Summary of selected files.
  39. * Instance of FileSummary.
  40. */
  41. _selectionSummary: null,
  42. /**
  43. * Sort attribute
  44. */
  45. _sort: 'name',
  46. /**
  47. * Sort direction: 'asc' or 'desc'
  48. */
  49. _sortDirection: 'asc',
  50. /**
  51. * Sort comparator function for the current sort
  52. */
  53. _sortComparator: null,
  54. /**
  55. * Initialize the file list and its components
  56. */
  57. initialize: function() {
  58. var self = this;
  59. if (this.initialized) {
  60. return;
  61. }
  62. // TODO: FileList should not know about global elements
  63. this.$el = $('#filestable');
  64. this.$fileList = $('#fileList');
  65. this.files = [];
  66. this._selectedFiles = {};
  67. this._selectionSummary = new FileSummary();
  68. this.fileSummary = this._createSummary();
  69. this.setSort('name', 'asc');
  70. this.breadcrumb = new BreadCrumb({
  71. onClick: this._onClickBreadCrumb,
  72. onDrop: _.bind(this._onDropOnBreadCrumb, this),
  73. getCrumbUrl: function(part, index) {
  74. return self.linkTo(part.dir);
  75. }
  76. });
  77. $('#controls').prepend(this.breadcrumb.$el);
  78. this.$el.find('thead th .columntitle').click(_.bind(this._onClickHeader, this));
  79. $(window).resize(function() {
  80. // TODO: debounce this ?
  81. var width = $(this).width();
  82. FileList.breadcrumb.resize(width, false);
  83. });
  84. this.$fileList.on('click','td.filename a', _.bind(this._onClickFile, this));
  85. this.$fileList.on('change', 'td.filename input:checkbox', _.bind(this._onClickFileCheckbox, this));
  86. this.$el.find('#select_all').click(_.bind(this._onClickSelectAll, this));
  87. this.$el.find('.download').click(_.bind(this._onClickDownloadSelected, this));
  88. this.$el.find('.delete-selected').click(_.bind(this._onClickDeleteSelected, this));
  89. },
  90. /**
  91. * Selected/deselects the given file element and updated
  92. * the internal selection cache.
  93. *
  94. * @param $tr single file row element
  95. * @param state true to select, false to deselect
  96. */
  97. _selectFileEl: function($tr, state) {
  98. var $checkbox = $tr.find('input:checkbox');
  99. var oldData = !!this._selectedFiles[$tr.data('id')];
  100. var data;
  101. $checkbox.prop('checked', state);
  102. $tr.toggleClass('selected', state);
  103. // already selected ?
  104. if (state === oldData) {
  105. return;
  106. }
  107. data = this.elementToFile($tr);
  108. if (state) {
  109. this._selectedFiles[$tr.data('id')] = data;
  110. this._selectionSummary.add(data);
  111. }
  112. else {
  113. delete this._selectedFiles[$tr.data('id')];
  114. this._selectionSummary.remove(data);
  115. }
  116. this.$el.find('#select_all').prop('checked', this._selectionSummary.getTotal() === this.files.length);
  117. },
  118. /**
  119. * Event handler for when clicking on files to select them
  120. */
  121. _onClickFile: function(event) {
  122. var $tr = $(event.target).closest('tr');
  123. if (event.ctrlKey || event.shiftKey) {
  124. event.preventDefault();
  125. if (event.shiftKey) {
  126. var $lastTr = $(this._lastChecked);
  127. var lastIndex = $lastTr.index();
  128. var currentIndex = $tr.index();
  129. var $rows = this.$fileList.children('tr');
  130. // last clicked checkbox below current one ?
  131. if (lastIndex > currentIndex) {
  132. var aux = lastIndex;
  133. lastIndex = currentIndex;
  134. currentIndex = aux;
  135. }
  136. // auto-select everything in-between
  137. for (var i = lastIndex + 1; i < currentIndex; i++) {
  138. this._selectFileEl($rows.eq(i), true);
  139. }
  140. }
  141. else {
  142. this._lastChecked = $tr;
  143. }
  144. var $checkbox = $tr.find('input:checkbox');
  145. this._selectFileEl($tr, !$checkbox.prop('checked'));
  146. this.updateSelectionSummary();
  147. } else {
  148. var filename = $tr.attr('data-file');
  149. var renaming = $tr.data('renaming');
  150. if (!renaming) {
  151. FileActions.currentFile = $tr.find('td');
  152. var mime=FileActions.getCurrentMimeType();
  153. var type=FileActions.getCurrentType();
  154. var permissions = FileActions.getCurrentPermissions();
  155. var action=FileActions.getDefault(mime,type, permissions);
  156. if (action) {
  157. event.preventDefault();
  158. action(filename);
  159. }
  160. }
  161. }
  162. },
  163. /**
  164. * Event handler for when clicking on a file's checkbox
  165. */
  166. _onClickFileCheckbox: function(e) {
  167. var $tr = $(e.target).closest('tr');
  168. this._selectFileEl($tr, !$tr.hasClass('selected'));
  169. this._lastChecked = $tr;
  170. this.updateSelectionSummary();
  171. },
  172. /**
  173. * Event handler for when selecting/deselecting all files
  174. */
  175. _onClickSelectAll: function(e) {
  176. var checked = $(e.target).prop('checked');
  177. this.$fileList.find('td.filename input:checkbox').prop('checked', checked)
  178. .closest('tr').toggleClass('selected', checked);
  179. this._selectedFiles = {};
  180. this._selectionSummary.clear();
  181. if (checked) {
  182. for (var i = 0; i < this.files.length; i++) {
  183. var fileData = this.files[i];
  184. this._selectedFiles[fileData.id] = fileData;
  185. this._selectionSummary.add(fileData);
  186. }
  187. }
  188. this.updateSelectionSummary();
  189. },
  190. /**
  191. * Event handler for when clicking on "Download" for the selected files
  192. */
  193. _onClickDownloadSelected: function(event) {
  194. var files;
  195. var dir = this.getCurrentDirectory();
  196. if (this.isAllSelected()) {
  197. files = OC.basename(dir);
  198. dir = OC.dirname(dir) || '/';
  199. }
  200. else {
  201. files = _.pluck(this.getSelectedFiles(), 'name');
  202. }
  203. OC.Notification.show(t('files','Your download is being prepared. This might take some time if the files are big.'));
  204. OC.redirect(Files.getDownloadUrl(files, dir));
  205. return false;
  206. },
  207. /**
  208. * Event handler for when clicking on "Delete" for the selected files
  209. */
  210. _onClickDeleteSelected: function(event) {
  211. var files = null;
  212. if (!FileList.isAllSelected()) {
  213. files = _.pluck(this.getSelectedFiles(), 'name');
  214. }
  215. this.do_delete(files);
  216. event.preventDefault();
  217. return false;
  218. },
  219. /**
  220. * Event handler when clicking on a table header
  221. */
  222. _onClickHeader: function(e) {
  223. var $target = $(e.target);
  224. var sort;
  225. if (!$target.is('a')) {
  226. $target = $target.closest('a');
  227. }
  228. sort = $target.attr('data-sort');
  229. if (sort) {
  230. if (this._sort === sort) {
  231. this.setSort(sort, (this._sortDirection === 'desc')?'asc':'desc');
  232. }
  233. else {
  234. this.setSort(sort, 'asc');
  235. }
  236. this.reload();
  237. }
  238. },
  239. /**
  240. * Event handler when clicking on a bread crumb
  241. */
  242. _onClickBreadCrumb: function(e) {
  243. var $el = $(e.target).closest('.crumb'),
  244. $targetDir = $el.data('dir');
  245. if ($targetDir !== undefined) {
  246. e.preventDefault();
  247. FileList.changeDirectory($targetDir);
  248. }
  249. },
  250. _onScroll: function(e) {
  251. if ($(window).scrollTop() + $(window).height() > $(document).height() - 500) {
  252. this._nextPage(true);
  253. }
  254. },
  255. /**
  256. * Event handler when dropping on a breadcrumb
  257. */
  258. _onDropOnBreadCrumb: function( event, ui ) {
  259. var $target = $(event.target);
  260. if (!$target.is('.crumb')) {
  261. $target = $target.closest('.crumb');
  262. }
  263. var targetPath = $(event.target).data('dir');
  264. var dir = this.getCurrentDirectory();
  265. while (dir.substr(0,1) === '/') {//remove extra leading /'s
  266. dir = dir.substr(1);
  267. }
  268. dir = '/' + dir;
  269. if (dir.substr(-1,1) !== '/') {
  270. dir = dir + '/';
  271. }
  272. // do nothing if dragged on current dir
  273. if (targetPath === dir || targetPath + '/' === dir) {
  274. return;
  275. }
  276. var files = this.getSelectedFiles();
  277. if (files.length === 0) {
  278. // single one selected without checkbox?
  279. files = _.map(ui.helper.find('tr'), FileList.elementToFile);
  280. }
  281. FileList.move(_.pluck(files, 'name'), targetPath);
  282. },
  283. /**
  284. * Sets a new page title
  285. */
  286. setPageTitle: function(title){
  287. if (title) {
  288. title += ' - ';
  289. } else {
  290. title = '';
  291. }
  292. title += FileList.appName;
  293. // Sets the page title with the " - ownCloud" suffix as in templates
  294. window.document.title = title + ' - ' + oc_defaults.title;
  295. return true;
  296. },
  297. /**
  298. * Returns the tr element for a given file name
  299. * @param fileName file name
  300. */
  301. findFileEl: function(fileName){
  302. // use filterAttr to avoid escaping issues
  303. return this.$fileList.find('tr').filterAttr('data-file', fileName);
  304. },
  305. /**
  306. * Returns the file data from a given file element.
  307. * @param $el file tr element
  308. * @return file data
  309. */
  310. elementToFile: function($el){
  311. $el = $($el);
  312. return {
  313. id: parseInt($el.attr('data-id'), 10),
  314. name: $el.attr('data-file'),
  315. mimetype: $el.attr('data-mime'),
  316. type: $el.attr('data-type'),
  317. size: parseInt($el.attr('data-size'), 10),
  318. etag: $el.attr('data-etag')
  319. };
  320. },
  321. /**
  322. * Appends the next page of files into the table
  323. * @param animate true to animate the new elements
  324. */
  325. _nextPage: function(animate) {
  326. var index = this.$fileList.children().length,
  327. count = this.pageSize,
  328. tr,
  329. fileData,
  330. newTrs = [],
  331. isAllSelected = this.isAllSelected();
  332. if (index >= this.files.length) {
  333. return;
  334. }
  335. while (count > 0 && index < this.files.length) {
  336. fileData = this.files[index];
  337. tr = this._renderRow(fileData, {updateSummary: false});
  338. this.$fileList.append(tr);
  339. if (isAllSelected || this._selectedFiles[fileData.id]) {
  340. tr.addClass('selected');
  341. tr.find('input:checkbox').prop('checked', true);
  342. }
  343. if (animate) {
  344. tr.addClass('appear transparent');
  345. newTrs.push(tr);
  346. }
  347. index++;
  348. count--;
  349. }
  350. if (animate) {
  351. // defer, for animation
  352. window.setTimeout(function() {
  353. for (var i = 0; i < newTrs.length; i++ ) {
  354. newTrs[i].removeClass('transparent');
  355. }
  356. }, 0);
  357. }
  358. },
  359. /**
  360. * Sets the files to be displayed in the list.
  361. * This operation will re-render the list and update the summary.
  362. * @param filesArray array of file data (map)
  363. */
  364. setFiles: function(filesArray) {
  365. // detach to make adding multiple rows faster
  366. this.files = filesArray;
  367. this.$fileList.detach();
  368. this.$fileList.empty();
  369. // clear "Select all" checkbox
  370. this.$el.find('#select_all').prop('checked', false);
  371. this.isEmpty = this.files.length === 0;
  372. this._nextPage();
  373. this.$el.find('thead').after(this.$fileList);
  374. this.updateEmptyContent();
  375. this.$fileList.trigger(jQuery.Event("fileActionsReady"));
  376. // "Files" might not be loaded in extending apps
  377. if (window.Files) {
  378. Files.setupDragAndDrop();
  379. }
  380. this.fileSummary.calculate(filesArray);
  381. FileList.updateSelectionSummary();
  382. $(window).scrollTop(0);
  383. this.$fileList.trigger(jQuery.Event("updated"));
  384. },
  385. /**
  386. * Creates a new table row element using the given file data.
  387. * @param fileData map of file attributes
  388. * @param options map of attribute "loading" whether the entry is currently loading
  389. * @return new tr element (not appended to the table)
  390. */
  391. _createRow: function(fileData, options) {
  392. var td, simpleSize, basename, extension, sizeColor,
  393. icon = OC.Util.replaceSVGIcon(fileData.icon),
  394. name = fileData.name,
  395. type = fileData.type || 'file',
  396. mtime = parseInt(fileData.mtime, 10) || new Date().getTime(),
  397. mime = fileData.mimetype,
  398. linkUrl;
  399. options = options || {};
  400. if (type === 'dir') {
  401. mime = mime || 'httpd/unix-directory';
  402. }
  403. // user should always be able to rename a share mount point
  404. var allowRename = 0;
  405. if (fileData.isShareMountPoint) {
  406. allowRename = OC.PERMISSION_UPDATE;
  407. }
  408. //containing tr
  409. var tr = $('<tr></tr>').attr({
  410. "data-id" : fileData.id,
  411. "data-type": type,
  412. "data-size": fileData.size,
  413. "data-file": name,
  414. "data-mime": mime,
  415. "data-mtime": mtime,
  416. "data-etag": fileData.etag,
  417. "data-permissions": fileData.permissions | allowRename || this.getDirectoryPermissions()
  418. });
  419. if (type === 'dir') {
  420. // use default folder icon
  421. icon = icon || OC.imagePath('core', 'filetypes/folder');
  422. }
  423. else {
  424. icon = icon || OC.imagePath('core', 'filetypes/file');
  425. }
  426. // filename td
  427. td = $('<td></td>').attr({
  428. "class": "filename",
  429. "style": 'background-image:url(' + icon + '); background-size: 32px;'
  430. });
  431. // linkUrl
  432. if (type === 'dir') {
  433. linkUrl = FileList.linkTo(FileList.getCurrentDirectory() + '/' + name);
  434. }
  435. else {
  436. linkUrl = Files.getDownloadUrl(name, FileList.getCurrentDirectory());
  437. }
  438. td.append('<input id="select-' + fileData.id + '" type="checkbox" /><label for="select-' + fileData.id + '"></label>');
  439. var linkElem = $('<a></a>').attr({
  440. "class": "name",
  441. "href": linkUrl
  442. });
  443. // from here work on the display name
  444. name = fileData.displayName || name;
  445. // split extension from filename for non dirs
  446. if (type !== 'dir' && name.indexOf('.') !== -1) {
  447. basename = name.substr(0, name.lastIndexOf('.'));
  448. extension = name.substr(name.lastIndexOf('.'));
  449. } else {
  450. basename = name;
  451. extension = false;
  452. }
  453. var nameSpan=$('<span></span>').addClass('nametext').text(basename);
  454. linkElem.append(nameSpan);
  455. if (extension) {
  456. nameSpan.append($('<span></span>').addClass('extension').text(extension));
  457. }
  458. // dirs can show the number of uploaded files
  459. if (type === 'dir') {
  460. linkElem.append($('<span></span>').attr({
  461. 'class': 'uploadtext',
  462. 'currentUploads': 0
  463. }));
  464. }
  465. td.append(linkElem);
  466. tr.append(td);
  467. // size column
  468. if (typeof(fileData.size) !== 'undefined' && fileData.size >= 0) {
  469. simpleSize = humanFileSize(parseInt(fileData.size, 10));
  470. sizeColor = Math.round(160-Math.pow((fileData.size/(1024*1024)),2));
  471. } else {
  472. simpleSize = t('files', 'Pending');
  473. }
  474. td = $('<td></td>').attr({
  475. "class": "filesize",
  476. "style": 'color:rgb(' + sizeColor + ',' + sizeColor + ',' + sizeColor + ')'
  477. }).text(simpleSize);
  478. tr.append(td);
  479. // date column
  480. var modifiedColor = Math.round((Math.round((new Date()).getTime() / 1000) - mtime)/60/60/24*5);
  481. td = $('<td></td>').attr({ "class": "date" });
  482. td.append($('<span></span>').attr({
  483. "class": "modified",
  484. "title": formatDate(mtime),
  485. "style": 'color:rgb('+modifiedColor+','+modifiedColor+','+modifiedColor+')'
  486. }).text( relative_modified_date(mtime / 1000) ));
  487. tr.find('.filesize').text(simpleSize);
  488. tr.append(td);
  489. return tr;
  490. },
  491. /**
  492. * Adds an entry to the files array and also into the DOM
  493. * in a sorted manner.
  494. *
  495. * @param fileData map of file attributes
  496. * @param options map of attributes:
  497. * - "updateSummary" true to update the summary after adding (default), false otherwise
  498. * @return new tr element (not appended to the table)
  499. */
  500. add: function(fileData, options) {
  501. var index = -1;
  502. var $tr;
  503. var $rows;
  504. var $insertionPoint;
  505. options = options || {};
  506. // there are three situations to cover:
  507. // 1) insertion point is visible on the current page
  508. // 2) insertion point is on a not visible page (visible after scrolling)
  509. // 3) insertion point is at the end of the list
  510. $rows = this.$fileList.children();
  511. index = this._findInsertionIndex(fileData);
  512. if (index > this.files.length) {
  513. index = this.files.length;
  514. }
  515. else {
  516. $insertionPoint = $rows.eq(index);
  517. }
  518. // is the insertion point visible ?
  519. if ($insertionPoint.length) {
  520. // only render if it will really be inserted
  521. $tr = this._renderRow(fileData, options);
  522. $insertionPoint.before($tr);
  523. }
  524. else {
  525. // if insertion point is after the last visible
  526. // entry, append
  527. if (index === $rows.length) {
  528. $tr = this._renderRow(fileData, options);
  529. this.$fileList.append($tr);
  530. }
  531. }
  532. this.isEmpty = false;
  533. this.files.splice(index, 0, fileData);
  534. if ($tr && options.animate) {
  535. $tr.addClass('appear transparent');
  536. window.setTimeout(function() {
  537. $tr.removeClass('transparent');
  538. });
  539. }
  540. // defaults to true if not defined
  541. if (typeof(options.updateSummary) === 'undefined' || !!options.updateSummary) {
  542. this.fileSummary.add(fileData, true);
  543. this.updateEmptyContent();
  544. }
  545. return $tr;
  546. },
  547. /**
  548. * Creates a new row element based on the given attributes
  549. * and returns it.
  550. *
  551. * @param fileData map of file attributes
  552. * @param options map of attributes:
  553. * - "index" optional index at which to insert the element
  554. * - "updateSummary" true to update the summary after adding (default), false otherwise
  555. * @return new tr element (not appended to the table)
  556. */
  557. _renderRow: function(fileData, options) {
  558. options = options || {};
  559. var type = fileData.type || 'file',
  560. mime = fileData.mimetype,
  561. permissions = parseInt(fileData.permissions, 10) || 0;
  562. if (fileData.isShareMountPoint) {
  563. permissions = permissions | OC.PERMISSION_UPDATE;
  564. }
  565. if (type === 'dir') {
  566. mime = mime || 'httpd/unix-directory';
  567. }
  568. var tr = this._createRow(
  569. fileData,
  570. options
  571. );
  572. var filenameTd = tr.find('td.filename');
  573. // TODO: move dragging to FileActions ?
  574. // enable drag only for deletable files
  575. if (permissions & OC.PERMISSION_DELETE) {
  576. filenameTd.draggable(dragOptions);
  577. }
  578. // allow dropping on folders
  579. if (fileData.type === 'dir') {
  580. filenameTd.droppable(folderDropOptions);
  581. }
  582. if (options.hidden) {
  583. tr.addClass('hidden');
  584. }
  585. // display actions
  586. FileActions.display(filenameTd, false);
  587. if (fileData.isPreviewAvailable) {
  588. // lazy load / newly inserted td ?
  589. if (!fileData.icon) {
  590. Files.lazyLoadPreview(getPathForPreview(fileData.name), mime, function(url) {
  591. filenameTd.css('background-image', 'url(' + url + ')');
  592. }, null, null, fileData.etag);
  593. }
  594. else {
  595. // set the preview URL directly
  596. var urlSpec = {
  597. file: FileList.getCurrentDirectory() + '/' + fileData.name,
  598. c: fileData.etag
  599. };
  600. var previewUrl = Files.generatePreviewUrl(urlSpec);
  601. previewUrl = previewUrl.replace('(', '%28').replace(')', '%29');
  602. filenameTd.css('background-image', 'url(' + previewUrl + ')');
  603. }
  604. }
  605. return tr;
  606. },
  607. /**
  608. * Returns the current directory
  609. * @return current directory
  610. */
  611. getCurrentDirectory: function(){
  612. return $('#dir').val() || '/';
  613. },
  614. /**
  615. * Returns the directory permissions
  616. * @return permission value as integer
  617. */
  618. getDirectoryPermissions: function() {
  619. return parseInt($('#permissions').val(), 10);
  620. },
  621. /**
  622. * @brief Changes the current directory and reload the file list.
  623. * @param targetDir target directory (non URL encoded)
  624. * @param changeUrl false if the URL must not be changed (defaults to true)
  625. * @param {boolean} force set to true to force changing directory
  626. */
  627. changeDirectory: function(targetDir, changeUrl, force) {
  628. var $dir = $('#dir'),
  629. currentDir = $dir.val() || '/';
  630. targetDir = targetDir || '/';
  631. if (!force && currentDir === targetDir) {
  632. return;
  633. }
  634. FileList._setCurrentDir(targetDir, changeUrl);
  635. $('#fileList').trigger(
  636. jQuery.Event('changeDirectory', {
  637. dir: targetDir,
  638. previousDir: currentDir
  639. }
  640. ));
  641. this._selectedFiles = {};
  642. this._selectionSummary.clear();
  643. this.reload();
  644. },
  645. linkTo: function(dir) {
  646. return OC.linkTo('files', 'index.php')+"?dir="+ encodeURIComponent(dir).replace(/%2F/g, '/');
  647. },
  648. /**
  649. * Sets the current directory name and updates the breadcrumb.
  650. * @param targetDir directory to display
  651. * @param changeUrl true to also update the URL, false otherwise (default)
  652. */
  653. _setCurrentDir: function(targetDir, changeUrl) {
  654. var url,
  655. baseDir = OC.basename(targetDir);
  656. if (baseDir !== '') {
  657. FileList.setPageTitle(baseDir);
  658. }
  659. else {
  660. FileList.setPageTitle();
  661. }
  662. $('#dir').val(targetDir);
  663. if (changeUrl !== false) {
  664. if (window.history.pushState && changeUrl !== false) {
  665. url = FileList.linkTo(targetDir);
  666. window.history.pushState({dir: targetDir}, '', url);
  667. }
  668. // use URL hash for IE8
  669. else{
  670. window.location.hash = '?dir='+ encodeURIComponent(targetDir).replace(/%2F/g, '/');
  671. }
  672. }
  673. this.breadcrumb.setDirectory(this.getCurrentDirectory());
  674. },
  675. /**
  676. * Sets the current sorting and refreshes the list
  677. *
  678. * @param sort sort attribute name
  679. * @param direction sort direction, one of "asc" or "desc"
  680. */
  681. setSort: function(sort, direction) {
  682. var comparator = this.Comparators[sort] || this.Comparators.name;
  683. this._sort = sort;
  684. this._sortDirection = (direction === 'desc')?'desc':'asc';
  685. this._sortComparator = comparator;
  686. if (direction === 'desc') {
  687. this._sortComparator = function(fileInfo1, fileInfo2) {
  688. return -comparator(fileInfo1, fileInfo2);
  689. };
  690. }
  691. this.$el.find('thead th .sort-indicator')
  692. .removeClass(this.SORT_INDICATOR_ASC_CLASS + ' ' + this.SORT_INDICATOR_DESC_CLASS);
  693. this.$el.find('thead th.column-' + sort + ' .sort-indicator')
  694. .addClass(direction === 'desc' ? this.SORT_INDICATOR_DESC_CLASS : this.SORT_INDICATOR_ASC_CLASS);
  695. },
  696. /**
  697. * @brief Reloads the file list using ajax call
  698. */
  699. reload: function() {
  700. FileList.showMask();
  701. if (FileList._reloadCall) {
  702. FileList._reloadCall.abort();
  703. }
  704. FileList._reloadCall = $.ajax({
  705. url: Files.getAjaxUrl('list'),
  706. data: {
  707. dir: $('#dir').val(),
  708. sort: FileList._sort,
  709. sortdirection: FileList._sortDirection
  710. },
  711. error: function(result) {
  712. FileList.reloadCallback(result);
  713. },
  714. success: function(result) {
  715. FileList.reloadCallback(result);
  716. }
  717. });
  718. },
  719. reloadCallback: function(result) {
  720. delete this._reloadCall;
  721. this.hideMask();
  722. if (!result || result.status === 'error') {
  723. OC.Notification.show(result.data.message);
  724. return;
  725. }
  726. if (result.status === 404) {
  727. // go back home
  728. this.changeDirectory('/');
  729. return;
  730. }
  731. // aborted ?
  732. if (result.status === 0){
  733. return;
  734. }
  735. // TODO: should rather return upload file size through
  736. // the files list ajax call
  737. Files.updateStorageStatistics(true);
  738. if (result.data.permissions) {
  739. this.setDirectoryPermissions(result.data.permissions);
  740. }
  741. this.setFiles(result.data.files);
  742. },
  743. setDirectoryPermissions: function(permissions) {
  744. var isCreatable = (permissions & OC.PERMISSION_CREATE) !== 0;
  745. $('#permissions').val(permissions);
  746. $('.creatable').toggleClass('hidden', !isCreatable);
  747. $('.notCreatable').toggleClass('hidden', isCreatable);
  748. },
  749. /**
  750. * Shows/hides action buttons
  751. *
  752. * @param show true for enabling, false for disabling
  753. */
  754. showActions: function(show){
  755. $('.actions,#file_action_panel').toggleClass('hidden', !show);
  756. if (show){
  757. // make sure to display according to permissions
  758. var permissions = this.getDirectoryPermissions();
  759. var isCreatable = (permissions & OC.PERMISSION_CREATE) !== 0;
  760. $('.creatable').toggleClass('hidden', !isCreatable);
  761. $('.notCreatable').toggleClass('hidden', isCreatable);
  762. // remove old style breadcrumbs (some apps might create them)
  763. $('#controls .crumb').remove();
  764. // refresh breadcrumbs in case it was replaced by an app
  765. this.breadcrumb.render();
  766. }
  767. else{
  768. $('.creatable, .notCreatable').addClass('hidden');
  769. }
  770. },
  771. /**
  772. * Enables/disables viewer mode.
  773. * In viewer mode, apps can embed themselves under the controls bar.
  774. * In viewer mode, the actions of the file list will be hidden.
  775. * @param show true for enabling, false for disabling
  776. */
  777. setViewerMode: function(show){
  778. this.showActions(!show);
  779. $('#filestable').toggleClass('hidden', show);
  780. },
  781. /**
  782. * Removes a file entry from the list
  783. * @param name name of the file to remove
  784. * @param options optional options as map:
  785. * "updateSummary": true to update the summary (default), false otherwise
  786. * @return deleted element
  787. */
  788. remove: function(name, options){
  789. options = options || {};
  790. var fileEl = FileList.findFileEl(name);
  791. var index = fileEl.index();
  792. if (!fileEl.length) {
  793. return null;
  794. }
  795. if (this._selectedFiles[fileEl.data('id')]) {
  796. // remove from selection first
  797. this._selectFileEl(fileEl, false);
  798. this.updateSelectionSummary();
  799. }
  800. if (fileEl.data('permissions') & OC.PERMISSION_DELETE) {
  801. // file is only draggable when delete permissions are set
  802. fileEl.find('td.filename').draggable('destroy');
  803. }
  804. this.files.splice(index, 1);
  805. fileEl.remove();
  806. // TODO: improve performance on batch update
  807. FileList.isEmpty = !this.files.length;
  808. if (typeof(options.updateSummary) === 'undefined' || !!options.updateSummary) {
  809. FileList.updateEmptyContent();
  810. this.fileSummary.remove({type: fileEl.attr('data-type'), size: fileEl.attr('data-size')}, true);
  811. }
  812. var lastIndex = this.$fileList.children().length;
  813. // if there are less elements visible than one page
  814. // but there are still pending elements in the array,
  815. // then directly append the next page
  816. if (lastIndex < this.files.length && lastIndex < this.pageSize) {
  817. this._nextPage(true);
  818. }
  819. return fileEl;
  820. },
  821. /**
  822. * Finds the index of the row before which the given
  823. * fileData should be inserted, considering the current
  824. * sorting
  825. */
  826. _findInsertionIndex: function(fileData) {
  827. var index = 0;
  828. while (index < this.files.length && this._sortComparator(fileData, this.files[index]) > 0) {
  829. index++;
  830. }
  831. return index;
  832. },
  833. /**
  834. * Moves a file to a given target folder.
  835. *
  836. * @param fileNames array of file names to move
  837. * @param targetPath absolute target path
  838. */
  839. move: function(fileNames, targetPath) {
  840. var self = this;
  841. var dir = this.getCurrentDirectory();
  842. var target = OC.basename(targetPath);
  843. if (!_.isArray(fileNames)) {
  844. fileNames = [fileNames];
  845. }
  846. _.each(fileNames, function(fileName) {
  847. var $tr = self.findFileEl(fileName);
  848. var $td = $tr.children('td.filename');
  849. var oldBackgroundImage = $td.css('background-image');
  850. $td.css('background-image', 'url('+ OC.imagePath('core', 'loading.gif') + ')');
  851. // TODO: improve performance by sending all file names in a single call
  852. $.post(
  853. OC.filePath('files', 'ajax', 'move.php'),
  854. {
  855. dir: dir,
  856. file: fileName,
  857. target: targetPath
  858. },
  859. function(result) {
  860. if (result) {
  861. if (result.status === 'success') {
  862. // if still viewing the same directory
  863. if (self.getCurrentDirectory() === dir) {
  864. // recalculate folder size
  865. var oldFile = self.findFileEl(target);
  866. var newFile = self.findFileEl(fileName);
  867. var oldSize = oldFile.data('size');
  868. var newSize = oldSize + newFile.data('size');
  869. oldFile.data('size', newSize);
  870. oldFile.find('td.filesize').text(OC.Util.humanFileSize(newSize));
  871. // TODO: also update entry in FileList.files
  872. self.remove(fileName);
  873. }
  874. } else {
  875. OC.Notification.hide();
  876. if (result.status === 'error' && result.data.message) {
  877. OC.Notification.show(result.data.message);
  878. }
  879. else {
  880. OC.Notification.show(t('files', 'Error moving file.'));
  881. }
  882. // hide notification after 10 sec
  883. setTimeout(function() {
  884. OC.Notification.hide();
  885. }, 10000);
  886. }
  887. } else {
  888. OC.dialogs.alert(t('files', 'Error moving file'), t('files', 'Error'));
  889. }
  890. $td.css('background-image', oldBackgroundImage);
  891. });
  892. });
  893. },
  894. /**
  895. * Triggers file rename input field for the given file name.
  896. * If the user enters a new name, the file will be renamed.
  897. *
  898. * @param oldname file name of the file to rename
  899. */
  900. rename: function(oldname) {
  901. var tr, td, input, form;
  902. tr = FileList.findFileEl(oldname);
  903. var oldFileInfo = this.files[tr.index()];
  904. tr.data('renaming',true);
  905. td = tr.children('td.filename');
  906. input = $('<input type="text" class="filename"/>').val(oldname);
  907. form = $('<form></form>');
  908. form.append(input);
  909. td.children('a.name').hide();
  910. td.append(form);
  911. input.focus();
  912. //preselect input
  913. var len = input.val().lastIndexOf('.');
  914. if ( len === -1 ||
  915. tr.data('type') === 'dir' ) {
  916. len = input.val().length;
  917. }
  918. input.selectRange(0, len);
  919. var checkInput = function () {
  920. var filename = input.val();
  921. if (filename !== oldname) {
  922. // Files.isFileNameValid(filename) throws an exception itself
  923. Files.isFileNameValid(filename);
  924. if (FileList.inList(filename)) {
  925. throw t('files', '{new_name} already exists', {new_name: filename});
  926. }
  927. }
  928. return true;
  929. };
  930. form.submit(function(event) {
  931. event.stopPropagation();
  932. event.preventDefault();
  933. try {
  934. var newName = input.val();
  935. if (newName !== oldname) {
  936. checkInput();
  937. // mark as loading
  938. td.css('background-image', 'url('+ OC.imagePath('core', 'loading.gif') + ')');
  939. $.ajax({
  940. url: OC.filePath('files','ajax','rename.php'),
  941. data: {
  942. dir : $('#dir').val(),
  943. newname: newName,
  944. file: oldname
  945. },
  946. success: function(result) {
  947. var fileInfo;
  948. if (!result || result.status === 'error') {
  949. OC.dialogs.alert(result.data.message, t('core', 'Could not rename file'));
  950. fileInfo = oldFileInfo;
  951. }
  952. else {
  953. fileInfo = result.data;
  954. }
  955. // reinsert row
  956. FileList.files.splice(tr.index(), 1);
  957. tr.remove();
  958. FileList.add(fileInfo);
  959. }
  960. });
  961. }
  962. input.tipsy('hide');
  963. tr.data('renaming',false);
  964. tr.attr('data-file', newName);
  965. var path = td.children('a.name').attr('href');
  966. // FIXME this will fail if the path contains the filename.
  967. td.children('a.name').attr('href', path.replace(encodeURIComponent(oldname), encodeURIComponent(newName)));
  968. var basename = newName;
  969. if (newName.indexOf('.') > 0 && tr.data('type') !== 'dir') {
  970. basename = newName.substr(0, newName.lastIndexOf('.'));
  971. }
  972. td.find('a.name span.nametext').text(basename);
  973. if (newName.indexOf('.') > 0 && tr.data('type') !== 'dir') {
  974. if ( ! td.find('a.name span.extension').exists() ) {
  975. td.find('a.name span.nametext').append('<span class="extension"></span>');
  976. }
  977. td.find('a.name span.extension').text(newName.substr(newName.lastIndexOf('.')));
  978. }
  979. form.remove();
  980. FileActions.display( tr.find('td.filename'), true);
  981. td.children('a.name').show();
  982. } catch (error) {
  983. input.attr('title', error);
  984. input.tipsy({gravity: 'w', trigger: 'manual'});
  985. input.tipsy('show');
  986. input.addClass('error');
  987. }
  988. return false;
  989. });
  990. input.keyup(function(event) {
  991. // verify filename on typing
  992. try {
  993. checkInput();
  994. input.tipsy('hide');
  995. input.removeClass('error');
  996. } catch (error) {
  997. input.attr('title', error);
  998. input.tipsy({gravity: 'w', trigger: 'manual'});
  999. input.tipsy('show');
  1000. input.addClass('error');
  1001. }
  1002. if (event.keyCode === 27) {
  1003. input.tipsy('hide');
  1004. tr.data('renaming',false);
  1005. form.remove();
  1006. td.children('a.name').show();
  1007. }
  1008. });
  1009. input.click(function(event) {
  1010. event.stopPropagation();
  1011. event.preventDefault();
  1012. });
  1013. input.blur(function() {
  1014. form.trigger('submit');
  1015. });
  1016. },
  1017. inList:function(file) {
  1018. return FileList.findFileEl(file).length;
  1019. },
  1020. /**
  1021. * Delete the given files from the given dir
  1022. * @param files file names list (without path)
  1023. * @param dir directory in which to delete the files, defaults to the current
  1024. * directory
  1025. */
  1026. do_delete:function(files, dir) {
  1027. var params;
  1028. if (files && files.substr) {
  1029. files=[files];
  1030. }
  1031. if (files) {
  1032. for (var i=0; i<files.length; i++) {
  1033. var deleteAction = FileList.findFileEl(files[i]).children("td.date").children(".action.delete");
  1034. deleteAction.removeClass('delete-icon').addClass('progress-icon');
  1035. }
  1036. }
  1037. // Finish any existing actions
  1038. if (FileList.lastAction) {
  1039. FileList.lastAction();
  1040. }
  1041. params = {
  1042. dir: dir || FileList.getCurrentDirectory()
  1043. };
  1044. if (files) {
  1045. params.files = JSON.stringify(files);
  1046. }
  1047. else {
  1048. // no files passed, delete all in current dir
  1049. params.allfiles = true;
  1050. }
  1051. $.post(OC.filePath('files', 'ajax', 'delete.php'),
  1052. params,
  1053. function(result) {
  1054. if (result.status === 'success') {
  1055. if (params.allfiles) {
  1056. FileList.setFiles([]);
  1057. }
  1058. else {
  1059. $.each(files,function(index,file) {
  1060. var fileEl = FileList.remove(file, {updateSummary: false});
  1061. // FIXME: not sure why we need this after the
  1062. // element isn't even in the DOM any more
  1063. fileEl.find('input[type="checkbox"]').prop('checked', false);
  1064. fileEl.removeClass('selected');
  1065. FileList.fileSummary.remove({type: fileEl.attr('data-type'), size: fileEl.attr('data-size')});
  1066. });
  1067. }
  1068. checkTrashStatus();
  1069. FileList.updateEmptyContent();
  1070. FileList.fileSummary.update();
  1071. FileList.updateSelectionSummary();
  1072. Files.updateStorageStatistics();
  1073. } else {
  1074. if (result.status === 'error' && result.data.message) {
  1075. OC.Notification.show(result.data.message);
  1076. }
  1077. else {
  1078. OC.Notification.show(t('files', 'Error deleting file.'));
  1079. }
  1080. // hide notification after 10 sec
  1081. setTimeout(function() {
  1082. OC.Notification.hide();
  1083. }, 10000);
  1084. if (params.allfiles) {
  1085. // reload the page as we don't know what files were deleted
  1086. // and which ones remain
  1087. FileList.reload();
  1088. }
  1089. else {
  1090. $.each(files,function(index,file) {
  1091. var deleteAction = FileList.findFileEl(file).find('.action.delete');
  1092. deleteAction.removeClass('progress-icon').addClass('delete-icon');
  1093. });
  1094. }
  1095. }
  1096. });
  1097. },
  1098. /**
  1099. * Creates the file summary section
  1100. */
  1101. _createSummary: function() {
  1102. var $tr = $('<tr class="summary"></tr>');
  1103. this.$el.find('tfoot').append($tr);
  1104. return new FileSummary($tr);
  1105. },
  1106. updateEmptyContent: function() {
  1107. var permissions = $('#permissions').val();
  1108. var isCreatable = (permissions & OC.PERMISSION_CREATE) !== 0;
  1109. $('#emptycontent').toggleClass('hidden', !isCreatable || !FileList.isEmpty);
  1110. $('#filestable thead th').toggleClass('hidden', FileList.isEmpty);
  1111. },
  1112. /**
  1113. * Shows the loading mask.
  1114. *
  1115. * @see #hideMask
  1116. */
  1117. showMask: function() {
  1118. // in case one was shown before
  1119. var $mask = $('#content .mask');
  1120. if ($mask.exists()) {
  1121. return;
  1122. }
  1123. this.$el.addClass('hidden');
  1124. $mask = $('<div class="mask transparent"></div>');
  1125. $mask.css('background-image', 'url('+ OC.imagePath('core', 'loading.gif') + ')');
  1126. $mask.css('background-repeat', 'no-repeat');
  1127. $('#content').append($mask);
  1128. $mask.removeClass('transparent');
  1129. },
  1130. /**
  1131. * Hide the loading mask.
  1132. * @see #showMask
  1133. */
  1134. hideMask: function() {
  1135. $('#content .mask').remove();
  1136. this.$el.removeClass('hidden');
  1137. },
  1138. scrollTo:function(file) {
  1139. //scroll to and highlight preselected file
  1140. var $scrollToRow = FileList.findFileEl(file);
  1141. if ($scrollToRow.exists()) {
  1142. $scrollToRow.addClass('searchresult');
  1143. $(window).scrollTop($scrollToRow.position().top);
  1144. //remove highlight when hovered over
  1145. $scrollToRow.one('hover', function() {
  1146. $scrollToRow.removeClass('searchresult');
  1147. });
  1148. }
  1149. },
  1150. filter:function(query) {
  1151. $('#fileList tr').each(function(i,e) {
  1152. if ($(e).data('file').toString().toLowerCase().indexOf(query.toLowerCase()) !== -1) {
  1153. $(e).addClass("searchresult");
  1154. } else {
  1155. $(e).removeClass("searchresult");
  1156. }
  1157. });
  1158. //do not use scrollto to prevent removing searchresult css class
  1159. var first = $('#fileList tr.searchresult').first();
  1160. if (first.exists()) {
  1161. $(window).scrollTop(first.position().top);
  1162. }
  1163. },
  1164. unfilter:function() {
  1165. $('#fileList tr.searchresult').each(function(i,e) {
  1166. $(e).removeClass("searchresult");
  1167. });
  1168. },
  1169. /**
  1170. * Update UI based on the current selection
  1171. */
  1172. updateSelectionSummary: function() {
  1173. var summary = this._selectionSummary.summary;
  1174. if (summary.totalFiles === 0 && summary.totalDirs === 0) {
  1175. $('#headerName a.name>span:first').text(t('files','Name'));
  1176. $('#headerSize a>span:first').text(t('files','Size'));
  1177. $('#modified a>span:first').text(t('files','Modified'));
  1178. $('table').removeClass('multiselect');
  1179. $('.selectedActions').addClass('hidden');
  1180. }
  1181. else {
  1182. $('.selectedActions').removeClass('hidden');
  1183. $('#headerSize a>span:first').text(OC.Util.humanFileSize(summary.totalSize));
  1184. var selection = '';
  1185. if (summary.totalDirs > 0) {
  1186. selection += n('files', '%n folder', '%n folders', summary.totalDirs);
  1187. if (summary.totalFiles > 0) {
  1188. selection += ' & ';
  1189. }
  1190. }
  1191. if (summary.totalFiles > 0) {
  1192. selection += n('files', '%n file', '%n files', summary.totalFiles);
  1193. }
  1194. $('#headerName a.name>span:first').text(selection);
  1195. $('#modified a>span:first').text('');
  1196. $('table').addClass('multiselect');
  1197. }
  1198. },
  1199. /**
  1200. * Returns whether all files are selected
  1201. * @return true if all files are selected, false otherwise
  1202. */
  1203. isAllSelected: function() {
  1204. return this.$el.find('#select_all').prop('checked');
  1205. },
  1206. /**
  1207. * Returns the file info of the selected files
  1208. *
  1209. * @return array of file names
  1210. */
  1211. getSelectedFiles: function() {
  1212. return _.values(this._selectedFiles);
  1213. }
  1214. };
  1215. $(document).ready(function() {
  1216. FileList.initialize();
  1217. // handle upload events
  1218. var fileUploadStart = $('#file_upload_start');
  1219. fileUploadStart.on('fileuploaddrop', function(e, data) {
  1220. OC.Upload.log('filelist handle fileuploaddrop', e, data);
  1221. var dropTarget = $(e.originalEvent.target).closest('tr, .crumb');
  1222. if (dropTarget && (dropTarget.data('type') === 'dir' || dropTarget.hasClass('crumb'))) { // drag&drop upload to folder
  1223. // remember as context
  1224. data.context = dropTarget;
  1225. var dir = dropTarget.data('file');
  1226. // if from file list, need to prepend parent dir
  1227. if (dir) {
  1228. var parentDir = $('#dir').val() || '/';
  1229. if (parentDir[parentDir.length - 1] !== '/') {
  1230. parentDir += '/';
  1231. }
  1232. dir = parentDir + dir;
  1233. }
  1234. else{
  1235. // read full path from crumb
  1236. dir = dropTarget.data('dir') || '/';
  1237. }
  1238. // update folder in form
  1239. data.formData = function(form) {
  1240. return [
  1241. {name: 'dir', value: dir},
  1242. {name: 'requesttoken', value: oc_requesttoken},
  1243. {name: 'file_directory', value: data.files[0].relativePath}
  1244. ];
  1245. };
  1246. } else {
  1247. // cancel uploads to current dir if no permission
  1248. var isCreatable = (FileList.getDirectoryPermissions() & OC.PERMISSION_CREATE) !== 0;
  1249. if (!isCreatable) {
  1250. return false;
  1251. }
  1252. }
  1253. });
  1254. fileUploadStart.on('fileuploadadd', function(e, data) {
  1255. OC.Upload.log('filelist handle fileuploadadd', e, data);
  1256. //finish delete if we are uploading a deleted file
  1257. if (FileList.deleteFiles && FileList.deleteFiles.indexOf(data.files[0].name)!==-1) {
  1258. FileList.finishDelete(null, true); //delete file before continuing
  1259. }
  1260. // add ui visualization to existing folder
  1261. if (data.context && data.context.data('type') === 'dir') {
  1262. // add to existing folder
  1263. // update upload counter ui
  1264. var uploadText = data.context.find('.uploadtext');
  1265. var currentUploads = parseInt(uploadText.attr('currentUploads'), 10);
  1266. currentUploads += 1;
  1267. uploadText.attr('currentUploads', currentUploads);
  1268. var translatedText = n('files', 'Uploading %n file', 'Uploading %n files', currentUploads);
  1269. if (currentUploads === 1) {
  1270. var img = OC.imagePath('core', 'loading.gif');
  1271. data.context.find('td.filename').attr('style','background-image:url('+img+')');
  1272. uploadText.text(translatedText);
  1273. uploadText.show();
  1274. } else {
  1275. uploadText.text(translatedText);
  1276. }
  1277. }
  1278. });
  1279. /*
  1280. * when file upload done successfully add row to filelist
  1281. * update counter when uploading to sub folder
  1282. */
  1283. fileUploadStart.on('fileuploaddone', function(e, data) {
  1284. OC.Upload.log('filelist handle fileuploaddone', e, data);
  1285. var response;
  1286. if (typeof data.result === 'string') {
  1287. response = data.result;
  1288. } else {
  1289. // fetch response from iframe
  1290. response = data.result[0].body.innerText;
  1291. }
  1292. var result=$.parseJSON(response);
  1293. if (typeof result[0] !== 'undefined' && result[0].status === 'success') {
  1294. var file = result[0];
  1295. var size = 0;
  1296. if (data.context && data.context.data('type') === 'dir') {
  1297. // update upload counter ui
  1298. var uploadText = data.context.find('.uploadtext');
  1299. var currentUploads = parseInt(uploadText.attr('currentUploads'), 10);
  1300. currentUploads -= 1;
  1301. uploadText.attr('currentUploads', currentUploads);
  1302. var translatedText = n('files', 'Uploading %n file', 'Uploading %n files', currentUploads);
  1303. if (currentUploads === 0) {
  1304. var img = OC.imagePath('core', 'filetypes/folder');
  1305. data.context.find('td.filename').attr('style','background-image:url('+img+')');
  1306. uploadText.text(translatedText);
  1307. uploadText.hide();
  1308. } else {
  1309. uploadText.text(translatedText);
  1310. }
  1311. // update folder size
  1312. size = parseInt(data.context.data('size'), 10);
  1313. size += parseInt(file.size, 10);
  1314. data.context.attr('data-size', size);
  1315. data.context.find('td.filesize').text(humanFileSize(size));
  1316. } else {
  1317. // only append new file if uploaded into the current folder
  1318. if (file.directory !== '/' && file.directory !== FileList.getCurrentDirectory()) {
  1319. var fileDirectory = file.directory.replace('/','').replace(/\/$/, "").split('/');
  1320. if (fileDirectory.length === 1) {
  1321. fileDirectory = fileDirectory[0];
  1322. // Get the directory
  1323. var fd = FileList.findFileEl(fileDirectory);
  1324. if (fd.length === 0) {
  1325. var dir = {
  1326. name: fileDirectory,
  1327. type: 'dir',
  1328. mimetype: 'httpd/unix-directory',
  1329. permissions: file.permissions,
  1330. size: 0,
  1331. id: file.parentId
  1332. };
  1333. FileList.add(dir, {insert: true});
  1334. }
  1335. } else {
  1336. fileDirectory = fileDirectory[0];
  1337. }
  1338. fileDirectory = FileList.findFileEl(fileDirectory);
  1339. // update folder size
  1340. size = parseInt(fileDirectory.attr('data-size'), 10);
  1341. size += parseInt(file.size, 10);
  1342. fileDirectory.attr('data-size', size);
  1343. fileDirectory.find('td.filesize').text(humanFileSize(size));
  1344. return;
  1345. }
  1346. // add as stand-alone row to filelist
  1347. size = t('files', 'Pending');
  1348. if (data.files[0].size>=0) {
  1349. size=data.files[0].size;
  1350. }
  1351. //should the file exist in the list remove it
  1352. FileList.remove(file.name);
  1353. // create new file context
  1354. data.context = FileList.add(file, {animate: true});
  1355. }
  1356. }
  1357. });
  1358. fileUploadStart.on('fileuploadstop', function(e, data) {
  1359. OC.Upload.log('filelist handle fileuploadstop', e, data);
  1360. //if user pressed cancel hide upload chrome
  1361. if (data.errorThrown === 'abort') {
  1362. //cleanup uploading to a dir
  1363. var uploadText = $('tr .uploadtext');
  1364. var img = OC.imagePath('core', 'filetypes/folder');
  1365. uploadText.parents('td.filename').attr('style','background-image:url('+img+')');
  1366. uploadText.fadeOut();
  1367. uploadText.attr('currentUploads', 0);
  1368. }
  1369. });
  1370. fileUploadStart.on('fileuploadfail', function(e, data) {
  1371. OC.Upload.log('filelist handle fileuploadfail', e, data);
  1372. //if user pressed cancel hide upload chrome
  1373. if (data.errorThrown === 'abort') {
  1374. //cleanup uploading to a dir
  1375. var uploadText = $('tr .uploadtext');
  1376. var img = OC.imagePath('core', 'filetypes/folder');
  1377. uploadText.parents('td.filename').attr('style','background-image:url('+img+')');
  1378. uploadText.fadeOut();
  1379. uploadText.attr('currentUploads', 0);
  1380. }
  1381. });
  1382. $('#notification').hide();
  1383. $('#notification:first-child').on('click', '.replace', function() {
  1384. OC.Notification.hide(function() {
  1385. FileList.replace(
  1386. $('#notification > span').attr('data-oldName'),
  1387. $('#notification > span').attr('data-newName'),
  1388. $('#notification > span').attr('data-isNewFile'));
  1389. });
  1390. });
  1391. $('#notification:first-child').on('click', '.suggest', function() {
  1392. var file = $('#notification > span').attr('data-oldName');
  1393. FileList.findFileEl(file).removeClass('hidden');
  1394. OC.Notification.hide();
  1395. });
  1396. $('#notification:first-child').on('click', '.cancel', function() {
  1397. if ($('#notification > span').attr('data-isNewFile')) {
  1398. FileList.deleteCanceled = false;
  1399. FileList.deleteFiles = [$('#notification > span').attr('data-oldName')];
  1400. }
  1401. });
  1402. FileList.useUndo=(window.onbeforeunload)?true:false;
  1403. $(window).bind('beforeunload', function () {
  1404. if (FileList.lastAction) {
  1405. FileList.lastAction();
  1406. }
  1407. });
  1408. $(window).unload(function () {
  1409. $(window).trigger('beforeunload');
  1410. });
  1411. function decodeQuery(query) {
  1412. return query.replace(/\+/g, ' ');
  1413. }
  1414. function parseHashQuery() {
  1415. var hash = window.location.hash,
  1416. pos = hash.indexOf('?');
  1417. if (pos >= 0) {
  1418. return hash.substr(pos + 1);
  1419. }
  1420. return '';
  1421. }
  1422. function parseCurrentDirFromUrl() {
  1423. var query = parseHashQuery(),
  1424. params;
  1425. // try and parse from URL hash first
  1426. if (query) {
  1427. params = OC.parseQueryString(decodeQuery(query));
  1428. }
  1429. // else read from query attributes
  1430. if (!params) {
  1431. params = OC.parseQueryString(decodeQuery(location.search));
  1432. }
  1433. return (params && params.dir) || '/';
  1434. }
  1435. // disable ajax/history API for public app (TODO: until it gets ported)
  1436. // fallback to hashchange when no history support
  1437. if (!window.history.pushState) {
  1438. $(window).on('hashchange', function() {
  1439. FileList.changeDirectory(parseCurrentDirFromUrl(), false);
  1440. });
  1441. }
  1442. window.onpopstate = function(e) {
  1443. var targetDir;
  1444. if (e.state && e.state.dir) {
  1445. targetDir = e.state.dir;
  1446. }
  1447. else{
  1448. // read from URL
  1449. targetDir = parseCurrentDirFromUrl();
  1450. }
  1451. if (targetDir) {
  1452. FileList.changeDirectory(targetDir, false);
  1453. }
  1454. };
  1455. $(window).scroll(function(e) {FileList._onScroll(e);});
  1456. var dir = parseCurrentDirFromUrl();
  1457. // trigger ajax load, deferred to let sub-apps do their overrides first
  1458. setTimeout(function() {
  1459. FileList.changeDirectory(dir, false, true);
  1460. }, 0);
  1461. });
  1462. /**
  1463. * Sort comparators.
  1464. */
  1465. FileList.Comparators = {
  1466. /**
  1467. * Compares two file infos by name, making directories appear
  1468. * first.
  1469. *
  1470. * @param fileInfo1 file info
  1471. * @param fileInfo2 file info
  1472. * @return -1 if the first file must appear before the second one,
  1473. * 0 if they are identify, 1 otherwise.
  1474. */
  1475. name: function(fileInfo1, fileInfo2) {
  1476. if (fileInfo1.type === 'dir' && fileInfo2.type !== 'dir') {
  1477. return -1;
  1478. }
  1479. if (fileInfo1.type !== 'dir' && fileInfo2.type === 'dir') {
  1480. return 1;
  1481. }
  1482. return fileInfo1.name.localeCompare(fileInfo2.name);
  1483. },
  1484. /**
  1485. * Compares two file infos by size.
  1486. *
  1487. * @param fileInfo1 file info
  1488. * @param fileInfo2 file info
  1489. * @return -1 if the first file must appear before the second one,
  1490. * 0 if they are identify, 1 otherwise.
  1491. */
  1492. size: function(fileInfo1, fileInfo2) {
  1493. return fileInfo1.size - fileInfo2.size;
  1494. },
  1495. /**
  1496. * Compares two file infos by timestamp.
  1497. *
  1498. * @param fileInfo1 file info
  1499. * @param fileInfo2 file info
  1500. * @return -1 if the first file must appear before the second one,
  1501. * 0 if they are identify, 1 otherwise.
  1502. */
  1503. mtime: function(fileInfo1, fileInfo2) {
  1504. return fileInfo1.mtime - fileInfo2.mtime;
  1505. }
  1506. };