diff options
Diffstat (limited to 'apps/files')
-rw-r--r-- | apps/files/css/files.css | 13 | ||||
-rw-r--r-- | apps/files/index.php | 5 | ||||
-rw-r--r-- | apps/files/js/app.js | 35 | ||||
-rw-r--r-- | apps/files/js/fileactions.js | 103 | ||||
-rw-r--r-- | apps/files/js/filelist.js | 78 | ||||
-rw-r--r-- | apps/files/l10n/et_EE.php | 7 | ||||
-rw-r--r-- | apps/files/l10n/pt_PT.php | 5 | ||||
-rw-r--r-- | apps/files/l10n/ru.php | 5 | ||||
-rw-r--r-- | apps/files/templates/index.php | 2 | ||||
-rw-r--r-- | apps/files/tests/js/appSpec.js | 53 | ||||
-rw-r--r-- | apps/files/tests/js/fileactionsSpec.js | 80 | ||||
-rw-r--r-- | apps/files/tests/js/filelistSpec.js | 184 |
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(); + }); + }); + }); }); |