aboutsummaryrefslogtreecommitdiffstats
path: root/apps/files
diff options
context:
space:
mode:
Diffstat (limited to 'apps/files')
-rw-r--r--apps/files/css/files.css13
-rw-r--r--apps/files/index.php5
-rw-r--r--apps/files/js/app.js35
-rw-r--r--apps/files/js/fileactions.js103
-rw-r--r--apps/files/js/filelist.js78
-rw-r--r--apps/files/l10n/et_EE.php7
-rw-r--r--apps/files/l10n/pt_PT.php5
-rw-r--r--apps/files/l10n/ru.php5
-rw-r--r--apps/files/templates/index.php2
-rw-r--r--apps/files/tests/js/appSpec.js53
-rw-r--r--apps/files/tests/js/fileactionsSpec.js80
-rw-r--r--apps/files/tests/js/filelistSpec.js184
12 files changed, 492 insertions, 78 deletions
diff --git a/apps/files/css/files.css b/apps/files/css/files.css
index 731dd7a23e7..decfdbd2cda 100644
--- a/apps/files/css/files.css
+++ b/apps/files/css/files.css
@@ -91,6 +91,11 @@
position: relative;
}
+/* fit app list view heights */
+.app-files #app-content>.viewcontainer {
+ height: 100%;
+}
+
/**
* Override global #controls styles
* to be more flexible / relative
@@ -151,7 +156,13 @@ tr:hover span.extension {
}
table tr.mouseOver td { background-color:#eee; }
-table th { height:24px; padding:0 8px; color:#999; }
+table th { height:24px; padding:0 8px; }
+table th, table th a {
+ color: #999;
+}
+table.multiselect th a {
+ color: #000;
+}
table th .columntitle {
display: inline-block;
padding: 15px;
diff --git a/apps/files/index.php b/apps/files/index.php
index e24c535cb20..95ae7977ecc 100644
--- a/apps/files/index.php
+++ b/apps/files/index.php
@@ -74,7 +74,12 @@ if (OC_App::isEnabled('files_encryption')) {
$nav = new OCP\Template('files', 'appnavigation', '');
+function sortNavigationItems($item1, $item2) {
+ return $item1['order'] - $item2['order'];
+}
+
$navItems = \OCA\Files\App::getNavigationManager()->getAll();
+usort($navItems, 'sortNavigationItems');
$nav->assign('navigationItems', $navItems);
$contentItems = array();
diff --git a/apps/files/js/app.js b/apps/files/js/app.js
index 9155fb38cdb..71802948a5c 100644
--- a/apps/files/js/app.js
+++ b/apps/files/js/app.js
@@ -24,20 +24,27 @@
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.fileActions = OCA.Files.FileActions;
+ var fileActions = new OCA.Files.FileActions();
+ // default actions
+ fileActions.registerDefaultActions();
+ // legacy actions
+ fileActions.merge(window.FileActions);
+ // regular actions
+ fileActions.merge(OCA.Files.fileActions);
+
this.files = OCA.Files.Files;
+ // TODO: ideally these should be in a separate class / app (the embedded "all files" app)
this.fileList = new OCA.Files.FileList(
$('#app-content-files'), {
scrollContainer: $('#app-content'),
dragOptions: dragOptions,
- folderDropOptions: folderDropOptions
+ folderDropOptions: folderDropOptions,
+ fileActions: fileActions,
+ allowLegacyActions: true
}
);
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
@@ -58,6 +65,22 @@
},
/**
+ * Sets the currently active view
+ * @param viewId view id
+ */
+ setActiveView: function(viewId, options) {
+ this.navigation.setActiveItem(viewId, options);
+ },
+
+ /**
+ * Returns the view id of the currently active view
+ * @return view id
+ */
+ getActiveView: function() {
+ return this.navigation.getActiveItem();
+ },
+
+ /**
* Setup events based on URL changes
*/
_setupEvents: function() {
@@ -138,7 +161,7 @@
})();
$(document).ready(function() {
- // wait for other apps/extensions to register their event handlers
+ // wait for other apps/extensions to register their event handlers and file actions
// in the "ready" clause
_.defer(function() {
OCA.Files.App.initialize();
diff --git a/apps/files/js/fileactions.js b/apps/files/js/fileactions.js
index 2edb45f544c..3df62f37518 100644
--- a/apps/files/js/fileactions.js
+++ b/apps/files/js/fileactions.js
@@ -11,11 +11,40 @@
/* global trashBinApp */
(function() {
- var FileActions = {
+ /**
+ * Construct a new FileActions instance
+ */
+ var FileActions = function() {
+ this.initialize();
+ }
+ FileActions.prototype = {
actions: {},
defaults: {},
icons: {},
currentFile: null,
+ initialize: function() {
+ this.clear();
+ },
+ /**
+ * Merges the actions from the given fileActions into
+ * this instance.
+ *
+ * @param fileActions instance of OCA.Files.FileActions
+ */
+ merge: function(fileActions) {
+ var self = this;
+ // merge first level to avoid unintended overwriting
+ _.each(fileActions.actions, function(sourceMimeData, mime) {
+ var targetMimeData = self.actions[mime];
+ if (!targetMimeData) {
+ targetMimeData = {};
+ }
+ self.actions[mime] = _.extend(targetMimeData, sourceMimeData);
+ });
+
+ this.defaults = _.extend(this.defaults, fileActions.defaults);
+ this.icons = _.extend(this.icons, fileActions.icons);
+ },
register: function (mime, name, permissions, icon, action, displayName) {
if (!this.actions[mime]) {
this.actions[mime] = {};
@@ -98,8 +127,13 @@
* @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)
+ * @param fileList OCA.Files.FileList instance on which the action is
+ * done, defaults to OCA.Files.App.fileList
*/
- display: function (parent, triggerEvent) {
+ display: function (parent, triggerEvent, fileList) {
+ if (!fileList) {
+ console.warn('FileActions.display() MUST be called with a OCA.Files.FileList instance');
+ }
this.currentFile = parent;
var self = this;
var actions = this.getActions(this.getCurrentMimeType(), this.getCurrentType(), this.getCurrentPermissions());
@@ -120,9 +154,18 @@
event.preventDefault();
self.currentFile = event.data.elem;
+ // also set on global object for legacy apps
+ window.FileActions.currentFile = self.currentFile;
+
var file = self.getCurrentFile();
+ var $tr = $(this).closest('tr');
- event.data.actionFunc(file);
+ event.data.actionFunc(file, {
+ $file: $tr,
+ fileList: fileList || OCA.Files.App.fileList,
+ fileActions: self,
+ dir: $tr.attr('data-path') || fileList.getCurrentDirectory()
+ });
};
var addAction = function (name, action, displayName) {
@@ -180,7 +223,7 @@
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 = '<a href="#" original-title="' + t('files', 'Delete') + '" class="action delete delete-icon" />';
}
var element = $(html);
element.data('action', actions['Delete']);
@@ -189,7 +232,7 @@
}
if (triggerEvent){
- $('#fileList').trigger(jQuery.Event("fileActionsReady"));
+ fileList.$fileList.trigger(jQuery.Event("fileActionsReady", {fileList: fileList}));
}
},
getCurrentFile: function () {
@@ -208,29 +251,27 @@
/**
* Register the actions that are used by default for the files app.
*/
- registerDefaultActions: function(fileList) {
- // TODO: try to find a way to not make it depend on fileList,
- // maybe get a handler or listener to trigger events on
+ registerDefaultActions: function() {
this.register('all', 'Delete', OC.PERMISSION_DELETE, function () {
return OC.imagePath('core', 'actions/delete');
- }, function (filename) {
- fileList.do_delete(filename);
+ }, function (filename, context) {
+ context.fileList.do_delete(filename);
$('.tipsy').remove();
});
// t('files', 'Rename')
this.register('all', 'Rename', OC.PERMISSION_UPDATE, function () {
return OC.imagePath('core', 'actions/rename');
- }, function (filename) {
- fileList.rename(filename);
+ }, function (filename, context) {
+ context.fileList.rename(filename);
});
- this.register('dir', 'Open', OC.PERMISSION_READ, '', function (filename) {
- var dir = fileList.getCurrentDirectory();
+ this.register('dir', 'Open', OC.PERMISSION_READ, '', function (filename, context) {
+ var dir = context.fileList.getCurrentDirectory();
if (dir !== '/') {
dir = dir + '/';
}
- fileList.changeDirectory(dir + filename);
+ context.fileList.changeDirectory(dir + filename);
});
this.setDefault('dir', 'Open');
@@ -243,20 +284,38 @@
this.register(downloadScope, 'Download', OC.PERMISSION_READ, function () {
return OC.imagePath('core', 'actions/download');
- }, function (filename) {
- var url = fileList.getDownloadUrl(filename, fileList.getCurrentDirectory());
+ }, function (filename, context) {
+ var dir = context.dir || context.fileList.getCurrentDirectory();
+ var url = context.fileList.getDownloadUrl(filename, dir);
if (url) {
OC.redirect(url);
}
});
-
- fileList.$fileList.trigger(jQuery.Event("fileActionsReady"));
}
};
OCA.Files.FileActions = FileActions;
-})();
-// for backward compatibility
-window.FileActions = OCA.Files.FileActions;
+ // global file actions to be used by all lists
+ OCA.Files.fileActions = new OCA.Files.FileActions();
+ OCA.Files.legacyFileActions = new OCA.Files.FileActions();
+
+ // for backward compatibility
+ //
+ // legacy apps are expecting a stateful global FileActions object to register
+ // their actions on. Since legacy apps are very likely to break with other
+ // FileList views than the main one ("All files"), actions registered
+ // through window.FileActions will be limited to the main file list.
+ window.FileActions = OCA.Files.legacyFileActions;
+ window.FileActions.register = function (mime, name, permissions, icon, action, displayName) {
+ console.warn('FileActions.register() is deprecated, please use OCA.Files.fileActions.register() instead', arguments);
+ OCA.Files.FileActions.prototype.register.call(
+ window.FileActions, mime, name, permissions, icon, action, displayName
+ );
+ };
+ window.FileActions.setDefault = function (mime, name) {
+ console.warn('FileActions.setDefault() is deprecated, please use OCA.Files.fileActions.setDefault() instead', mime, name);
+ OCA.Files.FileActions.prototype.setDefault.call(window.FileActions, mime, name);
+ };
+})();
diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js
index 72e1a688041..1b2a62137e5 100644
--- a/apps/files/js/filelist.js
+++ b/apps/files/js/filelist.js
@@ -125,7 +125,7 @@
this.$container = options.scrollContainer || $(window);
this.$table = $el.find('table:first');
this.$fileList = $el.find('#fileList');
- this.fileActions = OCA.Files.FileActions;
+ this._initFileActions(options.fileActions);
this.files = [];
this._selectedFiles = {};
this._selectionSummary = new OCA.Files.FileSummary();
@@ -168,6 +168,14 @@
this.$container.on('scroll', _.bind(this._onScroll, this));
},
+ _initFileActions: function(fileActions) {
+ this.fileActions = fileActions;
+ if (!this.fileActions) {
+ this.fileActions = new OCA.Files.FileActions();
+ this.fileActions.registerDefaultActions();
+ }
+ },
+
/**
* Event handler for when the URL changed
*/
@@ -248,7 +256,14 @@
var action = this.fileActions.getDefault(mime,type, permissions);
if (action) {
event.preventDefault();
- action(filename);
+ // also set on global object for legacy apps
+ window.FileActions.currentFile = this.fileActions.currentFile;
+ action(filename, {
+ $file: $tr,
+ fileList: this,
+ fileActions: this.fileActions,
+ dir: $tr.attr('data-path') || this.getCurrentDirectory()
+ });
}
}
}
@@ -448,7 +463,7 @@
while (count > 0 && index < this.files.length) {
fileData = this.files[index];
- tr = this._renderRow(fileData, {updateSummary: false});
+ tr = this._renderRow(fileData, {updateSummary: false, silent: true});
this.$fileList.append(tr);
if (isAllSelected || this._selectedFiles[fileData.id]) {
tr.addClass('selected');
@@ -493,7 +508,7 @@
this.$el.find('thead').after(this.$fileList);
this.updateEmptyContent();
- this.$fileList.trigger(jQuery.Event("fileActionsReady"));
+ this.$fileList.trigger($.Event('fileActionsReady', {fileList: this}));
this.fileSummary.calculate(filesArray);
@@ -515,6 +530,7 @@
type = fileData.type || 'file',
mtime = parseInt(fileData.mtime, 10) || new Date().getTime(),
mime = fileData.mimetype,
+ path = fileData.path,
linkUrl;
options = options || {};
@@ -534,6 +550,13 @@
"data-permissions": fileData.permissions || this.getDirectoryPermissions()
});
+ if (!_.isUndefined(path)) {
+ tr.attr('data-path', path);
+ }
+ else {
+ path = this.getCurrentDirectory();
+ }
+
if (type === 'dir') {
// use default folder icon
icon = icon || OC.imagePath('core', 'filetypes/folder');
@@ -550,10 +573,10 @@
// linkUrl
if (type === 'dir') {
- linkUrl = this.linkTo(this.getCurrentDirectory() + '/' + name);
+ linkUrl = this.linkTo(path + '/' + name);
}
else {
- linkUrl = this.getDownloadUrl(name, this.getCurrentDirectory());
+ linkUrl = this.getDownloadUrl(name, path);
}
td.append('<input id="select-' + this.id + '-' + fileData.id +
'" type="checkbox" /><label for="select-' + this.id + '-' + fileData.id + '"></label>');
@@ -621,7 +644,8 @@
*
* @param fileData map of file attributes
* @param options map of attributes:
- * - "updateSummary" true to update the summary after adding (default), false otherwise
+ * - "updateSummary": true to update the summary after adding (default), false otherwise
+ * - "silent": true to prevent firing events like "fileActionsReady"
* @return new tr element (not appended to the table)
*/
add: function(fileData, options) {
@@ -693,6 +717,7 @@
options = options || {};
var type = fileData.type || 'file',
mime = fileData.mimetype,
+ path = fileData.path || this.getCurrentDirectory(),
permissions = parseInt(fileData.permissions, 10) || 0;
if (fileData.isShareMountPoint) {
@@ -723,13 +748,13 @@
}
// display actions
- this.fileActions.display(filenameTd, false);
+ this.fileActions.display(filenameTd, !options.silent, this);
if (fileData.isPreviewAvailable) {
// lazy load / newly inserted td ?
if (!fileData.icon) {
this.lazyLoadPreview({
- path: this.getCurrentDirectory() + '/' + fileData.name,
+ path: path + '/' + fileData.name,
mime: mime,
etag: fileData.etag,
callback: function(url) {
@@ -740,7 +765,7 @@
else {
// set the preview URL directly
var urlSpec = {
- file: this.getCurrentDirectory() + '/' + fileData.name,
+ file: path + '/' + fileData.name,
c: fileData.etag
};
var previewUrl = this.generatePreviewUrl(urlSpec);
@@ -784,13 +809,6 @@
},
/**
- * Sets the file actions handler
- */
- setFileActions: function(fileActions) {
- this.fileActions = fileActions;
- },
-
- /**
* Sets the current directory name and updates the breadcrumb.
* @param targetDir directory to display
* @param changeUrl true to also update the URL, false otherwise (default)
@@ -1213,16 +1231,16 @@
// reinsert row
self.files.splice(tr.index(), 1);
tr.remove();
- self.add(fileInfo, {updateSummary: false});
- self.$fileList.trigger($.Event('fileActionsReady'));
+ self.add(fileInfo, {updateSummary: false, silent: true});
+ self.$fileList.trigger($.Event('fileActionsReady', {fileList: self}));
}
});
} else {
// add back the old file info when cancelled
self.files.splice(tr.index(), 1);
tr.remove();
- self.add(oldFileInfo, {updateSummary: false});
- self.$fileList.trigger($.Event('fileActionsReady'));
+ self.add(oldFileInfo, {updateSummary: false, silent: true});
+ self.$fileList.trigger($.Event('fileActionsReady', {fileList: self}));
}
} catch (error) {
input.attr('title', error);
@@ -1511,13 +1529,18 @@
fileUploadStart.on('fileuploaddrop', function(e, data) {
OC.Upload.log('filelist handle fileuploaddrop', e, data);
- var dropTarget = $(e.originalEvent.target).closest('tr, .crumb');
- // check if dropped inside this list at all
- if (dropTarget && !self.$el.has(dropTarget).length) {
+ var dropTarget = $(e.originalEvent.target);
+ // check if dropped inside this container and not another one
+ if (dropTarget.length && !self.$el.is(dropTarget) && !self.$el.has(dropTarget).length) {
return false;
}
- if (dropTarget && (dropTarget.data('type') === 'dir' || dropTarget.hasClass('crumb'))) { // drag&drop upload to folder
+ // find the closest tr or crumb to use as target
+ dropTarget = dropTarget.closest('tr, .crumb');
+
+ // if dropping on tr or crumb, drag&drop upload to folder
+ if (dropTarget && (dropTarget.data('type') === 'dir' ||
+ dropTarget.hasClass('crumb'))) {
// remember as context
data.context = dropTarget;
@@ -1537,7 +1560,7 @@
}
// update folder in form
- data.formData = function(form) {
+ data.formData = function() {
return [
{name: 'dir', value: dir},
{name: 'requesttoken', value: oc_requesttoken},
@@ -1545,6 +1568,9 @@
];
};
} else {
+ // we are dropping somewhere inside the file list, which will
+ // upload the file to the current directory
+
// cancel uploads to current dir if no permission
var isCreatable = (self.getDirectoryPermissions() & OC.PERMISSION_CREATE) !== 0;
if (!isCreatable) {
diff --git a/apps/files/l10n/et_EE.php b/apps/files/l10n/et_EE.php
index 7e8a84cdf65..7531d2b2562 100644
--- a/apps/files/l10n/et_EE.php
+++ b/apps/files/l10n/et_EE.php
@@ -28,6 +28,7 @@ $TRANSLATIONS = array(
"Upload failed. Could not get file info." => "Üleslaadimine ebaõnnestus. Faili info hankimine ebaõnnestus.",
"Invalid directory." => "Vigane kaust.",
"Files" => "Failid",
+"All files" => "Kõik failid",
"Unable to upload {filename} as it is a directory or has 0 bytes" => "Ei saa üles laadida {filename}, kuna see on kataloog või selle suurus on 0 baiti",
"Total file size {size1} exceeds upload limit {size2}" => "Faili suurus {size1} ületab faili üleslaadimise mahu piirangu {size2}.",
"Not enough free space, you are uploading {size1} but only {size2} is left" => "Pole piisavalt vaba ruumi. Sa laadid üles {size1}, kuid ainult {size2} on saadaval.",
@@ -41,9 +42,11 @@ $TRANSLATIONS = array(
"Error fetching URL" => "Viga URL-i haaramisel",
"Share" => "Jaga",
"Delete permanently" => "Kustuta jäädavalt",
+"Delete" => "Kustuta",
"Rename" => "Nimeta ümber",
"Your download is being prepared. This might take some time if the files are big." => "Valmistatakse allalaadimist. See võib võtta veidi aega, kui on tegu suurte failidega. ",
"Pending" => "Ootel",
+"Error moving file." => "Viga faili liigutamisel.",
"Error moving file" => "Viga faili eemaldamisel",
"Error" => "Viga",
"Could not rename file" => "Ei suuda faili ümber nimetada",
@@ -83,9 +86,9 @@ $TRANSLATIONS = array(
"You don’t have permission to upload or create files here" => "Sul puuduvad õigused siia failide üleslaadimiseks või tekitamiseks",
"Nothing in here. Upload something!" => "Siin pole midagi. Lae midagi üles!",
"Download" => "Lae alla",
-"Delete" => "Kustuta",
"Upload too large" => "Üleslaadimine on liiga suur",
"The files you are trying to upload exceed the maximum size for file uploads on this server." => "Failid, mida sa proovid üles laadida, ületab serveri poolt üleslaetavatele failidele määratud maksimaalse suuruse.",
-"Files are being scanned, please wait." => "Faile skannitakse, palun oota."
+"Files are being scanned, please wait." => "Faile skannitakse, palun oota.",
+"Currently scanning" => "Praegu skännimisel"
);
$PLURAL_FORMS = "nplurals=2; plural=(n != 1);";
diff --git a/apps/files/l10n/pt_PT.php b/apps/files/l10n/pt_PT.php
index 77fbbdd3224..5ba46073084 100644
--- a/apps/files/l10n/pt_PT.php
+++ b/apps/files/l10n/pt_PT.php
@@ -42,6 +42,7 @@ $TRANSLATIONS = array(
"Error fetching URL" => "Erro ao obter URL",
"Share" => "Partilhar",
"Delete permanently" => "Eliminar permanentemente",
+"Delete" => "Eliminar",
"Rename" => "Renomear",
"Your download is being prepared. This might take some time if the files are big." => "O seu download está a ser preparado. Este processo pode demorar algum tempo se os ficheiros forem grandes.",
"Pending" => "Pendente",
@@ -85,9 +86,9 @@ $TRANSLATIONS = array(
"You don’t have permission to upload or create files here" => "Você não tem permissão para enviar ou criar ficheiros aqui",
"Nothing in here. Upload something!" => "Vazio. Envie alguma coisa!",
"Download" => "Transferir",
-"Delete" => "Eliminar",
"Upload too large" => "Upload muito grande",
"The files you are trying to upload exceed the maximum size for file uploads on this server." => "Os ficheiro que está a tentar enviar excedem o tamanho máximo de envio neste servidor.",
-"Files are being scanned, please wait." => "Os ficheiros estão a ser analisados, por favor aguarde."
+"Files are being scanned, please wait." => "Os ficheiros estão a ser analisados, por favor aguarde.",
+"Currently scanning" => "A analisar"
);
$PLURAL_FORMS = "nplurals=2; plural=(n != 1);";
diff --git a/apps/files/l10n/ru.php b/apps/files/l10n/ru.php
index ccffdfdc0d7..f24744223ee 100644
--- a/apps/files/l10n/ru.php
+++ b/apps/files/l10n/ru.php
@@ -42,6 +42,7 @@ $TRANSLATIONS = array(
"Error fetching URL" => "Ошибка получения URL",
"Share" => "Открыть доступ",
"Delete permanently" => "Удалить окончательно",
+"Delete" => "Удалить",
"Rename" => "Переименовать",
"Your download is being prepared. This might take some time if the files are big." => "Идёт подготовка к скачиванию. Это может занять некоторое время, если файлы большого размера.",
"Pending" => "Ожидание",
@@ -85,9 +86,9 @@ $TRANSLATIONS = array(
"You don’t have permission to upload or create files here" => "У вас нет прав для загрузки или создания файлов здесь.",
"Nothing in here. Upload something!" => "Здесь ничего нет. Загрузите что-нибудь!",
"Download" => "Скачать",
-"Delete" => "Удалить",
"Upload too large" => "Файл слишком велик",
"The files you are trying to upload exceed the maximum size for file uploads on this server." => "Файлы, которые вы пытаетесь загрузить, превышают лимит максимального размера на этом сервере.",
-"Files are being scanned, please wait." => "Подождите, файлы сканируются."
+"Files are being scanned, please wait." => "Подождите, файлы сканируются.",
+"Currently scanning" => "В настоящее время сканируется"
);
$PLURAL_FORMS = "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);";
diff --git a/apps/files/templates/index.php b/apps/files/templates/index.php
index 8cab4ce220b..b52effb1e78 100644
--- a/apps/files/templates/index.php
+++ b/apps/files/templates/index.php
@@ -2,7 +2,7 @@
<?php $_['appNavigation']->printPage(); ?>
<div id="app-content">
<?php foreach ($_['appContents'] as $content) { ?>
- <div id="app-content-<?php p($content['id']) ?>" class="hidden">
+ <div id="app-content-<?php p($content['id']) ?>" class="hidden viewcontainer">
<?php print_unescaped($content['content']) ?>
</div>
<?php } ?>
diff --git a/apps/files/tests/js/appSpec.js b/apps/files/tests/js/appSpec.js
index 0e9abad6989..a9bbab03ecb 100644
--- a/apps/files/tests/js/appSpec.js
+++ b/apps/files/tests/js/appSpec.js
@@ -41,6 +41,10 @@ describe('OCA.Files.App tests', function() {
'</div>'
);
+ window.FileActions = new OCA.Files.FileActions();
+ OCA.Files.legacyFileActions = window.FileActions;
+ OCA.Files.fileActions = new OCA.Files.FileActions();
+
pushStateStub = sinon.stub(OC.Util.History, 'pushState');
parseUrlQueryStub = sinon.stub(OC.Util.History, 'parseUrlQuery');
parseUrlQueryStub.returns({});
@@ -51,8 +55,6 @@ describe('OCA.Files.App tests', function() {
App.navigation = null;
App.fileList = null;
App.files = null;
- App.fileActions.clear();
- App.fileActions = null;
pushStateStub.restore();
parseUrlQueryStub.restore();
@@ -64,6 +66,53 @@ describe('OCA.Files.App tests', function() {
expect(App.fileList.fileActions.actions.all).toBeDefined();
expect(App.fileList.$el.is('#app-content-files')).toEqual(true);
});
+ it('merges the legacy file actions with the default ones', function() {
+ var legacyActionStub = sinon.stub();
+ var actionStub = sinon.stub();
+ // legacy action
+ window.FileActions.register(
+ 'all',
+ 'LegacyTest',
+ OC.PERMISSION_READ,
+ OC.imagePath('core', 'actions/test'),
+ legacyActionStub
+ );
+ // legacy action to be overwritten
+ window.FileActions.register(
+ 'all',
+ 'OverwriteThis',
+ OC.PERMISSION_READ,
+ OC.imagePath('core', 'actions/test'),
+ legacyActionStub
+ );
+
+ // regular file actions
+ OCA.Files.fileActions.register(
+ 'all',
+ 'RegularTest',
+ OC.PERMISSION_READ,
+ OC.imagePath('core', 'actions/test'),
+ actionStub
+ );
+
+ // overwrite
+ OCA.Files.fileActions.register(
+ 'all',
+ 'OverwriteThis',
+ OC.PERMISSION_READ,
+ OC.imagePath('core', 'actions/test'),
+ actionStub
+ );
+
+ App.initialize();
+
+ var actions = App.fileList.fileActions.actions;
+ expect(actions.all.OverwriteThis.action).toBe(actionStub);
+ expect(actions.all.LegacyTest.action).toBe(legacyActionStub);
+ expect(actions.all.RegularTest.action).toBe(actionStub);
+ // default one still there
+ expect(actions.dir.Open.action).toBeDefined();
+ });
});
describe('URL handling', function() {
diff --git a/apps/files/tests/js/fileactionsSpec.js b/apps/files/tests/js/fileactionsSpec.js
index 9152dbb58c3..490594a1773 100644
--- a/apps/files/tests/js/fileactionsSpec.js
+++ b/apps/files/tests/js/fileactionsSpec.js
@@ -21,7 +21,7 @@
describe('OCA.Files.FileActions tests', function() {
var $filesTable, fileList;
- var FileActions = OCA.Files.FileActions;
+ var FileActions;
beforeEach(function() {
// init horrible parameters
@@ -31,10 +31,11 @@ describe('OCA.Files.FileActions tests', function() {
// dummy files table
$filesTable = $body.append('<table id="filestable"></table>');
fileList = new OCA.Files.FileList($('#testArea'));
- FileActions.registerDefaultActions(fileList);
+ FileActions = new OCA.Files.FileActions();
+ FileActions.registerDefaultActions();
});
afterEach(function() {
- FileActions.clear();
+ FileActions = null;
fileList = undefined;
$('#dir, #permissions, #filestable').remove();
});
@@ -78,8 +79,8 @@ describe('OCA.Files.FileActions tests', function() {
};
var $tr = fileList.add(fileData);
- FileActions.display($tr.find('td.filename'), true);
- FileActions.display($tr.find('td.filename'), true);
+ FileActions.display($tr.find('td.filename'), true, fileList);
+ FileActions.display($tr.find('td.filename'), true, fileList);
// actions defined after cal
expect($tr.find('.action.action-download').length).toEqual(1);
@@ -98,12 +99,39 @@ describe('OCA.Files.FileActions tests', function() {
mtime: '123456'
};
var $tr = fileList.add(fileData);
- FileActions.display($tr.find('td.filename'), true);
+ FileActions.display($tr.find('td.filename'), true, fileList);
+
+ $tr.find('.action-download').click();
+
+ expect(redirectStub.calledOnce).toEqual(true);
+ expect(redirectStub.getCall(0).args[0]).toEqual(
+ OC.webroot +
+ '/index.php/apps/files/ajax/download.php' +
+ '?dir=%2Fsubdir&files=testName.txt');
+ redirectStub.restore();
+ });
+ it('takes the file\'s path into account when clicking download', function() {
+ var redirectStub = sinon.stub(OC, 'redirect');
+ var fileData = {
+ id: 18,
+ type: 'file',
+ name: 'testName.txt',
+ path: '/anotherpath/there',
+ mimetype: 'text/plain',
+ size: '1234',
+ etag: 'a01234c',
+ mtime: '123456'
+ };
+ var $tr = fileList.add(fileData);
+ FileActions.display($tr.find('td.filename'), true, fileList);
$tr.find('.action-download').click();
expect(redirectStub.calledOnce).toEqual(true);
- expect(redirectStub.getCall(0).args[0]).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fsubdir&files=testName.txt');
+ expect(redirectStub.getCall(0).args[0]).toEqual(
+ OC.webroot + '/index.php/apps/files/ajax/download.php' +
+ '?dir=%2Fanotherpath%2Fthere&files=testName.txt'
+ );
redirectStub.restore();
});
it('deletes file when clicking delete', function() {
@@ -118,11 +146,47 @@ describe('OCA.Files.FileActions tests', function() {
mtime: '123456'
};
var $tr = fileList.add(fileData);
- FileActions.display($tr.find('td.filename'), true);
+ FileActions.display($tr.find('td.filename'), true, fileList);
$tr.find('.action.delete').click();
expect(deleteStub.calledOnce).toEqual(true);
deleteStub.restore();
});
+ it('passes context to action handler', function() {
+ var actionStub = sinon.stub();
+ var fileData = {
+ id: 18,
+ type: 'file',
+ name: 'testName.txt',
+ mimetype: 'text/plain',
+ size: '1234',
+ etag: 'a01234c',
+ mtime: '123456'
+ };
+ var $tr = fileList.add(fileData);
+ FileActions.register(
+ 'all',
+ 'Test',
+ OC.PERMISSION_READ,
+ OC.imagePath('core', 'actions/test'),
+ actionStub
+ );
+ FileActions.display($tr.find('td.filename'), true, fileList);
+ $tr.find('.action-test').click();
+ expect(actionStub.calledOnce).toEqual(true);
+ expect(actionStub.getCall(0).args[0]).toEqual('testName.txt');
+ var context = actionStub.getCall(0).args[1];
+ expect(context.$file.is($tr)).toEqual(true);
+ expect(context.fileList).toBeDefined();
+ expect(context.fileActions).toBeDefined();
+ expect(context.dir).toEqual('/subdir');
+
+ // when data-path is defined
+ actionStub.reset();
+ $tr.attr('data-path', '/somepath');
+ $tr.find('.action-test').click();
+ context = actionStub.getCall(0).args[1];
+ expect(context.dir).toEqual('/somepath');
+ });
});
diff --git a/apps/files/tests/js/filelistSpec.js b/apps/files/tests/js/filelistSpec.js
index a3dc5b255a1..855a5c9af51 100644
--- a/apps/files/tests/js/filelistSpec.js
+++ b/apps/files/tests/js/filelistSpec.js
@@ -21,7 +21,6 @@
describe('OCA.Files.FileList tests', function() {
var testFiles, alertStub, notificationStub, fileList;
- var FileActions = OCA.Files.FileActions;
/**
* Generate test file data
@@ -65,6 +64,8 @@ describe('OCA.Files.FileList tests', function() {
' <div class="actions creatable"></div>' +
' <div class="notCreatable"></div>' +
'</div>' +
+ // uploader
+ '<input type="file" id="file_upload_start" name="files[]" multiple="multiple">' +
// dummy table
// TODO: at some point this will be rendered by the fileList class itself!
'<table id="filestable">' +
@@ -117,15 +118,11 @@ describe('OCA.Files.FileList tests', function() {
}];
fileList = new OCA.Files.FileList($('#app-content-files'));
- FileActions.clear();
- FileActions.registerDefaultActions(fileList);
- fileList.setFileActions(FileActions);
});
afterEach(function() {
testFiles = undefined;
fileList = undefined;
- FileActions.clear();
notificationStub.restore();
alertStub.restore();
});
@@ -488,7 +485,7 @@ describe('OCA.Files.FileList tests', function() {
var $input, request;
for (var i = 0; i < testFiles.length; i++) {
- fileList.add(testFiles[i]);
+ fileList.add(testFiles[i], {silent: true});
}
// trigger rename prompt
@@ -753,6 +750,20 @@ describe('OCA.Files.FileList tests', function() {
fileList.setFiles(testFiles);
expect(handler.calledOnce).toEqual(true);
});
+ it('triggers "fileActionsReady" event after single add', function() {
+ var handler = sinon.stub();
+ fileList.setFiles(testFiles);
+ fileList.$fileList.on('fileActionsReady', handler);
+ fileList.add({name: 'test.txt'});
+ expect(handler.calledOnce).toEqual(true);
+ });
+ it('does not trigger "fileActionsReady" event after single add with silent argument', function() {
+ var handler = sinon.stub();
+ fileList.setFiles(testFiles);
+ fileList.$fileList.on('fileActionsReady', handler);
+ fileList.add({name: 'test.txt'}, {silent: true});
+ expect(handler.notCalled).toEqual(true);
+ });
it('triggers "updated" event after update', function() {
var handler = sinon.stub();
fileList.$fileList.on('updated', handler);
@@ -1512,6 +1523,32 @@ describe('OCA.Files.FileList tests', function() {
expect(fileList.getSelectedFiles()).toEqual([]);
});
});
+ describe('File actions', function() {
+ it('Clicking on a file name will trigger default action', function() {
+ var actionStub = sinon.stub();
+ fileList.setFiles(testFiles);
+ fileList.fileActions.register(
+ 'text/plain',
+ 'Test',
+ OC.PERMISSION_ALL,
+ function() {
+ // Specify icon for hitory button
+ return OC.imagePath('core','actions/history');
+ },
+ actionStub
+ );
+ fileList.fileActions.setDefault('text/plain', 'Test');
+ var $tr = fileList.findFileEl('One.txt');
+ $tr.find('td.filename>a.name').click();
+ expect(actionStub.calledOnce).toEqual(true);
+ expect(actionStub.getCall(0).args[0]).toEqual('One.txt');
+ var context = actionStub.getCall(0).args[1];
+ expect(context.$file.is($tr)).toEqual(true);
+ expect(context.fileList).toBeDefined();
+ expect(context.fileActions).toBeDefined();
+ expect(context.dir).toEqual('/subdir');
+ });
+ });
describe('Sorting files', function() {
it('Sorts by name by default', function() {
fileList.reload();
@@ -1651,4 +1688,139 @@ describe('OCA.Files.FileList tests', function() {
expect(fileList.findFileEl('Three.pdf').index()).toEqual(0);
});
});
+ /**
+ * Test upload mostly by testing the code inside the event handlers
+ * that were registered on the magic upload object
+ */
+ describe('file upload', function() {
+ var $uploader;
+
+ beforeEach(function() {
+ // note: this isn't the real blueimp file uploader from jquery.fileupload
+ // but it makes it possible to simulate the event triggering to
+ // test the response of the handlers
+ $uploader = $('#file_upload_start');
+ fileList.setupUploadEvents();
+ fileList.setFiles(testFiles);
+ });
+
+ afterEach(function() {
+ $uploader = null;
+ });
+
+ describe('dropping external files', function() {
+ var uploadData;
+
+ /**
+ * Simulate drop event on the given target
+ *
+ * @param $target target element to drop on
+ * @return event object including the result
+ */
+ function dropOn($target, data) {
+ var eventData = {
+ originalEvent: {
+ target: $target
+ }
+ };
+ var ev = new $.Event('fileuploaddrop', eventData);
+ // using triggerHandler instead of trigger so we can pass
+ // extra data
+ $uploader.triggerHandler(ev, data || {});
+ return ev;
+ }
+
+ /**
+ * Convert form data to a flat list
+ *
+ * @param formData form data array as used by jquery.upload
+ * @return map based on the array's key values
+ */
+ function decodeFormData(data) {
+ var map = {};
+ _.each(data.formData(), function(entry) {
+ map[entry.name] = entry.value;
+ });
+ return map;
+ }
+
+ beforeEach(function() {
+ // simulate data structure from jquery.upload
+ uploadData = {
+ files: [{
+ relativePath: 'fileToUpload.txt'
+ }]
+ };
+ });
+ afterEach(function() {
+ uploadData = null;
+ });
+ it('drop on a tr or crumb outside file list does not trigger upload', function() {
+ var $anotherTable = $('<table><tbody><tr><td>outside<div class="crumb">crumb</div></td></tr></table>');
+ var ev;
+ $('#testArea').append($anotherTable);
+ ev = dropOn($anotherTable.find('tr'), uploadData);
+ expect(ev.result).toEqual(false);
+
+ ev = dropOn($anotherTable.find('.crumb'));
+ expect(ev.result).toEqual(false);
+ });
+ it('drop on an element outside file list container does not trigger upload', function() {
+ var $anotherEl = $('<div>outside</div>');
+ var ev;
+ $('#testArea').append($anotherEl);
+ ev = dropOn($anotherEl);
+
+ expect(ev.result).toEqual(false);
+ });
+ it('drop on an element inside the table triggers upload', function() {
+ var ev;
+ ev = dropOn(fileList.$fileList.find('th:first'), uploadData);
+
+ expect(ev.result).not.toEqual(false);
+ });
+ it('drop on an element on the table container triggers upload', function() {
+ var ev;
+ ev = dropOn($('#app-content-files'), uploadData);
+
+ expect(ev.result).not.toEqual(false);
+ });
+ it('drop on an element inside the table does not trigger upload if no upload permission', function() {
+ $('#permissions').val(0);
+ var ev;
+ ev = dropOn(fileList.$fileList.find('th:first'));
+
+ expect(ev.result).toEqual(false);
+ });
+ it('drop on a file row inside the table triggers upload to current folder', function() {
+ var ev;
+ ev = dropOn(fileList.findFileEl('One.txt').find('td:first'), uploadData);
+
+ expect(ev.result).not.toEqual(false);
+ });
+ it('drop on a folder row inside the table triggers upload to target folder', function() {
+ var ev, formData;
+ ev = dropOn(fileList.findFileEl('somedir').find('td:eq(2)'), uploadData);
+
+ expect(ev.result).not.toEqual(false);
+ expect(uploadData.formData).toBeDefined();
+ formData = decodeFormData(uploadData);
+ expect(formData.dir).toEqual('/subdir/somedir');
+ expect(formData.file_directory).toEqual('fileToUpload.txt');
+ expect(formData.requesttoken).toBeDefined();
+ });
+ it('drop on a breadcrumb inside the table triggers upload to target folder', function() {
+ var ev, formData;
+ fileList.changeDirectory('a/b/c/d');
+ ev = dropOn(fileList.$el.find('.crumb:eq(2)'), uploadData);
+
+ expect(ev.result).not.toEqual(false);
+ expect(uploadData.formData).toBeDefined();
+ formData = decodeFormData(uploadData);
+ expect(formData.dir).toEqual('/a/b');
+ expect(formData.file_directory).toEqual('fileToUpload.txt');
+ expect(formData.requesttoken).toBeDefined();
+ });
+ });
+ });
});