diff options
author | Vincent Petry <pvince81@owncloud.com> | 2014-05-08 22:06:30 +0200 |
---|---|---|
committer | Vincent Petry <pvince81@owncloud.com> | 2014-05-15 17:51:04 +0200 |
commit | 9d38e3602b2faf37d861729c52690ce51b8fee97 (patch) | |
tree | 5da63d26db4a4e8ec356dee45fc8f7804c6fe38a | |
parent | fb10bf4048aaf5b2a9665fc9dff217c790efe005 (diff) | |
download | nextcloud-server-9d38e3602b2faf37d861729c52690ce51b8fee97.tar.gz nextcloud-server-9d38e3602b2faf37d861729c52690ce51b8fee97.zip |
Namespacing for FileList, FileActions and trashbin app
- FileList is now an instantiable class
- FileActions is now in namespace
- added App class for trashbin app
- moved trashbin overrides into classes extending FileList
- replaced many static calls with "this." or "self." to make the classes
reusable/extendable
- new URL parameter "view" to specify which view is shown, for example
"files" or "trashbin"
- added OC.Util.History utility class in core for handling history
- moved URL handling/routing to OCA.Files.App
- popstate will correctly update the current view and notify the view of
the URL change so it can update the current dir
- added JS unitt tests for the trashbin app
- fixed public app to work with the new namespaces
37 files changed, 3796 insertions, 2941 deletions
diff --git a/apps/files/appinfo/app.php b/apps/files/appinfo/app.php index 15a29133789..4b0db457ada 100644 --- a/apps/files/appinfo/app.php +++ b/apps/files/appinfo/app.php @@ -19,3 +19,13 @@ $templateManager->registerTemplate('text/html', 'core/templates/filetemplates/te $templateManager->registerTemplate('application/vnd.oasis.opendocument.presentation', 'core/templates/filetemplates/template.odp'); $templateManager->registerTemplate('application/vnd.oasis.opendocument.text', 'core/templates/filetemplates/template.odt'); $templateManager->registerTemplate('application/vnd.oasis.opendocument.spreadsheet', 'core/templates/filetemplates/template.ods'); + +\OCA\Files\App::getNavigationManager()->add( + array( + "id" => 'files', + "appname" => 'files', + "script" => 'list.php', + "order" => 0, + "name" => $l->t('All files') + ) +); diff --git a/apps/files/css/files.css b/apps/files/css/files.css index db23f54f215..ba299ea2911 100644 --- a/apps/files/css/files.css +++ b/apps/files/css/files.css @@ -70,9 +70,8 @@ /* FILE TABLE */ -#filestable { +.app-files #filestable { position: relative; - top: 44px; width: 100%; } /* make sure there's enough room for the file actions */ @@ -84,9 +83,29 @@ } #filestable tbody tr { background-color:#fff; height:51px; } +/** + * Override global #controls styles + * to be more flexible / relative + */ .app-files #controls { - left: 300px; + position: static; + left: auto; + top: auto; } + +.app-files #app-navigation { + width: 150px; +} + +.app-files #app-settings { + width: 149px; /* DUH */ +} + +.app-files #app-settings input { + width: 90%; +} + +#filestable tbody tr { background-color:#fff; height:40px; } #filestable tbody tr:hover, tbody tr:active { background-color: rgb(240,240,240); } @@ -434,7 +453,3 @@ table.dragshadow td.size { .mask.transparent{ opacity: 0; } - -.app-files #app-settings input { - width: 90%; -} diff --git a/apps/files/index.php b/apps/files/index.php index 07c828fffef..69fa1c19131 100644 --- a/apps/files/index.php +++ b/apps/files/index.php @@ -38,28 +38,23 @@ OCP\Util::addscript('files', 'breadcrumb'); OCP\Util::addscript('files', 'filelist'); OCP\App::setActiveNavigationEntry('files_index'); -// Load the files -$dir = isset($_GET['dir']) ? stripslashes($_GET['dir']) : ''; -$dir = \OC\Files\Filesystem::normalizePath($dir); -$dirInfo = \OC\Files\Filesystem::getFileInfo($dir, false); -// Redirect if directory does not exist -if (!$dirInfo || !$dirInfo->getType() === 'dir') { - header('Location: ' . OCP\Util::getScriptName() . ''); - exit(); -} $isIE8 = false; preg_match('/MSIE (.*?);/', $_SERVER['HTTP_USER_AGENT'], $matches); -if (count($matches) > 0 && $matches[1] <= 8){ +if (count($matches) > 0 && $matches[1] <= 9) { $isIE8 = true; } -// if IE8 and "?dir=path" was specified, reformat the URL to use a hash like "#?dir=path" -if ($isIE8 && isset($_GET['dir'])){ - if ($dir === ''){ - $dir = '/'; +// if IE8 and "?dir=path&view=someview" was specified, reformat the URL to use a hash like "#?dir=path&view=someview" +if ($isIE8 && (isset($_GET['dir']) || isset($_GET['view']))) { + $hash = '#?'; + $dir = isset($_GET['dir']) ? $_GET['dir'] : '/'; + $view = isset($_GET['view']) ? $_GET['view'] : 'files'; + $hash = '#?dir=' . \OCP\Util::encodePath($dir); + if ($view !== 'files') { + $hash .= '&view=' . urlencode($view); } - header('Location: ' . OCP\Util::linkTo('files', 'index.php') . '#?dir=' . \OCP\Util::encodePath($dir)); + header('Location: ' . OCP\Util::linkTo('files', 'index.php') . $hash); exit(); } @@ -67,16 +62,6 @@ $user = OC_User::getUser(); $config = \OC::$server->getConfig(); -// needed for share init, permissions will be reloaded -// anyway with ajax load -$permissions = $dirInfo->getPermissions(); - -// information about storage capacities -$storageInfo=OC_Helper::getStorageInfo($dir, $dirInfo); -$freeSpace=$storageInfo['free']; -$uploadLimit=OCP\Util::uploadLimit(); -$maxUploadFilesize=OCP\Util::maxUploadFilesize($dir, $freeSpace); -$publicUploadEnabled = $config->getAppValue('core', 'shareapi_allow_public_upload', 'yes'); // if the encryption app is disabled, than everything is fine (INIT_SUCCESSFUL status code) $encryptionInitStatus = 2; if (OC_App::isEnabled('files_encryption')) { @@ -105,6 +90,7 @@ function renderScript($appName, $scriptName) { return $content; } +// render the container content for every navigation item foreach ($navItems as $item) { $content = ''; if (isset($item['script'])) { @@ -121,21 +107,11 @@ OCP\Util::addscript('files', 'files'); OCP\Util::addscript('files', 'navigation'); OCP\Util::addscript('files', 'keyboardshortcuts'); $tmpl = new OCP\Template('files', 'index', 'user'); -$tmpl->assign('dir', $dir); -$tmpl->assign('permissions', $permissions); -$tmpl->assign('uploadMaxFilesize', $maxUploadFilesize); // minimium of freeSpace and uploadLimit -$tmpl->assign('uploadMaxHumanFilesize', OCP\Util::humanFileSize($maxUploadFilesize)); -$tmpl->assign('freeSpace', $freeSpace); -$tmpl->assign('uploadLimit', $uploadLimit); // PHP upload limit -$tmpl->assign('allowZipDownload', intval(OCP\Config::getSystemValue('allowZipDownload', true))); -$tmpl->assign('usedSpacePercent', (int)$storageInfo['relative']); $tmpl->assign('isPublic', false); -$tmpl->assign('publicUploadEnabled', $publicUploadEnabled); $tmpl->assign("encryptedFiles", \OCP\Util::encryptedFiles()); $tmpl->assign("mailNotificationEnabled", $config->getAppValue('core', 'shareapi_allow_mail_notification', 'yes')); $tmpl->assign("allowShareWithLink", $config->getAppValue('core', 'shareapi_allow_links', 'yes')); $tmpl->assign("encryptionInitStatus", $encryptionInitStatus); -$tmpl->assign('disableSharing', false); $tmpl->assign('appNavigation', $nav); $tmpl->assign('appContents', $contentItems); diff --git a/apps/files/js/app.js b/apps/files/js/app.js index 87f7e2bb6ba..6cdb339b22d 100644 --- a/apps/files/js/app.js +++ b/apps/files/js/app.js @@ -11,15 +11,117 @@ * */ -if (!OCA.Files) { - OCA.Files = {}; -} +(function() { -$(document).ready(function() { - var nav = new OCA.Files.Navigation($('#app-navigation ul')); + if (!OCA.Files) { + OCA.Files = {}; + } + + var App = { + navigation: null, + + initialize: function() { + this.navigation = new OCA.Files.Navigation($('#app-navigation')); + + // TODO: ideally these should be in a separate class / app (the embedded "all files" app) + this.fileList = OCA.Files.FileList; + this.fileActions = OCA.Files.FileActions; + this.files = OCA.Files.Files; + + this.fileList = new OCA.Files.FileList($('#app-content-files')); + this.files.initialize(); + this.fileActions.registerDefaultActions(this.fileList); + this.fileList.setFileActions(this.fileActions); + + // for backward compatibility, the global FileList will + // refer to the one of the "files" view + window.FileList = this.fileList; + + this._setupEvents(); + // trigger URL change event handlers + this._onPopState(OC.Util.History.parseUrlQuery()); + }, + + /** + * Returns the container of the currently visible app. + * + * @return app container + */ + getCurrentAppContainer: function() { + return this.navigation.getActiveContainer(); + }, - nav.setSelectedItem('files'); + /** + * Setup events based on URL changes + */ + _setupEvents: function() { + OC.Util.History.addOnPopStateHandler(_.bind(this._onPopState, this)); - // TODO: init file list, actions and others + // detect when app changed their current directory + $('#app-content>div').on('changeDirectory', _.bind(this._onDirectoryChanged, this)); + + $('#app-navigation').on('itemChanged', _.bind(this._onNavigationChanged, this)); + }, + + /** + * Event handler for when the current navigation item has changed + */ + _onNavigationChanged: function(e) { + var params; + if (e && e.itemId) { + params = { + view: e.itemId, + dir: '/' + }; + this._changeUrl(params.view, params.dir); + this.navigation.getActiveContainer().trigger(new $.Event('urlChanged', params)); + } + }, + + /** + * Event handler for when an app notified that its directory changed + */ + _onDirectoryChanged: function(e) { + if (e.dir) { + this._changeUrl(this.navigation.getActiveItem(), e.dir); + } + }, + + /** + * Event handler for when the URL changed + */ + _onPopState: function(params) { + params = _.extend({ + dir: '/', + view: 'files' + }, params); + var lastId = this.navigation.getActiveItem(); + this.navigation.setActiveItem(params.view, {silent: true}); + if (lastId !== this.navigation.getActiveItem()) { + this.navigation.getActiveContainer().trigger(new $.Event('show')); + } + this.navigation.getActiveContainer().trigger(new $.Event('urlChanged', params)); + }, + + /** + * Change the URL to point to the given dir and view + */ + _changeUrl: function(view, dir) { + var params = {dir: dir}; + if (view !== 'files') { + params.view = view; + } + OC.Util.History.pushState(params); + } + }; + OCA.Files.App = App; +})(); + +$(document).ready(function() { + // wait for other apps/extensions to register their event handlers + // in the "ready" clause + _.defer(function() { + OCA.Files.App.initialize(); + }); }); diff --git a/apps/files/js/breadcrumb.js b/apps/files/js/breadcrumb.js index 5bc2fac1369..1a77481b678 100644 --- a/apps/files/js/breadcrumb.js +++ b/apps/files/js/breadcrumb.js @@ -236,6 +236,6 @@ } }; - window.BreadCrumb = BreadCrumb; + OCA.Files.BreadCrumb = BreadCrumb; })(); diff --git a/apps/files/js/file-upload.js b/apps/files/js/file-upload.js index 67775b22c54..0ca2852f9f6 100644 --- a/apps/files/js/file-upload.js +++ b/apps/files/js/file-upload.js @@ -346,7 +346,7 @@ OC.Upload = { // noone set update parameters, we set the minimum data.formData = { requesttoken: oc_requesttoken, - dir: $('#dir').val(), + dir: FileList.getCurrentDirectory(), file_directory: fileDirectory }; } @@ -595,7 +595,7 @@ OC.Upload = { if (FileList.lastAction) { FileList.lastAction(); } - var name = getUniqueName(newname); + var name = FileList.getUniqueName(newname); if (newname !== name) { FileList.checkName(name, newname, true); var hidden = true; @@ -607,7 +607,7 @@ OC.Upload = { $.post( OC.filePath('files', 'ajax', 'newfile.php'), { - dir: $('#dir').val(), + dir: FileList.getCurrentDirectory(), filename: name }, function(result) { @@ -623,7 +623,7 @@ OC.Upload = { $.post( OC.filePath('files','ajax','newfolder.php'), { - dir: $('#dir').val(), + dir: FileList.getCurrentDirectory(), foldername: name }, function(result) { @@ -648,7 +648,7 @@ OC.Upload = { } else { //or the domain localName = (localName.match(/:\/\/(.[^\/]+)/)[1]).replace('www.', ''); } - localName = getUniqueName(localName); + localName = FileList.getUniqueName(localName); //IE < 10 does not fire the necessary events for the progress bar. if ($('html.lte9').length === 0) { $('#uploadprogressbar').progressbar({value: 0}); @@ -658,7 +658,7 @@ OC.Upload = { var eventSource = new OC.EventSource( OC.filePath('files', 'ajax', 'newfile.php'), { - dir: $('#dir').val(), + dir: FileList.getCurrentDirectory(), source: name, filename: localName } diff --git a/apps/files/js/fileactions.js b/apps/files/js/fileactions.js index ecdfa72a477..7be599871e4 100644 --- a/apps/files/js/fileactions.js +++ b/apps/files/js/fileactions.js @@ -8,242 +8,253 @@ * */ -/* global OC, FileList, Files */ /* global trashBinApp */ -var FileActions = { - actions: {}, - defaults: {}, - icons: {}, - currentFile: null, - register: function (mime, name, permissions, icon, action, displayName) { - if (!FileActions.actions[mime]) { - FileActions.actions[mime] = {}; - } - if (!FileActions.actions[mime][name]) { - FileActions.actions[mime][name] = {}; - } - if (!displayName) { - displayName = t('files', name); - } - FileActions.actions[mime][name]['action'] = action; - FileActions.actions[mime][name]['permissions'] = permissions; - FileActions.actions[mime][name]['displayName'] = displayName; - FileActions.icons[name] = icon; - }, - setDefault: function (mime, name) { - FileActions.defaults[mime] = name; - }, - get: function (mime, type, permissions) { - var actions = this.getActions(mime, type, permissions); - var filteredActions = {}; - $.each(actions, function (name, action) { - filteredActions[name] = action.action; - }); - return filteredActions; - }, - getActions: function (mime, type, permissions) { - var actions = {}; - if (FileActions.actions.all) { - actions = $.extend(actions, FileActions.actions.all); - } - if (type) {//type is 'dir' or 'file' - if (FileActions.actions[type]) { - actions = $.extend(actions, FileActions.actions[type]); +(function() { + + var FileActions = { + actions: {}, + defaults: {}, + icons: {}, + currentFile: null, + register: function (mime, name, permissions, icon, action, displayName) { + if (!this.actions[mime]) { + this.actions[mime] = {}; } - } - if (mime) { - var mimePart = mime.substr(0, mime.indexOf('/')); - if (FileActions.actions[mimePart]) { - actions = $.extend(actions, FileActions.actions[mimePart]); + if (!this.actions[mime][name]) { + this.actions[mime][name] = {}; } - if (FileActions.actions[mime]) { - actions = $.extend(actions, FileActions.actions[mime]); + if (!displayName) { + displayName = t('files', name); } - } - var filteredActions = {}; - $.each(actions, function (name, action) { - if (action.permissions & permissions) { - filteredActions[name] = action; + this.actions[mime][name]['action'] = action; + this.actions[mime][name]['permissions'] = permissions; + this.actions[mime][name]['displayName'] = displayName; + this.icons[name] = icon; + }, + clear: function() { + this.actions = {}; + this.defaults = {}; + this.icons = {}; + this.currentFile = null; + }, + setDefault: function (mime, name) { + this.defaults[mime] = name; + }, + get: function (mime, type, permissions) { + var actions = this.getActions(mime, type, permissions); + var filteredActions = {}; + $.each(actions, function (name, action) { + filteredActions[name] = action.action; + }); + return filteredActions; + }, + getActions: function (mime, type, permissions) { + var actions = {}; + if (this.actions.all) { + actions = $.extend(actions, this.actions.all); } - }); - return filteredActions; - }, - getDefault: function (mime, type, permissions) { - var mimePart; - if (mime) { - mimePart = mime.substr(0, mime.indexOf('/')); - } - var name = false; - if (mime && FileActions.defaults[mime]) { - name = FileActions.defaults[mime]; - } else if (mime && FileActions.defaults[mimePart]) { - name = FileActions.defaults[mimePart]; - } else if (type && FileActions.defaults[type]) { - name = FileActions.defaults[type]; - } else { - name = FileActions.defaults.all; - } - var actions = this.get(mime, type, permissions); - return actions[name]; - }, - /** - * Display file actions for the given element - * @param parent "td" element of the file for which to display actions - * @param triggerEvent if true, triggers the fileActionsReady on the file - * list afterwards (false by default) - */ - display: function (parent, triggerEvent) { - FileActions.currentFile = parent; - var actions = FileActions.getActions(FileActions.getCurrentMimeType(), FileActions.getCurrentType(), FileActions.getCurrentPermissions()); - var file = FileActions.getCurrentFile(); - var nameLinks; - if (FileList.findFileEl(file).data('renaming')) { - return; - } + if (type) {//type is 'dir' or 'file' + if (this.actions[type]) { + actions = $.extend(actions, this.actions[type]); + } + } + if (mime) { + var mimePart = mime.substr(0, mime.indexOf('/')); + if (this.actions[mimePart]) { + actions = $.extend(actions, this.actions[mimePart]); + } + if (this.actions[mime]) { + actions = $.extend(actions, this.actions[mime]); + } + } + var filteredActions = {}; + $.each(actions, function (name, action) { + if (action.permissions & permissions) { + filteredActions[name] = action; + } + }); + return filteredActions; + }, + getDefault: function (mime, type, permissions) { + var mimePart; + if (mime) { + mimePart = mime.substr(0, mime.indexOf('/')); + } + var name = false; + if (mime && this.defaults[mime]) { + name = this.defaults[mime]; + } else if (mime && this.defaults[mimePart]) { + name = this.defaults[mimePart]; + } else if (type && this.defaults[type]) { + name = this.defaults[type]; + } else { + name = this.defaults.all; + } + var actions = this.get(mime, type, permissions); + return actions[name]; + }, + /** + * Display file actions for the given element + * @param parent "td" element of the file for which to display actions + * @param triggerEvent if true, triggers the fileActionsReady on the file + * list afterwards (false by default) + */ + display: function (parent, triggerEvent) { + this.currentFile = parent; + var self = this; + var actions = this.getActions(this.getCurrentMimeType(), this.getCurrentType(), this.getCurrentPermissions()); + var file = this.getCurrentFile(); + var nameLinks; + if (parent.closest('tr').data('renaming')) { + return; + } + + // recreate fileactions + nameLinks = parent.children('a.name'); + nameLinks.find('.fileactions, .nametext .action').remove(); + nameLinks.append('<span class="fileactions" />'); + var defaultAction = this.getDefault(this.getCurrentMimeType(), this.getCurrentType(), this.getCurrentPermissions()); - // recreate fileactions - nameLinks = parent.children('a.name'); - nameLinks.find('.fileactions, .nametext .action').remove(); - nameLinks.append('<span class="fileactions" />'); - var defaultAction = FileActions.getDefault(FileActions.getCurrentMimeType(), FileActions.getCurrentType(), FileActions.getCurrentPermissions()); + var actionHandler = function (event) { + event.stopPropagation(); + event.preventDefault(); - var actionHandler = function (event) { - event.stopPropagation(); - event.preventDefault(); + self.currentFile = event.data.elem; + var file = self.getCurrentFile(); - FileActions.currentFile = event.data.elem; - var file = FileActions.getCurrentFile(); + event.data.actionFunc(file); + }; - event.data.actionFunc(file); - }; + var addAction = function (name, action, displayName) { - var addAction = function (name, action, displayName) { + if ((name === 'Download' || action !== defaultAction) && name !== 'Delete') { - if ((name === 'Download' || action !== defaultAction) && name !== 'Delete') { + var img = self.icons[name], + actionText = displayName, + actionContainer = 'a.name>span.fileactions'; - var img = FileActions.icons[name], - actionText = displayName, - actionContainer = 'a.name>span.fileactions'; + if (name === 'Rename') { + // rename has only an icon which appears behind + // the file name + actionText = ''; + actionContainer = 'a.name span.nametext'; + } + if (img.call) { + img = img(file); + } + var html = '<a href="#" class="action action-' + name.toLowerCase() + '" data-action="' + name + '">'; + if (img) { + html += '<img class ="svg" src="' + img + '" />'; + } + html += '<span> ' + actionText + '</span></a>'; - if (name === 'Rename') { - // rename has only an icon which appears behind - // the file name - actionText = ''; - actionContainer = 'a.name span.nametext'; + var element = $(html); + element.data('action', name); + element.on('click', {a: null, elem: parent, actionFunc: actions[name].action}, actionHandler); + parent.find(actionContainer).append(element); } + + }; + + $.each(actions, function (name, action) { + if (name !== 'Share') { + displayName = action.displayName; + ah = action.action; + + addAction(name, ah, displayName); + } + }); + if(actions.Share){ + displayName = t('files', 'Share'); + addAction('Share', actions.Share, displayName); + } + + // remove the existing delete action + parent.parent().children().last().find('.action.delete').remove(); + if (actions['Delete']) { + var img = self.icons['Delete']; + var html; if (img.call) { img = img(file); } - var html = '<a href="#" class="action action-' + name.toLowerCase() + '" data-action="' + name + '">'; - if (img) { - html += '<img class ="svg" src="' + img + '" />'; + if (typeof trashBinApp !== 'undefined' && trashBinApp) { + html = '<a href="#" original-title="' + t('files', 'Delete permanently') + '" class="action delete delete-icon" />'; + } else { + html = '<a href="#" class="action delete delete-icon" />'; } - html += '<span> ' + actionText + '</span></a>'; - var element = $(html); - element.data('action', name); - element.on('click', {a: null, elem: parent, actionFunc: actions[name].action}, actionHandler); - parent.find(actionContainer).append(element); + element.data('action', actions['Delete']); + element.on('click', {a: null, elem: parent, actionFunc: actions['Delete'].action}, actionHandler); + parent.parent().children().last().append(element); } - }; + if (triggerEvent){ + $('#fileList').trigger(jQuery.Event("fileActionsReady")); + } + }, + getCurrentFile: function () { + return this.currentFile.parent().attr('data-file'); + }, + getCurrentMimeType: function () { + return this.currentFile.parent().attr('data-mime'); + }, + getCurrentType: function () { + return this.currentFile.parent().attr('data-type'); + }, + getCurrentPermissions: function () { + return this.currentFile.parent().data('permissions'); + }, - $.each(actions, function (name, action) { - if (name !== 'Share') { - displayName = action.displayName; - ah = action.action; + /** + * Register the actions that are used by default for the files app. + */ + registerDefaultActions: function(fileList) { + this.register('all', 'Delete', OC.PERMISSION_DELETE, function () { + return OC.imagePath('core', 'actions/delete'); + }, function (filename) { + fileList.do_delete(filename); + $('.tipsy').remove(); + }); - addAction(name, ah, displayName); - } - }); - if(actions.Share){ - displayName = t('files', 'Share'); - addAction('Share', actions.Share, displayName); - } + // t('files', 'Rename') + this.register('all', 'Rename', OC.PERMISSION_UPDATE, function () { + return OC.imagePath('core', 'actions/rename'); + }, function (filename) { + fileList.rename(filename); + }); - // remove the existing delete action - parent.parent().children().last().find('.action.delete').remove(); - if (actions['Delete']) { - var img = FileActions.icons['Delete']; - var html; - if (img.call) { - img = img(file); - } - if (typeof trashBinApp !== 'undefined' && trashBinApp) { - html = '<a href="#" original-title="' + t('files', 'Delete permanently') + '" class="action delete delete-icon" />'; + this.register('dir', 'Open', OC.PERMISSION_READ, '', function (filename) { + var dir = fileList.getCurrentDirectory(); + if (dir !== '/') { + dir = dir + '/'; + } + fileList.changeDirectory(dir + filename); + }); + + this.setDefault('dir', 'Open'); + var downloadScope; + if ($('#allowZipDownload').val() == 1) { + downloadScope = 'all'; } else { - html = '<a href="#" class="action delete delete-icon" />'; + downloadScope = 'file'; } - var element = $(html); - element.data('action', actions['Delete']); - element.on('click', {a: null, elem: parent, actionFunc: actions['Delete'].action}, actionHandler); - parent.parent().children().last().append(element); - } - if (triggerEvent){ - $('#fileList').trigger(jQuery.Event("fileActionsReady")); + this.register(downloadScope, 'Download', OC.PERMISSION_READ, function () { + return OC.imagePath('core', 'actions/download'); + }, function (filename) { + var url = OCA.Files.Files.getDownloadUrl(filename, fileList.getCurrentDirectory()); + if (url) { + OC.redirect(url); + } + }); + + fileList.$fileList.trigger(jQuery.Event("fileActionsReady")); } - }, - getCurrentFile: function () { - return FileActions.currentFile.parent().attr('data-file'); - }, - getCurrentMimeType: function () { - return FileActions.currentFile.parent().attr('data-mime'); - }, - getCurrentType: function () { - return FileActions.currentFile.parent().attr('data-type'); - }, - getCurrentPermissions: function () { - return FileActions.currentFile.parent().data('permissions'); - } -}; - -$(document).ready(function () { - var downloadScope; - if ($('#allowZipDownload').val() == 1) { - downloadScope = 'all'; - } else { - downloadScope = 'file'; - } - - if (typeof disableDownloadActions == 'undefined' || !disableDownloadActions) { - FileActions.register(downloadScope, 'Download', OC.PERMISSION_READ, function () { - return OC.imagePath('core', 'actions/download'); - }, function (filename) { - var url = Files.getDownloadUrl(filename); - if (url) { - OC.redirect(url); - } - }); - } - $('#fileList tr').each(function () { - FileActions.display($(this).children('td.filename')); - }); - - $('#fileList').trigger(jQuery.Event("fileActionsReady")); - -}); - -FileActions.register('all', 'Delete', OC.PERMISSION_DELETE, function () { - return OC.imagePath('core', 'actions/delete'); -}, function (filename) { - FileList.do_delete(filename); - $('.tipsy').remove(); -}); - -// t('files', 'Rename') -FileActions.register('all', 'Rename', OC.PERMISSION_UPDATE, function () { - return OC.imagePath('core', 'actions/rename'); -}, function (filename) { - FileList.rename(filename); -}); - -FileActions.register('dir', 'Open', OC.PERMISSION_READ, '', function (filename) { - var dir = $('#dir').val() || '/'; - if (dir !== '/') { - dir = dir + '/'; - } - FileList.changeDirectory(dir + filename); -}); - -FileActions.setDefault('dir', 'Open'); + }; + + OCA.Files.FileActions = FileActions; +})(); + +// for backward compatibility +window.FileActions = OCA.Files.FileActions; + diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js index 73a441368bb..de34d932b11 100644 --- a/apps/files/js/filelist.js +++ b/apps/files/js/filelist.js @@ -8,1639 +8,1730 @@ * */ -/* global OC, t, n, FileList, FileActions, Files, FileSummary, BreadCrumb */ +/* global Files */ /* global dragOptions, folderDropOptions */ -window.FileList = { - SORT_INDICATOR_ASC_CLASS: 'icon-triangle-s', - SORT_INDICATOR_DESC_CLASS: 'icon-triangle-n', - - appName: t('files', 'Files'), - isEmpty: true, - useUndo:true, - $el: $('#filestable'), - $fileList: $('#fileList'), - breadcrumb: null, - - /** - * Instance of FileSummary - */ - fileSummary: null, - initialized: false, - - // number of files per page - pageSize: 20, - +(function() { /** - * Array of files in the current folder. - * The entries are of file data. + * The FileList class manages a file list view. + * A file list view consists of a controls bar and + * a file list table. */ - files: [], - - /** - * Map of file id to file data - */ - _selectedFiles: {}, - - /** - * Summary of selected files. - * Instance of FileSummary. - */ - _selectionSummary: null, + var FileList = function($el) { + this.initialize($el); + }; + FileList.prototype = { + SORT_INDICATOR_ASC_CLASS: 'icon-triangle-s', + SORT_INDICATOR_DESC_CLASS: 'icon-triangle-n', + + appName: t('files', 'Files'), + isEmpty: true, + useUndo:true, + + /** + * Top-level container with controls and file list + */ + $el: null, + + /** + * Files table + */ + $table: null, + + /** + * List of rows (table tbody) + */ + $fileList: null, + + breadcrumb: null, + + /** + * Instance of FileSummary + */ + fileSummary: null, + initialized: false, + + // number of files per page + pageSize: 20, + + /** + * Array of files in the current folder. + * The entries are of file data. + */ + files: [], + + /** + * File actions handler, defaults to OCA.Files.FileActions + */ + fileActions: null, + + /** + * Map of file id to file data + */ + _selectedFiles: {}, + + /** + * Summary of selected files. + * Instance of FileSummary. + */ + _selectionSummary: null, + + /** + * Sort attribute + */ + _sort: 'name', + + /** + * Sort direction: 'asc' or 'desc' + */ + _sortDirection: 'asc', + + /** + * Sort comparator function for the current sort + */ + _sortComparator: null, + + /** + * Current directory + */ + _currentDirectory: null, + + /** + * Initialize the file list and its components + */ + initialize: function($el) { + var self = this; + if (this.initialized) { + return; + } - /** - * Sort attribute - */ - _sort: 'name', + this.$el = $el; + this.$table = $el.find('table:first'); + this.$fileList = $el.find('#fileList'); + this.fileActions = OCA.Files.FileActions; + this.files = []; + this._selectedFiles = {}; + this._selectionSummary = new OCA.Files.FileSummary(); - /** - * Sort direction: 'asc' or 'desc' - */ - _sortDirection: 'asc', + this.fileSummary = this._createSummary(); - /** - * Sort comparator function for the current sort - */ - _sortComparator: null, + this.setSort('name', 'asc'); - /** - * Initialize the file list and its components - */ - initialize: function() { - var self = this; - if (this.initialized) { - return; - } + this.breadcrumb = new OCA.Files.BreadCrumb({ + onClick: _.bind(this._onClickBreadCrumb, this), + onDrop: _.bind(this._onDropOnBreadCrumb, this), + getCrumbUrl: function(part, index) { + return self.linkTo(part.dir); + } + }); - // TODO: FileList should not know about global elements - this.$el = $('#filestable'); - this.$fileList = $('#fileList'); - this.files = []; - this._selectedFiles = {}; - this._selectionSummary = new FileSummary(); + this.$el.find('#controls').prepend(this.breadcrumb.$el); - this.fileSummary = this._createSummary(); + this.$el.find('thead th .columntitle').click(_.bind(this._onClickHeader, this)); - this.setSort('name', 'asc'); + $(window).resize(function() { + // TODO: debounce this ? + var width = $(this).width(); + self.breadcrumb.resize(width, false); + }); - this.breadcrumb = new BreadCrumb({ - onClick: this._onClickBreadCrumb, - onDrop: _.bind(this._onDropOnBreadCrumb, this), - getCrumbUrl: function(part, index) { - return self.linkTo(part.dir); + this.$fileList.on('click','td.filename>a.name', _.bind(this._onClickFile, this)); + this.$fileList.on('change', 'td.filename>input:checkbox', _.bind(this._onClickFileCheckbox, this)); + this.$el.on('urlChanged', _.bind(this._onUrlChanged, this)); + this.$el.find('#select_all').click(_.bind(this._onClickSelectAll, this)); + this.$el.find('.download').click(_.bind(this._onClickDownloadSelected, this)); + this.$el.find('.delete-selected').click(_.bind(this._onClickDeleteSelected, this)); + + this.setupUploadEvents(); + + // FIXME: only do this when visible + $(window).scroll(function(e) {self._onScroll(e);}); + }, + + /** + * Event handler for when the URL changed + */ + _onUrlChanged: function(e) { + if (e && e.dir) { + this.changeDirectory(e.dir, false, true); } - }); - - $('#controls').prepend(this.breadcrumb.$el); - - this.$el.find('thead th .columntitle').click(_.bind(this._onClickHeader, this)); - - $(window).resize(function() { - // TODO: debounce this ? - var width = $(this).width(); - FileList.breadcrumb.resize(width, false); - }); - - this.$fileList.on('click','td.filename>a.name', _.bind(this._onClickFile, this)); - this.$fileList.on('change', 'td.filename>input:checkbox', _.bind(this._onClickFileCheckbox, this)); - this.$el.find('#select_all').click(_.bind(this._onClickSelectAll, this)); - this.$el.find('.download').click(_.bind(this._onClickDownloadSelected, this)); - this.$el.find('.delete-selected').click(_.bind(this._onClickDeleteSelected, this)); - }, - - /** - * Selected/deselects the given file element and updated - * the internal selection cache. - * - * @param $tr single file row element - * @param state true to select, false to deselect - */ - _selectFileEl: function($tr, state) { - var $checkbox = $tr.find('td.filename>input:checkbox'); - var oldData = !!this._selectedFiles[$tr.data('id')]; - var data; - $checkbox.prop('checked', state); - $tr.toggleClass('selected', state); - // already selected ? - if (state === oldData) { - return; - } - data = this.elementToFile($tr); - if (state) { - this._selectedFiles[$tr.data('id')] = data; - this._selectionSummary.add(data); - } - else { - delete this._selectedFiles[$tr.data('id')]; - this._selectionSummary.remove(data); - } - this.$el.find('#select_all').prop('checked', this._selectionSummary.getTotal() === this.files.length); - }, + }, + + /** + * Selected/deselects the given file element and updated + * the internal selection cache. + * + * @param $tr single file row element + * @param state true to select, false to deselect + */ + _selectFileEl: function($tr, state) { + var $checkbox = $tr.find('td.filename>input:checkbox'); + var oldData = !!this._selectedFiles[$tr.data('id')]; + var data; + $checkbox.prop('checked', state); + $tr.toggleClass('selected', state); + // already selected ? + if (state === oldData) { + return; + } + data = this.elementToFile($tr); + if (state) { + this._selectedFiles[$tr.data('id')] = data; + this._selectionSummary.add(data); + } + else { + delete this._selectedFiles[$tr.data('id')]; + this._selectionSummary.remove(data); + } + this.$el.find('#select_all').prop('checked', this._selectionSummary.getTotal() === this.files.length); + }, + + /** + * Event handler for when clicking on files to select them + */ + _onClickFile: function(event) { + var $tr = $(event.target).closest('tr'); + if (event.ctrlKey || event.shiftKey) { + event.preventDefault(); + if (event.shiftKey) { + var $lastTr = $(this._lastChecked); + var lastIndex = $lastTr.index(); + var currentIndex = $tr.index(); + var $rows = this.$fileList.children('tr'); + + // last clicked checkbox below current one ? + if (lastIndex > currentIndex) { + var aux = lastIndex; + lastIndex = currentIndex; + currentIndex = aux; + } - /** - * Event handler for when clicking on files to select them - */ - _onClickFile: function(event) { - var $tr = $(event.target).closest('tr'); - if (event.ctrlKey || event.shiftKey) { - event.preventDefault(); - if (event.shiftKey) { - var $lastTr = $(this._lastChecked); - var lastIndex = $lastTr.index(); - var currentIndex = $tr.index(); - var $rows = this.$fileList.children('tr'); - - // last clicked checkbox below current one ? - if (lastIndex > currentIndex) { - var aux = lastIndex; - lastIndex = currentIndex; - currentIndex = aux; + // auto-select everything in-between + for (var i = lastIndex + 1; i < currentIndex; i++) { + this._selectFileEl($rows.eq(i), true); + } } - - // auto-select everything in-between - for (var i = lastIndex + 1; i < currentIndex; i++) { - this._selectFileEl($rows.eq(i), true); + else { + this._lastChecked = $tr; + } + var $checkbox = $tr.find('td.filename>input:checkbox'); + this._selectFileEl($tr, !$checkbox.prop('checked')); + this.updateSelectionSummary(); + } else { + var filename = $tr.attr('data-file'); + var renaming = $tr.data('renaming'); + if (!renaming) { + this.fileActions.currentFile = $tr.find('td'); + var mime = this.fileActions.getCurrentMimeType(); + var type = this.fileActions.getCurrentType(); + var permissions = this.fileActions.getCurrentPermissions(); + var action = this.fileActions.getDefault(mime,type, permissions); + if (action) { + event.preventDefault(); + action(filename); + } } } - else { - this._lastChecked = $tr; + }, + + /** + * Event handler for when clicking on a file's checkbox + */ + _onClickFileCheckbox: function(e) { + var $tr = $(e.target).closest('tr'); + this._selectFileEl($tr, !$tr.hasClass('selected')); + this._lastChecked = $tr; + this.updateSelectionSummary(); + }, + + /** + * Event handler for when selecting/deselecting all files + */ + _onClickSelectAll: function(e) { + var checked = $(e.target).prop('checked'); + this.$fileList.find('td.filename>input:checkbox').prop('checked', checked) + .closest('tr').toggleClass('selected', checked); + this._selectedFiles = {}; + this._selectionSummary.clear(); + if (checked) { + for (var i = 0; i < this.files.length; i++) { + var fileData = this.files[i]; + this._selectedFiles[fileData.id] = fileData; + this._selectionSummary.add(fileData); + } } - var $checkbox = $tr.find('td.filename>input:checkbox'); - this._selectFileEl($tr, !$checkbox.prop('checked')); this.updateSelectionSummary(); - } else { - var filename = $tr.attr('data-file'); - var renaming = $tr.data('renaming'); - if (!renaming) { - FileActions.currentFile = $tr.find('td'); - var mime=FileActions.getCurrentMimeType(); - var type=FileActions.getCurrentType(); - var permissions = FileActions.getCurrentPermissions(); - var action=FileActions.getDefault(mime,type, permissions); - if (action) { - event.preventDefault(); - action(filename); + }, + + /** + * Event handler for when clicking on "Download" for the selected files + */ + _onClickDownloadSelected: function(event) { + var files; + var dir = this.getCurrentDirectory(); + if (this.isAllSelected()) { + files = OC.basename(dir); + dir = OC.dirname(dir) || '/'; + } + else { + files = _.pluck(this.getSelectedFiles(), 'name'); + } + OC.Notification.show(t('files','Your download is being prepared. This might take some time if the files are big.')); + OC.redirect(this.getDownloadUrl(files, dir)); + return false; + }, + + /** + * Event handler for when clicking on "Delete" for the selected files + */ + _onClickDeleteSelected: function(event) { + var files = null; + if (!this.isAllSelected()) { + files = _.pluck(this.getSelectedFiles(), 'name'); + } + this.do_delete(files); + event.preventDefault(); + return false; + }, + + /** + * Event handler when clicking on a table header + */ + _onClickHeader: function(e) { + var $target = $(e.target); + var sort; + if (!$target.is('a')) { + $target = $target.closest('a'); + } + sort = $target.attr('data-sort'); + if (sort) { + if (this._sort === sort) { + this.setSort(sort, (this._sortDirection === 'desc')?'asc':'desc'); } + else { + this.setSort(sort, 'asc'); + } + this.reload(); } - } - }, + }, + + /** + * Event handler when clicking on a bread crumb + */ + _onClickBreadCrumb: function(e) { + var $el = $(e.target).closest('.crumb'), + $targetDir = $el.data('dir'); + + if ($targetDir !== undefined) { + e.preventDefault(); + this.changeDirectory($targetDir); + } + }, - /** - * Event handler for when clicking on a file's checkbox - */ - _onClickFileCheckbox: function(e) { - var $tr = $(e.target).closest('tr'); - this._selectFileEl($tr, !$tr.hasClass('selected')); - this._lastChecked = $tr; - this.updateSelectionSummary(); - }, + _onScroll: function(e) { + if ($(window).scrollTop() + $(window).height() > $(document).height() - 500) { + this._nextPage(true); + } + }, + + /** + * Event handler when dropping on a breadcrumb + */ + _onDropOnBreadCrumb: function( event, ui ) { + var $target = $(event.target); + if (!$target.is('.crumb')) { + $target = $target.closest('.crumb'); + } + var targetPath = $(event.target).data('dir'); + var dir = this.getCurrentDirectory(); + while (dir.substr(0,1) === '/') {//remove extra leading /'s + dir = dir.substr(1); + } + dir = '/' + dir; + if (dir.substr(-1,1) !== '/') { + dir = dir + '/'; + } + // do nothing if dragged on current dir + if (targetPath === dir || targetPath + '/' === dir) { + return; + } - /** - * Event handler for when selecting/deselecting all files - */ - _onClickSelectAll: function(e) { - var checked = $(e.target).prop('checked'); - this.$fileList.find('td.filename>input:checkbox').prop('checked', checked) - .closest('tr').toggleClass('selected', checked); - this._selectedFiles = {}; - this._selectionSummary.clear(); - if (checked) { - for (var i = 0; i < this.files.length; i++) { - var fileData = this.files[i]; - this._selectedFiles[fileData.id] = fileData; - this._selectionSummary.add(fileData); + var files = this.getSelectedFiles(); + if (files.length === 0) { + // single one selected without checkbox? + files = _.map(ui.helper.find('tr'), this.elementToFile); } - } - this.updateSelectionSummary(); - }, - /** - * Event handler for when clicking on "Download" for the selected files - */ - _onClickDownloadSelected: function(event) { - var files; - var dir = this.getCurrentDirectory(); - if (this.isAllSelected()) { - files = OC.basename(dir); - dir = OC.dirname(dir) || '/'; - } - else { - files = _.pluck(this.getSelectedFiles(), 'name'); - } - OC.Notification.show(t('files','Your download is being prepared. This might take some time if the files are big.')); - OC.redirect(Files.getDownloadUrl(files, dir)); - return false; - }, + this.move(_.pluck(files, 'name'), targetPath); + }, - /** - * Event handler for when clicking on "Delete" for the selected files - */ - _onClickDeleteSelected: function(event) { - var files = null; - if (!FileList.isAllSelected()) { - files = _.pluck(this.getSelectedFiles(), 'name'); - } - this.do_delete(files); - event.preventDefault(); - return false; - }, + /** + * Sets a new page title + */ + setPageTitle: function(title){ + if (title) { + title += ' - '; + } else { + title = ''; + } + title += this.appName; + // Sets the page title with the " - ownCloud" suffix as in templates + window.document.title = title + ' - ' + oc_defaults.title; - /** - * Event handler when clicking on a table header - */ - _onClickHeader: function(e) { - var $target = $(e.target); - var sort; - if (!$target.is('a')) { - $target = $target.closest('a'); - } - sort = $target.attr('data-sort'); - if (sort) { - if (this._sort === sort) { - this.setSort(sort, (this._sortDirection === 'desc')?'asc':'desc'); + return true; + }, + /** + * Returns the tr element for a given file name + * @param fileName file name + */ + findFileEl: function(fileName){ + // use filterAttr to avoid escaping issues + return this.$fileList.find('tr').filterAttr('data-file', fileName); + }, + + /** + * Returns the file data from a given file element. + * @param $el file tr element + * @return file data + */ + elementToFile: function($el){ + $el = $($el); + return { + id: parseInt($el.attr('data-id'), 10), + name: $el.attr('data-file'), + mimetype: $el.attr('data-mime'), + type: $el.attr('data-type'), + size: parseInt($el.attr('data-size'), 10), + etag: $el.attr('data-etag') + }; + }, + + /** + * Appends the next page of files into the table + * @param animate true to animate the new elements + */ + _nextPage: function(animate) { + var index = this.$fileList.children().length, + count = this.pageSize, + tr, + fileData, + newTrs = [], + isAllSelected = this.isAllSelected(); + + if (index >= this.files.length) { + return; } - else { - this.setSort(sort, 'asc'); + + while (count > 0 && index < this.files.length) { + fileData = this.files[index]; + tr = this._renderRow(fileData, {updateSummary: false}); + this.$fileList.append(tr); + if (isAllSelected || this._selectedFiles[fileData.id]) { + tr.addClass('selected'); + tr.find('input:checkbox').prop('checked', true); + } + if (animate) { + tr.addClass('appear transparent'); + newTrs.push(tr); + } + index++; + count--; } - this.reload(); - } - }, - /** - * Event handler when clicking on a bread crumb - */ - _onClickBreadCrumb: function(e) { - var $el = $(e.target).closest('.crumb'), - $targetDir = $el.data('dir'); + if (animate) { + // defer, for animation + window.setTimeout(function() { + for (var i = 0; i < newTrs.length; i++ ) { + newTrs[i].removeClass('transparent'); + } + }, 0); + } + }, - if ($targetDir !== undefined) { - e.preventDefault(); - FileList.changeDirectory($targetDir); - } - }, + /** + * Sets the files to be displayed in the list. + * This operation will re-render the list and update the summary. + * @param filesArray array of file data (map) + */ + setFiles: function(filesArray) { + // detach to make adding multiple rows faster + this.files = filesArray; - _onScroll: function(e) { - if ($(window).scrollTop() + $(window).height() > $(document).height() - 500) { - this._nextPage(true); - } - }, + this.$fileList.detach(); + this.$fileList.empty(); - /** - * Event handler when dropping on a breadcrumb - */ - _onDropOnBreadCrumb: function( event, ui ) { - var $target = $(event.target); - if (!$target.is('.crumb')) { - $target = $target.closest('.crumb'); - } - var targetPath = $(event.target).data('dir'); - var dir = this.getCurrentDirectory(); - while (dir.substr(0,1) === '/') {//remove extra leading /'s - dir = dir.substr(1); - } - dir = '/' + dir; - if (dir.substr(-1,1) !== '/') { - dir = dir + '/'; - } - // do nothing if dragged on current dir - if (targetPath === dir || targetPath + '/' === dir) { - return; - } + // clear "Select all" checkbox + this.$el.find('#select_all').prop('checked', false); - var files = this.getSelectedFiles(); - if (files.length === 0) { - // single one selected without checkbox? - files = _.map(ui.helper.find('tr'), FileList.elementToFile); - } + this.isEmpty = this.files.length === 0; + this._nextPage(); - FileList.move(_.pluck(files, 'name'), targetPath); - }, + this.$el.find('thead').after(this.$fileList); - /** - * Sets a new page title - */ - setPageTitle: function(title){ - if (title) { - title += ' - '; - } else { - title = ''; - } - title += FileList.appName; - // Sets the page title with the " - ownCloud" suffix as in templates - window.document.title = title + ' - ' + oc_defaults.title; + this.updateEmptyContent(); + this.$fileList.trigger(jQuery.Event("fileActionsReady")); + // "Files" might not be loaded in extending apps + if (window.Files) { + Files.setupDragAndDrop(); + } - return true; - }, - /** - * Returns the tr element for a given file name - * @param fileName file name - */ - findFileEl: function(fileName){ - // use filterAttr to avoid escaping issues - return this.$fileList.find('tr').filterAttr('data-file', fileName); - }, + this.fileSummary.calculate(filesArray); - /** - * Returns the file data from a given file element. - * @param $el file tr element - * @return file data - */ - elementToFile: function($el){ - $el = $($el); - return { - id: parseInt($el.attr('data-id'), 10), - name: $el.attr('data-file'), - mimetype: $el.attr('data-mime'), - type: $el.attr('data-type'), - size: parseInt($el.attr('data-size'), 10), - etag: $el.attr('data-etag') - }; - }, + this.updateSelectionSummary(); + $(window).scrollTop(0); + + this.$fileList.trigger(jQuery.Event("updated")); + }, + /** + * Creates a new table row element using the given file data. + * @param fileData map of file attributes + * @param options map of attribute "loading" whether the entry is currently loading + * @return new tr element (not appended to the table) + */ + _createRow: function(fileData, options) { + var td, simpleSize, basename, extension, sizeColor, + icon = OC.Util.replaceSVGIcon(fileData.icon), + name = fileData.name, + type = fileData.type || 'file', + mtime = parseInt(fileData.mtime, 10) || new Date().getTime(), + mime = fileData.mimetype, + linkUrl; + options = options || {}; + + if (type === 'dir') { + mime = mime || 'httpd/unix-directory'; + } - /** - * Appends the next page of files into the table - * @param animate true to animate the new elements - */ - _nextPage: function(animate) { - var index = this.$fileList.children().length, - count = this.pageSize, - tr, - fileData, - newTrs = [], - isAllSelected = this.isAllSelected(); - - if (index >= this.files.length) { - return; - } + // user should always be able to rename a share mount point + var allowRename = 0; + if (fileData.isShareMountPoint) { + allowRename = OC.PERMISSION_UPDATE; + } + + //containing tr + var tr = $('<tr></tr>').attr({ + "data-id" : fileData.id, + "data-type": type, + "data-size": fileData.size, + "data-file": name, + "data-mime": mime, + "data-mtime": mtime, + "data-etag": fileData.etag, + "data-permissions": fileData.permissions | allowRename || this.getDirectoryPermissions() + }); - while (count > 0 && index < this.files.length) { - fileData = this.files[index]; - tr = this._renderRow(fileData, {updateSummary: false}); - this.$fileList.append(tr); - if (isAllSelected || this._selectedFiles[fileData.id]) { - tr.addClass('selected'); - tr.find('input:checkbox').prop('checked', true); + if (type === 'dir') { + // use default folder icon + icon = icon || OC.imagePath('core', 'filetypes/folder'); } - if (animate) { - tr.addClass('appear transparent'); - newTrs.push(tr); + else { + icon = icon || OC.imagePath('core', 'filetypes/file'); } - index++; - count--; - } - if (animate) { - // defer, for animation - window.setTimeout(function() { - for (var i = 0; i < newTrs.length; i++ ) { - newTrs[i].removeClass('transparent'); - } - }, 0); - } - }, + // filename td + td = $('<td></td>').attr({ + "class": "filename", + "style": 'background-image:url(' + icon + '); background-size: 32px;' + }); - /** - * Sets the files to be displayed in the list. - * This operation will re-render the list and update the summary. - * @param filesArray array of file data (map) - */ - setFiles: function(filesArray) { - // detach to make adding multiple rows faster - this.files = filesArray; + // linkUrl + if (type === 'dir') { + linkUrl = this.linkTo(this.getCurrentDirectory() + '/' + name); + } + else { + linkUrl = this.getDownloadUrl(name, this.getCurrentDirectory()); + } + td.append('<input id="select-' + fileData.id + '" type="checkbox" /><label for="select-' + fileData.id + '"></label>'); + var linkElem = $('<a></a>').attr({ + "class": "name", + "href": linkUrl + }); - this.$fileList.detach(); - this.$fileList.empty(); + // from here work on the display name + name = fileData.displayName || name; - // clear "Select all" checkbox - this.$el.find('#select_all').prop('checked', false); + // split extension from filename for non dirs + if (type !== 'dir' && name.indexOf('.') !== -1) { + basename = name.substr(0, name.lastIndexOf('.')); + extension = name.substr(name.lastIndexOf('.')); + } else { + basename = name; + extension = false; + } + var nameSpan=$('<span></span>').addClass('nametext').text(basename); + linkElem.append(nameSpan); + if (extension) { + nameSpan.append($('<span></span>').addClass('extension').text(extension)); + } + // dirs can show the number of uploaded files + if (type === 'dir') { + linkElem.append($('<span></span>').attr({ + 'class': 'uploadtext', + 'currentUploads': 0 + })); + } + td.append(linkElem); + tr.append(td); - this.isEmpty = this.files.length === 0; - this._nextPage(); + // size column + if (typeof(fileData.size) !== 'undefined' && fileData.size >= 0) { + simpleSize = humanFileSize(parseInt(fileData.size, 10)); + sizeColor = Math.round(160-Math.pow((fileData.size/(1024*1024)),2)); + } else { + simpleSize = t('files', 'Pending'); + } - this.$el.find('thead').after(this.$fileList); + td = $('<td></td>').attr({ + "class": "filesize", + "style": 'color:rgb(' + sizeColor + ',' + sizeColor + ',' + sizeColor + ')' + }).text(simpleSize); + tr.append(td); + + // date column + var modifiedColor = Math.round((Math.round((new Date()).getTime() / 1000) - mtime)/60/60/24*5); + td = $('<td></td>').attr({ "class": "date" }); + td.append($('<span></span>').attr({ + "class": "modified", + "title": formatDate(mtime), + "style": 'color:rgb('+modifiedColor+','+modifiedColor+','+modifiedColor+')' + }).text( relative_modified_date(mtime / 1000) )); + tr.find('.filesize').text(simpleSize); + tr.append(td); + return tr; + }, + + /** + * Adds an entry to the files array and also into the DOM + * in a sorted manner. + * + * @param fileData map of file attributes + * @param options map of attributes: + * - "updateSummary" true to update the summary after adding (default), false otherwise + * @return new tr element (not appended to the table) + */ + add: function(fileData, options) { + var index = -1; + var $tr; + var $rows; + var $insertionPoint; + options = options || {}; + + // there are three situations to cover: + // 1) insertion point is visible on the current page + // 2) insertion point is on a not visible page (visible after scrolling) + // 3) insertion point is at the end of the list + + $rows = this.$fileList.children(); + index = this._findInsertionIndex(fileData); + if (index > this.files.length) { + index = this.files.length; + } + else { + $insertionPoint = $rows.eq(index); + } - this.updateEmptyContent(); - this.$fileList.trigger(jQuery.Event("fileActionsReady")); - // "Files" might not be loaded in extending apps - if (window.Files) { - Files.setupDragAndDrop(); - } + // is the insertion point visible ? + if ($insertionPoint.length) { + // only render if it will really be inserted + $tr = this._renderRow(fileData, options); + $insertionPoint.before($tr); + } + else { + // if insertion point is after the last visible + // entry, append + if (index === $rows.length) { + $tr = this._renderRow(fileData, options); + this.$fileList.append($tr); + } + } - this.fileSummary.calculate(filesArray); + this.isEmpty = false; + this.files.splice(index, 0, fileData); - FileList.updateSelectionSummary(); - $(window).scrollTop(0); + if ($tr && options.animate) { + $tr.addClass('appear transparent'); + window.setTimeout(function() { + $tr.removeClass('transparent'); + }); + } - this.$fileList.trigger(jQuery.Event("updated")); - }, - /** - * Creates a new table row element using the given file data. - * @param fileData map of file attributes - * @param options map of attribute "loading" whether the entry is currently loading - * @return new tr element (not appended to the table) - */ - _createRow: function(fileData, options) { - var td, simpleSize, basename, extension, sizeColor, - icon = OC.Util.replaceSVGIcon(fileData.icon), - name = fileData.name, - type = fileData.type || 'file', - mtime = parseInt(fileData.mtime, 10) || new Date().getTime(), - mime = fileData.mimetype, - linkUrl; - options = options || {}; - - if (type === 'dir') { - mime = mime || 'httpd/unix-directory'; - } + // defaults to true if not defined + if (typeof(options.updateSummary) === 'undefined' || !!options.updateSummary) { + this.fileSummary.add(fileData, true); + this.updateEmptyContent(); + } - // user should always be able to rename a share mount point - var allowRename = 0; - if (fileData.isShareMountPoint) { - allowRename = OC.PERMISSION_UPDATE; - } + return $tr; + }, + + /** + * Creates a new row element based on the given attributes + * and returns it. + * + * @param fileData map of file attributes + * @param options map of attributes: + * - "index" optional index at which to insert the element + * - "updateSummary" true to update the summary after adding (default), false otherwise + * @return new tr element (not appended to the table) + */ + _renderRow: function(fileData, options) { + options = options || {}; + var type = fileData.type || 'file', + mime = fileData.mimetype, + permissions = parseInt(fileData.permissions, 10) || 0; + + if (fileData.isShareMountPoint) { + permissions = permissions | OC.PERMISSION_UPDATE; + } - //containing tr - var tr = $('<tr></tr>').attr({ - "data-id" : fileData.id, - "data-type": type, - "data-size": fileData.size, - "data-file": name, - "data-mime": mime, - "data-mtime": mtime, - "data-etag": fileData.etag, - "data-permissions": fileData.permissions | allowRename || this.getDirectoryPermissions() - }); - - if (type === 'dir') { - // use default folder icon - icon = icon || OC.imagePath('core', 'filetypes/folder'); - } - else { - icon = icon || OC.imagePath('core', 'filetypes/file'); - } + if (type === 'dir') { + mime = mime || 'httpd/unix-directory'; + } + var tr = this._createRow( + fileData, + options + ); + var filenameTd = tr.find('td.filename'); + + // TODO: move dragging to FileActions ? + // enable drag only for deletable files + if (permissions & OC.PERMISSION_DELETE) { + filenameTd.draggable(dragOptions); + } + // allow dropping on folders + if (fileData.type === 'dir') { + filenameTd.droppable(folderDropOptions); + } - // filename td - td = $('<td></td>').attr({ - "class": "filename", - "style": 'background-image:url(' + icon + '); background-size: 32px;' - }); + if (options.hidden) { + tr.addClass('hidden'); + } - // linkUrl - if (type === 'dir') { - linkUrl = FileList.linkTo(FileList.getCurrentDirectory() + '/' + name); - } - else { - linkUrl = Files.getDownloadUrl(name, FileList.getCurrentDirectory()); - } - td.append('<input id="select-' + fileData.id + '" type="checkbox" /><label for="select-' + fileData.id + '"></label>'); - var linkElem = $('<a></a>').attr({ - "class": "name", - "href": linkUrl - }); - - // from here work on the display name - name = fileData.displayName || name; - - // split extension from filename for non dirs - if (type !== 'dir' && name.indexOf('.') !== -1) { - basename = name.substr(0, name.lastIndexOf('.')); - extension = name.substr(name.lastIndexOf('.')); - } else { - basename = name; - extension = false; - } - var nameSpan=$('<span></span>').addClass('nametext').text(basename); - linkElem.append(nameSpan); - if (extension) { - nameSpan.append($('<span></span>').addClass('extension').text(extension)); - } - // dirs can show the number of uploaded files - if (type === 'dir') { - linkElem.append($('<span></span>').attr({ - 'class': 'uploadtext', - 'currentUploads': 0 - })); - } - td.append(linkElem); - tr.append(td); - - // size column - if (typeof(fileData.size) !== 'undefined' && fileData.size >= 0) { - simpleSize = humanFileSize(parseInt(fileData.size, 10)); - sizeColor = Math.round(160-Math.pow((fileData.size/(1024*1024)),2)); - } else { - simpleSize = t('files', 'Pending'); - } + // display actions + this.fileActions.display(filenameTd, false); + + if (fileData.isPreviewAvailable) { + // lazy load / newly inserted td ? + if (!fileData.icon) { + this.lazyLoadPreview({ + path: this.getCurrentDirectory() + '/' + fileData.name, + mime: mime, + etag: fileData.etag, + callback: function(url) { + filenameTd.css('background-image', 'url(' + url + ')'); + } + }); + } + else { + // set the preview URL directly + var urlSpec = { + file: this.getCurrentDirectory() + '/' + fileData.name, + c: fileData.etag + }; + var previewUrl = this.generatePreviewUrl(urlSpec); + previewUrl = previewUrl.replace('(', '%28').replace(')', '%29'); + filenameTd.css('background-image', 'url(' + previewUrl + ')'); + } + } + return tr; + }, + /** + * Returns the current directory + * @return current directory + */ + getCurrentDirectory: function(){ + return this._currentDirectory || this.$el.find('#dir').val() || '/'; + }, + /** + * Returns the directory permissions + * @return permission value as integer + */ + getDirectoryPermissions: function() { + return parseInt(this.$el.find('#permissions').val(), 10); + }, + /** + * @brief Changes the current directory and reload the file list. + * @param targetDir target directory (non URL encoded) + * @param changeUrl false if the URL must not be changed (defaults to true) + * @param {boolean} force set to true to force changing directory + */ + changeDirectory: function(targetDir, changeUrl, force) { + var currentDir = this.getCurrentDirectory(); + targetDir = targetDir || '/'; + if (!force && currentDir === targetDir) { + return; + } + this._setCurrentDir(targetDir, changeUrl); + this.reload(); + }, + linkTo: function(dir) { + return OC.linkTo('files', 'index.php')+"?dir="+ encodeURIComponent(dir).replace(/%2F/g, '/'); + }, + + /** + * Sets the file actions handler + */ + setFileActions: function(fileActions) { + this.fileActions = fileActions; + }, + + /** + * Sets the current directory name and updates the breadcrumb. + * @param targetDir directory to display + * @param changeUrl true to also update the URL, false otherwise (default) + */ + _setCurrentDir: function(targetDir, changeUrl) { + var url, + previousDir = this.getCurrentDirectory(), + baseDir = OC.basename(targetDir); + + if (baseDir !== '') { + this.setPageTitle(baseDir); + } + else { + this.setPageTitle(); + } - td = $('<td></td>').attr({ - "class": "filesize", - "style": 'color:rgb(' + sizeColor + ',' + sizeColor + ',' + sizeColor + ')' - }).text(simpleSize); - tr.append(td); - - // date column - var modifiedColor = Math.round((Math.round((new Date()).getTime() / 1000) - mtime)/60/60/24*5); - td = $('<td></td>').attr({ "class": "date" }); - td.append($('<span></span>').attr({ - "class": "modified", - "title": formatDate(mtime), - "style": 'color:rgb('+modifiedColor+','+modifiedColor+','+modifiedColor+')' - }).text( relative_modified_date(mtime / 1000) )); - tr.find('.filesize').text(simpleSize); - tr.append(td); - return tr; - }, + this._currentDirectory = targetDir; - /** - * Adds an entry to the files array and also into the DOM - * in a sorted manner. - * - * @param fileData map of file attributes - * @param options map of attributes: - * - "updateSummary" true to update the summary after adding (default), false otherwise - * @return new tr element (not appended to the table) - */ - add: function(fileData, options) { - var index = -1; - var $tr; - var $rows; - var $insertionPoint; - options = options || {}; - - // there are three situations to cover: - // 1) insertion point is visible on the current page - // 2) insertion point is on a not visible page (visible after scrolling) - // 3) insertion point is at the end of the list - - $rows = this.$fileList.children(); - index = this._findInsertionIndex(fileData); - if (index > this.files.length) { - index = this.files.length; - } - else { - $insertionPoint = $rows.eq(index); - } + // legacy stuff + this.$el.find('#dir').val(targetDir); - // is the insertion point visible ? - if ($insertionPoint.length) { - // only render if it will really be inserted - $tr = this._renderRow(fileData, options); - $insertionPoint.before($tr); - } - else { - // if insertion point is after the last visible - // entry, append - if (index === $rows.length) { - $tr = this._renderRow(fileData, options); - this.$fileList.append($tr); + if (changeUrl !== false) { + this.$el.trigger(jQuery.Event('changeDirectory', { + dir: targetDir, + previousDir: previousDir + })); } - } - - this.isEmpty = false; - this.files.splice(index, 0, fileData); - - if ($tr && options.animate) { - $tr.addClass('appear transparent'); - window.setTimeout(function() { - $tr.removeClass('transparent'); + this.breadcrumb.setDirectory(this.getCurrentDirectory()); + }, + /** + * Sets the current sorting and refreshes the list + * + * @param sort sort attribute name + * @param direction sort direction, one of "asc" or "desc" + */ + setSort: function(sort, direction) { + var comparator = FileList.Comparators[sort] || FileList.Comparators.name; + this._sort = sort; + this._sortDirection = (direction === 'desc')?'desc':'asc'; + this._sortComparator = comparator; + if (direction === 'desc') { + this._sortComparator = function(fileInfo1, fileInfo2) { + return -comparator(fileInfo1, fileInfo2); + }; + } + this.$el.find('thead th .sort-indicator') + .removeClass(this.SORT_INDICATOR_ASC_CLASS + ' ' + this.SORT_INDICATOR_DESC_CLASS); + this.$el.find('thead th.column-' + sort + ' .sort-indicator') + .addClass(direction === 'desc' ? this.SORT_INDICATOR_DESC_CLASS : this.SORT_INDICATOR_ASC_CLASS); + }, + /** + * @brief Reloads the file list using ajax call + */ + reload: function() { + var self = this; + this._selectedFiles = {}; + this._selectionSummary.clear(); + this.$el.find('#select_all').prop('checked', false); + this.showMask(); + if (this._reloadCall) { + this._reloadCall.abort(); + } + this._reloadCall = $.ajax({ + url: this.getAjaxUrl('list'), + data: { + dir : this.getCurrentDirectory(), + sort: this._sort, + sortdirection: this._sortDirection + }, + error: function(result) { + self.reloadCallback(result); + }, + success: function(result) { + self.reloadCallback(result); + } }); - } - - // defaults to true if not defined - if (typeof(options.updateSummary) === 'undefined' || !!options.updateSummary) { - this.fileSummary.add(fileData, true); - this.updateEmptyContent(); - } - - return $tr; - }, - - /** - * Creates a new row element based on the given attributes - * and returns it. - * - * @param fileData map of file attributes - * @param options map of attributes: - * - "index" optional index at which to insert the element - * - "updateSummary" true to update the summary after adding (default), false otherwise - * @return new tr element (not appended to the table) - */ - _renderRow: function(fileData, options) { - options = options || {}; - var type = fileData.type || 'file', - mime = fileData.mimetype, - permissions = parseInt(fileData.permissions, 10) || 0; - - if (fileData.isShareMountPoint) { - permissions = permissions | OC.PERMISSION_UPDATE; - } + }, + reloadCallback: function(result) { + delete this._reloadCall; + this.hideMask(); + + if (!result || result.status === 'error') { + OC.Notification.show(result.data.message); + return; + } - if (type === 'dir') { - mime = mime || 'httpd/unix-directory'; - } - var tr = this._createRow( - fileData, - options - ); - var filenameTd = tr.find('td.filename'); - - // TODO: move dragging to FileActions ? - // enable drag only for deletable files - if (permissions & OC.PERMISSION_DELETE) { - filenameTd.draggable(dragOptions); - } - // allow dropping on folders - if (fileData.type === 'dir') { - filenameTd.droppable(folderDropOptions); - } + if (result.status === 404) { + // go back home + this.changeDirectory('/'); + return; + } + // aborted ? + if (result.status === 0){ + return; + } - if (options.hidden) { - tr.addClass('hidden'); - } + // TODO: should rather return upload file size through + // the files list ajax call + Files.updateStorageStatistics(true); - // display actions - FileActions.display(filenameTd, false); + if (result.data.permissions) { + this.setDirectoryPermissions(result.data.permissions); + } - if (fileData.isPreviewAvailable) { - // lazy load / newly inserted td ? - if (!fileData.icon) { - Files.lazyLoadPreview(getPathForPreview(fileData.name), mime, function(url) { - filenameTd.css('background-image', 'url(' + url + ')'); - }, null, null, fileData.etag); + this.setFiles(result.data.files); + }, + + getAjaxUrl: function(action, params) { + return Files.getAjaxUrl(action, params); + }, + + getDownloadUrl: function(files, dir) { + return Files.getDownloadUrl(files, dir || this.getCurrentDirectory()); + }, + + /** + * Generates a preview URL based on the URL space. + * @param urlSpec map with {x: width, y: height, file: file path} + * @return preview URL + */ + generatePreviewUrl: function(urlSpec) { + urlSpec = urlSpec || {}; + if (!urlSpec.x) { + urlSpec.x = this.$table.data('preview-x') || 36; } - else { - // set the preview URL directly - var urlSpec = { - file: FileList.getCurrentDirectory() + '/' + fileData.name, - c: fileData.etag - }; - var previewUrl = Files.generatePreviewUrl(urlSpec); - previewUrl = previewUrl.replace('(', '%28').replace(')', '%29'); - filenameTd.css('background-image', 'url(' + previewUrl + ')'); + if (!urlSpec.y) { + urlSpec.y = this.$table.data('preview-y') || 36; } - } - return tr; - }, - /** - * Returns the current directory - * @return current directory - */ - getCurrentDirectory: function(){ - return $('#dir').val() || '/'; - }, - /** - * Returns the directory permissions - * @return permission value as integer - */ - getDirectoryPermissions: function() { - return parseInt($('#permissions').val(), 10); - }, - /** - * @brief Changes the current directory and reload the file list. - * @param targetDir target directory (non URL encoded) - * @param changeUrl false if the URL must not be changed (defaults to true) - * @param {boolean} force set to true to force changing directory - */ - changeDirectory: function(targetDir, changeUrl, force) { - var $dir = $('#dir'), - currentDir = $dir.val() || '/'; - targetDir = targetDir || '/'; - if (!force && currentDir === targetDir) { - return; - } - FileList._setCurrentDir(targetDir, changeUrl); - $('#fileList').trigger( - jQuery.Event('changeDirectory', { - dir: targetDir, - previousDir: currentDir - } - )); - this.reload(); - }, - linkTo: function(dir) { - return OC.linkTo('files', 'index.php')+"?dir="+ encodeURIComponent(dir).replace(/%2F/g, '/'); - }, - - /** - * Sets the current directory name and updates the breadcrumb. - * @param targetDir directory to display - * @param changeUrl true to also update the URL, false otherwise (default) - */ - _setCurrentDir: function(targetDir, changeUrl) { - var url, - baseDir = OC.basename(targetDir); + urlSpec.y *= window.devicePixelRatio; + urlSpec.x *= window.devicePixelRatio; + urlSpec.forceIcon = 0; + return OC.generateUrl('/core/preview.png?') + $.param(urlSpec); + }, + + /** + * Lazy load a file's preview. + * + * @param path path of the file + * @param mime mime type + * @param callback callback function to call when the image was loaded + * @param etag file etag (for caching) + */ + lazyLoadPreview : function(options) { + var self = this; + var path = options.path; + var mime = options.mime; + var ready = options.callback; + var etag = options.etag; + + // get mime icon url + OCA.Files.Files.getMimeIcon(mime, function(iconURL) { + var previewURL, + urlSpec = {}; + ready(iconURL); // set mimeicon URL + + urlSpec.file = OCA.Files.Files.fixPath(path); + + if (etag){ + // use etag as cache buster + urlSpec.c = etag; + } + else { + console.warn('OCA.Files.FileList.lazyLoadPreview(): missing etag argument'); + } - if (baseDir !== '') { - FileList.setPageTitle(baseDir); - } - else { - FileList.setPageTitle(); - } + previewURL = self.generatePreviewUrl(urlSpec); + previewURL = previewURL.replace('(', '%28'); + previewURL = previewURL.replace(')', '%29'); + + // preload image to prevent delay + // this will make the browser cache the image + var img = new Image(); + img.onload = function(){ + // if loading the preview image failed (no preview for the mimetype) then img.width will < 5 + if (img.width > 5) { + ready(previewURL); + } + }; + img.src = previewURL; + }); + }, - $('#dir').val(targetDir); - if (changeUrl !== false) { - if (window.history.pushState && changeUrl !== false) { - url = FileList.linkTo(targetDir); - window.history.pushState({dir: targetDir}, '', url); + setDirectoryPermissions: function(permissions) { + var isCreatable = (permissions & OC.PERMISSION_CREATE) !== 0; + this.$el.find('#permissions').val(permissions); + this.$el.find('.creatable').toggleClass('hidden', !isCreatable); + this.$el.find('.notCreatable').toggleClass('hidden', isCreatable); + }, + /** + * Shows/hides action buttons + * + * @param show true for enabling, false for disabling + */ + showActions: function(show){ + this.$el.find('.actions,#file_action_panel').toggleClass('hidden', !show); + if (show){ + // make sure to display according to permissions + var permissions = this.getDirectoryPermissions(); + var isCreatable = (permissions & OC.PERMISSION_CREATE) !== 0; + this.$el.find('.creatable').toggleClass('hidden', !isCreatable); + this.$el.find('.notCreatable').toggleClass('hidden', isCreatable); + // remove old style breadcrumbs (some apps might create them) + this.$el.find('#controls .crumb').remove(); + // refresh breadcrumbs in case it was replaced by an app + this.breadcrumb.render(); } - // use URL hash for IE8 else{ - window.location.hash = '?dir='+ encodeURIComponent(targetDir).replace(/%2F/g, '/'); + this.$el.find('.creatable, .notCreatable').addClass('hidden'); + } + }, + /** + * Enables/disables viewer mode. + * In viewer mode, apps can embed themselves under the controls bar. + * In viewer mode, the actions of the file list will be hidden. + * @param show true for enabling, false for disabling + */ + setViewerMode: function(show){ + this.showActions(!show); + this.$el.find('#filestable').toggleClass('hidden', show); + }, + /** + * Removes a file entry from the list + * @param name name of the file to remove + * @param options optional options as map: + * "updateSummary": true to update the summary (default), false otherwise + * @return deleted element + */ + remove: function(name, options){ + options = options || {}; + var fileEl = this.findFileEl(name); + var index = fileEl.index(); + if (!fileEl.length) { + return null; + } + if (this._selectedFiles[fileEl.data('id')]) { + // remove from selection first + this._selectFileEl(fileEl, false); + this.updateSelectionSummary(); + } + if (fileEl.data('permissions') & OC.PERMISSION_DELETE) { + // file is only draggable when delete permissions are set + fileEl.find('td.filename').draggable('destroy'); + } + this.files.splice(index, 1); + fileEl.remove(); + // TODO: improve performance on batch update + this.isEmpty = !this.files.length; + if (typeof(options.updateSummary) === 'undefined' || !!options.updateSummary) { + this.updateEmptyContent(); + this.fileSummary.remove({type: fileEl.attr('data-type'), size: fileEl.attr('data-size')}, true); } - } - this.breadcrumb.setDirectory(this.getCurrentDirectory()); - }, - /** - * Sets the current sorting and refreshes the list - * - * @param sort sort attribute name - * @param direction sort direction, one of "asc" or "desc" - */ - setSort: function(sort, direction) { - var comparator = this.Comparators[sort] || this.Comparators.name; - this._sort = sort; - this._sortDirection = (direction === 'desc')?'desc':'asc'; - this._sortComparator = comparator; - if (direction === 'desc') { - this._sortComparator = function(fileInfo1, fileInfo2) { - return -comparator(fileInfo1, fileInfo2); - }; - } - this.$el.find('thead th .sort-indicator') - .removeClass(this.SORT_INDICATOR_ASC_CLASS + ' ' + this.SORT_INDICATOR_DESC_CLASS); - this.$el.find('thead th.column-' + sort + ' .sort-indicator') - .addClass(direction === 'desc' ? this.SORT_INDICATOR_DESC_CLASS : this.SORT_INDICATOR_ASC_CLASS); - }, - /** - * @brief Reloads the file list using ajax call - */ - reload: function() { - this._selectedFiles = {}; - this._selectionSummary.clear(); - this.$el.find('#select_all').prop('checked', false); - FileList.showMask(); - if (FileList._reloadCall) { - FileList._reloadCall.abort(); - } - FileList._reloadCall = $.ajax({ - url: Files.getAjaxUrl('list'), - data: { - dir: $('#dir').val(), - sort: FileList._sort, - sortdirection: FileList._sortDirection - }, - error: function(result) { - FileList.reloadCallback(result); - }, - success: function(result) { - FileList.reloadCallback(result); - } - }); - }, - reloadCallback: function(result) { - delete this._reloadCall; - this.hideMask(); - - if (!result || result.status === 'error') { - OC.Notification.show(result.data.message); - return; - } - if (result.status === 404) { - // go back home - this.changeDirectory('/'); - return; - } - // aborted ? - if (result.status === 0){ - return; - } + var lastIndex = this.$fileList.children().length; + // if there are less elements visible than one page + // but there are still pending elements in the array, + // then directly append the next page + if (lastIndex < this.files.length && lastIndex < this.pageSize) { + this._nextPage(true); + } - // TODO: should rather return upload file size through - // the files list ajax call - Files.updateStorageStatistics(true); + return fileEl; + }, + /** + * Finds the index of the row before which the given + * fileData should be inserted, considering the current + * sorting + */ + _findInsertionIndex: function(fileData) { + var index = 0; + while (index < this.files.length && this._sortComparator(fileData, this.files[index]) > 0) { + index++; + } + return index; + }, + /** + * Moves a file to a given target folder. + * + * @param fileNames array of file names to move + * @param targetPath absolute target path + */ + move: function(fileNames, targetPath) { + var self = this; + var dir = this.getCurrentDirectory(); + var target = OC.basename(targetPath); + if (!_.isArray(fileNames)) { + fileNames = [fileNames]; + } + _.each(fileNames, function(fileName) { + var $tr = self.findFileEl(fileName); + var $td = $tr.children('td.filename'); + var oldBackgroundImage = $td.css('background-image'); + $td.css('background-image', 'url('+ OC.imagePath('core', 'loading.gif') + ')'); + // TODO: improve performance by sending all file names in a single call + $.post( + OC.filePath('files', 'ajax', 'move.php'), + { + dir: dir, + file: fileName, + target: targetPath + }, + function(result) { + if (result) { + if (result.status === 'success') { + // if still viewing the same directory + if (self.getCurrentDirectory() === dir) { + // recalculate folder size + var oldFile = self.findFileEl(target); + var newFile = self.findFileEl(fileName); + var oldSize = oldFile.data('size'); + var newSize = oldSize + newFile.data('size'); + oldFile.data('size', newSize); + oldFile.find('td.filesize').text(OC.Util.humanFileSize(newSize)); + + // TODO: also update entry in FileList.files + + self.remove(fileName); + } + } else { + OC.Notification.hide(); + if (result.status === 'error' && result.data.message) { + OC.Notification.show(result.data.message); + } + else { + OC.Notification.show(t('files', 'Error moving file.')); + } + // hide notification after 10 sec + setTimeout(function() { + OC.Notification.hide(); + }, 10000); + } + } else { + OC.dialogs.alert(t('files', 'Error moving file'), t('files', 'Error')); + } + $td.css('background-image', oldBackgroundImage); + }); + }); - if (result.data.permissions) { - this.setDirectoryPermissions(result.data.permissions); - } + }, + + /** + * Triggers file rename input field for the given file name. + * If the user enters a new name, the file will be renamed. + * + * @param oldname file name of the file to rename + */ + rename: function(oldname) { + var self = this; + var tr, td, input, form; + tr = this.findFileEl(oldname); + var oldFileInfo = this.files[tr.index()]; + tr.data('renaming',true); + td = tr.children('td.filename'); + input = $('<input type="text" class="filename"/>').val(oldname); + form = $('<form></form>'); + form.append(input); + td.children('a.name').hide(); + td.append(form); + input.focus(); + //preselect input + var len = input.val().lastIndexOf('.'); + if ( len === -1 || + tr.data('type') === 'dir' ) { + len = input.val().length; + } + input.selectRange(0, len); + var checkInput = function () { + var filename = input.val(); + if (filename !== oldname) { + // Files.isFileNameValid(filename) throws an exception itself + Files.isFileNameValid(filename); + if (self.inList(filename)) { + throw t('files', '{new_name} already exists', {new_name: filename}); + } + } + return true; + }; - this.setFiles(result.data.files); - }, - setDirectoryPermissions: function(permissions) { - var isCreatable = (permissions & OC.PERMISSION_CREATE) !== 0; - $('#permissions').val(permissions); - $('.creatable').toggleClass('hidden', !isCreatable); - $('.notCreatable').toggleClass('hidden', isCreatable); - }, - /** - * Shows/hides action buttons - * - * @param show true for enabling, false for disabling - */ - showActions: function(show){ - $('.actions,#file_action_panel').toggleClass('hidden', !show); - if (show){ - // make sure to display according to permissions - var permissions = this.getDirectoryPermissions(); - var isCreatable = (permissions & OC.PERMISSION_CREATE) !== 0; - $('.creatable').toggleClass('hidden', !isCreatable); - $('.notCreatable').toggleClass('hidden', isCreatable); - // remove old style breadcrumbs (some apps might create them) - $('#controls .crumb').remove(); - // refresh breadcrumbs in case it was replaced by an app - this.breadcrumb.render(); - } - else{ - $('.creatable, .notCreatable').addClass('hidden'); - } - }, - /** - * Enables/disables viewer mode. - * In viewer mode, apps can embed themselves under the controls bar. - * In viewer mode, the actions of the file list will be hidden. - * @param show true for enabling, false for disabling - */ - setViewerMode: function(show){ - this.showActions(!show); - $('#filestable').toggleClass('hidden', show); - }, - /** - * Removes a file entry from the list - * @param name name of the file to remove - * @param options optional options as map: - * "updateSummary": true to update the summary (default), false otherwise - * @return deleted element - */ - remove: function(name, options){ - options = options || {}; - var fileEl = FileList.findFileEl(name); - var index = fileEl.index(); - if (!fileEl.length) { - return null; - } - if (this._selectedFiles[fileEl.data('id')]) { - // remove from selection first - this._selectFileEl(fileEl, false); - this.updateSelectionSummary(); - } - if (fileEl.data('permissions') & OC.PERMISSION_DELETE) { - // file is only draggable when delete permissions are set - fileEl.find('td.filename').draggable('destroy'); - } - this.files.splice(index, 1); - fileEl.remove(); - // TODO: improve performance on batch update - FileList.isEmpty = !this.files.length; - if (typeof(options.updateSummary) === 'undefined' || !!options.updateSummary) { - FileList.updateEmptyContent(); - this.fileSummary.remove({type: fileEl.attr('data-type'), size: fileEl.attr('data-size')}, true); - } + form.submit(function(event) { + event.stopPropagation(); + event.preventDefault(); + try { + var newName = input.val(); + if (newName !== oldname) { + checkInput(); + // mark as loading + td.css('background-image', 'url('+ OC.imagePath('core', 'loading.gif') + ')'); + $.ajax({ + url: OC.filePath('files','ajax','rename.php'), + data: { + dir : self.getCurrentDirectory(), + newname: newName, + file: oldname + }, + success: function(result) { + var fileInfo; + if (!result || result.status === 'error') { + OC.dialogs.alert(result.data.message, t('core', 'Could not rename file')); + fileInfo = oldFileInfo; + } + else { + fileInfo = result.data; + } + // reinsert row + self.files.splice(tr.index(), 1); + tr.remove(); + self.add(fileInfo); + } + }); + } + input.tipsy('hide'); + tr.data('renaming',false); + tr.attr('data-file', newName); + var path = td.children('a.name').attr('href'); + // FIXME this will fail if the path contains the filename. + td.children('a.name').attr('href', path.replace(encodeURIComponent(oldname), encodeURIComponent(newName))); + var basename = newName; + if (newName.indexOf('.') > 0 && tr.data('type') !== 'dir') { + basename = newName.substr(0, newName.lastIndexOf('.')); + } + td.find('a.name span.nametext').text(basename); + if (newName.indexOf('.') > 0 && tr.data('type') !== 'dir') { + if ( ! td.find('a.name span.extension').exists() ) { + td.find('a.name span.nametext').append('<span class="extension"></span>'); + } + td.find('a.name span.extension').text(newName.substr(newName.lastIndexOf('.'))); + } + form.remove(); + self.fileActions.display( tr.find('td.filename'), true); + td.children('a.name').show(); + } catch (error) { + input.attr('title', error); + input.tipsy({gravity: 'w', trigger: 'manual'}); + input.tipsy('show'); + input.addClass('error'); + } + return false; + }); + input.keyup(function(event) { + // verify filename on typing + try { + checkInput(); + input.tipsy('hide'); + input.removeClass('error'); + } catch (error) { + input.attr('title', error); + input.tipsy({gravity: 'w', trigger: 'manual'}); + input.tipsy('show'); + input.addClass('error'); + } + if (event.keyCode === 27) { + input.tipsy('hide'); + tr.data('renaming',false); + form.remove(); + td.children('a.name').show(); + } + }); + input.click(function(event) { + event.stopPropagation(); + event.preventDefault(); + }); + input.blur(function() { + form.trigger('submit'); + }); + }, + inList:function(file) { + return this.findFileEl(file).length; + }, + /** + * Delete the given files from the given dir + * @param files file names list (without path) + * @param dir directory in which to delete the files, defaults to the current + * directory + */ + do_delete:function(files, dir) { + var self = this; + var params; + if (files && files.substr) { + files=[files]; + } + if (files) { + for (var i=0; i<files.length; i++) { + var deleteAction = this.findFileEl(files[i]).children("td.date").children(".action.delete"); + deleteAction.removeClass('delete-icon').addClass('progress-icon'); + } + } + // Finish any existing actions + if (this.lastAction) { + this.lastAction(); + } - var lastIndex = this.$fileList.children().length; - // if there are less elements visible than one page - // but there are still pending elements in the array, - // then directly append the next page - if (lastIndex < this.files.length && lastIndex < this.pageSize) { - this._nextPage(true); - } + params = { + dir: dir || this.getCurrentDirectory() + }; + if (files) { + params.files = JSON.stringify(files); + } + else { + // no files passed, delete all in current dir + params.allfiles = true; + // show spinner for all files + this.$fileList.find('tr>td.date .action.delete').removeClass('delete-icon').addClass('progress-icon'); + } - return fileEl; - }, - /** - * Finds the index of the row before which the given - * fileData should be inserted, considering the current - * sorting - */ - _findInsertionIndex: function(fileData) { - var index = 0; - while (index < this.files.length && this._sortComparator(fileData, this.files[index]) > 0) { - index++; - } - return index; - }, - /** - * Moves a file to a given target folder. - * - * @param fileNames array of file names to move - * @param targetPath absolute target path - */ - move: function(fileNames, targetPath) { - var self = this; - var dir = this.getCurrentDirectory(); - var target = OC.basename(targetPath); - if (!_.isArray(fileNames)) { - fileNames = [fileNames]; - } - _.each(fileNames, function(fileName) { - var $tr = self.findFileEl(fileName); - var $td = $tr.children('td.filename'); - var oldBackgroundImage = $td.css('background-image'); - $td.css('background-image', 'url('+ OC.imagePath('core', 'loading.gif') + ')'); - // TODO: improve performance by sending all file names in a single call - $.post( - OC.filePath('files', 'ajax', 'move.php'), - { - dir: dir, - file: fileName, - target: targetPath - }, - function(result) { - if (result) { + $.post(OC.filePath('files', 'ajax', 'delete.php'), + params, + function(result) { if (result.status === 'success') { - // if still viewing the same directory - if (self.getCurrentDirectory() === dir) { - // recalculate folder size - var oldFile = self.findFileEl(target); - var newFile = self.findFileEl(fileName); - var oldSize = oldFile.data('size'); - var newSize = oldSize + newFile.data('size'); - oldFile.data('size', newSize); - oldFile.find('td.filesize').text(OC.Util.humanFileSize(newSize)); - - // TODO: also update entry in FileList.files - - self.remove(fileName); + if (params.allfiles) { + self.setFiles([]); + } + else { + $.each(files,function(index,file) { + var fileEl = self.remove(file, {updateSummary: false}); + // FIXME: not sure why we need this after the + // element isn't even in the DOM any more + fileEl.find('input[type="checkbox"]').prop('checked', false); + fileEl.removeClass('selected'); + self.fileSummary.remove({type: fileEl.attr('data-type'), size: fileEl.attr('data-size')}); + }); } + // TODO: this info should be returned by the ajax call! + self.updateEmptyContent(); + self.fileSummary.update(); + self.updateSelectionSummary(); + Files.updateStorageStatistics(); } else { - OC.Notification.hide(); if (result.status === 'error' && result.data.message) { OC.Notification.show(result.data.message); } else { - OC.Notification.show(t('files', 'Error moving file.')); + OC.Notification.show(t('files', 'Error deleting file.')); } // hide notification after 10 sec setTimeout(function() { OC.Notification.hide(); }, 10000); - } - } else { - OC.dialogs.alert(t('files', 'Error moving file'), t('files', 'Error')); - } - $td.css('background-image', oldBackgroundImage); - }); - }); - - }, - - /** - * Triggers file rename input field for the given file name. - * If the user enters a new name, the file will be renamed. - * - * @param oldname file name of the file to rename - */ - rename: function(oldname) { - var tr, td, input, form; - tr = FileList.findFileEl(oldname); - var oldFileInfo = this.files[tr.index()]; - tr.data('renaming',true); - td = tr.children('td.filename'); - input = $('<input type="text" class="filename"/>').val(oldname); - form = $('<form></form>'); - form.append(input); - td.children('a.name').hide(); - td.append(form); - input.focus(); - //preselect input - var len = input.val().lastIndexOf('.'); - if ( len === -1 || - tr.data('type') === 'dir' ) { - len = input.val().length; - } - input.selectRange(0, len); - var checkInput = function () { - var filename = input.val(); - if (filename !== oldname) { - // Files.isFileNameValid(filename) throws an exception itself - Files.isFileNameValid(filename); - if (FileList.inList(filename)) { - throw t('files', '{new_name} already exists', {new_name: filename}); - } - } - return true; - }; - - form.submit(function(event) { - event.stopPropagation(); - event.preventDefault(); - try { - var newName = input.val(); - if (newName !== oldname) { - checkInput(); - // mark as loading - td.css('background-image', 'url('+ OC.imagePath('core', 'loading.gif') + ')'); - $.ajax({ - url: OC.filePath('files','ajax','rename.php'), - data: { - dir : $('#dir').val(), - newname: newName, - file: oldname - }, - success: function(result) { - var fileInfo; - if (!result || result.status === 'error') { - OC.dialogs.alert(result.data.message, t('core', 'Could not rename file')); - fileInfo = oldFileInfo; + if (params.allfiles) { + // reload the page as we don't know what files were deleted + // and which ones remain + self.reload(); } else { - fileInfo = result.data; + $.each(files,function(index,file) { + var deleteAction = self.findFileEl(file).find('.action.delete'); + deleteAction.removeClass('progress-icon').addClass('delete-icon'); + }); } - // reinsert row - FileList.files.splice(tr.index(), 1); - tr.remove(); - FileList.add(fileInfo); } }); - } - input.tipsy('hide'); - tr.data('renaming',false); - tr.attr('data-file', newName); - var path = td.children('a.name').attr('href'); - // FIXME this will fail if the path contains the filename. - td.children('a.name').attr('href', path.replace(encodeURIComponent(oldname), encodeURIComponent(newName))); - var basename = newName; - if (newName.indexOf('.') > 0 && tr.data('type') !== 'dir') { - basename = newName.substr(0, newName.lastIndexOf('.')); - } - td.find('a.name span.nametext').text(basename); - if (newName.indexOf('.') > 0 && tr.data('type') !== 'dir') { - if ( ! td.find('a.name span.extension').exists() ) { - td.find('a.name span.nametext').append('<span class="extension"></span>'); - } - td.find('a.name span.extension').text(newName.substr(newName.lastIndexOf('.'))); - } - form.remove(); - FileActions.display( tr.find('td.filename'), true); - td.children('a.name').show(); - } catch (error) { - input.attr('title', error); - input.tipsy({gravity: 'w', trigger: 'manual'}); - input.tipsy('show'); - input.addClass('error'); - } - return false; - }); - input.keyup(function(event) { - // verify filename on typing - try { - checkInput(); - input.tipsy('hide'); - input.removeClass('error'); - } catch (error) { - input.attr('title', error); - input.tipsy({gravity: 'w', trigger: 'manual'}); - input.tipsy('show'); - input.addClass('error'); - } - if (event.keyCode === 27) { - input.tipsy('hide'); - tr.data('renaming',false); - form.remove(); - td.children('a.name').show(); - } - }); - input.click(function(event) { - event.stopPropagation(); - event.preventDefault(); - }); - input.blur(function() { - form.trigger('submit'); - }); - }, - inList:function(file) { - return FileList.findFileEl(file).length; - }, - /** - * Delete the given files from the given dir - * @param files file names list (without path) - * @param dir directory in which to delete the files, defaults to the current - * directory - */ - do_delete:function(files, dir) { - var params; - if (files && files.substr) { - files=[files]; - } - if (files) { - for (var i=0; i<files.length; i++) { - var deleteAction = FileList.findFileEl(files[i]).children("td.date").children(".action.delete"); - deleteAction.removeClass('delete-icon').addClass('progress-icon'); + }, + /** + * Creates the file summary section + */ + _createSummary: function() { + var $tr = $('<tr class="summary"></tr>'); + this.$el.find('tfoot').append($tr); + + return new OCA.Files.FileSummary($tr); + }, + updateEmptyContent: function() { + var permissions = this.getDirectoryPermissions(); + var isCreatable = (permissions & OC.PERMISSION_CREATE) !== 0; + this.$el.find('#emptycontent').toggleClass('hidden', !isCreatable || !this.isEmpty); + this.$el.find('#filestable thead th').toggleClass('hidden', this.isEmpty); + }, + /** + * Shows the loading mask. + * + * @see #hideMask + */ + showMask: function() { + // in case one was shown before + var $mask = this.$el.find('.mask'); + if ($mask.exists()) { + return; } - } - // Finish any existing actions - if (FileList.lastAction) { - FileList.lastAction(); - } - params = { - dir: dir || FileList.getCurrentDirectory() - }; - if (files) { - params.files = JSON.stringify(files); - } - else { - // no files passed, delete all in current dir - params.allfiles = true; - // show spinner for all files - this.$fileList.find('tr>td.date .action.delete').removeClass('delete-icon').addClass('progress-icon'); - } - - $.post(OC.filePath('files', 'ajax', 'delete.php'), - params, - function(result) { - if (result.status === 'success') { - if (params.allfiles) { - FileList.setFiles([]); - } - else { - $.each(files,function(index,file) { - var fileEl = FileList.remove(file, {updateSummary: false}); - // FIXME: not sure why we need this after the - // element isn't even in the DOM any more - fileEl.find('input[type="checkbox"]').prop('checked', false); - fileEl.removeClass('selected'); - FileList.fileSummary.remove({type: fileEl.attr('data-type'), size: fileEl.attr('data-size')}); - }); - } - // TODO: this info should be returned by the ajax call! - checkTrashStatus(); - FileList.updateEmptyContent(); - FileList.fileSummary.update(); - FileList.updateSelectionSummary(); - Files.updateStorageStatistics(); - } else { - if (result.status === 'error' && result.data.message) { - OC.Notification.show(result.data.message); - } - else { - OC.Notification.show(t('files', 'Error deleting file.')); - } - // hide notification after 10 sec - setTimeout(function() { - OC.Notification.hide(); - }, 10000); - if (params.allfiles) { - // reload the page as we don't know what files were deleted - // and which ones remain - FileList.reload(); - } - else { - $.each(files,function(index,file) { - var deleteAction = FileList.findFileEl(file).find('.action.delete'); - deleteAction.removeClass('progress-icon').addClass('delete-icon'); - }); - } - } + this.$table.addClass('hidden'); + + $mask = $('<div class="mask transparent"></div>'); + + $mask.css('background-image', 'url('+ OC.imagePath('core', 'loading.gif') + ')'); + $mask.css('background-repeat', 'no-repeat'); + this.$el.append($mask); + + $mask.removeClass('transparent'); + }, + /** + * Hide the loading mask. + * @see #showMask + */ + hideMask: function() { + this.$el.find('.mask').remove(); + this.$table.removeClass('hidden'); + }, + scrollTo:function(file) { + //scroll to and highlight preselected file + var $scrollToRow = this.findFileEl(file); + if ($scrollToRow.exists()) { + $scrollToRow.addClass('searchresult'); + $(window).scrollTop($scrollToRow.position().top); + //remove highlight when hovered over + $scrollToRow.one('hover', function() { + $scrollToRow.removeClass('searchresult'); }); - }, - /** - * Creates the file summary section - */ - _createSummary: function() { - var $tr = $('<tr class="summary"></tr>'); - this.$el.find('tfoot').append($tr); - - return new FileSummary($tr); - }, - updateEmptyContent: function() { - var permissions = $('#permissions').val(); - var isCreatable = (permissions & OC.PERMISSION_CREATE) !== 0; - $('#emptycontent').toggleClass('hidden', !isCreatable || !FileList.isEmpty); - $('#filestable thead th').toggleClass('hidden', FileList.isEmpty); - }, - /** - * Shows the loading mask. - * - * @see #hideMask - */ - showMask: function() { - // in case one was shown before - var $mask = $('#content .mask'); - if ($mask.exists()) { - return; - } - - this.$el.addClass('hidden'); - - $mask = $('<div class="mask transparent"></div>'); - - $mask.css('background-image', 'url('+ OC.imagePath('core', 'loading.gif') + ')'); - $mask.css('background-repeat', 'no-repeat'); - $('#content').append($mask); - - $mask.removeClass('transparent'); - }, - /** - * Hide the loading mask. - * @see #showMask - */ - hideMask: function() { - $('#content .mask').remove(); - this.$el.removeClass('hidden'); - }, - scrollTo:function(file) { - //scroll to and highlight preselected file - var $scrollToRow = FileList.findFileEl(file); - if ($scrollToRow.exists()) { - $scrollToRow.addClass('searchresult'); - $(window).scrollTop($scrollToRow.position().top); - //remove highlight when hovered over - $scrollToRow.one('hover', function() { - $scrollToRow.removeClass('searchresult'); + } + }, + filter:function(query) { + this.$fileList.find('tr').each(function(i,e) { + if ($(e).data('file').toString().toLowerCase().indexOf(query.toLowerCase()) !== -1) { + $(e).addClass("searchresult"); + } else { + $(e).removeClass("searchresult"); + } }); - } - }, - filter:function(query) { - $('#fileList tr').each(function(i,e) { - if ($(e).data('file').toString().toLowerCase().indexOf(query.toLowerCase()) !== -1) { - $(e).addClass("searchresult"); - } else { + //do not use scrollto to prevent removing searchresult css class + var first = this.$fileList.find('tr.searchresult').first(); + if (first.exists()) { + $(window).scrollTop(first.position().top); + } + }, + unfilter:function() { + this.$fileList.find('tr.searchresult').each(function(i,e) { $(e).removeClass("searchresult"); + }); + }, + /** + * Update UI based on the current selection + */ + updateSelectionSummary: function() { + var summary = this._selectionSummary.summary; + if (summary.totalFiles === 0 && summary.totalDirs === 0) { + this.$el.find('#headerName a.name>span:first').text(t('files','Name')); + this.$el.find('#headerSize a>span:first').text(t('files','Size')); + this.$el.find('#modified a>span:first').text(t('files','Modified')); + this.$el.find('table').removeClass('multiselect'); + this.$el.find('.selectedActions').addClass('hidden'); } - }); - //do not use scrollto to prevent removing searchresult css class - var first = $('#fileList tr.searchresult').first(); - if (first.exists()) { - $(window).scrollTop(first.position().top); - } - }, - unfilter:function() { - $('#fileList tr.searchresult').each(function(i,e) { - $(e).removeClass("searchresult"); - }); - }, - /** - * Update UI based on the current selection - */ - updateSelectionSummary: function() { - var summary = this._selectionSummary.summary; - if (summary.totalFiles === 0 && summary.totalDirs === 0) { - $('#headerName a.name>span:first').text(t('files','Name')); - $('#headerSize a>span:first').text(t('files','Size')); - $('#modified a>span:first').text(t('files','Modified')); - $('table').removeClass('multiselect'); - $('.selectedActions').addClass('hidden'); - } - else { - $('.selectedActions').removeClass('hidden'); - $('#headerSize a>span:first').text(OC.Util.humanFileSize(summary.totalSize)); - var selection = ''; - if (summary.totalDirs > 0) { - selection += n('files', '%n folder', '%n folders', summary.totalDirs); + else { + this.$el.find('.selectedActions').removeClass('hidden'); + this.$el.find('#headerSize a>span:first').text(OC.Util.humanFileSize(summary.totalSize)); + var selection = ''; + if (summary.totalDirs > 0) { + selection += n('files', '%n folder', '%n folders', summary.totalDirs); + if (summary.totalFiles > 0) { + selection += ' & '; + } + } if (summary.totalFiles > 0) { - selection += ' & '; + selection += n('files', '%n file', '%n files', summary.totalFiles); } + this.$el.find('#headerName a.name>span:first').text(selection); + this.$el.find('#modified a>span:first').text(''); + this.$el.find('table').addClass('multiselect'); } - if (summary.totalFiles > 0) { - selection += n('files', '%n file', '%n files', summary.totalFiles); + }, + + /** + * Returns whether all files are selected + * @return true if all files are selected, false otherwise + */ + isAllSelected: function() { + return this.$el.find('#select_all').prop('checked'); + }, + + /** + * Returns the file info of the selected files + * + * @return array of file names + */ + getSelectedFiles: function() { + return _.values(this._selectedFiles); + }, + + getUniqueName: function(name) { + if (this.findFileEl(name).exists()) { + var numMatch; + var parts=name.split('.'); + var extension = ""; + if (parts.length > 1) { + extension=parts.pop(); + } + var base=parts.join('.'); + numMatch=base.match(/\((\d+)\)/); + var num=2; + if (numMatch && numMatch.length>0) { + num=parseInt(numMatch[numMatch.length-1])+1; + base=base.split('('); + base.pop(); + base=$.trim(base.join('(')); + } + name=base+' ('+num+')'; + if (extension) { + name = name+'.'+extension; + } + // FIXME: ugly recursion + return this.getUniqueName(name); } - $('#headerName a.name>span:first').text(selection); - $('#modified a>span:first').text(''); - $('table').addClass('multiselect'); - } - }, - - /** - * Returns whether all files are selected - * @return true if all files are selected, false otherwise - */ - isAllSelected: function() { - return this.$el.find('#select_all').prop('checked'); - }, - - /** - * Returns the file info of the selected files - * - * @return array of file names - */ - getSelectedFiles: function() { - return _.values(this._selectedFiles); - } -}; - -$(document).ready(function() { - FileList.initialize(); - - // handle upload events - var fileUploadStart = $('#file_upload_start'); + return name; + }, + + setupUploadEvents: function() { + var self = this; + + // handle upload events + var fileUploadStart = this.$el.find('#file_upload_start'); + + fileUploadStart.on('fileuploaddrop', function(e, data) { + OC.Upload.log('filelist handle fileuploaddrop', e, data); + + var dropTarget = $(e.originalEvent.target).closest('tr, .crumb'); + if (dropTarget && (dropTarget.data('type') === 'dir' || dropTarget.hasClass('crumb'))) { // drag&drop upload to folder + + // remember as context + data.context = dropTarget; + + var dir = dropTarget.data('file'); + // if from file list, need to prepend parent dir + if (dir) { + var parentDir = self.getCurrentDirectory(); + if (parentDir[parentDir.length - 1] !== '/') { + parentDir += '/'; + } + dir = parentDir + dir; + } + else{ + // read full path from crumb + dir = dropTarget.data('dir') || '/'; + } - fileUploadStart.on('fileuploaddrop', function(e, data) { - OC.Upload.log('filelist handle fileuploaddrop', e, data); + // update folder in form + data.formData = function(form) { + return [ + {name: 'dir', value: dir}, + {name: 'requesttoken', value: oc_requesttoken}, + {name: 'file_directory', value: data.files[0].relativePath} + ]; + }; + } else { + // cancel uploads to current dir if no permission + var isCreatable = (this.getDirectoryPermissions() & OC.PERMISSION_CREATE) !== 0; + if (!isCreatable) { + return false; + } + } + }); + fileUploadStart.on('fileuploadadd', function(e, data) { + OC.Upload.log('filelist handle fileuploadadd', e, data); - var dropTarget = $(e.originalEvent.target).closest('tr, .crumb'); - if (dropTarget && (dropTarget.data('type') === 'dir' || dropTarget.hasClass('crumb'))) { // drag&drop upload to folder + //finish delete if we are uploading a deleted file + if (self.deleteFiles && self.deleteFiles.indexOf(data.files[0].name)!==-1) { + self.finishDelete(null, true); //delete file before continuing + } - // remember as context - data.context = dropTarget; + // add ui visualization to existing folder + if (data.context && data.context.data('type') === 'dir') { + // add to existing folder + + // update upload counter ui + var uploadText = data.context.find('.uploadtext'); + var currentUploads = parseInt(uploadText.attr('currentUploads'), 10); + currentUploads += 1; + uploadText.attr('currentUploads', currentUploads); + + var translatedText = n('files', 'Uploading %n file', 'Uploading %n files', currentUploads); + if (currentUploads === 1) { + var img = OC.imagePath('core', 'loading.gif'); + data.context.find('td.filename').attr('style','background-image:url('+img+')'); + uploadText.text(translatedText); + uploadText.show(); + } else { + uploadText.text(translatedText); + } + } - var dir = dropTarget.data('file'); - // if from file list, need to prepend parent dir - if (dir) { - var parentDir = $('#dir').val() || '/'; - if (parentDir[parentDir.length - 1] !== '/') { - parentDir += '/'; + }); + /* + * when file upload done successfully add row to filelist + * update counter when uploading to sub folder + */ + fileUploadStart.on('fileuploaddone', function(e, data) { + OC.Upload.log('filelist handle fileuploaddone', e, data); + + var response; + if (typeof data.result === 'string') { + response = data.result; + } else { + // fetch response from iframe + response = data.result[0].body.innerText; } - dir = parentDir + dir; - } - else{ - // read full path from crumb - dir = dropTarget.data('dir') || '/'; - } + var result=$.parseJSON(response); + + if (typeof result[0] !== 'undefined' && result[0].status === 'success') { + var file = result[0]; + var size = 0; + + if (data.context && data.context.data('type') === 'dir') { + + // update upload counter ui + var uploadText = data.context.find('.uploadtext'); + var currentUploads = parseInt(uploadText.attr('currentUploads'), 10); + currentUploads -= 1; + uploadText.attr('currentUploads', currentUploads); + var translatedText = n('files', 'Uploading %n file', 'Uploading %n files', currentUploads); + if (currentUploads === 0) { + var img = OC.imagePath('core', 'filetypes/folder'); + data.context.find('td.filename').attr('style','background-image:url('+img+')'); + uploadText.text(translatedText); + uploadText.hide(); + } else { + uploadText.text(translatedText); + } - // update folder in form - data.formData = function(form) { - return [ - {name: 'dir', value: dir}, - {name: 'requesttoken', value: oc_requesttoken}, - {name: 'file_directory', value: data.files[0].relativePath} - ]; - }; - } else { - // cancel uploads to current dir if no permission - var isCreatable = (FileList.getDirectoryPermissions() & OC.PERMISSION_CREATE) !== 0; - if (!isCreatable) { - return false; - } - } - }); - fileUploadStart.on('fileuploadadd', function(e, data) { - OC.Upload.log('filelist handle fileuploadadd', e, data); + // update folder size + size = parseInt(data.context.data('size'), 10); + size += parseInt(file.size, 10); + data.context.attr('data-size', size); + data.context.find('td.filesize').text(humanFileSize(size)); + } else { + // only append new file if uploaded into the current folder + if (file.directory !== '/' && file.directory !== self.getCurrentDirectory()) { + + var fileDirectory = file.directory.replace('/','').replace(/\/$/, "").split('/'); + + if (fileDirectory.length === 1) { + fileDirectory = fileDirectory[0]; + + // Get the directory + var fd = self.findFileEl(fileDirectory); + if (fd.length === 0) { + var dir = { + name: fileDirectory, + type: 'dir', + mimetype: 'httpd/unix-directory', + permissions: file.permissions, + size: 0, + id: file.parentId + }; + self.add(dir, {insert: true}); + } + } else { + fileDirectory = fileDirectory[0]; + } + + fileDirectory = self.findFileEl(fileDirectory); - //finish delete if we are uploading a deleted file - if (FileList.deleteFiles && FileList.deleteFiles.indexOf(data.files[0].name)!==-1) { - FileList.finishDelete(null, true); //delete file before continuing - } + // update folder size + size = parseInt(fileDirectory.attr('data-size'), 10); + size += parseInt(file.size, 10); + fileDirectory.attr('data-size', size); + fileDirectory.find('td.filesize').text(humanFileSize(size)); - // add ui visualization to existing folder - if (data.context && data.context.data('type') === 'dir') { - // add to existing folder - - // update upload counter ui - var uploadText = data.context.find('.uploadtext'); - var currentUploads = parseInt(uploadText.attr('currentUploads'), 10); - currentUploads += 1; - uploadText.attr('currentUploads', currentUploads); - - var translatedText = n('files', 'Uploading %n file', 'Uploading %n files', currentUploads); - if (currentUploads === 1) { - var img = OC.imagePath('core', 'loading.gif'); - data.context.find('td.filename').attr('style','background-image:url('+img+')'); - uploadText.text(translatedText); - uploadText.show(); - } else { - uploadText.text(translatedText); - } - } - - }); - /* - * when file upload done successfully add row to filelist - * update counter when uploading to sub folder - */ - fileUploadStart.on('fileuploaddone', function(e, data) { - OC.Upload.log('filelist handle fileuploaddone', e, data); - - var response; - if (typeof data.result === 'string') { - response = data.result; - } else { - // fetch response from iframe - response = data.result[0].body.innerText; - } - var result=$.parseJSON(response); + return; + } - if (typeof result[0] !== 'undefined' && result[0].status === 'success') { - var file = result[0]; - var size = 0; + // add as stand-alone row to filelist + size = t('files', 'Pending'); + if (data.files[0].size>=0) { + size=data.files[0].size; + } + //should the file exist in the list remove it + self.remove(file.name); - if (data.context && data.context.data('type') === 'dir') { + // create new file context + data.context = self.add(file, {animate: true}); + } + } + }); + fileUploadStart.on('fileuploadstop', function(e, data) { + OC.Upload.log('filelist handle fileuploadstop', e, data); - // update upload counter ui - var uploadText = data.context.find('.uploadtext'); - var currentUploads = parseInt(uploadText.attr('currentUploads'), 10); - currentUploads -= 1; - uploadText.attr('currentUploads', currentUploads); - var translatedText = n('files', 'Uploading %n file', 'Uploading %n files', currentUploads); - if (currentUploads === 0) { + //if user pressed cancel hide upload chrome + if (data.errorThrown === 'abort') { + //cleanup uploading to a dir + var uploadText = $('tr .uploadtext'); var img = OC.imagePath('core', 'filetypes/folder'); - data.context.find('td.filename').attr('style','background-image:url('+img+')'); - uploadText.text(translatedText); - uploadText.hide(); - } else { - uploadText.text(translatedText); + uploadText.parents('td.filename').attr('style','background-image:url('+img+')'); + uploadText.fadeOut(); + uploadText.attr('currentUploads', 0); } + }); + fileUploadStart.on('fileuploadfail', function(e, data) { + OC.Upload.log('filelist handle fileuploadfail', e, data); - // update folder size - size = parseInt(data.context.data('size'), 10); - size += parseInt(file.size, 10); - data.context.attr('data-size', size); - data.context.find('td.filesize').text(humanFileSize(size)); - } else { - // only append new file if uploaded into the current folder - if (file.directory !== '/' && file.directory !== FileList.getCurrentDirectory()) { - - var fileDirectory = file.directory.replace('/','').replace(/\/$/, "").split('/'); - - if (fileDirectory.length === 1) { - fileDirectory = fileDirectory[0]; - - // Get the directory - var fd = FileList.findFileEl(fileDirectory); - if (fd.length === 0) { - var dir = { - name: fileDirectory, - type: 'dir', - mimetype: 'httpd/unix-directory', - permissions: file.permissions, - size: 0, - id: file.parentId - }; - FileList.add(dir, {insert: true}); - } - } else { - fileDirectory = fileDirectory[0]; - } - - fileDirectory = FileList.findFileEl(fileDirectory); - - // update folder size - size = parseInt(fileDirectory.attr('data-size'), 10); - size += parseInt(file.size, 10); - fileDirectory.attr('data-size', size); - fileDirectory.find('td.filesize').text(humanFileSize(size)); - - return; + //if user pressed cancel hide upload chrome + if (data.errorThrown === 'abort') { + //cleanup uploading to a dir + var uploadText = $('tr .uploadtext'); + var img = OC.imagePath('core', 'filetypes/folder'); + uploadText.parents('td.filename').attr('style','background-image:url('+img+')'); + uploadText.fadeOut(); + uploadText.attr('currentUploads', 0); } + }); - // add as stand-alone row to filelist - size = t('files', 'Pending'); - if (data.files[0].size>=0) { - size=data.files[0].size; - } - //should the file exist in the list remove it - FileList.remove(file.name); + } + }; - // create new file context - data.context = FileList.add(file, {animate: true}); + /** + * Sort comparators. + */ + FileList.Comparators = { + /** + * Compares two file infos by name, making directories appear + * first. + * + * @param fileInfo1 file info + * @param fileInfo2 file info + * @return -1 if the first file must appear before the second one, + * 0 if they are identify, 1 otherwise. + */ + name: function(fileInfo1, fileInfo2) { + if (fileInfo1.type === 'dir' && fileInfo2.type !== 'dir') { + return -1; } + if (fileInfo1.type !== 'dir' && fileInfo2.type === 'dir') { + return 1; + } + return fileInfo1.name.localeCompare(fileInfo2.name); + }, + /** + * Compares two file infos by size. + * + * @param fileInfo1 file info + * @param fileInfo2 file info + * @return -1 if the first file must appear before the second one, + * 0 if they are identify, 1 otherwise. + */ + size: function(fileInfo1, fileInfo2) { + return fileInfo1.size - fileInfo2.size; + }, + /** + * Compares two file infos by timestamp. + * + * @param fileInfo1 file info + * @param fileInfo2 file info + * @return -1 if the first file must appear before the second one, + * 0 if they are identify, 1 otherwise. + */ + mtime: function(fileInfo1, fileInfo2) { + return fileInfo1.mtime - fileInfo2.mtime; } - }); - fileUploadStart.on('fileuploadstop', function(e, data) { - OC.Upload.log('filelist handle fileuploadstop', e, data); - - //if user pressed cancel hide upload chrome - if (data.errorThrown === 'abort') { - //cleanup uploading to a dir - var uploadText = $('tr .uploadtext'); - var img = OC.imagePath('core', 'filetypes/folder'); - uploadText.parents('td.filename').attr('style','background-image:url('+img+')'); - uploadText.fadeOut(); - uploadText.attr('currentUploads', 0); - } - }); - fileUploadStart.on('fileuploadfail', function(e, data) { - OC.Upload.log('filelist handle fileuploadfail', e, data); - - //if user pressed cancel hide upload chrome - if (data.errorThrown === 'abort') { - //cleanup uploading to a dir - var uploadText = $('tr .uploadtext'); - var img = OC.imagePath('core', 'filetypes/folder'); - uploadText.parents('td.filename').attr('style','background-image:url('+img+')'); - uploadText.fadeOut(); - uploadText.attr('currentUploads', 0); - } - }); + }; - $('#notification').hide(); - $('#notification:first-child').on('click', '.replace', function() { - OC.Notification.hide(function() { - FileList.replace( - $('#notification > span').attr('data-oldName'), - $('#notification > span').attr('data-newName'), - $('#notification > span').attr('data-isNewFile')); - }); - }); - $('#notification:first-child').on('click', '.suggest', function() { - var file = $('#notification > span').attr('data-oldName'); - FileList.findFileEl(file).removeClass('hidden'); - OC.Notification.hide(); - }); - $('#notification:first-child').on('click', '.cancel', function() { - if ($('#notification > span').attr('data-isNewFile')) { - FileList.deleteCanceled = false; - FileList.deleteFiles = [$('#notification > span').attr('data-oldName')]; - } - }); - FileList.useUndo=(window.onbeforeunload)?true:false; + OCA.Files.FileList = FileList; +})(); + +$(document).ready(function() { + // FIXME: unused ? + OCA.Files.FileList.useUndo = (window.onbeforeunload)?true:false; $(window).bind('beforeunload', function () { - if (FileList.lastAction) { - FileList.lastAction(); + if (OCA.Files.FileList.lastAction) { + OCA.Files.FileList.lastAction(); } }); $(window).unload(function () { $(window).trigger('beforeunload'); }); - function decodeQuery(query) { - return query.replace(/\+/g, ' '); - } - - function parseHashQuery() { - var hash = window.location.hash, - pos = hash.indexOf('?'); - if (pos >= 0) { - return hash.substr(pos + 1); - } - return ''; - } - - function parseCurrentDirFromUrl() { - var query = parseHashQuery(), - params; - // try and parse from URL hash first - if (query) { - params = OC.parseQueryString(decodeQuery(query)); - } - // else read from query attributes - if (!params) { - params = OC.parseQueryString(decodeQuery(location.search)); - } - return (params && params.dir) || '/'; - } - - // disable ajax/history API for public app (TODO: until it gets ported) - // fallback to hashchange when no history support - if (!window.history.pushState) { - $(window).on('hashchange', function() { - FileList.changeDirectory(parseCurrentDirFromUrl(), false); - }); - } - window.onpopstate = function(e) { - var targetDir; - if (e.state && e.state.dir) { - targetDir = e.state.dir; - } - else{ - // read from URL - targetDir = parseCurrentDirFromUrl(); - } - if (targetDir) { - FileList.changeDirectory(targetDir, false); - } - }; - - $(window).scroll(function(e) {FileList._onScroll(e);}); - - var dir = parseCurrentDirFromUrl(); - // trigger ajax load, deferred to let sub-apps do their overrides first - setTimeout(function() { - FileList.changeDirectory(dir, false, true); - }, 0); }); - -/** - * Sort comparators. - */ -FileList.Comparators = { - /** - * Compares two file infos by name, making directories appear - * first. - * - * @param fileInfo1 file info - * @param fileInfo2 file info - * @return -1 if the first file must appear before the second one, - * 0 if they are identify, 1 otherwise. - */ - name: function(fileInfo1, fileInfo2) { - if (fileInfo1.type === 'dir' && fileInfo2.type !== 'dir') { - return -1; - } - if (fileInfo1.type !== 'dir' && fileInfo2.type === 'dir') { - return 1; - } - return fileInfo1.name.localeCompare(fileInfo2.name); - }, - /** - * Compares two file infos by size. - * - * @param fileInfo1 file info - * @param fileInfo2 file info - * @return -1 if the first file must appear before the second one, - * 0 if they are identify, 1 otherwise. - */ - size: function(fileInfo1, fileInfo2) { - return fileInfo1.size - fileInfo2.size; - }, - /** - * Compares two file infos by timestamp. - * - * @param fileInfo1 file info - * @param fileInfo2 file info - * @return -1 if the first file must appear before the second one, - * 0 if they are identify, 1 otherwise. - */ - mtime: function(fileInfo1, fileInfo2) { - return fileInfo1.mtime - fileInfo2.mtime; - } -}; - diff --git a/apps/files/js/files.js b/apps/files/js/files.js index 60e20a62e87..9ab8d0fcb53 100644 --- a/apps/files/js/files.js +++ b/apps/files/js/files.js @@ -8,265 +8,314 @@ * */ -/* global OC, t, FileList */ /* global getURLParameter */ -var Files = { - // file space size sync - _updateStorageStatistics: function() { - Files._updateStorageStatisticsTimeout = null; - var currentDir = FileList.getCurrentDirectory(), - state = Files.updateStorageStatistics; - if (state.dir){ - if (state.dir === currentDir) { +/** + * Utility class for file related operations + */ +(function() { + var Files = { + // file space size sync + _updateStorageStatistics: function() { + // FIXME + console.warn('FIXME: storage statistics!'); + return; + Files._updateStorageStatisticsTimeout = null; + var currentDir = OCA.Files.FileList.getCurrentDirectory(), + state = Files.updateStorageStatistics; + if (state.dir){ + if (state.dir === currentDir) { + return; + } + // cancel previous call, as it was for another dir + state.call.abort(); + } + state.dir = currentDir; + state.call = $.getJSON(OC.filePath('files','ajax','getstoragestats.php') + '?dir=' + encodeURIComponent(currentDir),function(response) { + state.dir = null; + state.call = null; + Files.updateMaxUploadFilesize(response); + }); + }, + updateStorageStatistics: function(force) { + if (!OC.currentUser) { return; } - // cancel previous call, as it was for another dir - state.call.abort(); - } - state.dir = currentDir; - state.call = $.getJSON(OC.filePath('files','ajax','getstoragestats.php') + '?dir=' + encodeURIComponent(currentDir),function(response) { - state.dir = null; - state.call = null; - Files.updateMaxUploadFilesize(response); - }); - }, - updateStorageStatistics: function(force) { - if (!OC.currentUser) { - return; - } - // debounce to prevent calling too often - if (Files._updateStorageStatisticsTimeout) { - clearTimeout(Files._updateStorageStatisticsTimeout); - } - if (force) { - Files._updateStorageStatistics(); - } - else { - Files._updateStorageStatisticsTimeout = setTimeout(Files._updateStorageStatistics, 250); - } - }, + // debounce to prevent calling too often + if (Files._updateStorageStatisticsTimeout) { + clearTimeout(Files._updateStorageStatisticsTimeout); + } + if (force) { + Files._updateStorageStatistics(); + } + else { + Files._updateStorageStatisticsTimeout = setTimeout(Files._updateStorageStatistics, 250); + } + }, - updateMaxUploadFilesize:function(response) { - if (response === undefined) { - return; - } - if (response.data !== undefined && response.data.uploadMaxFilesize !== undefined) { - $('#max_upload').val(response.data.uploadMaxFilesize); - $('#free_space').val(response.data.freeSpace); - $('#upload.button').attr('original-title', response.data.maxHumanFilesize); - $('#usedSpacePercent').val(response.data.usedSpacePercent); - Files.displayStorageWarnings(); - } - if (response[0] === undefined) { - return; - } - if (response[0].uploadMaxFilesize !== undefined) { - $('#max_upload').val(response[0].uploadMaxFilesize); - $('#upload.button').attr('original-title', response[0].maxHumanFilesize); - $('#usedSpacePercent').val(response[0].usedSpacePercent); - Files.displayStorageWarnings(); - } + updateMaxUploadFilesize:function(response) { + if (response === undefined) { + return; + } + if (response.data !== undefined && response.data.uploadMaxFilesize !== undefined) { + $('#max_upload').val(response.data.uploadMaxFilesize); + $('#free_space').val(response.data.freeSpace); + $('#upload.button').attr('original-title', response.data.maxHumanFilesize); + $('#usedSpacePercent').val(response.data.usedSpacePercent); + Files.displayStorageWarnings(); + } + if (response[0] === undefined) { + return; + } + if (response[0].uploadMaxFilesize !== undefined) { + $('#max_upload').val(response[0].uploadMaxFilesize); + $('#upload.button').attr('original-title', response[0].maxHumanFilesize); + $('#usedSpacePercent').val(response[0].usedSpacePercent); + Files.displayStorageWarnings(); + } - }, + }, - /** - * Fix path name by removing double slash at the beginning, if any - */ - fixPath: function(fileName) { - if (fileName.substr(0, 2) == '//') { - return fileName.substr(1); - } - return fileName; - }, + /** + * Fix path name by removing double slash at the beginning, if any + */ + fixPath: function(fileName) { + if (fileName.substr(0, 2) == '//') { + return fileName.substr(1); + } + return fileName; + }, + + /** + * Checks whether the given file name is valid. + * @param name file name to check + * @return true if the file name is valid. + * Throws a string exception with an error message if + * the file name is not valid + */ + isFileNameValid: function (name) { + var trimmedName = name.trim(); + if (trimmedName === '.' || trimmedName === '..') + { + throw t('files', '"{name}" is an invalid file name.', {name: name}); + } else if (trimmedName.length === 0) { + throw t('files', 'File name cannot be empty.'); + } + // check for invalid characters + var invalidCharacters = + ['\\', '/', '<', '>', ':', '"', '|', '?', '*', '\n']; + for (var i = 0; i < invalidCharacters.length; i++) { + if (trimmedName.indexOf(invalidCharacters[i]) !== -1) { + throw t('files', "Invalid name, '\\', '/', '<', '>', ':', '\"', '|', '?' and '*' are not allowed."); + } + } + return true; + }, + displayStorageWarnings: function() { + if (!OC.Notification.isHidden()) { + return; + } - /** - * Checks whether the given file name is valid. - * @param name file name to check - * @return true if the file name is valid. - * Throws a string exception with an error message if - * the file name is not valid - */ - isFileNameValid: function (name) { - var trimmedName = name.trim(); - if (trimmedName === '.' || trimmedName === '..') - { - throw t('files', '"{name}" is an invalid file name.', {name: name}); - } else if (trimmedName.length === 0) { - throw t('files', 'File name cannot be empty.'); - } - // check for invalid characters - var invalidCharacters = - ['\\', '/', '<', '>', ':', '"', '|', '?', '*', '\n']; - for (var i = 0; i < invalidCharacters.length; i++) { - if (trimmedName.indexOf(invalidCharacters[i]) !== -1) { - throw t('files', "Invalid name, '\\', '/', '<', '>', ':', '\"', '|', '?' and '*' are not allowed."); + var usedSpacePercent = $('#usedSpacePercent').val(); + if (usedSpacePercent > 98) { + OC.Notification.show(t('files', 'Your storage is full, files can not be updated or synced anymore!')); + return; } - } - return true; - }, - displayStorageWarnings: function() { - if (!OC.Notification.isHidden()) { - return; - } + if (usedSpacePercent > 90) { + OC.Notification.show(t('files', 'Your storage is almost full ({usedSpacePercent}%)', + {usedSpacePercent: usedSpacePercent})); + } + }, - var usedSpacePercent = $('#usedSpacePercent').val(); - if (usedSpacePercent > 98) { - OC.Notification.show(t('files', 'Your storage is full, files can not be updated or synced anymore!')); - return; - } - if (usedSpacePercent > 90) { - OC.Notification.show(t('files', 'Your storage is almost full ({usedSpacePercent}%)', - {usedSpacePercent: usedSpacePercent})); - } - }, + displayEncryptionWarning: function() { - displayEncryptionWarning: function() { + if (!OC.Notification.isHidden()) { + return; + } - if (!OC.Notification.isHidden()) { - return; - } + var encryptedFiles = $('#encryptedFiles').val(); + var initStatus = $('#encryptionInitStatus').val(); + if (initStatus === '0') { // enc not initialized, but should be + OC.Notification.show(t('files', 'Encryption App is enabled but your keys are not initialized, please log-out and log-in again')); + return; + } + if (initStatus === '1') { // encryption tried to init but failed + OC.Notification.show(t('files', 'Invalid private key for Encryption App. Please update your private key password in your personal settings to recover access to your encrypted files.')); + return; + } + if (encryptedFiles === '1') { + OC.Notification.show(t('files', 'Encryption was disabled but your files are still encrypted. Please go to your personal settings to decrypt your files.')); + return; + } + }, - var encryptedFiles = $('#encryptedFiles').val(); - var initStatus = $('#encryptionInitStatus').val(); - if (initStatus === '0') { // enc not initialized, but should be - OC.Notification.show(t('files', 'Encryption App is enabled but your keys are not initialized, please log-out and log-in again')); - return; - } - if (initStatus === '1') { // encryption tried to init but failed - OC.Notification.show(t('files', 'Invalid private key for Encryption App. Please update your private key password in your personal settings to recover access to your encrypted files.')); - return; - } - if (encryptedFiles === '1') { - OC.Notification.show(t('files', 'Encryption was disabled but your files are still encrypted. Please go to your personal settings to decrypt your files.')); - return; - } - }, + // TODO: move to FileList class + setupDragAndDrop: function() { + var $fileList = $('#fileList'); - // TODO: move to FileList class - setupDragAndDrop: function() { - var $fileList = $('#fileList'); + //drag/drop of files + $fileList.find('tr td.filename').each(function(i,e) { + if ($(e).parent().data('permissions') & OC.PERMISSION_DELETE) { + $(e).draggable(dragOptions); + } + }); - //drag/drop of files - $fileList.find('tr td.filename').each(function(i,e) { - if ($(e).parent().data('permissions') & OC.PERMISSION_DELETE) { - $(e).draggable(dragOptions); + $fileList.find('tr[data-type="dir"] td.filename').each(function(i,e) { + if ($(e).parent().data('permissions') & OC.PERMISSION_CREATE) { + $(e).droppable(folderDropOptions); + } + }); + }, + + /** + * 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); } - }); - - $fileList.find('tr[data-type="dir"] td.filename').each(function(i,e) { - if ($(e).parent().data('permissions') & OC.PERMISSION_CREATE) { - $(e).droppable(folderDropOptions); + var params = { + dir: dir, + files: filename + }; + return this.getAjaxUrl('download', params); + }, + + /** + * 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; + }, + + getMimeIcon: function(mime, ready) { + if (Files.getMimeIcon.cache[mime]) { + ready(Files.getMimeIcon.cache[mime]); + } else { + $.get( OC.filePath('files','ajax','mimeicon.php'), {mime: mime}, function(path) { + if(OC.Util.hasSVGSupport()){ + path = path.substr(0, path.length-4) + '.svg'; + } + Files.getMimeIcon.cache[mime]=path; + ready(Files.getMimeIcon.cache[mime]); + }); + } + }, + + /** + * Generates a preview URL based on the URL space. + * @param urlSpec map with {x: width, y: height, file: file path} + * @return preview URL + * @deprecated used OCA.Files.FileList.generatePreviewUrl instead + */ + generatePreviewUrl: function(urlSpec) { + console.warn('DEPRECATED: please use generatePreviewUrl() from an OCA.Files.FileList instance'); + return OCA.Files.App.fileList.generatePreviewUrl(urlSpec); + }, + + /** + * Lazy load preview + * @deprecated used OCA.Files.FileList.lazyLoadPreview instead + */ + lazyLoadPreview : function(path, mime, ready, width, height, etag) { + console.warn('DEPRECATED: please use lazyLoadPreview() from an OCA.Files.FileList instance'); + return OCA.Files.App.fileList.lazyLoadPreview({ + path: path, + mime: mime, + callback: ready, + width: width, + height: height, + etag: etag + }); + }, - /** - * 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); - } - var params = { - dir: dir || FileList.getCurrentDirectory(), - files: filename - }; - return this.getAjaxUrl('download', params); - }, + /** + * Initialize the files view + */ + initialize: function() { + Files.getMimeIcon.cache = {}; + Files.displayEncryptionWarning(); + Files.bindKeyboardShortcuts(document, jQuery); - /** - * 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() { - // FIXME: workaround for trashbin app - if (window.trashBinApp) { - return; - } - Files.displayEncryptionWarning(); - Files.bindKeyboardShortcuts(document, jQuery); + Files.setupDragAndDrop(); - Files.setupDragAndDrop(); + // TODO: move file list related code (upload) to OCA.Files.FileList + $('#file_action_panel').attr('activeAction', false); - $('#file_action_panel').attr('activeAction', false); - - // Triggers invisible file input - $('#upload a').on('click', function() { - $(this).parent().children('#file_upload_start').trigger('click'); - return false; - }); + // Triggers invisible file input + $('#upload a').on('click', function() { + $(this).parent().children('#file_upload_start').trigger('click'); + return false; + }); - // Trigger cancelling of file upload - $('#uploadprogresswrapper .stop').on('click', function() { - OC.Upload.cancelUploads(); - FileList.updateSelectionSummary(); - }); + // Trigger cancelling of file upload + $('#uploadprogresswrapper .stop').on('click', function() { + OC.Upload.cancelUploads(); + OCA.Files.FileList.updateSelectionSummary(); + }); - // Show trash bin - $('#trash').on('click', function() { - window.location=OC.filePath('files_trashbin', '', 'index.php'); - }); + // drag&drop support using jquery.fileupload + // TODO use OC.dialogs + $(document).bind('drop dragover', function (e) { + e.preventDefault(); // prevent browser from doing anything, if file isn't dropped in dropZone + }); + + //do a background scan if needed + scanFiles(); + + // display storage warnings + setTimeout(Files.displayStorageWarnings, 100); + OC.Notification.setDefault(Files.displayStorageWarnings); + + // only possible at the moment if user is logged in + if (OC.currentUser) { + // start on load - we ask the server every 5 minutes + var updateStorageStatisticsInterval = 5*60*1000; + var updateStorageStatisticsIntervalId = setInterval(Files.updateStorageStatistics, updateStorageStatisticsInterval); + + // Use jquery-visibility to de-/re-activate file stats sync + if ($.support.pageVisibility) { + $(document).on({ + 'show.visibility': function() { + if (!updateStorageStatisticsIntervalId) { + updateStorageStatisticsIntervalId = setInterval(Files.updateStorageStatistics, updateStorageStatisticsInterval); + } + }, + 'hide.visibility': function() { + clearInterval(updateStorageStatisticsIntervalId); + updateStorageStatisticsIntervalId = 0; + } + }); + } + } - // drag&drop support using jquery.fileupload - // TODO use OC.dialogs - $(document).bind('drop dragover', function (e) { - e.preventDefault(); // prevent browser from doing anything, if file isn't dropped in dropZone - }); - - //do a background scan if needed - scanFiles(); - - // display storage warnings - setTimeout(Files.displayStorageWarnings, 100); - OC.Notification.setDefault(Files.displayStorageWarnings); - - // only possible at the moment if user is logged in - if (OC.currentUser) { - // start on load - we ask the server every 5 minutes - var updateStorageStatisticsInterval = 5*60*1000; - var updateStorageStatisticsIntervalId = setInterval(Files.updateStorageStatistics, updateStorageStatisticsInterval); - - // Use jquery-visibility to de-/re-activate file stats sync - if ($.support.pageVisibility) { - $(document).on({ - 'show.visibility': function() { - if (!updateStorageStatisticsIntervalId) { - updateStorageStatisticsIntervalId = setInterval(Files.updateStorageStatistics, updateStorageStatisticsInterval); - } - }, - 'hide.visibility': function() { - clearInterval(updateStorageStatisticsIntervalId); - updateStorageStatisticsIntervalId = 0; + $('#app-settings-header').on('click', function() { + var $settings = $('#app-settings'); + $settings.toggleClass('opened'); + if ($settings.hasClass('opened')) { + $settings.find('input').focus(); } }); - } - } - $('#app-settings-header').on('click', function() { - var $settings = $('#app-settings'); - $settings.toggleClass('opened'); - if ($settings.hasClass('opened')) { - $settings.find('input').focus(); + //scroll to and highlight preselected file + /* + if (getURLParameter('scrollto')) { + FileList.scrollTo(getURLParameter('scrollto')); + } + */ } - }); - - //scroll to and highlight preselected file - if (getURLParameter('scrollto')) { - FileList.scrollTo(getURLParameter('scrollto')); } -}); + + OCA.Files.Files = Files; +})(); function scanFiles(force, dir, users) { if (!OC.currentUser) { @@ -300,7 +349,7 @@ function scanFiles(force, dir, users) { scannerEventSource.listen('done',function(count) { scanFiles.scanning=false; console.log('done after ' + count + ' files'); - Files.updateStorageStatistics(); + OCA.Files.Files.updateStorageStatistics(); }); scannerEventSource.listen('user',function(user) { console.log('scanning files for ' + user); @@ -311,6 +360,7 @@ scanFiles.scanning=false; // TODO: move to FileList var createDragShadow = function(event) { //select dragged file + var FileList = OCA.Files.FileList; var isDragSelected = $(event.target).parents('tr').find('td input:first').prop('checked'); if (!isDragSelected) { //select dragged file @@ -331,7 +381,7 @@ var createDragShadow = function(event) { var tbody = $('<tbody></tbody>'); dragshadow.append(tbody); - var dir=$('#dir').val(); + var dir = FileList.getCurrentDirectory(); $(selectedFiles).each(function(i,elem) { var newtr = $('<tr/>') @@ -344,8 +394,8 @@ var createDragShadow = function(event) { if (elem.type === 'dir') { newtr.find('td.filename').attr('style','background-image:url('+OC.imagePath('core', 'filetypes/folder.png')+')'); } else { - var path = getPathForPreview(elem.name); - Files.lazyLoadPreview(path, elem.mime, function(previewpath) { + var path = dir + '/' + elem.name; + OCA.Files.App.fileList.lazyLoadPreview(path, elem.mime, function(previewpath) { newtr.find('td.filename').attr('style','background-image:url('+previewpath+')'); }, null, null, elem.etag); } @@ -358,9 +408,14 @@ var createDragShadow = function(event) { //start&stop handlers needs some cleaning up // TODO: move to FileList class var dragOptions={ - revert: 'invalid', revertDuration: 300, - opacity: 0.7, zIndex: 100, appendTo: 'body', cursorAt: { left: 24, top: 18 }, - helper: createDragShadow, cursor: 'move', + revert: 'invalid', + revertDuration: 300, + opacity: 0.7, + zIndex: 100, + appendTo: 'body', + cursorAt: { left: 24, top: 18 }, + helper: createDragShadow, + cursor: 'move', start: function(event, ui){ var $selectedFiles = $('td.filename input:checkbox:checked'); if($selectedFiles.length > 1){ @@ -391,6 +446,7 @@ var folderDropOptions = { hoverClass: "canDrop", drop: function( event, ui ) { // don't allow moving a file into a selected folder + var FileList = OCA.Files.FileList; if ($(event.target).parents('tr').find('td input:first').prop('checked') === true) { return false; } @@ -408,115 +464,11 @@ var folderDropOptions = { tolerance: 'pointer' }; -Files.getMimeIcon = function(mime, ready) { - if (Files.getMimeIcon.cache[mime]) { - ready(Files.getMimeIcon.cache[mime]); - } else { - $.get( OC.filePath('files','ajax','mimeicon.php'), {mime: mime}, function(path) { - if(OC.Util.hasSVGSupport()){ - path = path.substr(0, path.length-4) + '.svg'; - } - Files.getMimeIcon.cache[mime]=path; - ready(Files.getMimeIcon.cache[mime]); - }); - } -} -Files.getMimeIcon.cache={}; - -function getPathForPreview(name) { - var path = $('#dir').val() + '/' + 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.y *= window.devicePixelRatio; - urlSpec.x *= window.devicePixelRatio; - 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 previewURL, - urlSpec = {}; - ready(iconURL); // set mimeicon URL - - urlSpec.file = Files.fixPath(path); - - if (etag){ - // use etag as cache buster - urlSpec.c = etag; - } - else { - console.warn('Files.lazyLoadPreview(): missing etag argument'); - } - - previewURL = Files.generatePreviewUrl(urlSpec); - previewURL = previewURL.replace('(', '%28'); - previewURL = previewURL.replace(')', '%29'); - - // preload image to prevent delay - // this will make the browser cache the image - var img = new Image(); - img.onload = function(){ - // if loading the preview image failed (no preview for the mimetype) then img.width will < 5 - if (img.width > 5) { - ready(previewURL); - } - }; - img.src = previewURL; - }); -}; - -function getUniqueName(name) { - if (FileList.findFileEl(name).exists()) { - var numMatch; - var parts=name.split('.'); - var extension = ""; - if (parts.length > 1) { - extension=parts.pop(); - } - var base=parts.join('.'); - numMatch=base.match(/\((\d+)\)/); - var num=2; - if (numMatch && numMatch.length>0) { - num=parseInt(numMatch[numMatch.length-1])+1; - base=base.split('('); - base.pop(); - base=$.trim(base.join('(')); - } - name=base+' ('+num+')'; - if (extension) { - name = name+'.'+extension; - } - return getUniqueName(name); - } - return name; -} - -function checkTrashStatus() { - $.post(OC.filePath('files_trashbin', 'ajax', 'isEmpty.php'), function(result) { - if (result.data.isEmpty === false) { - $("input[type=button][id=trash]").removeAttr("disabled"); - } - }); -} - // override core's fileDownloadPath (legacy) function fileDownloadPath(dir, file) { - return Files.getDownloadUrl(file, dir); + return OCA.Files.Files.getDownloadUrl(file, dir); } +// for backward compatibility +window.Files = OCA.Files.Files; + diff --git a/apps/files/js/filesummary.js b/apps/files/js/filesummary.js index b5130247cc9..104dabf1b04 100644 --- a/apps/files/js/filesummary.js +++ b/apps/files/js/filesummary.js @@ -190,6 +190,6 @@ this.$el.append($summary); } }; - window.FileSummary = FileSummary; + OCA.Files.FileSummary = FileSummary; })(); diff --git a/apps/files/js/navigation.js b/apps/files/js/navigation.js index f53abddd4db..c4a02ee7549 100644 --- a/apps/files/js/navigation.js +++ b/apps/files/js/navigation.js @@ -22,7 +22,7 @@ /** * Currently selected item in the list */ - _selectedItem: null, + _activeItem: null, /** * Currently selected container @@ -35,7 +35,7 @@ */ initialize: function($el) { this.$el = $el; - this._selectedItem = null; + this._activeItem = null; this.$currentContent = null; this._setupEvents(); }, @@ -48,22 +48,47 @@ }, /** + * Returns the container of the currently active app. + * + * @return app container + */ + getActiveContainer: function() { + return this.$currentContent; + }, + + /** + * Returns the currently active item + * + * @return item ID + */ + getActiveItem: function() { + return this._activeItem; + }, + + /** * Switch the currently selected item, mark it as selected and * make the content container visible, if any. + * * @param string itemId id of the navigation item to select + * @param array options "silent" to not trigger event */ - setSelectedItem: function(itemId) { - if (itemId === this._selectedItem) { + setActiveItem: function(itemId, options) { + if (itemId === this._activeItem) { return; } - this._selectedItem = itemId; + this._activeItem = itemId; this.$el.find('li').removeClass('selected'); if (this.$currentContent) { this.$currentContent.addClass('hidden'); + this.$currentContent.trigger(jQuery.Event('hide')); } this.$currentContent = $('#app-content-' + itemId); this.$currentContent.removeClass('hidden'); this.$el.find('li[data-id=' + itemId + ']').addClass('selected'); + if (!options || !options.silent) { + this.$currentContent.trigger(jQuery.Event('show')); + this.$el.trigger(new $.Event('itemChanged', {itemId: itemId})); + } }, /** @@ -72,7 +97,8 @@ _onClickItem: function(ev) { var $target = $(ev.target); var itemId = $target.closest('li').attr('data-id'); - this.setSelectedItem(itemId); + this.setActiveItem(itemId); + return false; } }; diff --git a/apps/files/list.php b/apps/files/list.php new file mode 100644 index 00000000000..78b1b072b88 --- /dev/null +++ b/apps/files/list.php @@ -0,0 +1,50 @@ +<?php + +/** + * ownCloud - Files list + * + * @author Vincent Petry + * @copyright 2014 Vincent Petry <pvince81@owncloud.com> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + + +// Check if we are a user +OCP\User::checkLoggedIn(); + +// dummy, will be refreshed with an ajax call +$dir = '/'; + +// information about storage capacities +// FIXME: storage info +/* +$storageInfo=OC_Helper::getStorageInfo($dir, $dirInfo); +$publicUploadEnabled = $config->getAppValue('core', 'shareapi_allow_public_upload', 'yes'); +$uploadLimit=OCP\Util::uploadLimit(); +$maxUploadFilesize=OCP\Util::maxUploadFilesize($dir, $freeSpace); +$freeSpace=$storageInfo['free']; +*/ + +$tmpl = new OCP\Template('files', 'list', ''); +$tmpl->assign('usedSpacePercent', (int)$storageInfo['relative']); +$tmpl->assign('uploadMaxFilesize', $maxUploadFilesize); // minimium of freeSpace and uploadLimit +$tmpl->assign('uploadMaxHumanFilesize', OCP\Util::humanFileSize($maxUploadFilesize)); +$tmpl->assign('uploadLimit', $uploadLimit); // PHP upload limit +$tmpl->assign('freeSpace', $freeSpace); +$tmpl->assign('publicUploadEnabled', $publicUploadEnabled); +$tmpl->assign('allowZipDownload', intval(OCP\Config::getSystemValue('allowZipDownload', true))); +$tmpl->printPage(); + diff --git a/apps/files/templates/appnavigation.php b/apps/files/templates/appnavigation.php index 52e4284d3e5..86436bbe8c4 100644 --- a/apps/files/templates/appnavigation.php +++ b/apps/files/templates/appnavigation.php @@ -1,7 +1,5 @@ <div id="app-navigation"> <ul> - <li data-id="files" class="nav-allfiles"><a href="#"><?php p($l->t('All Files'));?></a></li> - <li class="sep"></li> <?php foreach ($_['navigationItems'] as $item) { ?> <li data-id="<?php p($item['id']) ?>" class="nav-<?php p($item['id']) ?>"><a href="<?php p(isset($item['href']) ? $item['href'] : '#') ?>"><?php p($item['name']);?></a></li> <?php } ?> diff --git a/apps/files/templates/index.php b/apps/files/templates/index.php index e93e93140d1..ce065a987fb 100644 --- a/apps/files/templates/index.php +++ b/apps/files/templates/index.php @@ -1,117 +1,6 @@ <?php /** @var $l OC_L10N */ ?> <?php $_['appNavigation']->printPage(); ?> <div id="app-content"> -<div id="app-content-files"> -<div id="controls"> - <div class="actions creatable hidden"> - <?php if(!isset($_['dirToken'])):?> - <div id="new" class="button"> - <a><?php p($l->t('New'));?></a> - <ul> - <li class="icon-filetype-text svg" - data-type="file" data-newname="<?php p($l->t('New text file')) ?>.txt"> - <p><?php p($l->t('Text file'));?></p> - </li> - <li class="icon-filetype-folder svg" - data-type="folder" data-newname="<?php p($l->t('New folder')) ?>"> - <p><?php p($l->t('Folder'));?></p> - </li> - <li class="icon-link svg" data-type="web"> - <p><?php p($l->t('From link'));?></p> - </li> - </ul> - </div> - <?php endif;?> - <div id="upload" class="button" - title="<?php p($l->t('Upload (max. %s)', array($_['uploadMaxHumanFilesize']))) ?>"> - <?php if($_['uploadMaxFilesize'] >= 0):?> - <input type="hidden" id="max_upload" name="MAX_FILE_SIZE" value="<?php p($_['uploadMaxFilesize']) ?>"> - <?php endif;?> - <input type="hidden" id="upload_limit" value="<?php p($_['uploadLimit']) ?>"> - <input type="hidden" id="free_space" value="<?php p($_['freeSpace']) ?>"> - <?php if(isset($_['dirToken'])):?> - <input type="hidden" id="publicUploadRequestToken" name="requesttoken" value="<?php p($_['requesttoken']) ?>" /> - <input type="hidden" id="dirToken" name="dirToken" value="<?php p($_['dirToken']) ?>" /> - <?php endif;?> - <input type="hidden" class="max_human_file_size" - value="(max <?php p($_['uploadMaxHumanFilesize']); ?>)"> - <input type="hidden" name="dir" value="<?php p($_['dir']) ?>" id="dir"> - <input type="file" id="file_upload_start" name='files[]' - data-url="<?php print_unescaped(OCP\Util::linkTo('files', 'ajax/upload.php')); ?>" /> - <a href="#" class="svg icon-upload"></a> - </div> - <div id="uploadprogresswrapper"> - <div id="uploadprogressbar"></div> - <input type="button" class="stop" style="display:none" - value="<?php p($l->t('Cancel upload'));?>" - /> - </div> - </div> - <div id="file_action_panel"></div> - <div class="notCreatable notPublic hidden"> - <?php p($l->t('You don’t have permission to upload or create files here'))?> - </div> - <input type="hidden" name="permissions" value="<?php p($_['permissions']); ?>" id="permissions"> -</div> - -<div id="emptycontent" class="hidden"><?php p($l->t('Nothing in here. Upload something!'))?></div> - -<input type="hidden" id="disableSharing" data-status="<?php p($_['disableSharing']); ?>" /> - -<table id="filestable" data-allow-public-upload="<?php p($_['publicUploadEnabled'])?>" data-preview-x="36" data-preview-y="36"> - <thead> - <tr> - <th id='headerName' class="hidden column-name"> - <div id="headerName-container"> - <input type="checkbox" id="select_all" /> - <label for="select_all"></label> - <a class="name sort columntitle" data-sort="name"><span><?php p($l->t( 'Name' )); ?></span><span class="sort-indicator"></span></a> - <span id="selectedActionsList" class="selectedActions"> - <?php if($_['allowZipDownload']) : ?> - <a href="" class="download"> - <img class="svg" alt="Download" - src="<?php print_unescaped(OCP\image_path("core", "actions/download.svg")); ?>" /> - <?php p($l->t('Download'))?> - </a> - <?php endif; ?> - </span> - </div> - </th> - <th id="headerSize" class="hidden column-size"> - <a class="size sort columntitle" data-sort="size"><span><?php p($l->t('Size')); ?></span><span class="sort-indicator"></span></a> - </th> - <th id="headerDate" class="hidden column-mtime"> - <a id="modified" class="columntitle" data-sort="mtime"><span><?php p($l->t( 'Modified' )); ?></span><span class="sort-indicator"></span></a> - <?php if ($_['permissions'] & OCP\PERMISSION_DELETE): ?> - <span class="selectedActions"><a href="" class="delete-selected"> - <?php p($l->t('Delete'))?> - <img class="svg" alt="<?php p($l->t('Delete'))?>" - src="<?php print_unescaped(OCP\image_path("core", "actions/delete.svg")); ?>" /> - </a></span> - <?php endif; ?> - </th> - </tr> - </thead> - <tbody id="fileList"> - </tbody> - <tfoot> - </tfoot> -</table> -<div id="editor"></div><!-- FIXME Do not use this div in your app! It is deprecated and will be removed in the future! --> -<div id="uploadsize-message" title="<?php p($l->t('Upload too large'))?>"> - <p> - <?php p($l->t('The files you are trying to upload exceed the maximum size for file uploads on this server.'));?> - </p> -</div> -<div id="scanning-message"> - <h3> - <?php p($l->t('Files are being scanned, please wait.'));?> <span id='scan-count'></span> - </h3> - <p> - <?php p($l->t('Current scanning'));?> <span id='scan-current'></span> - </p> -</div> -</div><!-- closing app-content-files --> <?php foreach ($_['appContents'] as $content) { ?> <div id="app-content-<?php p($content['id']) ?>" class="hidden"> <?php print_unescaped($content['content']) ?> diff --git a/apps/files/templates/list.php b/apps/files/templates/list.php new file mode 100644 index 00000000000..a11defae052 --- /dev/null +++ b/apps/files/templates/list.php @@ -0,0 +1,104 @@ +<div id="controls"> + <div class="actions creatable hidden"> + <?php if(!isset($_['dirToken'])):?> + <div id="new" class="button"> + <a><?php p($l->t('New'));?></a> + <ul> + <li class="icon-filetype-text svg" + data-type="file" data-newname="<?php p($l->t('New text file')) ?>.txt"> + <p><?php p($l->t('Text file'));?></p> + </li> + <li class="icon-filetype-folder svg" + data-type="folder" data-newname="<?php p($l->t('New folder')) ?>"> + <p><?php p($l->t('Folder'));?></p> + </li> + <li class="icon-link svg" data-type="web"> + <p><?php p($l->t('From link'));?></p> + </li> + </ul> + </div> + <?php endif;?> + <div id="upload" class="button" + title="<?php p($l->t('Upload (max. %s)', array($_['uploadMaxHumanFilesize']))) ?>"> + <?php if($_['uploadMaxFilesize'] >= 0):?> + <input type="hidden" id="max_upload" name="MAX_FILE_SIZE" value="<?php p($_['uploadMaxFilesize']) ?>"> + <?php endif;?> + <input type="hidden" id="upload_limit" value="<?php p($_['uploadLimit']) ?>"> + <input type="hidden" id="free_space" value="<?php p($_['freeSpace']) ?>"> + <?php if(isset($_['dirToken'])):?> + <input type="hidden" id="publicUploadRequestToken" name="requesttoken" value="<?php p($_['requesttoken']) ?>" /> + <input type="hidden" id="dirToken" name="dirToken" value="<?php p($_['dirToken']) ?>" /> + <?php endif;?> + <input type="hidden" class="max_human_file_size" + value="(max <?php p($_['uploadMaxHumanFilesize']); ?>)"> + <input type="file" id="file_upload_start" name='files[]' + data-url="<?php print_unescaped(OCP\Util::linkTo('files', 'ajax/upload.php')); ?>" /> + <a href="#" class="svg icon-upload"></a> + </div> + <div id="uploadprogresswrapper"> + <div id="uploadprogressbar"></div> + <input type="button" class="stop" style="display:none" + value="<?php p($l->t('Cancel upload'));?>" + /> + </div> + </div> + <div id="file_action_panel"></div> + <div class="notCreatable notPublic hidden"> + <?php p($l->t('You don’t have permission to upload or create files here'))?> + </div> + <input type="hidden" name="permissions" value="" id="permissions"> +</div> + +<div id="emptycontent" class="hidden"><?php p($l->t('Nothing in here. Upload something!'))?></div> + +<table id="filestable" data-allow-public-upload="<?php p($_['publicUploadEnabled'])?>" data-preview-x="36" data-preview-y="36"> + <thead> + <tr> + <th id='headerName' class="hidden column-name"> + <div id="headerName-container"> + <input type="checkbox" id="select_all" /> + <label for="select_all"></label> + <a class="name sort columntitle" data-sort="name"><span><?php p($l->t( 'Name' )); ?></span><span class="sort-indicator"></span></a> + <span id="selectedActionsList" class="selectedActions"> + <?php if($_['allowZipDownload']) : ?> + <a href="" class="download"> + <img class="svg" alt="Download" + src="<?php print_unescaped(OCP\image_path("core", "actions/download.svg")); ?>" /> + <?php p($l->t('Download'))?> + </a> + <?php endif; ?> + </span> + </div> + </th> + <th id="headerSize" class="hidden column-size"> + <a class="size sort columntitle" data-sort="size"><span><?php p($l->t('Size')); ?></span><span class="sort-indicator"></span></a> + </th> + <th id="headerDate" class="hidden column-mtime"> + <a id="modified" class="columntitle" data-sort="mtime"><span><?php p($l->t( 'Modified' )); ?></span><span class="sort-indicator"></span></a> + <span class="selectedActions"><a href="" class="delete-selected"> + <?php p($l->t('Delete'))?> + <img class="svg" alt="<?php p($l->t('Delete'))?>" + src="<?php print_unescaped(OCP\image_path("core", "actions/delete.svg")); ?>" /> + </a></span> + </th> + </tr> + </thead> + <tbody id="fileList"> + </tbody> + <tfoot> + </tfoot> +</table> +<div id="editor"></div><!-- FIXME Do not use this div in your app! It is deprecated and will be removed in the future! --> +<div id="uploadsize-message" title="<?php p($l->t('Upload too large'))?>"> + <p> + <?php p($l->t('The files you are trying to upload exceed the maximum size for file uploads on this server.'));?> + </p> +</div> +<div id="scanning-message"> + <h3> + <?php p($l->t('Files are being scanned, please wait.'));?> <span id='scan-count'></span> + </h3> + <p> + <?php p($l->t('Current scanning'));?> <span id='scan-current'></span> + </p> +</div> diff --git a/apps/files/tests/js/appSpec.js b/apps/files/tests/js/appSpec.js new file mode 100644 index 00000000000..315f11f4b5b --- /dev/null +++ b/apps/files/tests/js/appSpec.js @@ -0,0 +1,169 @@ +/** +* ownCloud +* +* @author Vincent Petry +* @copyright 2014 Vincent Petry <pvince81@owncloud.com> +* +* 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 <http://www.gnu.org/licenses/>. +* +*/ + +describe('OCA.Files.App tests', function() { + var App = OCA.Files.App; + var pushStateStub; + var parseUrlQueryStub; + + beforeEach(function() { + $('#testArea').append( + '<div id="app-navigation">' + + '<ul><li data-id="files"><a>Files</a></li>' + + '<li data-id="other"><a>Other</a></li>' + + '</div>' + + '<div id="app-content">' + + '<div id="app-content-files" class="hidden">' + + '</div>' + + '<div id="app-content-other" class="hidden">' + + '</div>' + + '</div>' + + '</div>' + ); + + pushStateStub = sinon.stub(OC.Util.History, 'pushState'); + parseUrlQueryStub = sinon.stub(OC.Util.History, 'parseUrlQuery'); + parseUrlQueryStub.returns({}); + + App.initialize(); + }); + afterEach(function() { + App.navigation = null; + App.fileList = null; + App.files = null; + App.fileActions.clear(); + App.fileActions = null; + + pushStateStub.restore(); + parseUrlQueryStub.restore(); + }); + + describe('initialization', function() { + it('initializes the default file list with the default file actions', function() { + expect(App.fileList).toBeDefined(); + expect(App.fileList.fileActions.actions.all).toBeDefined(); + expect(App.fileList.$el.is('#app-content-files')).toEqual(true); + }); + }); + + describe('URL handling', function() { + it('pushes the state to the URL when current app changed directory', function() { + $('#app-content-files').trigger(new $.Event('changeDirectory', {dir: 'subdir'})); + expect(pushStateStub.calledOnce).toEqual(true); + expect(pushStateStub.getCall(0).args[0].dir).toEqual('subdir'); + expect(pushStateStub.getCall(0).args[0].view).not.toBeDefined(); + + $('li[data-id=other]>a').click(); + pushStateStub.reset(); + + $('#app-content-other').trigger(new $.Event('changeDirectory', {dir: 'subdir'})); + expect(pushStateStub.calledOnce).toEqual(true); + expect(pushStateStub.getCall(0).args[0].dir).toEqual('subdir'); + expect(pushStateStub.getCall(0).args[0].view).toEqual('other'); + }); + describe('onpopstate', function() { + it('sends "urlChanged" event to current app', function() { + var handler = sinon.stub(); + $('#app-content-files').on('urlChanged', handler); + App._onPopState({view: 'files', dir: '/somedir'}); + expect(handler.calledOnce).toEqual(true); + expect(handler.getCall(0).args[0].view).toEqual('files'); + expect(handler.getCall(0).args[0].dir).toEqual('/somedir'); + }); + it('sends "show" event to current app and sets navigation', function() { + var handlerFiles = sinon.stub(); + var handlerOther = sinon.stub(); + $('#app-content-files').on('show', handlerFiles); + $('#app-content-other').on('show', handlerOther); + App._onPopState({view: 'other', dir: '/somedir'}); + expect(handlerFiles.notCalled).toEqual(true); + expect(handlerOther.calledOnce).toEqual(true); + + handlerFiles.reset(); + handlerOther.reset(); + + App._onPopState({view: 'files', dir: '/somedir'}); + expect(handlerFiles.calledOnce).toEqual(true); + expect(handlerOther.notCalled).toEqual(true); + + expect(App.navigation.getActiveItem()).toEqual('files'); + expect($('#app-content-files').hasClass('hidden')).toEqual(false); + expect($('#app-content-other').hasClass('hidden')).toEqual(true); + }); + it('does not send "show" event to current app when already visible', function() { + var handler = sinon.stub(); + $('#app-content-files').on('show', handler); + App._onPopState({view: 'files', dir: '/somedir'}); + expect(handler.notCalled).toEqual(true); + }); + it('state defaults to files app with root dir', function() { + var handler = sinon.stub(); + parseUrlQueryStub.returns({}); + $('#app-content-files').on('urlChanged', handler); + App._onPopState(); + expect(handler.calledOnce).toEqual(true); + expect(handler.getCall(0).args[0].view).toEqual('files'); + expect(handler.getCall(0).args[0].dir).toEqual('/'); + }); + }); + describe('navigation', function() { + it('switches the navigation item and panel visibility when onpopstate', function() { + App._onPopState({view: 'other', dir: '/somedir'}); + expect(App.navigation.getActiveItem()).toEqual('other'); + expect($('#app-content-files').hasClass('hidden')).toEqual(true); + expect($('#app-content-other').hasClass('hidden')).toEqual(false); + expect($('li[data-id=files]').hasClass('selected')).toEqual(false); + expect($('li[data-id=other]').hasClass('selected')).toEqual(true); + + App._onPopState({view: 'files', dir: '/somedir'}); + + expect(App.navigation.getActiveItem()).toEqual('files'); + expect($('#app-content-files').hasClass('hidden')).toEqual(false); + expect($('#app-content-other').hasClass('hidden')).toEqual(true); + expect($('li[data-id=files]').hasClass('selected')).toEqual(true); + expect($('li[data-id=other]').hasClass('selected')).toEqual(false); + }); + it('clicking on navigation switches the panel visibility', function() { + $('li[data-id=other]>a').click(); + expect(App.navigation.getActiveItem()).toEqual('other'); + expect($('#app-content-files').hasClass('hidden')).toEqual(true); + expect($('#app-content-other').hasClass('hidden')).toEqual(false); + expect($('li[data-id=files]').hasClass('selected')).toEqual(false); + expect($('li[data-id=other]').hasClass('selected')).toEqual(true); + + $('li[data-id=files]>a').click(); + expect(App.navigation.getActiveItem()).toEqual('files'); + expect($('#app-content-files').hasClass('hidden')).toEqual(false); + expect($('#app-content-other').hasClass('hidden')).toEqual(true); + expect($('li[data-id=files]').hasClass('selected')).toEqual(true); + expect($('li[data-id=other]').hasClass('selected')).toEqual(false); + }); + it('clicking on navigation sends "urlChanged" event', function() { + var handler = sinon.stub(); + $('#app-content-other').on('urlChanged', handler); + $('li[data-id=other]>a').click(); + expect(handler.calledOnce).toEqual(true); + expect(handler.getCall(0).args[0].view).toEqual('other'); + expect(handler.getCall(0).args[0].dir).toEqual('/'); + }); + }); + }); +}); diff --git a/apps/files/tests/js/breadcrumbSpec.js b/apps/files/tests/js/breadcrumbSpec.js index 1bfe5308a27..e3d9c757a7c 100644 --- a/apps/files/tests/js/breadcrumbSpec.js +++ b/apps/files/tests/js/breadcrumbSpec.js @@ -20,7 +20,9 @@ */ /* global BreadCrumb */ -describe('BreadCrumb tests', function() { +describe('OCA.Files.BreadCrumb tests', function() { + var BreadCrumb = OCA.Files.BreadCrumb; + describe('Rendering', function() { var bc; beforeEach(function() { diff --git a/apps/files/tests/js/fileactionsSpec.js b/apps/files/tests/js/fileactionsSpec.js index f5eafba509f..23a13be45d4 100644 --- a/apps/files/tests/js/fileactionsSpec.js +++ b/apps/files/tests/js/fileactionsSpec.js @@ -19,20 +19,23 @@ * */ -/* global OC, FileActions, FileList */ -describe('FileActions tests', function() { - var $filesTable; +describe('OCA.Files.FileActions tests', function() { + var $filesTable, fileList; + var FileActions = OCA.Files.FileActions; beforeEach(function() { // init horrible parameters - var $body = $('body'); + var $body = $('#testArea'); $body.append('<input type="hidden" id="dir" value="/subdir"></input>'); $body.append('<input type="hidden" id="permissions" value="31"></input>'); // dummy files table $filesTable = $body.append('<table id="filestable"></table>'); - FileList.files = []; + fileList = new OCA.Files.FileList($('#testArea')); + FileActions.registerDefaultActions(fileList); }); afterEach(function() { + FileActions.clear(); + fileList = undefined; $('#dir, #permissions, #filestable').remove(); }); it('calling display() sets file actions', function() { @@ -47,7 +50,7 @@ describe('FileActions tests', function() { }; // note: FileActions.display() is called implicitly - var $tr = FileList.add(fileData); + var $tr = fileList.add(fileData); // actions defined after call expect($tr.find('.action.action-download').length).toEqual(1); @@ -66,7 +69,7 @@ describe('FileActions tests', function() { etag: 'a01234c', mtime: '123456' }; - var $tr = FileList.add(fileData); + var $tr = fileList.add(fileData); FileActions.display($tr.find('td.filename'), true); FileActions.display($tr.find('td.filename'), true); @@ -87,7 +90,7 @@ describe('FileActions tests', function() { etag: 'a01234c', mtime: '123456' }; - var $tr = FileList.add(fileData); + var $tr = fileList.add(fileData); FileActions.display($tr.find('td.filename'), true); $tr.find('.action-download').click(); @@ -97,7 +100,7 @@ describe('FileActions tests', function() { redirectStub.restore(); }); it('deletes file when clicking delete', function() { - var deleteStub = sinon.stub(FileList, 'do_delete'); + var deleteStub = sinon.stub(fileList, 'do_delete'); var fileData = { id: 18, type: 'file', @@ -107,7 +110,7 @@ describe('FileActions tests', function() { etag: 'a01234c', mtime: '123456' }; - var $tr = FileList.add(fileData); + 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 edb9e15e9bc..a0d8870f33c 100644 --- a/apps/files/tests/js/filelistSpec.js +++ b/apps/files/tests/js/filelistSpec.js @@ -19,10 +19,9 @@ * */ -/* global OC, FileList */ -describe('FileList tests', function() { - var testFiles, alertStub, notificationStub, - pushStateStub; +describe('OCA.Files.FileList tests', function() { + var testFiles, alertStub, notificationStub, fileList; + var FileActions = OCA.Files.FileActions; /** * Generate test file data @@ -59,14 +58,12 @@ describe('FileList tests', function() { // dummy files table $body.append('<table id="filestable"></table>'); - // 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( + '<div id="app-content-files">' + '<input type="hidden" id="dir" value="/subdir"></input>' + '<input type="hidden" id="permissions" value="31"></input>' + // dummy controls @@ -75,7 +72,7 @@ describe('FileList tests', function() { ' <div class="notCreatable"></div>' + '</div>' + // dummy table - // TODO: at some point this will be rendered by the FileList class itself! + // TODO: at some point this will be rendered by the fileList class itself! '<table id="filestable">' + '<thead><tr>' + '<th id="headerName" class="hidden column-name">' + @@ -91,7 +88,8 @@ describe('FileList tests', function() { '<tbody id="fileList"></tbody>' + '<tfoot></tfoot>' + '</table>' + - '<div id="emptycontent">Empty content message</div>' + '<div id="emptycontent">Empty content message</div>' + + '</div>' ); testFiles = [{ @@ -124,27 +122,27 @@ describe('FileList tests', function() { etag: '456' }]; - FileList.initialize(); + fileList = new OCA.Files.FileList($('#app-content-files')); + FileActions.registerDefaultActions(fileList); + fileList.setFileActions(FileActions); }); afterEach(function() { testFiles = undefined; - FileList.initialized = false; - FileList.isEmpty = true; - delete FileList._reloadCall; + fileList = undefined; + FileActions.clear(); $('#dir, #permissions, #filestable').remove(); notificationStub.restore(); alertStub.restore(); - pushStateStub.restore(); }); describe('Getters', function() { it('Returns the current directory', function() { $('#dir').val('/one/two/three'); - expect(FileList.getCurrentDirectory()).toEqual('/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); + expect(fileList.getDirectoryPermissions()).toEqual(23); }); }); describe('Adding files', function() { @@ -167,7 +165,7 @@ describe('FileList tests', function() { etag: 'a01234c', mtime: '123456' }; - var $tr = FileList.add(fileData); + var $tr = fileList.add(fileData); expect($tr).toBeDefined(); expect($tr[0].tagName.toLowerCase()).toEqual('tr'); @@ -182,7 +180,7 @@ describe('FileList tests', function() { 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]); + 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 = { @@ -194,7 +192,7 @@ describe('FileList tests', function() { etag: 'a01234c', mtime: '123456' }; - var $tr = FileList.add(fileData); + var $tr = fileList.add(fileData); expect($tr).toBeDefined(); expect($tr[0].tagName.toLowerCase()).toEqual('tr'); @@ -209,7 +207,7 @@ describe('FileList tests', function() { expect($tr.find('.filesize').text()).toEqual('1 kB'); - expect(FileList.findFileEl('testFolder')[0]).toEqual($tr[0]); + expect(fileList.findFileEl('testFolder')[0]).toEqual($tr[0]); }); it('generates file element with default attributes when calling add() with minimal data', function() { var fileData = { @@ -217,8 +215,8 @@ describe('FileList tests', function() { name: 'testFile.txt' }; - clock.tick(123456); - var $tr = FileList.add(fileData); + clock.tick(123456); + var $tr = fileList.add(fileData); expect($tr).toBeDefined(); expect($tr[0].tagName.toLowerCase()).toEqual('tr'); @@ -238,8 +236,8 @@ describe('FileList tests', function() { type: 'dir', name: 'testFolder' }; - clock.tick(123456); - var $tr = FileList.add(fileData); + clock.tick(123456); + var $tr = fileList.add(fileData); expect($tr).toBeDefined(); expect($tr[0].tagName.toLowerCase()).toEqual('tr'); @@ -260,7 +258,7 @@ describe('FileList tests', function() { name: 'testFolder', size: '0' }; - var $tr = FileList.add(fileData); + var $tr = fileList.add(fileData); expect($tr.find('.filesize').text()).toEqual('0 B'); }); it('adds new file to the end of the list', function() { @@ -269,19 +267,19 @@ describe('FileList tests', function() { type: 'file', name: 'ZZZ.txt' }; - FileList.setFiles(testFiles); - $tr = FileList.add(fileData); + fileList.setFiles(testFiles); + $tr = fileList.add(fileData); expect($tr.index()).toEqual(4); }); it('inserts files in a sorted manner when insert option is enabled', function() { var $tr; for (var i = 0; i < testFiles.length; i++) { - FileList.add(testFiles[i]); + fileList.add(testFiles[i]); } - expect(FileList.files[0].name).toEqual('somedir'); - expect(FileList.files[1].name).toEqual('One.txt'); - expect(FileList.files[2].name).toEqual('Three.pdf'); - expect(FileList.files[3].name).toEqual('Two.jpg'); + expect(fileList.files[0].name).toEqual('somedir'); + expect(fileList.files[1].name).toEqual('One.txt'); + expect(fileList.files[2].name).toEqual('Three.pdf'); + expect(fileList.files[3].name).toEqual('Two.jpg'); }); it('inserts new file at correct position', function() { var $tr; @@ -290,12 +288,12 @@ describe('FileList tests', function() { name: 'P comes after O.txt' }; for (var i = 0; i < testFiles.length; i++) { - FileList.add(testFiles[i]); + fileList.add(testFiles[i]); } - $tr = FileList.add(fileData); + $tr = fileList.add(fileData); // after "One.txt" expect($tr.index()).toEqual(2); - expect(FileList.files[2]).toEqual(fileData); + expect(fileList.files[2]).toEqual(fileData); }); it('inserts new folder at correct position in insert mode', function() { var $tr; @@ -304,11 +302,11 @@ describe('FileList tests', function() { name: 'somedir2 comes after somedir' }; for (var i = 0; i < testFiles.length; i++) { - FileList.add(testFiles[i]); + fileList.add(testFiles[i]); } - $tr = FileList.add(fileData); + $tr = fileList.add(fileData); expect($tr.index()).toEqual(1); - expect(FileList.files[1]).toEqual(fileData); + expect(fileList.files[1]).toEqual(fileData); }); it('inserts new file at the end correctly', function() { var $tr; @@ -317,21 +315,22 @@ describe('FileList tests', function() { name: 'zzz.txt' }; for (var i = 0; i < testFiles.length; i++) { - FileList.add(testFiles[i]); + fileList.add(testFiles[i]); } - $tr = FileList.add(fileData); + $tr = fileList.add(fileData); expect($tr.index()).toEqual(4); - expect(FileList.files[4]).toEqual(fileData); + expect(fileList.files[4]).toEqual(fileData); }); it('removes empty content message and shows summary when adding first file', function() { + var $summary; var fileData = { type: 'file', name: 'first file.txt', size: 12 }; - FileList.setFiles([]); - expect(FileList.isEmpty).toEqual(true); - FileList.add(fileData); + fileList.setFiles([]); + expect(fileList.isEmpty).toEqual(true); + fileList.add(fileData); $summary = $('#filestable .summary'); expect($summary.hasClass('hidden')).toEqual(false); // yes, ugly... @@ -341,19 +340,20 @@ describe('FileList tests', function() { 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); + 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 $summary; var $removedEl; - FileList.setFiles(testFiles); - $removedEl = FileList.remove('One.txt'); + fileList.setFiles(testFiles); + $removedEl = fileList.remove('One.txt'); expect($removedEl).toBeDefined(); expect($removedEl.attr('data-file')).toEqual('One.txt'); expect($('#fileList tr').length).toEqual(3); - expect(FileList.files.length).toEqual(3); - expect(FileList.findFileEl('One.txt').length).toEqual(0); + expect(fileList.files.length).toEqual(3); + expect(fileList.findFileEl('One.txt').length).toEqual(0); $summary = $('#filestable .summary'); expect($summary.hasClass('hidden')).toEqual(false); @@ -361,27 +361,28 @@ describe('FileList tests', function() { 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); + expect(fileList.isEmpty).toEqual(false); }); it('Shows empty content when removing last file', function() { - FileList.setFiles([testFiles[0]]); - FileList.remove('One.txt'); + var $summary; + fileList.setFiles([testFiles[0]]); + fileList.remove('One.txt'); expect($('#fileList tr').length).toEqual(0); - expect(FileList.files.length).toEqual(0); - expect(FileList.findFileEl('One.txt').length).toEqual(0); + expect(fileList.files.length).toEqual(0); + expect(fileList.findFileEl('One.txt').length).toEqual(0); $summary = $('#filestable .summary'); expect($summary.hasClass('hidden')).toEqual(true); expect($('#filestable thead th').hasClass('hidden')).toEqual(true); expect($('#emptycontent').hasClass('hidden')).toEqual(false); - expect(FileList.isEmpty).toEqual(true); + 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']); + fileList.do_delete(['One.txt', 'Two.jpg']); expect(fakeServer.requests.length).toEqual(1); request = fakeServer.requests[0]; @@ -391,7 +392,8 @@ describe('FileList tests', function() { 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); + var $summary; + fileList.setFiles(testFiles); doDelete(); fakeServer.requests[0].respond( @@ -400,10 +402,10 @@ describe('FileList tests', function() { 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').length).toEqual(2); + 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').length).toEqual(2); $summary = $('#filestable .summary'); expect($summary.hasClass('hidden')).toEqual(false); @@ -411,28 +413,29 @@ describe('FileList tests', function() { 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(fileList.isEmpty).toEqual(false); expect($('#filestable thead th').hasClass('hidden')).toEqual(false); expect($('#emptycontent').hasClass('hidden')).toEqual(true); expect(notificationStub.notCalled).toEqual(true); }); it('shows spinner on files to be deleted', function() { - FileList.setFiles(testFiles); + fileList.setFiles(testFiles); doDelete(); - expect(FileList.findFileEl('One.txt').find('.progress-icon:not(.delete-icon)').length).toEqual(1); - expect(FileList.findFileEl('Three.pdf').find('.delete-icon:not(.progress-icon)').length).toEqual(1); + expect(fileList.findFileEl('One.txt').find('.progress-icon:not(.delete-icon)').length).toEqual(1); + expect(fileList.findFileEl('Three.pdf').find('.delete-icon:not(.progress-icon)').length).toEqual(1); }); it('shows spinner on all files when deleting all', function() { - FileList.setFiles(testFiles); + fileList.setFiles(testFiles); - FileList.do_delete(); + fileList.do_delete(); - expect(FileList.$fileList.find('tr .progress-icon:not(.delete-icon)').length).toEqual(4); + expect(fileList.$fileList.find('tr .progress-icon:not(.delete-icon)').length).toEqual(4); }); it('updates summary when deleting last file', function() { - FileList.setFiles([testFiles[0], testFiles[1]]); + var $summary; + fileList.setFiles([testFiles[0], testFiles[1]]); doDelete(); fakeServer.requests[0].respond( @@ -441,17 +444,17 @@ describe('FileList tests', function() { JSON.stringify({status: 'success'}) ); - expect(FileList.$fileList.find('tr').length).toEqual(0); + expect(fileList.$fileList.find('tr').length).toEqual(0); $summary = $('#filestable .summary'); expect($summary.hasClass('hidden')).toEqual(true); - expect(FileList.isEmpty).toEqual(true); - expect(FileList.files.length).toEqual(0); + expect(fileList.isEmpty).toEqual(true); + expect(fileList.files.length).toEqual(0); 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); + fileList.setFiles(testFiles); doDelete(); fakeServer.requests[0].respond( @@ -461,9 +464,9 @@ describe('FileList tests', function() { ); // 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').length).toEqual(4); + expect(fileList.findFileEl('One.txt').length).toEqual(1); + expect(fileList.findFileEl('Two.jpg').length).toEqual(1); + expect(fileList.$fileList.find('tr').length).toEqual(4); expect(notificationStub.calledOnce).toEqual(true); }); @@ -473,12 +476,12 @@ describe('FileList tests', function() { var $input, request; for (var i = 0; i < testFiles.length; i++) { - FileList.add(testFiles[i]); + fileList.add(testFiles[i]); } // trigger rename prompt - FileList.rename('One.txt'); - $input = FileList.$fileList.find('input.filename'); + fileList.rename('One.txt'); + $input = fileList.$fileList.find('input.filename'); $input.val('Tu_after_three.txt').blur(); expect(fakeServer.requests.length).toEqual(1); @@ -487,10 +490,10 @@ describe('FileList tests', function() { expect(OC.parseQueryString(request.url)).toEqual({'dir': '/subdir', newname: 'Tu_after_three.txt', file: 'One.txt'}); // element is renamed before the request finishes - expect(FileList.findFileEl('One.txt').length).toEqual(0); - expect(FileList.findFileEl('Tu_after_three.txt').length).toEqual(1); + expect(fileList.findFileEl('One.txt').length).toEqual(0); + expect(fileList.findFileEl('Tu_after_three.txt').length).toEqual(1); // input is gone - expect(FileList.$fileList.find('input.filename').length).toEqual(0); + expect(fileList.$fileList.find('input.filename').length).toEqual(0); } it('Inserts renamed file entry at correct position if rename ajax call suceeded', function() { doRename(); @@ -504,9 +507,9 @@ describe('FileList tests', function() { })); // element stays renamed - expect(FileList.findFileEl('One.txt').length).toEqual(0); - expect(FileList.findFileEl('Tu_after_three.txt').length).toEqual(1); - expect(FileList.findFileEl('Tu_after_three.txt').index()).toEqual(2); // after Two.txt + expect(fileList.findFileEl('One.txt').length).toEqual(0); + expect(fileList.findFileEl('Tu_after_three.txt').length).toEqual(1); + expect(fileList.findFileEl('Tu_after_three.txt').index()).toEqual(2); // after Two.txt expect(alertStub.notCalled).toEqual(true); }); @@ -521,9 +524,9 @@ describe('FileList tests', function() { })); // element was reverted - expect(FileList.findFileEl('One.txt').length).toEqual(1); - expect(FileList.findFileEl('One.txt').index()).toEqual(1); // after somedir - expect(FileList.findFileEl('Tu_after_three.txt').length).toEqual(0); + expect(fileList.findFileEl('One.txt').length).toEqual(1); + expect(fileList.findFileEl('One.txt').index()).toEqual(1); // after somedir + expect(fileList.findFileEl('Tu_after_three.txt').length).toEqual(0); expect(alertStub.calledOnce).toEqual(true); }); @@ -538,7 +541,7 @@ describe('FileList tests', function() { } })); - $tr = FileList.findFileEl('Tu_after_three.txt'); + $tr = fileList.findFileEl('Tu_after_three.txt'); expect($tr.find('a.name').attr('href')).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fsubdir&files=Tu_after_three.txt'); }); // FIXME: fix this in the source code! @@ -555,17 +558,17 @@ describe('FileList tests', function() { } })); - $tr = FileList.findFileEl('Tu_after_three.txt'); + $tr = fileList.findFileEl('Tu_after_three.txt'); expect($tr.find('a.name').attr('href')).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fsubdir&files=One.txt'); }); }); describe('Moving files', function() { beforeEach(function() { - FileList.setFiles(testFiles); + fileList.setFiles(testFiles); }); it('Moves single file to target folder', function() { var request; - FileList.move('One.txt', '/somedir'); + fileList.move('One.txt', '/somedir'); expect(fakeServer.requests.length).toEqual(1); request = fakeServer.requests[0]; @@ -580,17 +583,17 @@ describe('FileList tests', function() { } })); - expect(FileList.findFileEl('One.txt').length).toEqual(0); + expect(fileList.findFileEl('One.txt').length).toEqual(0); // folder size has increased - expect(FileList.findFileEl('somedir').data('size')).toEqual(262); - expect(FileList.findFileEl('somedir').find('.filesize').text()).toEqual('262 B'); + expect(fileList.findFileEl('somedir').data('size')).toEqual(262); + expect(fileList.findFileEl('somedir').find('.filesize').text()).toEqual('262 B'); expect(notificationStub.notCalled).toEqual(true); }); it('Moves list of files to target folder', function() { var request; - FileList.move(['One.txt', 'Two.jpg'], '/somedir'); + fileList.move(['One.txt', 'Two.jpg'], '/somedir'); expect(fakeServer.requests.length).toEqual(2); request = fakeServer.requests[0]; @@ -609,11 +612,11 @@ describe('FileList tests', function() { } })); - expect(FileList.findFileEl('One.txt').length).toEqual(0); + expect(fileList.findFileEl('One.txt').length).toEqual(0); // folder size has increased - expect(FileList.findFileEl('somedir').data('size')).toEqual(262); - expect(FileList.findFileEl('somedir').find('.filesize').text()).toEqual('262 B'); + expect(fileList.findFileEl('somedir').data('size')).toEqual(262); + expect(fileList.findFileEl('somedir').find('.filesize').text()).toEqual('262 B'); fakeServer.requests[1].respond(200, {'Content-Type': 'application/json'}, JSON.stringify({ status: 'success', @@ -623,17 +626,17 @@ describe('FileList tests', function() { } })); - expect(FileList.findFileEl('Two.jpg').length).toEqual(0); + expect(fileList.findFileEl('Two.jpg').length).toEqual(0); // folder size has increased - expect(FileList.findFileEl('somedir').data('size')).toEqual(12311); - expect(FileList.findFileEl('somedir').find('.filesize').text()).toEqual('12 kB'); + expect(fileList.findFileEl('somedir').data('size')).toEqual(12311); + expect(fileList.findFileEl('somedir').find('.filesize').text()).toEqual('12 kB'); expect(notificationStub.notCalled).toEqual(true); }); it('Shows notification if a file could not be moved', function() { var request; - FileList.move('One.txt', '/somedir'); + fileList.move('One.txt', '/somedir'); expect(fakeServer.requests.length).toEqual(1); request = fakeServer.requests[0]; @@ -647,7 +650,7 @@ describe('FileList tests', function() { } })); - expect(FileList.findFileEl('One.txt').length).toEqual(1); + expect(fileList.findFileEl('One.txt').length).toEqual(1); expect(notificationStub.calledOnce).toEqual(true); expect(notificationStub.getCall(0).args[0]).toEqual('Error while moving file'); @@ -655,47 +658,47 @@ describe('FileList tests', function() { }); describe('List rendering', function() { it('renders a list of files using add()', function() { - expect(FileList.files.length).toEqual(0); - expect(FileList.files).toEqual([]); - FileList.setFiles(testFiles); + expect(fileList.files.length).toEqual(0); + expect(fileList.files).toEqual([]); + fileList.setFiles(testFiles); expect($('#fileList tr').length).toEqual(4); - expect(FileList.files.length).toEqual(4); - expect(FileList.files).toEqual(testFiles); + expect(fileList.files.length).toEqual(4); + expect(fileList.files).toEqual(testFiles); }); it('updates summary using the file sizes', function() { var $summary; - FileList.setFiles(testFiles); + fileList.setFiles(testFiles); $summary = $('#filestable .summary'); expect($summary.hasClass('hidden')).toEqual(false); 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); + fileList.setFiles(testFiles); expect($('#filestable thead th').hasClass('hidden')).toEqual(false); expect($('#emptycontent').hasClass('hidden')).toEqual(true); - expect(FileList.$el.find('.summary').hasClass('hidden')).toEqual(false); + expect(fileList.$el.find('.summary').hasClass('hidden')).toEqual(false); }); it('hides headers, summary and show empty content message after setting empty file list', function(){ - FileList.setFiles([]); + fileList.setFiles([]); expect($('#filestable thead th').hasClass('hidden')).toEqual(true); expect($('#emptycontent').hasClass('hidden')).toEqual(false); - expect(FileList.$el.find('.summary').hasClass('hidden')).toEqual(true); + expect(fileList.$el.find('.summary').hasClass('hidden')).toEqual(true); }); it('hides headers, empty content message, and summary when list is empty and user has no creation permission', function(){ $('#permissions').val(0); - FileList.setFiles([]); + fileList.setFiles([]); expect($('#filestable thead th').hasClass('hidden')).toEqual(true); expect($('#emptycontent').hasClass('hidden')).toEqual(true); - expect(FileList.$el.find('.summary').hasClass('hidden')).toEqual(true); + expect(fileList.$el.find('.summary').hasClass('hidden')).toEqual(true); }); it('calling findFileEl() can find existing file element', function() { - FileList.setFiles(testFiles); - expect(FileList.findFileEl('Two.jpg').length).toEqual(1); + 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); + 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'); @@ -704,121 +707,122 @@ describe('FileList tests', function() { name: 'testFile.txt', directory: '/current dir' }; - var $tr = FileList.add(fileData); - expect(FileList.findFileEl('testFile.txt').length).toEqual(1); + 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); + 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); + fileList.$fileList.on('updated', handler); + fileList.setFiles(testFiles); expect(handler.calledOnce).toEqual(true); }); it('does not update summary when removing non-existing files', function() { + var $summary; // single file - FileList.setFiles([testFiles[0]]); + fileList.setFiles([testFiles[0]]); $summary = $('#filestable .summary'); expect($summary.hasClass('hidden')).toEqual(false); expect($summary.find('.info').text()).toEqual('0 folders and 1 file'); - FileList.remove('unexist.txt'); + fileList.remove('unexist.txt'); expect($summary.hasClass('hidden')).toEqual(false); expect($summary.find('.info').text()).toEqual('0 folders and 1 file'); }); }); describe('Rendering next page on scroll', function() { beforeEach(function() { - FileList.setFiles(generateFiles(0, 64)); + fileList.setFiles(generateFiles(0, 64)); }); it('renders only the first page', function() { - expect(FileList.files.length).toEqual(65); + expect(fileList.files.length).toEqual(65); expect($('#fileList tr').length).toEqual(20); }); it('renders the second page when scrolling down (trigger nextPage)', function() { // TODO: can't simulate scrolling here, so calling nextPage directly - FileList._nextPage(true); + fileList._nextPage(true); expect($('#fileList tr').length).toEqual(40); - FileList._nextPage(true); + fileList._nextPage(true); expect($('#fileList tr').length).toEqual(60); - FileList._nextPage(true); + fileList._nextPage(true); expect($('#fileList tr').length).toEqual(65); - FileList._nextPage(true); + fileList._nextPage(true); // stays at 65 expect($('#fileList tr').length).toEqual(65); }); it('inserts into the DOM if insertion point is in the visible page ', function() { - FileList.add({ + fileList.add({ id: 2000, type: 'file', name: 'File with index 15b.txt' }); expect($('#fileList tr').length).toEqual(21); - expect(FileList.findFileEl('File with index 15b.txt').index()).toEqual(16); + expect(fileList.findFileEl('File with index 15b.txt').index()).toEqual(16); }); it('does not inserts into the DOM if insertion point is not the visible page ', function() { - FileList.add({ + fileList.add({ id: 2000, type: 'file', name: 'File with index 28b.txt' }); expect($('#fileList tr').length).toEqual(20); - expect(FileList.findFileEl('File with index 28b.txt').length).toEqual(0); - FileList._nextPage(true); + expect(fileList.findFileEl('File with index 28b.txt').length).toEqual(0); + fileList._nextPage(true); expect($('#fileList tr').length).toEqual(40); - expect(FileList.findFileEl('File with index 28b.txt').index()).toEqual(29); + expect(fileList.findFileEl('File with index 28b.txt').index()).toEqual(29); }); it('appends into the DOM when inserting a file after the last visible element', function() { - FileList.add({ + fileList.add({ id: 2000, type: 'file', name: 'File with index 19b.txt' }); expect($('#fileList tr').length).toEqual(21); - FileList._nextPage(true); + fileList._nextPage(true); expect($('#fileList tr').length).toEqual(41); }); it('appends into the DOM when inserting a file on the last page when visible', function() { - FileList._nextPage(true); + fileList._nextPage(true); expect($('#fileList tr').length).toEqual(40); - FileList._nextPage(true); + fileList._nextPage(true); expect($('#fileList tr').length).toEqual(60); - FileList._nextPage(true); + fileList._nextPage(true); expect($('#fileList tr').length).toEqual(65); - FileList._nextPage(true); - FileList.add({ + fileList._nextPage(true); + fileList.add({ id: 2000, type: 'file', name: 'File with index 88.txt' }); expect($('#fileList tr').length).toEqual(66); - FileList._nextPage(true); + fileList._nextPage(true); expect($('#fileList tr').length).toEqual(66); }); it('shows additional page when appending a page of files and scrolling down', function() { var newFiles = generateFiles(66, 81); for (var i = 0; i < newFiles.length; i++) { - FileList.add(newFiles[i]); + fileList.add(newFiles[i]); } expect($('#fileList tr').length).toEqual(20); - FileList._nextPage(true); + fileList._nextPage(true); expect($('#fileList tr').length).toEqual(40); - FileList._nextPage(true); + fileList._nextPage(true); expect($('#fileList tr').length).toEqual(60); - FileList._nextPage(true); + fileList._nextPage(true); expect($('#fileList tr').length).toEqual(80); - FileList._nextPage(true); + fileList._nextPage(true); expect($('#fileList tr').length).toEqual(81); - FileList._nextPage(true); + fileList._nextPage(true); expect($('#fileList tr').length).toEqual(81); }); it('automatically renders next page when there are not enough elements visible', function() { // delete the 15 first elements for (var i = 0; i < 15; i++) { - FileList.remove(FileList.files[0].name); + fileList.remove(fileList.files[0].name); } // still makes sure that there are 20 elements visible, if any expect($('#fileList tr').length).toEqual(25); @@ -838,7 +842,7 @@ describe('FileList tests', function() { } beforeEach(function() { - previewLoadStub = sinon.stub(Files, 'lazyLoadPreview'); + previewLoadStub = sinon.stub(OCA.Files.FileList.prototype, 'lazyLoadPreview'); }); afterEach(function() { previewLoadStub.restore(); @@ -848,7 +852,7 @@ describe('FileList tests', function() { type: 'file', name: 'testFile.txt' }; - var $tr = FileList.add(fileData); + 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); @@ -858,7 +862,7 @@ describe('FileList tests', function() { type: 'dir', name: 'test dir' }; - var $tr = FileList.add(fileData); + 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); @@ -869,7 +873,7 @@ describe('FileList tests', function() { name: 'test dir', icon: OC.webroot + '/core/img/filetypes/application-pdf.svg' }; - var $tr = FileList.add(fileData); + 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); @@ -880,12 +884,12 @@ describe('FileList tests', function() { name: 'test dir', isPreviewAvailable: true }; - var $tr = FileList.add(fileData); + 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'); + previewLoadStub.getCall(0).args[0].callback(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() { @@ -894,7 +898,7 @@ describe('FileList tests', function() { name: 'test dir', isPreviewAvailable: false }; - var $tr = FileList.add(fileData); + 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); @@ -902,22 +906,22 @@ describe('FileList tests', function() { }); describe('viewer mode', function() { it('enabling viewer mode hides files table and action buttons', function() { - FileList.setViewerMode(true); + 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); + 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); + fileList.setViewerMode(true); + fileList.setViewerMode(false); expect($('#filestable').hasClass('hidden')).toEqual(false); expect($('.actions').hasClass('hidden')).toEqual(true); expect($('.notCreatable').hasClass('hidden')).toEqual(false); @@ -940,18 +944,18 @@ describe('FileList tests', function() { ]); }); it('fetches file list from server and renders it when reload() is called', function() { - FileList.reload(); + 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', sort: 'name', sortdirection: 'asc'}); fakeServer.respond(); expect($('#fileList tr').length).toEqual(4); - expect(FileList.findFileEl('One.txt').length).toEqual(1); + 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'); + 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); @@ -965,14 +969,14 @@ describe('FileList tests', function() { }, '' ]); - FileList.changeDirectory('/unexist'); + fileList.changeDirectory('/unexist'); fakeServer.respond(); - expect(FileList.getCurrentDirectory()).toEqual('/'); + 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'); + 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(); @@ -981,18 +985,23 @@ describe('FileList tests', function() { 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('triggers "changeDirectory" event when changing directory', function() { + var handler = sinon.stub(); + $('#app-content-files').on('changeDirectory', handler); + fileList.changeDirectory('/somedir'); + expect(handler.calledOnce).toEqual(true); + expect(handler.getCall(0).args[0].dir).toEqual('/somedir'); + }); + it('changes the directory when receiving "urlChanged" event', function() { + $('#app-content-files').trigger(new $.Event('urlChanged', {view: 'files', dir: '/somedir'})); + expect(fileList.getCurrentDirectory()).toEqual('/somedir'); }); it('refreshes breadcrumb after update', function() { - var setDirSpy = sinon.spy(FileList.breadcrumb, 'setDirectory'); - FileList.changeDirectory('/anothersubdir'); + 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); + expect(fileList.breadcrumb.setDirectory.calledOnce).toEqual(true); + expect(fileList.breadcrumb.setDirectory.calledWith('/anothersubdir')).toEqual(true); setDirSpy.restore(); }); }); @@ -1013,20 +1022,20 @@ describe('FileList tests', function() { ]); }); it('clicking on root breadcrumb changes directory to root', function() { - FileList.changeDirectory('/subdir/two/three with space/four/five'); + 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(); + 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'); + 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(); + 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'); @@ -1034,9 +1043,9 @@ describe('FileList tests', function() { }); it('dropping files on breadcrumb calls move operation', function() { var request, query, testDir = '/subdir/two/three with space/four/five'; - FileList.changeDirectory(testDir); + fileList.changeDirectory(testDir); fakeServer.respond(); - var $crumb = FileList.breadcrumb.$el.find('.crumb:eq(3)'); + var $crumb = fileList.breadcrumb.$el.find('.crumb:eq(3)'); // no idea what this is but is required by the handler var ui = { helper: { @@ -1049,7 +1058,7 @@ describe('FileList tests', function() { $('<tr data-file="Two.jpg" data-dir="' + testDir + '"></tr>') ]); // simulate drop event - FileList._onDropOnBreadCrumb(new $.Event('drop', {target: $crumb}), ui); + fileList._onDropOnBreadCrumb(new $.Event('drop', {target: $crumb}), ui); // will trigger two calls to move.php (first one was previous list.php) expect(fakeServer.requests.length).toEqual(3); @@ -1075,10 +1084,10 @@ describe('FileList tests', function() { }); }); it('dropping files on same dir breadcrumb does nothing', function() { - var request, query, testDir = '/subdir/two/three with space/four/five'; - FileList.changeDirectory(testDir); + var testDir = '/subdir/two/three with space/four/five'; + fileList.changeDirectory(testDir); fakeServer.respond(); - var $crumb = FileList.breadcrumb.$el.find('.crumb:last'); + var $crumb = fileList.breadcrumb.$el.find('.crumb:last'); // no idea what this is but is required by the handler var ui = { helper: { @@ -1091,7 +1100,7 @@ describe('FileList tests', function() { $('<tr data-file="Two.jpg" data-dir="' + testDir + '"></tr>') ]); // simulate drop event - FileList._onDropOnBreadCrumb(new $.Event('drop', {target: $crumb}), ui); + fileList._onDropOnBreadCrumb(new $.Event('drop', {target: $crumb}), ui); // no extra server request expect(fakeServer.requests.length).toEqual(1); @@ -1099,32 +1108,32 @@ describe('FileList tests', function() { }); describe('Download Url', function() { it('returns correct download URL for single files', function() { - 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'); + 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'); $('#dir').val('/'); - expect(Files.getDownloadUrl('some file.txt')).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2F&files=some%20file.txt'); + expect(fileList.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(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'); + 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'); }); 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'); + expect(fileList.getAjaxUrl('test', {a:1, b:'x y'})).toEqual(OC.webroot + '/index.php/apps/files/ajax/test.php?a=1&b=x%20y'); }); }); describe('File selection', function() { beforeEach(function() { - FileList.setFiles(testFiles); + fileList.setFiles(testFiles); }); it('Selects a file when clicking its checkbox', function() { - var $tr = FileList.findFileEl('One.txt'); + var $tr = fileList.findFileEl('One.txt'); expect($tr.find('input:checkbox').prop('checked')).toEqual(false); $tr.find('td.filename input:checkbox').click(); expect($tr.find('input:checkbox').prop('checked')).toEqual(true); }); it('Selects/deselect a file when clicking on the name while holding Ctrl', function() { - var $tr = FileList.findFileEl('One.txt'); - var $tr2 = FileList.findFileEl('Three.pdf'); + var $tr = fileList.findFileEl('One.txt'); + var $tr2 = fileList.findFileEl('Three.pdf'); var e; expect($tr.find('input:checkbox').prop('checked')).toEqual(false); expect($tr2.find('input:checkbox').prop('checked')).toEqual(false); @@ -1142,7 +1151,7 @@ describe('FileList tests', function() { expect($tr.find('input:checkbox').prop('checked')).toEqual(true); expect($tr2.find('input:checkbox').prop('checked')).toEqual(true); - expect(_.pluck(FileList.getSelectedFiles(), 'name')).toEqual(['One.txt', 'Three.pdf']); + expect(_.pluck(fileList.getSelectedFiles(), 'name')).toEqual(['One.txt', 'Three.pdf']); // deselect now e = new $.Event('click'); @@ -1150,11 +1159,11 @@ describe('FileList tests', function() { $tr2.find('td.filename .name').trigger(e); expect($tr.find('input:checkbox').prop('checked')).toEqual(true); expect($tr2.find('input:checkbox').prop('checked')).toEqual(false); - expect(_.pluck(FileList.getSelectedFiles(), 'name')).toEqual(['One.txt']); + expect(_.pluck(fileList.getSelectedFiles(), 'name')).toEqual(['One.txt']); }); it('Selects a range when clicking on one file then Shift clicking on another one', function() { - var $tr = FileList.findFileEl('One.txt'); - var $tr2 = FileList.findFileEl('Three.pdf'); + var $tr = fileList.findFileEl('One.txt'); + var $tr2 = fileList.findFileEl('Three.pdf'); var e; $tr.find('td.filename input:checkbox').click(); e = new $.Event('click'); @@ -1163,16 +1172,16 @@ describe('FileList tests', function() { expect($tr.find('input:checkbox').prop('checked')).toEqual(true); expect($tr2.find('input:checkbox').prop('checked')).toEqual(true); - expect(FileList.findFileEl('Two.jpg').find('input:checkbox').prop('checked')).toEqual(true); - var selection = _.pluck(FileList.getSelectedFiles(), 'name'); + expect(fileList.findFileEl('Two.jpg').find('input:checkbox').prop('checked')).toEqual(true); + var selection = _.pluck(fileList.getSelectedFiles(), 'name'); expect(selection.length).toEqual(3); expect(selection).toContain('One.txt'); expect(selection).toContain('Two.jpg'); expect(selection).toContain('Three.pdf'); }); it('Selects a range when clicking on one file then Shift clicking on another one that is above the first one', function() { - var $tr = FileList.findFileEl('One.txt'); - var $tr2 = FileList.findFileEl('Three.pdf'); + var $tr = fileList.findFileEl('One.txt'); + var $tr2 = fileList.findFileEl('Three.pdf'); var e; $tr2.find('td.filename input:checkbox').click(); e = new $.Event('click'); @@ -1181,8 +1190,8 @@ describe('FileList tests', function() { expect($tr.find('input:checkbox').prop('checked')).toEqual(true); expect($tr2.find('input:checkbox').prop('checked')).toEqual(true); - expect(FileList.findFileEl('Two.jpg').find('input:checkbox').prop('checked')).toEqual(true); - var selection = _.pluck(FileList.getSelectedFiles(), 'name'); + expect(fileList.findFileEl('Two.jpg').find('input:checkbox').prop('checked')).toEqual(true); + var selection = _.pluck(fileList.getSelectedFiles(), 'name'); expect(selection.length).toEqual(3); expect(selection).toContain('One.txt'); expect(selection).toContain('Two.jpg'); @@ -1194,19 +1203,19 @@ describe('FileList tests', function() { expect($('#select_all').prop('checked')).toEqual(true); }); it('Selecting all files on the first visible page will not automatically check "select all" checkbox', function() { - FileList.setFiles(generateFiles(0, 41)); + fileList.setFiles(generateFiles(0, 41)); expect($('#select_all').prop('checked')).toEqual(false); $('#fileList tr td.filename input:checkbox').click(); expect($('#select_all').prop('checked')).toEqual(false); }); it('Clicking "select all" will select/deselect all files', function() { - FileList.setFiles(generateFiles(0, 41)); + fileList.setFiles(generateFiles(0, 41)); $('#select_all').click(); expect($('#select_all').prop('checked')).toEqual(true); $('#fileList tr input:checkbox').each(function() { expect($(this).prop('checked')).toEqual(true); }); - expect(_.pluck(FileList.getSelectedFiles(), 'name').length).toEqual(42); + expect(_.pluck(fileList.getSelectedFiles(), 'name').length).toEqual(42); $('#select_all').click(); expect($('#select_all').prop('checked')).toEqual(false); @@ -1214,71 +1223,71 @@ describe('FileList tests', function() { $('#fileList tr input:checkbox').each(function() { expect($(this).prop('checked')).toEqual(false); }); - expect(_.pluck(FileList.getSelectedFiles(), 'name').length).toEqual(0); + expect(_.pluck(fileList.getSelectedFiles(), 'name').length).toEqual(0); }); it('Clicking "select all" then deselecting a file will uncheck "select all"', function() { $('#select_all').click(); expect($('#select_all').prop('checked')).toEqual(true); - var $tr = FileList.findFileEl('One.txt'); + var $tr = fileList.findFileEl('One.txt'); $tr.find('input:checkbox').click(); expect($('#select_all').prop('checked')).toEqual(false); - expect(_.pluck(FileList.getSelectedFiles(), 'name').length).toEqual(3); + expect(_.pluck(fileList.getSelectedFiles(), 'name').length).toEqual(3); }); it('Updates the selection summary when doing a few manipulations with "Select all"', function() { $('#select_all').click(); expect($('#select_all').prop('checked')).toEqual(true); - var $tr = FileList.findFileEl('One.txt'); + var $tr = fileList.findFileEl('One.txt'); // unselect one $tr.find('input:checkbox').click(); expect($('#select_all').prop('checked')).toEqual(false); - expect(_.pluck(FileList.getSelectedFiles(), 'name').length).toEqual(3); + expect(_.pluck(fileList.getSelectedFiles(), 'name').length).toEqual(3); // select all $('#select_all').click(); expect($('#select_all').prop('checked')).toEqual(true); - expect(_.pluck(FileList.getSelectedFiles(), 'name').length).toEqual(4); + expect(_.pluck(fileList.getSelectedFiles(), 'name').length).toEqual(4); // unselect one $tr.find('input:checkbox').click(); expect($('#select_all').prop('checked')).toEqual(false); - expect(_.pluck(FileList.getSelectedFiles(), 'name').length).toEqual(3); + expect(_.pluck(fileList.getSelectedFiles(), 'name').length).toEqual(3); // re-select it $tr.find('input:checkbox').click(); expect($('#select_all').prop('checked')).toEqual(true); - expect(_.pluck(FileList.getSelectedFiles(), 'name').length).toEqual(4); + expect(_.pluck(fileList.getSelectedFiles(), 'name').length).toEqual(4); }); it('Auto-selects files on next page when "select all" is checked', function() { - FileList.setFiles(generateFiles(0, 41)); + fileList.setFiles(generateFiles(0, 41)); $('#select_all').click(); - expect(FileList.$fileList.find('tr input:checkbox:checked').length).toEqual(20); - FileList._nextPage(true); - expect(FileList.$fileList.find('tr input:checkbox:checked').length).toEqual(40); - FileList._nextPage(true); - expect(FileList.$fileList.find('tr input:checkbox:checked').length).toEqual(42); - expect(_.pluck(FileList.getSelectedFiles(), 'name').length).toEqual(42); + expect(fileList.$fileList.find('tr input:checkbox:checked').length).toEqual(20); + fileList._nextPage(true); + expect(fileList.$fileList.find('tr input:checkbox:checked').length).toEqual(40); + fileList._nextPage(true); + expect(fileList.$fileList.find('tr input:checkbox:checked').length).toEqual(42); + expect(_.pluck(fileList.getSelectedFiles(), 'name').length).toEqual(42); }); it('Selecting files updates selection summary', function() { var $summary = $('#headerName a.name>span:first'); expect($summary.text()).toEqual('Name'); - FileList.findFileEl('One.txt').find('input:checkbox').click(); - FileList.findFileEl('Three.pdf').find('input:checkbox').click(); - FileList.findFileEl('somedir').find('input:checkbox').click(); + fileList.findFileEl('One.txt').find('input:checkbox').click(); + fileList.findFileEl('Three.pdf').find('input:checkbox').click(); + fileList.findFileEl('somedir').find('input:checkbox').click(); expect($summary.text()).toEqual('1 folder & 2 files'); }); it('Unselecting files hides selection summary', function() { var $summary = $('#headerName a.name>span:first'); - FileList.findFileEl('One.txt').find('input:checkbox').click().click(); + fileList.findFileEl('One.txt').find('input:checkbox').click().click(); expect($summary.text()).toEqual('Name'); }); it('Select/deselect files shows/hides file actions', function() { var $actions = $('#headerName .selectedActions'); - var $checkbox = FileList.findFileEl('One.txt').find('input:checkbox'); + var $checkbox = fileList.findFileEl('One.txt').find('input:checkbox'); expect($actions.hasClass('hidden')).toEqual(true); $checkbox.click(); expect($actions.hasClass('hidden')).toEqual(false); @@ -1300,31 +1309,31 @@ describe('FileList tests', function() { }, JSON.stringify(data) ]); - FileList.changeDirectory('/'); + fileList.changeDirectory('/'); fakeServer.respond(); expect($('#select_all').prop('checked')).toEqual(false); - expect(_.pluck(FileList.getSelectedFiles(), 'name')).toEqual([]); + expect(_.pluck(fileList.getSelectedFiles(), 'name')).toEqual([]); }); it('getSelectedFiles returns the selected files even when they are on the next page', function() { var selectedFiles; - FileList.setFiles(generateFiles(0, 41)); + fileList.setFiles(generateFiles(0, 41)); $('#select_all').click(); // unselect one to not have the "allFiles" case - FileList.$fileList.find('tr input:checkbox:first').click(); + fileList.$fileList.find('tr input:checkbox:first').click(); // only 20 files visible, must still return all the selected ones - selectedFiles = _.pluck(FileList.getSelectedFiles(), 'name'); + selectedFiles = _.pluck(fileList.getSelectedFiles(), 'name'); expect(selectedFiles.length).toEqual(41); }); describe('Actions', function() { beforeEach(function() { - FileList.findFileEl('One.txt').find('input:checkbox').click(); - FileList.findFileEl('Three.pdf').find('input:checkbox').click(); - FileList.findFileEl('somedir').find('input:checkbox').click(); + fileList.findFileEl('One.txt').find('input:checkbox').click(); + fileList.findFileEl('Three.pdf').find('input:checkbox').click(); + fileList.findFileEl('somedir').find('input:checkbox').click(); }); it('getSelectedFiles returns the selected file data', function() { - var files = FileList.getSelectedFiles(); + var files = fileList.getSelectedFiles(); expect(files.length).toEqual(3); expect(files[0]).toEqual({ id: 1, @@ -1352,8 +1361,8 @@ describe('FileList tests', function() { }); }); it('Removing a file removes it from the selection', function() { - FileList.remove('Three.pdf'); - var files = FileList.getSelectedFiles(); + fileList.remove('Three.pdf'); + var files = fileList.getSelectedFiles(); expect(files.length).toEqual(2); expect(files[0]).toEqual({ id: 1, @@ -1412,10 +1421,10 @@ describe('FileList tests', function() { { 'Content-Type': 'application/json' }, JSON.stringify({status: 'success'}) ); - expect(FileList.findFileEl('One.txt').length).toEqual(0); - expect(FileList.findFileEl('Three.pdf').length).toEqual(0); - expect(FileList.findFileEl('somedir').length).toEqual(0); - expect(FileList.findFileEl('Two.jpg').length).toEqual(1); + expect(fileList.findFileEl('One.txt').length).toEqual(0); + expect(fileList.findFileEl('Three.pdf').length).toEqual(0); + expect(fileList.findFileEl('somedir').length).toEqual(0); + expect(fileList.findFileEl('Two.jpg').length).toEqual(1); }); it('Deletes all files when all selected when "Delete" clicked', function() { var request; @@ -1431,20 +1440,20 @@ describe('FileList tests', function() { { 'Content-Type': 'application/json' }, JSON.stringify({status: 'success'}) ); - expect(FileList.isEmpty).toEqual(true); + expect(fileList.isEmpty).toEqual(true); }); }); }); it('resets the file selection on reload', function() { - FileList.$el.find('#select_all').click(); - FileList.reload(); - expect(FileList.$el.find('#select_all').prop('checked')).toEqual(false); - expect(FileList.getSelectedFiles()).toEqual([]); + fileList.$el.find('#select_all').click(); + fileList.reload(); + expect(fileList.$el.find('#select_all').prop('checked')).toEqual(false); + expect(fileList.getSelectedFiles()).toEqual([]); }); }); describe('Sorting files', function() { it('Sorts by name by default', function() { - FileList.reload(); + fileList.reload(); expect(fakeServer.requests.length).toEqual(1); var url = fakeServer.requests[0].url; var query = OC.parseQueryString(url.substr(url.indexOf('?') + 1)); @@ -1452,7 +1461,7 @@ describe('FileList tests', function() { expect(query.sortdirection).toEqual('asc'); }); it('Reloads file list with a different sort when clicking on column header of unsorted column', function() { - FileList.$el.find('.column-size .columntitle').click(); + fileList.$el.find('.column-size .columntitle').click(); expect(fakeServer.requests.length).toEqual(1); var url = fakeServer.requests[0].url; var query = OC.parseQueryString(url.substr(url.indexOf('?') + 1)); @@ -1460,7 +1469,7 @@ describe('FileList tests', function() { expect(query.sortdirection).toEqual('asc'); }); it('Toggles sort direction when clicking on already sorted column', function() { - FileList.$el.find('.column-name .columntitle').click(); + fileList.$el.find('.column-name .columntitle').click(); expect(fakeServer.requests.length).toEqual(1); var url = fakeServer.requests[0].url; var query = OC.parseQueryString(url.substr(url.indexOf('?') + 1)); @@ -1468,42 +1477,42 @@ describe('FileList tests', function() { expect(query.sortdirection).toEqual('desc'); }); it('Toggles the sort indicator when clicking on a column header', function() { - var ASC_CLASS = FileList.SORT_INDICATOR_ASC_CLASS; - var DESC_CLASS = FileList.SORT_INDICATOR_DESC_CLASS; - FileList.$el.find('.column-size .columntitle').click(); + var ASC_CLASS = fileList.SORT_INDICATOR_ASC_CLASS; + var DESC_CLASS = fileList.SORT_INDICATOR_DESC_CLASS; + fileList.$el.find('.column-size .columntitle').click(); // moves triangle to size column expect( - FileList.$el.find('.column-name .sort-indicator').hasClass(ASC_CLASS + ' ' + DESC_CLASS) + fileList.$el.find('.column-name .sort-indicator').hasClass(ASC_CLASS + ' ' + DESC_CLASS) ).toEqual(false); expect( - FileList.$el.find('.column-size .sort-indicator').hasClass(ASC_CLASS) + fileList.$el.find('.column-size .sort-indicator').hasClass(ASC_CLASS) ).toEqual(true); // click again on size column, reverses direction - FileList.$el.find('.column-size .columntitle').click(); + fileList.$el.find('.column-size .columntitle').click(); expect( - FileList.$el.find('.column-size .sort-indicator').hasClass(DESC_CLASS) + fileList.$el.find('.column-size .sort-indicator').hasClass(DESC_CLASS) ).toEqual(true); // click again on size column, reverses direction - FileList.$el.find('.column-size .columntitle').click(); + fileList.$el.find('.column-size .columntitle').click(); expect( - FileList.$el.find('.column-size .sort-indicator').hasClass(ASC_CLASS) + fileList.$el.find('.column-size .sort-indicator').hasClass(ASC_CLASS) ).toEqual(true); // click on mtime column, moves indicator there - FileList.$el.find('.column-mtime .columntitle').click(); + fileList.$el.find('.column-mtime .columntitle').click(); expect( - FileList.$el.find('.column-size .sort-indicator').hasClass(ASC_CLASS + ' ' + DESC_CLASS) + fileList.$el.find('.column-size .sort-indicator').hasClass(ASC_CLASS + ' ' + DESC_CLASS) ).toEqual(false); expect( - FileList.$el.find('.column-mtime .sort-indicator').hasClass(ASC_CLASS) + fileList.$el.find('.column-mtime .sort-indicator').hasClass(ASC_CLASS) ).toEqual(true); }); it('Uses correct sort comparator when inserting files', function() { - testFiles.sort(FileList.Comparators.size); + testFiles.sort(OCA.Files.FileList.Comparators.size); // this will make it reload the testFiles with the correct sorting - FileList.$el.find('.column-size .columntitle').click(); + fileList.$el.find('.column-size .columntitle').click(); expect(fakeServer.requests.length).toEqual(1); fakeServer.requests[0].respond( 200, @@ -1524,20 +1533,20 @@ describe('FileList tests', function() { size: 40001, etag: '999' }; - FileList.add(newFileData); - expect(FileList.files.length).toEqual(5); - expect(FileList.$fileList.find('tr').length).toEqual(5); - expect(FileList.findFileEl('One.txt').index()).toEqual(0); - expect(FileList.findFileEl('somedir').index()).toEqual(1); - expect(FileList.findFileEl('Two.jpg').index()).toEqual(2); - expect(FileList.findFileEl('new file.txt').index()).toEqual(3); - expect(FileList.findFileEl('Three.pdf').index()).toEqual(4); + fileList.add(newFileData); + expect(fileList.files.length).toEqual(5); + expect(fileList.$fileList.find('tr').length).toEqual(5); + expect(fileList.findFileEl('One.txt').index()).toEqual(0); + expect(fileList.findFileEl('somedir').index()).toEqual(1); + expect(fileList.findFileEl('Two.jpg').index()).toEqual(2); + expect(fileList.findFileEl('new file.txt').index()).toEqual(3); + expect(fileList.findFileEl('Three.pdf').index()).toEqual(4); }); it('Uses correct reversed sort comparator when inserting files', function() { - testFiles.sort(FileList.Comparators.size); + testFiles.sort(OCA.Files.FileList.Comparators.size); testFiles.reverse(); // this will make it reload the testFiles with the correct sorting - FileList.$el.find('.column-size .columntitle').click(); + fileList.$el.find('.column-size .columntitle').click(); expect(fakeServer.requests.length).toEqual(1); fakeServer.requests[0].respond( 200, @@ -1551,7 +1560,7 @@ describe('FileList tests', function() { }) ); // reverse sort - FileList.$el.find('.column-size .columntitle').click(); + fileList.$el.find('.column-size .columntitle').click(); fakeServer.requests[1].respond( 200, { 'Content-Type': 'application/json' }, @@ -1571,14 +1580,14 @@ describe('FileList tests', function() { size: 40001, etag: '999' }; - FileList.add(newFileData); - expect(FileList.files.length).toEqual(5); - expect(FileList.$fileList.find('tr').length).toEqual(5); - expect(FileList.findFileEl('One.txt').index()).toEqual(4); - expect(FileList.findFileEl('somedir').index()).toEqual(3); - expect(FileList.findFileEl('Two.jpg').index()).toEqual(2); - expect(FileList.findFileEl('new file.txt').index()).toEqual(1); - expect(FileList.findFileEl('Three.pdf').index()).toEqual(0); + fileList.add(newFileData); + expect(fileList.files.length).toEqual(5); + expect(fileList.$fileList.find('tr').length).toEqual(5); + expect(fileList.findFileEl('One.txt').index()).toEqual(4); + expect(fileList.findFileEl('somedir').index()).toEqual(3); + expect(fileList.findFileEl('Two.jpg').index()).toEqual(2); + expect(fileList.findFileEl('new file.txt').index()).toEqual(1); + expect(fileList.findFileEl('Three.pdf').index()).toEqual(0); }); }); }); diff --git a/apps/files/tests/js/filesSpec.js b/apps/files/tests/js/filesSpec.js index 7f8848619f5..4f8d5a29318 100644 --- a/apps/files/tests/js/filesSpec.js +++ b/apps/files/tests/js/filesSpec.js @@ -19,8 +19,9 @@ * */ -/* global OC, Files */ -describe('Files tests', function() { +describe('OCA.Files.Files tests', function() { + var Files = OCA.Files.Files; + describe('File name validation', function() { it('Validates correct file names', function() { var fileNames = [ @@ -83,18 +84,6 @@ describe('Files tests', function() { }); }); describe('getDownloadUrl', function() { - var curDirStub; - beforeEach(function() { - curDirStub = sinon.stub(FileList, 'getCurrentDirectory'); - }); - afterEach(function() { - curDirStub.restore(); - }); - it('returns the ajax download URL when only filename specified', function() { - curDirStub.returns('/subdir'); - var url = Files.getDownloadUrl('test file.txt'); - expect(url).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fsubdir&files=test%20file.txt'); - }); it('returns the ajax download URL when filename and dir specified', function() { var url = Files.getDownloadUrl('test file.txt', '/subdir'); expect(url).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fsubdir&files=test%20file.txt'); diff --git a/apps/files/tests/js/filesummarySpec.js b/apps/files/tests/js/filesummarySpec.js index c493700de38..5e39dd1d232 100644 --- a/apps/files/tests/js/filesummarySpec.js +++ b/apps/files/tests/js/filesummarySpec.js @@ -20,7 +20,8 @@ */ /* global FileSummary */ -describe('FileSummary tests', function() { +describe('OCA.Files.FileSummary tests', function() { + var FileSummary = OCA.Files.FileSummary; var $container; beforeEach(function() { diff --git a/apps/files_sharing/css/public.css b/apps/files_sharing/css/public.css index 67d84701946..8abeb8b0340 100644 --- a/apps/files_sharing/css/public.css +++ b/apps/files_sharing/css/public.css @@ -2,6 +2,10 @@ left: 0; } +#filestable { + margin-top: 90px; +} + #preview { background: #fff; text-align: center; diff --git a/apps/files_sharing/js/public.js b/apps/files_sharing/js/public.js index 0b4dec81764..6ee54968e1e 100644 --- a/apps/files_sharing/js/public.js +++ b/apps/files_sharing/js/public.js @@ -9,94 +9,141 @@ */ /* global OC, FileActions, FileList, Files */ +OCA.Sharing = {}; +if (!OCA.Files) { + OCA.Files = {}; +} +OCA.Sharing.PublicApp = { + _initialized: false, -$(document).ready(function() { + initialize: function($el) { + if (this._initialized) { + return; + } + this._initialized = true; + // file list mode ? + if ($el.find('#filestable')) { + this.fileList = new OCA.Files.FileList($el); + } - var mimetype = $('#mimetype').val(); + var mimetype = $('#mimetype').val(); - if (typeof FileActions !== 'undefined') { - // Show file preview if previewer is available, images are already handled by the template - if (mimetype.substr(0, mimetype.indexOf('/')) !== 'image' && $('.publicpreview').length === 0) { - // Trigger default action if not download TODO - var action = FileActions.getDefault(mimetype, 'file', OC.PERMISSION_READ); - if (typeof action !== 'undefined') { - action($('#filename').val()); + if (typeof FileActions !== 'undefined') { + // Show file preview if previewer is available, images are already handled by the template + if (mimetype.substr(0, mimetype.indexOf('/')) !== 'image' && $('.publicpreview').length === 0) { + // Trigger default action if not download TODO + var action = FileActions.getDefault(mimetype, 'file', OC.PERMISSION_READ); + if (typeof action !== 'undefined') { + action($('#filename').val()); + } } } - } - // dynamically load image previews - if (mimetype.substr(0, mimetype.indexOf('/')) === 'image' ) { + // dynamically load image previews + if (mimetype.substr(0, mimetype.indexOf('/')) === 'image' ) { - var params = { - x: $(document).width() * window.devicePixelRatio, - a: 'true', - file: encodeURIComponent($('#dir').val() + $('#filename').val()), - t: $('#sharingToken').val() - }; + var params = { + x: $(document).width() * window.devicePixelRatio, + a: 'true', + file: encodeURIComponent($('#dir').val() + $('#filename').val()), + t: $('#sharingToken').val() + }; - var img = $('<img class="publicpreview">'); - img.attr('src', OC.filePath('files_sharing', 'ajax', 'publicpreview.php') + '?' + OC.buildQueryString(params)); - img.appendTo('#imgframe'); - } + var img = $('<img class="publicpreview">'); + img.attr('src', OC.filePath('files_sharing', 'ajax', 'publicpreview.php') + '?' + OC.buildQueryString(params)); + img.appendTo('#imgframe'); + } - // override since the format is different - if (typeof Files !== 'undefined') { - 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 + if (this.fileList) { + // TODO: move this to a separate PublicFileList class that extends OCA.Files.FileList (+ unit tests) + this.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, + files: filename, + download: null + }; + return OC.filePath('', '', 'public.php') + '?' + OC.buildQueryString(params); }; - 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); - }; + this.fileList.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 + this.fileList.linkTo = function(dir) { + var params = { + service: 'files', + t: $('#sharingToken').val(), + dir: dir + }; + return OC.filePath('', '', 'public.php') + '?' + OC.buildQueryString(params); }; - 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) { - var fileDirectory = ''; - if(typeof data.files[0].relativePath !== 'undefined') { - fileDirectory = data.files[0].relativePath; - } - // Add custom data to the upload handler - data.formData = { - requesttoken: $('#publicUploadRequestToken').val(), - dirToken: $('#dirToken').val(), - subdir: $('input#dir').val(), - file_directory: fileDirectory + this.fileList.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) { + var fileDirectory = ''; + if(typeof data.files[0].relativePath !== 'undefined') { + fileDirectory = data.files[0].relativePath; + } + + // Add custom data to the upload handler + data.formData = { + requesttoken: $('#publicUploadRequestToken').val(), + dirToken: $('#dirToken').val(), + subdir: $('input#dir').val(), + file_directory: fileDirectory + }; + }); + + this.fileActions = _.extend({}, OCA.Files.FileActions); + this.fileActions.registerDefaultActions(this.fileList); + delete this.fileActions.actions.all.Share; + this.fileList.setFileActions(this.fileActions); + + this.fileList.changeDirectory($('#dir').val() || '/', false, true); + + // URL history handling + this.fileList.$el.on('changeDirectory', _.bind(this._onDirectoryChanged, this)); + OC.Util.History.addOnPopStateHandler(_.bind(this._onUrlChanged, this)); + } + + $(document).on('click', '#directLink', function() { + $(this).focus(); + $(this).select(); }); - } - $(document).on('click', '#directLink', function() { - $(this).focus(); - $(this).select(); - }); + // legacy + window.FileList = this.fileList; + }, + _onDirectoryChanged: function(e) { + OC.Util.History.pushState({ + service: 'files', + t: $('#sharingToken').val(), + // arghhhh, why is this not called "dir" !? + path: e.dir + }); + }, + + _onUrlChanged: function(params) { + this.fileList.changeDirectory(params.path || params.dir, false, true); + } +}; + +$(document).ready(function() { + var App = OCA.Sharing.PublicApp; + App.initialize($('#preview')); }); + diff --git a/apps/files_sharing/js/share.js b/apps/files_sharing/js/share.js index 3c7c9239a6c..ac46ab7b9e5 100644 --- a/apps/files_sharing/js/share.js +++ b/apps/files_sharing/js/share.js @@ -11,10 +11,9 @@ /* global OC, t, FileList, FileActions */ $(document).ready(function() { - var disableSharing = $('#disableSharing').data('status'), - sharesLoaded = false; + var sharesLoaded = false; - if (typeof OC.Share !== 'undefined' && typeof FileActions !== 'undefined' && !disableSharing) { + if (typeof OC.Share !== 'undefined' && typeof FileActions !== 'undefined') { var oldCreateRow = FileList._createRow; FileList._createRow = function(fileData) { var tr = oldCreateRow.apply(this, arguments); diff --git a/apps/files_sharing/public.php b/apps/files_sharing/public.php index e17ffc48036..a73d97f4fef 100644 --- a/apps/files_sharing/public.php +++ b/apps/files_sharing/public.php @@ -149,7 +149,7 @@ if (isset($path)) { $freeSpace=OCP\Util::freeSpace($path); $uploadLimit=OCP\Util::uploadLimit(); - $folder = new OCP\Template('files', 'index', ''); + $folder = new OCP\Template('files', 'list', ''); $folder->assign('dir', $getPath); $folder->assign('dirToken', $linkItem['token']); $folder->assign('permissions', OCP\PERMISSION_READ); @@ -162,7 +162,6 @@ 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('disableSharing', true); $folder->assign('trash', false); $tmpl->assign('folder', $folder->fetchPage()); $allowZip = OCP\Config::getSystemValue('allowZipDownload', true); diff --git a/apps/files_trashbin/appinfo/app.php b/apps/files_trashbin/appinfo/app.php index 219c5d6cb7e..06c2e3447fe 100644 --- a/apps/files_trashbin/appinfo/app.php +++ b/apps/files_trashbin/appinfo/app.php @@ -5,11 +5,11 @@ $l = OC_L10N::get('files_trashbin'); \OCA\Files_Trashbin\Trashbin::registerHooks(); \OCA\Files\App::getNavigationManager()->add( - array( - "id" => 'trashbin', - "appname" => 'files_trashbin', - "script" => 'index.php', - "order" => 1, - "name" => $l->t('Deleted files') - ) +array( + "id" => 'trashbin', + "appname" => 'files_trashbin', + "script" => 'index.php', + "order" => 1, + "name" => $l->t('Deleted files') +) ); diff --git a/apps/files_trashbin/index.php b/apps/files_trashbin/index.php index 4c5527822fb..08227b7f322 100644 --- a/apps/files_trashbin/index.php +++ b/apps/files_trashbin/index.php @@ -5,12 +5,8 @@ OCP\User::checkLoggedIn(); $tmpl = new OCP\Template('files_trashbin', 'index', ''); -// TODO: re-enable after making sure the scripts doesn't -// override the files app -/* -OCP\Util::addScript('files_trashbin', 'disableDefaultActions'); OCP\Util::addStyle('files_trashbin', 'trash'); +OCP\Util::addScript('files_trashbin', 'app'); +OCP\Util::addScript('files_trashbin', 'files'); OCP\Util::addScript('files_trashbin', 'filelist'); -OCP\Util::addScript('files_trashbin', 'trash'); - */ $tmpl->printPage(); diff --git a/apps/files_trashbin/js/app.js b/apps/files_trashbin/js/app.js new file mode 100644 index 00000000000..9ab78e7cbb3 --- /dev/null +++ b/apps/files_trashbin/js/app.js @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2014 + * + * This file is licensed under the Affero General Public License version 3 + * or later. + * + * See the COPYING-README file. + * + */ + +OCA.Trashbin = {}; +OCA.Trashbin.App = { + _initialized: false, + + initialize: function($el) { + if (this._initialized) { + return; + } + this._initialized = true; + this.fileList = new OCA.Trashbin.FileList($el); + this.registerFileActions(this.fileList); + }, + + registerFileActions: function(fileList) { + var self = this; + var fileActions = _.extend({}, OCA.Files.FileActions); + fileActions.clear(); + fileActions.register('dir', 'Open', OC.PERMISSION_READ, '', function (filename) { + var dir = fileList.getCurrentDirectory(); + if (dir !== '/') { + dir = dir + '/'; + } + fileList.changeDirectory(dir + filename); + }); + + fileActions.setDefault('dir', 'Open'); + + fileActions.register('all', 'Restore', OC.PERMISSION_READ, OC.imagePath('core', 'actions/history'), function(filename) { + var tr = fileList.findFileEl(filename); + var deleteAction = tr.children("td.date").children(".action.delete"); + deleteAction.removeClass('delete-icon').addClass('progress-icon'); + fileList.disableActions(); + $.post(OC.filePath('files_trashbin', 'ajax', 'undelete.php'), { + files: JSON.stringify([filename]), + dir: fileList.getCurrentDirectory() + }, + _.bind(fileList._removeCallback, fileList) + ); + }, t('files_trashbin', 'Restore')); + + fileActions.register('all', 'Delete', OC.PERMISSION_READ, function() { + return OC.imagePath('core', 'actions/delete'); + }, function(filename) { + $('.tipsy').remove(); + var tr = fileList.findFileEl(filename); + var deleteAction = tr.children("td.date").children(".action.delete"); + deleteAction.removeClass('delete-icon').addClass('progress-icon'); + fileList.disableActions(); + $.post(OC.filePath('files_trashbin', 'ajax', 'delete.php'), { + files: JSON.stringify([filename]), + dir: fileList.getCurrentDirectory() + }, + _.bind(fileList._removeCallback, fileList) + ); + }); + fileList.setFileActions(fileActions); + } +}; + +$(document).ready(function() { + $('#app-content-trashbin').on('show', function() { + var App = OCA.Trashbin.App; + App.initialize($('#app-content-trashbin')); + // force breadcrumb init + // App.fileList.changeDirectory(App.fileList.getCurrentDirectory(), false, true); + }); +}); + diff --git a/apps/files_trashbin/js/disableDefaultActions.js b/apps/files_trashbin/js/disableDefaultActions.js deleted file mode 100644 index 50ceaf4696f..00000000000 --- a/apps/files_trashbin/js/disableDefaultActions.js +++ /dev/null @@ -1,3 +0,0 @@ -/* disable download and sharing actions */ -var disableDownloadActions = true; -var trashBinApp = true; diff --git a/apps/files_trashbin/js/filelist.js b/apps/files_trashbin/js/filelist.js index 00fc7e16b88..d3206958e8b 100644 --- a/apps/files_trashbin/js/filelist.js +++ b/apps/files_trashbin/js/filelist.js @@ -1,8 +1,14 @@ -/* global OC, t, FileList */ +/* + * Copyright (c) 2014 + * + * This file is licensed under the Affero General Public License version 3 + * or later. + * + * See the COPYING-README file. + * + */ (function() { - FileList.appName = t('files_trashbin', 'Deleted files'); - - FileList._deletedRegExp = new RegExp(/^(.+)\.d[0-9]+$/); + var DELETED_REGEXP = new RegExp(/^(.+)\.d[0-9]+$/); /** * Convert a file name in the format filename.d12345 to the real file name. @@ -11,195 +17,236 @@ * @param name file name * @return converted file name */ - FileList.getDeletedFileName = function(name) { + function getDeletedFileName(name) { name = OC.basename(name); - var match = FileList._deletedRegExp.exec(name); + var match = DELETED_REGEXP.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 baseDir = OC.basename(targetDir); - if (baseDir !== '') { - FileList.setPageTitle(FileList.getDeletedFileName(baseDir)); - } + var FileList = function($el) { + this.initialize($el); }; + FileList.prototype = _.extend({}, OCA.Files.FileList.prototype, { + appName: t('files_trashbin', 'Deleted files'), - 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; - }; + initialize: function() { + var result = OCA.Files.FileList.prototype.initialize.apply(this, arguments); + this.$el.find('.undelete').click('click', _.bind(this._onClickRestoreSelected, this)); - 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); - } - }; + this.setSort('mtime', 'desc'); - var oldRenderRow = FileList._renderRow; - FileList._renderRow = 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 oldRenderRow.call(this, fileData, options); - }; + // override crumb URL maker + this.breadcrumb.getCrumbUrl = function(part, index) { + return OC.linkTo('files_trashbin', 'index.php')+"?view=trashbin&dir=" + encodeURIComponent(part.dir); + }; + /** + * Override crumb making to add "Deleted Files" entry + * and convert files with ".d" extensions to a more + * user friendly name. + */ + this.breadcrumb._makeCrumbs = function() { + var parts = OCA.Files.BreadCrumb.prototype._makeCrumbs.apply(this, arguments); + for (var i = 1; i < parts.length; i++) { + parts[i].name = getDeletedFileName(parts[i].name); + } + return parts; + }; - FileList.linkTo = function(dir){ - return OC.linkTo('files_trashbin', 'index.php')+"?dir="+ encodeURIComponent(dir).replace(/%2F/g, '/'); - }; + return result; + }, - FileList.updateEmptyContent = function(){ - var $fileList = $('#fileList'); - var exists = $fileList.find('tr:first').exists(); - $('#emptycontent').toggleClass('hidden', exists); - $('#filestable th').toggleClass('hidden', !exists); - }; + _setCurrentDir: function(targetDir) { + OCA.Files.FileList.prototype._setCurrentDir.apply(this, arguments); - var oldInit = FileList.initialize; - FileList.initialize = function() { - var result = oldInit.apply(this, arguments); - $('.undelete').click('click', FileList._onClickRestoreSelected); - this.setSort('mtime', 'desc'); - return result; - }; - - FileList._removeCallback = function(result) { - if (result.status !== 'success') { - OC.dialogs.alert(result.data.message, t('files_trashbin', 'Error')); - } + var baseDir = OC.basename(targetDir); + if (baseDir !== '') { + this.setPageTitle(getDeletedFileName(baseDir)); + } + }, + + _createRow: function() { + // FIXME: MEGAHACK until we find a better solution + var tr = OCA.Files.FileList.prototype._createRow.apply(this, arguments); + tr.find('td.filesize').remove(); + return tr; + }, + + _renderRow: function(fileData, options) { + options = options || {}; + var dir = this.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 OCA.Files.FileList.prototype._renderRow.call(this, fileData, options); + }, - var files = result.data.success; - var $el; - for (var i = 0; i < files.length; i++) { - $el = FileList.remove(OC.basename(files[i].filename), {updateSummary: false}); - FileList.fileSummary.remove({type: $el.attr('data-type'), size: $el.attr('data-size')}); - } - FileList.fileSummary.update(); - FileList.updateEmptyContent(); - enableActions(); - } + getAjaxUrl: function(action, params) { + var q = ''; + if (params) { + q = '?' + OC.buildQueryString(params); + } + return OC.filePath('files_trashbin', 'ajax', action + '.php') + q; + }, + + linkTo: function(dir){ + return OC.linkTo('files_trashbin', 'index.php')+"?dir="+ encodeURIComponent(dir).replace(/%2F/g, '/'); + }, + + updateEmptyContent: function(){ + var exists = this.$fileList.find('tr:first').exists(); + this.$el.find('#emptycontent').toggleClass('hidden', exists); + this.$el.find('#filestable th').toggleClass('hidden', !exists); + }, + + _removeCallback: function(result) { + if (result.status !== 'success') { + OC.dialogs.alert(result.data.message, t('files_trashbin', 'Error')); + } - FileList._onClickRestoreSelected = function(event) { - event.preventDefault(); - var allFiles = $('#select_all').is(':checked'); - var files = []; - var params = {}; - disableActions(); - if (allFiles) { - FileList.showMask(); - params = { - allfiles: true, - dir: FileList.getCurrentDirectory() - }; - } - else { - files = _.pluck(FileList.getSelectedFiles(), 'name'); + var files = result.data.success; + var $el; 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'); + $el = this.remove(OC.basename(files[i].filename), {updateSummary: false}); + this.fileSummary.remove({type: $el.attr('data-type'), size: $el.attr('data-size')}); } - params = { - files: JSON.stringify(files), - dir: FileList.getCurrentDirectory() - }; - } + this.fileSummary.update(); + this.updateEmptyContent(); + this.enableActions(); + }, - $.post(OC.filePath('files_trashbin', 'ajax', 'undelete.php'), - params, - function(result) { - if (allFiles) { - if (result.status !== 'success') { - OC.dialogs.alert(result.data.message, t('files_trashbin', 'Error')); - } - FileList.hideMask(); - // simply remove all files - FileList.setFiles([]); - enableActions(); - } - else { - FileList._removeCallback(result); - } + _onClickRestoreSelected: function(event) { + event.preventDefault(); + var self = this; + var allFiles = this.$el.find('#select_all').is(':checked'); + var files = []; + var params = {}; + this.disableActions(); + if (allFiles) { + this.showMask(); + params = { + allfiles: true, + dir: this.getCurrentDirectory() + }; } - ); - }; - - FileList._onClickDeleteSelected = function(event) { - event.preventDefault(); - var allFiles = $('#select_all').is(':checked'); - var files = []; - var params = {}; - if (allFiles) { - params = { - allfiles: true, - dir: FileList.getCurrentDirectory() - }; - } - else { - files = _.pluck(FileList.getSelectedFiles(), 'name'); - params = { - files: JSON.stringify(files), - dir: FileList.getCurrentDirectory() - }; - } - - disableActions(); - if (allFiles) { - FileList.showMask(); - } - else { - 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'); + else { + files = _.pluck(this.getSelectedFiles(), 'name'); + for (var i = 0; i < files.length; i++) { + var deleteAction = this.findFileEl(files[i]).children("td.date").children(".action.delete"); + deleteAction.removeClass('delete-icon').addClass('progress-icon'); + } + params = { + files: JSON.stringify(files), + dir: this.getCurrentDirectory() + }; } - } - $.post(OC.filePath('files_trashbin', 'ajax', 'delete.php'), + $.post(OC.filePath('files_trashbin', 'ajax', 'undelete.php'), params, function(result) { if (allFiles) { if (result.status !== 'success') { OC.dialogs.alert(result.data.message, t('files_trashbin', 'Error')); } - FileList.hideMask(); + self.hideMask(); // simply remove all files - FileList.setFiles([]); - enableActions(); + self.setFiles([]); + self.enableActions(); } else { - FileList._removeCallback(result); + self._removeCallback(result); } } - ); - }; + ); + }, - var oldClickFile = FileList._onClickFile; - FileList._onClickFile = function(event) { - var mime = $(this).parent().parent().data('mime'); - if (mime !== 'httpd/unix-directory') { + _onClickDeleteSelected: function(event) { event.preventDefault(); + var self = this; + var allFiles = this.$el.find('#select_all').is(':checked'); + var files = []; + var params = {}; + if (allFiles) { + params = { + allfiles: true, + dir: this.getCurrentDirectory() + }; + } + else { + files = _.pluck(this.getSelectedFiles(), 'name'); + params = { + files: JSON.stringify(files), + dir: this.getCurrentDirectory() + }; + } + + this.disableActions(); + if (allFiles) { + this.showMask(); + } + else { + for (var i = 0; i < files.length; i++) { + var deleteAction = this.findFileEl(files[i]).children("td.date").children(".action.delete"); + deleteAction.removeClass('delete-icon').addClass('progress-icon'); + } + } + + $.post(OC.filePath('files_trashbin', 'ajax', 'delete.php'), + params, + function(result) { + if (allFiles) { + if (result.status !== 'success') { + OC.dialogs.alert(result.data.message, t('files_trashbin', 'Error')); + } + self.hideMask(); + // simply remove all files + self.setFiles([]); + self.enableActions(); + } + else { + self._removeCallback(result); + } + } + ); + }, + + _onClickFile: function(event) { + var mime = $(this).parent().parent().data('mime'); + if (mime !== 'httpd/unix-directory') { + event.preventDefault(); + } + return OCA.Files.FileList.prototype._onClickFile.apply(this, arguments); + }, + + generatePreviewUrl: function(urlSpec) { + return OC.generateUrl('/apps/files_trashbin/ajax/preview.php?') + $.param(urlSpec); + }, + + getDownloadUrl: function(action, params) { + // no downloads + return '#'; + }, + + enableActions: function() { + this.$el.find('.action').css('display', 'inline'); + this.$el.find(':input:checkbox').css('display', 'inline'); + }, + + disableActions: function() { + this.$el.find('.action').css('display', 'none'); + this.$el.find(':input:checkbox').css('display', 'none'); } - return oldClickFile.apply(this, arguments); - }; + }); + + OCA.Trashbin.FileList = FileList; })(); + diff --git a/apps/files_trashbin/js/files.js b/apps/files_trashbin/js/files.js new file mode 100644 index 00000000000..f46b96a40b3 --- /dev/null +++ b/apps/files_trashbin/js/files.js @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2014 + * + * This file is licensed under the Affero General Public License version 3 + * or later. + * + * See the COPYING-README file. + * + */ + +(function() { + + var Files = _.extend({}, OCA.Files.Files, { + updateStorageStatistics: function() { + // no op because the trashbin doesn't have + // storage info like free space / used space + } + + }); + + OCA.Trashbin.Files = Files; +})(); + diff --git a/apps/files_trashbin/js/trash.js b/apps/files_trashbin/js/trash.js deleted file mode 100644 index 5f2436de809..00000000000 --- a/apps/files_trashbin/js/trash.js +++ /dev/null @@ -1,130 +0,0 @@ -/* - * 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, 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; - } - - Files.updateStorageStatistics = function() { - // no op because the trashbin doesn't have - // storage info like free space / used space - }; - - if (typeof FileActions !== 'undefined') { - FileActions.register('all', 'Restore', OC.PERMISSION_READ, OC.imagePath('core', 'actions/history'), function(filename) { - var tr = FileList.findFileEl(filename); - var deleteAction = tr.children("td.date").children(".action.delete"); - deleteAction.removeClass('delete-icon').addClass('progress-icon'); - disableActions(); - $.post(OC.filePath('files_trashbin', 'ajax', 'undelete.php'), { - files: JSON.stringify([filename]), - dir: FileList.getCurrentDirectory() - }, - FileList._removeCallback - ); - }, t('files_trashbin', 'Restore')); - }; - - FileActions.register('all', 'Delete', OC.PERMISSION_READ, function() { - return OC.imagePath('core', 'actions/delete'); - }, function(filename) { - $('.tipsy').remove(); - var tr = FileList.findFileEl(filename); - var deleteAction = tr.children("td.date").children(".action.delete"); - deleteAction.removeClass('delete-icon').addClass('progress-icon'); - disableActions(); - $.post(OC.filePath('files_trashbin', 'ajax', 'delete.php'), { - files: JSON.stringify([filename]), - dir: FileList.getCurrentDirectory() - }, - FileList._removeCallback - ); - }); - - /** - * 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 - }; -}); - -function enableActions() { - $(".action").css("display", "inline"); - $(":input:checkbox").css("display", "inline"); -} - -function disableActions() { - $(".action").css("display", "none"); - $(":input:checkbox").css("display", "none"); -} - diff --git a/apps/files_trashbin/templates/index.php b/apps/files_trashbin/templates/index.php index e90162e4d54..6622c1d8f5f 100644 --- a/apps/files_trashbin/templates/index.php +++ b/apps/files_trashbin/templates/index.php @@ -6,6 +6,8 @@ <div id="emptycontent" class="hidden"><?php p($l->t('Nothing in here. Your trash bin is empty!'))?></div> +<input type="hidden" name="dir" value="" id="dir"> + <table id="filestable"> <thead> <tr> diff --git a/apps/files_trashbin/tests/js/appSpec.js b/apps/files_trashbin/tests/js/appSpec.js new file mode 100644 index 00000000000..ca7d71831f8 --- /dev/null +++ b/apps/files_trashbin/tests/js/appSpec.js @@ -0,0 +1,69 @@ +/** +* ownCloud +* +* @author Vincent Petry +* @copyright 2014 Vincent Petry <pvince81@owncloud.com> +* +* 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 <http://www.gnu.org/licenses/>. +* +*/ + +describe('OCA.Trashbin.App tests', function() { + var App = OCA.Trashbin.App; + + beforeEach(function() { + $('#testArea').append( + '<div id="app-navigation">' + + '<ul><li data-id="files"><a>Files</a></li>' + + '<li data-id="trashbin"><a>Trashbin</a></li>' + + '</div>' + + '<div id="app-content">' + + '<div id="app-content-files" class="hidden">' + + '</div>' + + '<div id="app-content-trashbin" class="hidden">' + + '</div>' + + '</div>' + + '</div>' + ); + App.initialize($('#app-content-trashbin')); + }); + afterEach(function() { + App._initialized = false; + App.fileList = null; + }); + + describe('initialization', function() { + it('creates a custom filelist instance', function() { + App.initialize(); + expect(App.fileList).toBeDefined(); + expect(App.fileList.$el.is('#app-content-trashbin')).toEqual(true); + }); + + it('registers custom file actions', function() { + var fileActions; + App.initialize(); + + fileActions = App.fileList.fileActions; + + expect(fileActions.actions.all).toBeDefined(); + expect(fileActions.actions.all.Restore).toBeDefined(); + expect(fileActions.actions.all.Delete).toBeDefined(); + + expect(fileActions.actions.all.Rename).not.toBeDefined(); + expect(fileActions.actions.all.Download).not.toBeDefined(); + + expect(fileActions.defaults.dir).toEqual('Open'); + }); + }); +}); diff --git a/apps/files_trashbin/tests/js/filelistSpec.js b/apps/files_trashbin/tests/js/filelistSpec.js new file mode 100644 index 00000000000..291b2ffe14c --- /dev/null +++ b/apps/files_trashbin/tests/js/filelistSpec.js @@ -0,0 +1,219 @@ +/** +* ownCloud +* +* @author Vincent Petry +* @copyright 2014 Vincent Petry <pvince81@owncloud.com> +* +* 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 <http://www.gnu.org/licenses/>. +* +*/ + +describe('OCA.Trashbin.FileList tests', function() { + var testFiles, alertStub, notificationStub, fileList; + var FileActions = OCA.Files.FileActions; + + beforeEach(function() { + // init horrible parameters + var $body = $('body'); + $body.append('<input type="hidden" id="dir" value="/"></input>'); + // dummy files table + $body.append('<table id="filestable"></table>'); + + alertStub = sinon.stub(OC.dialogs, 'alert'); + notificationStub = sinon.stub(OC.Notification, 'show'); + + // init parameters and test table elements + $('#testArea').append( + '<div id="app-content-trashbin">' + + '<input type="hidden" id="dir" value="/"></input>' + + '<input type="hidden" id="permissions" value="31"></input>' + + // dummy controls + '<div id="controls">' + + ' <div class="actions creatable"></div>' + + ' <div class="notCreatable"></div>' + + '</div>' + + // dummy table + // TODO: at some point this will be rendered by the fileList class itself! + '<table id="filestable">' + + '<thead><tr><th id="headerName" class="hidden">' + + '<input type="checkbox" id="select_all">' + + '<span class="name">Name</span>' + + '<span class="selectedActions hidden">' + + '<a href class="undelete">Restore</a>' + + '<a href class="delete-selected">Delete</a></span>' + + '</th></tr></thead>' + + '<tbody id="fileList"></tbody>' + + '<tfoot></tfoot>' + + '</table>' + + '<div id="emptycontent">Empty content message</div>' + + '</div>' + ); + + testFiles = [{ + id: 1, + type: 'file', + name: 'One.txt', + mtime: 11111000, + mimetype: 'text/plain', + size: 12, + etag: 'abc' + }, { + id: 2, + type: 'file', + name: 'Two.jpg', + mtime: 22222000, + mimetype: 'image/jpeg', + size: 12049, + etag: 'def', + }, { + id: 3, + type: 'file', + name: 'Three.pdf', + mtime: 33333000, + mimetype: 'application/pdf', + size: 58009, + etag: '123', + }, { + id: 4, + type: 'dir', + mtime: 99999000, + name: 'somedir', + mimetype: 'httpd/unix-directory', + size: 250, + etag: '456' + }]; + + fileList = new OCA.Trashbin.FileList($('#app-content-trashbin')); + OCA.Trashbin.App.registerFileActions(fileList); + }); + afterEach(function() { + testFiles = undefined; + fileList = undefined; + + FileActions.clear(); + $('#dir').remove(); + notificationStub.restore(); + alertStub.restore(); + }); + describe('Rendering rows', function() { + // TODO. test that rows show the correct name but + // have the real file name with the ".d" suffix + // TODO: with and without dir listing + }); + describe('File actions', function() { + describe('Deleting single files', function() { + // TODO: checks ajax call + // TODO: checks spinner + // TODO: remove item after delete + // TODO: bring back item if delete failed + }); + describe('Restoring single files', function() { + // TODO: checks ajax call + // TODO: checks spinner + // TODO: remove item after restore + // TODO: bring back item if restore failed + }); + }); + describe('file previews', function() { + // TODO: check that preview URL is going through files_trashbin + }); + describe('loading file list', function() { + // TODO: check that ajax URL is going through files_trashbin + }); + describe('breadcrumbs', function() { + // TODO: test label + URL + }); + describe('Global Actions', function() { + beforeEach(function() { + fileList.setFiles(testFiles); + fileList.findFileEl('One.txt.d11111').find('input:checkbox').click(); + fileList.findFileEl('Three.pdf.d33333').find('input:checkbox').click(); + fileList.findFileEl('somedir.d99999').find('input:checkbox').click(); + }); + describe('Delete', function() { + // TODO: also test with "allFiles" + it('Deletes selected files when "Delete" clicked', function() { + var request; + $('.selectedActions .delete-selected').click(); + expect(fakeServer.requests.length).toEqual(1); + request = fakeServer.requests[0]; + expect(request.url).toEqual(OC.webroot + '/index.php/apps/files_trashbin/ajax/delete.php'); + expect(OC.parseQueryString(request.requestBody)) + .toEqual({'dir': '/', files: '["One.txt.d11111","Three.pdf.d33333","somedir.d99999"]'}); + fakeServer.requests[0].respond( + 200, + { 'Content-Type': 'application/json' }, + JSON.stringify({status: 'success'}) + ); + expect(fileList.findFileEl('One.txt.d11111').length).toEqual(0); + expect(fileList.findFileEl('Three.pdf.d33333').length).toEqual(0); + expect(fileList.findFileEl('somedir.d99999').length).toEqual(0); + expect(fileList.findFileEl('Two.jpg.d22222').length).toEqual(1); + }); + it('Deletes all files when all selected when "Delete" clicked', function() { + var request; + $('#select_all').click(); + $('.selectedActions .delete-selected').click(); + expect(fakeServer.requests.length).toEqual(1); + request = fakeServer.requests[0]; + expect(request.url).toEqual(OC.webroot + '/index.php/apps/files_trashbin/ajax/delete.php'); + expect(OC.parseQueryString(request.requestBody)) + .toEqual({'dir': '/', allfiles: 'true'}); + fakeServer.requests[0].respond( + 200, + { 'Content-Type': 'application/json' }, + JSON.stringify({status: 'success'}) + ); + expect(fileList.isEmpty).toEqual(true); + }); + }); + describe('Restore', function() { + // TODO: also test with "allFiles" + it('Restores selected files when "Restore" clicked', function() { + var request; + $('.selectedActions .undelete').click(); + expect(fakeServer.requests.length).toEqual(1); + request = fakeServer.requests[0]; + expect(request.url).toEqual(OC.webroot + '/index.php/apps/files_trashbin/ajax/undelete.php'); + expect(OC.parseQueryString(request.requestBody)) + .toEqual({'dir': '/', files: '["One.txt.d11111","Three.pdf.d33333","somedir.d99999"]'}); + fakeServer.requests[0].respond( + 200, + { 'Content-Type': 'application/json' }, + JSON.stringify({status: 'success'}) + ); + expect(fileList.findFileEl('One.txt').length).toEqual(0); + expect(fileList.findFileEl('Three.pdf').length).toEqual(0); + expect(fileList.findFileEl('somedir').length).toEqual(0); + expect(fileList.findFileEl('Two.jpg').length).toEqual(1); + }); + it('Restores all files when all selected when "Restore" clicked', function() { + var request; + $('#select_all').click(); + $('.selectedActions .undelete').click(); + expect(fakeServer.requests.length).toEqual(1); + request = fakeServer.requests[0]; + expect(request.url).toEqual(OC.webroot + '/index.php/apps/files_trashbin/ajax/undelete.php'); + expect(OC.parseQueryString(request.requestBody)) + .toEqual({'dir': '/', allfiles: 'true'}); + fakeServer.requests[0].respond( + 200, + { 'Content-Type': 'application/json' }, + JSON.stringify({status: 'success'}) + ); + expect(fileList.isEmpty).toEqual(true); + }); + }); + }); +}); diff --git a/core/js/js.js b/core/js/js.js index 93f4196f056..38b97590430 100644 --- a/core/js/js.js +++ b/core/js/js.js @@ -1319,6 +1319,114 @@ OC.Util = { }; /** + * Utility class for the history API, + * includes fallback to using the URL hash when + * the browser doesn't support the history API. + */ +OC.Util.History = { + _handlers: [], + + /** + * Push the current URL parameters to the history stack + * and change the visible URL. + * Note: this includes a workaround for IE8/IE9 that uses + * the hash part instead of the search part. + * + * @param params to append to the URL, can be either a string + * or a map + */ + pushState: function(params) { + var strParams; + if (typeof(params) === 'string') { + strParams = params; + } + else { + strParams = OC.buildQueryString(params); + } + if (window.history.pushState) { + var url = location.pathname + '?' + strParams; + window.history.pushState(params, '', url); + } + // use URL hash for IE8 + else { + window.location.hash = '?' + strParams; + // inhibit next onhashchange that just added itself + // to the event queue + this._cancelPop = true; + } + }, + + /** + * Add a popstate handler + * + * @param handler function + */ + addOnPopStateHandler: function(handler) { + this._handlers.push(handler); + }, + + /** + * Parse a query string from the hash part of the URL. + * (workaround for IE8 / IE9) + */ + _parseHashQuery: function() { + var hash = window.location.hash, + pos = hash.indexOf('?'); + if (pos >= 0) { + return hash.substr(pos + 1); + } + return ''; + }, + + _decodeQuery: function(query) { + return query.replace(/\+/g, ' '); + }, + + /** + * Parse the query/search part of the URL. + * Also try and parse it from the URL hash (for IE8) + * + * @return map of parameters + */ + parseUrlQuery: function() { + var query = this._parseHashQuery(), + params; + // try and parse from URL hash first + if (query) { + params = OC.parseQueryString(this._decodeQuery(query)); + } + // else read from query attributes + if (!params) { + params = OC.parseQueryString(this._decodeQuery(location.search)); + } + return params || {}; + }, + + _onPopState: function(e) { + if (this._cancelPop) { + this._cancelPop = false; + return; + } + var params; + if (!this._handlers.length) { + return; + } + params = (e && e.state) || this.parseUrlQuery() || {}; + for (var i = 0; i < this._handlers.length; i++) { + this._handlers[i](params); + } + } +}; + +// fallback to hashchange when no history support +if (window.history.pushState) { + window.onpopstate = _.bind(OC.Util.History._onPopState, OC.Util.History); +} +else { + $(window).on('hashchange', _.bind(OC.Util.History._onPopState, OC.Util.History)); +} + +/** * Get a variable by name * @param {string} name * @return {*} diff --git a/tests/karma.config.js b/tests/karma.config.js index 338e3f868e9..08b49d854e0 100644 --- a/tests/karma.config.js +++ b/tests/karma.config.js @@ -43,7 +43,7 @@ module.exports = function(config) { return apps; */ // other apps tests don't run yet... needs further research / clean up - return ['files']; + return ['files', 'files_trashbin']; } // respect NOCOVERAGE env variable |