diff options
Diffstat (limited to 'apps/files/js/filelist.js')
-rw-r--r-- | apps/files/js/filelist.js | 3099 |
1 files changed, 1595 insertions, 1504 deletions
diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js index 73a441368bb..de34d932b11 100644 --- a/apps/files/js/filelist.js +++ b/apps/files/js/filelist.js @@ -8,1639 +8,1730 @@ * */ -/* global OC, t, n, FileList, FileActions, Files, FileSummary, BreadCrumb */ +/* global Files */ /* global dragOptions, folderDropOptions */ -window.FileList = { - SORT_INDICATOR_ASC_CLASS: 'icon-triangle-s', - SORT_INDICATOR_DESC_CLASS: 'icon-triangle-n', - - appName: t('files', 'Files'), - isEmpty: true, - useUndo:true, - $el: $('#filestable'), - $fileList: $('#fileList'), - breadcrumb: null, - - /** - * Instance of FileSummary - */ - fileSummary: null, - initialized: false, - - // number of files per page - pageSize: 20, - +(function() { /** - * Array of files in the current folder. - * The entries are of file data. + * The FileList class manages a file list view. + * A file list view consists of a controls bar and + * a file list table. */ - files: [], - - /** - * Map of file id to file data - */ - _selectedFiles: {}, - - /** - * Summary of selected files. - * Instance of FileSummary. - */ - _selectionSummary: null, + var FileList = function($el) { + this.initialize($el); + }; + FileList.prototype = { + SORT_INDICATOR_ASC_CLASS: 'icon-triangle-s', + SORT_INDICATOR_DESC_CLASS: 'icon-triangle-n', + + appName: t('files', 'Files'), + isEmpty: true, + useUndo:true, + + /** + * Top-level container with controls and file list + */ + $el: null, + + /** + * Files table + */ + $table: null, + + /** + * List of rows (table tbody) + */ + $fileList: null, + + breadcrumb: null, + + /** + * Instance of FileSummary + */ + fileSummary: null, + initialized: false, + + // number of files per page + pageSize: 20, + + /** + * Array of files in the current folder. + * The entries are of file data. + */ + files: [], + + /** + * File actions handler, defaults to OCA.Files.FileActions + */ + fileActions: null, + + /** + * Map of file id to file data + */ + _selectedFiles: {}, + + /** + * Summary of selected files. + * Instance of FileSummary. + */ + _selectionSummary: null, + + /** + * Sort attribute + */ + _sort: 'name', + + /** + * Sort direction: 'asc' or 'desc' + */ + _sortDirection: 'asc', + + /** + * Sort comparator function for the current sort + */ + _sortComparator: null, + + /** + * Current directory + */ + _currentDirectory: null, + + /** + * Initialize the file list and its components + */ + initialize: function($el) { + var self = this; + if (this.initialized) { + return; + } - /** - * Sort attribute - */ - _sort: 'name', + this.$el = $el; + this.$table = $el.find('table:first'); + this.$fileList = $el.find('#fileList'); + this.fileActions = OCA.Files.FileActions; + this.files = []; + this._selectedFiles = {}; + this._selectionSummary = new OCA.Files.FileSummary(); - /** - * Sort direction: 'asc' or 'desc' - */ - _sortDirection: 'asc', + this.fileSummary = this._createSummary(); - /** - * Sort comparator function for the current sort - */ - _sortComparator: null, + this.setSort('name', 'asc'); - /** - * Initialize the file list and its components - */ - initialize: function() { - var self = this; - if (this.initialized) { - return; - } + this.breadcrumb = new OCA.Files.BreadCrumb({ + onClick: _.bind(this._onClickBreadCrumb, this), + onDrop: _.bind(this._onDropOnBreadCrumb, this), + getCrumbUrl: function(part, index) { + return self.linkTo(part.dir); + } + }); - // TODO: FileList should not know about global elements - this.$el = $('#filestable'); - this.$fileList = $('#fileList'); - this.files = []; - this._selectedFiles = {}; - this._selectionSummary = new FileSummary(); + this.$el.find('#controls').prepend(this.breadcrumb.$el); - this.fileSummary = this._createSummary(); + this.$el.find('thead th .columntitle').click(_.bind(this._onClickHeader, this)); - this.setSort('name', 'asc'); + $(window).resize(function() { + // TODO: debounce this ? + var width = $(this).width(); + self.breadcrumb.resize(width, false); + }); - this.breadcrumb = new BreadCrumb({ - onClick: this._onClickBreadCrumb, - onDrop: _.bind(this._onDropOnBreadCrumb, this), - getCrumbUrl: function(part, index) { - return self.linkTo(part.dir); + this.$fileList.on('click','td.filename>a.name', _.bind(this._onClickFile, this)); + this.$fileList.on('change', 'td.filename>input:checkbox', _.bind(this._onClickFileCheckbox, this)); + this.$el.on('urlChanged', _.bind(this._onUrlChanged, this)); + this.$el.find('#select_all').click(_.bind(this._onClickSelectAll, this)); + this.$el.find('.download').click(_.bind(this._onClickDownloadSelected, this)); + this.$el.find('.delete-selected').click(_.bind(this._onClickDeleteSelected, this)); + + this.setupUploadEvents(); + + // FIXME: only do this when visible + $(window).scroll(function(e) {self._onScroll(e);}); + }, + + /** + * Event handler for when the URL changed + */ + _onUrlChanged: function(e) { + if (e && e.dir) { + this.changeDirectory(e.dir, false, true); } - }); - - $('#controls').prepend(this.breadcrumb.$el); - - this.$el.find('thead th .columntitle').click(_.bind(this._onClickHeader, this)); - - $(window).resize(function() { - // TODO: debounce this ? - var width = $(this).width(); - FileList.breadcrumb.resize(width, false); - }); - - this.$fileList.on('click','td.filename>a.name', _.bind(this._onClickFile, this)); - this.$fileList.on('change', 'td.filename>input:checkbox', _.bind(this._onClickFileCheckbox, this)); - this.$el.find('#select_all').click(_.bind(this._onClickSelectAll, this)); - this.$el.find('.download').click(_.bind(this._onClickDownloadSelected, this)); - this.$el.find('.delete-selected').click(_.bind(this._onClickDeleteSelected, this)); - }, - - /** - * Selected/deselects the given file element and updated - * the internal selection cache. - * - * @param $tr single file row element - * @param state true to select, false to deselect - */ - _selectFileEl: function($tr, state) { - var $checkbox = $tr.find('td.filename>input:checkbox'); - var oldData = !!this._selectedFiles[$tr.data('id')]; - var data; - $checkbox.prop('checked', state); - $tr.toggleClass('selected', state); - // already selected ? - if (state === oldData) { - return; - } - data = this.elementToFile($tr); - if (state) { - this._selectedFiles[$tr.data('id')] = data; - this._selectionSummary.add(data); - } - else { - delete this._selectedFiles[$tr.data('id')]; - this._selectionSummary.remove(data); - } - this.$el.find('#select_all').prop('checked', this._selectionSummary.getTotal() === this.files.length); - }, + }, + + /** + * Selected/deselects the given file element and updated + * the internal selection cache. + * + * @param $tr single file row element + * @param state true to select, false to deselect + */ + _selectFileEl: function($tr, state) { + var $checkbox = $tr.find('td.filename>input:checkbox'); + var oldData = !!this._selectedFiles[$tr.data('id')]; + var data; + $checkbox.prop('checked', state); + $tr.toggleClass('selected', state); + // already selected ? + if (state === oldData) { + return; + } + data = this.elementToFile($tr); + if (state) { + this._selectedFiles[$tr.data('id')] = data; + this._selectionSummary.add(data); + } + else { + delete this._selectedFiles[$tr.data('id')]; + this._selectionSummary.remove(data); + } + this.$el.find('#select_all').prop('checked', this._selectionSummary.getTotal() === this.files.length); + }, + + /** + * Event handler for when clicking on files to select them + */ + _onClickFile: function(event) { + var $tr = $(event.target).closest('tr'); + if (event.ctrlKey || event.shiftKey) { + event.preventDefault(); + if (event.shiftKey) { + var $lastTr = $(this._lastChecked); + var lastIndex = $lastTr.index(); + var currentIndex = $tr.index(); + var $rows = this.$fileList.children('tr'); + + // last clicked checkbox below current one ? + if (lastIndex > currentIndex) { + var aux = lastIndex; + lastIndex = currentIndex; + currentIndex = aux; + } - /** - * Event handler for when clicking on files to select them - */ - _onClickFile: function(event) { - var $tr = $(event.target).closest('tr'); - if (event.ctrlKey || event.shiftKey) { - event.preventDefault(); - if (event.shiftKey) { - var $lastTr = $(this._lastChecked); - var lastIndex = $lastTr.index(); - var currentIndex = $tr.index(); - var $rows = this.$fileList.children('tr'); - - // last clicked checkbox below current one ? - if (lastIndex > currentIndex) { - var aux = lastIndex; - lastIndex = currentIndex; - currentIndex = aux; + // auto-select everything in-between + for (var i = lastIndex + 1; i < currentIndex; i++) { + this._selectFileEl($rows.eq(i), true); + } } - - // auto-select everything in-between - for (var i = lastIndex + 1; i < currentIndex; i++) { - this._selectFileEl($rows.eq(i), true); + else { + this._lastChecked = $tr; + } + var $checkbox = $tr.find('td.filename>input:checkbox'); + this._selectFileEl($tr, !$checkbox.prop('checked')); + this.updateSelectionSummary(); + } else { + var filename = $tr.attr('data-file'); + var renaming = $tr.data('renaming'); + if (!renaming) { + this.fileActions.currentFile = $tr.find('td'); + var mime = this.fileActions.getCurrentMimeType(); + var type = this.fileActions.getCurrentType(); + var permissions = this.fileActions.getCurrentPermissions(); + var action = this.fileActions.getDefault(mime,type, permissions); + if (action) { + event.preventDefault(); + action(filename); + } } } - else { - this._lastChecked = $tr; + }, + + /** + * Event handler for when clicking on a file's checkbox + */ + _onClickFileCheckbox: function(e) { + var $tr = $(e.target).closest('tr'); + this._selectFileEl($tr, !$tr.hasClass('selected')); + this._lastChecked = $tr; + this.updateSelectionSummary(); + }, + + /** + * Event handler for when selecting/deselecting all files + */ + _onClickSelectAll: function(e) { + var checked = $(e.target).prop('checked'); + this.$fileList.find('td.filename>input:checkbox').prop('checked', checked) + .closest('tr').toggleClass('selected', checked); + this._selectedFiles = {}; + this._selectionSummary.clear(); + if (checked) { + for (var i = 0; i < this.files.length; i++) { + var fileData = this.files[i]; + this._selectedFiles[fileData.id] = fileData; + this._selectionSummary.add(fileData); + } } - var $checkbox = $tr.find('td.filename>input:checkbox'); - this._selectFileEl($tr, !$checkbox.prop('checked')); this.updateSelectionSummary(); - } else { - var filename = $tr.attr('data-file'); - var renaming = $tr.data('renaming'); - if (!renaming) { - FileActions.currentFile = $tr.find('td'); - var mime=FileActions.getCurrentMimeType(); - var type=FileActions.getCurrentType(); - var permissions = FileActions.getCurrentPermissions(); - var action=FileActions.getDefault(mime,type, permissions); - if (action) { - event.preventDefault(); - action(filename); + }, + + /** + * Event handler for when clicking on "Download" for the selected files + */ + _onClickDownloadSelected: function(event) { + var files; + var dir = this.getCurrentDirectory(); + if (this.isAllSelected()) { + files = OC.basename(dir); + dir = OC.dirname(dir) || '/'; + } + else { + files = _.pluck(this.getSelectedFiles(), 'name'); + } + OC.Notification.show(t('files','Your download is being prepared. This might take some time if the files are big.')); + OC.redirect(this.getDownloadUrl(files, dir)); + return false; + }, + + /** + * Event handler for when clicking on "Delete" for the selected files + */ + _onClickDeleteSelected: function(event) { + var files = null; + if (!this.isAllSelected()) { + files = _.pluck(this.getSelectedFiles(), 'name'); + } + this.do_delete(files); + event.preventDefault(); + return false; + }, + + /** + * Event handler when clicking on a table header + */ + _onClickHeader: function(e) { + var $target = $(e.target); + var sort; + if (!$target.is('a')) { + $target = $target.closest('a'); + } + sort = $target.attr('data-sort'); + if (sort) { + if (this._sort === sort) { + this.setSort(sort, (this._sortDirection === 'desc')?'asc':'desc'); } + else { + this.setSort(sort, 'asc'); + } + this.reload(); } - } - }, + }, + + /** + * Event handler when clicking on a bread crumb + */ + _onClickBreadCrumb: function(e) { + var $el = $(e.target).closest('.crumb'), + $targetDir = $el.data('dir'); + + if ($targetDir !== undefined) { + e.preventDefault(); + this.changeDirectory($targetDir); + } + }, - /** - * Event handler for when clicking on a file's checkbox - */ - _onClickFileCheckbox: function(e) { - var $tr = $(e.target).closest('tr'); - this._selectFileEl($tr, !$tr.hasClass('selected')); - this._lastChecked = $tr; - this.updateSelectionSummary(); - }, + _onScroll: function(e) { + if ($(window).scrollTop() + $(window).height() > $(document).height() - 500) { + this._nextPage(true); + } + }, + + /** + * Event handler when dropping on a breadcrumb + */ + _onDropOnBreadCrumb: function( event, ui ) { + var $target = $(event.target); + if (!$target.is('.crumb')) { + $target = $target.closest('.crumb'); + } + var targetPath = $(event.target).data('dir'); + var dir = this.getCurrentDirectory(); + while (dir.substr(0,1) === '/') {//remove extra leading /'s + dir = dir.substr(1); + } + dir = '/' + dir; + if (dir.substr(-1,1) !== '/') { + dir = dir + '/'; + } + // do nothing if dragged on current dir + if (targetPath === dir || targetPath + '/' === dir) { + return; + } - /** - * Event handler for when selecting/deselecting all files - */ - _onClickSelectAll: function(e) { - var checked = $(e.target).prop('checked'); - this.$fileList.find('td.filename>input:checkbox').prop('checked', checked) - .closest('tr').toggleClass('selected', checked); - this._selectedFiles = {}; - this._selectionSummary.clear(); - if (checked) { - for (var i = 0; i < this.files.length; i++) { - var fileData = this.files[i]; - this._selectedFiles[fileData.id] = fileData; - this._selectionSummary.add(fileData); + var files = this.getSelectedFiles(); + if (files.length === 0) { + // single one selected without checkbox? + files = _.map(ui.helper.find('tr'), this.elementToFile); } - } - this.updateSelectionSummary(); - }, - /** - * Event handler for when clicking on "Download" for the selected files - */ - _onClickDownloadSelected: function(event) { - var files; - var dir = this.getCurrentDirectory(); - if (this.isAllSelected()) { - files = OC.basename(dir); - dir = OC.dirname(dir) || '/'; - } - else { - files = _.pluck(this.getSelectedFiles(), 'name'); - } - OC.Notification.show(t('files','Your download is being prepared. This might take some time if the files are big.')); - OC.redirect(Files.getDownloadUrl(files, dir)); - return false; - }, + this.move(_.pluck(files, 'name'), targetPath); + }, - /** - * Event handler for when clicking on "Delete" for the selected files - */ - _onClickDeleteSelected: function(event) { - var files = null; - if (!FileList.isAllSelected()) { - files = _.pluck(this.getSelectedFiles(), 'name'); - } - this.do_delete(files); - event.preventDefault(); - return false; - }, + /** + * Sets a new page title + */ + setPageTitle: function(title){ + if (title) { + title += ' - '; + } else { + title = ''; + } + title += this.appName; + // Sets the page title with the " - ownCloud" suffix as in templates + window.document.title = title + ' - ' + oc_defaults.title; - /** - * Event handler when clicking on a table header - */ - _onClickHeader: function(e) { - var $target = $(e.target); - var sort; - if (!$target.is('a')) { - $target = $target.closest('a'); - } - sort = $target.attr('data-sort'); - if (sort) { - if (this._sort === sort) { - this.setSort(sort, (this._sortDirection === 'desc')?'asc':'desc'); + return true; + }, + /** + * Returns the tr element for a given file name + * @param fileName file name + */ + findFileEl: function(fileName){ + // use filterAttr to avoid escaping issues + return this.$fileList.find('tr').filterAttr('data-file', fileName); + }, + + /** + * Returns the file data from a given file element. + * @param $el file tr element + * @return file data + */ + elementToFile: function($el){ + $el = $($el); + return { + id: parseInt($el.attr('data-id'), 10), + name: $el.attr('data-file'), + mimetype: $el.attr('data-mime'), + type: $el.attr('data-type'), + size: parseInt($el.attr('data-size'), 10), + etag: $el.attr('data-etag') + }; + }, + + /** + * Appends the next page of files into the table + * @param animate true to animate the new elements + */ + _nextPage: function(animate) { + var index = this.$fileList.children().length, + count = this.pageSize, + tr, + fileData, + newTrs = [], + isAllSelected = this.isAllSelected(); + + if (index >= this.files.length) { + return; } - else { - this.setSort(sort, 'asc'); + + while (count > 0 && index < this.files.length) { + fileData = this.files[index]; + tr = this._renderRow(fileData, {updateSummary: false}); + this.$fileList.append(tr); + if (isAllSelected || this._selectedFiles[fileData.id]) { + tr.addClass('selected'); + tr.find('input:checkbox').prop('checked', true); + } + if (animate) { + tr.addClass('appear transparent'); + newTrs.push(tr); + } + index++; + count--; } - this.reload(); - } - }, - /** - * Event handler when clicking on a bread crumb - */ - _onClickBreadCrumb: function(e) { - var $el = $(e.target).closest('.crumb'), - $targetDir = $el.data('dir'); + if (animate) { + // defer, for animation + window.setTimeout(function() { + for (var i = 0; i < newTrs.length; i++ ) { + newTrs[i].removeClass('transparent'); + } + }, 0); + } + }, - if ($targetDir !== undefined) { - e.preventDefault(); - FileList.changeDirectory($targetDir); - } - }, + /** + * Sets the files to be displayed in the list. + * This operation will re-render the list and update the summary. + * @param filesArray array of file data (map) + */ + setFiles: function(filesArray) { + // detach to make adding multiple rows faster + this.files = filesArray; - _onScroll: function(e) { - if ($(window).scrollTop() + $(window).height() > $(document).height() - 500) { - this._nextPage(true); - } - }, + this.$fileList.detach(); + this.$fileList.empty(); - /** - * Event handler when dropping on a breadcrumb - */ - _onDropOnBreadCrumb: function( event, ui ) { - var $target = $(event.target); - if (!$target.is('.crumb')) { - $target = $target.closest('.crumb'); - } - var targetPath = $(event.target).data('dir'); - var dir = this.getCurrentDirectory(); - while (dir.substr(0,1) === '/') {//remove extra leading /'s - dir = dir.substr(1); - } - dir = '/' + dir; - if (dir.substr(-1,1) !== '/') { - dir = dir + '/'; - } - // do nothing if dragged on current dir - if (targetPath === dir || targetPath + '/' === dir) { - return; - } + // clear "Select all" checkbox + this.$el.find('#select_all').prop('checked', false); - var files = this.getSelectedFiles(); - if (files.length === 0) { - // single one selected without checkbox? - files = _.map(ui.helper.find('tr'), FileList.elementToFile); - } + this.isEmpty = this.files.length === 0; + this._nextPage(); - FileList.move(_.pluck(files, 'name'), targetPath); - }, + this.$el.find('thead').after(this.$fileList); - /** - * Sets a new page title - */ - setPageTitle: function(title){ - if (title) { - title += ' - '; - } else { - title = ''; - } - title += FileList.appName; - // Sets the page title with the " - ownCloud" suffix as in templates - window.document.title = title + ' - ' + oc_defaults.title; + this.updateEmptyContent(); + this.$fileList.trigger(jQuery.Event("fileActionsReady")); + // "Files" might not be loaded in extending apps + if (window.Files) { + Files.setupDragAndDrop(); + } - return true; - }, - /** - * Returns the tr element for a given file name - * @param fileName file name - */ - findFileEl: function(fileName){ - // use filterAttr to avoid escaping issues - return this.$fileList.find('tr').filterAttr('data-file', fileName); - }, + this.fileSummary.calculate(filesArray); - /** - * Returns the file data from a given file element. - * @param $el file tr element - * @return file data - */ - elementToFile: function($el){ - $el = $($el); - return { - id: parseInt($el.attr('data-id'), 10), - name: $el.attr('data-file'), - mimetype: $el.attr('data-mime'), - type: $el.attr('data-type'), - size: parseInt($el.attr('data-size'), 10), - etag: $el.attr('data-etag') - }; - }, + this.updateSelectionSummary(); + $(window).scrollTop(0); + + this.$fileList.trigger(jQuery.Event("updated")); + }, + /** + * Creates a new table row element using the given file data. + * @param fileData map of file attributes + * @param options map of attribute "loading" whether the entry is currently loading + * @return new tr element (not appended to the table) + */ + _createRow: function(fileData, options) { + var td, simpleSize, basename, extension, sizeColor, + icon = OC.Util.replaceSVGIcon(fileData.icon), + name = fileData.name, + type = fileData.type || 'file', + mtime = parseInt(fileData.mtime, 10) || new Date().getTime(), + mime = fileData.mimetype, + linkUrl; + options = options || {}; + + if (type === 'dir') { + mime = mime || 'httpd/unix-directory'; + } - /** - * Appends the next page of files into the table - * @param animate true to animate the new elements - */ - _nextPage: function(animate) { - var index = this.$fileList.children().length, - count = this.pageSize, - tr, - fileData, - newTrs = [], - isAllSelected = this.isAllSelected(); - - if (index >= this.files.length) { - return; - } + // user should always be able to rename a share mount point + var allowRename = 0; + if (fileData.isShareMountPoint) { + allowRename = OC.PERMISSION_UPDATE; + } + + //containing tr + var tr = $('<tr></tr>').attr({ + "data-id" : fileData.id, + "data-type": type, + "data-size": fileData.size, + "data-file": name, + "data-mime": mime, + "data-mtime": mtime, + "data-etag": fileData.etag, + "data-permissions": fileData.permissions | allowRename || this.getDirectoryPermissions() + }); - while (count > 0 && index < this.files.length) { - fileData = this.files[index]; - tr = this._renderRow(fileData, {updateSummary: false}); - this.$fileList.append(tr); - if (isAllSelected || this._selectedFiles[fileData.id]) { - tr.addClass('selected'); - tr.find('input:checkbox').prop('checked', true); + if (type === 'dir') { + // use default folder icon + icon = icon || OC.imagePath('core', 'filetypes/folder'); } - if (animate) { - tr.addClass('appear transparent'); - newTrs.push(tr); + else { + icon = icon || OC.imagePath('core', 'filetypes/file'); } - index++; - count--; - } - if (animate) { - // defer, for animation - window.setTimeout(function() { - for (var i = 0; i < newTrs.length; i++ ) { - newTrs[i].removeClass('transparent'); - } - }, 0); - } - }, + // filename td + td = $('<td></td>').attr({ + "class": "filename", + "style": 'background-image:url(' + icon + '); background-size: 32px;' + }); - /** - * Sets the files to be displayed in the list. - * This operation will re-render the list and update the summary. - * @param filesArray array of file data (map) - */ - setFiles: function(filesArray) { - // detach to make adding multiple rows faster - this.files = filesArray; + // linkUrl + if (type === 'dir') { + linkUrl = this.linkTo(this.getCurrentDirectory() + '/' + name); + } + else { + linkUrl = this.getDownloadUrl(name, this.getCurrentDirectory()); + } + td.append('<input id="select-' + fileData.id + '" type="checkbox" /><label for="select-' + fileData.id + '"></label>'); + var linkElem = $('<a></a>').attr({ + "class": "name", + "href": linkUrl + }); - this.$fileList.detach(); - this.$fileList.empty(); + // from here work on the display name + name = fileData.displayName || name; - // clear "Select all" checkbox - this.$el.find('#select_all').prop('checked', false); + // split extension from filename for non dirs + if (type !== 'dir' && name.indexOf('.') !== -1) { + basename = name.substr(0, name.lastIndexOf('.')); + extension = name.substr(name.lastIndexOf('.')); + } else { + basename = name; + extension = false; + } + var nameSpan=$('<span></span>').addClass('nametext').text(basename); + linkElem.append(nameSpan); + if (extension) { + nameSpan.append($('<span></span>').addClass('extension').text(extension)); + } + // dirs can show the number of uploaded files + if (type === 'dir') { + linkElem.append($('<span></span>').attr({ + 'class': 'uploadtext', + 'currentUploads': 0 + })); + } + td.append(linkElem); + tr.append(td); - this.isEmpty = this.files.length === 0; - this._nextPage(); + // size column + if (typeof(fileData.size) !== 'undefined' && fileData.size >= 0) { + simpleSize = humanFileSize(parseInt(fileData.size, 10)); + sizeColor = Math.round(160-Math.pow((fileData.size/(1024*1024)),2)); + } else { + simpleSize = t('files', 'Pending'); + } - this.$el.find('thead').after(this.$fileList); + td = $('<td></td>').attr({ + "class": "filesize", + "style": 'color:rgb(' + sizeColor + ',' + sizeColor + ',' + sizeColor + ')' + }).text(simpleSize); + tr.append(td); + + // date column + var modifiedColor = Math.round((Math.round((new Date()).getTime() / 1000) - mtime)/60/60/24*5); + td = $('<td></td>').attr({ "class": "date" }); + td.append($('<span></span>').attr({ + "class": "modified", + "title": formatDate(mtime), + "style": 'color:rgb('+modifiedColor+','+modifiedColor+','+modifiedColor+')' + }).text( relative_modified_date(mtime / 1000) )); + tr.find('.filesize').text(simpleSize); + tr.append(td); + return tr; + }, + + /** + * Adds an entry to the files array and also into the DOM + * in a sorted manner. + * + * @param fileData map of file attributes + * @param options map of attributes: + * - "updateSummary" true to update the summary after adding (default), false otherwise + * @return new tr element (not appended to the table) + */ + add: function(fileData, options) { + var index = -1; + var $tr; + var $rows; + var $insertionPoint; + options = options || {}; + + // there are three situations to cover: + // 1) insertion point is visible on the current page + // 2) insertion point is on a not visible page (visible after scrolling) + // 3) insertion point is at the end of the list + + $rows = this.$fileList.children(); + index = this._findInsertionIndex(fileData); + if (index > this.files.length) { + index = this.files.length; + } + else { + $insertionPoint = $rows.eq(index); + } - this.updateEmptyContent(); - this.$fileList.trigger(jQuery.Event("fileActionsReady")); - // "Files" might not be loaded in extending apps - if (window.Files) { - Files.setupDragAndDrop(); - } + // is the insertion point visible ? + if ($insertionPoint.length) { + // only render if it will really be inserted + $tr = this._renderRow(fileData, options); + $insertionPoint.before($tr); + } + else { + // if insertion point is after the last visible + // entry, append + if (index === $rows.length) { + $tr = this._renderRow(fileData, options); + this.$fileList.append($tr); + } + } - this.fileSummary.calculate(filesArray); + this.isEmpty = false; + this.files.splice(index, 0, fileData); - FileList.updateSelectionSummary(); - $(window).scrollTop(0); + if ($tr && options.animate) { + $tr.addClass('appear transparent'); + window.setTimeout(function() { + $tr.removeClass('transparent'); + }); + } - this.$fileList.trigger(jQuery.Event("updated")); - }, - /** - * Creates a new table row element using the given file data. - * @param fileData map of file attributes - * @param options map of attribute "loading" whether the entry is currently loading - * @return new tr element (not appended to the table) - */ - _createRow: function(fileData, options) { - var td, simpleSize, basename, extension, sizeColor, - icon = OC.Util.replaceSVGIcon(fileData.icon), - name = fileData.name, - type = fileData.type || 'file', - mtime = parseInt(fileData.mtime, 10) || new Date().getTime(), - mime = fileData.mimetype, - linkUrl; - options = options || {}; - - if (type === 'dir') { - mime = mime || 'httpd/unix-directory'; - } + // defaults to true if not defined + if (typeof(options.updateSummary) === 'undefined' || !!options.updateSummary) { + this.fileSummary.add(fileData, true); + this.updateEmptyContent(); + } - // user should always be able to rename a share mount point - var allowRename = 0; - if (fileData.isShareMountPoint) { - allowRename = OC.PERMISSION_UPDATE; - } + return $tr; + }, + + /** + * Creates a new row element based on the given attributes + * and returns it. + * + * @param fileData map of file attributes + * @param options map of attributes: + * - "index" optional index at which to insert the element + * - "updateSummary" true to update the summary after adding (default), false otherwise + * @return new tr element (not appended to the table) + */ + _renderRow: function(fileData, options) { + options = options || {}; + var type = fileData.type || 'file', + mime = fileData.mimetype, + permissions = parseInt(fileData.permissions, 10) || 0; + + if (fileData.isShareMountPoint) { + permissions = permissions | OC.PERMISSION_UPDATE; + } - //containing tr - var tr = $('<tr></tr>').attr({ - "data-id" : fileData.id, - "data-type": type, - "data-size": fileData.size, - "data-file": name, - "data-mime": mime, - "data-mtime": mtime, - "data-etag": fileData.etag, - "data-permissions": fileData.permissions | allowRename || this.getDirectoryPermissions() - }); - - if (type === 'dir') { - // use default folder icon - icon = icon || OC.imagePath('core', 'filetypes/folder'); - } - else { - icon = icon || OC.imagePath('core', 'filetypes/file'); - } + if (type === 'dir') { + mime = mime || 'httpd/unix-directory'; + } + var tr = this._createRow( + fileData, + options + ); + var filenameTd = tr.find('td.filename'); + + // TODO: move dragging to FileActions ? + // enable drag only for deletable files + if (permissions & OC.PERMISSION_DELETE) { + filenameTd.draggable(dragOptions); + } + // allow dropping on folders + if (fileData.type === 'dir') { + filenameTd.droppable(folderDropOptions); + } - // filename td - td = $('<td></td>').attr({ - "class": "filename", - "style": 'background-image:url(' + icon + '); background-size: 32px;' - }); + if (options.hidden) { + tr.addClass('hidden'); + } - // linkUrl - if (type === 'dir') { - linkUrl = FileList.linkTo(FileList.getCurrentDirectory() + '/' + name); - } - else { - linkUrl = Files.getDownloadUrl(name, FileList.getCurrentDirectory()); - } - td.append('<input id="select-' + fileData.id + '" type="checkbox" /><label for="select-' + fileData.id + '"></label>'); - var linkElem = $('<a></a>').attr({ - "class": "name", - "href": linkUrl - }); - - // from here work on the display name - name = fileData.displayName || name; - - // split extension from filename for non dirs - if (type !== 'dir' && name.indexOf('.') !== -1) { - basename = name.substr(0, name.lastIndexOf('.')); - extension = name.substr(name.lastIndexOf('.')); - } else { - basename = name; - extension = false; - } - var nameSpan=$('<span></span>').addClass('nametext').text(basename); - linkElem.append(nameSpan); - if (extension) { - nameSpan.append($('<span></span>').addClass('extension').text(extension)); - } - // dirs can show the number of uploaded files - if (type === 'dir') { - linkElem.append($('<span></span>').attr({ - 'class': 'uploadtext', - 'currentUploads': 0 - })); - } - td.append(linkElem); - tr.append(td); - - // size column - if (typeof(fileData.size) !== 'undefined' && fileData.size >= 0) { - simpleSize = humanFileSize(parseInt(fileData.size, 10)); - sizeColor = Math.round(160-Math.pow((fileData.size/(1024*1024)),2)); - } else { - simpleSize = t('files', 'Pending'); - } + // display actions + this.fileActions.display(filenameTd, false); + + if (fileData.isPreviewAvailable) { + // lazy load / newly inserted td ? + if (!fileData.icon) { + this.lazyLoadPreview({ + path: this.getCurrentDirectory() + '/' + fileData.name, + mime: mime, + etag: fileData.etag, + callback: function(url) { + filenameTd.css('background-image', 'url(' + url + ')'); + } + }); + } + else { + // set the preview URL directly + var urlSpec = { + file: this.getCurrentDirectory() + '/' + fileData.name, + c: fileData.etag + }; + var previewUrl = this.generatePreviewUrl(urlSpec); + previewUrl = previewUrl.replace('(', '%28').replace(')', '%29'); + filenameTd.css('background-image', 'url(' + previewUrl + ')'); + } + } + return tr; + }, + /** + * Returns the current directory + * @return current directory + */ + getCurrentDirectory: function(){ + return this._currentDirectory || this.$el.find('#dir').val() || '/'; + }, + /** + * Returns the directory permissions + * @return permission value as integer + */ + getDirectoryPermissions: function() { + return parseInt(this.$el.find('#permissions').val(), 10); + }, + /** + * @brief Changes the current directory and reload the file list. + * @param targetDir target directory (non URL encoded) + * @param changeUrl false if the URL must not be changed (defaults to true) + * @param {boolean} force set to true to force changing directory + */ + changeDirectory: function(targetDir, changeUrl, force) { + var currentDir = this.getCurrentDirectory(); + targetDir = targetDir || '/'; + if (!force && currentDir === targetDir) { + return; + } + this._setCurrentDir(targetDir, changeUrl); + this.reload(); + }, + linkTo: function(dir) { + return OC.linkTo('files', 'index.php')+"?dir="+ encodeURIComponent(dir).replace(/%2F/g, '/'); + }, + + /** + * Sets the file actions handler + */ + setFileActions: function(fileActions) { + this.fileActions = fileActions; + }, + + /** + * Sets the current directory name and updates the breadcrumb. + * @param targetDir directory to display + * @param changeUrl true to also update the URL, false otherwise (default) + */ + _setCurrentDir: function(targetDir, changeUrl) { + var url, + previousDir = this.getCurrentDirectory(), + baseDir = OC.basename(targetDir); + + if (baseDir !== '') { + this.setPageTitle(baseDir); + } + else { + this.setPageTitle(); + } - td = $('<td></td>').attr({ - "class": "filesize", - "style": 'color:rgb(' + sizeColor + ',' + sizeColor + ',' + sizeColor + ')' - }).text(simpleSize); - tr.append(td); - - // date column - var modifiedColor = Math.round((Math.round((new Date()).getTime() / 1000) - mtime)/60/60/24*5); - td = $('<td></td>').attr({ "class": "date" }); - td.append($('<span></span>').attr({ - "class": "modified", - "title": formatDate(mtime), - "style": 'color:rgb('+modifiedColor+','+modifiedColor+','+modifiedColor+')' - }).text( relative_modified_date(mtime / 1000) )); - tr.find('.filesize').text(simpleSize); - tr.append(td); - return tr; - }, + this._currentDirectory = targetDir; - /** - * Adds an entry to the files array and also into the DOM - * in a sorted manner. - * - * @param fileData map of file attributes - * @param options map of attributes: - * - "updateSummary" true to update the summary after adding (default), false otherwise - * @return new tr element (not appended to the table) - */ - add: function(fileData, options) { - var index = -1; - var $tr; - var $rows; - var $insertionPoint; - options = options || {}; - - // there are three situations to cover: - // 1) insertion point is visible on the current page - // 2) insertion point is on a not visible page (visible after scrolling) - // 3) insertion point is at the end of the list - - $rows = this.$fileList.children(); - index = this._findInsertionIndex(fileData); - if (index > this.files.length) { - index = this.files.length; - } - else { - $insertionPoint = $rows.eq(index); - } + // legacy stuff + this.$el.find('#dir').val(targetDir); - // is the insertion point visible ? - if ($insertionPoint.length) { - // only render if it will really be inserted - $tr = this._renderRow(fileData, options); - $insertionPoint.before($tr); - } - else { - // if insertion point is after the last visible - // entry, append - if (index === $rows.length) { - $tr = this._renderRow(fileData, options); - this.$fileList.append($tr); + if (changeUrl !== false) { + this.$el.trigger(jQuery.Event('changeDirectory', { + dir: targetDir, + previousDir: previousDir + })); } - } - - this.isEmpty = false; - this.files.splice(index, 0, fileData); - - if ($tr && options.animate) { - $tr.addClass('appear transparent'); - window.setTimeout(function() { - $tr.removeClass('transparent'); + this.breadcrumb.setDirectory(this.getCurrentDirectory()); + }, + /** + * Sets the current sorting and refreshes the list + * + * @param sort sort attribute name + * @param direction sort direction, one of "asc" or "desc" + */ + setSort: function(sort, direction) { + var comparator = FileList.Comparators[sort] || FileList.Comparators.name; + this._sort = sort; + this._sortDirection = (direction === 'desc')?'desc':'asc'; + this._sortComparator = comparator; + if (direction === 'desc') { + this._sortComparator = function(fileInfo1, fileInfo2) { + return -comparator(fileInfo1, fileInfo2); + }; + } + this.$el.find('thead th .sort-indicator') + .removeClass(this.SORT_INDICATOR_ASC_CLASS + ' ' + this.SORT_INDICATOR_DESC_CLASS); + this.$el.find('thead th.column-' + sort + ' .sort-indicator') + .addClass(direction === 'desc' ? this.SORT_INDICATOR_DESC_CLASS : this.SORT_INDICATOR_ASC_CLASS); + }, + /** + * @brief Reloads the file list using ajax call + */ + reload: function() { + var self = this; + this._selectedFiles = {}; + this._selectionSummary.clear(); + this.$el.find('#select_all').prop('checked', false); + this.showMask(); + if (this._reloadCall) { + this._reloadCall.abort(); + } + this._reloadCall = $.ajax({ + url: this.getAjaxUrl('list'), + data: { + dir : this.getCurrentDirectory(), + sort: this._sort, + sortdirection: this._sortDirection + }, + error: function(result) { + self.reloadCallback(result); + }, + success: function(result) { + self.reloadCallback(result); + } }); - } - - // defaults to true if not defined - if (typeof(options.updateSummary) === 'undefined' || !!options.updateSummary) { - this.fileSummary.add(fileData, true); - this.updateEmptyContent(); - } - - return $tr; - }, - - /** - * Creates a new row element based on the given attributes - * and returns it. - * - * @param fileData map of file attributes - * @param options map of attributes: - * - "index" optional index at which to insert the element - * - "updateSummary" true to update the summary after adding (default), false otherwise - * @return new tr element (not appended to the table) - */ - _renderRow: function(fileData, options) { - options = options || {}; - var type = fileData.type || 'file', - mime = fileData.mimetype, - permissions = parseInt(fileData.permissions, 10) || 0; - - if (fileData.isShareMountPoint) { - permissions = permissions | OC.PERMISSION_UPDATE; - } + }, + reloadCallback: function(result) { + delete this._reloadCall; + this.hideMask(); + + if (!result || result.status === 'error') { + OC.Notification.show(result.data.message); + return; + } - if (type === 'dir') { - mime = mime || 'httpd/unix-directory'; - } - var tr = this._createRow( - fileData, - options - ); - var filenameTd = tr.find('td.filename'); - - // TODO: move dragging to FileActions ? - // enable drag only for deletable files - if (permissions & OC.PERMISSION_DELETE) { - filenameTd.draggable(dragOptions); - } - // allow dropping on folders - if (fileData.type === 'dir') { - filenameTd.droppable(folderDropOptions); - } + if (result.status === 404) { + // go back home + this.changeDirectory('/'); + return; + } + // aborted ? + if (result.status === 0){ + return; + } - if (options.hidden) { - tr.addClass('hidden'); - } + // TODO: should rather return upload file size through + // the files list ajax call + Files.updateStorageStatistics(true); - // display actions - FileActions.display(filenameTd, false); + if (result.data.permissions) { + this.setDirectoryPermissions(result.data.permissions); + } - if (fileData.isPreviewAvailable) { - // lazy load / newly inserted td ? - if (!fileData.icon) { - Files.lazyLoadPreview(getPathForPreview(fileData.name), mime, function(url) { - filenameTd.css('background-image', 'url(' + url + ')'); - }, null, null, fileData.etag); + this.setFiles(result.data.files); + }, + + getAjaxUrl: function(action, params) { + return Files.getAjaxUrl(action, params); + }, + + getDownloadUrl: function(files, dir) { + return Files.getDownloadUrl(files, dir || this.getCurrentDirectory()); + }, + + /** + * Generates a preview URL based on the URL space. + * @param urlSpec map with {x: width, y: height, file: file path} + * @return preview URL + */ + generatePreviewUrl: function(urlSpec) { + urlSpec = urlSpec || {}; + if (!urlSpec.x) { + urlSpec.x = this.$table.data('preview-x') || 36; } - else { - // set the preview URL directly - var urlSpec = { - file: FileList.getCurrentDirectory() + '/' + fileData.name, - c: fileData.etag - }; - var previewUrl = Files.generatePreviewUrl(urlSpec); - previewUrl = previewUrl.replace('(', '%28').replace(')', '%29'); - filenameTd.css('background-image', 'url(' + previewUrl + ')'); + if (!urlSpec.y) { + urlSpec.y = this.$table.data('preview-y') || 36; } - } - return tr; - }, - /** - * Returns the current directory - * @return current directory - */ - getCurrentDirectory: function(){ - return $('#dir').val() || '/'; - }, - /** - * Returns the directory permissions - * @return permission value as integer - */ - getDirectoryPermissions: function() { - return parseInt($('#permissions').val(), 10); - }, - /** - * @brief Changes the current directory and reload the file list. - * @param targetDir target directory (non URL encoded) - * @param changeUrl false if the URL must not be changed (defaults to true) - * @param {boolean} force set to true to force changing directory - */ - changeDirectory: function(targetDir, changeUrl, force) { - var $dir = $('#dir'), - currentDir = $dir.val() || '/'; - targetDir = targetDir || '/'; - if (!force && currentDir === targetDir) { - return; - } - FileList._setCurrentDir(targetDir, changeUrl); - $('#fileList').trigger( - jQuery.Event('changeDirectory', { - dir: targetDir, - previousDir: currentDir - } - )); - this.reload(); - }, - linkTo: function(dir) { - return OC.linkTo('files', 'index.php')+"?dir="+ encodeURIComponent(dir).replace(/%2F/g, '/'); - }, - - /** - * Sets the current directory name and updates the breadcrumb. - * @param targetDir directory to display - * @param changeUrl true to also update the URL, false otherwise (default) - */ - _setCurrentDir: function(targetDir, changeUrl) { - var url, - baseDir = OC.basename(targetDir); + urlSpec.y *= window.devicePixelRatio; + urlSpec.x *= window.devicePixelRatio; + urlSpec.forceIcon = 0; + return OC.generateUrl('/core/preview.png?') + $.param(urlSpec); + }, + + /** + * Lazy load a file's preview. + * + * @param path path of the file + * @param mime mime type + * @param callback callback function to call when the image was loaded + * @param etag file etag (for caching) + */ + lazyLoadPreview : function(options) { + var self = this; + var path = options.path; + var mime = options.mime; + var ready = options.callback; + var etag = options.etag; + + // get mime icon url + OCA.Files.Files.getMimeIcon(mime, function(iconURL) { + var previewURL, + urlSpec = {}; + ready(iconURL); // set mimeicon URL + + urlSpec.file = OCA.Files.Files.fixPath(path); + + if (etag){ + // use etag as cache buster + urlSpec.c = etag; + } + else { + console.warn('OCA.Files.FileList.lazyLoadPreview(): missing etag argument'); + } - if (baseDir !== '') { - FileList.setPageTitle(baseDir); - } - else { - FileList.setPageTitle(); - } + previewURL = self.generatePreviewUrl(urlSpec); + previewURL = previewURL.replace('(', '%28'); + previewURL = previewURL.replace(')', '%29'); + + // preload image to prevent delay + // this will make the browser cache the image + var img = new Image(); + img.onload = function(){ + // if loading the preview image failed (no preview for the mimetype) then img.width will < 5 + if (img.width > 5) { + ready(previewURL); + } + }; + img.src = previewURL; + }); + }, - $('#dir').val(targetDir); - if (changeUrl !== false) { - if (window.history.pushState && changeUrl !== false) { - url = FileList.linkTo(targetDir); - window.history.pushState({dir: targetDir}, '', url); + setDirectoryPermissions: function(permissions) { + var isCreatable = (permissions & OC.PERMISSION_CREATE) !== 0; + this.$el.find('#permissions').val(permissions); + this.$el.find('.creatable').toggleClass('hidden', !isCreatable); + this.$el.find('.notCreatable').toggleClass('hidden', isCreatable); + }, + /** + * Shows/hides action buttons + * + * @param show true for enabling, false for disabling + */ + showActions: function(show){ + this.$el.find('.actions,#file_action_panel').toggleClass('hidden', !show); + if (show){ + // make sure to display according to permissions + var permissions = this.getDirectoryPermissions(); + var isCreatable = (permissions & OC.PERMISSION_CREATE) !== 0; + this.$el.find('.creatable').toggleClass('hidden', !isCreatable); + this.$el.find('.notCreatable').toggleClass('hidden', isCreatable); + // remove old style breadcrumbs (some apps might create them) + this.$el.find('#controls .crumb').remove(); + // refresh breadcrumbs in case it was replaced by an app + this.breadcrumb.render(); } - // use URL hash for IE8 else{ - window.location.hash = '?dir='+ encodeURIComponent(targetDir).replace(/%2F/g, '/'); + this.$el.find('.creatable, .notCreatable').addClass('hidden'); + } + }, + /** + * Enables/disables viewer mode. + * In viewer mode, apps can embed themselves under the controls bar. + * In viewer mode, the actions of the file list will be hidden. + * @param show true for enabling, false for disabling + */ + setViewerMode: function(show){ + this.showActions(!show); + this.$el.find('#filestable').toggleClass('hidden', show); + }, + /** + * Removes a file entry from the list + * @param name name of the file to remove + * @param options optional options as map: + * "updateSummary": true to update the summary (default), false otherwise + * @return deleted element + */ + remove: function(name, options){ + options = options || {}; + var fileEl = this.findFileEl(name); + var index = fileEl.index(); + if (!fileEl.length) { + return null; + } + if (this._selectedFiles[fileEl.data('id')]) { + // remove from selection first + this._selectFileEl(fileEl, false); + this.updateSelectionSummary(); + } + if (fileEl.data('permissions') & OC.PERMISSION_DELETE) { + // file is only draggable when delete permissions are set + fileEl.find('td.filename').draggable('destroy'); + } + this.files.splice(index, 1); + fileEl.remove(); + // TODO: improve performance on batch update + this.isEmpty = !this.files.length; + if (typeof(options.updateSummary) === 'undefined' || !!options.updateSummary) { + this.updateEmptyContent(); + this.fileSummary.remove({type: fileEl.attr('data-type'), size: fileEl.attr('data-size')}, true); } - } - this.breadcrumb.setDirectory(this.getCurrentDirectory()); - }, - /** - * Sets the current sorting and refreshes the list - * - * @param sort sort attribute name - * @param direction sort direction, one of "asc" or "desc" - */ - setSort: function(sort, direction) { - var comparator = this.Comparators[sort] || this.Comparators.name; - this._sort = sort; - this._sortDirection = (direction === 'desc')?'desc':'asc'; - this._sortComparator = comparator; - if (direction === 'desc') { - this._sortComparator = function(fileInfo1, fileInfo2) { - return -comparator(fileInfo1, fileInfo2); - }; - } - this.$el.find('thead th .sort-indicator') - .removeClass(this.SORT_INDICATOR_ASC_CLASS + ' ' + this.SORT_INDICATOR_DESC_CLASS); - this.$el.find('thead th.column-' + sort + ' .sort-indicator') - .addClass(direction === 'desc' ? this.SORT_INDICATOR_DESC_CLASS : this.SORT_INDICATOR_ASC_CLASS); - }, - /** - * @brief Reloads the file list using ajax call - */ - reload: function() { - this._selectedFiles = {}; - this._selectionSummary.clear(); - this.$el.find('#select_all').prop('checked', false); - FileList.showMask(); - if (FileList._reloadCall) { - FileList._reloadCall.abort(); - } - FileList._reloadCall = $.ajax({ - url: Files.getAjaxUrl('list'), - data: { - dir: $('#dir').val(), - sort: FileList._sort, - sortdirection: FileList._sortDirection - }, - error: function(result) { - FileList.reloadCallback(result); - }, - success: function(result) { - FileList.reloadCallback(result); - } - }); - }, - reloadCallback: function(result) { - delete this._reloadCall; - this.hideMask(); - - if (!result || result.status === 'error') { - OC.Notification.show(result.data.message); - return; - } - if (result.status === 404) { - // go back home - this.changeDirectory('/'); - return; - } - // aborted ? - if (result.status === 0){ - return; - } + var lastIndex = this.$fileList.children().length; + // if there are less elements visible than one page + // but there are still pending elements in the array, + // then directly append the next page + if (lastIndex < this.files.length && lastIndex < this.pageSize) { + this._nextPage(true); + } - // TODO: should rather return upload file size through - // the files list ajax call - Files.updateStorageStatistics(true); + return fileEl; + }, + /** + * Finds the index of the row before which the given + * fileData should be inserted, considering the current + * sorting + */ + _findInsertionIndex: function(fileData) { + var index = 0; + while (index < this.files.length && this._sortComparator(fileData, this.files[index]) > 0) { + index++; + } + return index; + }, + /** + * Moves a file to a given target folder. + * + * @param fileNames array of file names to move + * @param targetPath absolute target path + */ + move: function(fileNames, targetPath) { + var self = this; + var dir = this.getCurrentDirectory(); + var target = OC.basename(targetPath); + if (!_.isArray(fileNames)) { + fileNames = [fileNames]; + } + _.each(fileNames, function(fileName) { + var $tr = self.findFileEl(fileName); + var $td = $tr.children('td.filename'); + var oldBackgroundImage = $td.css('background-image'); + $td.css('background-image', 'url('+ OC.imagePath('core', 'loading.gif') + ')'); + // TODO: improve performance by sending all file names in a single call + $.post( + OC.filePath('files', 'ajax', 'move.php'), + { + dir: dir, + file: fileName, + target: targetPath + }, + function(result) { + if (result) { + if (result.status === 'success') { + // if still viewing the same directory + if (self.getCurrentDirectory() === dir) { + // recalculate folder size + var oldFile = self.findFileEl(target); + var newFile = self.findFileEl(fileName); + var oldSize = oldFile.data('size'); + var newSize = oldSize + newFile.data('size'); + oldFile.data('size', newSize); + oldFile.find('td.filesize').text(OC.Util.humanFileSize(newSize)); + + // TODO: also update entry in FileList.files + + self.remove(fileName); + } + } else { + OC.Notification.hide(); + if (result.status === 'error' && result.data.message) { + OC.Notification.show(result.data.message); + } + else { + OC.Notification.show(t('files', 'Error moving file.')); + } + // hide notification after 10 sec + setTimeout(function() { + OC.Notification.hide(); + }, 10000); + } + } else { + OC.dialogs.alert(t('files', 'Error moving file'), t('files', 'Error')); + } + $td.css('background-image', oldBackgroundImage); + }); + }); - if (result.data.permissions) { - this.setDirectoryPermissions(result.data.permissions); - } + }, + + /** + * Triggers file rename input field for the given file name. + * If the user enters a new name, the file will be renamed. + * + * @param oldname file name of the file to rename + */ + rename: function(oldname) { + var self = this; + var tr, td, input, form; + tr = this.findFileEl(oldname); + var oldFileInfo = this.files[tr.index()]; + tr.data('renaming',true); + td = tr.children('td.filename'); + input = $('<input type="text" class="filename"/>').val(oldname); + form = $('<form></form>'); + form.append(input); + td.children('a.name').hide(); + td.append(form); + input.focus(); + //preselect input + var len = input.val().lastIndexOf('.'); + if ( len === -1 || + tr.data('type') === 'dir' ) { + len = input.val().length; + } + input.selectRange(0, len); + var checkInput = function () { + var filename = input.val(); + if (filename !== oldname) { + // Files.isFileNameValid(filename) throws an exception itself + Files.isFileNameValid(filename); + if (self.inList(filename)) { + throw t('files', '{new_name} already exists', {new_name: filename}); + } + } + return true; + }; - this.setFiles(result.data.files); - }, - setDirectoryPermissions: function(permissions) { - var isCreatable = (permissions & OC.PERMISSION_CREATE) !== 0; - $('#permissions').val(permissions); - $('.creatable').toggleClass('hidden', !isCreatable); - $('.notCreatable').toggleClass('hidden', isCreatable); - }, - /** - * Shows/hides action buttons - * - * @param show true for enabling, false for disabling - */ - showActions: function(show){ - $('.actions,#file_action_panel').toggleClass('hidden', !show); - if (show){ - // make sure to display according to permissions - var permissions = this.getDirectoryPermissions(); - var isCreatable = (permissions & OC.PERMISSION_CREATE) !== 0; - $('.creatable').toggleClass('hidden', !isCreatable); - $('.notCreatable').toggleClass('hidden', isCreatable); - // remove old style breadcrumbs (some apps might create them) - $('#controls .crumb').remove(); - // refresh breadcrumbs in case it was replaced by an app - this.breadcrumb.render(); - } - else{ - $('.creatable, .notCreatable').addClass('hidden'); - } - }, - /** - * Enables/disables viewer mode. - * In viewer mode, apps can embed themselves under the controls bar. - * In viewer mode, the actions of the file list will be hidden. - * @param show true for enabling, false for disabling - */ - setViewerMode: function(show){ - this.showActions(!show); - $('#filestable').toggleClass('hidden', show); - }, - /** - * Removes a file entry from the list - * @param name name of the file to remove - * @param options optional options as map: - * "updateSummary": true to update the summary (default), false otherwise - * @return deleted element - */ - remove: function(name, options){ - options = options || {}; - var fileEl = FileList.findFileEl(name); - var index = fileEl.index(); - if (!fileEl.length) { - return null; - } - if (this._selectedFiles[fileEl.data('id')]) { - // remove from selection first - this._selectFileEl(fileEl, false); - this.updateSelectionSummary(); - } - if (fileEl.data('permissions') & OC.PERMISSION_DELETE) { - // file is only draggable when delete permissions are set - fileEl.find('td.filename').draggable('destroy'); - } - this.files.splice(index, 1); - fileEl.remove(); - // TODO: improve performance on batch update - FileList.isEmpty = !this.files.length; - if (typeof(options.updateSummary) === 'undefined' || !!options.updateSummary) { - FileList.updateEmptyContent(); - this.fileSummary.remove({type: fileEl.attr('data-type'), size: fileEl.attr('data-size')}, true); - } + form.submit(function(event) { + event.stopPropagation(); + event.preventDefault(); + try { + var newName = input.val(); + if (newName !== oldname) { + checkInput(); + // mark as loading + td.css('background-image', 'url('+ OC.imagePath('core', 'loading.gif') + ')'); + $.ajax({ + url: OC.filePath('files','ajax','rename.php'), + data: { + dir : self.getCurrentDirectory(), + newname: newName, + file: oldname + }, + success: function(result) { + var fileInfo; + if (!result || result.status === 'error') { + OC.dialogs.alert(result.data.message, t('core', 'Could not rename file')); + fileInfo = oldFileInfo; + } + else { + fileInfo = result.data; + } + // reinsert row + self.files.splice(tr.index(), 1); + tr.remove(); + self.add(fileInfo); + } + }); + } + input.tipsy('hide'); + tr.data('renaming',false); + tr.attr('data-file', newName); + var path = td.children('a.name').attr('href'); + // FIXME this will fail if the path contains the filename. + td.children('a.name').attr('href', path.replace(encodeURIComponent(oldname), encodeURIComponent(newName))); + var basename = newName; + if (newName.indexOf('.') > 0 && tr.data('type') !== 'dir') { + basename = newName.substr(0, newName.lastIndexOf('.')); + } + td.find('a.name span.nametext').text(basename); + if (newName.indexOf('.') > 0 && tr.data('type') !== 'dir') { + if ( ! td.find('a.name span.extension').exists() ) { + td.find('a.name span.nametext').append('<span class="extension"></span>'); + } + td.find('a.name span.extension').text(newName.substr(newName.lastIndexOf('.'))); + } + form.remove(); + self.fileActions.display( tr.find('td.filename'), true); + td.children('a.name').show(); + } catch (error) { + input.attr('title', error); + input.tipsy({gravity: 'w', trigger: 'manual'}); + input.tipsy('show'); + input.addClass('error'); + } + return false; + }); + input.keyup(function(event) { + // verify filename on typing + try { + checkInput(); + input.tipsy('hide'); + input.removeClass('error'); + } catch (error) { + input.attr('title', error); + input.tipsy({gravity: 'w', trigger: 'manual'}); + input.tipsy('show'); + input.addClass('error'); + } + if (event.keyCode === 27) { + input.tipsy('hide'); + tr.data('renaming',false); + form.remove(); + td.children('a.name').show(); + } + }); + input.click(function(event) { + event.stopPropagation(); + event.preventDefault(); + }); + input.blur(function() { + form.trigger('submit'); + }); + }, + inList:function(file) { + return this.findFileEl(file).length; + }, + /** + * Delete the given files from the given dir + * @param files file names list (without path) + * @param dir directory in which to delete the files, defaults to the current + * directory + */ + do_delete:function(files, dir) { + var self = this; + var params; + if (files && files.substr) { + files=[files]; + } + if (files) { + for (var i=0; i<files.length; i++) { + var deleteAction = this.findFileEl(files[i]).children("td.date").children(".action.delete"); + deleteAction.removeClass('delete-icon').addClass('progress-icon'); + } + } + // Finish any existing actions + if (this.lastAction) { + this.lastAction(); + } - var lastIndex = this.$fileList.children().length; - // if there are less elements visible than one page - // but there are still pending elements in the array, - // then directly append the next page - if (lastIndex < this.files.length && lastIndex < this.pageSize) { - this._nextPage(true); - } + params = { + dir: dir || this.getCurrentDirectory() + }; + if (files) { + params.files = JSON.stringify(files); + } + else { + // no files passed, delete all in current dir + params.allfiles = true; + // show spinner for all files + this.$fileList.find('tr>td.date .action.delete').removeClass('delete-icon').addClass('progress-icon'); + } - return fileEl; - }, - /** - * Finds the index of the row before which the given - * fileData should be inserted, considering the current - * sorting - */ - _findInsertionIndex: function(fileData) { - var index = 0; - while (index < this.files.length && this._sortComparator(fileData, this.files[index]) > 0) { - index++; - } - return index; - }, - /** - * Moves a file to a given target folder. - * - * @param fileNames array of file names to move - * @param targetPath absolute target path - */ - move: function(fileNames, targetPath) { - var self = this; - var dir = this.getCurrentDirectory(); - var target = OC.basename(targetPath); - if (!_.isArray(fileNames)) { - fileNames = [fileNames]; - } - _.each(fileNames, function(fileName) { - var $tr = self.findFileEl(fileName); - var $td = $tr.children('td.filename'); - var oldBackgroundImage = $td.css('background-image'); - $td.css('background-image', 'url('+ OC.imagePath('core', 'loading.gif') + ')'); - // TODO: improve performance by sending all file names in a single call - $.post( - OC.filePath('files', 'ajax', 'move.php'), - { - dir: dir, - file: fileName, - target: targetPath - }, - function(result) { - if (result) { + $.post(OC.filePath('files', 'ajax', 'delete.php'), + params, + function(result) { if (result.status === 'success') { - // if still viewing the same directory - if (self.getCurrentDirectory() === dir) { - // recalculate folder size - var oldFile = self.findFileEl(target); - var newFile = self.findFileEl(fileName); - var oldSize = oldFile.data('size'); - var newSize = oldSize + newFile.data('size'); - oldFile.data('size', newSize); - oldFile.find('td.filesize').text(OC.Util.humanFileSize(newSize)); - - // TODO: also update entry in FileList.files - - self.remove(fileName); + if (params.allfiles) { + self.setFiles([]); + } + else { + $.each(files,function(index,file) { + var fileEl = self.remove(file, {updateSummary: false}); + // FIXME: not sure why we need this after the + // element isn't even in the DOM any more + fileEl.find('input[type="checkbox"]').prop('checked', false); + fileEl.removeClass('selected'); + self.fileSummary.remove({type: fileEl.attr('data-type'), size: fileEl.attr('data-size')}); + }); } + // TODO: this info should be returned by the ajax call! + self.updateEmptyContent(); + self.fileSummary.update(); + self.updateSelectionSummary(); + Files.updateStorageStatistics(); } else { - OC.Notification.hide(); if (result.status === 'error' && result.data.message) { OC.Notification.show(result.data.message); } else { - OC.Notification.show(t('files', 'Error moving file.')); + OC.Notification.show(t('files', 'Error deleting file.')); } // hide notification after 10 sec setTimeout(function() { OC.Notification.hide(); }, 10000); - } - } else { - OC.dialogs.alert(t('files', 'Error moving file'), t('files', 'Error')); - } - $td.css('background-image', oldBackgroundImage); - }); - }); - - }, - - /** - * Triggers file rename input field for the given file name. - * If the user enters a new name, the file will be renamed. - * - * @param oldname file name of the file to rename - */ - rename: function(oldname) { - var tr, td, input, form; - tr = FileList.findFileEl(oldname); - var oldFileInfo = this.files[tr.index()]; - tr.data('renaming',true); - td = tr.children('td.filename'); - input = $('<input type="text" class="filename"/>').val(oldname); - form = $('<form></form>'); - form.append(input); - td.children('a.name').hide(); - td.append(form); - input.focus(); - //preselect input - var len = input.val().lastIndexOf('.'); - if ( len === -1 || - tr.data('type') === 'dir' ) { - len = input.val().length; - } - input.selectRange(0, len); - var checkInput = function () { - var filename = input.val(); - if (filename !== oldname) { - // Files.isFileNameValid(filename) throws an exception itself - Files.isFileNameValid(filename); - if (FileList.inList(filename)) { - throw t('files', '{new_name} already exists', {new_name: filename}); - } - } - return true; - }; - - form.submit(function(event) { - event.stopPropagation(); - event.preventDefault(); - try { - var newName = input.val(); - if (newName !== oldname) { - checkInput(); - // mark as loading - td.css('background-image', 'url('+ OC.imagePath('core', 'loading.gif') + ')'); - $.ajax({ - url: OC.filePath('files','ajax','rename.php'), - data: { - dir : $('#dir').val(), - newname: newName, - file: oldname - }, - success: function(result) { - var fileInfo; - if (!result || result.status === 'error') { - OC.dialogs.alert(result.data.message, t('core', 'Could not rename file')); - fileInfo = oldFileInfo; + if (params.allfiles) { + // reload the page as we don't know what files were deleted + // and which ones remain + self.reload(); } else { - fileInfo = result.data; + $.each(files,function(index,file) { + var deleteAction = self.findFileEl(file).find('.action.delete'); + deleteAction.removeClass('progress-icon').addClass('delete-icon'); + }); } - // reinsert row - FileList.files.splice(tr.index(), 1); - tr.remove(); - FileList.add(fileInfo); } }); - } - input.tipsy('hide'); - tr.data('renaming',false); - tr.attr('data-file', newName); - var path = td.children('a.name').attr('href'); - // FIXME this will fail if the path contains the filename. - td.children('a.name').attr('href', path.replace(encodeURIComponent(oldname), encodeURIComponent(newName))); - var basename = newName; - if (newName.indexOf('.') > 0 && tr.data('type') !== 'dir') { - basename = newName.substr(0, newName.lastIndexOf('.')); - } - td.find('a.name span.nametext').text(basename); - if (newName.indexOf('.') > 0 && tr.data('type') !== 'dir') { - if ( ! td.find('a.name span.extension').exists() ) { - td.find('a.name span.nametext').append('<span class="extension"></span>'); - } - td.find('a.name span.extension').text(newName.substr(newName.lastIndexOf('.'))); - } - form.remove(); - FileActions.display( tr.find('td.filename'), true); - td.children('a.name').show(); - } catch (error) { - input.attr('title', error); - input.tipsy({gravity: 'w', trigger: 'manual'}); - input.tipsy('show'); - input.addClass('error'); - } - return false; - }); - input.keyup(function(event) { - // verify filename on typing - try { - checkInput(); - input.tipsy('hide'); - input.removeClass('error'); - } catch (error) { - input.attr('title', error); - input.tipsy({gravity: 'w', trigger: 'manual'}); - input.tipsy('show'); - input.addClass('error'); - } - if (event.keyCode === 27) { - input.tipsy('hide'); - tr.data('renaming',false); - form.remove(); - td.children('a.name').show(); - } - }); - input.click(function(event) { - event.stopPropagation(); - event.preventDefault(); - }); - input.blur(function() { - form.trigger('submit'); - }); - }, - inList:function(file) { - return FileList.findFileEl(file).length; - }, - /** - * Delete the given files from the given dir - * @param files file names list (without path) - * @param dir directory in which to delete the files, defaults to the current - * directory - */ - do_delete:function(files, dir) { - var params; - if (files && files.substr) { - files=[files]; - } - if (files) { - for (var i=0; i<files.length; i++) { - var deleteAction = FileList.findFileEl(files[i]).children("td.date").children(".action.delete"); - deleteAction.removeClass('delete-icon').addClass('progress-icon'); + }, + /** + * Creates the file summary section + */ + _createSummary: function() { + var $tr = $('<tr class="summary"></tr>'); + this.$el.find('tfoot').append($tr); + + return new OCA.Files.FileSummary($tr); + }, + updateEmptyContent: function() { + var permissions = this.getDirectoryPermissions(); + var isCreatable = (permissions & OC.PERMISSION_CREATE) !== 0; + this.$el.find('#emptycontent').toggleClass('hidden', !isCreatable || !this.isEmpty); + this.$el.find('#filestable thead th').toggleClass('hidden', this.isEmpty); + }, + /** + * Shows the loading mask. + * + * @see #hideMask + */ + showMask: function() { + // in case one was shown before + var $mask = this.$el.find('.mask'); + if ($mask.exists()) { + return; } - } - // Finish any existing actions - if (FileList.lastAction) { - FileList.lastAction(); - } - params = { - dir: dir || FileList.getCurrentDirectory() - }; - if (files) { - params.files = JSON.stringify(files); - } - else { - // no files passed, delete all in current dir - params.allfiles = true; - // show spinner for all files - this.$fileList.find('tr>td.date .action.delete').removeClass('delete-icon').addClass('progress-icon'); - } - - $.post(OC.filePath('files', 'ajax', 'delete.php'), - params, - function(result) { - if (result.status === 'success') { - if (params.allfiles) { - FileList.setFiles([]); - } - else { - $.each(files,function(index,file) { - var fileEl = FileList.remove(file, {updateSummary: false}); - // FIXME: not sure why we need this after the - // element isn't even in the DOM any more - fileEl.find('input[type="checkbox"]').prop('checked', false); - fileEl.removeClass('selected'); - FileList.fileSummary.remove({type: fileEl.attr('data-type'), size: fileEl.attr('data-size')}); - }); - } - // TODO: this info should be returned by the ajax call! - checkTrashStatus(); - FileList.updateEmptyContent(); - FileList.fileSummary.update(); - FileList.updateSelectionSummary(); - Files.updateStorageStatistics(); - } else { - if (result.status === 'error' && result.data.message) { - OC.Notification.show(result.data.message); - } - else { - OC.Notification.show(t('files', 'Error deleting file.')); - } - // hide notification after 10 sec - setTimeout(function() { - OC.Notification.hide(); - }, 10000); - if (params.allfiles) { - // reload the page as we don't know what files were deleted - // and which ones remain - FileList.reload(); - } - else { - $.each(files,function(index,file) { - var deleteAction = FileList.findFileEl(file).find('.action.delete'); - deleteAction.removeClass('progress-icon').addClass('delete-icon'); - }); - } - } + this.$table.addClass('hidden'); + + $mask = $('<div class="mask transparent"></div>'); + + $mask.css('background-image', 'url('+ OC.imagePath('core', 'loading.gif') + ')'); + $mask.css('background-repeat', 'no-repeat'); + this.$el.append($mask); + + $mask.removeClass('transparent'); + }, + /** + * Hide the loading mask. + * @see #showMask + */ + hideMask: function() { + this.$el.find('.mask').remove(); + this.$table.removeClass('hidden'); + }, + scrollTo:function(file) { + //scroll to and highlight preselected file + var $scrollToRow = this.findFileEl(file); + if ($scrollToRow.exists()) { + $scrollToRow.addClass('searchresult'); + $(window).scrollTop($scrollToRow.position().top); + //remove highlight when hovered over + $scrollToRow.one('hover', function() { + $scrollToRow.removeClass('searchresult'); }); - }, - /** - * Creates the file summary section - */ - _createSummary: function() { - var $tr = $('<tr class="summary"></tr>'); - this.$el.find('tfoot').append($tr); - - return new FileSummary($tr); - }, - updateEmptyContent: function() { - var permissions = $('#permissions').val(); - var isCreatable = (permissions & OC.PERMISSION_CREATE) !== 0; - $('#emptycontent').toggleClass('hidden', !isCreatable || !FileList.isEmpty); - $('#filestable thead th').toggleClass('hidden', FileList.isEmpty); - }, - /** - * Shows the loading mask. - * - * @see #hideMask - */ - showMask: function() { - // in case one was shown before - var $mask = $('#content .mask'); - if ($mask.exists()) { - return; - } - - this.$el.addClass('hidden'); - - $mask = $('<div class="mask transparent"></div>'); - - $mask.css('background-image', 'url('+ OC.imagePath('core', 'loading.gif') + ')'); - $mask.css('background-repeat', 'no-repeat'); - $('#content').append($mask); - - $mask.removeClass('transparent'); - }, - /** - * Hide the loading mask. - * @see #showMask - */ - hideMask: function() { - $('#content .mask').remove(); - this.$el.removeClass('hidden'); - }, - scrollTo:function(file) { - //scroll to and highlight preselected file - var $scrollToRow = FileList.findFileEl(file); - if ($scrollToRow.exists()) { - $scrollToRow.addClass('searchresult'); - $(window).scrollTop($scrollToRow.position().top); - //remove highlight when hovered over - $scrollToRow.one('hover', function() { - $scrollToRow.removeClass('searchresult'); + } + }, + filter:function(query) { + this.$fileList.find('tr').each(function(i,e) { + if ($(e).data('file').toString().toLowerCase().indexOf(query.toLowerCase()) !== -1) { + $(e).addClass("searchresult"); + } else { + $(e).removeClass("searchresult"); + } }); - } - }, - filter:function(query) { - $('#fileList tr').each(function(i,e) { - if ($(e).data('file').toString().toLowerCase().indexOf(query.toLowerCase()) !== -1) { - $(e).addClass("searchresult"); - } else { + //do not use scrollto to prevent removing searchresult css class + var first = this.$fileList.find('tr.searchresult').first(); + if (first.exists()) { + $(window).scrollTop(first.position().top); + } + }, + unfilter:function() { + this.$fileList.find('tr.searchresult').each(function(i,e) { $(e).removeClass("searchresult"); + }); + }, + /** + * Update UI based on the current selection + */ + updateSelectionSummary: function() { + var summary = this._selectionSummary.summary; + if (summary.totalFiles === 0 && summary.totalDirs === 0) { + this.$el.find('#headerName a.name>span:first').text(t('files','Name')); + this.$el.find('#headerSize a>span:first').text(t('files','Size')); + this.$el.find('#modified a>span:first').text(t('files','Modified')); + this.$el.find('table').removeClass('multiselect'); + this.$el.find('.selectedActions').addClass('hidden'); } - }); - //do not use scrollto to prevent removing searchresult css class - var first = $('#fileList tr.searchresult').first(); - if (first.exists()) { - $(window).scrollTop(first.position().top); - } - }, - unfilter:function() { - $('#fileList tr.searchresult').each(function(i,e) { - $(e).removeClass("searchresult"); - }); - }, - /** - * Update UI based on the current selection - */ - updateSelectionSummary: function() { - var summary = this._selectionSummary.summary; - if (summary.totalFiles === 0 && summary.totalDirs === 0) { - $('#headerName a.name>span:first').text(t('files','Name')); - $('#headerSize a>span:first').text(t('files','Size')); - $('#modified a>span:first').text(t('files','Modified')); - $('table').removeClass('multiselect'); - $('.selectedActions').addClass('hidden'); - } - else { - $('.selectedActions').removeClass('hidden'); - $('#headerSize a>span:first').text(OC.Util.humanFileSize(summary.totalSize)); - var selection = ''; - if (summary.totalDirs > 0) { - selection += n('files', '%n folder', '%n folders', summary.totalDirs); + else { + this.$el.find('.selectedActions').removeClass('hidden'); + this.$el.find('#headerSize a>span:first').text(OC.Util.humanFileSize(summary.totalSize)); + var selection = ''; + if (summary.totalDirs > 0) { + selection += n('files', '%n folder', '%n folders', summary.totalDirs); + if (summary.totalFiles > 0) { + selection += ' & '; + } + } if (summary.totalFiles > 0) { - selection += ' & '; + selection += n('files', '%n file', '%n files', summary.totalFiles); } + this.$el.find('#headerName a.name>span:first').text(selection); + this.$el.find('#modified a>span:first').text(''); + this.$el.find('table').addClass('multiselect'); } - if (summary.totalFiles > 0) { - selection += n('files', '%n file', '%n files', summary.totalFiles); + }, + + /** + * Returns whether all files are selected + * @return true if all files are selected, false otherwise + */ + isAllSelected: function() { + return this.$el.find('#select_all').prop('checked'); + }, + + /** + * Returns the file info of the selected files + * + * @return array of file names + */ + getSelectedFiles: function() { + return _.values(this._selectedFiles); + }, + + getUniqueName: function(name) { + if (this.findFileEl(name).exists()) { + var numMatch; + var parts=name.split('.'); + var extension = ""; + if (parts.length > 1) { + extension=parts.pop(); + } + var base=parts.join('.'); + numMatch=base.match(/\((\d+)\)/); + var num=2; + if (numMatch && numMatch.length>0) { + num=parseInt(numMatch[numMatch.length-1])+1; + base=base.split('('); + base.pop(); + base=$.trim(base.join('(')); + } + name=base+' ('+num+')'; + if (extension) { + name = name+'.'+extension; + } + // FIXME: ugly recursion + return this.getUniqueName(name); } - $('#headerName a.name>span:first').text(selection); - $('#modified a>span:first').text(''); - $('table').addClass('multiselect'); - } - }, - - /** - * Returns whether all files are selected - * @return true if all files are selected, false otherwise - */ - isAllSelected: function() { - return this.$el.find('#select_all').prop('checked'); - }, - - /** - * Returns the file info of the selected files - * - * @return array of file names - */ - getSelectedFiles: function() { - return _.values(this._selectedFiles); - } -}; - -$(document).ready(function() { - FileList.initialize(); - - // handle upload events - var fileUploadStart = $('#file_upload_start'); + return name; + }, + + setupUploadEvents: function() { + var self = this; + + // handle upload events + var fileUploadStart = this.$el.find('#file_upload_start'); + + fileUploadStart.on('fileuploaddrop', function(e, data) { + OC.Upload.log('filelist handle fileuploaddrop', e, data); + + var dropTarget = $(e.originalEvent.target).closest('tr, .crumb'); + if (dropTarget && (dropTarget.data('type') === 'dir' || dropTarget.hasClass('crumb'))) { // drag&drop upload to folder + + // remember as context + data.context = dropTarget; + + var dir = dropTarget.data('file'); + // if from file list, need to prepend parent dir + if (dir) { + var parentDir = self.getCurrentDirectory(); + if (parentDir[parentDir.length - 1] !== '/') { + parentDir += '/'; + } + dir = parentDir + dir; + } + else{ + // read full path from crumb + dir = dropTarget.data('dir') || '/'; + } - fileUploadStart.on('fileuploaddrop', function(e, data) { - OC.Upload.log('filelist handle fileuploaddrop', e, data); + // update folder in form + data.formData = function(form) { + return [ + {name: 'dir', value: dir}, + {name: 'requesttoken', value: oc_requesttoken}, + {name: 'file_directory', value: data.files[0].relativePath} + ]; + }; + } else { + // cancel uploads to current dir if no permission + var isCreatable = (this.getDirectoryPermissions() & OC.PERMISSION_CREATE) !== 0; + if (!isCreatable) { + return false; + } + } + }); + fileUploadStart.on('fileuploadadd', function(e, data) { + OC.Upload.log('filelist handle fileuploadadd', e, data); - var dropTarget = $(e.originalEvent.target).closest('tr, .crumb'); - if (dropTarget && (dropTarget.data('type') === 'dir' || dropTarget.hasClass('crumb'))) { // drag&drop upload to folder + //finish delete if we are uploading a deleted file + if (self.deleteFiles && self.deleteFiles.indexOf(data.files[0].name)!==-1) { + self.finishDelete(null, true); //delete file before continuing + } - // remember as context - data.context = dropTarget; + // add ui visualization to existing folder + if (data.context && data.context.data('type') === 'dir') { + // add to existing folder + + // update upload counter ui + var uploadText = data.context.find('.uploadtext'); + var currentUploads = parseInt(uploadText.attr('currentUploads'), 10); + currentUploads += 1; + uploadText.attr('currentUploads', currentUploads); + + var translatedText = n('files', 'Uploading %n file', 'Uploading %n files', currentUploads); + if (currentUploads === 1) { + var img = OC.imagePath('core', 'loading.gif'); + data.context.find('td.filename').attr('style','background-image:url('+img+')'); + uploadText.text(translatedText); + uploadText.show(); + } else { + uploadText.text(translatedText); + } + } - var dir = dropTarget.data('file'); - // if from file list, need to prepend parent dir - if (dir) { - var parentDir = $('#dir').val() || '/'; - if (parentDir[parentDir.length - 1] !== '/') { - parentDir += '/'; + }); + /* + * when file upload done successfully add row to filelist + * update counter when uploading to sub folder + */ + fileUploadStart.on('fileuploaddone', function(e, data) { + OC.Upload.log('filelist handle fileuploaddone', e, data); + + var response; + if (typeof data.result === 'string') { + response = data.result; + } else { + // fetch response from iframe + response = data.result[0].body.innerText; } - dir = parentDir + dir; - } - else{ - // read full path from crumb - dir = dropTarget.data('dir') || '/'; - } + var result=$.parseJSON(response); + + if (typeof result[0] !== 'undefined' && result[0].status === 'success') { + var file = result[0]; + var size = 0; + + if (data.context && data.context.data('type') === 'dir') { + + // update upload counter ui + var uploadText = data.context.find('.uploadtext'); + var currentUploads = parseInt(uploadText.attr('currentUploads'), 10); + currentUploads -= 1; + uploadText.attr('currentUploads', currentUploads); + var translatedText = n('files', 'Uploading %n file', 'Uploading %n files', currentUploads); + if (currentUploads === 0) { + var img = OC.imagePath('core', 'filetypes/folder'); + data.context.find('td.filename').attr('style','background-image:url('+img+')'); + uploadText.text(translatedText); + uploadText.hide(); + } else { + uploadText.text(translatedText); + } - // update folder in form - data.formData = function(form) { - return [ - {name: 'dir', value: dir}, - {name: 'requesttoken', value: oc_requesttoken}, - {name: 'file_directory', value: data.files[0].relativePath} - ]; - }; - } else { - // cancel uploads to current dir if no permission - var isCreatable = (FileList.getDirectoryPermissions() & OC.PERMISSION_CREATE) !== 0; - if (!isCreatable) { - return false; - } - } - }); - fileUploadStart.on('fileuploadadd', function(e, data) { - OC.Upload.log('filelist handle fileuploadadd', e, data); + // update folder size + size = parseInt(data.context.data('size'), 10); + size += parseInt(file.size, 10); + data.context.attr('data-size', size); + data.context.find('td.filesize').text(humanFileSize(size)); + } else { + // only append new file if uploaded into the current folder + if (file.directory !== '/' && file.directory !== self.getCurrentDirectory()) { + + var fileDirectory = file.directory.replace('/','').replace(/\/$/, "").split('/'); + + if (fileDirectory.length === 1) { + fileDirectory = fileDirectory[0]; + + // Get the directory + var fd = self.findFileEl(fileDirectory); + if (fd.length === 0) { + var dir = { + name: fileDirectory, + type: 'dir', + mimetype: 'httpd/unix-directory', + permissions: file.permissions, + size: 0, + id: file.parentId + }; + self.add(dir, {insert: true}); + } + } else { + fileDirectory = fileDirectory[0]; + } + + fileDirectory = self.findFileEl(fileDirectory); - //finish delete if we are uploading a deleted file - if (FileList.deleteFiles && FileList.deleteFiles.indexOf(data.files[0].name)!==-1) { - FileList.finishDelete(null, true); //delete file before continuing - } + // update folder size + size = parseInt(fileDirectory.attr('data-size'), 10); + size += parseInt(file.size, 10); + fileDirectory.attr('data-size', size); + fileDirectory.find('td.filesize').text(humanFileSize(size)); - // add ui visualization to existing folder - if (data.context && data.context.data('type') === 'dir') { - // add to existing folder - - // update upload counter ui - var uploadText = data.context.find('.uploadtext'); - var currentUploads = parseInt(uploadText.attr('currentUploads'), 10); - currentUploads += 1; - uploadText.attr('currentUploads', currentUploads); - - var translatedText = n('files', 'Uploading %n file', 'Uploading %n files', currentUploads); - if (currentUploads === 1) { - var img = OC.imagePath('core', 'loading.gif'); - data.context.find('td.filename').attr('style','background-image:url('+img+')'); - uploadText.text(translatedText); - uploadText.show(); - } else { - uploadText.text(translatedText); - } - } - - }); - /* - * when file upload done successfully add row to filelist - * update counter when uploading to sub folder - */ - fileUploadStart.on('fileuploaddone', function(e, data) { - OC.Upload.log('filelist handle fileuploaddone', e, data); - - var response; - if (typeof data.result === 'string') { - response = data.result; - } else { - // fetch response from iframe - response = data.result[0].body.innerText; - } - var result=$.parseJSON(response); + return; + } - if (typeof result[0] !== 'undefined' && result[0].status === 'success') { - var file = result[0]; - var size = 0; + // add as stand-alone row to filelist + size = t('files', 'Pending'); + if (data.files[0].size>=0) { + size=data.files[0].size; + } + //should the file exist in the list remove it + self.remove(file.name); - if (data.context && data.context.data('type') === 'dir') { + // create new file context + data.context = self.add(file, {animate: true}); + } + } + }); + fileUploadStart.on('fileuploadstop', function(e, data) { + OC.Upload.log('filelist handle fileuploadstop', e, data); - // update upload counter ui - var uploadText = data.context.find('.uploadtext'); - var currentUploads = parseInt(uploadText.attr('currentUploads'), 10); - currentUploads -= 1; - uploadText.attr('currentUploads', currentUploads); - var translatedText = n('files', 'Uploading %n file', 'Uploading %n files', currentUploads); - if (currentUploads === 0) { + //if user pressed cancel hide upload chrome + if (data.errorThrown === 'abort') { + //cleanup uploading to a dir + var uploadText = $('tr .uploadtext'); var img = OC.imagePath('core', 'filetypes/folder'); - data.context.find('td.filename').attr('style','background-image:url('+img+')'); - uploadText.text(translatedText); - uploadText.hide(); - } else { - uploadText.text(translatedText); + uploadText.parents('td.filename').attr('style','background-image:url('+img+')'); + uploadText.fadeOut(); + uploadText.attr('currentUploads', 0); } + }); + fileUploadStart.on('fileuploadfail', function(e, data) { + OC.Upload.log('filelist handle fileuploadfail', e, data); - // update folder size - size = parseInt(data.context.data('size'), 10); - size += parseInt(file.size, 10); - data.context.attr('data-size', size); - data.context.find('td.filesize').text(humanFileSize(size)); - } else { - // only append new file if uploaded into the current folder - if (file.directory !== '/' && file.directory !== FileList.getCurrentDirectory()) { - - var fileDirectory = file.directory.replace('/','').replace(/\/$/, "").split('/'); - - if (fileDirectory.length === 1) { - fileDirectory = fileDirectory[0]; - - // Get the directory - var fd = FileList.findFileEl(fileDirectory); - if (fd.length === 0) { - var dir = { - name: fileDirectory, - type: 'dir', - mimetype: 'httpd/unix-directory', - permissions: file.permissions, - size: 0, - id: file.parentId - }; - FileList.add(dir, {insert: true}); - } - } else { - fileDirectory = fileDirectory[0]; - } - - fileDirectory = FileList.findFileEl(fileDirectory); - - // update folder size - size = parseInt(fileDirectory.attr('data-size'), 10); - size += parseInt(file.size, 10); - fileDirectory.attr('data-size', size); - fileDirectory.find('td.filesize').text(humanFileSize(size)); - - return; + //if user pressed cancel hide upload chrome + if (data.errorThrown === 'abort') { + //cleanup uploading to a dir + var uploadText = $('tr .uploadtext'); + var img = OC.imagePath('core', 'filetypes/folder'); + uploadText.parents('td.filename').attr('style','background-image:url('+img+')'); + uploadText.fadeOut(); + uploadText.attr('currentUploads', 0); } + }); - // add as stand-alone row to filelist - size = t('files', 'Pending'); - if (data.files[0].size>=0) { - size=data.files[0].size; - } - //should the file exist in the list remove it - FileList.remove(file.name); + } + }; - // create new file context - data.context = FileList.add(file, {animate: true}); + /** + * Sort comparators. + */ + FileList.Comparators = { + /** + * Compares two file infos by name, making directories appear + * first. + * + * @param fileInfo1 file info + * @param fileInfo2 file info + * @return -1 if the first file must appear before the second one, + * 0 if they are identify, 1 otherwise. + */ + name: function(fileInfo1, fileInfo2) { + if (fileInfo1.type === 'dir' && fileInfo2.type !== 'dir') { + return -1; } + if (fileInfo1.type !== 'dir' && fileInfo2.type === 'dir') { + return 1; + } + return fileInfo1.name.localeCompare(fileInfo2.name); + }, + /** + * Compares two file infos by size. + * + * @param fileInfo1 file info + * @param fileInfo2 file info + * @return -1 if the first file must appear before the second one, + * 0 if they are identify, 1 otherwise. + */ + size: function(fileInfo1, fileInfo2) { + return fileInfo1.size - fileInfo2.size; + }, + /** + * Compares two file infos by timestamp. + * + * @param fileInfo1 file info + * @param fileInfo2 file info + * @return -1 if the first file must appear before the second one, + * 0 if they are identify, 1 otherwise. + */ + mtime: function(fileInfo1, fileInfo2) { + return fileInfo1.mtime - fileInfo2.mtime; } - }); - fileUploadStart.on('fileuploadstop', function(e, data) { - OC.Upload.log('filelist handle fileuploadstop', e, data); - - //if user pressed cancel hide upload chrome - if (data.errorThrown === 'abort') { - //cleanup uploading to a dir - var uploadText = $('tr .uploadtext'); - var img = OC.imagePath('core', 'filetypes/folder'); - uploadText.parents('td.filename').attr('style','background-image:url('+img+')'); - uploadText.fadeOut(); - uploadText.attr('currentUploads', 0); - } - }); - fileUploadStart.on('fileuploadfail', function(e, data) { - OC.Upload.log('filelist handle fileuploadfail', e, data); - - //if user pressed cancel hide upload chrome - if (data.errorThrown === 'abort') { - //cleanup uploading to a dir - var uploadText = $('tr .uploadtext'); - var img = OC.imagePath('core', 'filetypes/folder'); - uploadText.parents('td.filename').attr('style','background-image:url('+img+')'); - uploadText.fadeOut(); - uploadText.attr('currentUploads', 0); - } - }); + }; - $('#notification').hide(); - $('#notification:first-child').on('click', '.replace', function() { - OC.Notification.hide(function() { - FileList.replace( - $('#notification > span').attr('data-oldName'), - $('#notification > span').attr('data-newName'), - $('#notification > span').attr('data-isNewFile')); - }); - }); - $('#notification:first-child').on('click', '.suggest', function() { - var file = $('#notification > span').attr('data-oldName'); - FileList.findFileEl(file).removeClass('hidden'); - OC.Notification.hide(); - }); - $('#notification:first-child').on('click', '.cancel', function() { - if ($('#notification > span').attr('data-isNewFile')) { - FileList.deleteCanceled = false; - FileList.deleteFiles = [$('#notification > span').attr('data-oldName')]; - } - }); - FileList.useUndo=(window.onbeforeunload)?true:false; + OCA.Files.FileList = FileList; +})(); + +$(document).ready(function() { + // FIXME: unused ? + OCA.Files.FileList.useUndo = (window.onbeforeunload)?true:false; $(window).bind('beforeunload', function () { - if (FileList.lastAction) { - FileList.lastAction(); + if (OCA.Files.FileList.lastAction) { + OCA.Files.FileList.lastAction(); } }); $(window).unload(function () { $(window).trigger('beforeunload'); }); - function decodeQuery(query) { - return query.replace(/\+/g, ' '); - } - - function parseHashQuery() { - var hash = window.location.hash, - pos = hash.indexOf('?'); - if (pos >= 0) { - return hash.substr(pos + 1); - } - return ''; - } - - function parseCurrentDirFromUrl() { - var query = parseHashQuery(), - params; - // try and parse from URL hash first - if (query) { - params = OC.parseQueryString(decodeQuery(query)); - } - // else read from query attributes - if (!params) { - params = OC.parseQueryString(decodeQuery(location.search)); - } - return (params && params.dir) || '/'; - } - - // disable ajax/history API for public app (TODO: until it gets ported) - // fallback to hashchange when no history support - if (!window.history.pushState) { - $(window).on('hashchange', function() { - FileList.changeDirectory(parseCurrentDirFromUrl(), false); - }); - } - window.onpopstate = function(e) { - var targetDir; - if (e.state && e.state.dir) { - targetDir = e.state.dir; - } - else{ - // read from URL - targetDir = parseCurrentDirFromUrl(); - } - if (targetDir) { - FileList.changeDirectory(targetDir, false); - } - }; - - $(window).scroll(function(e) {FileList._onScroll(e);}); - - var dir = parseCurrentDirFromUrl(); - // trigger ajax load, deferred to let sub-apps do their overrides first - setTimeout(function() { - FileList.changeDirectory(dir, false, true); - }, 0); }); - -/** - * Sort comparators. - */ -FileList.Comparators = { - /** - * Compares two file infos by name, making directories appear - * first. - * - * @param fileInfo1 file info - * @param fileInfo2 file info - * @return -1 if the first file must appear before the second one, - * 0 if they are identify, 1 otherwise. - */ - name: function(fileInfo1, fileInfo2) { - if (fileInfo1.type === 'dir' && fileInfo2.type !== 'dir') { - return -1; - } - if (fileInfo1.type !== 'dir' && fileInfo2.type === 'dir') { - return 1; - } - return fileInfo1.name.localeCompare(fileInfo2.name); - }, - /** - * Compares two file infos by size. - * - * @param fileInfo1 file info - * @param fileInfo2 file info - * @return -1 if the first file must appear before the second one, - * 0 if they are identify, 1 otherwise. - */ - size: function(fileInfo1, fileInfo2) { - return fileInfo1.size - fileInfo2.size; - }, - /** - * Compares two file infos by timestamp. - * - * @param fileInfo1 file info - * @param fileInfo2 file info - * @return -1 if the first file must appear before the second one, - * 0 if they are identify, 1 otherwise. - */ - mtime: function(fileInfo1, fileInfo2) { - return fileInfo1.mtime - fileInfo2.mtime; - } -}; - |