diff --git a/apps/files/ajax/list.php b/apps/files/ajax/list.php index 3bb35579d5..2d76b68501 100644 --- a/apps/files/ajax/list.php +++ b/apps/files/ajax/list.php @@ -7,37 +7,21 @@ OCP\JSON::checkLoggedIn(); $dir = isset( $_GET['dir'] ) ? $_GET['dir'] : ''; $dir = \OC\Files\Filesystem::normalizePath($dir); $dirInfo = \OC\Files\Filesystem::getFileInfo($dir); -if (!$dirInfo->getType() === 'dir') { +if (!$dirInfo || !$dirInfo->getType() === 'dir') { header("HTTP/1.0 404 Not Found"); exit(); } -$doBreadcrumb = isset($_GET['breadcrumb']); $data = array(); $baseUrl = OCP\Util::linkTo('files', 'index.php') . '?dir='; $permissions = $dirInfo->getPermissions(); -// Make breadcrumb -if($doBreadcrumb) { - $breadcrumb = \OCA\Files\Helper::makeBreadcrumb($dir); - - $breadcrumbNav = new OCP\Template('files', 'part.breadcrumb', ''); - $breadcrumbNav->assign('breadcrumb', $breadcrumb, false); - $breadcrumbNav->assign('baseURL', $baseUrl); - - $data['breadcrumb'] = $breadcrumbNav->fetchPage(); -} - // make filelist $files = \OCA\Files\Helper::getFiles($dir); -$list = new OCP\Template("files", "part.list", ""); -$list->assign('files', $files, false); -$list->assign('baseURL', $baseUrl, false); -$list->assign('downloadURL', OCP\Util::linkToRoute('download', array('file' => '/'))); -$list->assign('isPublic', false); -$data['files'] = $list->fetchPage(); +$data['directory'] = $dir; +$data['files'] = \OCA\Files\Helper::formatFileInfos($files); $data['permissions'] = $permissions; OCP\JSON::success(array('data' => $data)); diff --git a/apps/files/ajax/newfile.php b/apps/files/ajax/newfile.php index 1234cf1139..7d6be59bea 100644 --- a/apps/files/ajax/newfile.php +++ b/apps/files/ajax/newfile.php @@ -112,9 +112,8 @@ if($source) { } if($result) { $meta = \OC\Files\Filesystem::getFileInfo($target); - $mime=$meta['mimetype']; - $id = $meta['fileid']; - $eventSource->send('success', array('mime' => $mime, 'size' => \OC\Files\Filesystem::filesize($target), 'id' => $id, 'etag' => $meta['etag'])); + $data = \OCA\Files\Helper::formatFileInfo($meta); + $eventSource->send('success', $data); } else { $eventSource->send('error', array('message' => $l10n->t('Error while downloading %s to %s', array($source, $target)))); } @@ -139,16 +138,7 @@ if($source) { if($success) { $meta = \OC\Files\Filesystem::getFileInfo($target); - $id = $meta['fileid']; - $mime = $meta['mimetype']; - $size = $meta['size']; - OCP\JSON::success(array('data' => array( - 'id' => $id, - 'mime' => $mime, - 'size' => $size, - 'content' => $content, - 'etag' => $meta['etag'], - ))); + OCP\JSON::success(array('data' => \OCA\Files\Helper::formatFileInfo($meta))); exit(); } } diff --git a/apps/files/ajax/newfolder.php b/apps/files/ajax/newfolder.php index 032447460f..89c241189d 100644 --- a/apps/files/ajax/newfolder.php +++ b/apps/files/ajax/newfolder.php @@ -58,8 +58,8 @@ if(\OC\Files\Filesystem::mkdir($target)) { $path = '/'.$foldername; } $meta = \OC\Files\Filesystem::getFileInfo($path); - $id = $meta['fileid']; - OCP\JSON::success(array('data' => array('id' => $id))); + $meta['type'] = 'dir'; // missing ?! + OCP\JSON::success(array('data' => \OCA\Files\Helper::formatFileInfo($meta))); exit(); } diff --git a/apps/files/ajax/rawlist.php b/apps/files/ajax/rawlist.php deleted file mode 100644 index f18bbffb74..0000000000 --- a/apps/files/ajax/rawlist.php +++ /dev/null @@ -1,54 +0,0 @@ -close(); - -// Load the files -$dir = isset($_GET['dir']) ? $_GET['dir'] : ''; -$mimetypes = isset($_GET['mimetypes']) ? json_decode($_GET['mimetypes'], true) : ''; - -// Clean up duplicates from array and deal with non-array requests -if (is_array($mimetypes)) { - $mimetypes = array_unique($mimetypes); -} elseif (is_null($mimetypes)) { - $mimetypes = array($_GET['mimetypes']); -} - -// make filelist -$files = array(); -/** - * @var \OCP\Files\FileInfo[] $files - */ -// If a type other than directory is requested first load them. -if ($mimetypes && !in_array('httpd/unix-directory', $mimetypes)) { - $files = array_merge($files, \OC\Files\Filesystem::getDirectoryContent($dir, 'httpd/unix-directory')); -} - -if (is_array($mimetypes) && count($mimetypes)) { - foreach ($mimetypes as $mimetype) { - $files = array_merge($files, \OC\Files\Filesystem::getDirectoryContent($dir, $mimetype)); - } -} else { - $files = array_merge($files, \OC\Files\Filesystem::getDirectoryContent($dir)); -} -// Sort by name -usort($files, array('\OCA\Files\Helper', 'fileCmp')); - -$result = array(); -foreach ($files as $file) { - $fileData = array(); - $fileData['directory'] = $dir; - $fileData['name'] = $file->getName(); - $fileData['type'] = $file->getType(); - $fileData['path'] = $file['path']; - $fileData['id'] = $file->getId(); - $fileData['size'] = $file->getSize(); - $fileData['mtime'] = $file->getMtime(); - $fileData['mimetype'] = $file->getMimetype(); - $fileData['isPreviewAvailable'] = \OC::$server->getPreviewManager()->isMimeSupported($file->getMimetype()); - $fileData["date"] = OCP\Util::formatDate($file->getMtime()); - $fileData['mimetype_icon'] = \OCA\Files\Helper::determineIcon($file); - $result[] = $fileData; -} - -OC_JSON::success(array('data' => $result)); diff --git a/apps/files/ajax/upload.php b/apps/files/ajax/upload.php index 4ed51c5277..b21a9dfba2 100644 --- a/apps/files/ajax/upload.php +++ b/apps/files/ajax/upload.php @@ -20,6 +20,10 @@ if (empty($_POST['dirToken'])) { die(); } } else { + // TODO: ideally this code should be in files_sharing/ajax/upload.php + // and the upload/file transfer code needs to be refactored into a utility method + // that could be used there + // return only read permissions for public upload $allowedPermissions = OCP\PERMISSION_READ; $public_directory = !empty($_POST['subdir']) ? $_POST['subdir'] : '/'; @@ -141,19 +145,14 @@ if (strpos($dir, '..') === false) { $error = $l->t('The target folder has been moved or deleted.'); $errorCode = 'targetnotfound'; } else { - $result[] = array('status' => 'success', - 'mime' => $meta['mimetype'], - 'mtime' => $meta['mtime'], - 'size' => $meta['size'], - 'id' => $meta['fileid'], - 'name' => basename($target), - 'etag' => $meta['etag'], - 'originalname' => $files['tmp_name'][$i], - 'uploadMaxFilesize' => $maxUploadFileSize, - 'maxHumanFilesize' => $maxHumanFileSize, - 'permissions' => $meta['permissions'] & $allowedPermissions, - 'directory' => $directory, - ); + $data = \OCA\Files\Helper::formatFileInfo($meta); + $data['status'] = 'success'; + $data['originalname'] = $files['tmp_name'][$i]; + $data['uploadMaxFilesize'] = $maxUploadFileSize; + $data['maxHumanFilesize'] = $maxHumanFileSize; + $data['permissions'] = $meta['permissions'] & $allowedPermissions; + $data['directory'] = $directory; + $result[] = $data; } } else { @@ -169,19 +168,15 @@ if (strpos($dir, '..') === false) { if ($meta === false) { $error = $l->t('Upload failed. Could not get file info.'); } else { - $result[] = array('status' => 'existserror', - 'mime' => $meta['mimetype'], - 'mtime' => $meta['mtime'], - 'size' => $meta['size'], - 'id' => $meta['fileid'], - 'name' => basename($target), - 'etag' => $meta['etag'], - 'originalname' => $files['tmp_name'][$i], - 'uploadMaxFilesize' => $maxUploadFileSize, - 'maxHumanFilesize' => $maxHumanFileSize, - 'permissions' => $meta['permissions'] & $allowedPermissions, - 'directory' => $directory, - ); + $data = \OCA\Files\Helper::formatFileInfo($meta); + $data['permissions'] = $data['permissions'] & $allowedPermissions; + $data['status'] = 'existserror'; + $data['originalname'] = $files['tmp_name'][$i]; + $data['uploadMaxFilesize'] = $maxUploadFileSize; + $data['maxHumanFilesize'] = $maxHumanFileSize; + $data['permissions'] = $meta['permissions'] & $allowedPermissions; + $data['directory'] = $directory; + $result[] = $data; } } } diff --git a/apps/files/index.php b/apps/files/index.php index cd9341d3b1..7d25c816eb 100644 --- a/apps/files/index.php +++ b/apps/files/index.php @@ -32,6 +32,7 @@ OCP\Util::addscript('files', 'file-upload'); OCP\Util::addscript('files', 'jquery.iframe-transport'); OCP\Util::addscript('files', 'jquery.fileupload'); OCP\Util::addscript('files', 'jquery-visibility'); +OCP\Util::addscript('files', 'breadcrumb'); OCP\Util::addscript('files', 'filelist'); OCP\App::setActiveNavigationEntry('files_index'); @@ -60,37 +61,12 @@ if ($isIE8 && isset($_GET['dir'])){ exit(); } -$ajaxLoad = false; -$files = array(); $user = OC_User::getUser(); -if ($isIE8){ - // after the redirect above, the URL will have a format - // like "files#?dir=path" which means that no path was given - // (dir is not set). In that specific case, we don't return any - // files because the client will take care of switching the dir - // to the one from the hash, then ajax-load the initial file list - $files = array(); - $ajaxLoad = true; -} -else{ - $files = \OCA\Files\Helper::getFiles($dir); -} $config = \OC::$server->getConfig(); -// Make breadcrumb -$breadcrumb = \OCA\Files\Helper::makeBreadcrumb($dir); - -// make breadcrumb und filelist markup -$list = new OCP\Template('files', 'part.list', ''); -$list->assign('files', $files); -$list->assign('baseURL', OCP\Util::linkTo('files', 'index.php') . '?dir='); -$list->assign('downloadURL', OCP\Util::linkToRoute('download', array('file' => '/'))); -$list->assign('isPublic', false); -$breadcrumbNav = new OCP\Template('files', 'part.breadcrumb', ''); -$breadcrumbNav->assign('breadcrumb', $breadcrumb); -$breadcrumbNav->assign('baseURL', OCP\Util::linkTo('files', 'index.php') . '?dir='); - +// needed for share init, permissions will be reloaded +// anyway with ajax load $permissions = $dirInfo->getPermissions(); // information about storage capacities @@ -112,20 +88,12 @@ if ($trashEnabled) { $trashEmpty = \OCA\Files_Trashbin\Trashbin::isEmpty($user); } -$isCreatable = \OC\Files\Filesystem::isCreatable($dir . '/'); -$fileHeader = (!isset($files) or count($files) > 0); -$emptyContent = ($isCreatable and !$fileHeader) or $ajaxLoad; - OCP\Util::addscript('files', 'fileactions'); OCP\Util::addscript('files', 'files'); OCP\Util::addscript('files', 'keyboardshortcuts'); $tmpl = new OCP\Template('files', 'index', 'user'); -$tmpl->assign('fileList', $list->fetchPage()); -$tmpl->assign('breadcrumb', $breadcrumbNav->fetchPage()); $tmpl->assign('dir', $dir); -$tmpl->assign('isCreatable', $isCreatable); $tmpl->assign('permissions', $permissions); -$tmpl->assign('files', $files); $tmpl->assign('trash', $trashEnabled); $tmpl->assign('trashEmpty', $trashEmpty); $tmpl->assign('uploadMaxFilesize', $maxUploadFilesize); // minimium of freeSpace and uploadLimit @@ -141,8 +109,5 @@ $tmpl->assign("mailNotificationEnabled", $config->getAppValue('core', 'shareapi_ $tmpl->assign("allowShareWithLink", $config->getAppValue('core', 'shareapi_allow_links', 'yes')); $tmpl->assign("encryptionInitStatus", $encryptionInitStatus); $tmpl->assign('disableSharing', false); -$tmpl->assign('ajaxLoad', $ajaxLoad); -$tmpl->assign('emptyContent', $emptyContent); -$tmpl->assign('fileHeader', $fileHeader); $tmpl->printPage(); diff --git a/apps/files/js/breadcrumb.js b/apps/files/js/breadcrumb.js new file mode 100644 index 0000000000..ff017a22bb --- /dev/null +++ b/apps/files/js/breadcrumb.js @@ -0,0 +1,242 @@ +/** +* ownCloud +* +* @author Vincent Petry +* @copyright 2014 Vincent Petry +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE +* License as published by the Free Software Foundation; either +* version 3 of the License, or any later version. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU AFFERO GENERAL PUBLIC LICENSE for more details. +* +* You should have received a copy of the GNU Affero General Public +* License along with this library. If not, see . +* +*/ + +/* global OC */ +/* global SVGSupport, replaceSVG */ +(function() { + /** + * Creates an breadcrumb element in the given container + */ + var BreadCrumb = function(options){ + this.$el = $(''); + options = options || {}; + if (options.onClick) { + this.onClick = options.onClick; + } + if (options.onDrop) { + this.onDrop = options.onDrop; + } + if (options.getCrumbUrl) { + this.getCrumbUrl = options.getCrumbUrl; + } + }; + BreadCrumb.prototype = { + $el: null, + dir: null, + + lastWidth: 0, + hiddenBreadcrumbs: 0, + totalWidth: 0, + breadcrumbs: [], + onClick: null, + onDrop: null, + + /** + * Sets the directory to be displayed as breadcrumb. + * This will re-render the breadcrumb. + * @param dir path to be displayed as breadcrumb + */ + setDirectory: function(dir) { + dir = dir || '/'; + if (dir !== this.dir) { + this.dir = dir; + this.render(); + } + }, + + /** + * Returns the full URL to the given directory + * @param part crumb data as map + * @param index crumb index + * @return full URL + */ + getCrumbUrl: function(part, index) { + return '#'; + }, + + /** + * Renders the breadcrumb elements + */ + render: function() { + var parts = this._makeCrumbs(this.dir || '/'); + var $crumb; + this.$el.empty(); + this.breadcrumbs = []; + + for (var i = 0; i < parts.length; i++) { + var part = parts[i]; + var $image; + var $link = $('').attr('href', this.getCrumbUrl(part, i)); + $link.text(part.name); + $crumb = $('
'); + $crumb.append($link); + $crumb.attr('data-dir', part.dir); + + if (part.img) { + $image = $(''); + $image.attr('src', part.img); + $link.append($image); + } + this.breadcrumbs.push($crumb); + this.$el.append($crumb); + if (this.onClick) { + $crumb.on('click', this.onClick); + } + } + $crumb.addClass('last'); + + // in case svg is not supported by the browser we need to execute the fallback mechanism + if (!SVGSupport()) { + replaceSVG(); + } + + // setup drag and drop + if (this.onDrop) { + this.$el.find('.crumb:not(.last)').droppable({ + drop: this.onDrop, + tolerance: 'pointer' + }); + } + + this._updateTotalWidth(); + this.resize($(window).width(), true); + }, + + /** + * Makes a breadcrumb structure based on the given path + * @param dir path to split into a breadcrumb structure + * @return array of map {dir: path, name: displayName} + */ + _makeCrumbs: function(dir) { + var crumbs = []; + var pathToHere = ''; + // trim leading and trailing slashes + dir = dir.replace(/^\/+|\/+$/g, ''); + var parts = dir.split('/'); + if (dir === '') { + parts = []; + } + // root part + crumbs.push({ + dir: '/', + name: '', + img: OC.imagePath('core', 'places/home.svg') + }); + for (var i = 0; i < parts.length; i++) { + var part = parts[i]; + pathToHere = pathToHere + '/' + part; + crumbs.push({ + dir: pathToHere, + name: part + }); + } + return crumbs; + }, + + _updateTotalWidth: function () { + var self = this; + + this.lastWidth = 0; + + // initialize with some extra space + this.totalWidth = 64; + // FIXME: this class should not know about global elements + if ( $('#navigation').length ) { + this.totalWidth += $('#navigation').get(0).offsetWidth; + } + this.hiddenBreadcrumbs = 0; + + for (var i = 0; i < this.breadcrumbs.length; i++ ) { + this.totalWidth += $(this.breadcrumbs[i]).get(0).offsetWidth; + } + + $.each($('#controls .actions>div'), function(index, action) { + self.totalWidth += $(action).get(0).offsetWidth; + }); + + }, + + /** + * Show/hide breadcrumbs to fit the given width + */ + resize: function (width, firstRun) { + var i, $crumb; + + if (width === this.lastWidth) { + return; + } + + // window was shrinked since last time or first run ? + if ((width < this.lastWidth || firstRun) && width < this.totalWidth) { + if (this.hiddenBreadcrumbs === 0 && this.breadcrumbs.length > 1) { + // start by hiding the first breadcrumb after home, + // that one will have extra three dots displayed + $crumb = this.breadcrumbs[1]; + this.totalWidth -= $crumb.get(0).offsetWidth; + $crumb.find('a').addClass('hidden'); + $crumb.append('...'); + this.totalWidth += $crumb.get(0).offsetWidth; + this.hiddenBreadcrumbs = 2; + } + i = this.hiddenBreadcrumbs; + // hide subsequent breadcrumbs if the space is still not enough + while (width < this.totalWidth && i > 1 && i < this.breadcrumbs.length - 1) { + $crumb = this.breadcrumbs[i]; + this.totalWidth -= $crumb.get(0).offsetWidth; + $crumb.addClass('hidden'); + this.hiddenBreadcrumbs = i; + i++; + } + // window is bigger than last time + } else if (width > this.lastWidth && this.hiddenBreadcrumbs > 0) { + i = this.hiddenBreadcrumbs; + while (width > this.totalWidth && i > 0) { + if (this.hiddenBreadcrumbs === 1) { + // special handling for last one as it has the three dots + $crumb = this.breadcrumbs[1]; + if ($crumb) { + this.totalWidth -= $crumb.get(0).offsetWidth; + $crumb.find('.ellipsis').remove(); + $crumb.find('a').removeClass('hidden'); + this.totalWidth += $crumb.get(0).offsetWidth; + } + } else { + $crumb = this.breadcrumbs[i]; + $crumb.removeClass('hidden'); + this.totalWidth += $crumb.get(0).offsetWidth; + if (this.totalWidth > width) { + this.totalWidth -= $crumb.get(0).offsetWidth; + $crumb.addClass('hidden'); + break; + } + } + i--; + this.hiddenBreadcrumbs = i; + } + } + + this.lastWidth = width; + } + }; + + window.BreadCrumb = BreadCrumb; +})(); + diff --git a/apps/files/js/file-upload.js b/apps/files/js/file-upload.js index 371c83e742..e5d1eacbd1 100644 --- a/apps/files/js/file-upload.js +++ b/apps/files/js/file-upload.js @@ -180,7 +180,7 @@ OC.Upload = { }, init: function() { - if ( $('#file_upload_start').exists() && $('#file_upload_start').is(':visible')) { + if ( $('#file_upload_start').exists() ) { var file_upload_param = { dropZone: $('#content'), // restrict dropZone to content div @@ -483,28 +483,6 @@ OC.Upload = { $('#file_upload_start').attr('multiple', 'multiple'); } - //if the breadcrumb is to long, start by replacing foldernames with '...' except for the current folder - var crumb=$('div.crumb').first(); - while($('div.controls').height() > 40 && crumb.next('div.crumb').length > 0) { - crumb.children('a').text('...'); - crumb = crumb.next('div.crumb'); - } - //if that isn't enough, start removing items from the breacrumb except for the current folder and it's parent - var crumb = $('div.crumb').first(); - var next = crumb.next('div.crumb'); - while($('div.controls').height()>40 && next.next('div.crumb').length > 0) { - crumb.remove(); - crumb = next; - next = crumb.next('div.crumb'); - } - //still not enough, start shorting down the current folder name - var crumb=$('div.crumb>a').last(); - while($('div.controls').height() > 40 && crumb.text().length > 6) { - var text=crumb.text(); - text = text.substr(0,text.length-6)+'...'; - crumb.text(text); - } - $(document).click(function(ev) { // do not close when clicking in the dropdown if ($(ev.target).closest('#new').length){ @@ -617,21 +595,7 @@ OC.Upload = { {dir:$('#dir').val(), filename:name}, function(result) { if (result.status === 'success') { - var date = new Date(); - // TODO: ideally addFile should be able to receive - // all attributes and set them automatically, - // and also auto-load the preview - var tr = FileList.addFile(name, 0, date, false, hidden); - tr.attr('data-size', result.data.size); - tr.attr('data-mime', result.data.mime); - tr.attr('data-id', result.data.id); - tr.attr('data-etag', result.data.etag); - tr.find('.filesize').text(humanFileSize(result.data.size)); - var path = getPathForPreview(name); - Files.lazyLoadPreview(path, result.data.mime, function(previewpath) { - tr.find('td.filename').attr('style','background-image:url('+previewpath+')'); - }, null, null, result.data.etag); - FileActions.display(tr.find('td.filename'), true); + FileList.add(result.data, {hidden: hidden, insert: true}); } else { OC.dialogs.alert(result.data.message, t('core', 'Could not create file')); } @@ -644,10 +608,7 @@ OC.Upload = { {dir:$('#dir').val(), foldername:name}, function(result) { if (result.status === 'success') { - var date=new Date(); - FileList.addDir(name, 0, date, hidden); - var tr = FileList.findFileEl(name); - tr.attr('data-id', result.data.id); + FileList.add(result.data, {hidden: hidden, insert: true}); } else { OC.dialogs.alert(result.data.message, t('core', 'Could not create folder')); } @@ -682,20 +643,10 @@ OC.Upload = { } }); eventSource.listen('success',function(data) { - var mime = data.mime; - var size = data.size; - var id = data.id; + var file = data; $('#uploadprogressbar').fadeOut(); - var date = new Date(); - FileList.addFile(localName, size, date, false, hidden); - var tr = FileList.findFileEl(localName); - tr.data('mime', mime).data('id', id); - tr.attr('data-id', id); - var path = $('#dir').val()+'/'+localName; - Files.lazyLoadPreview(path, mime, function(previewpath) { - tr.find('td.filename').attr('style', 'background-image:url('+previewpath+')'); - }, null, null, data.etag); - FileActions.display(tr.find('td.filename'), true); + + FileList.add(file, {hidden: hidden, insert: true}); }); eventSource.listen('error',function(error) { $('#uploadprogressbar').fadeOut(); diff --git a/apps/files/js/fileactions.js b/apps/files/js/fileactions.js index 732690f047..631aebea95 100644 --- a/apps/files/js/fileactions.js +++ b/apps/files/js/fileactions.js @@ -8,7 +8,7 @@ * */ -/* global OC, FileList */ +/* global OC, FileList, Files */ /* global trashBinApp */ var FileActions = { actions: {}, @@ -214,7 +214,7 @@ $(document).ready(function () { FileActions.register(downloadScope, 'Download', OC.PERMISSION_READ, function () { return OC.imagePath('core', 'actions/download'); }, function (filename) { - var url = FileList.getDownloadUrl(filename); + var url = Files.getDownloadUrl(filename); if (url) { OC.redirect(url); } diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js index cda4e823a7..509929d0e5 100644 --- a/apps/files/js/filelist.js +++ b/apps/files/js/filelist.js @@ -8,17 +8,104 @@ * */ -/* global OC, t, n, FileList, FileActions, Files */ -/* global procesSelection, dragOptions, SVGSupport, replaceSVG */ -window.FileList={ +/* global OC, t, n, FileList, FileActions, Files, BreadCrumb */ +/* global procesSelection, dragOptions, SVGSupport */ +window.FileList = { appName: t('files', 'Files'), + isEmpty: true, useUndo:true, - postProcessList: function() { - $('#fileList tr').each(function() { - //little hack to set unescape filenames in attribute - $(this).attr('data-file',decodeURIComponent($(this).attr('data-file'))); + $el: $('#filestable'), + $fileList: $('#fileList'), + breadcrumb: null, + initialized: false, + + /** + * Initialize the file list and its components + */ + initialize: function() { + var self = this; + if (this.initialized) { + return; + } + + // TODO: FileList should not know about global elements + this.$el = $('#filestable'); + this.$fileList = $('#fileList'); + + this.breadcrumb = new BreadCrumb({ + onClick: this._onClickBreadCrumb, + onDrop: this._onDropOnBreadCrumb, + getCrumbUrl: function(part, index) { + return self.linkTo(part.dir); + } + }); + + $('#controls').prepend(this.breadcrumb.$el); + + $(window).resize(function() { + // TODO: debounce this ? + var width = $(this).width(); + FileList.breadcrumb.resize(width, false); }); }, + + /** + * 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(); + FileList.changeDirectory($targetDir); + } + }, + + /** + * Event handler when dropping on a breadcrumb + */ + _onDropOnBreadCrumb: function( event, ui ) { + var target=$(this).data('dir'); + var dir = FileList.getCurrentDirectory(); + while(dir.substr(0,1) === '/') {//remove extra leading /'s + dir=dir.substr(1); + } + dir = '/' + dir; + if (dir.substr(-1,1) !== '/') { + dir = dir + '/'; + } + if (target === dir || target+'/' === dir) { + return; + } + var files = ui.helper.find('tr'); + $(files).each(function(i,row) { + var dir = $(row).data('dir'); + var file = $(row).data('filename'); + //slapdash selector, tracking down our original element that the clone budded off of. + var origin = $('tr[data-id=' + $(row).data('origin') + ']'); + var td = origin.children('td.filename'); + var oldBackgroundImage = td.css('background-image'); + td.css('background-image', 'url('+ OC.imagePath('core', 'loading.gif') + ')'); + $.post(OC.filePath('files', 'ajax', 'move.php'), { dir: dir, file: file, target: target }, function(result) { + if (result) { + if (result.status === 'success') { + FileList.remove(file); + procesSelection(); + $('#notification').hide(); + } else { + $('#notification').hide(); + $('#notification').text(result.data.message); + $('#notification').fadeIn(); + } + } else { + OC.dialogs.alert(t('files', 'Error moving file'), t('files', 'Error')); + } + td.css('background-image', oldBackgroundImage); + }); + }); + }, + /** * Sets a new page title */ @@ -36,64 +123,133 @@ window.FileList={ }, /** * Returns the tr element for a given file name + * @param fileName file name */ findFileEl: function(fileName){ // use filterAttr to avoid escaping issues - return $('#fileList tr').filterAttr('data-file', fileName); + return this.$fileList.find('tr').filterAttr('data-file', fileName); }, - update:function(fileListHtml) { - var $fileList = $('#fileList'); - $fileList.empty().html(fileListHtml); - FileList.updateEmptyContent(); - $fileList.find('tr').each(function () { - FileActions.display($(this).children('td.filename')); - }); - $fileList.trigger(jQuery.Event("fileActionsReady")); - FileList.postProcessList(); + /** + * Sets the files to be displayed in the list. + * This operation will rerender the list and update the summary. + * @param filesArray array of file data (map) + */ + setFiles:function(filesArray) { + // detach to make adding multiple rows faster + this.$fileList.detach(); + + this.$fileList.empty(); + + this.isEmpty = filesArray.length === 0; + for (var i = 0; i < filesArray.length; i++) { + this.add(filesArray[i], {updateSummary: false}); + } + + this.$el.find('thead').after(this.$fileList); + + this.updateEmptyContent(); + this.$fileList.trigger(jQuery.Event("fileActionsReady")); // "Files" might not be loaded in extending apps if (window.Files) { Files.setupDragAndDrop(); } - FileList.updateFileSummary(); + this.updateFileSummary(); procesSelection(); - $(window).scrollTop(0); - $fileList.trigger(jQuery.Event("updated")); + + this.$fileList.trigger(jQuery.Event("updated")); }, - createRow:function(type, name, iconurl, linktarget, size, lastModified, permissions) { - var td, simpleSize, basename, extension; + /** + * If SVG is not supported, replaces the given images's extension + * from ".svg" to ".png". + * If SVG is supported, return the image path as is. + * @param icon image path + * @return fixed image path + */ + _replaceSVG: function(icon) { + if (!SVGSupport()) { + var i = icon.lastIndexOf('.svg'); + if (i >= 0) { + icon = icon.substr(0, i) + '.png' + icon.substr(i+4); + } + } + return icon; + }, + /** + * 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 = FileList._replaceSVG(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'; + } //containing tr var tr = $('').attr({ + "data-id" : fileData.id, "data-type": type, - "data-size": size, + "data-size": fileData.size, "data-file": name, - "data-permissions": permissions + "data-mime": mime, + "data-mtime": mtime, + "data-etag": fileData.etag, + "data-permissions": fileData.permissions || this.getDirectoryPermissions() }); + + if (type === 'dir') { + // use default folder icon + icon = icon || OC.imagePath('core', 'filetypes/folder'); + } + else { + icon = icon || OC.imagePath('core', 'filetypes/file'); + } + // filename td td = $('').attr({ - "class": "filename svg", - "style": 'background-image:url('+iconurl+'); background-size: 32px;' + "class": "filename", + "style": 'background-image:url(' + icon + '); background-size: 32px;' }); - var rand = Math.random().toString(16).slice(2); - td.append(''); + + // linkUrl + if (type === 'dir') { + linkUrl = FileList.linkTo(FileList.getCurrentDirectory() + '/' + name); + } + else { + linkUrl = Files.getDownloadUrl(name, FileList.getCurrentDirectory()); + } + td.append(''); var link_elem = $('').attr({ "class": "name", - "href": linktarget + "href": linkUrl }); - //split extension from filename for non dirs + + // 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('.')); + basename = name.substr(0, name.lastIndexOf('.')); + extension = name.substr(name.lastIndexOf('.')); } else { - basename=name; - extension=false; + basename = name; + extension = false; } var name_span=$('').addClass('nametext').text(basename); link_elem.append(name_span); if (extension) { name_span.append($('').addClass('extension').text(extension)); } - //dirs can show the number of uploaded files + // dirs can show the number of uploaded files if (type === 'dir') { link_elem.append($('').attr({ 'class': 'uploadtext', @@ -103,98 +259,122 @@ window.FileList={ td.append(link_elem); tr.append(td); - //size column - if (size !== t('files', 'Pending')) { - simpleSize = humanFileSize(size); + // 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'); + simpleSize = t('files', 'Pending'); } - var sizeColor = Math.round(160-Math.pow((size/(1024*1024)),2)); - var lastModifiedTime = Math.round(lastModified.getTime() / 1000); + var lastModifiedTime = Math.round(mtime / 1000); td = $('').attr({ "class": "filesize", - "style": 'color:rgb('+sizeColor+','+sizeColor+','+sizeColor+')' + "style": 'color:rgb(' + sizeColor + ',' + sizeColor + ',' + sizeColor + ')' }).text(simpleSize); tr.append(td); // date column - var modifiedColor = Math.round((Math.round((new Date()).getTime() / 1000)-lastModifiedTime)/60/60/24*5); + var modifiedColor = Math.round((Math.round((new Date()).getTime() / 1000) - mtime)/60/60/24*5); td = $('').attr({ "class": "date" }); td.append($('').attr({ "class": "modified", - "title": formatDate(lastModified), + "title": formatDate(mtime), "style": 'color:rgb('+modifiedColor+','+modifiedColor+','+modifiedColor+')' - }).text( relative_modified_date(lastModified.getTime() / 1000) )); + }).text( relative_modified_date(mtime / 1000) )); + tr.find('.filesize').text(simpleSize); tr.append(td); return tr; }, - addFile:function(name, size, lastModified, loading, hidden, param) { - var imgurl; + /** + * Adds an entry to the files table using the data from the given file data + * @param fileData map of file attributes + * @param options map of attributes: + * - "insert" true to insert in a sorted manner, false to append (default) + * - "updateSummary" true to update the summary after adding (default), false otherwise + * @return new tr element (not appended to the table) + */ + add: function(fileData, options) { + options = options || {}; + var type = fileData.type || 'file', + mime = fileData.mimetype, + permissions = parseInt(fileData.permissions, 10) || 0; - if (!param) { - param = {}; + if (type === 'dir') { + mime = mime || 'httpd/unix-directory'; } - - var download_url = null; - if (!param.download_url) { - download_url = OC.generateUrl( - 'apps/files/download{file}', - { file: $('#dir').val()+'/'+name }); - } else { - download_url = param.download_url; - } - - if (loading) { - imgurl = OC.imagePath('core', 'loading.gif'); - } else { - imgurl = OC.imagePath('core', 'filetypes/file'); - } - var tr = this.createRow( - 'file', - name, - imgurl, - download_url, - size, - lastModified, - $('#permissions').val() + var tr = this._createRow( + fileData, + options ); + var filenameTd = tr.find('td.filename'); - FileList.insertElement(name, 'file', tr); - if (loading) { - tr.data('loading', true); - } else { - tr.find('td.filename').draggable(dragOptions); + // sorted insert is expensive, so needs to be explicitly + // requested + if (options.insert) { + this.insertElement(fileData.name, type, tr); } - if (hidden) { - tr.hide(); + else { + this.$fileList.append(tr); + } + FileList.isEmpty = false; + + // 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 (options.hidden) { + tr.addClass('hidden'); + } + + // display actions + FileActions.display(filenameTd, false); + + 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); + } + 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 + ')'); + } + } + + // defaults to true if not defined + if (typeof(options.updateSummary) === 'undefined' || !!options.updateSummary) { + this.updateFileSummary(); + this.updateEmptyContent(); } return tr; }, - addDir:function(name, size, lastModified, hidden) { - - var tr = this.createRow( - 'dir', - name, - OC.imagePath('core', 'filetypes/folder'), - OC.linkTo('files', 'index.php')+"?dir="+ encodeURIComponent($('#dir').val()+'/'+name).replace(/%2F/g, '/'), - size, - lastModified, - $('#permissions').val() - ); - - FileList.insertElement(name, 'dir', tr); - var td = tr.find('td.filename'); - td.draggable(dragOptions); - td.droppable(folderDropOptions); - if (hidden) { - tr.hide(); - } - FileActions.display(tr.find('td.filename'), true); - 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) @@ -209,7 +389,7 @@ window.FileList={ if (!force && currentDir === targetDir) { return; } - FileList.setCurrentDir(targetDir, changeUrl); + FileList._setCurrentDir(targetDir, changeUrl); $('#fileList').trigger( jQuery.Event('changeDirectory', { dir: targetDir, @@ -221,7 +401,13 @@ window.FileList={ linkTo: function(dir) { return OC.linkTo('files', 'index.php')+"?dir="+ encodeURIComponent(dir).replace(/%2F/g, '/'); }, - setCurrentDir: function(targetDir, changeUrl) { + + /** + * 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); @@ -243,6 +429,7 @@ window.FileList={ window.location.hash = '?dir='+ encodeURIComponent(targetDir).replace(/%2F/g, '/'); } } + this.breadcrumb.setDirectory(this.getCurrentDirectory()); }, /** * @brief Reloads the file list using ajax call @@ -253,10 +440,9 @@ window.FileList={ FileList._reloadCall.abort(); } FileList._reloadCall = $.ajax({ - url: OC.filePath('files','ajax','list.php'), + url: Files.getAjaxUrl('list'), data: { - dir : $('#dir').val(), - breadcrumb: true + dir : $('#dir').val() }, error: function(result) { FileList.reloadCallback(result); @@ -269,8 +455,8 @@ window.FileList={ reloadCallback: function(result) { var $controls = $('#controls'); - delete FileList._reloadCall; - FileList.hideMask(); + delete this._reloadCall; + this.hideMask(); if (!result || result.status === 'error') { OC.Notification.show(result.data.message); @@ -279,7 +465,11 @@ window.FileList={ if (result.status === 404) { // go back home - FileList.changeDirectory('/'); + this.changeDirectory('/'); + return; + } + // aborted ? + if (result.status === 0){ return; } @@ -288,24 +478,10 @@ window.FileList={ Files.updateStorageStatistics(true); if (result.data.permissions) { - FileList.setDirectoryPermissions(result.data.permissions); + this.setDirectoryPermissions(result.data.permissions); } - if (typeof(result.data.breadcrumb) !== 'undefined') { - $controls.find('.crumb').remove(); - $controls.prepend(result.data.breadcrumb); - - var width = $(window).width(); - Files.initBreadCrumbs(); - Files.resizeBreadcrumbs(width, true); - - // in case svg is not supported by the browser we need to execute the fallback mechanism - if (!SVGSupport()) { - replaceSVG(); - } - } - - FileList.update(result.data.files); + this.setFiles(result.data.files); }, setDirectoryPermissions: function(permissions) { var isCreatable = (permissions & OC.PERMISSION_CREATE) !== 0; @@ -322,10 +498,14 @@ window.FileList={ $('.actions,#file_action_panel').toggleClass('hidden', !show); if (show){ // make sure to display according to permissions - var permissions = $('#permissions').val(); + 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'); @@ -341,22 +521,32 @@ window.FileList={ this.showActions(!show); $('#filestable').toggleClass('hidden', show); }, - remove:function(name){ + /** + * 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 + */ + remove:function(name, options){ + options = options || {}; var fileEl = FileList.findFileEl(name); if (fileEl.data('permissions') & OC.PERMISSION_DELETE) { // file is only draggable when delete permissions are set fileEl.find('td.filename').draggable('destroy'); } fileEl.remove(); - FileList.updateFileSummary(); - if ( ! $('tr[data-file]').exists() ) { - $('#emptycontent').removeClass('hidden'); - $('#filescontent th').addClass('hidden'); + // TODO: improve performance on batch update + FileList.isEmpty = !this.$fileList.find('tr:not(.summary)').length; + if (typeof(options.updateSummary) === 'undefined' || !!options.updateSummary) { + FileList.updateEmptyContent(); + FileList.updateFileSummary(); } + return fileEl; }, insertElement:function(name, type, element) { - //find the correct spot to insert the file or folder - var pos, fileElements=$('tr[data-file][data-type="'+type+'"]:visible'); + // find the correct spot to insert the file or folder + var pos, + fileElements = this.$fileList.find('tr[data-file][data-type="'+type+'"]:not(.hidden)'); if (name.localeCompare($(fileElements[0]).attr('data-file')) < 0) { pos = -1; } else if (name.localeCompare($(fileElements[fileElements.length-1]).attr('data-file')) > 0) { @@ -376,35 +566,18 @@ window.FileList={ } else { $(fileElements[pos]).after(element); } - } else if (type === 'dir' && $('tr[data-file]').exists()) { - $('tr[data-file]').first().before(element); - } else if (type === 'file' && $('tr[data-file]').exists()) { - $('tr[data-file]').last().before(element); + } else if (type === 'dir' && !FileList.isEmpty) { + this.$fileList.find('tr[data-file]:first').before(element); + } else if (type === 'file' && !FileList.isEmpty) { + this.$fileList.find('tr[data-file]:last').before(element); } else { - $('#fileList').append(element); + this.$fileList.append(element); } - $('#emptycontent').addClass('hidden'); - $('#filestable th').removeClass('hidden'); + FileList.isEmpty = false; + FileList.updateEmptyContent(); FileList.updateFileSummary(); }, - loadingDone:function(name, id) { - var mime, tr = FileList.findFileEl(name); - tr.data('loading', false); - mime = tr.data('mime'); - tr.attr('data-mime', mime); - if (id) { - tr.attr('data-id', id); - } - var path = getPathForPreview(name); - Files.lazyLoadPreview(path, mime, function(previewpath) { - tr.find('td.filename').attr('style','background-image:url('+previewpath+')'); - }, null, null, tr.attr('data-etag')); - tr.find('td.filename').draggable(dragOptions); - }, - isLoading:function(file) { - return FileList.findFileEl(file).data('loading'); - }, - rename:function(oldname) { + rename: function(oldname) { var tr, td, input, form; tr = FileList.findFileEl(oldname); tr.data('renaming',true); @@ -438,6 +611,7 @@ window.FileList={ event.preventDefault(); try { var newname = input.val(); + var directory = FileList.getCurrentDirectory(); if (newname !== oldname) { checkInput(); // save background image, because it's replaced by a spinner while async request @@ -480,12 +654,12 @@ window.FileList={ tr.attr('data-mime', fileInfo.mime); tr.attr('data-etag', fileInfo.etag); if (fileInfo.isPreviewAvailable) { - Files.lazyLoadPreview(fileInfo.directory + '/' + fileInfo.name, result.data.mime, function(previewpath) { + Files.lazyLoadPreview(directory + '/' + fileInfo.name, result.data.mime, function(previewpath) { tr.find('td.filename').attr('style','background-image:url('+previewpath+')'); }, null, null, result.data.etag); } else { - tr.find('td.filename').removeClass('preview').attr('style','background-image:url('+fileInfo.icon+')'); + tr.find('td.filename').removeClass('preview').attr('style','background-image:url('+FileList._replaceSVG(fileInfo.icon)+')'); } } // reinsert row @@ -554,58 +728,12 @@ window.FileList={ inList:function(file) { return FileList.findFileEl(file).length; }, - replace:function(oldName, newName, isNewFile) { - // Finish any existing actions - var oldFileEl = FileList.findFileEl(oldName); - var newFileEl = FileList.findFileEl(newName); - oldFileEl.hide(); - newFileEl.hide(); - var tr = oldFileEl.clone(); - tr.attr('data-replace', 'true'); - tr.attr('data-file', newName); - var td = tr.children('td.filename'); - td.children('a.name .span').text(newName); - var path = td.children('a.name').attr('href'); - td.children('a.name').attr('href', path.replace(encodeURIComponent(oldName), encodeURIComponent(newName))); - var basename = newName; - if (newName.indexOf('.') > 0) { - basename = newName.substr(0, newName.lastIndexOf('.')); - } - td.children('a.name').empty(); - var span = $(''); - span.text(basename); - td.children('a.name').append(span); - if (newName.indexOf('.') > 0) { - span.append($(''+newName.substr(newName.lastIndexOf('.'))+'')); - } - FileList.insertElement(newName, tr.data('type'), tr); - tr.show(); - FileList.replaceCanceled = false; - FileList.replaceOldName = oldName; - FileList.replaceNewName = newName; - FileList.replaceIsNewFile = isNewFile; - FileList.lastAction = function() { - FileList.finishReplace(); - }; - if (!isNewFile) { - OC.Notification.showHtml(t('files', 'replaced {new_name} with {old_name}', {new_name: newName}, {old_name: oldName})+''+t('files', 'undo')+''); - } - }, - finishReplace:function() { - if (!FileList.replaceCanceled && FileList.replaceOldName && FileList.replaceNewName) { - $.ajax({url: OC.filePath('files', 'ajax', 'rename.php'), async: false, data: { dir: $('#dir').val(), newname: FileList.replaceNewName, file: FileList.replaceOldName }, success: function(result) { - if (result && result.status === 'success') { - $('tr[data-replace="true"').removeAttr('data-replace'); - } else { - OC.dialogs.alert(result.data.message, 'Error moving file'); - } - FileList.replaceCanceled = true; - FileList.replaceOldName = null; - FileList.replaceNewName = null; - FileList.lastAction = null; - }}); - } - }, + /** + * 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) { @@ -622,7 +750,7 @@ window.FileList={ FileList.lastAction(); } - var params = { + params = { dir: dir || FileList.getCurrentDirectory() }; if (files) { @@ -643,10 +771,9 @@ window.FileList={ } else { $.each(files,function(index,file) { - var files = FileList.findFileEl(file); - files.remove(); - files.find('input[type="checkbox"]').removeAttr('checked'); - files.removeClass('selected'); + var fileEl = FileList.remove(file, {updateSummary: false}); + fileEl.find('input[type="checkbox"]').prop('checked', false); + fileEl.removeClass('selected'); }); } procesSelection(); @@ -680,7 +807,7 @@ window.FileList={ }); }, createFileSummary: function() { - if( $('#fileList tr').exists() ) { + if ( !FileList.isEmpty ) { var summary = this._calculateFileSummary(); // Get translations @@ -702,7 +829,7 @@ window.FileList={ } var $summary = $(''+info+''+fileSize+''); - $('#fileList').append($summary); + this.$fileList.append($summary); var $dirInfo = $summary.find('.dirinfo'); var $fileInfo = $summary.find('.fileinfo'); @@ -710,12 +837,12 @@ window.FileList={ // Show only what's necessary, e.g.: no files: don't show "0 files" if (summary.totalDirs === 0) { - $dirInfo.hide(); - $connector.hide(); + $dirInfo.addClass('hidden'); + $connector.addClass('hidden'); } if (summary.totalFiles === 0) { - $fileInfo.hide(); - $connector.hide(); + $fileInfo.addClass('hidden'); + $connector.addClass('hidden'); } } }, @@ -740,10 +867,13 @@ window.FileList={ return result; }, updateFileSummary: function() { - var $summary = $('.summary'); + var $summary = this.$el.find('.summary'); + + // always make it the last element + this.$fileList.append($summary.detach()); // Check if we should remove the summary to show "Upload something" - if ($('#fileList tr').length === 1 && $summary.length === 1) { + if (this.isEmpty && $summary.length === 1) { $summary.remove(); } // If there's no summary create one (createFileSummary checks if there's data) @@ -751,7 +881,7 @@ window.FileList={ FileList.createFileSummary(); } // There's a summary and data -> Update the summary - else if ($('#fileList tr').length > 1 && $summary.length === 1) { + else if (!this.isEmpty && $summary.length === 1) { var fileSummary = this._calculateFileSummary(); var $dirInfo = $('.summary .dirinfo'); var $fileInfo = $('.summary .fileinfo'); @@ -764,19 +894,19 @@ window.FileList={ // Show only what's necessary (may be hidden) if (fileSummary.totalDirs === 0) { - $dirInfo.hide(); - $connector.hide(); + $dirInfo.addClass('hidden'); + $connector.addClass('hidden'); } else { - $dirInfo.show(); + $dirInfo.removeClass('hidden'); } if (fileSummary.totalFiles === 0) { - $fileInfo.hide(); - $connector.hide(); + $fileInfo.addClass('hidden'); + $connector.addClass('hidden'); } else { - $fileInfo.show(); + $fileInfo.removeClass('hidden'); } if (fileSummary.totalDirs > 0 && fileSummary.totalFiles > 0) { - $connector.show(); + $connector.removeClass('hidden'); } } }, @@ -784,10 +914,14 @@ window.FileList={ var $fileList = $('#fileList'); var permissions = $('#permissions').val(); var isCreatable = (permissions & OC.PERMISSION_CREATE) !== 0; - var exists = $fileList.find('tr:first').exists(); - $('#emptycontent').toggleClass('hidden', !isCreatable || exists); - $('#filestable th').toggleClass('hidden', !exists); + $('#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'); @@ -795,23 +929,23 @@ window.FileList={ return; } + this.$el.addClass('hidden'); + $mask = $('
'); $mask.css('background-image', 'url('+ OC.imagePath('core', 'loading.gif') + ')'); $mask.css('background-repeat', 'no-repeat'); $('#content').append($mask); - // block UI, but only make visible in case loading takes longer - FileList._maskTimeout = window.setTimeout(function() { - // reset opacity - $mask.removeClass('transparent'); - }, 250); + $mask.removeClass('transparent'); }, + /** + * Hide the loading mask. + * @see #showMask + */ hideMask: function() { - var $mask = $('#content .mask').remove(); - if (FileList._maskTimeout) { - window.clearTimeout(FileList._maskTimeout); - } + $('#content .mask').remove(); + this.$el.removeClass('hidden'); }, scrollTo:function(file) { //scroll to and highlight preselected file @@ -850,29 +984,11 @@ window.FileList={ */ isAllSelected: function() { return $('#select_all').prop('checked'); - }, - - /** - * Returns the download URL of the given file - * @param filename file name of the file - * @param dir optional directory in which the file name is, defaults to the current directory - */ - getDownloadUrl: function(filename, dir) { - var files = filename; - if ($.isArray(filename)) { - files = JSON.stringify(filename); - } - var params = { - dir: dir || FileList.getCurrentDirectory(), - files: files - }; - return OC.filePath('files', 'ajax', 'download.php') + '?' + OC.buildQueryString(params); } }; $(document).ready(function() { - var baseDir, - isPublic = !!$('#isPublic').val(); + FileList.initialize(); // handle upload events var file_upload_start = $('#file_upload_start'); @@ -907,8 +1023,13 @@ $(document).ready(function() { {name: 'requesttoken', value: oc_requesttoken} ]; }; + } else { + // cancel uploads to current dir if no permission + var isCreatable = (FileList.getDirectoryPermissions() & OC.PERMISSION_CREATE) !== 0; + if (!isCreatable) { + return false; + } } - }); file_upload_start.on('fileuploadadd', function(e, data) { OC.Upload.log('filelist handle fileuploadadd', e, data); @@ -993,31 +1114,11 @@ $(document).ready(function() { if (data.files[0].size>=0) { size=data.files[0].size; } - var date=new Date(); - var param = {}; - if ($('#publicUploadRequestToken').exists()) { - param.download_url = document.location.href + '&download&path=/' + $('#dir').val() + '/' + file.name; - } //should the file exist in the list remove it FileList.remove(file.name); // create new file context - data.context = FileList.addFile(file.name, file.size, date, false, false, param); - - // update file data - data.context.attr('data-mime',file.mime).attr('data-id',file.id).attr('data-etag', file.etag); - - var permissions = data.context.data('permissions'); - if (permissions !== file.permissions) { - data.context.attr('data-permissions', file.permissions); - data.context.data('permissions', file.permissions); - } - FileActions.display(data.context.find('td.filename'), true); - - var path = getPathForPreview(file.name); - Files.lazyLoadPreview(path, file.mime, function(previewpath) { - data.context.find('td.filename').attr('style','background-image:url('+previewpath+')'); - }, null, null, file.etag); + data.context = FileList.add(file, {insert: true}); } } }); @@ -1049,31 +1150,6 @@ $(document).ready(function() { }); $('#notification').hide(); - $('#notification').on('click', '.undo', function() { - if (FileList.deleteFiles) { - $.each(FileList.deleteFiles,function(index,file) { - FileList.findFileEl(file).show(); - }); - FileList.deleteCanceled=true; - FileList.deleteFiles=null; - } else if (FileList.replaceOldName && FileList.replaceNewName) { - if (FileList.replaceIsNewFile) { - // Delete the new uploaded file - FileList.deleteCanceled = false; - FileList.deleteFiles = [FileList.replaceOldName]; - } else { - FileList.findFileEl(FileList.replaceOldName).show(); - } - $('tr[data-replace="true"').remove(); - FileList.findFileEl(FileList.replaceNewName).show(); - FileList.replaceCanceled = true; - FileList.replaceOldName = null; - FileList.replaceNewName = null; - FileList.replaceIsNewFile = null; - } - FileList.lastAction = null; - OC.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')); @@ -1081,7 +1157,7 @@ $(document).ready(function() { }); $('#notification:first-child').on('click', '.suggest', function() { var file = $('#notification > span').attr('data-oldName'); - FileList.findFileEl(file).show(); + FileList.findFileEl(file).removeClass('hidden'); OC.Notification.hide(); }); $('#notification:first-child').on('click', '.cancel', function() { @@ -1130,34 +1206,32 @@ $(document).ready(function() { } // disable ajax/history API for public app (TODO: until it gets ported) - if (!isPublic) { - // 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); - } - }; - - if (parseInt($('#ajaxLoad').val(), 10) === 1) { - // need to initially switch the dir to the one from the hash (IE8) - FileList.changeDirectory(parseCurrentDirFromUrl(), false, true); - } - - FileList.setCurrentDir(parseCurrentDirFromUrl(), false); + // 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); + } + }; + + var dir = parseCurrentDirFromUrl(); + // trigger ajax load, deferred to let sub-apps do their overrides first + setTimeout(function() { + FileList.changeDirectory(dir, false, true); + }, 0); FileList.createFileSummary(); }); + diff --git a/apps/files/js/files.js b/apps/files/js/files.js index 1137364db4..4c2d87d808 100644 --- a/apps/files/js/files.js +++ b/apps/files/js/files.js @@ -161,80 +161,33 @@ var Files = { }); }, - lastWidth: 0, - - initBreadCrumbs: function () { - var $controls = $('#controls'); - - Files.lastWidth = 0; - Files.breadcrumbs = []; - - // initialize with some extra space - Files.breadcrumbsWidth = 64; - if ( document.getElementById("navigation") ) { - Files.breadcrumbsWidth += $('#navigation').get(0).offsetWidth; + /** + * Returns the download URL of the given file(s) + * @param filename string or array of file names to download + * @param dir optional directory in which the file name is, defaults to the current directory + */ + getDownloadUrl: function(filename, dir) { + if ($.isArray(filename)) { + filename = JSON.stringify(filename); } - Files.hiddenBreadcrumbs = 0; - - $.each($('.crumb'), function(index, breadcrumb) { - Files.breadcrumbs[index] = breadcrumb; - Files.breadcrumbsWidth += $(breadcrumb).get(0).offsetWidth; - }); - - $.each($('#controls .actions>div'), function(index, action) { - Files.breadcrumbsWidth += $(action).get(0).offsetWidth; - }); - - // event handlers for breadcrumb items - $controls.find('.crumb a').on('click', onClickBreadcrumb); - - // setup drag and drop - $controls.find('.crumb:not(.last)').droppable(crumbDropOptions); + var params = { + dir: dir || FileList.getCurrentDirectory(), + files: filename + }; + return this.getAjaxUrl('download', params); }, - resizeBreadcrumbs: function (width, firstRun) { - if (width !== Files.lastWidth) { - if ((width < Files.lastWidth || firstRun) && width < Files.breadcrumbsWidth) { - if (Files.hiddenBreadcrumbs === 0) { - bc = $(Files.breadcrumbs[1]).get(0); - if (typeof bc != 'undefined') { - Files.breadcrumbsWidth -= bc.offsetWidth; - $(Files.breadcrumbs[1]).find('a').hide(); - $(Files.breadcrumbs[1]).append('...'); - Files.breadcrumbsWidth += bc.offsetWidth; - Files.hiddenBreadcrumbs = 2; - } - } - var i = Files.hiddenBreadcrumbs; - while (width < Files.breadcrumbsWidth && i > 1 && i < Files.breadcrumbs.length - 1) { - Files.breadcrumbsWidth -= $(Files.breadcrumbs[i]).get(0).offsetWidth; - $(Files.breadcrumbs[i]).hide(); - Files.hiddenBreadcrumbs = i; - i++; - } - } else if (width > Files.lastWidth && Files.hiddenBreadcrumbs > 0) { - var i = Files.hiddenBreadcrumbs; - while (width > Files.breadcrumbsWidth && i > 0) { - if (Files.hiddenBreadcrumbs === 1) { - Files.breadcrumbsWidth -= $(Files.breadcrumbs[1]).get(0).offsetWidth; - $(Files.breadcrumbs[1]).find('span').remove(); - $(Files.breadcrumbs[1]).find('a').show(); - Files.breadcrumbsWidth += $(Files.breadcrumbs[1]).get(0).offsetWidth; - } else { - $(Files.breadcrumbs[i]).show(); - Files.breadcrumbsWidth += $(Files.breadcrumbs[i]).get(0).offsetWidth; - if (Files.breadcrumbsWidth > width) { - Files.breadcrumbsWidth -= $(Files.breadcrumbs[i]).get(0).offsetWidth; - $(Files.breadcrumbs[i]).hide(); - break; - } - } - i--; - Files.hiddenBreadcrumbs = i; - } - } - Files.lastWidth = width; + /** + * Returns the ajax URL for a given action + * @param action action string + * @param params optional params map + */ + getAjaxUrl: function(action, params) { + var q = ''; + if (params) { + q = '?' + OC.buildQueryString(params); } + return OC.filePath('files', 'ajax', action + '.php') + q; } }; $(document).ready(function() { @@ -245,14 +198,10 @@ $(document).ready(function() { Files.displayEncryptionWarning(); Files.bindKeyboardShortcuts(document, jQuery); - FileList.postProcessList(); Files.setupDragAndDrop(); $('#file_action_panel').attr('activeAction', false); - // allow dropping on the "files" app icon - $('ul#apps li:first-child').data('dir','').droppable(crumbDropOptions); - // Triggers invisible file input $('#upload a').on('click', function() { $(this).parent().children('#file_upload_start').trigger('click'); @@ -311,7 +260,7 @@ $(document).ready(function() { var filename=$(this).parent().parent().attr('data-file'); var tr = FileList.findFileEl(filename); var renaming=tr.data('renaming'); - if (!renaming && !FileList.isLoading(filename)) { + if (!renaming) { FileActions.currentFile = $(this).parent(); var mime=FileActions.getCurrentMimeType(); var type=FileActions.getCurrentType(); @@ -377,15 +326,15 @@ $(document).ready(function() { dir = OC.dirname(dir) || '/'; } else { - files = getSelectedFilesTrash('name'); + files = Files.getSelectedFiles('name'); } OC.Notification.show(t('files','Your download is being prepared. This might take some time if the files are big.')); - OC.redirect(FileList.getDownloadUrl(files, dir)); + OC.redirect(Files.getDownloadUrl(files, dir)); return false; }); $('.delete-selected').click(function(event) { - var files=getSelectedFilesTrash('name'); + var files = Files.getSelectedFiles('name'); event.preventDefault(); if (FileList.isAllSelected()) { files = null; @@ -403,16 +352,6 @@ $(document).ready(function() { //do a background scan if needed scanFiles(); - Files.initBreadCrumbs(); - - $(window).resize(function() { - var width = $(this).width(); - Files.resizeBreadcrumbs(width, false); - }); - - var width = $(this).width(); - Files.resizeBreadcrumbs(width, true); - // display storage warnings setTimeout(Files.displayStorageWarnings, 100); OC.Notification.setDefault(Files.displayStorageWarnings); @@ -503,7 +442,7 @@ var createDragShadow = function(event) { $(event.target).parents('tr').find('td input:first').prop('checked',true); } - var selectedFiles = getSelectedFilesTrash(); + var selectedFiles = Files.getSelectedFiles(); if (!isDragSelected && selectedFiles.length === 1) { //revert the selection @@ -619,52 +558,8 @@ var folderDropOptions={ tolerance: 'pointer' }; -var crumbDropOptions={ - drop: function( event, ui ) { - var target=$(this).data('dir'); - var dir = $('#dir').val(); - while(dir.substr(0,1) === '/') {//remove extra leading /'s - dir=dir.substr(1); - } - dir = '/' + dir; - if (dir.substr(-1,1) !== '/') { - dir = dir + '/'; - } - if (target === dir || target+'/' === dir) { - return; - } - var files = ui.helper.find('tr'); - $(files).each(function(i,row) { - var dir = $(row).data('dir'); - var file = $(row).data('filename'); - //slapdash selector, tracking down our original element that the clone budded off of. - var origin = $('tr[data-id=' + $(row).data('origin') + ']'); - var td = origin.children('td.filename'); - var oldBackgroundImage = td.css('background-image'); - td.css('background-image', 'url('+ OC.imagePath('core', 'loading.gif') + ')'); - $.post(OC.filePath('files', 'ajax', 'move.php'), { dir: dir, file: file, target: target }, function(result) { - if (result) { - if (result.status === 'success') { - FileList.remove(file); - procesSelection(); - $('#notification').hide(); - } else { - $('#notification').hide(); - $('#notification').text(result.data.message); - $('#notification').fadeIn(); - } - } else { - OC.dialogs.alert(t('files', 'Error moving file'), t('files', 'Error')); - } - td.css('background-image', oldBackgroundImage); - }); - }); - }, - tolerance: 'pointer' -}; - function procesSelection() { - var selected = getSelectedFilesTrash(); + var selected = Files.getSelectedFiles(); var selectedFiles = selected.filter(function(el) { return el.type==='file'; }); @@ -714,7 +609,7 @@ function procesSelection() { * if property is set, an array with that property for each file is returnd * if it's ommited an array of objects with all properties is returned */ -function getSelectedFilesTrash(property) { +Files.getSelectedFiles = function(property) { var elements=$('td.filename input:checkbox:checked').parent().parent(); var files=[]; elements.each(function(i,element) { @@ -755,25 +650,30 @@ function getPathForPreview(name) { return path; } +/** + * Generates a preview URL based on the URL space. + * @param urlSpec map with {x: width, y: height, file: file path} + * @return preview URL + */ +Files.generatePreviewUrl = function(urlSpec) { + urlSpec = urlSpec || {}; + if (!urlSpec.x) { + urlSpec.x = $('#filestable').data('preview-x'); + } + if (!urlSpec.y) { + urlSpec.y = $('#filestable').data('preview-y'); + } + urlSpec.forceIcon = 0; + return OC.generateUrl('/core/preview.png?') + $.param(urlSpec); +} + Files.lazyLoadPreview = function(path, mime, ready, width, height, etag) { // get mime icon url Files.getMimeIcon(mime, function(iconURL) { - var urlSpec = {}; var previewURL; + urlSpec = {}; ready(iconURL); // set mimeicon URL - // now try getting a preview thumbnail URL - if ( ! width ) { - width = $('#filestable').data('preview-x'); - } - if ( ! height ) { - height = $('#filestable').data('preview-y'); - } - // note: the order of arguments must match the one - // from the server's template so that the browser - // knows it's the same file for caching - urlSpec.x = width; - urlSpec.y = height; urlSpec.file = Files.fixPath(path); if (etag){ @@ -784,15 +684,9 @@ Files.lazyLoadPreview = function(path, mime, ready, width, height, etag) { console.warn('Files.lazyLoadPreview(): missing etag argument'); } - if ( $('#isPublic').length ) { - urlSpec.t = $('#dirToken').val(); - previewURL = OC.generateUrl('/publicpreview.png?') + $.param(urlSpec); - } else { - previewURL = OC.generateUrl('/core/preview.png?') + $.param(urlSpec); - } + previewURL = Files.generatePreviewUrl(urlSpec); previewURL = previewURL.replace('(', '%28'); previewURL = previewURL.replace(')', '%29'); - previewURL += '&forceIcon=0'; // preload image to prevent delay // this will make the browser cache the image @@ -841,14 +735,8 @@ function checkTrashStatus() { }); } -function onClickBreadcrumb(e) { - var $el = $(e.target).closest('.crumb'), - $targetDir = $el.data('dir'), - isPublic = !!$('#isPublic').val(); - - if ($targetDir !== undefined && !isPublic) { - e.preventDefault(); - FileList.changeDirectory(decodeURIComponent($targetDir)); - } +// override core's fileDownloadPath (legacy) +function fileDownloadPath(dir, file) { + return Files.getDownloadUrl(file, dir); } diff --git a/apps/files/lib/app.php b/apps/files/lib/app.php index fea88faa92..adfca66957 100644 --- a/apps/files/lib/app.php +++ b/apps/files/lib/app.php @@ -84,25 +84,7 @@ class App { ) { // successful rename $meta = $this->view->getFileInfo($dir . '/' . $newname); - if ($meta['mimetype'] === 'httpd/unix-directory') { - $meta['type'] = 'dir'; - } - else { - $meta['type'] = 'file'; - } - // these need to be set for determineIcon() - $meta['isPreviewAvailable'] = \OC::$server->getPreviewManager()->isMimeSupported($meta['mimetype']); - $meta['directory'] = $dir; - $fileinfo = array( - 'id' => $meta['fileid'], - 'mime' => $meta['mimetype'], - 'size' => $meta['size'], - 'etag' => $meta['etag'], - 'directory' => $meta['directory'], - 'name' => $newname, - 'isPreviewAvailable' => $meta['isPreviewAvailable'], - 'icon' => \OCA\Files\Helper::determineIcon($meta) - ); + $fileinfo = \OCA\Files\Helper::formatFileInfo($meta); $result['success'] = true; $result['data'] = $fileinfo; } else { diff --git a/apps/files/lib/helper.php b/apps/files/lib/helper.php index c41e2d1558..b765fdaf3e 100644 --- a/apps/files/lib/helper.php +++ b/apps/files/lib/helper.php @@ -19,11 +19,17 @@ class Helper 'usedSpacePercent' => (int)$storageInfo['relative']); } + /** + * Determine icon for a given file + * + * @param \OC\Files\FileInfo $file file info + * @return string icon URL + */ public static function determineIcon($file) { if($file['type'] === 'dir') { $dir = $file['directory']; $icon = \OC_Helper::mimetypeIcon('dir'); - $absPath = \OC\Files\Filesystem::getView()->getAbsolutePath($dir.'/'.$file['name']); + $absPath = $file->getPath(); $mount = \OC\Files\Filesystem::getMountManager()->find($absPath); if (!is_null($mount)) { $sid = $mount->getStorageId(); @@ -38,11 +44,7 @@ class Helper } } }else{ - if($file['isPreviewAvailable']) { - $pathForPreview = $file['directory'] . '/' . $file['name']; - return \OC_Helper::previewIcon($pathForPreview) . '&c=' . $file['etag']; - } - $icon = \OC_Helper::mimetypeIcon($file['mimetype']); + $icon = \OC_Helper::mimetypeIcon($file->getMimetype()); } return substr($icon, 0, -3) . 'svg'; @@ -69,52 +71,58 @@ class Helper } /** - * Retrieves the contents of the given directory and - * returns it as a sorted array. - * @param string $dir path to the directory - * @return array of files + * Formats the file info to be returned as JSON to the client. + * + * @param \OCP\Files\FileInfo file info + * @return array formatted file info */ - public static function getFiles($dir) { - $content = \OC\Files\Filesystem::getDirectoryContent($dir); - $files = array(); + public static function formatFileInfo($i) { + $entry = array(); - foreach ($content as $i) { - $i['date'] = \OCP\Util::formatDate($i['mtime']); - if ($i['type'] === 'file') { - $fileinfo = pathinfo($i['name']); - $i['basename'] = $fileinfo['filename']; - if (!empty($fileinfo['extension'])) { - $i['extension'] = '.' . $fileinfo['extension']; - } else { - $i['extension'] = ''; - } - } - $i['directory'] = $dir; - $i['isPreviewAvailable'] = \OC::$server->getPreviewManager()->isMimeSupported($i['mimetype']); - $i['icon'] = \OCA\Files\Helper::determineIcon($i); - $files[] = $i; + $entry['id'] = $i['fileid']; + $entry['date'] = \OCP\Util::formatDate($i['mtime']); + $entry['mtime'] = $i['mtime'] * 1000; + // only pick out the needed attributes + $entry['icon'] = \OCA\Files\Helper::determineIcon($i); + if (\OC::$server->getPreviewManager()->isMimeSupported($i['mimetype'])) { + $entry['isPreviewAvailable'] = true; } + $entry['name'] = $i['name']; + $entry['permissions'] = $i['permissions']; + $entry['mimetype'] = $i['mimetype']; + $entry['size'] = $i['size']; + $entry['type'] = $i['type']; + $entry['etag'] = $i['etag']; + if (isset($i['displayname_owner'])) { + $entry['shareOwner'] = $i['displayname_owner']; + } + return $entry; + } - usort($files, array('\OCA\Files\Helper', 'fileCmp')); + /** + * Format file info for JSON + * @param \OCP\Files\FileInfo[] $fileInfos file infos + */ + public static function formatFileInfos($fileInfos) { + $files = array(); + foreach ($fileInfos as $i) { + $files[] = self::formatFileInfo($i); + } return $files; } /** - * Splits the given path into a breadcrumb structure. - * @param string $dir path to process - * @return array where each entry is a hash of the absolute - * directory path and its name + * Retrieves the contents of the given directory and + * returns it as a sorted array of FileInfo. + * + * @param string $dir path to the directory + * @return \OCP\Files\FileInfo[] files */ - public static function makeBreadcrumb($dir){ - $breadcrumb = array(); - $pathtohere = ''; - foreach (explode('/', $dir) as $i) { - if ($i !== '') { - $pathtohere .= '/' . $i; - $breadcrumb[] = array('dir' => $pathtohere, 'name' => $i); - } - } - return $breadcrumb; + public static function getFiles($dir) { + $content = \OC\Files\Filesystem::getDirectoryContent($dir); + + usort($content, array('\OCA\Files\Helper', 'fileCmp')); + return $content; } } diff --git a/apps/files/templates/index.php b/apps/files/templates/index.php index 34acd9c4f5..95edd625cb 100644 --- a/apps/files/templates/index.php +++ b/apps/files/templates/index.php @@ -1,6 +1,5 @@
- -
+
-
+
-
class="hidden">t('Nothing in here. Upload something!'))?>
+ - - - + -
class="hidden" id='headerName'> + class="hidden" id="headerSize">t('Size')); ?>class="hidden" id="headerDate"> +
@@ -111,7 +109,6 @@ - diff --git a/apps/files/templates/part.breadcrumb.php b/apps/files/templates/part.breadcrumb.php deleted file mode 100644 index 69b4cbca10..0000000000 --- a/apps/files/templates/part.breadcrumb.php +++ /dev/null @@ -1,17 +0,0 @@ -
" data-dir=''> - - - - - -
- -
svg" - data-dir=''> - -
- -160) $relative_date_color = 160; - $name = \OCP\Util::encodePath($file['name']); - $directory = \OCP\Util::encodePath($file['directory']); ?> - - data-share-owner="" - - > - - - - - style="background-image:url()" - > - - - - - - - - - - - - - - - - - - - - - - - - - - - - -viewMock->expects($this->any()) ->method('getFileInfo') - ->will($this->returnValue(array( + ->will($this->returnValue(new \OC\Files\FileInfo( + '/test', + null, + '/test', + array( 'fileid' => 123, 'type' => 'dir', 'mimetype' => 'httpd/unix-directory', + 'mtime' => 0, + 'permissions' => 31, 'size' => 18, 'etag' => 'abcdef', 'directory' => '/', 'name' => 'new_name', - ))); + )))); $result = $this->files->rename($dir, $oldname, $newname); $this->assertTrue($result['success']); $this->assertEquals(123, $result['data']['id']); $this->assertEquals('new_name', $result['data']['name']); - $this->assertEquals('/test', $result['data']['directory']); $this->assertEquals(18, $result['data']['size']); - $this->assertEquals('httpd/unix-directory', $result['data']['mime']); + $this->assertEquals('httpd/unix-directory', $result['data']['mimetype']); $icon = \OC_Helper::mimetypeIcon('dir'); $icon = substr($icon, 0, -3) . 'svg'; $this->assertEquals($icon, $result['data']['icon']); - $this->assertFalse($result['data']['isPreviewAvailable']); } /** @@ -148,29 +152,33 @@ class Test_OC_Files_App_Rename extends \PHPUnit_Framework_TestCase { $this->viewMock->expects($this->any()) ->method('getFileInfo') - ->will($this->returnValue(array( + ->will($this->returnValue(new \OC\Files\FileInfo( + '/', + null, + '/', + array( 'fileid' => 123, 'type' => 'dir', 'mimetype' => 'httpd/unix-directory', + 'mtime' => 0, + 'permissions' => 31, 'size' => 18, 'etag' => 'abcdef', 'directory' => '/', 'name' => 'new_name', - ))); + )))); $result = $this->files->rename($dir, $oldname, $newname); $this->assertTrue($result['success']); $this->assertEquals(123, $result['data']['id']); - $this->assertEquals('newname', $result['data']['name']); - $this->assertEquals('/', $result['data']['directory']); + $this->assertEquals('new_name', $result['data']['name']); $this->assertEquals(18, $result['data']['size']); - $this->assertEquals('httpd/unix-directory', $result['data']['mime']); + $this->assertEquals('httpd/unix-directory', $result['data']['mimetype']); $this->assertEquals('abcdef', $result['data']['etag']); $icon = \OC_Helper::mimetypeIcon('dir'); $icon = substr($icon, 0, -3) . 'svg'; $this->assertEquals($icon, $result['data']['icon']); - $this->assertFalse($result['data']['isPreviewAvailable']); } /** diff --git a/apps/files/tests/js/breadcrumbSpec.js b/apps/files/tests/js/breadcrumbSpec.js new file mode 100644 index 0000000000..1bfe5308a2 --- /dev/null +++ b/apps/files/tests/js/breadcrumbSpec.js @@ -0,0 +1,248 @@ +/** +* ownCloud +* +* @author Vincent Petry +* @copyright 2014 Vincent Petry +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE +* License as published by the Free Software Foundation; either +* version 3 of the License, or any later version. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU AFFERO GENERAL PUBLIC LICENSE for more details. +* +* You should have received a copy of the GNU Affero General Public +* License along with this library. If not, see . +* +*/ + +/* global BreadCrumb */ +describe('BreadCrumb tests', function() { + describe('Rendering', function() { + var bc; + beforeEach(function() { + bc = new BreadCrumb({ + getCrumbUrl: function(part, index) { + // for testing purposes + return part.dir + '#' + index; + } + }); + }); + afterEach(function() { + bc = null; + }); + it('Renders its own container', function() { + bc.render(); + expect(bc.$el.hasClass('breadcrumb')).toEqual(true); + }); + it('Renders root by default', function() { + var $crumbs; + bc.render(); + $crumbs = bc.$el.find('.crumb'); + expect($crumbs.length).toEqual(1); + expect($crumbs.eq(0).find('a').attr('href')).toEqual('/#0'); + expect($crumbs.eq(0).find('img').length).toEqual(1); + expect($crumbs.eq(0).attr('data-dir')).toEqual('/'); + }); + it('Renders root when switching to root', function() { + var $crumbs; + bc.setDirectory('/somedir'); + bc.setDirectory('/'); + $crumbs = bc.$el.find('.crumb'); + expect($crumbs.length).toEqual(1); + expect($crumbs.eq(0).attr('data-dir')).toEqual('/'); + }); + it('Renders last crumb with "last" class', function() { + bc.setDirectory('/abc/def'); + expect(bc.$el.find('.crumb:last').hasClass('last')).toEqual(true); + }); + it('Renders single path section', function() { + var $crumbs; + bc.setDirectory('/somedir'); + $crumbs = bc.$el.find('.crumb'); + expect($crumbs.length).toEqual(2); + expect($crumbs.eq(0).find('a').attr('href')).toEqual('/#0'); + expect($crumbs.eq(0).find('img').length).toEqual(1); + expect($crumbs.eq(0).attr('data-dir')).toEqual('/'); + expect($crumbs.eq(1).find('a').attr('href')).toEqual('/somedir#1'); + expect($crumbs.eq(1).find('img').length).toEqual(0); + expect($crumbs.eq(1).attr('data-dir')).toEqual('/somedir'); + }); + it('Renders multiple path sections and special chars', function() { + var $crumbs; + bc.setDirectory('/somedir/with space/abc'); + $crumbs = bc.$el.find('.crumb'); + expect($crumbs.length).toEqual(4); + expect($crumbs.eq(0).find('a').attr('href')).toEqual('/#0'); + expect($crumbs.eq(0).find('img').length).toEqual(1); + expect($crumbs.eq(0).attr('data-dir')).toEqual('/'); + + expect($crumbs.eq(1).find('a').attr('href')).toEqual('/somedir#1'); + expect($crumbs.eq(1).find('img').length).toEqual(0); + expect($crumbs.eq(1).attr('data-dir')).toEqual('/somedir'); + + expect($crumbs.eq(2).find('a').attr('href')).toEqual('/somedir/with space#2'); + expect($crumbs.eq(2).find('img').length).toEqual(0); + expect($crumbs.eq(2).attr('data-dir')).toEqual('/somedir/with space'); + + expect($crumbs.eq(3).find('a').attr('href')).toEqual('/somedir/with space/abc#3'); + expect($crumbs.eq(3).find('img').length).toEqual(0); + expect($crumbs.eq(3).attr('data-dir')).toEqual('/somedir/with space/abc'); + }); + }); + describe('Events', function() { + it('Calls onClick handler when clicking on a crumb', function() { + var handler = sinon.stub(); + var bc = new BreadCrumb({ + onClick: handler + }); + bc.setDirectory('/one/two/three/four'); + bc.$el.find('.crumb:eq(3)').click(); + expect(handler.calledOnce).toEqual(true); + expect(handler.getCall(0).thisValue).toEqual(bc.$el.find('.crumb').get(3)); + + handler.reset(); + bc.$el.find('.crumb:eq(0) a').click(); + expect(handler.calledOnce).toEqual(true); + expect(handler.getCall(0).thisValue).toEqual(bc.$el.find('.crumb').get(0)); + }); + it('Calls onDrop handler when dropping on a crumb', function() { + var droppableStub = sinon.stub($.fn, 'droppable'); + var handler = sinon.stub(); + var bc = new BreadCrumb({ + onDrop: handler + }); + bc.setDirectory('/one/two/three/four'); + expect(droppableStub.calledOnce).toEqual(true); + + expect(droppableStub.getCall(0).args[0].drop).toBeDefined(); + // simulate drop + droppableStub.getCall(0).args[0].drop({dummy: true}); + + expect(handler.calledOnce).toEqual(true); + expect(handler.getCall(0).args[0]).toEqual({dummy: true}); + + droppableStub.restore(); + }); + }); + describe('Resizing', function() { + var bc, widthStub, dummyDir, + oldUpdateTotalWidth; + + beforeEach(function() { + dummyDir = '/short name/longer name/looooooooooooonger/even longer long long long longer long/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/last one'; + + oldUpdateTotalWidth = BreadCrumb.prototype._updateTotalWidth; + BreadCrumb.prototype._updateTotalWidth = function() { + // need to set display:block for correct offsetWidth (no CSS loaded here) + $('div.crumb').css({ + 'display': 'block', + 'float': 'left' + }); + + return oldUpdateTotalWidth.apply(this, arguments); + }; + + bc = new BreadCrumb(); + widthStub = sinon.stub($.fn, 'width'); + // append dummy navigation and controls + // as they are currently used for measurements + $('#testArea').append( + '', + '
' + ); + + // make sure we know the test screen width + $('#testArea').css('width', 1280); + + // use test area as we need it for measurements + $('#controls').append(bc.$el); + $('#controls').append('
Dummy action with a given width
'); + }); + afterEach(function() { + BreadCrumb.prototype._updateTotalWidth = oldUpdateTotalWidth; + widthStub.restore(); + bc = null; + }); + it('Hides breadcrumbs to fit window', function() { + var $crumbs; + + widthStub.returns(500); + // triggers resize implicitly + bc.setDirectory(dummyDir); + $crumbs = bc.$el.find('.crumb'); + + // first one is always visible + expect($crumbs.eq(0).hasClass('hidden')).toEqual(false); + // second one has ellipsis + expect($crumbs.eq(1).hasClass('hidden')).toEqual(false); + expect($crumbs.eq(1).find('.ellipsis').length).toEqual(1); + // there is only one ellipsis in total + expect($crumbs.find('.ellipsis').length).toEqual(1); + // subsequent elements are hidden + expect($crumbs.eq(2).hasClass('hidden')).toEqual(true); + expect($crumbs.eq(3).hasClass('hidden')).toEqual(true); + expect($crumbs.eq(4).hasClass('hidden')).toEqual(true); + expect($crumbs.eq(5).hasClass('hidden')).toEqual(true); + expect($crumbs.eq(6).hasClass('hidden')).toEqual(false); + }); + it('Updates ellipsis on window size increase', function() { + var $crumbs; + + widthStub.returns(500); + // triggers resize implicitly + bc.setDirectory(dummyDir); + $crumbs = bc.$el.find('.crumb'); + + // simulate increase + $('#testArea').css('width', 1800); + bc.resize(1800); + + // first one is always visible + expect($crumbs.eq(0).hasClass('hidden')).toEqual(false); + // second one has ellipsis + expect($crumbs.eq(1).hasClass('hidden')).toEqual(false); + expect($crumbs.eq(1).find('.ellipsis').length).toEqual(1); + // there is only one ellipsis in total + expect($crumbs.find('.ellipsis').length).toEqual(1); + // subsequent elements are hidden + expect($crumbs.eq(2).hasClass('hidden')).toEqual(true); + expect($crumbs.eq(3).hasClass('hidden')).toEqual(true); + expect($crumbs.eq(4).hasClass('hidden')).toEqual(true); + // the rest is visible + expect($crumbs.eq(5).hasClass('hidden')).toEqual(false); + expect($crumbs.eq(6).hasClass('hidden')).toEqual(false); + }); + it('Updates ellipsis on window size decrease', function() { + var $crumbs; + + $('#testArea').css('width', 2000); + widthStub.returns(2000); + // triggers resize implicitly + bc.setDirectory(dummyDir); + $crumbs = bc.$el.find('.crumb'); + + // simulate decrease + bc.resize(500); + $('#testArea').css('width', 500); + + // first one is always visible + expect($crumbs.eq(0).hasClass('hidden')).toEqual(false); + // second one has ellipsis + expect($crumbs.eq(1).hasClass('hidden')).toEqual(false); + expect($crumbs.eq(1).find('.ellipsis').length).toEqual(1); + // there is only one ellipsis in total + expect($crumbs.find('.ellipsis').length).toEqual(1); + // subsequent elements are hidden + expect($crumbs.eq(2).hasClass('hidden')).toEqual(true); + expect($crumbs.eq(3).hasClass('hidden')).toEqual(true); + expect($crumbs.eq(4).hasClass('hidden')).toEqual(true); + // the rest is visible + expect($crumbs.eq(5).hasClass('hidden')).toEqual(true); + expect($crumbs.eq(6).hasClass('hidden')).toEqual(false); + }); + }); +}); diff --git a/apps/files/tests/js/fileactionsSpec.js b/apps/files/tests/js/fileactionsSpec.js index 80c04b5b24..3c22c84b86 100644 --- a/apps/files/tests/js/fileactionsSpec.js +++ b/apps/files/tests/js/fileactionsSpec.js @@ -22,6 +22,7 @@ /* global OC, FileActions, FileList */ describe('FileActions tests', function() { var $filesTable; + beforeEach(function() { // init horrible parameters var $body = $('body'); @@ -34,17 +35,20 @@ describe('FileActions tests', function() { $('#dir, #permissions, #filestable').remove(); }); it('calling display() sets file actions', function() { - // note: download_url is actually the link target, not the actual download URL... - var $tr = FileList.addFile('testName.txt', 1234, new Date(), false, false, {download_url: 'test/download/url'}); + var fileData = { + id: 18, + type: 'file', + name: 'testName.txt', + mimetype: 'plain/text', + size: '1234', + etag: 'a01234c', + mtime: '123456' + }; - // no actions before call - expect($tr.find('.action.action-download').length).toEqual(0); - expect($tr.find('.action.action-rename').length).toEqual(0); - expect($tr.find('.action.delete').length).toEqual(0); + // note: FileActions.display() is called implicitly + var $tr = FileList.add(fileData); - FileActions.display($tr.find('td.filename'), true); - - // actions defined after cal + // actions defined after call expect($tr.find('.action.action-download').length).toEqual(1); expect($tr.find('.action.action-download').attr('data-action')).toEqual('Download'); expect($tr.find('.nametext .action.action-rename').length).toEqual(1); @@ -52,7 +56,16 @@ describe('FileActions tests', function() { expect($tr.find('.action.delete').length).toEqual(1); }); it('calling display() twice correctly replaces file actions', function() { - var $tr = FileList.addFile('testName.txt', 1234, new Date(), false, false, {download_url: 'test/download/url'}); + var fileData = { + id: 18, + type: 'file', + name: 'testName.txt', + mimetype: 'plain/text', + size: '1234', + etag: 'a01234c', + mtime: '123456' + }; + var $tr = FileList.add(fileData); FileActions.display($tr.find('td.filename'), true); FileActions.display($tr.find('td.filename'), true); @@ -64,19 +77,36 @@ describe('FileActions tests', function() { }); it('redirects to download URL when clicking download', function() { var redirectStub = sinon.stub(OC, 'redirect'); - // note: download_url is actually the link target, not the actual download URL... - var $tr = FileList.addFile('test download File.txt', 1234, new Date(), false, false, {download_url: 'test/download/url'}); + var fileData = { + id: 18, + type: 'file', + name: 'testName.txt', + mimetype: 'plain/text', + size: '1234', + etag: 'a01234c', + mtime: '123456' + }; + var $tr = FileList.add(fileData); FileActions.display($tr.find('td.filename'), true); $tr.find('.action-download').click(); expect(redirectStub.calledOnce).toEqual(true); - expect(redirectStub.getCall(0).args[0]).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fsubdir&files=test%20download%20File.txt'); + expect(redirectStub.getCall(0).args[0]).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fsubdir&files=testName.txt'); redirectStub.restore(); }); it('deletes file when clicking delete', function() { var deleteStub = sinon.stub(FileList, 'do_delete'); - var $tr = FileList.addFile('test delete File.txt', 1234, new Date()); + var fileData = { + id: 18, + type: 'file', + name: 'testName.txt', + mimetype: 'plain/text', + size: '1234', + etag: 'a01234c', + mtime: '123456' + }; + var $tr = FileList.add(fileData); FileActions.display($tr.find('td.filename'), true); $tr.find('.action.delete').click(); diff --git a/apps/files/tests/js/filelistSpec.js b/apps/files/tests/js/filelistSpec.js index 8f4cb86ab4..ca85a360cf 100644 --- a/apps/files/tests/js/filelistSpec.js +++ b/apps/files/tests/js/filelistSpec.js @@ -21,6 +21,9 @@ /* global OC, FileList */ describe('FileList tests', function() { + var testFiles, alertStub, notificationStub, + pushStateStub; + beforeEach(function() { // init horrible parameters var $body = $('body'); @@ -28,45 +31,784 @@ describe('FileList tests', function() { $body.append(''); // dummy files table $body.append('
'); + + // prevents URL changes during tests + pushStateStub = sinon.stub(window.history, 'pushState'); + + alertStub = sinon.stub(OC.dialogs, 'alert'); + notificationStub = sinon.stub(OC.Notification, 'show'); + + // init parameters and test table elements + $('#testArea').append( + '' + + '' + + // dummy controls + '
' + + '
' + + '
' + + '
' + + // dummy table + '' + + '' + + '' + + '
' + + '
Empty content message
' + ); + + testFiles = [{ + id: 1, + type: 'file', + name: 'One.txt', + mimetype: 'text/plain', + size: 12 + }, { + id: 2, + type: 'file', + name: 'Two.jpg', + mimetype: 'image/jpeg', + size: 12049 + }, { + id: 3, + type: 'file', + name: 'Three.pdf', + mimetype: 'application/pdf', + size: 58009 + }, { + id: 4, + type: 'dir', + name: 'somedir', + mimetype: 'httpd/unix-directory', + size: 250 + }]; + + FileList.initialize(); }); afterEach(function() { + testFiles = undefined; + FileList.initialized = false; + FileList.isEmpty = true; + delete FileList._reloadCall; + $('#dir, #permissions, #filestable').remove(); + notificationStub.restore(); + alertStub.restore(); + pushStateStub.restore(); }); - it('generates file element with correct attributes when calling addFile', function() { - var lastMod = new Date(10000); - // note: download_url is actually the link target, not the actual download URL... - var $tr = FileList.addFile('testName.txt', 1234, lastMod, false, false, {download_url: 'test/download/url'}); - - expect($tr).toBeDefined(); - expect($tr[0].tagName.toLowerCase()).toEqual('tr'); - expect($tr.find('a:first').attr('href')).toEqual('test/download/url'); - expect($tr.attr('data-type')).toEqual('file'); - expect($tr.attr('data-file')).toEqual('testName.txt'); - expect($tr.attr('data-size')).toEqual('1234'); - expect($tr.attr('data-permissions')).toEqual('31'); - //expect($tr.attr('data-mime')).toEqual('plain/text'); + describe('Getters', function() { + it('Returns the current directory', function() { + $('#dir').val('/one/two/three'); + expect(FileList.getCurrentDirectory()).toEqual('/one/two/three'); + }); + it('Returns the directory permissions as int', function() { + $('#permissions').val('23'); + expect(FileList.getDirectoryPermissions()).toEqual(23); + }); }); - it('generates dir element with correct attributes when calling addDir', function() { - var lastMod = new Date(10000); - var $tr = FileList.addDir('testFolder', 1234, lastMod, false); + describe('Adding files', function() { + var clock, now; + beforeEach(function() { + // to prevent date comparison issues + clock = sinon.useFakeTimers(); + now = new Date(); + }); + afterEach(function() { + clock.restore(); + }); + it('generates file element with correct attributes when calling add() with file data', function() { + var fileData = { + id: 18, + type: 'file', + name: 'testName.txt', + mimetype: 'plain/text', + size: '1234', + etag: 'a01234c', + mtime: '123456' + }; + var $tr = FileList.add(fileData); - expect($tr).toBeDefined(); - expect($tr[0].tagName.toLowerCase()).toEqual('tr'); - expect($tr.attr('data-type')).toEqual('dir'); - expect($tr.attr('data-file')).toEqual('testFolder'); - expect($tr.attr('data-size')).toEqual('1234'); - expect($tr.attr('data-permissions')).toEqual('31'); - //expect($tr.attr('data-mime')).toEqual('httpd/unix-directory'); + expect($tr).toBeDefined(); + expect($tr[0].tagName.toLowerCase()).toEqual('tr'); + expect($tr.attr('data-id')).toEqual('18'); + expect($tr.attr('data-type')).toEqual('file'); + expect($tr.attr('data-file')).toEqual('testName.txt'); + expect($tr.attr('data-size')).toEqual('1234'); + expect($tr.attr('data-etag')).toEqual('a01234c'); + expect($tr.attr('data-permissions')).toEqual('31'); + expect($tr.attr('data-mime')).toEqual('plain/text'); + expect($tr.attr('data-mtime')).toEqual('123456'); + expect($tr.find('a.name').attr('href')).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fsubdir&files=testName.txt'); + + expect($tr.find('.filesize').text()).toEqual('1 kB'); + expect(FileList.findFileEl('testName.txt')[0]).toEqual($tr[0]); + }); + it('generates dir element with correct attributes when calling add() with dir data', function() { + var fileData = { + id: 19, + type: 'dir', + name: 'testFolder', + mimetype: 'httpd/unix-directory', + size: '1234', + etag: 'a01234c', + mtime: '123456' + }; + var $tr = FileList.add(fileData); + + expect($tr).toBeDefined(); + expect($tr[0].tagName.toLowerCase()).toEqual('tr'); + expect($tr.attr('data-id')).toEqual('19'); + expect($tr.attr('data-type')).toEqual('dir'); + expect($tr.attr('data-file')).toEqual('testFolder'); + expect($tr.attr('data-size')).toEqual('1234'); + expect($tr.attr('data-etag')).toEqual('a01234c'); + expect($tr.attr('data-permissions')).toEqual('31'); + expect($tr.attr('data-mime')).toEqual('httpd/unix-directory'); + expect($tr.attr('data-mtime')).toEqual('123456'); + + expect($tr.find('.filesize').text()).toEqual('1 kB'); + + expect(FileList.findFileEl('testFolder')[0]).toEqual($tr[0]); + }); + it('generates file element with default attributes when calling add() with minimal data', function() { + var fileData = { + type: 'file', + name: 'testFile.txt' + }; + + clock.tick(123456); + var $tr = FileList.add(fileData); + + expect($tr).toBeDefined(); + expect($tr[0].tagName.toLowerCase()).toEqual('tr'); + expect($tr.attr('data-id')).toEqual(null); + expect($tr.attr('data-type')).toEqual('file'); + expect($tr.attr('data-file')).toEqual('testFile.txt'); + expect($tr.attr('data-size')).toEqual(null); + expect($tr.attr('data-etag')).toEqual(null); + expect($tr.attr('data-permissions')).toEqual('31'); + expect($tr.attr('data-mime')).toEqual(null); + expect($tr.attr('data-mtime')).toEqual('123456'); + + expect($tr.find('.filesize').text()).toEqual('Pending'); + }); + it('generates dir element with default attributes when calling add() with minimal data', function() { + var fileData = { + type: 'dir', + name: 'testFolder' + }; + clock.tick(123456); + var $tr = FileList.add(fileData); + + expect($tr).toBeDefined(); + expect($tr[0].tagName.toLowerCase()).toEqual('tr'); + expect($tr.attr('data-id')).toEqual(null); + expect($tr.attr('data-type')).toEqual('dir'); + expect($tr.attr('data-file')).toEqual('testFolder'); + expect($tr.attr('data-size')).toEqual(null); + expect($tr.attr('data-etag')).toEqual(null); + expect($tr.attr('data-permissions')).toEqual('31'); + expect($tr.attr('data-mime')).toEqual('httpd/unix-directory'); + expect($tr.attr('data-mtime')).toEqual('123456'); + + expect($tr.find('.filesize').text()).toEqual('Pending'); + }); + it('generates file element with zero size when size is explicitly zero', function() { + var fileData = { + type: 'dir', + name: 'testFolder', + size: '0' + }; + var $tr = FileList.add(fileData); + expect($tr.find('.filesize').text()).toEqual('0 B'); + }); + it('adds new file to the end of the list before the summary', function() { + var fileData = { + type: 'file', + name: 'P comes after O.txt' + }; + FileList.setFiles(testFiles); + $tr = FileList.add(fileData); + expect($tr.index()).toEqual(4); + expect($tr.next().hasClass('summary')).toEqual(true); + }); + it('adds new file at correct position in insert mode', function() { + var fileData = { + type: 'file', + name: 'P comes after O.txt' + }; + FileList.setFiles(testFiles); + $tr = FileList.add(fileData, {insert: true}); + // after "One.txt" + expect($tr.index()).toEqual(1); + }); + it('removes empty content message and shows summary when adding first file', function() { + var fileData = { + type: 'file', + name: 'first file.txt', + size: 12 + }; + FileList.setFiles([]); + expect(FileList.isEmpty).toEqual(true); + FileList.add(fileData); + $summary = $('#fileList .summary'); + expect($summary.length).toEqual(1); + // yes, ugly... + expect($summary.find('.info').text()).toEqual('0 folders and 1 file'); + expect($summary.find('.dirinfo').hasClass('hidden')).toEqual(true); + expect($summary.find('.fileinfo').hasClass('hidden')).toEqual(false); + expect($summary.find('.filesize').text()).toEqual('12 B'); + expect($('#filestable thead th').hasClass('hidden')).toEqual(false); + expect($('#emptycontent').hasClass('hidden')).toEqual(true); + expect(FileList.isEmpty).toEqual(false); + }); + }); + describe('Removing files from the list', function() { + it('Removes file from list when calling remove() and updates summary', function() { + var $removedEl; + FileList.setFiles(testFiles); + $removedEl = FileList.remove('One.txt'); + expect($removedEl).toBeDefined(); + expect($removedEl.attr('data-file')).toEqual('One.txt'); + expect($('#fileList tr:not(.summary)').length).toEqual(3); + expect(FileList.findFileEl('One.txt').length).toEqual(0); + + $summary = $('#fileList .summary'); + expect($summary.length).toEqual(1); + expect($summary.find('.info').text()).toEqual('1 folder and 2 files'); + expect($summary.find('.dirinfo').hasClass('hidden')).toEqual(false); + expect($summary.find('.fileinfo').hasClass('hidden')).toEqual(false); + expect($summary.find('.filesize').text()).toEqual('69 kB'); + expect(FileList.isEmpty).toEqual(false); + }); + it('Shows empty content when removing last file', function() { + FileList.setFiles([testFiles[0]]); + FileList.remove('One.txt'); + expect($('#fileList tr:not(.summary)').length).toEqual(0); + expect(FileList.findFileEl('One.txt').length).toEqual(0); + + $summary = $('#fileList .summary'); + expect($summary.length).toEqual(0); + expect($('#filestable thead th').hasClass('hidden')).toEqual(true); + expect($('#emptycontent').hasClass('hidden')).toEqual(false); + expect(FileList.isEmpty).toEqual(true); + }); + }); + describe('Deleting files', function() { + function doDelete() { + var request, query; + // note: normally called from FileActions + FileList.do_delete(['One.txt', 'Two.jpg']); + + expect(fakeServer.requests.length).toEqual(1); + request = fakeServer.requests[0]; + expect(request.url).toEqual(OC.webroot + '/index.php/apps/files/ajax/delete.php'); + + query = fakeServer.requests[0].requestBody; + expect(OC.parseQueryString(query)).toEqual({'dir': '/subdir', files: '["One.txt","Two.jpg"]'}); + } + it('calls delete.php, removes the deleted entries and updates summary', function() { + FileList.setFiles(testFiles); + doDelete(); + + fakeServer.requests[0].respond( + 200, + { 'Content-Type': 'application/json' }, + JSON.stringify({status: 'success'}) + ); + + expect(FileList.findFileEl('One.txt').length).toEqual(0); + expect(FileList.findFileEl('Two.jpg').length).toEqual(0); + expect(FileList.findFileEl('Three.pdf').length).toEqual(1); + expect(FileList.$fileList.find('tr:not(.summary)').length).toEqual(2); + + $summary = $('#fileList .summary'); + expect($summary.length).toEqual(1); + expect($summary.find('.info').text()).toEqual('1 folder and 1 file'); + expect($summary.find('.dirinfo').hasClass('hidden')).toEqual(false); + expect($summary.find('.fileinfo').hasClass('hidden')).toEqual(false); + expect($summary.find('.filesize').text()).toEqual('57 kB'); + expect(FileList.isEmpty).toEqual(false); + expect($('#filestable thead th').hasClass('hidden')).toEqual(false); + expect($('#emptycontent').hasClass('hidden')).toEqual(true); + + expect(notificationStub.notCalled).toEqual(true); + }); + it('updates summary when deleting last file', function() { + FileList.setFiles([testFiles[0], testFiles[1]]); + doDelete(); + + fakeServer.requests[0].respond( + 200, + { 'Content-Type': 'application/json' }, + JSON.stringify({status: 'success'}) + ); + + expect(FileList.$fileList.find('tr:not(.summary)').length).toEqual(0); + + $summary = $('#fileList .summary'); + expect($summary.length).toEqual(0); + expect(FileList.isEmpty).toEqual(true); + expect($('#filestable thead th').hasClass('hidden')).toEqual(true); + expect($('#emptycontent').hasClass('hidden')).toEqual(false); + }); + it('bring back deleted item when delete call failed', function() { + FileList.setFiles(testFiles); + doDelete(); + + fakeServer.requests[0].respond( + 200, + { 'Content-Type': 'application/json' }, + JSON.stringify({status: 'error', data: {message: 'WOOT'}}) + ); + + // files are still in the list + expect(FileList.findFileEl('One.txt').length).toEqual(1); + expect(FileList.findFileEl('Two.jpg').length).toEqual(1); + expect(FileList.$fileList.find('tr:not(.summary)').length).toEqual(4); + + expect(notificationStub.calledOnce).toEqual(true); + }); + }); + describe('Renaming files', function() { + function doRename() { + var $input, request; + + FileList.setFiles(testFiles); + + // trigger rename prompt + FileList.rename('One.txt'); + $input = FileList.$fileList.find('input.filename'); + $input.val('One_renamed.txt').blur(); + + expect(fakeServer.requests.length).toEqual(1); + var request = fakeServer.requests[0]; + expect(request.url.substr(0, request.url.indexOf('?'))).toEqual(OC.webroot + '/index.php/apps/files/ajax/rename.php'); + expect(OC.parseQueryString(request.url)).toEqual({'dir': '/subdir', newname: 'One_renamed.txt', file: 'One.txt'}); + + // element is renamed before the request finishes + expect(FileList.findFileEl('One.txt').length).toEqual(0); + expect(FileList.findFileEl('One_renamed.txt').length).toEqual(1); + // input is gone + expect(FileList.$fileList.find('input.filename').length).toEqual(0); + } + it('Keeps renamed file entry if rename ajax call suceeded', function() { + doRename(); + + fakeServer.requests[0].respond(200, {'Content-Type': 'application/json'}, JSON.stringify({ + status: 'success', + data: { + name: 'One_renamed.txt' + } + })); + + // element stays renamed + expect(FileList.findFileEl('One.txt').length).toEqual(0); + expect(FileList.findFileEl('One_renamed.txt').length).toEqual(1); + + expect(alertStub.notCalled).toEqual(true); + }); + it('Reverts file entry if rename ajax call failed', function() { + doRename(); + + fakeServer.requests[0].respond(200, {'Content-Type': 'application/json'}, JSON.stringify({ + status: 'error', + data: { + message: 'Something went wrong' + } + })); + + // element was reverted + expect(FileList.findFileEl('One.txt').length).toEqual(1); + expect(FileList.findFileEl('One_renamed.txt').length).toEqual(0); + + expect(alertStub.calledOnce).toEqual(true); + }); + it('Correctly updates file link after rename', function() { + var $tr; + doRename(); + + fakeServer.requests[0].respond(200, {'Content-Type': 'application/json'}, JSON.stringify({ + status: 'success', + data: { + name: 'One_renamed.txt' + } + })); + + $tr = FileList.findFileEl('One_renamed.txt'); + expect($tr.find('a.name').attr('href')).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fsubdir&files=One_renamed.txt'); + }); + // FIXME: fix this in the source code! + xit('Correctly updates file link after rename when path has same name', function() { + var $tr; + // evil case: because of buggy code + $('#dir').val('/One.txt/subdir'); + doRename(); + + fakeServer.requests[0].respond(200, {'Content-Type': 'application/json'}, JSON.stringify({ + status: 'success', + data: { + name: 'One_renamed.txt' + } + })); + + $tr = FileList.findFileEl('One_renamed.txt'); + expect($tr.find('a.name').attr('href')).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fsubdir&files=One.txt'); + }); + }); + describe('List rendering', function() { + it('renders a list of files using add()', function() { + var addSpy = sinon.spy(FileList, 'add'); + FileList.setFiles(testFiles); + expect(addSpy.callCount).toEqual(4); + expect($('#fileList tr:not(.summary)').length).toEqual(4); + addSpy.restore(); + }); + it('updates summary using the file sizes', function() { + var $summary; + FileList.setFiles(testFiles); + $summary = $('#fileList .summary'); + expect($summary.length).toEqual(1); + expect($summary.find('.info').text()).toEqual('1 folder and 3 files'); + expect($summary.find('.filesize').text()).toEqual('69 kB'); + }); + it('shows headers, summary and hide empty content message after setting files', function(){ + FileList.setFiles(testFiles); + expect($('#filestable thead th').hasClass('hidden')).toEqual(false); + expect($('#emptycontent').hasClass('hidden')).toEqual(true); + expect(FileList.$fileList.find('.summary').length).toEqual(1); + }); + it('hides headers, summary and show empty content message after setting empty file list', function(){ + FileList.setFiles([]); + expect($('#filestable thead th').hasClass('hidden')).toEqual(true); + expect($('#emptycontent').hasClass('hidden')).toEqual(false); + expect(FileList.$fileList.find('.summary').length).toEqual(0); + }); + it('hides headers, empty content message, and summary when list is empty and user has no creation permission', function(){ + $('#permissions').val(0); + FileList.setFiles([]); + expect($('#filestable thead th').hasClass('hidden')).toEqual(true); + expect($('#emptycontent').hasClass('hidden')).toEqual(true); + expect(FileList.$fileList.find('.summary').length).toEqual(0); + }); + it('calling findFileEl() can find existing file element', function() { + FileList.setFiles(testFiles); + expect(FileList.findFileEl('Two.jpg').length).toEqual(1); + }); + it('calling findFileEl() returns empty when file not found in file', function() { + FileList.setFiles(testFiles); + expect(FileList.findFileEl('unexist.dat').length).toEqual(0); + }); + it('only add file if in same current directory', function() { + $('#dir').val('/current dir'); + var fileData = { + type: 'file', + name: 'testFile.txt', + directory: '/current dir' + }; + var $tr = FileList.add(fileData); + expect(FileList.findFileEl('testFile.txt').length).toEqual(1); + }); + it('triggers "fileActionsReady" event after update', function() { + var handler = sinon.stub(); + FileList.$fileList.on('fileActionsReady', handler); + FileList.setFiles(testFiles); + expect(handler.calledOnce).toEqual(true); + }); + it('triggers "updated" event after update', function() { + var handler = sinon.stub(); + FileList.$fileList.on('updated', handler); + FileList.setFiles(testFiles); + expect(handler.calledOnce).toEqual(true); + }); + }); + describe('file previews', function() { + var previewLoadStub; + + function getImageUrl($el) { + // might be slightly different cross-browser + var url = $el.css('background-image'); + var r = url.match(/url\(['"]?([^'")]*)['"]?\)/); + if (!r) { + return url; + } + return r[1]; + } + + beforeEach(function() { + previewLoadStub = sinon.stub(Files, 'lazyLoadPreview'); + }); + afterEach(function() { + previewLoadStub.restore(); + }); + it('renders default icon for file when none provided and no preview is available', function() { + var fileData = { + type: 'file', + name: 'testFile.txt' + }; + var $tr = FileList.add(fileData); + var $td = $tr.find('td.filename'); + expect(getImageUrl($td)).toEqual(OC.webroot + '/core/img/filetypes/file.svg'); + expect(previewLoadStub.notCalled).toEqual(true); + }); + it('renders default icon for dir when none provided and no preview is available', function() { + var fileData = { + type: 'dir', + name: 'test dir' + }; + var $tr = FileList.add(fileData); + var $td = $tr.find('td.filename'); + expect(getImageUrl($td)).toEqual(OC.webroot + '/core/img/filetypes/folder.svg'); + expect(previewLoadStub.notCalled).toEqual(true); + }); + it('renders provided icon for file when provided', function() { + var fileData = { + type: 'file', + name: 'test dir', + icon: OC.webroot + '/core/img/filetypes/application-pdf.svg' + }; + var $tr = FileList.add(fileData); + var $td = $tr.find('td.filename'); + expect(getImageUrl($td)).toEqual(OC.webroot + '/core/img/filetypes/application-pdf.svg'); + expect(previewLoadStub.notCalled).toEqual(true); + }); + it('renders preview when no icon was provided and preview is available', function() { + var fileData = { + type: 'file', + name: 'test dir', + isPreviewAvailable: true + }; + var $tr = FileList.add(fileData); + var $td = $tr.find('td.filename'); + expect(getImageUrl($td)).toEqual(OC.webroot + '/core/img/filetypes/file.svg'); + expect(previewLoadStub.calledOnce).toEqual(true); + // third argument is callback + previewLoadStub.getCall(0).args[2](OC.webroot + '/somepath.png'); + expect(getImageUrl($td)).toEqual(OC.webroot + '/somepath.png'); + }); + it('renders default file type icon when no icon was provided and no preview is available', function() { + var fileData = { + type: 'file', + name: 'test dir', + isPreviewAvailable: false + }; + var $tr = FileList.add(fileData); + var $td = $tr.find('td.filename'); + expect(getImageUrl($td)).toEqual(OC.webroot + '/core/img/filetypes/file.svg'); + expect(previewLoadStub.notCalled).toEqual(true); + }); + }); + describe('viewer mode', function() { + it('enabling viewer mode hides files table and action buttons', function() { + FileList.setViewerMode(true); + expect($('#filestable').hasClass('hidden')).toEqual(true); + expect($('.actions').hasClass('hidden')).toEqual(true); + expect($('.notCreatable').hasClass('hidden')).toEqual(true); + }); + it('disabling viewer mode restores files table and action buttons', function() { + FileList.setViewerMode(true); + FileList.setViewerMode(false); + expect($('#filestable').hasClass('hidden')).toEqual(false); + expect($('.actions').hasClass('hidden')).toEqual(false); + expect($('.notCreatable').hasClass('hidden')).toEqual(true); + }); + it('disabling viewer mode restores files table and action buttons with correct permissions', function() { + $('#permissions').val(0); + FileList.setViewerMode(true); + FileList.setViewerMode(false); + expect($('#filestable').hasClass('hidden')).toEqual(false); + expect($('.actions').hasClass('hidden')).toEqual(true); + expect($('.notCreatable').hasClass('hidden')).toEqual(false); + }); + }); + describe('loading file list', function() { + beforeEach(function() { + var data = { + status: 'success', + data: { + files: testFiles, + permissions: 31 + } + }; + fakeServer.respondWith(/\/index\.php\/apps\/files\/ajax\/list.php\?dir=%2F(subdir|anothersubdir)/, [ + 200, { + "Content-Type": "application/json" + }, + JSON.stringify(data) + ]); + }); + it('fetches file list from server and renders it when reload() is called', function() { + FileList.reload(); + expect(fakeServer.requests.length).toEqual(1); + var url = fakeServer.requests[0].url; + var query = url.substr(url.indexOf('?') + 1); + expect(OC.parseQueryString(query)).toEqual({'dir': '/subdir'}); + fakeServer.respond(); + expect($('#fileList tr:not(.summary)').length).toEqual(4); + expect(FileList.findFileEl('One.txt').length).toEqual(1); + }); + it('switches dir and fetches file list when calling changeDirectory()', function() { + FileList.changeDirectory('/anothersubdir'); + expect(FileList.getCurrentDirectory()).toEqual('/anothersubdir'); + expect(fakeServer.requests.length).toEqual(1); + var url = fakeServer.requests[0].url; + var query = url.substr(url.indexOf('?') + 1); + expect(OC.parseQueryString(query)).toEqual({'dir': '/anothersubdir'}); + fakeServer.respond(); + }); + it('switches to root dir when current directory does not exist', function() { + fakeServer.respondWith(/\/index\.php\/apps\/files\/ajax\/list.php\?dir=%2funexist/, [ + 404, { + "Content-Type": "application/json" + }, + '' + ]); + FileList.changeDirectory('/unexist'); + fakeServer.respond(); + expect(FileList.getCurrentDirectory()).toEqual('/'); + }); + it('shows mask before loading file list then hides it at the end', function() { + var showMaskStub = sinon.stub(FileList, 'showMask'); + var hideMaskStub = sinon.stub(FileList, 'hideMask'); + FileList.changeDirectory('/anothersubdir'); + expect(showMaskStub.calledOnce).toEqual(true); + expect(hideMaskStub.calledOnce).toEqual(false); + fakeServer.respond(); + expect(showMaskStub.calledOnce).toEqual(true); + expect(hideMaskStub.calledOnce).toEqual(true); + showMaskStub.restore(); + hideMaskStub.restore(); + }); + it('changes URL to target dir', function() { + FileList.changeDirectory('/somedir'); + expect(pushStateStub.calledOnce).toEqual(true); + expect(pushStateStub.getCall(0).args[0]).toEqual({dir: '/somedir'}); + expect(pushStateStub.getCall(0).args[2]).toEqual(OC.webroot + '/index.php/apps/files?dir=/somedir'); + }); + it('refreshes breadcrumb after update', function() { + var setDirSpy = sinon.spy(FileList.breadcrumb, 'setDirectory'); + FileList.changeDirectory('/anothersubdir'); + fakeServer.respond(); + expect(FileList.breadcrumb.setDirectory.calledOnce).toEqual(true); + expect(FileList.breadcrumb.setDirectory.calledWith('/anothersubdir')).toEqual(true); + setDirSpy.restore(); + }); + }); + describe('breadcrumb events', function() { + beforeEach(function() { + var data = { + status: 'success', + data: { + files: testFiles, + permissions: 31 + } + }; + fakeServer.respondWith(/\/index\.php\/apps\/files\/ajax\/list.php\?dir=%2Fsubdir/, [ + 200, { + "Content-Type": "application/json" + }, + JSON.stringify(data) + ]); + }); + it('clicking on root breadcrumb changes directory to root', function() { + FileList.changeDirectory('/subdir/two/three with space/four/five'); + fakeServer.respond(); + var changeDirStub = sinon.stub(FileList, 'changeDirectory'); + FileList.breadcrumb.$el.find('.crumb:eq(0)').click(); + + expect(changeDirStub.calledOnce).toEqual(true); + expect(changeDirStub.getCall(0).args[0]).toEqual('/'); + changeDirStub.restore(); + }); + it('clicking on breadcrumb changes directory', function() { + FileList.changeDirectory('/subdir/two/three with space/four/five'); + fakeServer.respond(); + var changeDirStub = sinon.stub(FileList, 'changeDirectory'); + FileList.breadcrumb.$el.find('.crumb:eq(3)').click(); + + expect(changeDirStub.calledOnce).toEqual(true); + expect(changeDirStub.getCall(0).args[0]).toEqual('/subdir/two/three with space'); + changeDirStub.restore(); + }); + it('dropping files on breadcrumb calls move operation', function() { + var request, query, testDir = '/subdir/two/three with space/four/five'; + FileList.changeDirectory(testDir); + fakeServer.respond(); + var $crumb = FileList.breadcrumb.$el.find('.crumb:eq(3)'); + // no idea what this is but is required by the handler + var ui = { + helper: { + find: sinon.stub() + } + }; + // returns a list of tr that were dragged + // FIXME: why are their attributes different than the + // regular file trs ? + ui.helper.find.returns([ + $(''), + $('') + ]); + // simulate drop event + FileList._onDropOnBreadCrumb.call($crumb, new $.Event('drop'), ui); + + // will trigger two calls to move.php (first one was previous list.php) + expect(fakeServer.requests.length).toEqual(3); + + request = fakeServer.requests[1]; + expect(request.method).toEqual('POST'); + expect(request.url).toEqual(OC.webroot + '/index.php/apps/files/ajax/move.php'); + query = OC.parseQueryString(request.requestBody); + expect(query).toEqual({ + target: '/subdir/two/three with space', + dir: testDir, + file: 'One.txt' + }); + + request = fakeServer.requests[2]; + expect(request.method).toEqual('POST'); + expect(request.url).toEqual(OC.webroot + '/index.php/apps/files/ajax/move.php'); + query = OC.parseQueryString(request.requestBody); + expect(query).toEqual({ + target: '/subdir/two/three with space', + dir: testDir, + file: 'Two.jpg' + }); + }); + it('dropping files on same dir breadcrumb does nothing', function() { + var request, query, testDir = '/subdir/two/three with space/four/five'; + FileList.changeDirectory(testDir); + fakeServer.respond(); + var $crumb = FileList.breadcrumb.$el.find('.crumb:last'); + // no idea what this is but is required by the handler + var ui = { + helper: { + find: sinon.stub() + } + }; + // returns a list of tr that were dragged + // FIXME: why are their attributes different than the + // regular file trs ? + ui.helper.find.returns([ + $(''), + $('') + ]); + // simulate drop event + FileList._onDropOnBreadCrumb.call($crumb, new $.Event('drop'), ui); + + // no extra server request + expect(fakeServer.requests.length).toEqual(1); + }); }); describe('Download Url', function() { it('returns correct download URL for single files', function() { - expect(FileList.getDownloadUrl('some file.txt')).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fsubdir&files=some%20file.txt'); - expect(FileList.getDownloadUrl('some file.txt', '/anotherpath/abc')).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fanotherpath%2Fabc&files=some%20file.txt'); + expect(Files.getDownloadUrl('some file.txt')).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fsubdir&files=some%20file.txt'); + expect(Files.getDownloadUrl('some file.txt', '/anotherpath/abc')).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fanotherpath%2Fabc&files=some%20file.txt'); $('#dir').val('/'); - expect(FileList.getDownloadUrl('some file.txt')).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2F&files=some%20file.txt'); + expect(Files.getDownloadUrl('some file.txt')).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2F&files=some%20file.txt'); }); it('returns correct download URL for multiple files', function() { - expect(FileList.getDownloadUrl(['a b c.txt', 'd e f.txt'])).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fsubdir&files=%5B%22a%20b%20c.txt%22%2C%22d%20e%20f.txt%22%5D'); + expect(Files.getDownloadUrl(['a b c.txt', 'd e f.txt'])).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fsubdir&files=%5B%22a%20b%20c.txt%22%2C%22d%20e%20f.txt%22%5D'); + }); + it('returns the correct ajax URL', function() { + expect(Files.getAjaxUrl('test', {a:1, b:'x y'})).toEqual(OC.webroot + '/index.php/apps/files/ajax/test.php?a=1&b=x%20y'); }); }); }); diff --git a/apps/files_sharing/ajax/list.php b/apps/files_sharing/ajax/list.php new file mode 100644 index 0000000000..4b64549625 --- /dev/null +++ b/apps/files_sharing/ajax/list.php @@ -0,0 +1,91 @@ + + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see . + * + */ + +// only need filesystem apps +$RUNTIME_APPTYPES=array('filesystem'); + +// Init owncloud + +if(!\OC_App::isEnabled('files_sharing')){ + exit; +} + +if(!isset($_GET['t'])){ + \OC_Response::setStatus(400); //400 Bad Request + \OC_Log::write('core-preview', 'No token parameter was passed', \OC_Log::DEBUG); + exit; +} + +$token = $_GET['t']; + +$password = null; +if (isset($_POST['password'])) { + $password = $_POST['password']; +} + +$relativePath = null; +if (isset($_GET['dir'])) { + $relativePath = $_GET['dir']; +} + +$data = \OCA\Files_Sharing\Helper::setupFromToken($token, $relativePath, $password); + +$linkItem = $data['linkItem']; +// Load the files +$dir = $data['realPath']; + +$dir = \OC\Files\Filesystem::normalizePath($dir); +if (!\OC\Files\Filesystem::is_dir($dir . '/')) { + \OC_Response::setStatus(404); + \OCP\JSON::error(array('success' => false)); + exit(); +} + +$data = array(); +$baseUrl = OCP\Util::linkTo('files_sharing', 'index.php') . '?t=' . urlencode($token) . '&dir='; + +// make filelist +$files = \OCA\Files\Helper::getFiles($dir); + +$formattedFiles = array(); +foreach ($files as $file) { + $entry = \OCA\Files\Helper::formatFileInfo($file); + unset($entry['directory']); // for now + $entry['permissions'] = \OCP\PERMISSION_READ; + $formattedFiles[] = $entry; +} + +$data['directory'] = $relativePath; +$data['files'] = $formattedFiles; +$data['dirToken'] = $linkItem['token']; + +$permissions = $linkItem['permissions']; + +// if globally disabled +if (OC_Appconfig::getValue('core', 'shareapi_allow_public_upload', 'yes') === 'no') { + // only allow reading + $permissions = \OCP\PERMISSION_READ; +} + +$data['permissions'] = $permissions; + +OCP\JSON::success(array('data' => $data)); diff --git a/apps/files_sharing/css/public.css b/apps/files_sharing/css/public.css index 5246a4b2fe..f0b9b04491 100644 --- a/apps/files_sharing/css/public.css +++ b/apps/files_sharing/css/public.css @@ -35,6 +35,11 @@ body { background: #fff; text-align: center; margin: 45px auto 0; + min-height: 150px; +} + +#preview .notCreatable { + display: none; } #noPreview { diff --git a/apps/files_sharing/js/public.js b/apps/files_sharing/js/public.js index 06c168969d..9ce8985f1f 100644 --- a/apps/files_sharing/js/public.js +++ b/apps/files_sharing/js/public.js @@ -8,16 +8,7 @@ * */ -/* global OC, FileList, FileActions */ - -// Override download path to files_sharing/public.php -function fileDownloadPath(dir, file) { - var url = $('#downloadURL').val(); - if (url.indexOf('&path=') != -1) { - url += '/'+file; - } - return url; -} +/* global OC, FileActions, FileList, Files */ $(document).ready(function() { @@ -31,32 +22,44 @@ $(document).ready(function() { action($('#filename').val()); } } - FileActions.register('dir', 'Open', OC.PERMISSION_READ, '', function(filename) { - var tr = FileList.findFileEl(filename); - if (tr.length > 0) { - window.location = $(tr).find('a.name').attr('href'); - } - }); - - // override since the format is different - FileList.getDownloadUrl = function(filename, dir) { - if ($.isArray(filename)) { - filename = JSON.stringify(filename); - } - var path = dir || FileList.getCurrentDirectory(); - var params = { - service: 'files', - t: $('#sharingToken').val(), - path: path, - download: null - }; - if (filename) { - params.files = filename; - } - return OC.filePath('', '', 'public.php') + '?' + OC.buildQueryString(params); - }; } + // override since the format is different + Files.getDownloadUrl = function(filename, dir) { + if ($.isArray(filename)) { + filename = JSON.stringify(filename); + } + var path = dir || FileList.getCurrentDirectory(); + var params = { + service: 'files', + t: $('#sharingToken').val(), + path: path, + files: filename, + download: null + }; + return OC.filePath('', '', 'public.php') + '?' + OC.buildQueryString(params); + }; + + Files.getAjaxUrl = function(action, params) { + params = params || {}; + params.t = $('#sharingToken').val(); + return OC.filePath('files_sharing', 'ajax', action + '.php') + '?' + OC.buildQueryString(params); + }; + + FileList.linkTo = function(dir) { + var params = { + service: 'files', + t: $('#sharingToken').val(), + dir: dir + }; + return OC.filePath('', '', 'public.php') + '?' + OC.buildQueryString(params); + }; + + Files.generatePreviewUrl = function(urlSpec) { + urlSpec.t = $('#dirToken').val(); + return OC.generateUrl('/apps/files_sharing/ajax/publicpreview.php?') + $.param(urlSpec); + }; + var file_upload_start = $('#file_upload_start'); file_upload_start.on('fileuploadadd', function(e, data) { // Add custom data to the upload handler diff --git a/apps/files_sharing/js/share.js b/apps/files_sharing/js/share.js index 9f0ed12f93..ea518f3b70 100644 --- a/apps/files_sharing/js/share.js +++ b/apps/files_sharing/js/share.js @@ -1,15 +1,35 @@ +/* + * Copyright (c) 2014 + * + * This file is licensed under the Affero General Public License version 3 + * or later. + * + * See the COPYING-README file. + * + */ + +/* global OC, t, FileList, FileActions */ $(document).ready(function() { var disableSharing = $('#disableSharing').data('status'), sharesLoaded = false; if (typeof OC.Share !== 'undefined' && typeof FileActions !== 'undefined' && !disableSharing) { + var oldCreateRow = FileList._createRow; + FileList._createRow = function(fileData) { + var tr = oldCreateRow.apply(this, arguments); + if (fileData.shareOwner) { + tr.attr('data-share-owner', fileData.shareOwner); + } + return tr; + }; + $('#fileList').on('fileActionsReady',function(){ - var allShared = $('#fileList').find('[data-share-owner]').find('[data-Action="Share"]'); + var allShared = $('#fileList').find('[data-share-owner] [data-Action="Share"]'); allShared.addClass('permanent'); allShared.find('span').text(function(){ - $owner = $(this).closest('tr').attr('data-share-owner'); + var $owner = $(this).closest('tr').attr('data-share-owner'); return ' ' + t('files_sharing', 'Shared by {owner}', {owner: $owner}); }); diff --git a/apps/files_sharing/lib/helper.php b/apps/files_sharing/lib/helper.php new file mode 100644 index 0000000000..b602fe3599 --- /dev/null +++ b/apps/files_sharing/lib/helper.php @@ -0,0 +1,114 @@ + false)); + exit(); + } + + if (!isset($linkItem['item_type'])) { + \OCP\Util::writeLog('share', 'No item type set for share id: ' . $linkItem['id'], \OCP\Util::ERROR); + \OC_Response::setStatus(404); + \OCP\JSON::error(array('success' => false)); + exit(); + } + + if (isset($linkItem['share_with'])) { + if (!self::authenticate($linkItem, $password)) { + \OC_Response::setStatus(403); + \OCP\JSON::error(array('success' => false)); + exit(); + } + } + + $basePath = $path; + $rootName = basename($path); + + if ($relativePath !== null && \OC\Files\Filesystem::isReadable($basePath . $relativePath)) { + $path .= \OC\Files\Filesystem::normalizePath($relativePath); + } + + return array( + 'linkItem' => $linkItem, + 'basePath' => $basePath, + 'realPath' => $path + ); + } + + /** + * Authenticate link item with the given password + * or with the session if no password was given. + * @param array $linkItem link item array + * @param string $password optional password + * + * @return true if authorized, false otherwise + */ + public static function authenticate($linkItem, $password) { + if ($password !== null) { + if ($linkItem['share_type'] == \OCP\Share::SHARE_TYPE_LINK) { + // Check Password + $forcePortable = (CRYPT_BLOWFISH != 1); + $hasher = new PasswordHash(8, $forcePortable); + if (!($hasher->CheckPassword($password.OC_Config::getValue('passwordsalt', ''), + $linkItem['share_with']))) { + return false; + } else { + // Save item id in session for future requests + \OC::$session->set('public_link_authenticated', $linkItem['id']); + } + } else { + \OCP\Util::writeLog('share', 'Unknown share type '.$linkItem['share_type'] + .' for share id '.$linkItem['id'], \OCP\Util::ERROR); + return false; + } + + } + else { + // not authenticated ? + if ( ! \OC::$session->exists('public_link_authenticated') + || \OC::$session->get('public_link_authenticated') !== $linkItem['id']) { + return false; + } + } + return true; + } +} diff --git a/apps/files_sharing/public.php b/apps/files_sharing/public.php index fe61dd4d5a..ce51eca6dd 100644 --- a/apps/files_sharing/public.php +++ b/apps/files_sharing/public.php @@ -11,31 +11,6 @@ if ($appConfig->getValue('core', 'shareapi_allow_links', 'yes') !== 'yes') { exit(); } -function fileCmp($a, $b) { - if ($a['type'] == 'dir' and $b['type'] != 'dir') { - return -1; - } elseif ($a['type'] != 'dir' and $b['type'] == 'dir') { - return 1; - } else { - return strnatcasecmp($a['name'], $b['name']); - } -} - -function determineIcon($file, $sharingRoot, $sharingToken) { - // for folders we simply reuse the files logic - if($file['type'] == 'dir') { - return \OCA\Files\Helper::determineIcon($file); - } - - $relativePath = substr($file['path'], 6); - $relativePath = substr($relativePath, strlen($sharingRoot)); - if($file['isPreviewAvailable']) { - return OCP\publicPreview_icon($relativePath, $sharingToken) . '&c=' . $file['etag']; - } - $icon = OCP\mimetype_icon($file['mimetype']); - return substr($icon, 0, -3) . 'svg'; -} - if (isset($_GET['t'])) { $token = $_GET['t']; $linkItem = OCP\Share::getShareByToken($token, false); @@ -153,13 +128,6 @@ if (isset($path)) { $tmpl->assign('mimetype', \OC\Files\Filesystem::getMimeType($path)); $tmpl->assign('dirToken', $linkItem['token']); $tmpl->assign('sharingToken', $token); - $allowPublicUploadEnabled = (bool) ($linkItem['permissions'] & OCP\PERMISSION_CREATE); - if ($appConfig->getValue('core', 'shareapi_allow_public_upload', 'yes') === 'no') { - $allowPublicUploadEnabled = false; - } - if ($linkItem['item_type'] !== 'folder') { - $allowPublicUploadEnabled = false; - } $urlLinkIdentifiers= (isset($token)?'&t='.$token:'') .(isset($_GET['dir'])?'&dir='.$_GET['dir']:'') @@ -170,64 +138,18 @@ if (isset($path)) { OCP\Util::addStyle('files', 'files'); OCP\Util::addStyle('files', 'upload'); + OCP\Util::addScript('files', 'breadcrumb'); OCP\Util::addScript('files', 'files'); OCP\Util::addScript('files', 'filelist'); OCP\Util::addscript('files', 'keyboardshortcuts'); $files = array(); $rootLength = strlen($basePath) + 1; - $totalSize = 0; - foreach (\OC\Files\Filesystem::getDirectoryContent($path) as $i) { - $totalSize += $i['size']; - $i['date'] = OCP\Util::formatDate($i['mtime']); - if ($i['type'] == 'file') { - $fileinfo = pathinfo($i['name']); - $i['basename'] = $fileinfo['filename']; - if (!empty($fileinfo['extension'])) { - $i['extension'] = '.' . $fileinfo['extension']; - } else { - $i['extension'] = ''; - } - } - $i['isPreviewAvailable'] = \OC::$server->getPreviewManager()->isMimeSupported($i['mimetype']); - $i['directory'] = $getPath; - $i['permissions'] = OCP\PERMISSION_READ; - $i['icon'] = determineIcon($i, $basePath, $token); - $files[] = $i; - } - usort($files, "fileCmp"); - - // Make breadcrumb - $breadcrumb = array(); - $pathtohere = ''; - foreach (explode('/', $getPath) as $i) { - if ($i != '') { - $pathtohere .= '/' . $i; - $breadcrumb[] = array('dir' => $pathtohere, 'name' => $i); - } - } - $list = new OCP\Template('files', 'part.list', ''); - $list->assign('files', $files); - $list->assign('baseURL', OCP\Util::linkToPublic('files') . $urlLinkIdentifiers . '&path='); - $list->assign('downloadURL', - OCP\Util::linkToPublic('files') . $urlLinkIdentifiers . '&download&path='); - $list->assign('isPublic', true); - $list->assign('sharingtoken', $token); - $list->assign('sharingroot', $basePath); - $breadcrumbNav = new OCP\Template('files', 'part.breadcrumb', ''); - $breadcrumbNav->assign('breadcrumb', $breadcrumb); - $breadcrumbNav->assign('rootBreadCrumb', $rootName); - $breadcrumbNav->assign('baseURL', OCP\Util::linkToPublic('files') . $urlLinkIdentifiers . '&path='); $maxUploadFilesize=OCP\Util::maxUploadFilesize($path); - $fileHeader = (!isset($files) or count($files) > 0); - $emptyContent = ($allowPublicUploadEnabled and !$fileHeader); $freeSpace=OCP\Util::freeSpace($path); $uploadLimit=OCP\Util::uploadLimit(); $folder = new OCP\Template('files', 'index', ''); - $folder->assign('fileList', $list->fetchPage()); - $folder->assign('breadcrumb', $breadcrumbNav->fetchPage()); $folder->assign('dir', $getPath); - $folder->assign('isCreatable', $allowPublicUploadEnabled); $folder->assign('dirToken', $linkItem['token']); $folder->assign('permissions', OCP\PERMISSION_READ); $folder->assign('isPublic',true); @@ -239,15 +161,11 @@ if (isset($path)) { $folder->assign('uploadLimit', $uploadLimit); // PHP upload limit $folder->assign('allowZipDownload', intval(OCP\Config::getSystemValue('allowZipDownload', true))); $folder->assign('usedSpacePercent', 0); - $folder->assign('fileHeader', $fileHeader); $folder->assign('disableSharing', true); $folder->assign('trash', false); - $folder->assign('emptyContent', $emptyContent); - $folder->assign('ajaxLoad', false); $tmpl->assign('folder', $folder->fetchPage()); $maxInputFileSize = OCP\Config::getSystemValue('maxZipInputSize', OCP\Util::computerFileSize('800 MB')); - $allowZip = OCP\Config::getSystemValue('allowZipDownload', true) - && ( $maxInputFileSize === 0 || $totalSize <= $maxInputFileSize); + $allowZip = OCP\Config::getSystemValue('allowZipDownload', true); $tmpl->assign('allowZipDownload', intval($allowZip)); $tmpl->assign('downloadURL', OCP\Util::linkToPublic('files') . $urlLinkIdentifiers . '&download&path=' . urlencode($getPath)); diff --git a/apps/files_trashbin/ajax/list.php b/apps/files_trashbin/ajax/list.php index cec18c4652..89a5511452 100644 --- a/apps/files_trashbin/ajax/list.php +++ b/apps/files_trashbin/ajax/list.php @@ -4,21 +4,8 @@ OCP\JSON::checkLoggedIn(); // Load the files $dir = isset( $_GET['dir'] ) ? $_GET['dir'] : ''; -$doBreadcrumb = isset( $_GET['breadcrumb'] ) ? true : false; $data = array(); -// Make breadcrumb -if($doBreadcrumb) { - $breadcrumb = \OCA\Files_Trashbin\Helper::makeBreadcrumb($dir); - - $breadcrumbNav = new OCP\Template('files_trashbin', 'part.breadcrumb', ''); - $breadcrumbNav->assign('breadcrumb', $breadcrumb, false); - $breadcrumbNav->assign('baseURL', OCP\Util::linkTo('files_trashbin', 'index.php') . '?dir='); - $breadcrumbNav->assign('home', OCP\Util::linkTo('files', 'index.php')); - - $data['breadcrumb'] = $breadcrumbNav->fetchPage(); -} - // make filelist try { $files = \OCA\Files_Trashbin\Helper::getTrashFiles($dir); @@ -27,19 +14,11 @@ try { exit(); } -$dirlisting = false; -if ($dir && $dir !== '/') { - $dirlisting = true; -} - $encodedDir = \OCP\Util::encodePath($dir); -$list = new OCP\Template('files_trashbin', 'part.list', ''); -$list->assign('files', $files, false); -$list->assign('baseURL', OCP\Util::linkTo('files_trashbin', 'index.php'). '?dir='.$encodedDir); -$list->assign('downloadURL', OCP\Util::linkToRoute('download', array('file' => '/'))); -$list->assign('dirlisting', $dirlisting); -$list->assign('disableDownloadActions', true); -$data['files'] = $list->fetchPage(); + +$data['permissions'] = 0; +$data['directory'] = $dir; +$data['files'] = \OCA\Files_Trashbin\Helper::formatFileInfos($files); OCP\JSON::success(array('data' => $data)); diff --git a/apps/files_trashbin/ajax/preview.php b/apps/files_trashbin/ajax/preview.php index ce64d9ecc9..32905b2a71 100644 --- a/apps/files_trashbin/ajax/preview.php +++ b/apps/files_trashbin/ajax/preview.php @@ -34,7 +34,7 @@ try{ if ($view->is_dir($file)) { $mimetype = 'httpd/unix-directory'; } else { - $pathInfo = pathinfo($file); + $pathInfo = pathinfo(ltrim($file, '/')); $fileName = $pathInfo['basename']; // if in root dir if ($pathInfo['dirname'] === '.') { diff --git a/apps/files_trashbin/ajax/undelete.php b/apps/files_trashbin/ajax/undelete.php index 9c3ccba7ed..2b00078669 100644 --- a/apps/files_trashbin/ajax/undelete.php +++ b/apps/files_trashbin/ajax/undelete.php @@ -19,7 +19,7 @@ if (isset($_POST['allfiles']) and $_POST['allfiles'] === 'true') { foreach (OCA\Files_Trashbin\Helper::getTrashFiles($dir) as $file) { $fileName = $file['name']; if (!$dirListing) { - $fileName .= '.d' . $file['timestamp']; + $fileName .= '.d' . $file['mtime']; } $list[] = $fileName; } diff --git a/apps/files_trashbin/css/trash.css b/apps/files_trashbin/css/trash.css index 97819f4e80..7ca3e355fc 100644 --- a/apps/files_trashbin/css/trash.css +++ b/apps/files_trashbin/css/trash.css @@ -1,3 +1,4 @@ -#fileList td a.file, #fileList td a.file span { +#fileList tr[data-type="file"] td a.name, +#fileList tr[data-type="file"] td a.name span { cursor: default; } diff --git a/apps/files_trashbin/index.php b/apps/files_trashbin/index.php index f0c5b0508b..e63fe1e418 100644 --- a/apps/files_trashbin/index.php +++ b/apps/files_trashbin/index.php @@ -11,6 +11,7 @@ $tmpl = new OCP\Template('files_trashbin', 'index', 'user'); OCP\Util::addStyle('files', 'files'); OCP\Util::addStyle('files_trashbin', 'trash'); +OCP\Util::addScript('files', 'breadcrumb'); OCP\Util::addScript('files', 'filelist'); // filelist overrides OCP\Util::addScript('files_trashbin', 'filelist'); @@ -34,48 +35,7 @@ if ($isIE8 && isset($_GET['dir'])){ exit(); } -$ajaxLoad = false; - -if (!$isIE8){ - try { - $files = \OCA\Files_Trashbin\Helper::getTrashFiles($dir); - } catch (Exception $e) { - header('Location: ' . OCP\Util::linkTo('files_trashbin', 'index.php')); - exit(); - } -} -else{ - $files = array(); - $ajaxLoad = true; -} - -$dirlisting = false; -if ($dir && $dir !== '/') { - $dirlisting = true; -} - -$breadcrumb = \OCA\Files_Trashbin\Helper::makeBreadcrumb($dir); - -$breadcrumbNav = new OCP\Template('files_trashbin', 'part.breadcrumb', ''); -$breadcrumbNav->assign('breadcrumb', $breadcrumb); -$breadcrumbNav->assign('baseURL', OCP\Util::linkTo('files_trashbin', 'index.php') . '?dir='); -$breadcrumbNav->assign('home', OCP\Util::linkTo('files', 'index.php')); - -$list = new OCP\Template('files_trashbin', 'part.list', ''); -$list->assign('files', $files); - -$encodedDir = \OCP\Util::encodePath($dir); -$list->assign('baseURL', OCP\Util::linkTo('files_trashbin', 'index.php'). '?dir='.$encodedDir); -$list->assign('downloadURL', OCP\Util::linkTo('files_trashbin', 'download.php') . '?file='.$encodedDir); -$list->assign('dirlisting', $dirlisting); -$list->assign('disableDownloadActions', true); - -$tmpl->assign('dirlisting', $dirlisting); -$tmpl->assign('breadcrumb', $breadcrumbNav->fetchPage()); -$tmpl->assign('fileList', $list->fetchPage()); -$tmpl->assign('files', $files); $tmpl->assign('dir', $dir); $tmpl->assign('disableSharing', true); -$tmpl->assign('ajaxLoad', true); $tmpl->printPage(); diff --git a/apps/files_trashbin/js/disableDefaultActions.js b/apps/files_trashbin/js/disableDefaultActions.js index afa80cacd6..50ceaf4696 100644 --- a/apps/files_trashbin/js/disableDefaultActions.js +++ b/apps/files_trashbin/js/disableDefaultActions.js @@ -1,4 +1,3 @@ /* disable download and sharing actions */ var disableDownloadActions = true; -var disableSharing = true; var trashBinApp = true; diff --git a/apps/files_trashbin/js/filelist.js b/apps/files_trashbin/js/filelist.js index a88459b0a9..7795daf277 100644 --- a/apps/files_trashbin/js/filelist.js +++ b/apps/files_trashbin/js/filelist.js @@ -1,61 +1,78 @@ -/* globals OC, FileList, t */ -// override reload with own ajax call -FileList.reload = function(){ - FileList.showMask(); - if (FileList._reloadCall){ - FileList._reloadCall.abort(); - } - $.ajax({ - url: OC.filePath('files_trashbin','ajax','list.php'), - data: { - dir : $('#dir').val(), - breadcrumb: true - }, - error: function(result) { - FileList.reloadCallback(result); - }, - success: function(result) { - FileList.reloadCallback(result); +/* global OC, t, FileList */ +(function() { + FileList.appName = t('files_trashbin', 'Deleted files'); + + FileList._deletedRegExp = new RegExp(/^(.+)\.d[0-9]+$/); + + /** + * Convert a file name in the format filename.d12345 to the real file name. + * This will use basename. + * The name will not be changed if it has no ".d12345" suffix. + * @param name file name + * @return converted file name + */ + FileList.getDeletedFileName = function(name) { + name = OC.basename(name); + var match = FileList._deletedRegExp.exec(name); + if (match && match.length > 1) { + name = match[1]; } - }); -}; + return name; + }; -FileList.appName = t('files_trashbin', 'Deleted files'); + var oldSetCurrentDir = FileList._setCurrentDir; + FileList._setCurrentDir = function(targetDir) { + oldSetCurrentDir.apply(this, arguments); -FileList._deletedRegExp = new RegExp(/^(.+)\.d[0-9]+$/); + var baseDir = OC.basename(targetDir); + if (baseDir !== '') { + FileList.setPageTitle(FileList.getDeletedFileName(baseDir)); + } + }; -/** - * Convert a file name in the format filename.d12345 to the real file name. - * This will use basename. - * The name will not be changed if it has no ".d12345" suffix. - * @param name file name - * @return converted file name - */ -FileList.getDeletedFileName = function(name) { - name = OC.basename(name); - var match = FileList._deletedRegExp.exec(name); - if (match && match.length > 1) { - name = match[1]; - } - return name; -}; -var oldSetCurrentDir = FileList.setCurrentDir; -FileList.setCurrentDir = function(targetDir) { - oldSetCurrentDir.apply(this, arguments); + var oldCreateRow = FileList._createRow; + FileList._createRow = function() { + // FIXME: MEGAHACK until we find a better solution + var tr = oldCreateRow.apply(this, arguments); + tr.find('td.filesize').remove(); + return tr; + }; - var baseDir = OC.basename(targetDir); - if (baseDir !== '') { - FileList.setPageTitle(FileList.getDeletedFileName(baseDir)); - } -}; + FileList._onClickBreadCrumb = function(e) { + var $el = $(e.target).closest('.crumb'), + index = $el.index(), + $targetDir = $el.data('dir'); + // first one is home, let the link makes it default action + if (index !== 0) { + e.preventDefault(); + FileList.changeDirectory($targetDir); + } + }; -FileList.linkTo = function(dir){ - return OC.linkTo('files_trashbin', 'index.php')+"?dir="+ encodeURIComponent(dir).replace(/%2F/g, '/'); -} + var oldAdd = FileList.add; + FileList.add = function(fileData, options) { + options = options || {}; + var dir = FileList.getCurrentDirectory(); + var dirListing = dir !== '' && dir !== '/'; + // show deleted time as mtime + if (fileData.mtime) { + fileData.mtime = parseInt(fileData.mtime, 10); + } + if (!dirListing) { + fileData.displayName = fileData.name; + fileData.name = fileData.name + '.d' + Math.floor(fileData.mtime / 1000); + } + return oldAdd.call(this, fileData, options); + }; -FileList.updateEmptyContent = function(){ - var $fileList = $('#fileList'); - var exists = $fileList.find('tr:first').exists(); - $('#emptycontent').toggleClass('hidden', exists); - $('#filestable th').toggleClass('hidden', !exists); -} + FileList.linkTo = function(dir){ + return OC.linkTo('files_trashbin', 'index.php')+"?dir="+ encodeURIComponent(dir).replace(/%2F/g, '/'); + }; + + FileList.updateEmptyContent = function(){ + var $fileList = $('#fileList'); + var exists = $fileList.find('tr:first').exists(); + $('#emptycontent').toggleClass('hidden', exists); + $('#filestable th').toggleClass('hidden', !exists); + }; +})(); diff --git a/apps/files_trashbin/js/trash.js b/apps/files_trashbin/js/trash.js index efe1e89f0b..5cd49e19aa 100644 --- a/apps/files_trashbin/js/trash.js +++ b/apps/files_trashbin/js/trash.js @@ -8,9 +8,26 @@ * */ -/* global OC, t, FileList, FileActions */ - +/* global OC, t, BreadCrumb, FileActions, FileList, Files */ $(document).ready(function() { + var deletedRegExp = new RegExp(/^(.+)\.d[0-9]+$/); + + /** + * Convert a file name in the format filename.d12345 to the real file name. + * This will use basename. + * The name will not be changed if it has no ".d12345" suffix. + * @param name file name + * @return converted file name + */ + function getDeletedFileName(name) { + name = OC.basename(name); + var match = deletedRegExp.exec(name); + if (match && match.length > 1) { + name = match[1]; + } + return name; + } + function removeCallback(result) { if (result.status !== 'success') { OC.dialogs.alert(result.data.message, t('core', 'Error')); @@ -18,7 +35,7 @@ $(document).ready(function() { var files = result.data.success; for (var i = 0; i < files.length; i++) { - FileList.findFileEl(OC.basename(files[i].filename)).remove(); + FileList.remove(OC.basename(files[i].filename), {updateSummary: false}); } FileList.updateFileSummary(); FileList.updateEmptyContent(); @@ -74,7 +91,6 @@ $(document).ready(function() { } procesSelection(); }); - $('.undelete').click('click', function(event) { event.preventDefault(); var allFiles = $('#select_all').is(':checked'); @@ -89,7 +105,7 @@ $(document).ready(function() { }; } else { - files = getSelectedFiles('name'); + files = Files.getSelectedFiles('name'); 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'); @@ -131,7 +147,7 @@ $(document).ready(function() { }; } else { - files = getSelectedFiles('name'); + files = Files.getSelectedFiles('name'); params = { files: JSON.stringify(files), dir: FileList.getCurrentDirectory() @@ -158,7 +174,7 @@ $(document).ready(function() { } FileList.hideMask(); // simply remove all files - FileList.update(''); + FileList.setFiles([]); enableActions(); } else { @@ -191,7 +207,7 @@ $(document).ready(function() { var filename = $(this).parent().parent().attr('data-file'); var tr = FileList.findFileEl(filename); var renaming = tr.data('renaming'); - if(!renaming && !FileList.isLoading(filename)){ + if(!renaming){ if(mime.substr(0, 5) === 'text/'){ //no texteditor for now return; } @@ -203,48 +219,62 @@ $(document).ready(function() { action(filename); } } - - // event handlers for breadcrumb items - $('#controls').delegate('.crumb:not(.home) a', 'click', onClickBreadcrumb); }); + /** + * Override crumb URL maker (hacky!) + */ + FileList.breadcrumb.getCrumbUrl = function(part, index) { + if (index === 0) { + return OC.linkTo('files', 'index.php'); + } + return OC.linkTo('files_trashbin', 'index.php')+"?dir=" + encodeURIComponent(part.dir); + }; + + Files.generatePreviewUrl = function(urlSpec) { + return OC.generateUrl('/apps/files_trashbin/ajax/preview.php?') + $.param(urlSpec); + }; + + Files.getDownloadUrl = function(action, params) { + // no downloads + return '#'; + }; + + Files.getAjaxUrl = function(action, params) { + var q = ''; + if (params) { + q = '?' + OC.buildQueryString(params); + } + return OC.filePath('files_trashbin', 'ajax', action + '.php') + q; + }; + + + /** + * Override crumb making to add "Deleted Files" entry + * and convert files with ".d" extensions to a more + * user friendly name. + */ + var oldMakeCrumbs = BreadCrumb.prototype._makeCrumbs; + BreadCrumb.prototype._makeCrumbs = function() { + var parts = oldMakeCrumbs.apply(this, arguments); + // duplicate first part + parts.unshift(parts[0]); + parts[1] = { + dir: '/', + name: t('files_trashbin', 'Deleted Files') + }; + for (var i = 2; i < parts.length; i++) { + parts[i].name = getDeletedFileName(parts[i].name); + } + return parts; + }; + FileActions.actions.dir = { // only keep 'Open' action for navigation 'Open': FileActions.actions.dir.Open }; }); -/** - * @brief get a list of selected files - * @param string property (option) the property of the file requested - * @return array - * - * possible values for property: name, mime, size and type - * if property is set, an array with that property for each file is returnd - * if it's ommited an array of objects with all properties is returned - */ -function getSelectedFiles(property){ - var elements=$('td.filename input:checkbox:checked').parent().parent(); - var files=[]; - elements.each(function(i,element){ - var file={ - name:$(element).attr('data-file'), - timestamp:$(element).attr('data-timestamp'), - type:$(element).attr('data-type') - }; - if(property){ - files.push(file[property]); - }else{ - files.push(file); - } - }); - return files; -} - -function fileDownloadPath(dir, file) { - return OC.filePath('files_trashbin', '', 'download.php') + '?file='+encodeURIComponent(file); -} - function enableActions() { $(".action").css("display", "inline"); $(":input:checkbox").css("display", "inline"); diff --git a/apps/files_trashbin/lib/helper.php b/apps/files_trashbin/lib/helper.php index 9c24332a96..e6ca73520a 100644 --- a/apps/files_trashbin/lib/helper.php +++ b/apps/files_trashbin/lib/helper.php @@ -27,6 +27,10 @@ class Helper if ($dirContent === false) { return $result; } + + list($storage, $internalPath) = $view->resolvePath($dir); + $absoluteDir = $view->getAbsolutePath($dir); + if (is_resource($dirContent)) { while (($entryName = readdir($dirContent)) !== false) { if (!\OC\Files\Filesystem::isIgnoredDir($entryName)) { @@ -40,76 +44,41 @@ class Helper $parts = explode('/', ltrim($dir, '/')); $timestamp = substr(pathinfo($parts[0], PATHINFO_EXTENSION), 1); } - $result[] = array( - 'id' => $id, - 'timestamp' => $timestamp, - 'mime' => \OC_Helper::getFileNameMimeType($id), + $i = array( + 'name' => $id, + 'mtime' => $timestamp, + 'mimetype' => \OC_Helper::getFileNameMimeType($id), 'type' => $view->is_dir($dir . '/' . $entryName) ? 'dir' : 'file', - 'location' => $dir, + 'directory' => ($dir === '/') ? '' : $dir, ); + $result[] = new FileInfo($absoluteDir . '/' . $i['name'], $storage, $internalPath . '/' . $i['name'], $i); } } closedir($dirContent); } - $files = array(); - $id = 0; - list($storage, $internalPath) = $view->resolvePath($dir); - $absoluteDir = $view->getAbsolutePath($dir); - foreach ($result as $r) { - $i = array(); - $i['id'] = $id++; - $i['name'] = $r['id']; - $i['date'] = \OCP\Util::formatDate($r['timestamp']); - $i['timestamp'] = $r['timestamp']; - $i['etag'] = $r['timestamp']; // add fake etag, it is only needed to identify the preview image - $i['mimetype'] = $r['mime']; - $i['type'] = $r['type']; - if ($i['type'] === 'file') { - $fileinfo = pathinfo($r['id']); - $i['basename'] = $fileinfo['filename']; - $i['extension'] = isset($fileinfo['extension']) ? ('.'.$fileinfo['extension']) : ''; - } - $i['directory'] = $r['location']; - if ($i['directory'] === '/') { - $i['directory'] = ''; - } - $i['permissions'] = \OCP\PERMISSION_READ; - if (\OCP\App::isEnabled('files_encryption')) { - $i['isPreviewAvailable'] = false; - } else { - $i['isPreviewAvailable'] = \OC::$server->getPreviewManager()->isMimeSupported($r['mime']); - } - $i['icon'] = \OCA\Files\Helper::determineIcon($i); - $files[] = new FileInfo($absoluteDir . '/' . $i['name'], $storage, $internalPath . '/' . $i['name'], $i); - } + usort($result, array('\OCA\Files\Helper', 'fileCmp')); - usort($files, array('\OCA\Files\Helper', 'fileCmp')); - - return $files; + return $result; } /** - * Splits the given path into a breadcrumb structure. - * @param string $dir path to process - * @return array where each entry is a hash of the absolute - * directory path and its name + * Format file infos for JSON + * @param \OCP\Files\FileInfo[] $fileInfos file infos */ - public static function makeBreadcrumb($dir){ - // Make breadcrumb - $pathtohere = ''; - $breadcrumb = array(); - foreach (explode('/', $dir) as $i) { - if ($i !== '') { - if ( preg_match('/^(.+)\.d[0-9]+$/', $i, $match) ) { - $name = $match[1]; - } else { - $name = $i; - } - $pathtohere .= '/' . $i; - $breadcrumb[] = array('dir' => $pathtohere, 'name' => $name); + public static function formatFileInfos($fileInfos) { + $files = array(); + $id = 0; + foreach ($fileInfos as $i) { + $entry = \OCA\Files\Helper::formatFileInfo($i); + $entry['id'] = $id++; + $entry['etag'] = $entry['mtime']; // add fake etag, it is only needed to identify the preview image + $entry['permissions'] = \OCP\PERMISSION_READ; + if (\OCP\App::isEnabled('files_encryption')) { + $entry['isPreviewAvailable'] = false; } + $files[] = $entry; } - return $breadcrumb; + return $files; } } diff --git a/apps/files_trashbin/templates/index.php b/apps/files_trashbin/templates/index.php index f9264d4352..615cf8bdd0 100644 --- a/apps/files_trashbin/templates/index.php +++ b/apps/files_trashbin/templates/index.php @@ -1,13 +1,11 @@
- -
+
-
class="hidden">t('Nothing in here. Your trash bin is empty!'))?>
+ - - + @@ -40,6 +38,5 @@ -
diff --git a/apps/files_trashbin/templates/part.breadcrumb.php b/apps/files_trashbin/templates/part.breadcrumb.php deleted file mode 100644 index fdf78c190d..0000000000 --- a/apps/files_trashbin/templates/part.breadcrumb.php +++ /dev/null @@ -1,19 +0,0 @@ - - - - - -
svg" - data-dir=''> - -
-200) $relative_date_color = 200; - $name = \OCP\Util::encodePath($file['name']); - $directory = \OCP\Util::encodePath($file['directory']); ?> - ' - - id="" - data-file="" - data-timestamp='' - data-dirlisting=1 - - id="" - data-file="" - data-timestamp='' - data-dirlisting=0 - > - - - - - style="background-image:url()" - - - style="background-image:url()" - - style="background-image:url()" - - - > - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -= 0){ + queryString = queryString.substr(pos + 1); } - parts = queryString.split('&'); + parts = queryString.replace(/\+/g, '%20').split('&'); for (var i = 0; i < parts.length; i++){ - components = parts[i].split('='); + // split on first equal sign + var part = parts[i] + pos = part.indexOf('='); + if (pos >= 0) { + components = [ + part.substr(0, pos), + part.substr(pos + 1) + ] + } + else { + // key only + components = [part]; + } if (!components.length){ continue; } @@ -391,8 +406,14 @@ var OC={ if (!key){ continue; } - value = components[1]; - result[key] = value && decodeURIComponent(value); + // if equal sign was there, return string + if (components.length > 1) { + result[key] = decodeURIComponent(components[1]); + } + // no equal sign => null value + else { + result[key] = null; + } } return result; }, diff --git a/core/js/oc-dialogs.js b/core/js/oc-dialogs.js index d1bcb4659b..6fc8d4d352 100644 --- a/core/js/oc-dialogs.js +++ b/core/js/oc-dialogs.js @@ -294,7 +294,7 @@ var OCdialogs = { conflict.find('.replacement .mtime').text(formatDate(replacement.lastModifiedDate)); } var path = original.directory + '/' +original.name; - Files.lazyLoadPreview(path, original.mime, function(previewpath){ + Files.lazyLoadPreview(path, original.mimetype, function(previewpath){ conflict.find('.original .icon').css('background-image','url('+previewpath+')'); }, 96, 96, original.etag); getCroppedPreview(replacement).then( @@ -514,7 +514,7 @@ var OCdialogs = { } return $.getJSON( - OC.filePath('files', 'ajax', 'rawlist.php'), + OC.filePath('files', 'ajax', 'list.php'), { dir: dir, mimetypes: JSON.stringify(mimeType) @@ -539,7 +539,7 @@ var OCdialogs = { this.$filelist.empty().addClass('loading'); this.$filePicker.data('path', dir); $.when(this._getFileList(dir, this.$filePicker.data('mimetype'))).then(function(response) { - $.each(response.data, function(index, file) { + $.each(response.data.files, function(index, file) { if (file.type === 'dir') { dirs.push(file); } else { @@ -555,9 +555,16 @@ var OCdialogs = { type: entry.type, dir: dir, filename: entry.name, - date: OC.mtime2date(entry.mtime) + date: OC.mtime2date(Math.floor(entry.mtime / 1000)) }); - $li.find('img').attr('src', entry.mimetype_icon); + $li.find('img').attr('src', entry.icon); + if (entry.isPreviewAvailable) { + var urlSpec = { + file: dir + '/' + entry.name + }; + var previewUrl = OC.generateUrl('/core/preview.png?') + $.param(urlSpec); + $li.find('img').attr('src', previewUrl); + } self.$filelist.append($li); }); diff --git a/core/js/tests/specs/coreSpec.js b/core/js/tests/specs/coreSpec.js index 57ea5be8be..94a397b789 100644 --- a/core/js/tests/specs/coreSpec.js +++ b/core/js/tests/specs/coreSpec.js @@ -268,7 +268,72 @@ describe('Core base tests', function() { // still nothing expect(counter).toEqual(0); }); - + }); + describe('Parse query string', function() { + it('Parses query string from full URL', function() { + var query = OC.parseQueryString('http://localhost/stuff.php?q=a&b=x'); + expect(query).toEqual({q: 'a', b: 'x'}); + }); + it('Parses query string from query part alone', function() { + var query = OC.parseQueryString('q=a&b=x'); + expect(query).toEqual({q: 'a', b: 'x'}); + }); + it('Returns null hash when empty query', function() { + var query = OC.parseQueryString(''); + expect(query).toEqual(null); + }); + it('Returns empty hash when empty query with question mark', function() { + var query = OC.parseQueryString('?'); + expect(query).toEqual({}); + }); + it('Decodes regular query strings', function() { + var query = OC.parseQueryString('a=abc&b=def'); + expect(query).toEqual({ + a: 'abc', + b: 'def' + }); + }); + it('Ignores empty parts', function() { + var query = OC.parseQueryString('&q=a&&b=x&'); + expect(query).toEqual({q: 'a', b: 'x'}); + }); + it('Ignores lone equal signs', function() { + var query = OC.parseQueryString('&q=a&=&b=x&'); + expect(query).toEqual({q: 'a', b: 'x'}); + }); + it('Includes extra equal signs in value', function() { + var query = OC.parseQueryString('u=a=x&q=a=b'); + expect(query).toEqual({u: 'a=x', q: 'a=b'}); + }); + it('Decodes plus as space', function() { + var query = OC.parseQueryString('space+key=space+value'); + expect(query).toEqual({'space key': 'space value'}); + }); + it('Decodes special characters', function() { + var query = OC.parseQueryString('unicode=%E6%B1%89%E5%AD%97'); + expect(query).toEqual({unicode: '汉字'}); + query = OC.parseQueryString('b=spaace%20value&space%20key=normalvalue&slash%2Fthis=amp%26ersand'); + expect(query).toEqual({ + b: 'spaace value', + 'space key': 'normalvalue', + 'slash/this': 'amp&ersand' + }); + }); + it('Decodes empty values', function() { + var query = OC.parseQueryString('keywithemptystring=&keywithnostring'); + expect(query).toEqual({ + 'keywithemptystring': '', + 'keywithnostring': null + }); + }); + it('Does not interpret data types', function() { + var query = OC.parseQueryString('booleanfalse=false&booleantrue=true&number=123'); + expect(query).toEqual({ + 'booleanfalse': 'false', + 'booleantrue': 'true', + 'number': '123' + }); + }); }); describe('Generate Url', function() { it('returns absolute urls', function() {