[Pkg-owncloud-commits] [owncloud] 147/172: Fixed many issues, clean up

David Prévot taffit at moszumanska.debian.org
Sun May 18 20:09:51 UTC 2014


This is an automated email from the git hooks/post-receive script.

taffit pushed a commit to branch master
in repository owncloud.

commit 6fd084243b65a556d4775209ba3916145ef5912a
Author: Vincent Petry <pvince81 at owncloud.com>
Date:   Mon May 12 19:54:20 2014 +0200

    Fixed many issues, clean up
    
    - fixed upload and storage statistics
    - fixed infinite scroll to use the correct contain for scroll detection
    - fixed unit test that sometimes fail for rename case
    - controls are now sticky again
    - fixed selection overlay to be aligned with the table
    - fixed "select all" checkbox that had id conflicts
    - fixed public page
    - fixed global actions permissions detection
    - fix when URL contains an invalid view id
    - viewer mode now hides the sidebar (ex: text editor)
    - added unit tests for trashbin
    - clean up storage info in template (most is retrieved via ajax call now)
---
 apps/files/css/files.css                     |  45 ++++++---
 apps/files/index.php                         |   5 +
 apps/files/js/app.js                         |  26 ++++-
 apps/files/js/breadcrumb.js                  |  10 +-
 apps/files/js/file-upload.js                 |   3 -
 apps/files/js/fileactions.js                 |   2 +
 apps/files/js/filelist.js                    | 114 +++++++++++++++-------
 apps/files/js/files.js                       |  72 ++++++--------
 apps/files/js/keyboardshortcuts.js           |   3 +-
 apps/files/js/navigation.js                  |  21 +++-
 apps/files/list.php                          |  18 +---
 apps/files/templates/index.php               |   1 -
 apps/files/templates/list.php                |  21 ++--
 apps/files/tests/js/appSpec.js               |  81 +++++++++++++---
 apps/files/tests/js/fileactionsSpec.js       |  15 ++-
 apps/files/tests/js/filelistSpec.js          | 104 ++++++++++++--------
 apps/files_sharing/css/public.css            |   8 --
 apps/files_sharing/js/public.js              |  26 ++++-
 apps/files_sharing/js/share.js               |  13 ++-
 apps/files_sharing/public.php                |   2 +-
 apps/files_trashbin/appinfo/app.php          |   2 +-
 apps/files_trashbin/css/trash.css            |  13 ++-
 apps/files_trashbin/js/app.js                |   8 +-
 apps/files_trashbin/js/filelist.js           |  30 ++++--
 apps/files_trashbin/js/files.js              |  23 -----
 apps/files_trashbin/{index.php => list.php}  |   1 -
 apps/files_trashbin/templates/index.php      |   4 +-
 apps/files_trashbin/tests/js/filelistSpec.js | 140 ++++++++++++++++++++++-----
 core/css/styles.css                          |   5 +
 29 files changed, 540 insertions(+), 276 deletions(-)

diff --git a/apps/files/css/files.css b/apps/files/css/files.css
index ba299ea..009cb35 100644
--- a/apps/files/css/files.css
+++ b/apps/files/css/files.css
@@ -3,10 +3,11 @@
  See the COPYING-README file. */
 
 /* FILE MENU */
-.actions { padding:5px; height:32px; width: 100%; }
+.actions { padding:5px; height:32px; display: inline-block; float: left; }
 .actions input, .actions button, .actions .button { margin:0; float:left; }
 .actions .button a { color: #555; }
 .actions .button a:hover, .actions .button a:active { color: #333; }
+.actions.hidden { display: none; }
 
 #new {
 	z-index: 1010;
@@ -70,10 +71,12 @@
 
 /* FILE TABLE */
 
-.app-files #filestable {
+#filestable {
 	position: relative;
+	top: 44px;
 	width: 100%;
 }
+
 /* make sure there's enough room for the file actions */
 #body-user #filestable {
 	min-width: 688px; /* 768 (mobile break) - 80 (nav width) */
@@ -83,22 +86,33 @@
 }
 
 #filestable tbody tr { background-color:#fff; height:51px; }
+
+.app-files #app-content {
+	position: relative;
+}
+
 /**
  * Override global #controls styles
  * to be more flexible / relative
  */
-.app-files #controls {
-	position: static;
-	left: auto;
-	top: auto;
+#body-user .app-files #controls {
+	left: 310px; /* main nav bar + sidebar */
+	position: fixed;
+	padding-left: 0px;
+}
+
+/* this is mostly for file viewer apps, text editor, etc */
+#body-user .app-files.no-sidebar #controls {
+	left: 0px;
+	padding-left: 80px; /* main nav bar */
 }
 
 .app-files #app-navigation {
-	width: 150px;
+	width: 230px;
 }
 
 .app-files #app-settings {
-	width: 149px; /* DUH */
+	width: 229px; /* DUH */
 }
 
 .app-files #app-settings input {
@@ -205,9 +219,7 @@ table.multiselect thead {
 	z-index: 10;
 	-moz-box-sizing: border-box;
 	box-sizing: border-box;
-	left: 0;
-	padding-left: 80px;
-	width: 100%;
+	left: 310px; /* main nav bar + sidebar */
 }
 
 table.multiselect thead th {
@@ -294,7 +306,7 @@ table td.filename form { font-size:14px; margin-left:48px; margin-right:48px; }
 
 /* Use label to have bigger clickable size for checkbox */
 #fileList tr td.filename>input[type="checkbox"] + label,
-#select_all + label {
+.select-all + label {
 	height: 50px;
 	position: absolute;
 	width: 50px;
@@ -308,10 +320,10 @@ table td.filename form { font-size:14px; margin-left:48px; margin-right:48px; }
 #fileList tr td.filename>input[type="checkbox"] + label {
 	left: 0;
 }
-#select_all + label {
+.select-all + label {
 	top: 0;
 }
-#select_all {
+.select-all {
 	position: absolute;
 	top: 18px;
 	left: 18px;
@@ -359,6 +371,9 @@ a.action>img { max-height:16px; max-width:16px; vertical-align:text-bottom; }
 	display: inline;
 	padding: 17px 5px;
 }
+.selectedActions a.hidden {
+	display: none;
+}
 .selectedActions a img {
 	position:relative;
 	top:5px;
@@ -434,7 +449,7 @@ table.dragshadow td.size {
 }
 .mask {
 	z-index: 50;
-	position: fixed;
+	position: absolute;
 	top: 0;
 	left: 0;
 	right: 0;
diff --git a/apps/files/index.php b/apps/files/index.php
index 69fa1c1..9d4007d 100644
--- a/apps/files/index.php
+++ b/apps/files/index.php
@@ -62,6 +62,9 @@ $user = OC_User::getUser();
 
 $config = \OC::$server->getConfig();
 
+// mostly for the home storage's free space
+$dirInfo = \OC\Files\Filesystem::getFileInfo('/', false);
+$storageInfo=OC_Helper::getStorageInfo('/', $dirInfo);
 // if the encryption app is disabled, than everything is fine (INIT_SUCCESSFUL status code)
 $encryptionInitStatus = 2;
 if (OC_App::isEnabled('files_encryption')) {
@@ -107,6 +110,7 @@ OCP\Util::addscript('files', 'files');
 OCP\Util::addscript('files', 'navigation');
 OCP\Util::addscript('files', 'keyboardshortcuts');
 $tmpl = new OCP\Template('files', 'index', 'user');
+$tmpl->assign('usedSpacePercent', (int)$storageInfo['relative']);
 $tmpl->assign('isPublic', false);
 $tmpl->assign("encryptedFiles", \OCP\Util::encryptedFiles());
 $tmpl->assign("mailNotificationEnabled", $config->getAppValue('core', 'shareapi_allow_mail_notification', 'yes'));
@@ -114,5 +118,6 @@ $tmpl->assign("allowShareWithLink", $config->getAppValue('core', 'shareapi_allow
 $tmpl->assign("encryptionInitStatus", $encryptionInitStatus);
 $tmpl->assign('appNavigation', $nav);
 $tmpl->assign('appContents', $contentItems);
+$tmpl->assign('allowZipDownload', intval(OCP\Config::getSystemValue('allowZipDownload', true)));
 
 $tmpl->printPage();
diff --git a/apps/files/js/app.js b/apps/files/js/app.js
index 6cdb339..9155fb3 100644
--- a/apps/files/js/app.js
+++ b/apps/files/js/app.js
@@ -11,6 +11,7 @@
  *
  */
 
+/* global dragOptions, folderDropOptions */
 (function() {
 
 	if (!OCA.Files) {
@@ -24,11 +25,16 @@
 			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.fileList = new OCA.Files.FileList(
+				$('#app-content-files'), {
+					scrollContainer: $('#app-content'),
+					dragOptions: dragOptions,
+					folderDropOptions: folderDropOptions
+				}
+			);
 			this.files.initialize();
 			this.fileActions.registerDefaultActions(this.fileList);
 			this.fileList.setFileActions(this.fileActions);
@@ -58,7 +64,8 @@
 			OC.Util.History.addOnPopStateHandler(_.bind(this._onPopState, this));
 
 			// detect when app changed their current directory
-			$('#app-content>div').on('changeDirectory', _.bind(this._onDirectoryChanged, this));
+			$('#app-content').delegate('>div', 'changeDirectory', _.bind(this._onDirectoryChanged, this));
+			$('#app-content').delegate('>div', 'changeViewerMode', _.bind(this._onChangeViewerMode, this));
 
 			$('#app-navigation').on('itemChanged', _.bind(this._onNavigationChanged, this));
 		},
@@ -88,6 +95,16 @@
 		},
 
 		/**
+		 * Event handler for when an app notifies that it needs space
+		 * for viewer mode.
+		 */
+		_onChangeViewerMode: function(e) {
+			var state = !!e.viewerModeEnabled;
+			$('#app-navigation').toggleClass('hidden', state);
+			$('.app-files').toggleClass('viewer-mode no-sidebar', state);
+		},
+
+		/**
 		 * Event handler for when the URL changed
 		 */
 		_onPopState: function(params) {
@@ -96,6 +113,9 @@
 				view: 'files'
 			}, params);
 			var lastId = this.navigation.getActiveItem();
+			if (!this.navigation.itemExists(params.view)) {
+				params.view = 'files';
+			}
 			this.navigation.setActiveItem(params.view, {silent: true});
 			if (lastId !== this.navigation.getActiveItem()) {
 				this.navigation.getActiveContainer().trigger(new $.Event('show'));
diff --git a/apps/files/js/breadcrumb.js b/apps/files/js/breadcrumb.js
index 1a77481..c017d71 100644
--- a/apps/files/js/breadcrumb.js
+++ b/apps/files/js/breadcrumb.js
@@ -159,7 +159,11 @@
 			this.totalWidth = 64;
 			// FIXME: this class should not know about global elements
 			if ( $('#navigation').length ) {
-				this.totalWidth += $('#navigation').get(0).offsetWidth;
+				this.totalWidth += $('#navigation').outerWidth();
+			}
+
+			if ( $('#app-navigation').length && !$('#app-navigation').hasClass('hidden')) {
+				this.totalWidth += $('#app-navigation').outerWidth();
 			}
 			this.hiddenBreadcrumbs = 0;
 
@@ -167,8 +171,8 @@
 				this.totalWidth += $(this.breadcrumbs[i]).get(0).offsetWidth;
 			}
 
-			$.each($('#controls .actions>div'), function(index, action) {
-				self.totalWidth += $(action).get(0).offsetWidth;
+			$.each($('#controls .actions'), function(index, action) {
+				self.totalWidth += $(action).outerWidth();
 			});
 
 		},
diff --git a/apps/files/js/file-upload.js b/apps/files/js/file-upload.js
index 0ca2852..6b0ca79 100644
--- a/apps/files/js/file-upload.js
+++ b/apps/files/js/file-upload.js
@@ -460,7 +460,6 @@ OC.Upload = {
 
 					$('#uploadprogresswrapper input.stop').fadeOut();
 					$('#uploadprogressbar').fadeOut();
-					Files.updateStorageStatistics();
 				});
 				fileupload.on('fileuploadfail', function(e, data) {
 					OC.Upload.log('progress handle fileuploadfail', e, data);
@@ -471,8 +470,6 @@ OC.Upload = {
 					}
 				});
 
-			} else {
-				console.log('skipping file progress because your browser is broken');
 			}
 		}
 
diff --git a/apps/files/js/fileactions.js b/apps/files/js/fileactions.js
index 7be5998..b9cd981 100644
--- a/apps/files/js/fileactions.js
+++ b/apps/files/js/fileactions.js
@@ -209,6 +209,8 @@
 		 * Register the actions that are used by default for the files app.
 		 */
 		registerDefaultActions: function(fileList) {
+			// TODO: try to find a way to not make it depend on fileList,
+			// maybe get a handler or listener to trigger events on
 			this.register('all', 'Delete', OC.PERMISSION_DELETE, function () {
 				return OC.imagePath('core', 'actions/delete');
 			}, function (filename) {
diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js
index de34d93..38766e2 100644
--- a/apps/files/js/filelist.js
+++ b/apps/files/js/filelist.js
@@ -8,21 +8,20 @@
  *
  */
 
-/* global Files */
-/* global dragOptions, folderDropOptions */
 (function() {
 	/**
 	 * The FileList class manages a file list view.
 	 * A file list view consists of a controls bar and
 	 * a file list table.
 	 */
-	var FileList = function($el) {
-		this.initialize($el);
+	var FileList = function($el, options) {
+		this.initialize($el, options);
 	};
 	FileList.prototype = {
 		SORT_INDICATOR_ASC_CLASS: 'icon-triangle-s',
 		SORT_INDICATOR_DESC_CLASS: 'icon-triangle-n',
 
+		id: 'files',
 		appName: t('files', 'Files'),
 		isEmpty: true,
 		useUndo:true,
@@ -95,16 +94,35 @@
 		 */
 		_currentDirectory: null,
 
+		_dragOptions: null,
+		_folderDropOptions: null,
+
 		/**
 		 * Initialize the file list and its components
+		 *
+		 * @param $el container element with existing markup for the #controls
+		 * and a table
+		 * @param options map of options, see other parameters
+		 * @param scrollContainer scrollable container, defaults to $(window)
+		 * @param dragOptions drag options, disabled by default
+		 * @param folderDropOptions folder drop options, disabled by default
 		 */
-		initialize: function($el) {
+		initialize: function($el, options) {
 			var self = this;
+			options = options || {};
 			if (this.initialized) {
 				return;
 			}
 
+			if (options.dragOptions) {
+				this._dragOptions = options.dragOptions;
+			}
+			if (options.folderDropOptions) {
+				this._folderDropOptions = options.folderDropOptions;
+			}
+
 			this.$el = $el;
+			this.$container = options.scrollContainer || $(window);
 			this.$table = $el.find('table:first');
 			this.$fileList = $el.find('#fileList');
 			this.fileActions = OCA.Files.FileActions;
@@ -116,13 +134,17 @@
 
 			this.setSort('name', 'asc');
 
-			this.breadcrumb = new OCA.Files.BreadCrumb({
+			var breadcrumbOptions = {
 				onClick: _.bind(this._onClickBreadCrumb, this),
-				onDrop: _.bind(this._onDropOnBreadCrumb, this),
-				getCrumbUrl: function(part, index) {
+				getCrumbUrl: function(part) {
 					return self.linkTo(part.dir);
 				}
-			});
+			};
+			// if dropping on folders is allowed, then also allow on breadcrumbs
+			if (this._folderDropOptions) {
+				breadcrumbOptions.onDrop = _.bind(this._onDropOnBreadCrumb, this);
+			}
+			this.breadcrumb = new OCA.Files.BreadCrumb(breadcrumbOptions);
 
 			this.$el.find('#controls').prepend(this.breadcrumb.$el);
 
@@ -137,14 +159,13 @@
 			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('.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);});
+			this.$container.on('scroll', _.bind(this._onScroll, this));
 		},
 
 		/**
@@ -182,7 +203,7 @@
 				delete this._selectedFiles[$tr.data('id')];
 				this._selectionSummary.remove(data);
 			}
-			this.$el.find('#select_all').prop('checked', this._selectionSummary.getTotal() === this.files.length);
+			this.$el.find('.select-all').prop('checked', this._selectionSummary.getTotal() === this.files.length);
 		},
 
 		/**
@@ -327,8 +348,12 @@
 			}
 		},
 
+		/**
+		 * Event handler for when scrolling the list container.
+		 * This appends/renders the next page of entries when reaching the bottom.
+		 */
 		_onScroll: function(e) {
-			if ($(window).scrollTop() + $(window).height() > $(document).height() - 500) {
+			if (this.$container.scrollTop() + this.$container.height() > this.$el.height() - 100) {
 				this._nextPage(true);
 			}
 		},
@@ -460,7 +485,7 @@
 			this.$fileList.empty();
 
 			// clear "Select all" checkbox
-			this.$el.find('#select_all').prop('checked', false);
+			this.$el.find('.select-all').prop('checked', false);
 
 			this.isEmpty = this.files.length === 0;
 			this._nextPage();
@@ -469,10 +494,6 @@
 
 			this.updateEmptyContent();
 			this.$fileList.trigger(jQuery.Event("fileActionsReady"));
-			// "Files" might not be loaded in extending apps
-			if (window.Files) {
-				Files.setupDragAndDrop();
-			}
 
 			this.fileSummary.calculate(filesArray);
 
@@ -540,7 +561,8 @@
 			else {
 				linkUrl = this.getDownloadUrl(name, this.getCurrentDirectory());
 			}
-			td.append('<input id="select-' + fileData.id + '" type="checkbox" /><label for="select-' + fileData.id + '"></label>');
+			td.append('<input id="select-' + this.id + '-' + fileData.id +
+				'" type="checkbox" /><label for="select-' + this.id + '-' + fileData.id + '"></label>');
 			var linkElem = $('<a></a>').attr({
 				"class": "name",
 				"href": linkUrl
@@ -694,12 +716,12 @@
 
 			// TODO: move dragging to FileActions ?
 			// enable drag only for deletable files
-			if (permissions & OC.PERMISSION_DELETE) {
-				filenameTd.draggable(dragOptions);
+			if (this._dragOptions && permissions & OC.PERMISSION_DELETE) {
+				filenameTd.draggable(this._dragOptions);
 			}
 			// allow dropping on folders
-			if (fileData.type === 'dir') {
-				filenameTd.droppable(folderDropOptions);
+			if (this._folderDropOptions && fileData.type === 'dir') {
+				filenameTd.droppable(this._folderDropOptions);
 			}
 
 			if (options.hidden) {
@@ -780,8 +802,7 @@
 		 * @param changeUrl true to also update the URL, false otherwise (default)
 		 */
 		_setCurrentDir: function(targetDir, changeUrl) {
-			var url,
-				previousDir = this.getCurrentDirectory(),
+			var previousDir = this.getCurrentDirectory(),
 				baseDir = OC.basename(targetDir);
 
 			if (baseDir !== '') {
@@ -832,7 +853,7 @@
 			var self = this;
 			this._selectedFiles = {};
 			this._selectionSummary.clear();
-			this.$el.find('#select_all').prop('checked', false);
+			this.$el.find('.select-all').prop('checked', false);
 			this.showMask();
 			if (this._reloadCall) {
 				this._reloadCall.abort();
@@ -873,7 +894,7 @@
 
 			// TODO: should rather return upload file size through
 			// the files list ajax call
-			Files.updateStorageStatistics(true);
+			this.updateStorageStatistics(true);
 
 			if (result.data.permissions) {
 				this.setDirectoryPermissions(result.data.permissions);
@@ -882,12 +903,16 @@
 			this.setFiles(result.data.files);
 		},
 
+		updateStorageStatistics: function(force) {
+			OCA.Files.Files.updateStorageStatistics(this.getCurrentDirectory(), force);
+		},
+
 		getAjaxUrl: function(action, params) {
-			return Files.getAjaxUrl(action, params);
+			return OCA.Files.Files.getAjaxUrl(action, params);
 		},
 
 		getDownloadUrl: function(files, dir) {
-			return Files.getDownloadUrl(files, dir || this.getCurrentDirectory());
+			return OCA.Files.Files.getDownloadUrl(files, dir || this.getCurrentDirectory());
 		},
 
 		/**
@@ -994,6 +1019,7 @@
 		setViewerMode: function(show){
 			this.showActions(!show);
 			this.$el.find('#filestable').toggleClass('hidden', show);
+			this.$el.trigger(new $.Event('changeViewerMode', {viewerModeEnabled: show}));
 		},
 		/**
 		 * Removes a file entry from the list
@@ -1014,7 +1040,7 @@
 				this._selectFileEl(fileEl, false);
 				this.updateSelectionSummary();
 			}
-			if (fileEl.data('permissions') & OC.PERMISSION_DELETE) {
+			if (this._dragOptions && (fileEl.data('permissions') & OC.PERMISSION_DELETE)) {
 				// file is only draggable when delete permissions are set
 				fileEl.find('td.filename').draggable('destroy');
 			}
@@ -1109,7 +1135,8 @@
 							OC.dialogs.alert(t('files', 'Error moving file'), t('files', 'Error'));
 						}
 						$td.css('background-image', oldBackgroundImage);
-				});
+					}
+				);
 			});
 
 		},
@@ -1144,7 +1171,7 @@
 				var filename = input.val();
 				if (filename !== oldname) {
 					// Files.isFileNameValid(filename) throws an exception itself
-					Files.isFileNameValid(filename);
+					OCA.Files.Files.isFileNameValid(filename);
 					if (self.inList(filename)) {
 						throw t('files', '{new_name} already exists', {new_name: filename});
 					}
@@ -1299,7 +1326,7 @@
 							self.updateEmptyContent();
 							self.fileSummary.update();
 							self.updateSelectionSummary();
-							Files.updateStorageStatistics();
+							self.updateStorageStatistics();
 						} else {
 							if (result.status === 'error' && result.data.message) {
 								OC.Notification.show(result.data.message);
@@ -1406,6 +1433,7 @@
 		 */
 		updateSelectionSummary: function() {
 			var summary = this._selectionSummary.summary;
+			var canDelete;
 			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'));
@@ -1414,6 +1442,7 @@
 				this.$el.find('.selectedActions').addClass('hidden');
 			}
 			else {
+				canDelete = (this.getDirectoryPermissions() & OC.PERMISSION_DELETE);
 				this.$el.find('.selectedActions').removeClass('hidden');
 				this.$el.find('#headerSize a>span:first').text(OC.Util.humanFileSize(summary.totalSize));
 				var selection = '';
@@ -1429,6 +1458,7 @@
 				this.$el.find('#headerName a.name>span:first').text(selection);
 				this.$el.find('#modified a>span:first').text('');
 				this.$el.find('table').addClass('multiselect');
+				this.$el.find('.delete-selected').toggleClass('hidden', !canDelete);
 			}
 		},
 
@@ -1437,7 +1467,7 @@
 		 * @return true if all files are selected, false otherwise
 		 */
 		isAllSelected: function() {
-			return this.$el.find('#select_all').prop('checked');
+			return this.$el.find('.select-all').prop('checked');
 		},
 
 		/**
@@ -1475,7 +1505,10 @@
 			}
 			return name;
 		},
-	
+
+		/**
+		 * Setup file upload events related to the file-upload plugin
+		 */
 		setupUploadEvents: function() {
 			var self = this;
 
@@ -1486,6 +1519,11 @@
 				OC.Upload.log('filelist handle fileuploaddrop', e, data);
 
 				var dropTarget = $(e.originalEvent.target).closest('tr, .crumb');
+				// check if dropped inside this list at all
+				if (dropTarget && !self.$el.has(dropTarget).length) {
+					return false;
+				}
+
 				if (dropTarget && (dropTarget.data('type') === 'dir' || dropTarget.hasClass('crumb'))) { // drag&drop upload to folder
 
 					// remember as context
@@ -1515,7 +1553,7 @@
 					};
 				} else {
 					// cancel uploads to current dir if no permission
-					var isCreatable = (this.getDirectoryPermissions() & OC.PERMISSION_CREATE) !== 0;
+					var isCreatable = (self.getDirectoryPermissions() & OC.PERMISSION_CREATE) !== 0;
 					if (!isCreatable) {
 						return false;
 					}
@@ -1655,6 +1693,7 @@
 					uploadText.fadeOut();
 					uploadText.attr('currentUploads', 0);
 				}
+				self.updateStorageStatistics();
 			});
 			fileUploadStart.on('fileuploadfail', function(e, data) {
 				OC.Upload.log('filelist handle fileuploadfail', e, data);
@@ -1668,6 +1707,7 @@
 					uploadText.fadeOut();
 					uploadText.attr('currentUploads', 0);
 				}
+				self.updateStorageStatistics();
 			});
 
 		}
diff --git a/apps/files/js/files.js b/apps/files/js/files.js
index 9ab8d0f..81588c2 100644
--- a/apps/files/js/files.js
+++ b/apps/files/js/files.js
@@ -15,13 +15,8 @@
 (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;
+		_updateStorageStatistics: function(currentDir) {
+			var state = Files.updateStorageStatistics;
 			if (state.dir){
 				if (state.dir === currentDir) {
 					return;
@@ -36,20 +31,26 @@
 				Files.updateMaxUploadFilesize(response);
 			});
 		},
-		updateStorageStatistics: function(force) {
+		/**
+		 * Update storage statistics such as free space, max upload,
+		 * etc based on the given directory.
+		 *
+		 * Note this function is debounced to avoid making too
+		 * many ajax calls in a row.
+		 *
+		 * @param dir directory
+		 * @param force whether to force retrieving
+		 */
+		updateStorageStatistics: function(dir, force) {
 			if (!OC.currentUser) {
 				return;
 			}
 
-			// debounce to prevent calling too often
-			if (Files._updateStorageStatisticsTimeout) {
-				clearTimeout(Files._updateStorageStatisticsTimeout);
-			}
 			if (force) {
-				Files._updateStorageStatistics();
+				Files._updateStorageStatistics(dir);
 			}
 			else {
-				Files._updateStorageStatisticsTimeout = setTimeout(Files._updateStorageStatistics, 250);
+				Files._updateStorageStatisticsDebounced(dir);
 			}
 		},
 
@@ -149,24 +150,6 @@
 			}
 		},
 
-		// 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);
-				}
-			});
-
-			$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
@@ -243,9 +226,7 @@
 		initialize: function() {
 			Files.getMimeIcon.cache = {};
 			Files.displayEncryptionWarning();
-			Files.bindKeyboardShortcuts(document, jQuery);
-
-			Files.setupDragAndDrop();
+			Files.bindKeyboardShortcuts(document, $);
 
 			// TODO: move file list related code (upload) to OCA.Files.FileList
 			$('#file_action_panel').attr('activeAction', false);
@@ -259,7 +240,6 @@
 			// Trigger cancelling of file upload
 			$('#uploadprogresswrapper .stop').on('click', function() {
 				OC.Upload.cancelUploads();
-				OCA.Files.FileList.updateSelectionSummary();
 			});
 
 			// drag&drop support using jquery.fileupload
@@ -275,18 +255,19 @@
 			setTimeout(Files.displayStorageWarnings, 100);
 			OC.Notification.setDefault(Files.displayStorageWarnings);
 
-			// only possible at the moment if user is logged in
-			if (OC.currentUser) {
+			// only possible at the moment if user is logged in or the files app is loaded
+			if (OC.currentUser && OCA.Files.App) {
 				// start on load - we ask the server every 5 minutes
 				var updateStorageStatisticsInterval = 5*60*1000;
-				var updateStorageStatisticsIntervalId = setInterval(Files.updateStorageStatistics, updateStorageStatisticsInterval);
+				var updateStorageStatisticsIntervalId = setInterval(OCA.Files.App.fileList.updateStorageStatistics, updateStorageStatisticsInterval);
 
+				// TODO: this should also stop when switching to another view
 				// 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);
+								updateStorageStatisticsIntervalId = setInterval(OCA.Files.App.fileList.updateStorageStatistics, updateStorageStatisticsInterval);
 							}
 						},
 						'hide.visibility': function() {
@@ -314,6 +295,7 @@
 		}
 	}
 
+	Files._updateStorageStatisticsDebounced = _.debounce(Files._updateStorageStatistics, 250);
 	OCA.Files.Files = Files;
 })();
 
@@ -349,7 +331,9 @@ function scanFiles(force, dir, users) {
 	scannerEventSource.listen('done',function(count) {
 		scanFiles.scanning=false;
 		console.log('done after ' + count + ' files');
-		OCA.Files.Files.updateStorageStatistics();
+		if (OCA.Files.App) {
+			OCA.Files.App.fileList.updateStorageStatistics(true);
+		}
 	});
 	scannerEventSource.listen('user',function(user) {
 		console.log('scanning files for ' + user);
@@ -360,7 +344,7 @@ scanFiles.scanning=false;
 // TODO: move to FileList
 var createDragShadow = function(event) {
 	//select dragged file
-	var FileList = OCA.Files.FileList;
+	var FileList = OCA.Files.App.fileList;
 	var isDragSelected = $(event.target).parents('tr').find('td input:first').prop('checked');
 	if (!isDragSelected) {
 		//select dragged file
@@ -395,7 +379,7 @@ var createDragShadow = function(event) {
 			newtr.find('td.filename').attr('style','background-image:url('+OC.imagePath('core', 'filetypes/folder.png')+')');
 		} else {
 			var path = dir + '/' + elem.name;
-			OCA.Files.App.fileList.lazyLoadPreview(path, elem.mime, function(previewpath) {
+			OCA.Files.App.files.lazyLoadPreview(path, elem.mime, function(previewpath) {
 				newtr.find('td.filename').attr('style','background-image:url('+previewpath+')');
 			}, null, null, elem.etag);
 		}
@@ -446,7 +430,7 @@ var folderDropOptions = {
 	hoverClass: "canDrop",
 	drop: function( event, ui ) {
 		// don't allow moving a file into a selected folder
-		var FileList = OCA.Files.FileList;
+		var FileList = OCA.Files.App.fileList;
 		if ($(event.target).parents('tr').find('td input:first').prop('checked') === true) {
 			return false;
 		}
diff --git a/apps/files/js/keyboardshortcuts.js b/apps/files/js/keyboardshortcuts.js
index 9d6c3ae..b2f2cd0 100644
--- a/apps/files/js/keyboardshortcuts.js
+++ b/apps/files/js/keyboardshortcuts.js
@@ -12,7 +12,6 @@
  * enter: open file/folder
  * delete/backspace: delete file/folder
  *****************************/
-var Files = Files || {};
 (function(Files) {
 	var keys = [];
 	var keyCodes = {
@@ -167,4 +166,4 @@ var Files = Files || {};
 			removeA(keys, event.keyCode);
 		});
 	};
-})(Files);
+})((OCA.Files && OCA.Files.Files) || {});
diff --git a/apps/files/js/navigation.js b/apps/files/js/navigation.js
index c4a02ee..c58a284 100644
--- a/apps/files/js/navigation.js
+++ b/apps/files/js/navigation.js
@@ -73,25 +73,40 @@
 		 * @param array options "silent" to not trigger event
 		 */
 		setActiveItem: function(itemId, options) {
+			var oldItemId = this._activeItem;
 			if (itemId === this._activeItem) {
+				if (!options || !options.silent) {
+					this.$el.trigger(
+						new $.Event('itemChanged', {itemId: itemId, previousItemId: oldItemId})
+					);
+				}
 				return;
 			}
-			this._activeItem = itemId;
 			this.$el.find('li').removeClass('selected');
 			if (this.$currentContent) {
 				this.$currentContent.addClass('hidden');
 				this.$currentContent.trigger(jQuery.Event('hide'));
 			}
+			this._activeItem = itemId;
+			this.$el.find('li[data-id=' + itemId + ']').addClass('selected');
 			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}));
+				this.$el.trigger(
+					new $.Event('itemChanged', {itemId: itemId, previousItemId: oldItemId})
+				);
 			}
 		},
 
 		/**
+		 * Returns whether a given item exists
+		 */
+		itemExists: function(itemId) {
+			return this.$el.find('li[data-id=' + itemId + ']').length;
+		},
+
+		/**
 		 * Event handler for when clicking on an item.
 		 */
 		_onClickItem: function(ev) {
diff --git a/apps/files/list.php b/apps/files/list.php
index 78b1b07..e583839 100644
--- a/apps/files/list.php
+++ b/apps/files/list.php
@@ -21,29 +21,17 @@
  *
  */
 
-
 // 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);
+$config = \OC::$server->getConfig();
+// TODO: move this to the generated config.js
 $publicUploadEnabled = $config->getAppValue('core', 'shareapi_allow_public_upload', 'yes');
 $uploadLimit=OCP\Util::uploadLimit();
-$maxUploadFilesize=OCP\Util::maxUploadFilesize($dir, $freeSpace);
-$freeSpace=$storageInfo['free'];
-*/
 
+// renders the controls and table headers template
 $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/index.php b/apps/files/templates/index.php
index ce065a9..8cab4ce 100644
--- a/apps/files/templates/index.php
+++ b/apps/files/templates/index.php
@@ -10,7 +10,6 @@
 
 <!-- config hints for javascript -->
 <input type="hidden" name="filesApp" id="filesApp" value="1" />
-<input type="hidden" name="allowZipDownload" id="allowZipDownload" value="<?php p($_['allowZipDownload']); ?>" />
 <input type="hidden" name="usedSpacePercent" id="usedSpacePercent" value="<?php p($_['usedSpacePercent']); ?>" />
 <?php if (!$_['isPublic']) :?>
 <input type="hidden" name="encryptedFiles" id="encryptedFiles" value="<?php $_['encryptedFiles'] ? p('1') : p('0'); ?>" />
diff --git a/apps/files/templates/list.php b/apps/files/templates/list.php
index a11defa..8f11f96 100644
--- a/apps/files/templates/list.php
+++ b/apps/files/templates/list.php
@@ -18,19 +18,20 @@
 				</ul>
 			</div>
 			<?php endif;?>
+			<?php /* Note: the template attributes are here only for the public page. These are normally loaded
+					 through ajax instead (updateStorageStatistics).
+			*/ ?>
 			<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']) ?>">
+				 title="<?php isset($_['uploadMaxHumanFilesize']) ? p($l->t('Upload (max. %s)', array($_['uploadMaxHumanFilesize']))) : '' ?>">
+					<input type="hidden" id="max_upload" name="MAX_FILE_SIZE" value="<?php isset($_['uploadMaxFilesize']) ? p($_['uploadMaxFilesize']) : '' ?>">
+					<input type="hidden" id="upload_limit" value="<?php isset($_['uploadLimit']) ? p($_['uploadLimit']) : '' ?>">
+					<input type="hidden" id="free_space" value="<?php isset($_['freeSpace']) ? 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']); ?>)">
+						   value="(max <?php isset($_['uploadMaxHumanFilesize']) ? 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>
@@ -56,8 +57,8 @@
 		<tr>
 			<th id='headerName' class="hidden column-name">
 				<div id="headerName-container">
-					<input type="checkbox" id="select_all" />
-					<label for="select_all"></label>
+					<input type="checkbox" id="select_all_files" class="select-all"/>
+					<label for="select_all_files"></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']) : ?>
@@ -88,6 +89,8 @@
 	<tfoot>
 	</tfoot>
 </table>
+<input type="hidden" name="allowZipDownload" id="allowZipDownload" value="<?php p($_['allowZipDownload']); ?>" />
+<input type="hidden" name="dir" id="dir" value="" />
 <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>
diff --git a/apps/files/tests/js/appSpec.js b/apps/files/tests/js/appSpec.js
index 315f11f..0e9abad 100644
--- a/apps/files/tests/js/appSpec.js
+++ b/apps/files/tests/js/appSpec.js
@@ -26,6 +26,7 @@ describe('OCA.Files.App tests', function() {
 
 	beforeEach(function() {
 		$('#testArea').append(
+			'<div id="content" class="app-files">' +
 			'<div id="app-navigation">' +
 			'<ul><li data-id="files"><a>Files</a></li>' +
 			'<li data-id="other"><a>Other</a></li>' +
@@ -36,6 +37,7 @@ describe('OCA.Files.App tests', function() {
 			'<div id="app-content-other" class="hidden">' +
 			'</div>' +
 			'</div>' +
+			'</div>' +
 			'</div>'
 		);
 
@@ -89,30 +91,43 @@ describe('OCA.Files.App tests', function() {
 				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);
+				var showHandlerFiles = sinon.stub();
+				var showHandlerOther = sinon.stub();
+				var hideHandlerFiles = sinon.stub();
+				var hideHandlerOther = sinon.stub();
+				$('#app-content-files').on('show', showHandlerFiles);
+				$('#app-content-files').on('hide', hideHandlerFiles);
+				$('#app-content-other').on('show', showHandlerOther);
+				$('#app-content-other').on('hide', hideHandlerOther);
 				App._onPopState({view: 'other', dir: '/somedir'});
-				expect(handlerFiles.notCalled).toEqual(true);
-				expect(handlerOther.calledOnce).toEqual(true);
+				expect(showHandlerFiles.notCalled).toEqual(true);
+				expect(hideHandlerFiles.calledOnce).toEqual(true);
+				expect(showHandlerOther.calledOnce).toEqual(true);
+				expect(hideHandlerOther.notCalled).toEqual(true);
 
-				handlerFiles.reset();
-				handlerOther.reset();
+				showHandlerFiles.reset();
+				showHandlerOther.reset();
+				hideHandlerFiles.reset();
+				hideHandlerOther.reset();
 
 				App._onPopState({view: 'files', dir: '/somedir'});
-				expect(handlerFiles.calledOnce).toEqual(true);
-				expect(handlerOther.notCalled).toEqual(true);
+				expect(showHandlerFiles.calledOnce).toEqual(true);
+				expect(hideHandlerFiles.notCalled).toEqual(true);
+				expect(showHandlerOther.notCalled).toEqual(true);
+				expect(hideHandlerOther.calledOnce).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);
+			it('does not send "show" or "hide" event to current app when already visible', function() {
+				var showHandler = sinon.stub();
+				var hideHandler = sinon.stub();
+				$('#app-content-files').on('show', showHandler);
+				$('#app-content-files').on('hide', hideHandler);
 				App._onPopState({view: 'files', dir: '/somedir'});
-				expect(handler.notCalled).toEqual(true);
+				expect(showHandler.notCalled).toEqual(true);
+				expect(hideHandler.notCalled).toEqual(true);
 			});
 			it('state defaults to files app with root dir', function() {
 				var handler = sinon.stub();
@@ -123,6 +138,12 @@ describe('OCA.Files.App tests', function() {
 				expect(handler.getCall(0).args[0].view).toEqual('files');
 				expect(handler.getCall(0).args[0].dir).toEqual('/');
 			});
+			it('activates files app if invalid view is passed', function() {
+				App._onPopState({view: 'invalid', dir: '/somedir'});
+
+				expect(App.navigation.getActiveItem()).toEqual('files');
+				expect($('#app-content-files').hasClass('hidden')).toEqual(false);
+			});
 		});
 		describe('navigation', function() {
 			it('switches the navigation item and panel visibility when onpopstate', function() {
@@ -156,13 +177,43 @@ describe('OCA.Files.App tests', function() {
 				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() {
+			it('clicking on navigation sends "show" and "urlChanged" event', function() {
 				var handler = sinon.stub();
+				var showHandler = sinon.stub();
 				$('#app-content-other').on('urlChanged', handler);
+				$('#app-content-other').on('show', showHandler);
 				$('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('/');
+				expect(showHandler.calledOnce).toEqual(true);
+			});
+			it('clicking on activate navigation only sends "urlChanged" event', function() {
+				var handler = sinon.stub();
+				var showHandler = sinon.stub();
+				$('#app-content-files').on('urlChanged', handler);
+				$('#app-content-files').on('show', showHandler);
+				$('li[data-id=files]>a').click();
+				expect(handler.calledOnce).toEqual(true);
+				expect(handler.getCall(0).args[0].view).toEqual('files');
+				expect(handler.getCall(0).args[0].dir).toEqual('/');
+				expect(showHandler.notCalled).toEqual(true);
+			});
+		});
+		describe('viewer mode', function() {
+			it('toggles the sidebar when viewer mode is enabled', function() {
+				$('#app-content-files').trigger(
+					new $.Event('changeViewerMode', {viewerModeEnabled: true}
+				));
+				expect($('#app-navigation').hasClass('hidden')).toEqual(true);
+				expect($('.app-files').hasClass('viewer-mode no-sidebar')).toEqual(true);
+
+				$('#app-content-files').trigger(
+					new $.Event('changeViewerMode', {viewerModeEnabled: false}
+				));
+
+				expect($('#app-navigation').hasClass('hidden')).toEqual(false);
+				expect($('.app-files').hasClass('viewer-mode no-sidebar')).toEqual(false);
 			});
 		});
 	});
diff --git a/apps/files/tests/js/fileactionsSpec.js b/apps/files/tests/js/fileactionsSpec.js
index 23a13be..9152dbb 100644
--- a/apps/files/tests/js/fileactionsSpec.js
+++ b/apps/files/tests/js/fileactionsSpec.js
@@ -38,12 +38,19 @@ describe('OCA.Files.FileActions tests', function() {
 		fileList = undefined;
 		$('#dir, #permissions, #filestable').remove();
 	});
+	it('calling clear() clears file actions', function() {
+		FileActions.clear();
+		expect(FileActions.actions).toEqual({});
+		expect(FileActions.defaults).toEqual({});
+		expect(FileActions.icons).toEqual({});
+		expect(FileActions.currentFile).toBe(null);
+	});
 	it('calling display() sets file actions', function() {
 		var fileData = {
 			id: 18,
 			type: 'file',
 			name: 'testName.txt',
-			mimetype: 'plain/text',
+			mimetype: 'text/plain',
 			size: '1234',
 			etag: 'a01234c',
 			mtime: '123456'
@@ -64,7 +71,7 @@ describe('OCA.Files.FileActions tests', function() {
 			id: 18,
 			type: 'file',
 			name: 'testName.txt',
-			mimetype: 'plain/text',
+			mimetype: 'text/plain',
 			size: '1234',
 			etag: 'a01234c',
 			mtime: '123456'
@@ -85,7 +92,7 @@ describe('OCA.Files.FileActions tests', function() {
 			id: 18,
 			type: 'file',
 			name: 'testName.txt',
-			mimetype: 'plain/text',
+			mimetype: 'text/plain',
 			size: '1234',
 			etag: 'a01234c',
 			mtime: '123456'
@@ -105,7 +112,7 @@ describe('OCA.Files.FileActions tests', function() {
 			id: 18,
 			type: 'file',
 			name: 'testName.txt',
-			mimetype: 'plain/text',
+			mimetype: 'text/plain',
 			size: '1234',
 			etag: 'a01234c',
 			mtime: '123456'
diff --git a/apps/files/tests/js/filelistSpec.js b/apps/files/tests/js/filelistSpec.js
index a0d8870..bfc983c 100644
--- a/apps/files/tests/js/filelistSpec.js
+++ b/apps/files/tests/js/filelistSpec.js
@@ -51,19 +51,13 @@ describe('OCA.Files.FileList tests', function() {
 	}
 
 	beforeEach(function() {
-		// init horrible parameters
-		var $body = $('body');
-		$body.append('<input type="hidden" id="dir" value="/subdir"></input>');
-		$body.append('<input type="hidden" id="permissions" value="31"></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-files">' +
+			// init horrible parameters
 			'<input type="hidden" id="dir" value="/subdir"></input>' +
 			'<input type="hidden" id="permissions" value="31"></input>' +
 			// dummy controls
@@ -76,7 +70,7 @@ describe('OCA.Files.FileList tests', function() {
 			'<table id="filestable">' +
 			'<thead><tr>' +
 			'<th id="headerName" class="hidden column-name">' +
-			'<input type="checkbox" id="select_all">' +
+			'<input type="checkbox" id="select_all_files" class="select-all">' +
 			'<a class="name columntitle" data-sort="name"><span>Name</span><span class="sort-indicator"></span></a>' +
 			'<span class="selectedActions hidden">' +
 			'<a href class="download">Download</a>' +
@@ -85,7 +79,7 @@ describe('OCA.Files.FileList tests', function() {
 			'<th class="hidden column-size"><a class="columntitle" data-sort="size"><span class="sort-indicator"></span></a></th>' +
 			'<th class="hidden column-mtime"><a class="columntitle" data-sort="mtime"><span class="sort-indicator"></span></a></th>' +
 			'</tr></thead>' +
-		   	'<tbody id="fileList"></tbody>' +
+			'<tbody id="fileList"></tbody>' +
 			'<tfoot></tfoot>' +
 			'</table>' +
 			'<div id="emptycontent">Empty content message</div>' +
@@ -123,6 +117,7 @@ describe('OCA.Files.FileList tests', function() {
 		}];
 
 		fileList = new OCA.Files.FileList($('#app-content-files'));
+		FileActions.clear();
 		FileActions.registerDefaultActions(fileList);
 		fileList.setFileActions(FileActions);
 	});
@@ -131,7 +126,6 @@ describe('OCA.Files.FileList tests', function() {
 		fileList = undefined;
 
 		FileActions.clear();
-		$('#dir, #permissions, #filestable').remove();
 		notificationStub.restore();
 		alertStub.restore();
 	});
@@ -160,7 +154,7 @@ describe('OCA.Files.FileList tests', function() {
 				id: 18,
 				type: 'file',
 				name: 'testName.txt',
-				mimetype: 'plain/text',
+				mimetype: 'text/plain',
 				size: '1234',
 				etag: 'a01234c',
 				mtime: '123456'
@@ -175,9 +169,11 @@ describe('OCA.Files.FileList tests', function() {
 			expect($tr.attr('data-size')).toEqual('1234');
 			expect($tr.attr('data-etag')).toEqual('a01234c');
 			expect($tr.attr('data-permissions')).toEqual('31');
-			expect($tr.attr('data-mime')).toEqual('plain/text');
+			expect($tr.attr('data-mime')).toEqual('text/plain');
 			expect($tr.attr('data-mtime')).toEqual('123456');
-			expect($tr.find('a.name').attr('href')).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fsubdir&files=testName.txt');
+			expect($tr.find('a.name').attr('href'))
+				.toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fsubdir&files=testName.txt');
+			expect($tr.find('.nametext').text().trim()).toEqual('testName.txt');
 
 			expect($tr.find('.filesize').text()).toEqual('1 kB');
 			expect(fileList.findFileEl('testName.txt')[0]).toEqual($tr[0]);
@@ -482,7 +478,9 @@ describe('OCA.Files.FileList tests', function() {
 			// trigger rename prompt
 			fileList.rename('One.txt');
 			$input = fileList.$fileList.find('input.filename');
-			$input.val('Tu_after_three.txt').blur();
+			$input.val('Tu_after_three.txt');
+			// trigger submit because triggering blur doesn't work in all browsers
+			$input.closest('form').trigger('submit');
 
 			expect(fakeServer.requests.length).toEqual(1);
 			request = fakeServer.requests[0];
@@ -926,6 +924,18 @@ describe('OCA.Files.FileList tests', function() {
 			expect($('.actions').hasClass('hidden')).toEqual(true);
 			expect($('.notCreatable').hasClass('hidden')).toEqual(false);
 		});
+		it('toggling viewer mode triggers event', function() {
+			var handler = sinon.stub();
+			fileList.$el.on('changeViewerMode', handler);
+			fileList.setViewerMode(true);
+			expect(handler.calledOnce).toEqual(true);
+			expect(handler.getCall(0).args[0].viewerModeEnabled).toEqual(true);
+
+			handler.reset();
+			fileList.setViewerMode(false);
+			expect(handler.calledOnce).toEqual(true);
+			expect(handler.getCall(0).args[0].viewerModeEnabled).toEqual(false);
+		});
 	});
 	describe('loading file list', function() {
 		beforeEach(function() {
@@ -1198,27 +1208,27 @@ describe('OCA.Files.FileList tests', function() {
 			expect(selection).toContain('Three.pdf');
 		});
 		it('Selecting all files will automatically check "select all" checkbox', function() {
-			expect($('#select_all').prop('checked')).toEqual(false);
+			expect($('.select-all').prop('checked')).toEqual(false);
 			$('#fileList tr td.filename input:checkbox').click();
-			expect($('#select_all').prop('checked')).toEqual(true);
+			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));
-			expect($('#select_all').prop('checked')).toEqual(false);
+			expect($('.select-all').prop('checked')).toEqual(false);
 			$('#fileList tr td.filename input:checkbox').click();
-			expect($('#select_all').prop('checked')).toEqual(false);
+			expect($('.select-all').prop('checked')).toEqual(false);
 		});
 		it('Clicking "select all" will select/deselect all files', function() {
 			fileList.setFiles(generateFiles(0, 41));
-			$('#select_all').click();
-			expect($('#select_all').prop('checked')).toEqual(true);
+			$('.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);
 
-			$('#select_all').click();
-			expect($('#select_all').prop('checked')).toEqual(false);
+			$('.select-all').click();
+			expect($('.select-all').prop('checked')).toEqual(false);
 
 			$('#fileList tr input:checkbox').each(function() {
 				expect($(this).prop('checked')).toEqual(false);
@@ -1226,44 +1236,44 @@ describe('OCA.Files.FileList tests', function() {
 			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);
+			$('.select-all').click();
+			expect($('.select-all').prop('checked')).toEqual(true);
 
 			var $tr = fileList.findFileEl('One.txt');
 			$tr.find('input:checkbox').click();
 
-			expect($('#select_all').prop('checked')).toEqual(false);
+			expect($('.select-all').prop('checked')).toEqual(false);
 			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);
+			$('.select-all').click();
+			expect($('.select-all').prop('checked')).toEqual(true);
 
 			var $tr = fileList.findFileEl('One.txt');
 			// unselect one
 			$tr.find('input:checkbox').click();
 
-			expect($('#select_all').prop('checked')).toEqual(false);
+			expect($('.select-all').prop('checked')).toEqual(false);
 			expect(_.pluck(fileList.getSelectedFiles(), 'name').length).toEqual(3);
 
 			// select all
-			$('#select_all').click();
-			expect($('#select_all').prop('checked')).toEqual(true);
+			$('.select-all').click();
+			expect($('.select-all').prop('checked')).toEqual(true);
 			expect(_.pluck(fileList.getSelectedFiles(), 'name').length).toEqual(4);
 
 			// unselect one
 			$tr.find('input:checkbox').click();
-			expect($('#select_all').prop('checked')).toEqual(false);
+			expect($('.select-all').prop('checked')).toEqual(false);
 			expect(_.pluck(fileList.getSelectedFiles(), 'name').length).toEqual(3);
 
 			// re-select it
 			$tr.find('input:checkbox').click();
-			expect($('#select_all').prop('checked')).toEqual(true);
+			expect($('.select-all').prop('checked')).toEqual(true);
 			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));
-			$('#select_all').click();
+			$('.select-all').click();
 
 			expect(fileList.$fileList.find('tr input:checkbox:checked').length).toEqual(20);
 			fileList._nextPage(true);
@@ -1295,7 +1305,7 @@ describe('OCA.Files.FileList tests', function() {
 			expect($actions.hasClass('hidden')).toEqual(true);
 		});
 		it('Selection is cleared when switching dirs', function() {
-			$('#select_all').click();
+			$('.select-all').click();
 			var data = {
 				status: 'success',
 				data: {
@@ -1311,13 +1321,13 @@ describe('OCA.Files.FileList tests', function() {
 			]);
 			fileList.changeDirectory('/');
 			fakeServer.respond();
-			expect($('#select_all').prop('checked')).toEqual(false);
+			expect($('.select-all').prop('checked')).toEqual(false);
 			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));
-			$('#select_all').click();
+			$('.select-all').click();
 			// unselect one to not have the "allFiles" case
 			fileList.$fileList.find('tr input:checkbox:first').click();
 
@@ -1326,6 +1336,18 @@ describe('OCA.Files.FileList tests', function() {
 
 			expect(selectedFiles.length).toEqual(41);
 		});
+		describe('Selection overlay', function() {
+			it('show delete action according to directory permissions', function() {
+				fileList.setFiles(testFiles);
+				$('#permissions').val(OC.PERMISSION_READ | OC.PERMISSION_DELETE);
+				$('.select-all').click();
+				expect(fileList.$el.find('.delete-selected').hasClass('hidden')).toEqual(false);
+				$('.select-all').click();
+				$('#permissions').val(OC.PERMISSION_READ);
+				$('.select-all').click();
+				expect(fileList.$el.find('.delete-selected').hasClass('hidden')).toEqual(true);
+			});
+		});
 		describe('Actions', function() {
 			beforeEach(function() {
 				fileList.findFileEl('One.txt').find('input:checkbox').click();
@@ -1391,7 +1413,7 @@ describe('OCA.Files.FileList tests', function() {
 				});
 				it('Downloads root folder when all selected in root folder', function() {
 					$('#dir').val('/');
-					$('#select_all').click();
+					$('.select-all').click();
 					var redirectStub = sinon.stub(OC, 'redirect');
 					$('.selectedActions .download').click();
 					expect(redirectStub.calledOnce).toEqual(true);
@@ -1399,7 +1421,7 @@ describe('OCA.Files.FileList tests', function() {
 					redirectStub.restore();
 				});
 				it('Downloads parent folder when all selected in subfolder', function() {
-					$('#select_all').click();
+					$('.select-all').click();
 					var redirectStub = sinon.stub(OC, 'redirect');
 					$('.selectedActions .download').click();
 					expect(redirectStub.calledOnce).toEqual(true);
@@ -1428,7 +1450,7 @@ describe('OCA.Files.FileList tests', function() {
 				});
 				it('Deletes all files when all selected when "Delete" clicked', function() {
 					var request;
-					$('#select_all').click();
+					$('.select-all').click();
 					$('.selectedActions .delete-selected').click();
 					expect(fakeServer.requests.length).toEqual(1);
 					request = fakeServer.requests[0];
@@ -1445,9 +1467,9 @@ describe('OCA.Files.FileList tests', function() {
 			});
 		});
 		it('resets the file selection on reload', function() {
-			fileList.$el.find('#select_all').click();
+			fileList.$el.find('.select-all').click();
 			fileList.reload();
-			expect(fileList.$el.find('#select_all').prop('checked')).toEqual(false);
+			expect(fileList.$el.find('.select-all').prop('checked')).toEqual(false);
 			expect(fileList.getSelectedFiles()).toEqual([]);
 		});
 	});
diff --git a/apps/files_sharing/css/public.css b/apps/files_sharing/css/public.css
index 8abeb8b..70897af 100644
--- a/apps/files_sharing/css/public.css
+++ b/apps/files_sharing/css/public.css
@@ -1,11 +1,3 @@
-#controls {
-	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 6ee5496..d3d4479 100644
--- a/apps/files_sharing/js/public.js
+++ b/apps/files_sharing/js/public.js
@@ -8,7 +8,8 @@
  *
  */
 
-/* global OC, FileActions, FileList, Files */
+/* global FileActions, Files */
+/* global dragOptions, folderDropOptions */
 OCA.Sharing = {};
 if (!OCA.Files) {
 	OCA.Files = {};
@@ -23,7 +24,16 @@ OCA.Sharing.PublicApp = {
 		this._initialized = true;
 		// file list mode ?
 		if ($el.find('#filestable')) {
-			this.fileList = new OCA.Files.FileList($el);
+			this.fileList = new OCA.Files.FileList(
+				$el,
+				{
+					scrollContainer: $(window),
+					dragOptions: dragOptions,
+					folderDropOptions: folderDropOptions
+				}
+			);
+			this.files = OCA.Files.Files;
+			this.files.initialize();
 		}
 
 		var mimetype = $('#mimetype').val();
@@ -145,5 +155,17 @@ OCA.Sharing.PublicApp = {
 $(document).ready(function() {
 	var App = OCA.Sharing.PublicApp;
 	App.initialize($('#preview'));
+
+	// HACK: for oc-dialogs previews that depends on Files:
+	Files.lazyLoadPreview = function(path, mime, ready, width, height, etag) {
+		return App.fileList.lazyLoadPreview({
+			path: path,
+			mime: mime,
+			callback: ready,
+			width: width,
+			height: height,
+			etag: etag
+		});
+	};
 });
 
diff --git a/apps/files_sharing/js/share.js b/apps/files_sharing/js/share.js
index ac46ab7..973c63c 100644
--- a/apps/files_sharing/js/share.js
+++ b/apps/files_sharing/js/share.js
@@ -8,14 +8,15 @@
  *
  */
 
-/* global OC, t, FileList, FileActions */
+/* global FileList, FileActions */
 $(document).ready(function() {
 
 	var sharesLoaded = false;
 
 	if (typeof OC.Share !== 'undefined' && typeof FileActions !== 'undefined') {
-		var oldCreateRow = FileList._createRow;
-		FileList._createRow = function(fileData) {
+		// TODO: make a separate class for this or a hook or jQuery event ?
+		var oldCreateRow = OCA.Files.FileList.prototype._createRow;
+		OCA.Files.FileList.prototype._createRow = function(fileData) {
 			var tr = oldCreateRow.apply(this, arguments);
 			if (fileData.shareOwner) {
 				tr.attr('data-share-owner', fileData.shareOwner);
@@ -24,14 +25,16 @@ $(document).ready(function() {
 		};
 
 		$('#fileList').on('fileActionsReady',function(){
-
-			var allShared = $('#fileList').find('[data-share-owner] [data-Action="Share"]');
+			var $fileList = $(this);
+			var allShared = $fileList.find('[data-share-owner] [data-Action="Share"]');
 			allShared.addClass('permanent');
 			allShared.find('span').text(function(){
 				var $owner = $(this).closest('tr').attr('data-share-owner');
 				return ' ' + t('files_sharing', 'Shared by {owner}', {owner: $owner});
 			});
 
+			// FIXME: these calls are also working on hard-coded
+			// list selectors...
 			if (!sharesLoaded){
 				OC.Share.loadIcons('file');
 				// assume that we got all shares, so switching directories
diff --git a/apps/files_sharing/public.php b/apps/files_sharing/public.php
index a73d97f..8a86cb3 100644
--- a/apps/files_sharing/public.php
+++ b/apps/files_sharing/public.php
@@ -153,7 +153,7 @@ if (isset($path)) {
 			$folder->assign('dir', $getPath);
 			$folder->assign('dirToken', $linkItem['token']);
 			$folder->assign('permissions', OCP\PERMISSION_READ);
-			$folder->assign('isPublic',true);
+			$folder->assign('isPublic', true);
 			$folder->assign('publicUploadEnabled', 'no');
 			$folder->assign('files', $files);
 			$folder->assign('uploadMaxFilesize', $maxUploadFilesize);
diff --git a/apps/files_trashbin/appinfo/app.php b/apps/files_trashbin/appinfo/app.php
index 06c2e34..b8900ee 100644
--- a/apps/files_trashbin/appinfo/app.php
+++ b/apps/files_trashbin/appinfo/app.php
@@ -8,7 +8,7 @@ $l = OC_L10N::get('files_trashbin');
 array(
 	"id" => 'trashbin',
 	"appname" => 'files_trashbin',
-	"script" => 'index.php',
+	"script" => 'list.php',
 	"order" => 1,
 	"name" => $l->t('Deleted files')
 )
diff --git a/apps/files_trashbin/css/trash.css b/apps/files_trashbin/css/trash.css
index 7ca3e35..04b4a17 100644
--- a/apps/files_trashbin/css/trash.css
+++ b/apps/files_trashbin/css/trash.css
@@ -1,4 +1,13 @@
-#fileList tr[data-type="file"] td a.name,
-#fileList tr[data-type="file"] td a.name span {
+/*
+ * Copyright (c) 2014
+ *
+ * This file is licensed under the Affero General Public License version 3
+ * or later.
+ *
+ * See the COPYING-README file.
+ *
+ */
+#app-content-trashbin tbody tr[data-type="file"] td a.name,
+#app-content-trashbin tbody tr[data-type="file"] td a.name span {
     cursor: default;
 }
diff --git a/apps/files_trashbin/js/app.js b/apps/files_trashbin/js/app.js
index 9ab78e7..aa499ae 100644
--- a/apps/files_trashbin/js/app.js
+++ b/apps/files_trashbin/js/app.js
@@ -17,7 +17,11 @@ OCA.Trashbin.App = {
 			return;
 		}
 		this._initialized = true;
-		this.fileList = new OCA.Trashbin.FileList($el);
+		this.fileList = new OCA.Trashbin.FileList(
+			$('#app-content-trashbin'), {
+				scrollContainer: $('#app-content')
+			}
+		);
 		this.registerFileActions(this.fileList);
 	},
 
@@ -68,7 +72,7 @@ OCA.Trashbin.App = {
 };
 
 $(document).ready(function() {
-	$('#app-content-trashbin').on('show', function() {
+	$('#app-content-trashbin').one('show', function() {
 		var App = OCA.Trashbin.App;
 		App.initialize($('#app-content-trashbin'));
 		// force breadcrumb init
diff --git a/apps/files_trashbin/js/filelist.js b/apps/files_trashbin/js/filelist.js
index d320695..205f879 100644
--- a/apps/files_trashbin/js/filelist.js
+++ b/apps/files_trashbin/js/filelist.js
@@ -30,6 +30,7 @@
 		this.initialize($el);
 	};
 	FileList.prototype = _.extend({}, OCA.Files.FileList.prototype, {
+		id: 'trashbin',
 		appName: t('files_trashbin', 'Deleted files'),
 
 		initialize: function() {
@@ -37,11 +38,6 @@
 			this.$el.find('.undelete').click('click', _.bind(this._onClickRestoreSelected, this));
 
 			this.setSort('mtime', 'desc');
-
-			// 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
@@ -58,6 +54,13 @@
 			return result;
 		},
 
+		/**
+		 * Override to only return read permissions
+		 */
+		getDirectoryPermissions: function() {
+			return OC.PERMISSION_READ | OC.PERMISSION_DELETE;
+		},
+
 		_setCurrentDir: function(targetDir) {
 			OCA.Files.FileList.prototype._setCurrentDir.apply(this, arguments);
 
@@ -97,8 +100,12 @@
 			return OC.filePath('files_trashbin', 'ajax', action + '.php') + q;
 		},
 
+		setupUploadEvents: function() {
+			// override and do nothing
+		},
+
 		linkTo: function(dir){
-			return OC.linkTo('files_trashbin', 'index.php')+"?dir="+ encodeURIComponent(dir).replace(/%2F/g, '/');
+			return OC.linkTo('files', 'index.php')+"?view=trashbin&dir="+ encodeURIComponent(dir).replace(/%2F/g, '/');
 		},
 
 		updateEmptyContent: function(){
@@ -126,7 +133,7 @@
 		_onClickRestoreSelected: function(event) {
 			event.preventDefault();
 			var self = this;
-			var allFiles = this.$el.find('#select_all').is(':checked');
+			var allFiles = this.$el.find('.select-all').is(':checked');
 			var files = [];
 			var params = {};
 			this.disableActions();
@@ -171,7 +178,7 @@
 		_onClickDeleteSelected: function(event) {
 			event.preventDefault();
 			var self = this;
-			var allFiles = this.$el.find('#select_all').is(':checked');
+			var allFiles = this.$el.find('.select-all').is(':checked');
 			var files = [];
 			var params = {};
 			if (allFiles) {
@@ -230,7 +237,7 @@
 			return OC.generateUrl('/apps/files_trashbin/ajax/preview.php?') + $.param(urlSpec);
 		},
 
-		getDownloadUrl: function(action, params) {
+		getDownloadUrl: function() {
 			// no downloads
 			return '#';
 		},
@@ -243,6 +250,11 @@
 		disableActions: function() {
 			this.$el.find('.action').css('display', 'none');
 			this.$el.find(':input:checkbox').css('display', 'none');
+		},
+
+		updateStorageStatistics: function() {
+			// no op because the trashbin doesn't have
+			// storage info like free space / used space
 		}
 
 	});
diff --git a/apps/files_trashbin/js/files.js b/apps/files_trashbin/js/files.js
deleted file mode 100644
index f46b96a..0000000
--- a/apps/files_trashbin/js/files.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * 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/index.php b/apps/files_trashbin/list.php
similarity index 85%
rename from apps/files_trashbin/index.php
rename to apps/files_trashbin/list.php
index 08227b7..b4047b8 100644
--- a/apps/files_trashbin/index.php
+++ b/apps/files_trashbin/list.php
@@ -7,6 +7,5 @@ OCP\User::checkLoggedIn();
 $tmpl = new OCP\Template('files_trashbin', 'index', '');
 OCP\Util::addStyle('files_trashbin', 'trash');
 OCP\Util::addScript('files_trashbin', 'app');
-OCP\Util::addScript('files_trashbin', 'files');
 OCP\Util::addScript('files_trashbin', 'filelist');
 $tmpl->printPage();
diff --git a/apps/files_trashbin/templates/index.php b/apps/files_trashbin/templates/index.php
index 6622c1d..fc18e88 100644
--- a/apps/files_trashbin/templates/index.php
+++ b/apps/files_trashbin/templates/index.php
@@ -13,8 +13,8 @@
 		<tr>
 			<th id='headerName' class="hidden column-name">
 				<div id="headerName-container">
-					<input type="checkbox" id="select_all" />
-					<label for="select_all"></label>
+					<input type="checkbox" id="select_all_trash" class="select-all"/>
+					<label for="select_all_trash"></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'>
 						<a href="" class="undelete">
diff --git a/apps/files_trashbin/tests/js/filelistSpec.js b/apps/files_trashbin/tests/js/filelistSpec.js
index 291b2ff..d41c24c 100644
--- a/apps/files_trashbin/tests/js/filelistSpec.js
+++ b/apps/files_trashbin/tests/js/filelistSpec.js
@@ -24,19 +24,16 @@ describe('OCA.Trashbin.FileList tests', function() {
 	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">' +
+			// init horrible parameters
 			'<input type="hidden" id="dir" value="/"></input>' +
+			// set this but it shouldn't be used (could be the one from the
+			// files app)
 			'<input type="hidden" id="permissions" value="31"></input>' +
 			// dummy controls
 			'<div id="controls">' +
@@ -47,13 +44,13 @@ describe('OCA.Trashbin.FileList tests', function() {
 			// 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">' +
+			'<input type="checkbox" id="select_all_trash" class="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>' +
+			'<tbody id="fileList"></tbody>' +
 			'<tfoot></tfoot>' +
 			'</table>' +
 			'<div id="emptycontent">Empty content message</div>' +
@@ -66,7 +63,6 @@ describe('OCA.Trashbin.FileList tests', function() {
 			name: 'One.txt',
 			mtime: 11111000,
 			mimetype: 'text/plain',
-			size: 12,
 			etag: 'abc'
 		}, {
 			id: 2,
@@ -74,7 +70,6 @@ describe('OCA.Trashbin.FileList tests', function() {
 			name: 'Two.jpg',
 			mtime: 22222000,
 			mimetype: 'image/jpeg',
-			size: 12049,
 			etag: 'def',
 		}, {
 			id: 3,
@@ -82,7 +77,6 @@ describe('OCA.Trashbin.FileList tests', function() {
 			name: 'Three.pdf',
 			mtime: 33333000,
 			mimetype: 'application/pdf',
-			size: 58009,
 			etag: '123',
 		}, {
 			id: 4,
@@ -90,7 +84,6 @@ describe('OCA.Trashbin.FileList tests', function() {
 			mtime: 99999000,
 			name: 'somedir',
 			mimetype: 'httpd/unix-directory',
-			size: 250,
 			etag: '456'
 		}];
 
@@ -106,10 +99,91 @@ describe('OCA.Trashbin.FileList tests', function() {
 		notificationStub.restore();
 		alertStub.restore();
 	});
+	describe('Initialization', function() {
+		it('Sorts by mtime by default', function() {
+			expect(fileList._sort).toEqual('mtime');
+			expect(fileList._sortDirection).toEqual('desc');
+		});
+		it('Always returns read and delete permission', function() {
+			expect(fileList.getDirectoryPermissions()).toEqual(OC.PERMISSION_READ | OC.PERMISSION_DELETE);
+		});
+	});
+	describe('Breadcrumbs', function() {
+		beforeEach(function() {
+			var data = {
+				status: 'success',
+				data: {
+					files: testFiles,
+					permissions: 1
+				}
+			};
+			fakeServer.respondWith(/\/index\.php\/apps\/files_trashbin\/ajax\/list.php\?dir=%2Fsubdir/, [
+					200, {
+						"Content-Type": "application/json"
+					},
+					JSON.stringify(data)
+			]);
+		});
+		it('links the breadcrumb to the trashbin view', function() {
+			fileList.changeDirectory('/subdir', false, true);
+			fakeServer.respond();
+			var $crumbs = fileList.$el.find('#controls .crumb');
+			expect($crumbs.length).toEqual(2);
+			expect($crumbs.eq(0).find('a').text()).toEqual('');
+			expect($crumbs.eq(0).find('a').attr('href'))
+				.toEqual(OC.webroot + '/index.php/apps/files?view=trashbin&dir=/');
+			expect($crumbs.eq(1).find('a').text()).toEqual('subdir');
+			expect($crumbs.eq(1).find('a').attr('href'))
+				.toEqual(OC.webroot + '/index.php/apps/files?view=trashbin&dir=/subdir');
+		});
+	});
 	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
+		it('renders rows with the correct data when in root', function() {
+			// dir listing is false when in root
+			$('#dir').val('/');
+			fileList.setFiles(testFiles);
+			var $rows = fileList.$el.find('tbody tr');
+			var $tr = $rows.eq(0);
+			expect($rows.length).toEqual(4);
+			expect($tr.attr('data-id')).toEqual('1');
+			expect($tr.attr('data-type')).toEqual('file');
+			expect($tr.attr('data-file')).toEqual('One.txt.d11111');
+			expect($tr.attr('data-size')).not.toBeDefined();
+			expect($tr.attr('data-etag')).toEqual('abc');
+			expect($tr.attr('data-permissions')).toEqual('9'); // read and delete
+			expect($tr.attr('data-mime')).toEqual('text/plain');
+			expect($tr.attr('data-mtime')).toEqual('11111000');
+			expect($tr.find('a.name').attr('href')).toEqual('#');
+
+			expect($tr.find('.nametext').text().trim()).toEqual('One.txt');
+
+			expect(fileList.findFileEl('One.txt.d11111')[0]).toEqual($tr[0]);
+		});
+		it('renders rows with the correct data when in subdirectory', function() {
+			// dir listing is true when in a subdir
+			$('#dir').val('/subdir');
+
+			fileList.setFiles(testFiles);
+			var $rows = fileList.$el.find('tbody tr');
+			var $tr = $rows.eq(0);
+			expect($rows.length).toEqual(4);
+			expect($tr.attr('data-id')).toEqual('1');
+			expect($tr.attr('data-type')).toEqual('file');
+			expect($tr.attr('data-file')).toEqual('One.txt');
+			expect($tr.attr('data-size')).not.toBeDefined();
+			expect($tr.attr('data-etag')).toEqual('abc');
+			expect($tr.attr('data-permissions')).toEqual('9'); // read and delete
+			expect($tr.attr('data-mime')).toEqual('text/plain');
+			expect($tr.attr('data-mtime')).toEqual('11111000');
+			expect($tr.find('a.name').attr('href')).toEqual('#');
+
+			expect($tr.find('.nametext').text().trim()).toEqual('One.txt');
+
+			expect(fileList.findFileEl('One.txt')[0]).toEqual($tr[0]);
+		});
+		it('does not render a size column', function() {
+			expect(fileList.$el.find('tbody tr .filesize').length).toEqual(0);
+		});
 	});
 	describe('File actions', function() {
 		describe('Deleting single files', function() {
@@ -142,7 +216,6 @@ describe('OCA.Trashbin.FileList tests', function() {
 			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();
@@ -154,7 +227,16 @@ describe('OCA.Trashbin.FileList tests', function() {
 				fakeServer.requests[0].respond(
 					200,
 					{ 'Content-Type': 'application/json' },
-					JSON.stringify({status: 'success'})
+					JSON.stringify({
+						status: 'success',
+						data: {
+							success: [
+								{filename: 'One.txt.d11111'},
+								{filename: 'Three.pdf.d33333'},
+								{filename: 'somedir.d99999'}
+							]
+						}
+					})
 				);
 				expect(fileList.findFileEl('One.txt.d11111').length).toEqual(0);
 				expect(fileList.findFileEl('Three.pdf.d33333').length).toEqual(0);
@@ -163,7 +245,7 @@ describe('OCA.Trashbin.FileList tests', function() {
 			});
 			it('Deletes all files when all selected when "Delete" clicked', function() {
 				var request;
-				$('#select_all').click();
+				$('.select-all').click();
 				$('.selectedActions .delete-selected').click();
 				expect(fakeServer.requests.length).toEqual(1);
 				request = fakeServer.requests[0];
@@ -179,7 +261,6 @@ describe('OCA.Trashbin.FileList tests', function() {
 			});
 		});
 		describe('Restore', function() {
-			// TODO: also test with "allFiles"
 			it('Restores selected files when "Restore" clicked', function() {
 				var request;
 				$('.selectedActions .undelete').click();
@@ -191,16 +272,25 @@ describe('OCA.Trashbin.FileList tests', function() {
 				fakeServer.requests[0].respond(
 					200,
 					{ 'Content-Type': 'application/json' },
-					JSON.stringify({status: 'success'})
+					JSON.stringify({
+						status: 'success',
+						data: {
+							success: [
+								{filename: 'One.txt.d11111'},
+								{filename: 'Three.pdf.d33333'},
+								{filename: 'somedir.d99999'}
+							]
+						}
+					})
 				);
-				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.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('Restores all files when all selected when "Restore" clicked', function() {
 				var request;
-				$('#select_all').click();
+				$('.select-all').click();
 				$('.selectedActions .undelete').click();
 				expect(fakeServer.requests.length).toEqual(1);
 				request = fakeServer.requests[0];
diff --git a/core/css/styles.css b/core/css/styles.css
index b99af47..d21e6bc 100644
--- a/core/css/styles.css
+++ b/core/css/styles.css
@@ -302,6 +302,11 @@ input[type="submit"].enabled {
 	border-bottom: 1px solid #e7e7e7;
 	z-index: 50;
 }
+/* account for shift of controls bar due to app navigation */
+#body-user #controls,
+#body-settings #controls {
+	padding-left: 80px;
+}
 #controls .button,
 #controls button,
 #controls input[type='submit'],

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-owncloud/owncloud.git



More information about the Pkg-owncloud-commits mailing list