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
This commit is contained in:
Vincent Petry 2014-05-08 22:06:30 +02:00
parent fb10bf4048
commit 9d38e3602b
37 changed files with 3875 additions and 3020 deletions

View File

@ -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')
)
);

View File

@ -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%;
}

View File

@ -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);

View File

@ -11,15 +11,117 @@
*
*/
(function() {
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();
},
/**
* Setup events based on URL changes
*/
_setupEvents: function() {
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-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() {
var nav = new OCA.Files.Navigation($('#app-navigation ul'));
nav.setSelectedItem('files');
// TODO: init file list, actions and others
// wait for other apps/extensions to register their event handlers
// in the "ready" clause
_.defer(function() {
OCA.Files.App.initialize();
});
});

View File

@ -236,6 +236,6 @@
}
};
window.BreadCrumb = BreadCrumb;
OCA.Files.BreadCrumb = BreadCrumb;
})();

View File

@ -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
}

View File

@ -8,30 +8,37 @@
*
*/
/* global OC, FileList, Files */
/* global trashBinApp */
(function() {
var FileActions = {
actions: {},
defaults: {},
icons: {},
currentFile: null,
register: function (mime, name, permissions, icon, action, displayName) {
if (!FileActions.actions[mime]) {
FileActions.actions[mime] = {};
if (!this.actions[mime]) {
this.actions[mime] = {};
}
if (!FileActions.actions[mime][name]) {
FileActions.actions[mime][name] = {};
if (!this.actions[mime][name]) {
this.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;
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) {
FileActions.defaults[mime] = name;
this.defaults[mime] = name;
},
get: function (mime, type, permissions) {
var actions = this.getActions(mime, type, permissions);
@ -43,21 +50,21 @@ var FileActions = {
},
getActions: function (mime, type, permissions) {
var actions = {};
if (FileActions.actions.all) {
actions = $.extend(actions, FileActions.actions.all);
if (this.actions.all) {
actions = $.extend(actions, this.actions.all);
}
if (type) {//type is 'dir' or 'file'
if (FileActions.actions[type]) {
actions = $.extend(actions, FileActions.actions[type]);
if (this.actions[type]) {
actions = $.extend(actions, this.actions[type]);
}
}
if (mime) {
var mimePart = mime.substr(0, mime.indexOf('/'));
if (FileActions.actions[mimePart]) {
actions = $.extend(actions, FileActions.actions[mimePart]);
if (this.actions[mimePart]) {
actions = $.extend(actions, this.actions[mimePart]);
}
if (FileActions.actions[mime]) {
actions = $.extend(actions, FileActions.actions[mime]);
if (this.actions[mime]) {
actions = $.extend(actions, this.actions[mime]);
}
}
var filteredActions = {};
@ -74,14 +81,14 @@ var FileActions = {
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];
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 = FileActions.defaults.all;
name = this.defaults.all;
}
var actions = this.get(mime, type, permissions);
return actions[name];
@ -93,11 +100,12 @@ var FileActions = {
* 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();
this.currentFile = parent;
var self = this;
var actions = this.getActions(this.getCurrentMimeType(), this.getCurrentType(), this.getCurrentPermissions());
var file = this.getCurrentFile();
var nameLinks;
if (FileList.findFileEl(file).data('renaming')) {
if (parent.closest('tr').data('renaming')) {
return;
}
@ -105,14 +113,14 @@ var 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 defaultAction = this.getDefault(this.getCurrentMimeType(), this.getCurrentType(), this.getCurrentPermissions());
var actionHandler = function (event) {
event.stopPropagation();
event.preventDefault();
FileActions.currentFile = event.data.elem;
var file = FileActions.getCurrentFile();
self.currentFile = event.data.elem;
var file = self.getCurrentFile();
event.data.actionFunc(file);
};
@ -121,7 +129,7 @@ var FileActions = {
if ((name === 'Download' || action !== defaultAction) && name !== 'Delete') {
var img = FileActions.icons[name],
var img = self.icons[name],
actionText = displayName,
actionContainer = 'a.name>span.fileactions';
@ -164,7 +172,7 @@ var FileActions = {
// remove the existing delete action
parent.parent().children().last().find('.action.delete').remove();
if (actions['Delete']) {
var img = FileActions.icons['Delete'];
var img = self.icons['Delete'];
var html;
if (img.call) {
img = img(file);
@ -185,20 +193,45 @@ var FileActions = {
}
},
getCurrentFile: function () {
return FileActions.currentFile.parent().attr('data-file');
return this.currentFile.parent().attr('data-file');
},
getCurrentMimeType: function () {
return FileActions.currentFile.parent().attr('data-mime');
return this.currentFile.parent().attr('data-mime');
},
getCurrentType: function () {
return FileActions.currentFile.parent().attr('data-type');
return this.currentFile.parent().attr('data-type');
},
getCurrentPermissions: function () {
return FileActions.currentFile.parent().data('permissions');
}
};
return this.currentFile.parent().data('permissions');
},
$(document).ready(function () {
/**
* 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();
});
// t('files', 'Rename')
this.register('all', 'Rename', OC.PERMISSION_UPDATE, function () {
return OC.imagePath('core', 'actions/rename');
}, function (filename) {
fileList.rename(filename);
});
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';
@ -206,44 +239,22 @@ $(document).ready(function () {
downloadScope = 'file';
}
if (typeof disableDownloadActions == 'undefined' || !disableDownloadActions) {
FileActions.register(downloadScope, 'Download', OC.PERMISSION_READ, function () {
this.register(downloadScope, 'Download', OC.PERMISSION_READ, function () {
return OC.imagePath('core', 'actions/download');
}, function (filename) {
var url = Files.getDownloadUrl(filename);
var url = OCA.Files.Files.getDownloadUrl(filename, fileList.getCurrentDirectory());
if (url) {
OC.redirect(url);
}
});
fileList.$fileList.trigger(jQuery.Event("fileActionsReady"));
}
$('#fileList tr').each(function () {
FileActions.display($(this).children('td.filename'));
});
};
$('#fileList').trigger(jQuery.Event("fileActionsReady"));
OCA.Files.FileActions = FileActions;
})();
});
// for backward compatibility
window.FileActions = OCA.Files.FileActions;
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');

View File

@ -8,17 +8,40 @@
*
*/
/* global OC, t, n, FileList, FileActions, Files, FileSummary, BreadCrumb */
/* global Files */
/* global dragOptions, folderDropOptions */
window.FileList = {
(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);
};
FileList.prototype = {
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'),
/**
* Top-level container with controls and file list
*/
$el: null,
/**
* Files table
*/
$table: null,
/**
* List of rows (table tbody)
*/
$fileList: null,
breadcrumb: null,
/**
@ -36,6 +59,11 @@ window.FileList = {
*/
files: [],
/**
* File actions handler, defaults to OCA.Files.FileActions
*/
fileActions: null,
/**
* Map of file id to file data
*/
@ -62,49 +90,70 @@ window.FileList = {
*/
_sortComparator: null,
/**
* Current directory
*/
_currentDirectory: null,
/**
* Initialize the file list and its components
*/
initialize: function() {
initialize: function($el) {
var self = this;
if (this.initialized) {
return;
}
// TODO: FileList should not know about global elements
this.$el = $('#filestable');
this.$fileList = $('#fileList');
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 FileSummary();
this._selectionSummary = new OCA.Files.FileSummary();
this.fileSummary = this._createSummary();
this.setSort('name', 'asc');
this.breadcrumb = new BreadCrumb({
onClick: this._onClickBreadCrumb,
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);
}
});
$('#controls').prepend(this.breadcrumb.$el);
this.$el.find('#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);
self.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.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);
}
},
/**
@ -171,11 +220,11 @@ window.FileList = {
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);
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);
@ -227,7 +276,7 @@ window.FileList = {
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));
OC.redirect(this.getDownloadUrl(files, dir));
return false;
},
@ -236,7 +285,7 @@ window.FileList = {
*/
_onClickDeleteSelected: function(event) {
var files = null;
if (!FileList.isAllSelected()) {
if (!this.isAllSelected()) {
files = _.pluck(this.getSelectedFiles(), 'name');
}
this.do_delete(files);
@ -274,7 +323,7 @@ window.FileList = {
if ($targetDir !== undefined) {
e.preventDefault();
FileList.changeDirectory($targetDir);
this.changeDirectory($targetDir);
}
},
@ -309,10 +358,10 @@ window.FileList = {
var files = this.getSelectedFiles();
if (files.length === 0) {
// single one selected without checkbox?
files = _.map(ui.helper.find('tr'), FileList.elementToFile);
files = _.map(ui.helper.find('tr'), this.elementToFile);
}
FileList.move(_.pluck(files, 'name'), targetPath);
this.move(_.pluck(files, 'name'), targetPath);
},
/**
@ -324,7 +373,7 @@ window.FileList = {
} else {
title = '';
}
title += FileList.appName;
title += this.appName;
// Sets the page title with the " - ownCloud" suffix as in templates
window.document.title = title + ' - ' + oc_defaults.title;
@ -427,7 +476,7 @@ window.FileList = {
this.fileSummary.calculate(filesArray);
FileList.updateSelectionSummary();
this.updateSelectionSummary();
$(window).scrollTop(0);
this.$fileList.trigger(jQuery.Event("updated"));
@ -486,10 +535,10 @@ window.FileList = {
// linkUrl
if (type === 'dir') {
linkUrl = FileList.linkTo(FileList.getCurrentDirectory() + '/' + name);
linkUrl = this.linkTo(this.getCurrentDirectory() + '/' + name);
}
else {
linkUrl = Files.getDownloadUrl(name, FileList.getCurrentDirectory());
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({
@ -658,22 +707,27 @@ window.FileList = {
}
// display actions
FileActions.display(filenameTd, false);
this.fileActions.display(filenameTd, false);
if (fileData.isPreviewAvailable) {
// lazy load / newly inserted td ?
if (!fileData.icon) {
Files.lazyLoadPreview(getPathForPreview(fileData.name), mime, function(url) {
this.lazyLoadPreview({
path: this.getCurrentDirectory() + '/' + fileData.name,
mime: mime,
etag: fileData.etag,
callback: function(url) {
filenameTd.css('background-image', 'url(' + url + ')');
}, null, null, fileData.etag);
}
});
}
else {
// set the preview URL directly
var urlSpec = {
file: FileList.getCurrentDirectory() + '/' + fileData.name,
file: this.getCurrentDirectory() + '/' + fileData.name,
c: fileData.etag
};
var previewUrl = Files.generatePreviewUrl(urlSpec);
var previewUrl = this.generatePreviewUrl(urlSpec);
previewUrl = previewUrl.replace('(', '%28').replace(')', '%29');
filenameTd.css('background-image', 'url(' + previewUrl + ')');
}
@ -685,14 +739,14 @@ window.FileList = {
* @return current directory
*/
getCurrentDirectory: function(){
return $('#dir').val() || '/';
return this._currentDirectory || this.$el.find('#dir').val() || '/';
},
/**
* Returns the directory permissions
* @return permission value as integer
*/
getDirectoryPermissions: function() {
return parseInt($('#permissions').val(), 10);
return parseInt(this.$el.find('#permissions').val(), 10);
},
/**
* @brief Changes the current directory and reload the file list.
@ -701,25 +755,25 @@ window.FileList = {
* @param {boolean} force set to true to force changing directory
*/
changeDirectory: function(targetDir, changeUrl, force) {
var $dir = $('#dir'),
currentDir = $dir.val() || '/';
var currentDir = this.getCurrentDirectory();
targetDir = targetDir || '/';
if (!force && currentDir === targetDir) {
return;
}
FileList._setCurrentDir(targetDir, changeUrl);
$('#fileList').trigger(
jQuery.Event('changeDirectory', {
dir: targetDir,
previousDir: currentDir
}
));
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
@ -727,25 +781,26 @@ window.FileList = {
*/
_setCurrentDir: function(targetDir, changeUrl) {
var url,
previousDir = this.getCurrentDirectory(),
baseDir = OC.basename(targetDir);
if (baseDir !== '') {
FileList.setPageTitle(baseDir);
this.setPageTitle(baseDir);
}
else {
FileList.setPageTitle();
this.setPageTitle();
}
$('#dir').val(targetDir);
this._currentDirectory = targetDir;
// legacy stuff
this.$el.find('#dir').val(targetDir);
if (changeUrl !== false) {
if (window.history.pushState && changeUrl !== false) {
url = FileList.linkTo(targetDir);
window.history.pushState({dir: targetDir}, '', url);
}
// use URL hash for IE8
else{
window.location.hash = '?dir='+ encodeURIComponent(targetDir).replace(/%2F/g, '/');
}
this.$el.trigger(jQuery.Event('changeDirectory', {
dir: targetDir,
previousDir: previousDir
}));
}
this.breadcrumb.setDirectory(this.getCurrentDirectory());
},
@ -756,7 +811,7 @@ window.FileList = {
* @param direction sort direction, one of "asc" or "desc"
*/
setSort: function(sort, direction) {
var comparator = this.Comparators[sort] || this.Comparators.name;
var comparator = FileList.Comparators[sort] || FileList.Comparators.name;
this._sort = sort;
this._sortDirection = (direction === 'desc')?'desc':'asc';
this._sortComparator = comparator;
@ -774,25 +829,26 @@ window.FileList = {
* @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);
FileList.showMask();
if (FileList._reloadCall) {
FileList._reloadCall.abort();
this.showMask();
if (this._reloadCall) {
this._reloadCall.abort();
}
FileList._reloadCall = $.ajax({
url: Files.getAjaxUrl('list'),
this._reloadCall = $.ajax({
url: this.getAjaxUrl('list'),
data: {
dir: $('#dir').val(),
sort: FileList._sort,
sortdirection: FileList._sortDirection
dir : this.getCurrentDirectory(),
sort: this._sort,
sortdirection: this._sortDirection
},
error: function(result) {
FileList.reloadCallback(result);
self.reloadCallback(result);
},
success: function(result) {
FileList.reloadCallback(result);
self.reloadCallback(result);
}
});
},
@ -825,11 +881,87 @@ window.FileList = {
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;
}
if (!urlSpec.y) {
urlSpec.y = this.$table.data('preview-y') || 36;
}
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');
}
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;
});
},
setDirectoryPermissions: function(permissions) {
var isCreatable = (permissions & OC.PERMISSION_CREATE) !== 0;
$('#permissions').val(permissions);
$('.creatable').toggleClass('hidden', !isCreatable);
$('.notCreatable').toggleClass('hidden', isCreatable);
this.$el.find('#permissions').val(permissions);
this.$el.find('.creatable').toggleClass('hidden', !isCreatable);
this.$el.find('.notCreatable').toggleClass('hidden', isCreatable);
},
/**
* Shows/hides action buttons
@ -837,20 +969,20 @@ window.FileList = {
* @param show true for enabling, false for disabling
*/
showActions: function(show){
$('.actions,#file_action_panel').toggleClass('hidden', !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;
$('.creatable').toggleClass('hidden', !isCreatable);
$('.notCreatable').toggleClass('hidden', isCreatable);
this.$el.find('.creatable').toggleClass('hidden', !isCreatable);
this.$el.find('.notCreatable').toggleClass('hidden', isCreatable);
// remove old style breadcrumbs (some apps might create them)
$('#controls .crumb').remove();
this.$el.find('#controls .crumb').remove();
// refresh breadcrumbs in case it was replaced by an app
this.breadcrumb.render();
}
else{
$('.creatable, .notCreatable').addClass('hidden');
this.$el.find('.creatable, .notCreatable').addClass('hidden');
}
},
/**
@ -861,7 +993,7 @@ window.FileList = {
*/
setViewerMode: function(show){
this.showActions(!show);
$('#filestable').toggleClass('hidden', show);
this.$el.find('#filestable').toggleClass('hidden', show);
},
/**
* Removes a file entry from the list
@ -872,7 +1004,7 @@ window.FileList = {
*/
remove: function(name, options){
options = options || {};
var fileEl = FileList.findFileEl(name);
var fileEl = this.findFileEl(name);
var index = fileEl.index();
if (!fileEl.length) {
return null;
@ -889,9 +1021,9 @@ window.FileList = {
this.files.splice(index, 1);
fileEl.remove();
// TODO: improve performance on batch update
FileList.isEmpty = !this.files.length;
this.isEmpty = !this.files.length;
if (typeof(options.updateSummary) === 'undefined' || !!options.updateSummary) {
FileList.updateEmptyContent();
this.updateEmptyContent();
this.fileSummary.remove({type: fileEl.attr('data-type'), size: fileEl.attr('data-size')}, true);
}
@ -989,8 +1121,9 @@ window.FileList = {
* @param oldname file name of the file to rename
*/
rename: function(oldname) {
var self = this;
var tr, td, input, form;
tr = FileList.findFileEl(oldname);
tr = this.findFileEl(oldname);
var oldFileInfo = this.files[tr.index()];
tr.data('renaming',true);
td = tr.children('td.filename');
@ -1012,7 +1145,7 @@ window.FileList = {
if (filename !== oldname) {
// Files.isFileNameValid(filename) throws an exception itself
Files.isFileNameValid(filename);
if (FileList.inList(filename)) {
if (self.inList(filename)) {
throw t('files', '{new_name} already exists', {new_name: filename});
}
}
@ -1031,7 +1164,7 @@ window.FileList = {
$.ajax({
url: OC.filePath('files','ajax','rename.php'),
data: {
dir : $('#dir').val(),
dir : self.getCurrentDirectory(),
newname: newName,
file: oldname
},
@ -1045,9 +1178,9 @@ window.FileList = {
fileInfo = result.data;
}
// reinsert row
FileList.files.splice(tr.index(), 1);
self.files.splice(tr.index(), 1);
tr.remove();
FileList.add(fileInfo);
self.add(fileInfo);
}
});
}
@ -1069,7 +1202,7 @@ window.FileList = {
td.find('a.name span.extension').text(newName.substr(newName.lastIndexOf('.')));
}
form.remove();
FileActions.display( tr.find('td.filename'), true);
self.fileActions.display( tr.find('td.filename'), true);
td.children('a.name').show();
} catch (error) {
input.attr('title', error);
@ -1107,7 +1240,7 @@ window.FileList = {
});
},
inList:function(file) {
return FileList.findFileEl(file).length;
return this.findFileEl(file).length;
},
/**
* Delete the given files from the given dir
@ -1116,23 +1249,24 @@ window.FileList = {
* 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 = FileList.findFileEl(files[i]).children("td.date").children(".action.delete");
var deleteAction = this.findFileEl(files[i]).children("td.date").children(".action.delete");
deleteAction.removeClass('delete-icon').addClass('progress-icon');
}
}
// Finish any existing actions
if (FileList.lastAction) {
FileList.lastAction();
if (this.lastAction) {
this.lastAction();
}
params = {
dir: dir || FileList.getCurrentDirectory()
dir: dir || this.getCurrentDirectory()
};
if (files) {
params.files = JSON.stringify(files);
@ -1149,23 +1283,22 @@ window.FileList = {
function(result) {
if (result.status === 'success') {
if (params.allfiles) {
FileList.setFiles([]);
self.setFiles([]);
}
else {
$.each(files,function(index,file) {
var fileEl = FileList.remove(file, {updateSummary: false});
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');
FileList.fileSummary.remove({type: fileEl.attr('data-type'), size: fileEl.attr('data-size')});
self.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();
self.updateEmptyContent();
self.fileSummary.update();
self.updateSelectionSummary();
Files.updateStorageStatistics();
} else {
if (result.status === 'error' && result.data.message) {
@ -1181,11 +1314,11 @@ window.FileList = {
if (params.allfiles) {
// reload the page as we don't know what files were deleted
// and which ones remain
FileList.reload();
self.reload();
}
else {
$.each(files,function(index,file) {
var deleteAction = FileList.findFileEl(file).find('.action.delete');
var deleteAction = self.findFileEl(file).find('.action.delete');
deleteAction.removeClass('progress-icon').addClass('delete-icon');
});
}
@ -1199,13 +1332,13 @@ window.FileList = {
var $tr = $('<tr class="summary"></tr>');
this.$el.find('tfoot').append($tr);
return new FileSummary($tr);
return new OCA.Files.FileSummary($tr);
},
updateEmptyContent: function() {
var permissions = $('#permissions').val();
var permissions = this.getDirectoryPermissions();
var isCreatable = (permissions & OC.PERMISSION_CREATE) !== 0;
$('#emptycontent').toggleClass('hidden', !isCreatable || !FileList.isEmpty);
$('#filestable thead th').toggleClass('hidden', FileList.isEmpty);
this.$el.find('#emptycontent').toggleClass('hidden', !isCreatable || !this.isEmpty);
this.$el.find('#filestable thead th').toggleClass('hidden', this.isEmpty);
},
/**
* Shows the loading mask.
@ -1214,18 +1347,18 @@ window.FileList = {
*/
showMask: function() {
// in case one was shown before
var $mask = $('#content .mask');
var $mask = this.$el.find('.mask');
if ($mask.exists()) {
return;
}
this.$el.addClass('hidden');
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');
$('#content').append($mask);
this.$el.append($mask);
$mask.removeClass('transparent');
},
@ -1234,12 +1367,12 @@ window.FileList = {
* @see #showMask
*/
hideMask: function() {
$('#content .mask').remove();
this.$el.removeClass('hidden');
this.$el.find('.mask').remove();
this.$table.removeClass('hidden');
},
scrollTo:function(file) {
//scroll to and highlight preselected file
var $scrollToRow = FileList.findFileEl(file);
var $scrollToRow = this.findFileEl(file);
if ($scrollToRow.exists()) {
$scrollToRow.addClass('searchresult');
$(window).scrollTop($scrollToRow.position().top);
@ -1250,7 +1383,7 @@ window.FileList = {
}
},
filter:function(query) {
$('#fileList tr').each(function(i,e) {
this.$fileList.find('tr').each(function(i,e) {
if ($(e).data('file').toString().toLowerCase().indexOf(query.toLowerCase()) !== -1) {
$(e).addClass("searchresult");
} else {
@ -1258,13 +1391,13 @@ window.FileList = {
}
});
//do not use scrollto to prevent removing searchresult css class
var first = $('#fileList tr.searchresult').first();
var first = this.$fileList.find('tr.searchresult').first();
if (first.exists()) {
$(window).scrollTop(first.position().top);
}
},
unfilter:function() {
$('#fileList tr.searchresult').each(function(i,e) {
this.$fileList.find('tr.searchresult').each(function(i,e) {
$(e).removeClass("searchresult");
});
},
@ -1274,15 +1407,15 @@ window.FileList = {
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');
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');
}
else {
$('.selectedActions').removeClass('hidden');
$('#headerSize a>span:first').text(OC.Util.humanFileSize(summary.totalSize));
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);
@ -1293,9 +1426,9 @@ window.FileList = {
if (summary.totalFiles > 0) {
selection += n('files', '%n file', '%n files', summary.totalFiles);
}
$('#headerName a.name>span:first').text(selection);
$('#modified a>span:first').text('');
$('table').addClass('multiselect');
this.$el.find('#headerName a.name>span:first').text(selection);
this.$el.find('#modified a>span:first').text('');
this.$el.find('table').addClass('multiselect');
}
},
@ -1314,14 +1447,40 @@ window.FileList = {
*/
getSelectedFiles: function() {
return _.values(this._selectedFiles);
}
};
},
$(document).ready(function() {
FileList.initialize();
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);
}
return name;
},
setupUploadEvents: function() {
var self = this;
// handle upload events
var fileUploadStart = $('#file_upload_start');
var fileUploadStart = this.$el.find('#file_upload_start');
fileUploadStart.on('fileuploaddrop', function(e, data) {
OC.Upload.log('filelist handle fileuploaddrop', e, data);
@ -1335,7 +1494,7 @@ $(document).ready(function() {
var dir = dropTarget.data('file');
// if from file list, need to prepend parent dir
if (dir) {
var parentDir = $('#dir').val() || '/';
var parentDir = self.getCurrentDirectory();
if (parentDir[parentDir.length - 1] !== '/') {
parentDir += '/';
}
@ -1356,7 +1515,7 @@ $(document).ready(function() {
};
} else {
// cancel uploads to current dir if no permission
var isCreatable = (FileList.getDirectoryPermissions() & OC.PERMISSION_CREATE) !== 0;
var isCreatable = (this.getDirectoryPermissions() & OC.PERMISSION_CREATE) !== 0;
if (!isCreatable) {
return false;
}
@ -1366,8 +1525,8 @@ $(document).ready(function() {
OC.Upload.log('filelist handle fileuploadadd', e, data);
//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
if (self.deleteFiles && self.deleteFiles.indexOf(data.files[0].name)!==-1) {
self.finishDelete(null, true); //delete file before continuing
}
// add ui visualization to existing folder
@ -1436,7 +1595,7 @@ $(document).ready(function() {
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()) {
if (file.directory !== '/' && file.directory !== self.getCurrentDirectory()) {
var fileDirectory = file.directory.replace('/','').replace(/\/$/, "").split('/');
@ -1444,7 +1603,7 @@ $(document).ready(function() {
fileDirectory = fileDirectory[0];
// Get the directory
var fd = FileList.findFileEl(fileDirectory);
var fd = self.findFileEl(fileDirectory);
if (fd.length === 0) {
var dir = {
name: fileDirectory,
@ -1454,13 +1613,13 @@ $(document).ready(function() {
size: 0,
id: file.parentId
};
FileList.add(dir, {insert: true});
self.add(dir, {insert: true});
}
} else {
fileDirectory = fileDirectory[0];
}
fileDirectory = FileList.findFileEl(fileDirectory);
fileDirectory = self.findFileEl(fileDirectory);
// update folder size
size = parseInt(fileDirectory.attr('data-size'), 10);
@ -1477,10 +1636,10 @@ $(document).ready(function() {
size=data.files[0].size;
}
//should the file exist in the list remove it
FileList.remove(file.name);
self.remove(file.name);
// create new file context
data.context = FileList.add(file, {animate: true});
data.context = self.add(file, {animate: true});
}
}
});
@ -1511,93 +1670,9 @@ $(document).ready(function() {
}
});
$('#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;
$(window).bind('beforeunload', function () {
if (FileList.lastAction) {
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.
*/
@ -1644,3 +1719,19 @@ FileList.Comparators = {
}
};
OCA.Files.FileList = FileList;
})();
$(document).ready(function() {
// FIXME: unused ?
OCA.Files.FileList.useUndo = (window.onbeforeunload)?true:false;
$(window).bind('beforeunload', function () {
if (OCA.Files.FileList.lastAction) {
OCA.Files.FileList.lastAction();
}
});
$(window).unload(function () {
$(window).trigger('beforeunload');
});
});

View File

@ -8,13 +8,19 @@
*
*/
/* global OC, t, FileList */
/* global getURLParameter */
/**
* 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 = FileList.getCurrentDirectory(),
var currentDir = OCA.Files.FileList.getCurrentDirectory(),
state = Files.updateStorageStatistics;
if (state.dir){
if (state.dir === currentDir) {
@ -171,7 +177,7 @@ var Files = {
filename = JSON.stringify(filename);
}
var params = {
dir: dir || FileList.getCurrentDirectory(),
dir: dir,
files: filename
};
return this.getAjaxUrl('download', params);
@ -188,18 +194,60 @@ var Files = {
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';
}
};
$(document).ready(function() {
// FIXME: workaround for trashbin app
if (window.trashBinApp) {
return;
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
});
},
/**
* Initialize the files view
*/
initialize: function() {
Files.getMimeIcon.cache = {};
Files.displayEncryptionWarning();
Files.bindKeyboardShortcuts(document, jQuery);
Files.setupDragAndDrop();
// TODO: move file list related code (upload) to OCA.Files.FileList
$('#file_action_panel').attr('activeAction', false);
// Triggers invisible file input
@ -211,12 +259,7 @@ $(document).ready(function() {
// Trigger cancelling of file upload
$('#uploadprogresswrapper .stop').on('click', function() {
OC.Upload.cancelUploads();
FileList.updateSelectionSummary();
});
// Show trash bin
$('#trash').on('click', function() {
window.location=OC.filePath('files_trashbin', '', 'index.php');
OCA.Files.FileList.updateSelectionSummary();
});
// drag&drop support using jquery.fileupload
@ -263,10 +306,16 @@ $(document).ready(function() {
});
//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;

View File

@ -190,6 +190,6 @@
this.$el.append($summary);
}
};
window.FileSummary = FileSummary;
OCA.Files.FileSummary = FileSummary;
})();

View File

@ -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();
},
@ -47,23 +47,48 @@
this.$el.on('click', 'li a', _.bind(this._onClickItem, this));
},
/**
* 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;
}
};

50
apps/files/list.php Normal file
View File

@ -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();

View File

@ -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 } ?>

View File

@ -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 dont 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']) ?>

View File

@ -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 dont 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>

View File

@ -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('/');
});
});
});
});

View File

@ -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() {

View File

@ -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();

File diff suppressed because it is too large Load Diff

View File

@ -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');

View File

@ -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() {

View File

@ -2,6 +2,10 @@
left: 0;
}
#filestable {
margin-top: 90px;
}
#preview {
background: #fff;
text-align: center;

View File

@ -9,8 +9,22 @@
*/
/* 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();
@ -40,9 +54,9 @@ $(document).ready(function() {
img.appendTo('#imgframe');
}
// override since the format is different
if (typeof Files !== 'undefined') {
Files.getDownloadUrl = function(filename, dir) {
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);
}
@ -57,13 +71,13 @@ $(document).ready(function() {
return OC.filePath('', '', 'public.php') + '?' + OC.buildQueryString(params);
};
Files.getAjaxUrl = function(action, 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) {
this.fileList.linkTo = function(dir) {
var params = {
service: 'files',
t: $('#sharingToken').val(),
@ -72,7 +86,7 @@ $(document).ready(function() {
return OC.filePath('', '', 'public.php') + '?' + OC.buildQueryString(params);
};
Files.generatePreviewUrl = function(urlSpec) {
this.fileList.generatePreviewUrl = function(urlSpec) {
urlSpec.t = $('#dirToken').val();
return OC.generateUrl('/apps/files_sharing/ajax/publicpreview.php?') + $.param(urlSpec);
};
@ -92,6 +106,17 @@ $(document).ready(function() {
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() {
@ -99,4 +124,26 @@ $(document).ready(function() {
$(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'));
});

View File

@ -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);

View File

@ -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);

View File

@ -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();

View File

@ -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);
});
});

View File

@ -1,3 +0,0 @@
/* disable download and sharing actions */
var disableDownloadActions = true;
var trashBinApp = true;

View File

@ -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,48 +17,66 @@
* @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 FileList = function($el) {
this.initialize($el);
};
FileList.prototype = _.extend({}, OCA.Files.FileList.prototype, {
appName: t('files_trashbin', 'Deleted files'),
initialize: function() {
var result = OCA.Files.FileList.prototype.initialize.apply(this, arguments);
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
* 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;
};
var oldSetCurrentDir = FileList._setCurrentDir;
FileList._setCurrentDir = function(targetDir) {
oldSetCurrentDir.apply(this, arguments);
return result;
},
_setCurrentDir: function(targetDir) {
OCA.Files.FileList.prototype._setCurrentDir.apply(this, arguments);
var baseDir = OC.basename(targetDir);
if (baseDir !== '') {
FileList.setPageTitle(FileList.getDeletedFileName(baseDir));
this.setPageTitle(getDeletedFileName(baseDir));
}
};
},
var oldCreateRow = FileList._createRow;
FileList._createRow = function() {
_createRow: function() {
// FIXME: MEGAHACK until we find a better solution
var tr = oldCreateRow.apply(this, arguments);
var tr = OCA.Files.FileList.prototype._createRow.apply(this, arguments);
tr.find('td.filesize').remove();
return tr;
};
},
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);
}
};
var oldRenderRow = FileList._renderRow;
FileList._renderRow = function(fileData, options) {
_renderRow: function(fileData, options) {
options = options || {};
var dir = FileList.getCurrentDirectory();
var dir = this.getCurrentDirectory();
var dirListing = dir !== '' && dir !== '/';
// show deleted time as mtime
if (fileData.mtime) {
@ -62,29 +86,28 @@
fileData.displayName = fileData.name;
fileData.name = fileData.name + '.d' + Math.floor(fileData.mtime / 1000);
}
return oldRenderRow.call(this, fileData, options);
};
return OCA.Files.FileList.prototype._renderRow.call(this, fileData, options);
},
FileList.linkTo = function(dir){
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, '/');
};
},
FileList.updateEmptyContent = function(){
var $fileList = $('#fileList');
var exists = $fileList.find('tr:first').exists();
$('#emptycontent').toggleClass('hidden', exists);
$('#filestable th').toggleClass('hidden', !exists);
};
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);
},
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) {
_removeCallback: function(result) {
if (result.status !== 'success') {
OC.dialogs.alert(result.data.message, t('files_trashbin', 'Error'));
}
@ -92,36 +115,37 @@
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();
$el = this.remove(OC.basename(files[i].filename), {updateSummary: false});
this.fileSummary.remove({type: $el.attr('data-type'), size: $el.attr('data-size')});
}
this.fileSummary.update();
this.updateEmptyContent();
this.enableActions();
},
FileList._onClickRestoreSelected = function(event) {
_onClickRestoreSelected: function(event) {
event.preventDefault();
var allFiles = $('#select_all').is(':checked');
var self = this;
var allFiles = this.$el.find('#select_all').is(':checked');
var files = [];
var params = {};
disableActions();
this.disableActions();
if (allFiles) {
FileList.showMask();
this.showMask();
params = {
allfiles: true,
dir: FileList.getCurrentDirectory()
dir: this.getCurrentDirectory()
};
}
else {
files = _.pluck(FileList.getSelectedFiles(), 'name');
files = _.pluck(this.getSelectedFiles(), 'name');
for (var i = 0; i < files.length; i++) {
var deleteAction = FileList.findFileEl(files[i]).children("td.date").children(".action.delete");
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: FileList.getCurrentDirectory()
dir: this.getCurrentDirectory()
};
}
@ -132,44 +156,45 @@
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);
}
}
);
};
},
FileList._onClickDeleteSelected = function(event) {
_onClickDeleteSelected: function(event) {
event.preventDefault();
var allFiles = $('#select_all').is(':checked');
var self = this;
var allFiles = this.$el.find('#select_all').is(':checked');
var files = [];
var params = {};
if (allFiles) {
params = {
allfiles: true,
dir: FileList.getCurrentDirectory()
dir: this.getCurrentDirectory()
};
}
else {
files = _.pluck(FileList.getSelectedFiles(), 'name');
files = _.pluck(this.getSelectedFiles(), 'name');
params = {
files: JSON.stringify(files),
dir: FileList.getCurrentDirectory()
dir: this.getCurrentDirectory()
};
}
disableActions();
this.disableActions();
if (allFiles) {
FileList.showMask();
this.showMask();
}
else {
for (var i = 0; i < files.length; i++) {
var deleteAction = FileList.findFileEl(files[i]).children("td.date").children(".action.delete");
var deleteAction = this.findFileEl(files[i]).children("td.date").children(".action.delete");
deleteAction.removeClass('delete-icon').addClass('progress-icon');
}
}
@ -181,25 +206,47 @@
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) {
_onClickFile: function(event) {
var mime = $(this).parent().parent().data('mime');
if (mime !== 'httpd/unix-directory') {
event.preventDefault();
}
return oldClickFile.apply(this, arguments);
};
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');
}
});
OCA.Trashbin.FileList = FileList;
})();

View File

@ -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;
})();

View File

@ -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");
}

View File

@ -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>

View File

@ -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');
});
});
});

View File

@ -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);
});
});
});
});

View File

@ -1318,6 +1318,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

View File

@ -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