- 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 namespacestags/v7.0.0alpha2
@@ -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') | |||
) | |||
); |
@@ -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%; | |||
} |
@@ -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); | |||
@@ -11,15 +11,117 @@ | |||
* | |||
*/ | |||
if (!OCA.Files) { | |||
OCA.Files = {}; | |||
} | |||
(function() { | |||
$(document).ready(function() { | |||
var nav = new OCA.Files.Navigation($('#app-navigation ul')); | |||
if (!OCA.Files) { | |||
OCA.Files = {}; | |||
} | |||
var App = { | |||
navigation: null, | |||
initialize: function() { | |||
this.navigation = new OCA.Files.Navigation($('#app-navigation')); | |||
// TODO: ideally these should be in a separate class / app (the embedded "all files" app) | |||
this.fileList = OCA.Files.FileList; | |||
this.fileActions = OCA.Files.FileActions; | |||
this.files = OCA.Files.Files; | |||
this.fileList = new OCA.Files.FileList($('#app-content-files')); | |||
this.files.initialize(); | |||
this.fileActions.registerDefaultActions(this.fileList); | |||
this.fileList.setFileActions(this.fileActions); | |||
// for backward compatibility, the global FileList will | |||
// refer to the one of the "files" view | |||
window.FileList = this.fileList; | |||
this._setupEvents(); | |||
// trigger URL change event handlers | |||
this._onPopState(OC.Util.History.parseUrlQuery()); | |||
}, | |||
/** | |||
* Returns the container of the currently visible app. | |||
* | |||
* @return app container | |||
*/ | |||
getCurrentAppContainer: function() { | |||
return this.navigation.getActiveContainer(); | |||
}, | |||
nav.setSelectedItem('files'); | |||
/** | |||
* Setup events based on URL changes | |||
*/ | |||
_setupEvents: function() { | |||
OC.Util.History.addOnPopStateHandler(_.bind(this._onPopState, this)); | |||
// TODO: init file list, actions and others | |||
// detect when app changed their current directory | |||
$('#app-content>div').on('changeDirectory', _.bind(this._onDirectoryChanged, this)); | |||
$('#app-navigation').on('itemChanged', _.bind(this._onNavigationChanged, this)); | |||
}, | |||
/** | |||
* Event handler for when the current navigation item has changed | |||
*/ | |||
_onNavigationChanged: function(e) { | |||
var params; | |||
if (e && e.itemId) { | |||
params = { | |||
view: e.itemId, | |||
dir: '/' | |||
}; | |||
this._changeUrl(params.view, params.dir); | |||
this.navigation.getActiveContainer().trigger(new $.Event('urlChanged', params)); | |||
} | |||
}, | |||
/** | |||
* Event handler for when an app notified that its directory changed | |||
*/ | |||
_onDirectoryChanged: function(e) { | |||
if (e.dir) { | |||
this._changeUrl(this.navigation.getActiveItem(), e.dir); | |||
} | |||
}, | |||
/** | |||
* Event handler for when the URL changed | |||
*/ | |||
_onPopState: function(params) { | |||
params = _.extend({ | |||
dir: '/', | |||
view: 'files' | |||
}, params); | |||
var lastId = this.navigation.getActiveItem(); | |||
this.navigation.setActiveItem(params.view, {silent: true}); | |||
if (lastId !== this.navigation.getActiveItem()) { | |||
this.navigation.getActiveContainer().trigger(new $.Event('show')); | |||
} | |||
this.navigation.getActiveContainer().trigger(new $.Event('urlChanged', params)); | |||
}, | |||
/** | |||
* Change the URL to point to the given dir and view | |||
*/ | |||
_changeUrl: function(view, dir) { | |||
var params = {dir: dir}; | |||
if (view !== 'files') { | |||
params.view = view; | |||
} | |||
OC.Util.History.pushState(params); | |||
} | |||
}; | |||
OCA.Files.App = App; | |||
})(); | |||
$(document).ready(function() { | |||
// wait for other apps/extensions to register their event handlers | |||
// in the "ready" clause | |||
_.defer(function() { | |||
OCA.Files.App.initialize(); | |||
}); | |||
}); | |||
@@ -236,6 +236,6 @@ | |||
} | |||
}; | |||
window.BreadCrumb = BreadCrumb; | |||
OCA.Files.BreadCrumb = BreadCrumb; | |||
})(); | |||
@@ -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 | |||
} |
@@ -8,242 +8,253 @@ | |||
* | |||
*/ | |||
/* global OC, FileList, Files */ | |||
/* global trashBinApp */ | |||
var FileActions = { | |||
actions: {}, | |||
defaults: {}, | |||
icons: {}, | |||
currentFile: null, | |||
register: function (mime, name, permissions, icon, action, displayName) { | |||
if (!FileActions.actions[mime]) { | |||
FileActions.actions[mime] = {}; | |||
} | |||
if (!FileActions.actions[mime][name]) { | |||
FileActions.actions[mime][name] = {}; | |||
} | |||
if (!displayName) { | |||
displayName = t('files', name); | |||
} | |||
FileActions.actions[mime][name]['action'] = action; | |||
FileActions.actions[mime][name]['permissions'] = permissions; | |||
FileActions.actions[mime][name]['displayName'] = displayName; | |||
FileActions.icons[name] = icon; | |||
}, | |||
setDefault: function (mime, name) { | |||
FileActions.defaults[mime] = name; | |||
}, | |||
get: function (mime, type, permissions) { | |||
var actions = this.getActions(mime, type, permissions); | |||
var filteredActions = {}; | |||
$.each(actions, function (name, action) { | |||
filteredActions[name] = action.action; | |||
}); | |||
return filteredActions; | |||
}, | |||
getActions: function (mime, type, permissions) { | |||
var actions = {}; | |||
if (FileActions.actions.all) { | |||
actions = $.extend(actions, FileActions.actions.all); | |||
} | |||
if (type) {//type is 'dir' or 'file' | |||
if (FileActions.actions[type]) { | |||
actions = $.extend(actions, FileActions.actions[type]); | |||
(function() { | |||
var FileActions = { | |||
actions: {}, | |||
defaults: {}, | |||
icons: {}, | |||
currentFile: null, | |||
register: function (mime, name, permissions, icon, action, displayName) { | |||
if (!this.actions[mime]) { | |||
this.actions[mime] = {}; | |||
} | |||
} | |||
if (mime) { | |||
var mimePart = mime.substr(0, mime.indexOf('/')); | |||
if (FileActions.actions[mimePart]) { | |||
actions = $.extend(actions, FileActions.actions[mimePart]); | |||
if (!this.actions[mime][name]) { | |||
this.actions[mime][name] = {}; | |||
} | |||
if (FileActions.actions[mime]) { | |||
actions = $.extend(actions, FileActions.actions[mime]); | |||
if (!displayName) { | |||
displayName = t('files', name); | |||
} | |||
} | |||
var filteredActions = {}; | |||
$.each(actions, function (name, action) { | |||
if (action.permissions & permissions) { | |||
filteredActions[name] = action; | |||
this.actions[mime][name]['action'] = action; | |||
this.actions[mime][name]['permissions'] = permissions; | |||
this.actions[mime][name]['displayName'] = displayName; | |||
this.icons[name] = icon; | |||
}, | |||
clear: function() { | |||
this.actions = {}; | |||
this.defaults = {}; | |||
this.icons = {}; | |||
this.currentFile = null; | |||
}, | |||
setDefault: function (mime, name) { | |||
this.defaults[mime] = name; | |||
}, | |||
get: function (mime, type, permissions) { | |||
var actions = this.getActions(mime, type, permissions); | |||
var filteredActions = {}; | |||
$.each(actions, function (name, action) { | |||
filteredActions[name] = action.action; | |||
}); | |||
return filteredActions; | |||
}, | |||
getActions: function (mime, type, permissions) { | |||
var actions = {}; | |||
if (this.actions.all) { | |||
actions = $.extend(actions, this.actions.all); | |||
} | |||
}); | |||
return filteredActions; | |||
}, | |||
getDefault: function (mime, type, permissions) { | |||
var mimePart; | |||
if (mime) { | |||
mimePart = mime.substr(0, mime.indexOf('/')); | |||
} | |||
var name = false; | |||
if (mime && FileActions.defaults[mime]) { | |||
name = FileActions.defaults[mime]; | |||
} else if (mime && FileActions.defaults[mimePart]) { | |||
name = FileActions.defaults[mimePart]; | |||
} else if (type && FileActions.defaults[type]) { | |||
name = FileActions.defaults[type]; | |||
} else { | |||
name = FileActions.defaults.all; | |||
} | |||
var actions = this.get(mime, type, permissions); | |||
return actions[name]; | |||
}, | |||
/** | |||
* Display file actions for the given element | |||
* @param parent "td" element of the file for which to display actions | |||
* @param triggerEvent if true, triggers the fileActionsReady on the file | |||
* list afterwards (false by default) | |||
*/ | |||
display: function (parent, triggerEvent) { | |||
FileActions.currentFile = parent; | |||
var actions = FileActions.getActions(FileActions.getCurrentMimeType(), FileActions.getCurrentType(), FileActions.getCurrentPermissions()); | |||
var file = FileActions.getCurrentFile(); | |||
var nameLinks; | |||
if (FileList.findFileEl(file).data('renaming')) { | |||
return; | |||
} | |||
if (type) {//type is 'dir' or 'file' | |||
if (this.actions[type]) { | |||
actions = $.extend(actions, this.actions[type]); | |||
} | |||
} | |||
if (mime) { | |||
var mimePart = mime.substr(0, mime.indexOf('/')); | |||
if (this.actions[mimePart]) { | |||
actions = $.extend(actions, this.actions[mimePart]); | |||
} | |||
if (this.actions[mime]) { | |||
actions = $.extend(actions, this.actions[mime]); | |||
} | |||
} | |||
var filteredActions = {}; | |||
$.each(actions, function (name, action) { | |||
if (action.permissions & permissions) { | |||
filteredActions[name] = action; | |||
} | |||
}); | |||
return filteredActions; | |||
}, | |||
getDefault: function (mime, type, permissions) { | |||
var mimePart; | |||
if (mime) { | |||
mimePart = mime.substr(0, mime.indexOf('/')); | |||
} | |||
var name = false; | |||
if (mime && this.defaults[mime]) { | |||
name = this.defaults[mime]; | |||
} else if (mime && this.defaults[mimePart]) { | |||
name = this.defaults[mimePart]; | |||
} else if (type && this.defaults[type]) { | |||
name = this.defaults[type]; | |||
} else { | |||
name = this.defaults.all; | |||
} | |||
var actions = this.get(mime, type, permissions); | |||
return actions[name]; | |||
}, | |||
/** | |||
* Display file actions for the given element | |||
* @param parent "td" element of the file for which to display actions | |||
* @param triggerEvent if true, triggers the fileActionsReady on the file | |||
* list afterwards (false by default) | |||
*/ | |||
display: function (parent, triggerEvent) { | |||
this.currentFile = parent; | |||
var self = this; | |||
var actions = this.getActions(this.getCurrentMimeType(), this.getCurrentType(), this.getCurrentPermissions()); | |||
var file = this.getCurrentFile(); | |||
var nameLinks; | |||
if (parent.closest('tr').data('renaming')) { | |||
return; | |||
} | |||
// recreate fileactions | |||
nameLinks = parent.children('a.name'); | |||
nameLinks.find('.fileactions, .nametext .action').remove(); | |||
nameLinks.append('<span class="fileactions" />'); | |||
var defaultAction = this.getDefault(this.getCurrentMimeType(), this.getCurrentType(), this.getCurrentPermissions()); | |||
// recreate fileactions | |||
nameLinks = parent.children('a.name'); | |||
nameLinks.find('.fileactions, .nametext .action').remove(); | |||
nameLinks.append('<span class="fileactions" />'); | |||
var defaultAction = FileActions.getDefault(FileActions.getCurrentMimeType(), FileActions.getCurrentType(), FileActions.getCurrentPermissions()); | |||
var actionHandler = function (event) { | |||
event.stopPropagation(); | |||
event.preventDefault(); | |||
var actionHandler = function (event) { | |||
event.stopPropagation(); | |||
event.preventDefault(); | |||
self.currentFile = event.data.elem; | |||
var file = self.getCurrentFile(); | |||
FileActions.currentFile = event.data.elem; | |||
var file = FileActions.getCurrentFile(); | |||
event.data.actionFunc(file); | |||
}; | |||
event.data.actionFunc(file); | |||
}; | |||
var addAction = function (name, action, displayName) { | |||
var addAction = function (name, action, displayName) { | |||
if ((name === 'Download' || action !== defaultAction) && name !== 'Delete') { | |||
if ((name === 'Download' || action !== defaultAction) && name !== 'Delete') { | |||
var img = self.icons[name], | |||
actionText = displayName, | |||
actionContainer = 'a.name>span.fileactions'; | |||
var img = FileActions.icons[name], | |||
actionText = displayName, | |||
actionContainer = 'a.name>span.fileactions'; | |||
if (name === 'Rename') { | |||
// rename has only an icon which appears behind | |||
// the file name | |||
actionText = ''; | |||
actionContainer = 'a.name span.nametext'; | |||
} | |||
if (img.call) { | |||
img = img(file); | |||
} | |||
var html = '<a href="#" class="action action-' + name.toLowerCase() + '" data-action="' + name + '">'; | |||
if (img) { | |||
html += '<img class ="svg" src="' + img + '" />'; | |||
} | |||
html += '<span> ' + actionText + '</span></a>'; | |||
if (name === 'Rename') { | |||
// rename has only an icon which appears behind | |||
// the file name | |||
actionText = ''; | |||
actionContainer = 'a.name span.nametext'; | |||
var element = $(html); | |||
element.data('action', name); | |||
element.on('click', {a: null, elem: parent, actionFunc: actions[name].action}, actionHandler); | |||
parent.find(actionContainer).append(element); | |||
} | |||
}; | |||
$.each(actions, function (name, action) { | |||
if (name !== 'Share') { | |||
displayName = action.displayName; | |||
ah = action.action; | |||
addAction(name, ah, displayName); | |||
} | |||
}); | |||
if(actions.Share){ | |||
displayName = t('files', 'Share'); | |||
addAction('Share', actions.Share, displayName); | |||
} | |||
// remove the existing delete action | |||
parent.parent().children().last().find('.action.delete').remove(); | |||
if (actions['Delete']) { | |||
var img = self.icons['Delete']; | |||
var html; | |||
if (img.call) { | |||
img = img(file); | |||
} | |||
var html = '<a href="#" class="action action-' + name.toLowerCase() + '" data-action="' + name + '">'; | |||
if (img) { | |||
html += '<img class ="svg" src="' + img + '" />'; | |||
if (typeof trashBinApp !== 'undefined' && trashBinApp) { | |||
html = '<a href="#" original-title="' + t('files', 'Delete permanently') + '" class="action delete delete-icon" />'; | |||
} else { | |||
html = '<a href="#" class="action delete delete-icon" />'; | |||
} | |||
html += '<span> ' + actionText + '</span></a>'; | |||
var element = $(html); | |||
element.data('action', name); | |||
element.on('click', {a: null, elem: parent, actionFunc: actions[name].action}, actionHandler); | |||
parent.find(actionContainer).append(element); | |||
element.data('action', actions['Delete']); | |||
element.on('click', {a: null, elem: parent, actionFunc: actions['Delete'].action}, actionHandler); | |||
parent.parent().children().last().append(element); | |||
} | |||
}; | |||
if (triggerEvent){ | |||
$('#fileList').trigger(jQuery.Event("fileActionsReady")); | |||
} | |||
}, | |||
getCurrentFile: function () { | |||
return this.currentFile.parent().attr('data-file'); | |||
}, | |||
getCurrentMimeType: function () { | |||
return this.currentFile.parent().attr('data-mime'); | |||
}, | |||
getCurrentType: function () { | |||
return this.currentFile.parent().attr('data-type'); | |||
}, | |||
getCurrentPermissions: function () { | |||
return this.currentFile.parent().data('permissions'); | |||
}, | |||
$.each(actions, function (name, action) { | |||
if (name !== 'Share') { | |||
displayName = action.displayName; | |||
ah = action.action; | |||
/** | |||
* Register the actions that are used by default for the files app. | |||
*/ | |||
registerDefaultActions: function(fileList) { | |||
this.register('all', 'Delete', OC.PERMISSION_DELETE, function () { | |||
return OC.imagePath('core', 'actions/delete'); | |||
}, function (filename) { | |||
fileList.do_delete(filename); | |||
$('.tipsy').remove(); | |||
}); | |||
addAction(name, ah, displayName); | |||
} | |||
}); | |||
if(actions.Share){ | |||
displayName = t('files', 'Share'); | |||
addAction('Share', actions.Share, displayName); | |||
} | |||
// t('files', 'Rename') | |||
this.register('all', 'Rename', OC.PERMISSION_UPDATE, function () { | |||
return OC.imagePath('core', 'actions/rename'); | |||
}, function (filename) { | |||
fileList.rename(filename); | |||
}); | |||
// remove the existing delete action | |||
parent.parent().children().last().find('.action.delete').remove(); | |||
if (actions['Delete']) { | |||
var img = FileActions.icons['Delete']; | |||
var html; | |||
if (img.call) { | |||
img = img(file); | |||
} | |||
if (typeof trashBinApp !== 'undefined' && trashBinApp) { | |||
html = '<a href="#" original-title="' + t('files', 'Delete permanently') + '" class="action delete delete-icon" />'; | |||
this.register('dir', 'Open', OC.PERMISSION_READ, '', function (filename) { | |||
var dir = fileList.getCurrentDirectory(); | |||
if (dir !== '/') { | |||
dir = dir + '/'; | |||
} | |||
fileList.changeDirectory(dir + filename); | |||
}); | |||
this.setDefault('dir', 'Open'); | |||
var downloadScope; | |||
if ($('#allowZipDownload').val() == 1) { | |||
downloadScope = 'all'; | |||
} else { | |||
html = '<a href="#" class="action delete delete-icon" />'; | |||
downloadScope = 'file'; | |||
} | |||
var element = $(html); | |||
element.data('action', actions['Delete']); | |||
element.on('click', {a: null, elem: parent, actionFunc: actions['Delete'].action}, actionHandler); | |||
parent.parent().children().last().append(element); | |||
} | |||
if (triggerEvent){ | |||
$('#fileList').trigger(jQuery.Event("fileActionsReady")); | |||
this.register(downloadScope, 'Download', OC.PERMISSION_READ, function () { | |||
return OC.imagePath('core', 'actions/download'); | |||
}, function (filename) { | |||
var url = OCA.Files.Files.getDownloadUrl(filename, fileList.getCurrentDirectory()); | |||
if (url) { | |||
OC.redirect(url); | |||
} | |||
}); | |||
fileList.$fileList.trigger(jQuery.Event("fileActionsReady")); | |||
} | |||
}, | |||
getCurrentFile: function () { | |||
return FileActions.currentFile.parent().attr('data-file'); | |||
}, | |||
getCurrentMimeType: function () { | |||
return FileActions.currentFile.parent().attr('data-mime'); | |||
}, | |||
getCurrentType: function () { | |||
return FileActions.currentFile.parent().attr('data-type'); | |||
}, | |||
getCurrentPermissions: function () { | |||
return FileActions.currentFile.parent().data('permissions'); | |||
} | |||
}; | |||
$(document).ready(function () { | |||
var downloadScope; | |||
if ($('#allowZipDownload').val() == 1) { | |||
downloadScope = 'all'; | |||
} else { | |||
downloadScope = 'file'; | |||
} | |||
if (typeof disableDownloadActions == 'undefined' || !disableDownloadActions) { | |||
FileActions.register(downloadScope, 'Download', OC.PERMISSION_READ, function () { | |||
return OC.imagePath('core', 'actions/download'); | |||
}, function (filename) { | |||
var url = Files.getDownloadUrl(filename); | |||
if (url) { | |||
OC.redirect(url); | |||
} | |||
}); | |||
} | |||
$('#fileList tr').each(function () { | |||
FileActions.display($(this).children('td.filename')); | |||
}); | |||
$('#fileList').trigger(jQuery.Event("fileActionsReady")); | |||
}); | |||
FileActions.register('all', 'Delete', OC.PERMISSION_DELETE, function () { | |||
return OC.imagePath('core', 'actions/delete'); | |||
}, function (filename) { | |||
FileList.do_delete(filename); | |||
$('.tipsy').remove(); | |||
}); | |||
// t('files', 'Rename') | |||
FileActions.register('all', 'Rename', OC.PERMISSION_UPDATE, function () { | |||
return OC.imagePath('core', 'actions/rename'); | |||
}, function (filename) { | |||
FileList.rename(filename); | |||
}); | |||
FileActions.register('dir', 'Open', OC.PERMISSION_READ, '', function (filename) { | |||
var dir = $('#dir').val() || '/'; | |||
if (dir !== '/') { | |||
dir = dir + '/'; | |||
} | |||
FileList.changeDirectory(dir + filename); | |||
}); | |||
FileActions.setDefault('dir', 'Open'); | |||
}; | |||
OCA.Files.FileActions = FileActions; | |||
})(); | |||
// for backward compatibility | |||
window.FileActions = OCA.Files.FileActions; | |||
@@ -8,265 +8,314 @@ | |||
* | |||
*/ | |||
/* global OC, t, FileList */ | |||
/* global getURLParameter */ | |||
var Files = { | |||
// file space size sync | |||
_updateStorageStatistics: function() { | |||
Files._updateStorageStatisticsTimeout = null; | |||
var currentDir = FileList.getCurrentDirectory(), | |||
state = Files.updateStorageStatistics; | |||
if (state.dir){ | |||
if (state.dir === currentDir) { | |||
/** | |||
* Utility class for file related operations | |||
*/ | |||
(function() { | |||
var Files = { | |||
// file space size sync | |||
_updateStorageStatistics: function() { | |||
// FIXME | |||
console.warn('FIXME: storage statistics!'); | |||
return; | |||
Files._updateStorageStatisticsTimeout = null; | |||
var currentDir = OCA.Files.FileList.getCurrentDirectory(), | |||
state = Files.updateStorageStatistics; | |||
if (state.dir){ | |||
if (state.dir === currentDir) { | |||
return; | |||
} | |||
// cancel previous call, as it was for another dir | |||
state.call.abort(); | |||
} | |||
state.dir = currentDir; | |||
state.call = $.getJSON(OC.filePath('files','ajax','getstoragestats.php') + '?dir=' + encodeURIComponent(currentDir),function(response) { | |||
state.dir = null; | |||
state.call = null; | |||
Files.updateMaxUploadFilesize(response); | |||
}); | |||
}, | |||
updateStorageStatistics: function(force) { | |||
if (!OC.currentUser) { | |||
return; | |||
} | |||
// cancel previous call, as it was for another dir | |||
state.call.abort(); | |||
} | |||
state.dir = currentDir; | |||
state.call = $.getJSON(OC.filePath('files','ajax','getstoragestats.php') + '?dir=' + encodeURIComponent(currentDir),function(response) { | |||
state.dir = null; | |||
state.call = null; | |||
Files.updateMaxUploadFilesize(response); | |||
}); | |||
}, | |||
updateStorageStatistics: function(force) { | |||
if (!OC.currentUser) { | |||
return; | |||
} | |||
// debounce to prevent calling too often | |||
if (Files._updateStorageStatisticsTimeout) { | |||
clearTimeout(Files._updateStorageStatisticsTimeout); | |||
} | |||
if (force) { | |||
Files._updateStorageStatistics(); | |||
} | |||
else { | |||
Files._updateStorageStatisticsTimeout = setTimeout(Files._updateStorageStatistics, 250); | |||
} | |||
}, | |||
// debounce to prevent calling too often | |||
if (Files._updateStorageStatisticsTimeout) { | |||
clearTimeout(Files._updateStorageStatisticsTimeout); | |||
} | |||
if (force) { | |||
Files._updateStorageStatistics(); | |||
} | |||
else { | |||
Files._updateStorageStatisticsTimeout = setTimeout(Files._updateStorageStatistics, 250); | |||
} | |||
}, | |||
updateMaxUploadFilesize:function(response) { | |||
if (response === undefined) { | |||
return; | |||
} | |||
if (response.data !== undefined && response.data.uploadMaxFilesize !== undefined) { | |||
$('#max_upload').val(response.data.uploadMaxFilesize); | |||
$('#free_space').val(response.data.freeSpace); | |||
$('#upload.button').attr('original-title', response.data.maxHumanFilesize); | |||
$('#usedSpacePercent').val(response.data.usedSpacePercent); | |||
Files.displayStorageWarnings(); | |||
} | |||
if (response[0] === undefined) { | |||
return; | |||
} | |||
if (response[0].uploadMaxFilesize !== undefined) { | |||
$('#max_upload').val(response[0].uploadMaxFilesize); | |||
$('#upload.button').attr('original-title', response[0].maxHumanFilesize); | |||
$('#usedSpacePercent').val(response[0].usedSpacePercent); | |||
Files.displayStorageWarnings(); | |||
} | |||
updateMaxUploadFilesize:function(response) { | |||
if (response === undefined) { | |||
return; | |||
} | |||
if (response.data !== undefined && response.data.uploadMaxFilesize !== undefined) { | |||
$('#max_upload').val(response.data.uploadMaxFilesize); | |||
$('#free_space').val(response.data.freeSpace); | |||
$('#upload.button').attr('original-title', response.data.maxHumanFilesize); | |||
$('#usedSpacePercent').val(response.data.usedSpacePercent); | |||
Files.displayStorageWarnings(); | |||
} | |||
if (response[0] === undefined) { | |||
return; | |||
} | |||
if (response[0].uploadMaxFilesize !== undefined) { | |||
$('#max_upload').val(response[0].uploadMaxFilesize); | |||
$('#upload.button').attr('original-title', response[0].maxHumanFilesize); | |||
$('#usedSpacePercent').val(response[0].usedSpacePercent); | |||
Files.displayStorageWarnings(); | |||
} | |||
}, | |||
}, | |||
/** | |||
* Fix path name by removing double slash at the beginning, if any | |||
*/ | |||
fixPath: function(fileName) { | |||
if (fileName.substr(0, 2) == '//') { | |||
return fileName.substr(1); | |||
} | |||
return fileName; | |||
}, | |||
/** | |||
* Fix path name by removing double slash at the beginning, if any | |||
*/ | |||
fixPath: function(fileName) { | |||
if (fileName.substr(0, 2) == '//') { | |||
return fileName.substr(1); | |||
} | |||
return fileName; | |||
}, | |||
/** | |||
* Checks whether the given file name is valid. | |||
* @param name file name to check | |||
* @return true if the file name is valid. | |||
* Throws a string exception with an error message if | |||
* the file name is not valid | |||
*/ | |||
isFileNameValid: function (name) { | |||
var trimmedName = name.trim(); | |||
if (trimmedName === '.' || trimmedName === '..') | |||
{ | |||
throw t('files', '"{name}" is an invalid file name.', {name: name}); | |||
} else if (trimmedName.length === 0) { | |||
throw t('files', 'File name cannot be empty.'); | |||
} | |||
// check for invalid characters | |||
var invalidCharacters = | |||
['\\', '/', '<', '>', ':', '"', '|', '?', '*', '\n']; | |||
for (var i = 0; i < invalidCharacters.length; i++) { | |||
if (trimmedName.indexOf(invalidCharacters[i]) !== -1) { | |||
throw t('files', "Invalid name, '\\', '/', '<', '>', ':', '\"', '|', '?' and '*' are not allowed."); | |||
} | |||
} | |||
return true; | |||
}, | |||
displayStorageWarnings: function() { | |||
if (!OC.Notification.isHidden()) { | |||
return; | |||
} | |||
/** | |||
* Checks whether the given file name is valid. | |||
* @param name file name to check | |||
* @return true if the file name is valid. | |||
* Throws a string exception with an error message if | |||
* the file name is not valid | |||
*/ | |||
isFileNameValid: function (name) { | |||
var trimmedName = name.trim(); | |||
if (trimmedName === '.' || trimmedName === '..') | |||
{ | |||
throw t('files', '"{name}" is an invalid file name.', {name: name}); | |||
} else if (trimmedName.length === 0) { | |||
throw t('files', 'File name cannot be empty.'); | |||
} | |||
// check for invalid characters | |||
var invalidCharacters = | |||
['\\', '/', '<', '>', ':', '"', '|', '?', '*', '\n']; | |||
for (var i = 0; i < invalidCharacters.length; i++) { | |||
if (trimmedName.indexOf(invalidCharacters[i]) !== -1) { | |||
throw t('files', "Invalid name, '\\', '/', '<', '>', ':', '\"', '|', '?' and '*' are not allowed."); | |||
var usedSpacePercent = $('#usedSpacePercent').val(); | |||
if (usedSpacePercent > 98) { | |||
OC.Notification.show(t('files', 'Your storage is full, files can not be updated or synced anymore!')); | |||
return; | |||
} | |||
} | |||
return true; | |||
}, | |||
displayStorageWarnings: function() { | |||
if (!OC.Notification.isHidden()) { | |||
return; | |||
} | |||
if (usedSpacePercent > 90) { | |||
OC.Notification.show(t('files', 'Your storage is almost full ({usedSpacePercent}%)', | |||
{usedSpacePercent: usedSpacePercent})); | |||
} | |||
}, | |||
var usedSpacePercent = $('#usedSpacePercent').val(); | |||
if (usedSpacePercent > 98) { | |||
OC.Notification.show(t('files', 'Your storage is full, files can not be updated or synced anymore!')); | |||
return; | |||
} | |||
if (usedSpacePercent > 90) { | |||
OC.Notification.show(t('files', 'Your storage is almost full ({usedSpacePercent}%)', | |||
{usedSpacePercent: usedSpacePercent})); | |||
} | |||
}, | |||
displayEncryptionWarning: function() { | |||
displayEncryptionWarning: function() { | |||
if (!OC.Notification.isHidden()) { | |||
return; | |||
} | |||
if (!OC.Notification.isHidden()) { | |||
return; | |||
} | |||
var encryptedFiles = $('#encryptedFiles').val(); | |||
var initStatus = $('#encryptionInitStatus').val(); | |||
if (initStatus === '0') { // enc not initialized, but should be | |||
OC.Notification.show(t('files', 'Encryption App is enabled but your keys are not initialized, please log-out and log-in again')); | |||
return; | |||
} | |||
if (initStatus === '1') { // encryption tried to init but failed | |||
OC.Notification.show(t('files', 'Invalid private key for Encryption App. Please update your private key password in your personal settings to recover access to your encrypted files.')); | |||
return; | |||
} | |||
if (encryptedFiles === '1') { | |||
OC.Notification.show(t('files', 'Encryption was disabled but your files are still encrypted. Please go to your personal settings to decrypt your files.')); | |||
return; | |||
} | |||
}, | |||
var encryptedFiles = $('#encryptedFiles').val(); | |||
var initStatus = $('#encryptionInitStatus').val(); | |||
if (initStatus === '0') { // enc not initialized, but should be | |||
OC.Notification.show(t('files', 'Encryption App is enabled but your keys are not initialized, please log-out and log-in again')); | |||
return; | |||
} | |||
if (initStatus === '1') { // encryption tried to init but failed | |||
OC.Notification.show(t('files', 'Invalid private key for Encryption App. Please update your private key password in your personal settings to recover access to your encrypted files.')); | |||
return; | |||
} | |||
if (encryptedFiles === '1') { | |||
OC.Notification.show(t('files', 'Encryption was disabled but your files are still encrypted. Please go to your personal settings to decrypt your files.')); | |||
return; | |||
} | |||
}, | |||
// TODO: move to FileList class | |||
setupDragAndDrop: function() { | |||
var $fileList = $('#fileList'); | |||
// TODO: move to FileList class | |||
setupDragAndDrop: function() { | |||
var $fileList = $('#fileList'); | |||
//drag/drop of files | |||
$fileList.find('tr td.filename').each(function(i,e) { | |||
if ($(e).parent().data('permissions') & OC.PERMISSION_DELETE) { | |||
$(e).draggable(dragOptions); | |||
} | |||
}); | |||
//drag/drop of files | |||
$fileList.find('tr td.filename').each(function(i,e) { | |||
if ($(e).parent().data('permissions') & OC.PERMISSION_DELETE) { | |||
$(e).draggable(dragOptions); | |||
$fileList.find('tr[data-type="dir"] td.filename').each(function(i,e) { | |||
if ($(e).parent().data('permissions') & OC.PERMISSION_CREATE) { | |||
$(e).droppable(folderDropOptions); | |||
} | |||
}); | |||
}, | |||
/** | |||
* Returns the download URL of the given file(s) | |||
* @param filename string or array of file names to download | |||
* @param dir optional directory in which the file name is, defaults to the current directory | |||
*/ | |||
getDownloadUrl: function(filename, dir) { | |||
if ($.isArray(filename)) { | |||
filename = JSON.stringify(filename); | |||
} | |||
}); | |||
$fileList.find('tr[data-type="dir"] td.filename').each(function(i,e) { | |||
if ($(e).parent().data('permissions') & OC.PERMISSION_CREATE) { | |||
$(e).droppable(folderDropOptions); | |||
var params = { | |||
dir: dir, | |||
files: filename | |||
}; | |||
return this.getAjaxUrl('download', params); | |||
}, | |||
/** | |||
* Returns the ajax URL for a given action | |||
* @param action action string | |||
* @param params optional params map | |||
*/ | |||
getAjaxUrl: function(action, params) { | |||
var q = ''; | |||
if (params) { | |||
q = '?' + OC.buildQueryString(params); | |||
} | |||
}); | |||
}, | |||
return OC.filePath('files', 'ajax', action + '.php') + q; | |||
}, | |||
getMimeIcon: function(mime, ready) { | |||
if (Files.getMimeIcon.cache[mime]) { | |||
ready(Files.getMimeIcon.cache[mime]); | |||
} else { | |||
$.get( OC.filePath('files','ajax','mimeicon.php'), {mime: mime}, function(path) { | |||
if(OC.Util.hasSVGSupport()){ | |||
path = path.substr(0, path.length-4) + '.svg'; | |||
} | |||
Files.getMimeIcon.cache[mime]=path; | |||
ready(Files.getMimeIcon.cache[mime]); | |||
}); | |||
} | |||
}, | |||
/** | |||
* Generates a preview URL based on the URL space. | |||
* @param urlSpec map with {x: width, y: height, file: file path} | |||
* @return preview URL | |||
* @deprecated used OCA.Files.FileList.generatePreviewUrl instead | |||
*/ | |||
generatePreviewUrl: function(urlSpec) { | |||
console.warn('DEPRECATED: please use generatePreviewUrl() from an OCA.Files.FileList instance'); | |||
return OCA.Files.App.fileList.generatePreviewUrl(urlSpec); | |||
}, | |||
/** | |||
* Lazy load preview | |||
* @deprecated used OCA.Files.FileList.lazyLoadPreview instead | |||
*/ | |||
lazyLoadPreview : function(path, mime, ready, width, height, etag) { | |||
console.warn('DEPRECATED: please use lazyLoadPreview() from an OCA.Files.FileList instance'); | |||
return OCA.Files.App.fileList.lazyLoadPreview({ | |||
path: path, | |||
mime: mime, | |||
callback: ready, | |||
width: width, | |||
height: height, | |||
etag: etag | |||
}); | |||
}, | |||
/** | |||
* Returns the download URL of the given file(s) | |||
* @param filename string or array of file names to download | |||
* @param dir optional directory in which the file name is, defaults to the current directory | |||
*/ | |||
getDownloadUrl: function(filename, dir) { | |||
if ($.isArray(filename)) { | |||
filename = JSON.stringify(filename); | |||
} | |||
var params = { | |||
dir: dir || FileList.getCurrentDirectory(), | |||
files: filename | |||
}; | |||
return this.getAjaxUrl('download', params); | |||
}, | |||
/** | |||
* Initialize the files view | |||
*/ | |||
initialize: function() { | |||
Files.getMimeIcon.cache = {}; | |||
Files.displayEncryptionWarning(); | |||
Files.bindKeyboardShortcuts(document, jQuery); | |||
/** | |||
* Returns the ajax URL for a given action | |||
* @param action action string | |||
* @param params optional params map | |||
*/ | |||
getAjaxUrl: function(action, params) { | |||
var q = ''; | |||
if (params) { | |||
q = '?' + OC.buildQueryString(params); | |||
} | |||
return OC.filePath('files', 'ajax', action + '.php') + q; | |||
} | |||
}; | |||
$(document).ready(function() { | |||
// FIXME: workaround for trashbin app | |||
if (window.trashBinApp) { | |||
return; | |||
} | |||
Files.displayEncryptionWarning(); | |||
Files.bindKeyboardShortcuts(document, jQuery); | |||
Files.setupDragAndDrop(); | |||
Files.setupDragAndDrop(); | |||
// TODO: move file list related code (upload) to OCA.Files.FileList | |||
$('#file_action_panel').attr('activeAction', false); | |||
$('#file_action_panel').attr('activeAction', false); | |||
// Triggers invisible file input | |||
$('#upload a').on('click', function() { | |||
$(this).parent().children('#file_upload_start').trigger('click'); | |||
return false; | |||
}); | |||
// Triggers invisible file input | |||
$('#upload a').on('click', function() { | |||
$(this).parent().children('#file_upload_start').trigger('click'); | |||
return false; | |||
}); | |||
// Trigger cancelling of file upload | |||
$('#uploadprogresswrapper .stop').on('click', function() { | |||
OC.Upload.cancelUploads(); | |||
FileList.updateSelectionSummary(); | |||
}); | |||
// Trigger cancelling of file upload | |||
$('#uploadprogresswrapper .stop').on('click', function() { | |||
OC.Upload.cancelUploads(); | |||
OCA.Files.FileList.updateSelectionSummary(); | |||
}); | |||
// Show trash bin | |||
$('#trash').on('click', function() { | |||
window.location=OC.filePath('files_trashbin', '', 'index.php'); | |||
}); | |||
// drag&drop support using jquery.fileupload | |||
// TODO use OC.dialogs | |||
$(document).bind('drop dragover', function (e) { | |||
e.preventDefault(); // prevent browser from doing anything, if file isn't dropped in dropZone | |||
}); | |||
//do a background scan if needed | |||
scanFiles(); | |||
// display storage warnings | |||
setTimeout(Files.displayStorageWarnings, 100); | |||
OC.Notification.setDefault(Files.displayStorageWarnings); | |||
// only possible at the moment if user is logged in | |||
if (OC.currentUser) { | |||
// start on load - we ask the server every 5 minutes | |||
var updateStorageStatisticsInterval = 5*60*1000; | |||
var updateStorageStatisticsIntervalId = setInterval(Files.updateStorageStatistics, updateStorageStatisticsInterval); | |||
// Use jquery-visibility to de-/re-activate file stats sync | |||
if ($.support.pageVisibility) { | |||
$(document).on({ | |||
'show.visibility': function() { | |||
if (!updateStorageStatisticsIntervalId) { | |||
updateStorageStatisticsIntervalId = setInterval(Files.updateStorageStatistics, updateStorageStatisticsInterval); | |||
} | |||
}, | |||
'hide.visibility': function() { | |||
clearInterval(updateStorageStatisticsIntervalId); | |||
updateStorageStatisticsIntervalId = 0; | |||
} | |||
}); | |||
} | |||
} | |||
// drag&drop support using jquery.fileupload | |||
// TODO use OC.dialogs | |||
$(document).bind('drop dragover', function (e) { | |||
e.preventDefault(); // prevent browser from doing anything, if file isn't dropped in dropZone | |||
}); | |||
//do a background scan if needed | |||
scanFiles(); | |||
// display storage warnings | |||
setTimeout(Files.displayStorageWarnings, 100); | |||
OC.Notification.setDefault(Files.displayStorageWarnings); | |||
// only possible at the moment if user is logged in | |||
if (OC.currentUser) { | |||
// start on load - we ask the server every 5 minutes | |||
var updateStorageStatisticsInterval = 5*60*1000; | |||
var updateStorageStatisticsIntervalId = setInterval(Files.updateStorageStatistics, updateStorageStatisticsInterval); | |||
// Use jquery-visibility to de-/re-activate file stats sync | |||
if ($.support.pageVisibility) { | |||
$(document).on({ | |||
'show.visibility': function() { | |||
if (!updateStorageStatisticsIntervalId) { | |||
updateStorageStatisticsIntervalId = setInterval(Files.updateStorageStatistics, updateStorageStatisticsInterval); | |||
} | |||
}, | |||
'hide.visibility': function() { | |||
clearInterval(updateStorageStatisticsIntervalId); | |||
updateStorageStatisticsIntervalId = 0; | |||
$('#app-settings-header').on('click', function() { | |||
var $settings = $('#app-settings'); | |||
$settings.toggleClass('opened'); | |||
if ($settings.hasClass('opened')) { | |||
$settings.find('input').focus(); | |||
} | |||
}); | |||
} | |||
} | |||
$('#app-settings-header').on('click', function() { | |||
var $settings = $('#app-settings'); | |||
$settings.toggleClass('opened'); | |||
if ($settings.hasClass('opened')) { | |||
$settings.find('input').focus(); | |||
//scroll to and highlight preselected file | |||
/* | |||
if (getURLParameter('scrollto')) { | |||
FileList.scrollTo(getURLParameter('scrollto')); | |||
} | |||
*/ | |||
} | |||
}); | |||
//scroll to and highlight preselected file | |||
if (getURLParameter('scrollto')) { | |||
FileList.scrollTo(getURLParameter('scrollto')); | |||
} | |||
}); | |||
OCA.Files.Files = Files; | |||
})(); | |||
function scanFiles(force, dir, users) { | |||
if (!OC.currentUser) { | |||
@@ -300,7 +349,7 @@ function scanFiles(force, dir, users) { | |||
scannerEventSource.listen('done',function(count) { | |||
scanFiles.scanning=false; | |||
console.log('done after ' + count + ' files'); | |||
Files.updateStorageStatistics(); | |||
OCA.Files.Files.updateStorageStatistics(); | |||
}); | |||
scannerEventSource.listen('user',function(user) { | |||
console.log('scanning files for ' + user); | |||
@@ -311,6 +360,7 @@ scanFiles.scanning=false; | |||
// TODO: move to FileList | |||
var createDragShadow = function(event) { | |||
//select dragged file | |||
var FileList = OCA.Files.FileList; | |||
var isDragSelected = $(event.target).parents('tr').find('td input:first').prop('checked'); | |||
if (!isDragSelected) { | |||
//select dragged file | |||
@@ -331,7 +381,7 @@ var createDragShadow = function(event) { | |||
var tbody = $('<tbody></tbody>'); | |||
dragshadow.append(tbody); | |||
var dir=$('#dir').val(); | |||
var dir = FileList.getCurrentDirectory(); | |||
$(selectedFiles).each(function(i,elem) { | |||
var newtr = $('<tr/>') | |||
@@ -344,8 +394,8 @@ var createDragShadow = function(event) { | |||
if (elem.type === 'dir') { | |||
newtr.find('td.filename').attr('style','background-image:url('+OC.imagePath('core', 'filetypes/folder.png')+')'); | |||
} else { | |||
var path = getPathForPreview(elem.name); | |||
Files.lazyLoadPreview(path, elem.mime, function(previewpath) { | |||
var path = dir + '/' + elem.name; | |||
OCA.Files.App.fileList.lazyLoadPreview(path, elem.mime, function(previewpath) { | |||
newtr.find('td.filename').attr('style','background-image:url('+previewpath+')'); | |||
}, null, null, elem.etag); | |||
} | |||
@@ -358,9 +408,14 @@ var createDragShadow = function(event) { | |||
//start&stop handlers needs some cleaning up | |||
// TODO: move to FileList class | |||
var dragOptions={ | |||
revert: 'invalid', revertDuration: 300, | |||
opacity: 0.7, zIndex: 100, appendTo: 'body', cursorAt: { left: 24, top: 18 }, | |||
helper: createDragShadow, cursor: 'move', | |||
revert: 'invalid', | |||
revertDuration: 300, | |||
opacity: 0.7, | |||
zIndex: 100, | |||
appendTo: 'body', | |||
cursorAt: { left: 24, top: 18 }, | |||
helper: createDragShadow, | |||
cursor: 'move', | |||
start: function(event, ui){ | |||
var $selectedFiles = $('td.filename input:checkbox:checked'); | |||
if($selectedFiles.length > 1){ | |||
@@ -391,6 +446,7 @@ var folderDropOptions = { | |||
hoverClass: "canDrop", | |||
drop: function( event, ui ) { | |||
// don't allow moving a file into a selected folder | |||
var FileList = OCA.Files.FileList; | |||
if ($(event.target).parents('tr').find('td input:first').prop('checked') === true) { | |||
return false; | |||
} | |||
@@ -408,115 +464,11 @@ var folderDropOptions = { | |||
tolerance: 'pointer' | |||
}; | |||
Files.getMimeIcon = function(mime, ready) { | |||
if (Files.getMimeIcon.cache[mime]) { | |||
ready(Files.getMimeIcon.cache[mime]); | |||
} else { | |||
$.get( OC.filePath('files','ajax','mimeicon.php'), {mime: mime}, function(path) { | |||
if(OC.Util.hasSVGSupport()){ | |||
path = path.substr(0, path.length-4) + '.svg'; | |||
} | |||
Files.getMimeIcon.cache[mime]=path; | |||
ready(Files.getMimeIcon.cache[mime]); | |||
}); | |||
} | |||
} | |||
Files.getMimeIcon.cache={}; | |||
function getPathForPreview(name) { | |||
var path = $('#dir').val() + '/' + name; | |||
return path; | |||
} | |||
/** | |||
* Generates a preview URL based on the URL space. | |||
* @param urlSpec map with {x: width, y: height, file: file path} | |||
* @return preview URL | |||
*/ | |||
Files.generatePreviewUrl = function(urlSpec) { | |||
urlSpec = urlSpec || {}; | |||
if (!urlSpec.x) { | |||
urlSpec.x = $('#filestable').data('preview-x'); | |||
} | |||
if (!urlSpec.y) { | |||
urlSpec.y = $('#filestable').data('preview-y'); | |||
} | |||
urlSpec.y *= window.devicePixelRatio; | |||
urlSpec.x *= window.devicePixelRatio; | |||
urlSpec.forceIcon = 0; | |||
return OC.generateUrl('/core/preview.png?') + $.param(urlSpec); | |||
}; | |||
Files.lazyLoadPreview = function(path, mime, ready, width, height, etag) { | |||
// get mime icon url | |||
Files.getMimeIcon(mime, function(iconURL) { | |||
var previewURL, | |||
urlSpec = {}; | |||
ready(iconURL); // set mimeicon URL | |||
urlSpec.file = Files.fixPath(path); | |||
if (etag){ | |||
// use etag as cache buster | |||
urlSpec.c = etag; | |||
} | |||
else { | |||
console.warn('Files.lazyLoadPreview(): missing etag argument'); | |||
} | |||
previewURL = Files.generatePreviewUrl(urlSpec); | |||
previewURL = previewURL.replace('(', '%28'); | |||
previewURL = previewURL.replace(')', '%29'); | |||
// preload image to prevent delay | |||
// this will make the browser cache the image | |||
var img = new Image(); | |||
img.onload = function(){ | |||
// if loading the preview image failed (no preview for the mimetype) then img.width will < 5 | |||
if (img.width > 5) { | |||
ready(previewURL); | |||
} | |||
}; | |||
img.src = previewURL; | |||
}); | |||
}; | |||
function getUniqueName(name) { | |||
if (FileList.findFileEl(name).exists()) { | |||
var numMatch; | |||
var parts=name.split('.'); | |||
var extension = ""; | |||
if (parts.length > 1) { | |||
extension=parts.pop(); | |||
} | |||
var base=parts.join('.'); | |||
numMatch=base.match(/\((\d+)\)/); | |||
var num=2; | |||
if (numMatch && numMatch.length>0) { | |||
num=parseInt(numMatch[numMatch.length-1])+1; | |||
base=base.split('('); | |||
base.pop(); | |||
base=$.trim(base.join('(')); | |||
} | |||
name=base+' ('+num+')'; | |||
if (extension) { | |||
name = name+'.'+extension; | |||
} | |||
return getUniqueName(name); | |||
} | |||
return name; | |||
} | |||
function checkTrashStatus() { | |||
$.post(OC.filePath('files_trashbin', 'ajax', 'isEmpty.php'), function(result) { | |||
if (result.data.isEmpty === false) { | |||
$("input[type=button][id=trash]").removeAttr("disabled"); | |||
} | |||
}); | |||
} | |||
// override core's fileDownloadPath (legacy) | |||
function fileDownloadPath(dir, file) { | |||
return Files.getDownloadUrl(file, dir); | |||
return OCA.Files.Files.getDownloadUrl(file, dir); | |||
} | |||
// for backward compatibility | |||
window.Files = OCA.Files.Files; | |||
@@ -190,6 +190,6 @@ | |||
this.$el.append($summary); | |||
} | |||
}; | |||
window.FileSummary = FileSummary; | |||
OCA.Files.FileSummary = FileSummary; | |||
})(); | |||
@@ -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; | |||
} | |||
}; | |||
@@ -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(); | |||
@@ -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 } ?> |
@@ -1,117 +1,6 @@ | |||
<?php /** @var $l OC_L10N */ ?> | |||
<?php $_['appNavigation']->printPage(); ?> | |||
<div id="app-content"> | |||
<div id="app-content-files"> | |||
<div id="controls"> | |||
<div class="actions creatable hidden"> | |||
<?php if(!isset($_['dirToken'])):?> | |||
<div id="new" class="button"> | |||
<a><?php p($l->t('New'));?></a> | |||
<ul> | |||
<li class="icon-filetype-text svg" | |||
data-type="file" data-newname="<?php p($l->t('New text file')) ?>.txt"> | |||
<p><?php p($l->t('Text file'));?></p> | |||
</li> | |||
<li class="icon-filetype-folder svg" | |||
data-type="folder" data-newname="<?php p($l->t('New folder')) ?>"> | |||
<p><?php p($l->t('Folder'));?></p> | |||
</li> | |||
<li class="icon-link svg" data-type="web"> | |||
<p><?php p($l->t('From link'));?></p> | |||
</li> | |||
</ul> | |||
</div> | |||
<?php endif;?> | |||
<div id="upload" class="button" | |||
title="<?php p($l->t('Upload (max. %s)', array($_['uploadMaxHumanFilesize']))) ?>"> | |||
<?php if($_['uploadMaxFilesize'] >= 0):?> | |||
<input type="hidden" id="max_upload" name="MAX_FILE_SIZE" value="<?php p($_['uploadMaxFilesize']) ?>"> | |||
<?php endif;?> | |||
<input type="hidden" id="upload_limit" value="<?php p($_['uploadLimit']) ?>"> | |||
<input type="hidden" id="free_space" value="<?php p($_['freeSpace']) ?>"> | |||
<?php if(isset($_['dirToken'])):?> | |||
<input type="hidden" id="publicUploadRequestToken" name="requesttoken" value="<?php p($_['requesttoken']) ?>" /> | |||
<input type="hidden" id="dirToken" name="dirToken" value="<?php p($_['dirToken']) ?>" /> | |||
<?php endif;?> | |||
<input type="hidden" class="max_human_file_size" | |||
value="(max <?php p($_['uploadMaxHumanFilesize']); ?>)"> | |||
<input type="hidden" name="dir" value="<?php p($_['dir']) ?>" id="dir"> | |||
<input type="file" id="file_upload_start" name='files[]' | |||
data-url="<?php print_unescaped(OCP\Util::linkTo('files', 'ajax/upload.php')); ?>" /> | |||
<a href="#" class="svg icon-upload"></a> | |||
</div> | |||
<div id="uploadprogresswrapper"> | |||
<div id="uploadprogressbar"></div> | |||
<input type="button" class="stop" style="display:none" | |||
value="<?php p($l->t('Cancel upload'));?>" | |||
/> | |||
</div> | |||
</div> | |||
<div id="file_action_panel"></div> | |||
<div class="notCreatable notPublic hidden"> | |||
<?php p($l->t('You don’t have permission to upload or create files here'))?> | |||
</div> | |||
<input type="hidden" name="permissions" value="<?php p($_['permissions']); ?>" id="permissions"> | |||
</div> | |||
<div id="emptycontent" class="hidden"><?php p($l->t('Nothing in here. Upload something!'))?></div> | |||
<input type="hidden" id="disableSharing" data-status="<?php p($_['disableSharing']); ?>" /> | |||
<table id="filestable" data-allow-public-upload="<?php p($_['publicUploadEnabled'])?>" data-preview-x="36" data-preview-y="36"> | |||
<thead> | |||
<tr> | |||
<th id='headerName' class="hidden column-name"> | |||
<div id="headerName-container"> | |||
<input type="checkbox" id="select_all" /> | |||
<label for="select_all"></label> | |||
<a class="name sort columntitle" data-sort="name"><span><?php p($l->t( 'Name' )); ?></span><span class="sort-indicator"></span></a> | |||
<span id="selectedActionsList" class="selectedActions"> | |||
<?php if($_['allowZipDownload']) : ?> | |||
<a href="" class="download"> | |||
<img class="svg" alt="Download" | |||
src="<?php print_unescaped(OCP\image_path("core", "actions/download.svg")); ?>" /> | |||
<?php p($l->t('Download'))?> | |||
</a> | |||
<?php endif; ?> | |||
</span> | |||
</div> | |||
</th> | |||
<th id="headerSize" class="hidden column-size"> | |||
<a class="size sort columntitle" data-sort="size"><span><?php p($l->t('Size')); ?></span><span class="sort-indicator"></span></a> | |||
</th> | |||
<th id="headerDate" class="hidden column-mtime"> | |||
<a id="modified" class="columntitle" data-sort="mtime"><span><?php p($l->t( 'Modified' )); ?></span><span class="sort-indicator"></span></a> | |||
<?php if ($_['permissions'] & OCP\PERMISSION_DELETE): ?> | |||
<span class="selectedActions"><a href="" class="delete-selected"> | |||
<?php p($l->t('Delete'))?> | |||
<img class="svg" alt="<?php p($l->t('Delete'))?>" | |||
src="<?php print_unescaped(OCP\image_path("core", "actions/delete.svg")); ?>" /> | |||
</a></span> | |||
<?php endif; ?> | |||
</th> | |||
</tr> | |||
</thead> | |||
<tbody id="fileList"> | |||
</tbody> | |||
<tfoot> | |||
</tfoot> | |||
</table> | |||
<div id="editor"></div><!-- FIXME Do not use this div in your app! It is deprecated and will be removed in the future! --> | |||
<div id="uploadsize-message" title="<?php p($l->t('Upload too large'))?>"> | |||
<p> | |||
<?php p($l->t('The files you are trying to upload exceed the maximum size for file uploads on this server.'));?> | |||
</p> | |||
</div> | |||
<div id="scanning-message"> | |||
<h3> | |||
<?php p($l->t('Files are being scanned, please wait.'));?> <span id='scan-count'></span> | |||
</h3> | |||
<p> | |||
<?php p($l->t('Current scanning'));?> <span id='scan-current'></span> | |||
</p> | |||
</div> | |||
</div><!-- closing app-content-files --> | |||
<?php foreach ($_['appContents'] as $content) { ?> | |||
<div id="app-content-<?php p($content['id']) ?>" class="hidden"> | |||
<?php print_unescaped($content['content']) ?> |
@@ -0,0 +1,104 @@ | |||
<div id="controls"> | |||
<div class="actions creatable hidden"> | |||
<?php if(!isset($_['dirToken'])):?> | |||
<div id="new" class="button"> | |||
<a><?php p($l->t('New'));?></a> | |||
<ul> | |||
<li class="icon-filetype-text svg" | |||
data-type="file" data-newname="<?php p($l->t('New text file')) ?>.txt"> | |||
<p><?php p($l->t('Text file'));?></p> | |||
</li> | |||
<li class="icon-filetype-folder svg" | |||
data-type="folder" data-newname="<?php p($l->t('New folder')) ?>"> | |||
<p><?php p($l->t('Folder'));?></p> | |||
</li> | |||
<li class="icon-link svg" data-type="web"> | |||
<p><?php p($l->t('From link'));?></p> | |||
</li> | |||
</ul> | |||
</div> | |||
<?php endif;?> | |||
<div id="upload" class="button" | |||
title="<?php p($l->t('Upload (max. %s)', array($_['uploadMaxHumanFilesize']))) ?>"> | |||
<?php if($_['uploadMaxFilesize'] >= 0):?> | |||
<input type="hidden" id="max_upload" name="MAX_FILE_SIZE" value="<?php p($_['uploadMaxFilesize']) ?>"> | |||
<?php endif;?> | |||
<input type="hidden" id="upload_limit" value="<?php p($_['uploadLimit']) ?>"> | |||
<input type="hidden" id="free_space" value="<?php p($_['freeSpace']) ?>"> | |||
<?php if(isset($_['dirToken'])):?> | |||
<input type="hidden" id="publicUploadRequestToken" name="requesttoken" value="<?php p($_['requesttoken']) ?>" /> | |||
<input type="hidden" id="dirToken" name="dirToken" value="<?php p($_['dirToken']) ?>" /> | |||
<?php endif;?> | |||
<input type="hidden" class="max_human_file_size" | |||
value="(max <?php p($_['uploadMaxHumanFilesize']); ?>)"> | |||
<input type="file" id="file_upload_start" name='files[]' | |||
data-url="<?php print_unescaped(OCP\Util::linkTo('files', 'ajax/upload.php')); ?>" /> | |||
<a href="#" class="svg icon-upload"></a> | |||
</div> | |||
<div id="uploadprogresswrapper"> | |||
<div id="uploadprogressbar"></div> | |||
<input type="button" class="stop" style="display:none" | |||
value="<?php p($l->t('Cancel upload'));?>" | |||
/> | |||
</div> | |||
</div> | |||
<div id="file_action_panel"></div> | |||
<div class="notCreatable notPublic hidden"> | |||
<?php p($l->t('You don’t have permission to upload or create files here'))?> | |||
</div> | |||
<input type="hidden" name="permissions" value="" id="permissions"> | |||
</div> | |||
<div id="emptycontent" class="hidden"><?php p($l->t('Nothing in here. Upload something!'))?></div> | |||
<table id="filestable" data-allow-public-upload="<?php p($_['publicUploadEnabled'])?>" data-preview-x="36" data-preview-y="36"> | |||
<thead> | |||
<tr> | |||
<th id='headerName' class="hidden column-name"> | |||
<div id="headerName-container"> | |||
<input type="checkbox" id="select_all" /> | |||
<label for="select_all"></label> | |||
<a class="name sort columntitle" data-sort="name"><span><?php p($l->t( 'Name' )); ?></span><span class="sort-indicator"></span></a> | |||
<span id="selectedActionsList" class="selectedActions"> | |||
<?php if($_['allowZipDownload']) : ?> | |||
<a href="" class="download"> | |||
<img class="svg" alt="Download" | |||
src="<?php print_unescaped(OCP\image_path("core", "actions/download.svg")); ?>" /> | |||
<?php p($l->t('Download'))?> | |||
</a> | |||
<?php endif; ?> | |||
</span> | |||
</div> | |||
</th> | |||
<th id="headerSize" class="hidden column-size"> | |||
<a class="size sort columntitle" data-sort="size"><span><?php p($l->t('Size')); ?></span><span class="sort-indicator"></span></a> | |||
</th> | |||
<th id="headerDate" class="hidden column-mtime"> | |||
<a id="modified" class="columntitle" data-sort="mtime"><span><?php p($l->t( 'Modified' )); ?></span><span class="sort-indicator"></span></a> | |||
<span class="selectedActions"><a href="" class="delete-selected"> | |||
<?php p($l->t('Delete'))?> | |||
<img class="svg" alt="<?php p($l->t('Delete'))?>" | |||
src="<?php print_unescaped(OCP\image_path("core", "actions/delete.svg")); ?>" /> | |||
</a></span> | |||
</th> | |||
</tr> | |||
</thead> | |||
<tbody id="fileList"> | |||
</tbody> | |||
<tfoot> | |||
</tfoot> | |||
</table> | |||
<div id="editor"></div><!-- FIXME Do not use this div in your app! It is deprecated and will be removed in the future! --> | |||
<div id="uploadsize-message" title="<?php p($l->t('Upload too large'))?>"> | |||
<p> | |||
<?php p($l->t('The files you are trying to upload exceed the maximum size for file uploads on this server.'));?> | |||
</p> | |||
</div> | |||
<div id="scanning-message"> | |||
<h3> | |||
<?php p($l->t('Files are being scanned, please wait.'));?> <span id='scan-count'></span> | |||
</h3> | |||
<p> | |||
<?php p($l->t('Current scanning'));?> <span id='scan-current'></span> | |||
</p> | |||
</div> |
@@ -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('/'); | |||
}); | |||
}); | |||
}); | |||
}); |
@@ -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() { |
@@ -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(); |
@@ -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'); |
@@ -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() { |
@@ -2,6 +2,10 @@ | |||
left: 0; | |||
} | |||
#filestable { | |||
margin-top: 90px; | |||
} | |||
#preview { | |||
background: #fff; | |||
text-align: center; |
@@ -9,94 +9,141 @@ | |||
*/ | |||
/* global OC, FileActions, FileList, Files */ | |||
OCA.Sharing = {}; | |||
if (!OCA.Files) { | |||
OCA.Files = {}; | |||
} | |||
OCA.Sharing.PublicApp = { | |||
_initialized: false, | |||
$(document).ready(function() { | |||
initialize: function($el) { | |||
if (this._initialized) { | |||
return; | |||
} | |||
this._initialized = true; | |||
// file list mode ? | |||
if ($el.find('#filestable')) { | |||
this.fileList = new OCA.Files.FileList($el); | |||
} | |||
var mimetype = $('#mimetype').val(); | |||
var mimetype = $('#mimetype').val(); | |||
if (typeof FileActions !== 'undefined') { | |||
// Show file preview if previewer is available, images are already handled by the template | |||
if (mimetype.substr(0, mimetype.indexOf('/')) !== 'image' && $('.publicpreview').length === 0) { | |||
// Trigger default action if not download TODO | |||
var action = FileActions.getDefault(mimetype, 'file', OC.PERMISSION_READ); | |||
if (typeof action !== 'undefined') { | |||
action($('#filename').val()); | |||
if (typeof FileActions !== 'undefined') { | |||
// Show file preview if previewer is available, images are already handled by the template | |||
if (mimetype.substr(0, mimetype.indexOf('/')) !== 'image' && $('.publicpreview').length === 0) { | |||
// Trigger default action if not download TODO | |||
var action = FileActions.getDefault(mimetype, 'file', OC.PERMISSION_READ); | |||
if (typeof action !== 'undefined') { | |||
action($('#filename').val()); | |||
} | |||
} | |||
} | |||
} | |||
// dynamically load image previews | |||
if (mimetype.substr(0, mimetype.indexOf('/')) === 'image' ) { | |||
// dynamically load image previews | |||
if (mimetype.substr(0, mimetype.indexOf('/')) === 'image' ) { | |||
var params = { | |||
x: $(document).width() * window.devicePixelRatio, | |||
a: 'true', | |||
file: encodeURIComponent($('#dir').val() + $('#filename').val()), | |||
t: $('#sharingToken').val() | |||
}; | |||
var params = { | |||
x: $(document).width() * window.devicePixelRatio, | |||
a: 'true', | |||
file: encodeURIComponent($('#dir').val() + $('#filename').val()), | |||
t: $('#sharingToken').val() | |||
}; | |||
var img = $('<img class="publicpreview">'); | |||
img.attr('src', OC.filePath('files_sharing', 'ajax', 'publicpreview.php') + '?' + OC.buildQueryString(params)); | |||
img.appendTo('#imgframe'); | |||
} | |||
var img = $('<img class="publicpreview">'); | |||
img.attr('src', OC.filePath('files_sharing', 'ajax', 'publicpreview.php') + '?' + OC.buildQueryString(params)); | |||
img.appendTo('#imgframe'); | |||
} | |||
// override since the format is different | |||
if (typeof Files !== 'undefined') { | |||
Files.getDownloadUrl = function(filename, dir) { | |||
if ($.isArray(filename)) { | |||
filename = JSON.stringify(filename); | |||
} | |||
var path = dir || FileList.getCurrentDirectory(); | |||
var params = { | |||
service: 'files', | |||
t: $('#sharingToken').val(), | |||
path: path, | |||
files: filename, | |||
download: null | |||
if (this.fileList) { | |||
// TODO: move this to a separate PublicFileList class that extends OCA.Files.FileList (+ unit tests) | |||
this.fileList.getDownloadUrl = function(filename, dir) { | |||
if ($.isArray(filename)) { | |||
filename = JSON.stringify(filename); | |||
} | |||
var path = dir || FileList.getCurrentDirectory(); | |||
var params = { | |||
service: 'files', | |||
t: $('#sharingToken').val(), | |||
path: path, | |||
files: filename, | |||
download: null | |||
}; | |||
return OC.filePath('', '', 'public.php') + '?' + OC.buildQueryString(params); | |||
}; | |||
return OC.filePath('', '', 'public.php') + '?' + OC.buildQueryString(params); | |||
}; | |||
Files.getAjaxUrl = function(action, params) { | |||
params = params || {}; | |||
params.t = $('#sharingToken').val(); | |||
return OC.filePath('files_sharing', 'ajax', action + '.php') + '?' + OC.buildQueryString(params); | |||
}; | |||
this.fileList.getAjaxUrl = function(action, params) { | |||
params = params || {}; | |||
params.t = $('#sharingToken').val(); | |||
return OC.filePath('files_sharing', 'ajax', action + '.php') + '?' + OC.buildQueryString(params); | |||
}; | |||
FileList.linkTo = function(dir) { | |||
var params = { | |||
service: 'files', | |||
t: $('#sharingToken').val(), | |||
dir: dir | |||
this.fileList.linkTo = function(dir) { | |||
var params = { | |||
service: 'files', | |||
t: $('#sharingToken').val(), | |||
dir: dir | |||
}; | |||
return OC.filePath('', '', 'public.php') + '?' + OC.buildQueryString(params); | |||
}; | |||
return OC.filePath('', '', 'public.php') + '?' + OC.buildQueryString(params); | |||
}; | |||
Files.generatePreviewUrl = function(urlSpec) { | |||
urlSpec.t = $('#dirToken').val(); | |||
return OC.generateUrl('/apps/files_sharing/ajax/publicpreview.php?') + $.param(urlSpec); | |||
}; | |||
var file_upload_start = $('#file_upload_start'); | |||
file_upload_start.on('fileuploadadd', function(e, data) { | |||
var fileDirectory = ''; | |||
if(typeof data.files[0].relativePath !== 'undefined') { | |||
fileDirectory = data.files[0].relativePath; | |||
} | |||
// Add custom data to the upload handler | |||
data.formData = { | |||
requesttoken: $('#publicUploadRequestToken').val(), | |||
dirToken: $('#dirToken').val(), | |||
subdir: $('input#dir').val(), | |||
file_directory: fileDirectory | |||
this.fileList.generatePreviewUrl = function(urlSpec) { | |||
urlSpec.t = $('#dirToken').val(); | |||
return OC.generateUrl('/apps/files_sharing/ajax/publicpreview.php?') + $.param(urlSpec); | |||
}; | |||
var file_upload_start = $('#file_upload_start'); | |||
file_upload_start.on('fileuploadadd', function(e, data) { | |||
var fileDirectory = ''; | |||
if(typeof data.files[0].relativePath !== 'undefined') { | |||
fileDirectory = data.files[0].relativePath; | |||
} | |||
// Add custom data to the upload handler | |||
data.formData = { | |||
requesttoken: $('#publicUploadRequestToken').val(), | |||
dirToken: $('#dirToken').val(), | |||
subdir: $('input#dir').val(), | |||
file_directory: fileDirectory | |||
}; | |||
}); | |||
this.fileActions = _.extend({}, OCA.Files.FileActions); | |||
this.fileActions.registerDefaultActions(this.fileList); | |||
delete this.fileActions.actions.all.Share; | |||
this.fileList.setFileActions(this.fileActions); | |||
this.fileList.changeDirectory($('#dir').val() || '/', false, true); | |||
// URL history handling | |||
this.fileList.$el.on('changeDirectory', _.bind(this._onDirectoryChanged, this)); | |||
OC.Util.History.addOnPopStateHandler(_.bind(this._onUrlChanged, this)); | |||
} | |||
$(document).on('click', '#directLink', function() { | |||
$(this).focus(); | |||
$(this).select(); | |||
}); | |||
} | |||
$(document).on('click', '#directLink', function() { | |||
$(this).focus(); | |||
$(this).select(); | |||
}); | |||
// legacy | |||
window.FileList = this.fileList; | |||
}, | |||
_onDirectoryChanged: function(e) { | |||
OC.Util.History.pushState({ | |||
service: 'files', | |||
t: $('#sharingToken').val(), | |||
// arghhhh, why is this not called "dir" !? | |||
path: e.dir | |||
}); | |||
}, | |||
_onUrlChanged: function(params) { | |||
this.fileList.changeDirectory(params.path || params.dir, false, true); | |||
} | |||
}; | |||
$(document).ready(function() { | |||
var App = OCA.Sharing.PublicApp; | |||
App.initialize($('#preview')); | |||
}); | |||
@@ -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); |
@@ -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); |
@@ -5,11 +5,11 @@ $l = OC_L10N::get('files_trashbin'); | |||
\OCA\Files_Trashbin\Trashbin::registerHooks(); | |||
\OCA\Files\App::getNavigationManager()->add( | |||
array( | |||
"id" => 'trashbin', | |||
"appname" => 'files_trashbin', | |||
"script" => 'index.php', | |||
"order" => 1, | |||
"name" => $l->t('Deleted files') | |||
) | |||
array( | |||
"id" => 'trashbin', | |||
"appname" => 'files_trashbin', | |||
"script" => 'index.php', | |||
"order" => 1, | |||
"name" => $l->t('Deleted files') | |||
) | |||
); |
@@ -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(); |
@@ -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); | |||
}); | |||
}); | |||
@@ -1,3 +0,0 @@ | |||
/* disable download and sharing actions */ | |||
var disableDownloadActions = true; | |||
var trashBinApp = true; |
@@ -1,8 +1,14 @@ | |||
/* global OC, t, FileList */ | |||
/* | |||
* Copyright (c) 2014 | |||
* | |||
* This file is licensed under the Affero General Public License version 3 | |||
* or later. | |||
* | |||
* See the COPYING-README file. | |||
* | |||
*/ | |||
(function() { | |||
FileList.appName = t('files_trashbin', 'Deleted files'); | |||
FileList._deletedRegExp = new RegExp(/^(.+)\.d[0-9]+$/); | |||
var DELETED_REGEXP = new RegExp(/^(.+)\.d[0-9]+$/); | |||
/** | |||
* Convert a file name in the format filename.d12345 to the real file name. | |||
@@ -11,195 +17,236 @@ | |||
* @param name file name | |||
* @return converted file name | |||
*/ | |||
FileList.getDeletedFileName = function(name) { | |||
function getDeletedFileName(name) { | |||
name = OC.basename(name); | |||
var match = FileList._deletedRegExp.exec(name); | |||
var match = DELETED_REGEXP.exec(name); | |||
if (match && match.length > 1) { | |||
name = match[1]; | |||
} | |||
return name; | |||
}; | |||
var oldSetCurrentDir = FileList._setCurrentDir; | |||
FileList._setCurrentDir = function(targetDir) { | |||
oldSetCurrentDir.apply(this, arguments); | |||
} | |||
var baseDir = OC.basename(targetDir); | |||
if (baseDir !== '') { | |||
FileList.setPageTitle(FileList.getDeletedFileName(baseDir)); | |||
} | |||
var FileList = function($el) { | |||
this.initialize($el); | |||
}; | |||
FileList.prototype = _.extend({}, OCA.Files.FileList.prototype, { | |||
appName: t('files_trashbin', 'Deleted files'), | |||
var oldCreateRow = FileList._createRow; | |||
FileList._createRow = function() { | |||
// FIXME: MEGAHACK until we find a better solution | |||
var tr = oldCreateRow.apply(this, arguments); | |||
tr.find('td.filesize').remove(); | |||
return tr; | |||
}; | |||
initialize: function() { | |||
var result = OCA.Files.FileList.prototype.initialize.apply(this, arguments); | |||
this.$el.find('.undelete').click('click', _.bind(this._onClickRestoreSelected, this)); | |||
FileList._onClickBreadCrumb = function(e) { | |||
var $el = $(e.target).closest('.crumb'), | |||
index = $el.index(), | |||
$targetDir = $el.data('dir'); | |||
// first one is home, let the link makes it default action | |||
if (index !== 0) { | |||
e.preventDefault(); | |||
FileList.changeDirectory($targetDir); | |||
} | |||
}; | |||
this.setSort('mtime', 'desc'); | |||
var oldRenderRow = FileList._renderRow; | |||
FileList._renderRow = function(fileData, options) { | |||
options = options || {}; | |||
var dir = FileList.getCurrentDirectory(); | |||
var dirListing = dir !== '' && dir !== '/'; | |||
// show deleted time as mtime | |||
if (fileData.mtime) { | |||
fileData.mtime = parseInt(fileData.mtime, 10); | |||
} | |||
if (!dirListing) { | |||
fileData.displayName = fileData.name; | |||
fileData.name = fileData.name + '.d' + Math.floor(fileData.mtime / 1000); | |||
} | |||
return oldRenderRow.call(this, fileData, options); | |||
}; | |||
// override crumb URL maker | |||
this.breadcrumb.getCrumbUrl = function(part, index) { | |||
return OC.linkTo('files_trashbin', 'index.php')+"?view=trashbin&dir=" + encodeURIComponent(part.dir); | |||
}; | |||
/** | |||
* Override crumb making to add "Deleted Files" entry | |||
* and convert files with ".d" extensions to a more | |||
* user friendly name. | |||
*/ | |||
this.breadcrumb._makeCrumbs = function() { | |||
var parts = OCA.Files.BreadCrumb.prototype._makeCrumbs.apply(this, arguments); | |||
for (var i = 1; i < parts.length; i++) { | |||
parts[i].name = getDeletedFileName(parts[i].name); | |||
} | |||
return parts; | |||
}; | |||
FileList.linkTo = function(dir){ | |||
return OC.linkTo('files_trashbin', 'index.php')+"?dir="+ encodeURIComponent(dir).replace(/%2F/g, '/'); | |||
}; | |||
return result; | |||
}, | |||
FileList.updateEmptyContent = function(){ | |||
var $fileList = $('#fileList'); | |||
var exists = $fileList.find('tr:first').exists(); | |||
$('#emptycontent').toggleClass('hidden', exists); | |||
$('#filestable th').toggleClass('hidden', !exists); | |||
}; | |||
_setCurrentDir: function(targetDir) { | |||
OCA.Files.FileList.prototype._setCurrentDir.apply(this, arguments); | |||
var oldInit = FileList.initialize; | |||
FileList.initialize = function() { | |||
var result = oldInit.apply(this, arguments); | |||
$('.undelete').click('click', FileList._onClickRestoreSelected); | |||
this.setSort('mtime', 'desc'); | |||
return result; | |||
}; | |||
FileList._removeCallback = function(result) { | |||
if (result.status !== 'success') { | |||
OC.dialogs.alert(result.data.message, t('files_trashbin', 'Error')); | |||
} | |||
var baseDir = OC.basename(targetDir); | |||
if (baseDir !== '') { | |||
this.setPageTitle(getDeletedFileName(baseDir)); | |||
} | |||
}, | |||
_createRow: function() { | |||
// FIXME: MEGAHACK until we find a better solution | |||
var tr = OCA.Files.FileList.prototype._createRow.apply(this, arguments); | |||
tr.find('td.filesize').remove(); | |||
return tr; | |||
}, | |||
_renderRow: function(fileData, options) { | |||
options = options || {}; | |||
var dir = this.getCurrentDirectory(); | |||
var dirListing = dir !== '' && dir !== '/'; | |||
// show deleted time as mtime | |||
if (fileData.mtime) { | |||
fileData.mtime = parseInt(fileData.mtime, 10); | |||
} | |||
if (!dirListing) { | |||
fileData.displayName = fileData.name; | |||
fileData.name = fileData.name + '.d' + Math.floor(fileData.mtime / 1000); | |||
} | |||
return OCA.Files.FileList.prototype._renderRow.call(this, fileData, options); | |||
}, | |||
var files = result.data.success; | |||
var $el; | |||
for (var i = 0; i < files.length; i++) { | |||
$el = FileList.remove(OC.basename(files[i].filename), {updateSummary: false}); | |||
FileList.fileSummary.remove({type: $el.attr('data-type'), size: $el.attr('data-size')}); | |||
} | |||
FileList.fileSummary.update(); | |||
FileList.updateEmptyContent(); | |||
enableActions(); | |||
} | |||
getAjaxUrl: function(action, params) { | |||
var q = ''; | |||
if (params) { | |||
q = '?' + OC.buildQueryString(params); | |||
} | |||
return OC.filePath('files_trashbin', 'ajax', action + '.php') + q; | |||
}, | |||
linkTo: function(dir){ | |||
return OC.linkTo('files_trashbin', 'index.php')+"?dir="+ encodeURIComponent(dir).replace(/%2F/g, '/'); | |||
}, | |||
updateEmptyContent: function(){ | |||
var exists = this.$fileList.find('tr:first').exists(); | |||
this.$el.find('#emptycontent').toggleClass('hidden', exists); | |||
this.$el.find('#filestable th').toggleClass('hidden', !exists); | |||
}, | |||
_removeCallback: function(result) { | |||
if (result.status !== 'success') { | |||
OC.dialogs.alert(result.data.message, t('files_trashbin', 'Error')); | |||
} | |||
FileList._onClickRestoreSelected = function(event) { | |||
event.preventDefault(); | |||
var allFiles = $('#select_all').is(':checked'); | |||
var files = []; | |||
var params = {}; | |||
disableActions(); | |||
if (allFiles) { | |||
FileList.showMask(); | |||
params = { | |||
allfiles: true, | |||
dir: FileList.getCurrentDirectory() | |||
}; | |||
} | |||
else { | |||
files = _.pluck(FileList.getSelectedFiles(), 'name'); | |||
var files = result.data.success; | |||
var $el; | |||
for (var i = 0; i < files.length; i++) { | |||
var deleteAction = FileList.findFileEl(files[i]).children("td.date").children(".action.delete"); | |||
deleteAction.removeClass('delete-icon').addClass('progress-icon'); | |||
$el = this.remove(OC.basename(files[i].filename), {updateSummary: false}); | |||
this.fileSummary.remove({type: $el.attr('data-type'), size: $el.attr('data-size')}); | |||
} | |||
params = { | |||
files: JSON.stringify(files), | |||
dir: FileList.getCurrentDirectory() | |||
}; | |||
} | |||
this.fileSummary.update(); | |||
this.updateEmptyContent(); | |||
this.enableActions(); | |||
}, | |||
$.post(OC.filePath('files_trashbin', 'ajax', 'undelete.php'), | |||
params, | |||
function(result) { | |||
if (allFiles) { | |||
if (result.status !== 'success') { | |||
OC.dialogs.alert(result.data.message, t('files_trashbin', 'Error')); | |||
} | |||
FileList.hideMask(); | |||
// simply remove all files | |||
FileList.setFiles([]); | |||
enableActions(); | |||
} | |||
else { | |||
FileList._removeCallback(result); | |||
} | |||
_onClickRestoreSelected: function(event) { | |||
event.preventDefault(); | |||
var self = this; | |||
var allFiles = this.$el.find('#select_all').is(':checked'); | |||
var files = []; | |||
var params = {}; | |||
this.disableActions(); | |||
if (allFiles) { | |||
this.showMask(); | |||
params = { | |||
allfiles: true, | |||
dir: this.getCurrentDirectory() | |||
}; | |||
} | |||
); | |||
}; | |||
FileList._onClickDeleteSelected = function(event) { | |||
event.preventDefault(); | |||
var allFiles = $('#select_all').is(':checked'); | |||
var files = []; | |||
var params = {}; | |||
if (allFiles) { | |||
params = { | |||
allfiles: true, | |||
dir: FileList.getCurrentDirectory() | |||
}; | |||
} | |||
else { | |||
files = _.pluck(FileList.getSelectedFiles(), 'name'); | |||
params = { | |||
files: JSON.stringify(files), | |||
dir: FileList.getCurrentDirectory() | |||
}; | |||
} | |||
disableActions(); | |||
if (allFiles) { | |||
FileList.showMask(); | |||
} | |||
else { | |||
for (var i = 0; i < files.length; i++) { | |||
var deleteAction = FileList.findFileEl(files[i]).children("td.date").children(".action.delete"); | |||
deleteAction.removeClass('delete-icon').addClass('progress-icon'); | |||
else { | |||
files = _.pluck(this.getSelectedFiles(), 'name'); | |||
for (var i = 0; i < files.length; i++) { | |||
var deleteAction = this.findFileEl(files[i]).children("td.date").children(".action.delete"); | |||
deleteAction.removeClass('delete-icon').addClass('progress-icon'); | |||
} | |||
params = { | |||
files: JSON.stringify(files), | |||
dir: this.getCurrentDirectory() | |||
}; | |||
} | |||
} | |||
$.post(OC.filePath('files_trashbin', 'ajax', 'delete.php'), | |||
$.post(OC.filePath('files_trashbin', 'ajax', 'undelete.php'), | |||
params, | |||
function(result) { | |||
if (allFiles) { | |||
if (result.status !== 'success') { | |||
OC.dialogs.alert(result.data.message, t('files_trashbin', 'Error')); | |||
} | |||
FileList.hideMask(); | |||
self.hideMask(); | |||
// simply remove all files | |||
FileList.setFiles([]); | |||
enableActions(); | |||
self.setFiles([]); | |||
self.enableActions(); | |||
} | |||
else { | |||
FileList._removeCallback(result); | |||
self._removeCallback(result); | |||
} | |||
} | |||
); | |||
}; | |||
); | |||
}, | |||
var oldClickFile = FileList._onClickFile; | |||
FileList._onClickFile = function(event) { | |||
var mime = $(this).parent().parent().data('mime'); | |||
if (mime !== 'httpd/unix-directory') { | |||
_onClickDeleteSelected: function(event) { | |||
event.preventDefault(); | |||
var self = this; | |||
var allFiles = this.$el.find('#select_all').is(':checked'); | |||
var files = []; | |||
var params = {}; | |||
if (allFiles) { | |||
params = { | |||
allfiles: true, | |||
dir: this.getCurrentDirectory() | |||
}; | |||
} | |||
else { | |||
files = _.pluck(this.getSelectedFiles(), 'name'); | |||
params = { | |||
files: JSON.stringify(files), | |||
dir: this.getCurrentDirectory() | |||
}; | |||
} | |||
this.disableActions(); | |||
if (allFiles) { | |||
this.showMask(); | |||
} | |||
else { | |||
for (var i = 0; i < files.length; i++) { | |||
var deleteAction = this.findFileEl(files[i]).children("td.date").children(".action.delete"); | |||
deleteAction.removeClass('delete-icon').addClass('progress-icon'); | |||
} | |||
} | |||
$.post(OC.filePath('files_trashbin', 'ajax', 'delete.php'), | |||
params, | |||
function(result) { | |||
if (allFiles) { | |||
if (result.status !== 'success') { | |||
OC.dialogs.alert(result.data.message, t('files_trashbin', 'Error')); | |||
} | |||
self.hideMask(); | |||
// simply remove all files | |||
self.setFiles([]); | |||
self.enableActions(); | |||
} | |||
else { | |||
self._removeCallback(result); | |||
} | |||
} | |||
); | |||
}, | |||
_onClickFile: function(event) { | |||
var mime = $(this).parent().parent().data('mime'); | |||
if (mime !== 'httpd/unix-directory') { | |||
event.preventDefault(); | |||
} | |||
return OCA.Files.FileList.prototype._onClickFile.apply(this, arguments); | |||
}, | |||
generatePreviewUrl: function(urlSpec) { | |||
return OC.generateUrl('/apps/files_trashbin/ajax/preview.php?') + $.param(urlSpec); | |||
}, | |||
getDownloadUrl: function(action, params) { | |||
// no downloads | |||
return '#'; | |||
}, | |||
enableActions: function() { | |||
this.$el.find('.action').css('display', 'inline'); | |||
this.$el.find(':input:checkbox').css('display', 'inline'); | |||
}, | |||
disableActions: function() { | |||
this.$el.find('.action').css('display', 'none'); | |||
this.$el.find(':input:checkbox').css('display', 'none'); | |||
} | |||
return oldClickFile.apply(this, arguments); | |||
}; | |||
}); | |||
OCA.Trashbin.FileList = FileList; | |||
})(); | |||
@@ -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; | |||
})(); | |||
@@ -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"); | |||
} | |||
@@ -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> |
@@ -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'); | |||
}); | |||
}); | |||
}); |
@@ -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); | |||
}); | |||
}); | |||
}); | |||
}); |
@@ -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 |
@@ -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 |