summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJan-Christoph Borchardt <hey@jancborchardt.net>2015-08-11 15:13:59 +0200
committerJan-Christoph Borchardt <hey@jancborchardt.net>2015-08-11 15:13:59 +0200
commitd04a6bce6f81df4ee4351a311a630b14e8ecd8a3 (patch)
treeaa37e75da3021abf7e9fff06c419e8cd1461970b
parentcd0a2874de1f52827bca8d747128909099229cf4 (diff)
parent984ae8140d986e93a2fcea5951436e95c8e2c603 (diff)
downloadnextcloud-server-d04a6bce6f81df4ee4351a311a630b14e8ecd8a3.tar.gz
nextcloud-server-d04a6bce6f81df4ee4351a311a630b14e8ecd8a3.zip
Merge pull request #17709 from owncloud/fileactions-dropdown
Move file actions to dropdown
-rw-r--r--apps/files/css/files.css123
-rw-r--r--apps/files/css/mobile.css22
-rw-r--r--apps/files/index.php1
-rw-r--r--apps/files/js/fileactions.js321
-rw-r--r--apps/files/js/fileactionsmenu.js132
-rw-r--r--apps/files/js/filelist.js56
-rw-r--r--apps/files/js/tagsplugin.js1
-rw-r--r--apps/files/tests/js/fileactionsSpec.js563
-rw-r--r--apps/files/tests/js/fileactionsmenuSpec.js273
-rw-r--r--apps/files/tests/js/filelistSpec.js79
-rw-r--r--apps/files_sharing/js/share.js96
-rw-r--r--apps/files_sharing/templates/public.php1
-rw-r--r--apps/files_sharing/tests/js/shareSpec.js6
-rw-r--r--apps/files_trashbin/js/app.js1
-rw-r--r--core/css/apps.css13
-rw-r--r--core/js/core.json4
-rw-r--r--core/js/js.js61
-rw-r--r--core/js/share.js4
18 files changed, 1176 insertions, 581 deletions
diff --git a/apps/files/css/files.css b/apps/files/css/files.css
index 7e3318a962b..26ba86b28c8 100644
--- a/apps/files/css/files.css
+++ b/apps/files/css/files.css
@@ -249,8 +249,8 @@ table th.column-last, table td.column-last {
box-sizing: border-box;
position: relative;
/* this can not be just width, both need to be set … table styling */
- min-width: 176px;
- max-width: 176px;
+ min-width: 130px;
+ max-width: 130px;
}
/* Multiselect bar */
@@ -326,14 +326,7 @@ table td.filename .nametext, .uploadtext, .modified, .column-last>span:first-chi
position: relative;
overflow: hidden;
text-overflow: ellipsis;
- width: 90%;
-}
-/* ellipsize long modified dates to make room for showing delete button */
-#fileList tr:hover .modified,
-#fileList tr:focus .modified,
-#fileList tr:hover .column-last>span:first-child,
-#fileList tr:focus .column-last>span:first-child {
- width: 75%;
+ width: 110px;
}
/* TODO fix usability bug (accidental file/folder selection) */
@@ -372,45 +365,27 @@ table td.filename .nametext .innernametext {
@media only screen and (min-width: 1366px) {
table td.filename .nametext .innernametext {
- max-width: 760px;
- }
-
- table tr:hover td.filename .nametext .innernametext,
- table tr:focus td.filename .nametext .innernametext {
- max-width: 480px;
+ max-width: 660px;
}
}
-
@media only screen and (min-width: 1200px) and (max-width: 1366px) {
table td.filename .nametext .innernametext {
- max-width: 600px;
- }
-
- table tr:hover td.filename .nametext .innernametext,
- table tr:focus td.filename .nametext .innernametext {
- max-width: 320px;
+ max-width: 500px;
}
}
-
-@media only screen and (min-width: 1000px) and (max-width: 1200px) {
+@media only screen and (min-width: 1100px) and (max-width: 1200px) {
table td.filename .nametext .innernametext {
max-width: 400px;
}
-
- table tr:hover td.filename .nametext .innernametext,
- table tr:focus td.filename .nametext .innernametext {
- max-width: 120px;
+}
+@media only screen and (min-width: 1000px) and (max-width: 1100px) {
+ table td.filename .nametext .innernametext {
+ max-width: 310px;
}
}
-
@media only screen and (min-width: 768px) and (max-width: 1000px) {
table td.filename .nametext .innernametext {
- max-width: 320px;
- }
-
- table tr:hover td.filename .nametext .innernametext,
- table tr:focus td.filename .nametext .innernametext {
- max-width: 40px;
+ max-width: 240px;
}
}
@@ -517,6 +492,23 @@ table td.filename .uploadtext {
font-size: 11px;
}
+.busy .fileactions, .busy .action {
+ visibility: hidden;
+}
+
+/* fix position of bubble pointer for Files app */
+.bubble,
+#app-navigation .app-navigation-entry-menu {
+ border-top-right-radius: 3px;
+}
+.bubble:after,
+#app-navigation .app-navigation-entry-menu:after {
+ right: 6px;
+}
+.bubble:before,
+#app-navigation .app-navigation-entry-menu:before {
+ right: 6px;
+}
/* force show the loading icon, not only on hover */
#fileList .icon-loading-small {
@@ -527,21 +519,15 @@ table td.filename .uploadtext {
}
#fileList img.move2trash { display:inline; margin:-8px 0; padding:16px 8px 16px 8px !important; float:right; }
-#fileList a.action.delete {
- position: absolute;
- right: 15px;
- padding: 17px 14px;
-}
#fileList .action.action-share-notification span, #fileList a.name {
cursor: default !important;
}
-a.action>img {
- max-height:16px;
- max-width:16px;
- vertical-align:text-bottom;
- margin-bottom: -1px;
+a.action > img {
+ max-height: 16px;
+ max-width: 16px;
+ vertical-align: text-bottom;
}
/* Actions for selected files */
@@ -578,10 +564,6 @@ a.action>img {
display:none;
}
-#fileList a.action[data-action="Rename"] {
- padding: 16px 14px 17px !important;
-}
-
.ie8 #fileList a.action img,
#fileList tr:hover a.action,
#fileList a.action.permanent,
@@ -693,3 +675,44 @@ table.dragshadow td.size {
.mask.transparent{
opacity: 0;
}
+
+.fileActionsMenu {
+ padding: 4px 12px;
+}
+.fileActionsMenu li {
+ padding: 5px 0;
+}
+#fileList .fileActionsMenu a.action img {
+ padding: initial;
+}
+#fileList .fileActionsMenu a.action {
+ padding: 10px;
+ margin: -10px;
+}
+
+.fileActionsMenu.hidden {
+ display: none;
+}
+
+#fileList .fileActionsMenu .action {
+ display: block;
+ line-height: 30px;
+ padding-left: 5px;
+ color: #000;
+ padding: 0;
+}
+
+.fileActionsMenu .action img,
+.fileActionsMenu .action .no-icon {
+ display: inline-block;
+ width: 16px;
+ margin-right: 5px;
+}
+
+.fileActionsMenu .action {
+ opacity: 0.5;
+}
+
+.fileActionsMenu li:hover .action {
+ opacity: 1;
+}
diff --git a/apps/files/css/mobile.css b/apps/files/css/mobile.css
index 4881f7c70e4..dd8244a2913 100644
--- a/apps/files/css/mobile.css
+++ b/apps/files/css/mobile.css
@@ -5,11 +5,6 @@
min-width: initial !important;
}
-/* do not show Deleted Files on mobile, not optimized yet and button too long */
-#controls #trash {
- display: none;
-}
-
/* hide size and date columns */
table th#headerSize,
table td.filesize,
@@ -38,7 +33,8 @@ table td.filename .nametext {
}
/* always show actions on mobile, not only on hover */
-#fileList a.action {
+#fileList a.action,
+#fileList a.action.action-menu.permanent {
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=20)" !important;
filter: alpha(opacity=20) !important;
opacity: .2 !important;
@@ -50,17 +46,19 @@ table td.filename .nametext {
filter: alpha(opacity=70) !important;
opacity: .7 !important;
}
-/* do not show Rename or Versions on mobile */
-#fileList .action.action-rename,
-#fileList .action.action-versions {
- display: none !important;
+#fileList a.action.action-menu img {
+ padding-left: 2px;
+}
+
+#fileList .fileActionsMenu {
+ margin-right: 5px;
}
/* some padding for better clickability */
#fileList a.action img {
padding: 0 6px 0 12px;
}
-/* hide text of the actions on mobile */
-#fileList a.action span {
+/* hide text of the share action on mobile */
+#fileList a.action-share span {
display: none;
}
diff --git a/apps/files/index.php b/apps/files/index.php
index dca3e5ae74d..a41ec059b55 100644
--- a/apps/files/index.php
+++ b/apps/files/index.php
@@ -138,6 +138,7 @@ foreach ($navItems as $item) {
}
OCP\Util::addscript('files', 'fileactions');
+OCP\Util::addscript('files', 'fileactionsmenu');
OCP\Util::addscript('files', 'files');
OCP\Util::addscript('files', 'navigation');
OCP\Util::addscript('files', 'keyboardshortcuts');
diff --git a/apps/files/js/fileactions.js b/apps/files/js/fileactions.js
index 8dd26d71c3e..43f74c5816d 100644
--- a/apps/files/js/fileactions.js
+++ b/apps/files/js/fileactions.js
@@ -10,6 +10,12 @@
(function() {
+ var TEMPLATE_FILE_ACTION_TRIGGER =
+ '<a class="action action-{{nameLowerCase}}" href="#" data-action="{{name}}">' +
+ '{{#if icon}}<img class="svg" alt="{{altText}}" src="{{icon}}" />{{/if}}' +
+ '{{#if displayName}}<span> {{displayName}}</span>{{/if}}' +
+ '</a>';
+
/**
* Construct a new FileActions instance
* @constructs FileActions
@@ -18,6 +24,8 @@
var FileActions = function() {
this.initialize();
};
+ FileActions.TYPE_DROPDOWN = 0;
+ FileActions.TYPE_INLINE = 1;
FileActions.prototype = {
/** @lends FileActions.prototype */
actions: {},
@@ -38,6 +46,8 @@
*/
_updateListeners: {},
+ _fileActionTriggerTemplate: null,
+
/**
* @private
*/
@@ -46,6 +56,8 @@
// abusing jquery for events until we get a real event lib
this.$el = $('<div class="dummy-fileactions hidden"></div>');
$('body').append(this.$el);
+
+ this._showMenuClosure = _.bind(this._showMenu, this);
},
/**
@@ -111,6 +123,7 @@
displayName: displayName || name
});
},
+
/**
* Register action
*
@@ -125,15 +138,14 @@
displayName: action.displayName,
mime: mime,
icon: action.icon,
- permissions: action.permissions
+ permissions: action.permissions,
+ type: action.type || FileActions.TYPE_DROPDOWN
};
if (_.isUndefined(action.displayName)) {
actionSpec.displayName = t('files', name);
}
if (_.isFunction(action.render)) {
actionSpec.render = action.render;
- } else {
- actionSpec.render = _.bind(this._defaultRenderAction, this);
}
if (!this.actions[mime]) {
this.actions[mime] = {};
@@ -162,6 +174,16 @@
this.defaults[mime] = name;
this._notifyUpdateListeners('setDefault', {defaultAction: {mime: mime, name: name}});
},
+
+ /**
+ * Returns a map of file actions handlers matching the given conditions
+ *
+ * @param {string} mime mime type
+ * @param {string} type "dir" or "file"
+ * @param {int} permissions permissions
+ *
+ * @return {Object.<string,OCA.Files.FileActions~actionHandler>} map of action name to action spec
+ */
get: function (mime, type, permissions) {
var actions = this.getActions(mime, type, permissions);
var filteredActions = {};
@@ -170,6 +192,16 @@
});
return filteredActions;
},
+
+ /**
+ * Returns an array of file actions matching the given conditions
+ *
+ * @param {string} mime mime type
+ * @param {string} type "dir" or "file"
+ * @param {int} permissions permissions
+ *
+ * @return {Array.<OCA.Files.FileAction>} array of action specs
+ */
getActions: function (mime, type, permissions) {
var actions = {};
if (this.actions.all) {
@@ -197,7 +229,37 @@
});
return filteredActions;
},
+
+ /**
+ * Returns the default file action handler for the given conditions
+ *
+ * @param {string} mime mime type
+ * @param {string} type "dir" or "file"
+ * @param {int} permissions permissions
+ *
+ * @return {OCA.Files.FileActions~actionHandler} action handler
+ *
+ * @deprecated use getDefaultFileAction instead
+ */
getDefault: function (mime, type, permissions) {
+ var defaultActionSpec = this.getDefaultFileAction(mime, type, permissions);
+ if (defaultActionSpec) {
+ return defaultActionSpec.action;
+ }
+ return undefined;
+ },
+
+ /**
+ * Returns the default file action handler for the given conditions
+ *
+ * @param {string} mime mime type
+ * @param {string} type "dir" or "file"
+ * @param {int} permissions permissions
+ *
+ * @return {OCA.Files.FileActions~actionHandler} action handler
+ * @since 8.2
+ */
+ getDefaultFileAction: function(mime, type, permissions) {
var mimePart;
if (mime) {
mimePart = mime.substr(0, mime.indexOf('/'));
@@ -212,9 +274,10 @@
} else {
name = this.defaults.all;
}
- var actions = this.get(mime, type, permissions);
+ var actions = this.getActions(mime, type, permissions);
return actions[name];
},
+
/**
* Default function to render actions
*
@@ -224,87 +287,82 @@
* @param {OCA.Files.FileActionContext} context action context
*/
_defaultRenderAction: function(actionSpec, isDefault, context) {
- var name = actionSpec.name;
- if (name === 'Download' || !isDefault) {
- var $actionLink = this._makeActionLink(actionSpec, context);
+ if (!isDefault) {
+ var params = {
+ name: actionSpec.name,
+ nameLowerCase: actionSpec.name.toLowerCase(),
+ displayName: actionSpec.displayName,
+ icon: actionSpec.icon,
+ altText: actionSpec.altText,
+ };
+ if (_.isFunction(actionSpec.icon)) {
+ params.icon = actionSpec.icon(context.$file.attr('data-file'));
+ }
+
+ var $actionLink = this._makeActionLink(params, context);
context.$file.find('a.name>span.fileactions').append($actionLink);
+ $actionLink.addClass('permanent');
return $actionLink;
}
},
+
/**
* Renders the action link element
*
- * @param {OCA.Files.FileAction} actionSpec action object
- * @param {OCA.Files.FileActionContext} context action context
+ * @param {Object} params action params
*/
- _makeActionLink: function(actionSpec, context) {
- var img = actionSpec.icon;
- if (img && img.call) {
- img = img(context.$file.attr('data-file'));
- }
- var html = '<a href="#">';
- if (img) {
- html += '<img class="svg" alt="" src="' + img + '" />';
- }
- if (actionSpec.displayName) {
- html += '<span> ' + actionSpec.displayName + '</span>';
+ _makeActionLink: function(params) {
+ if (!this._fileActionTriggerTemplate) {
+ this._fileActionTriggerTemplate = Handlebars.compile(TEMPLATE_FILE_ACTION_TRIGGER);
}
- html += '</a>';
- return $(html);
+ return $(this._fileActionTriggerTemplate(params));
},
+
/**
- * Custom renderer for the "Rename" action.
- * Displays the rename action as an icon behind the file name.
+ * Displays the file actions dropdown menu
*
- * @param {OCA.Files.FileAction} actionSpec file action to render
- * @param {boolean} isDefault true if the action is a default action,
- * false otherwise
- * @param {OCAFiles.FileActionContext} context rendering context
+ * @param {string} fileName file name
+ * @param {OCA.Files.FileActionContext} context rendering context
*/
- _renderRenameAction: function(actionSpec, isDefault, context) {
- var $actionEl = this._makeActionLink(actionSpec, context);
- var $container = context.$file.find('a.name span.nametext');
- $actionEl.find('img').attr('alt', t('files', 'Rename'));
- $container.find('.action-rename').remove();
- $container.append($actionEl);
- return $actionEl;
+ _showMenu: function(fileName, context) {
+ var menu;
+ var $trigger = context.$file.closest('tr').find('.fileactions .action-menu');
+ $trigger.addClass('open');
+
+ menu = new OCA.Files.FileActionsMenu();
+ menu.$el.on('afterHide', function() {
+ context.$file.removeClass('mouseOver');
+ $trigger.removeClass('open');
+ menu.remove();
+ });
+
+ context.$file.addClass('mouseOver');
+ context.$file.find('td.filename').append(menu.$el);
+ menu.show(context);
},
+
/**
- * Custom renderer for the "Delete" action.
- * Displays the "Delete" action as a trash icon at the end of
- * the table row.
- *
- * @param {OCA.Files.FileAction} actionSpec file action to render
- * @param {boolean} isDefault true if the action is a default action,
- * false otherwise
- * @param {OCAFiles.FileActionContext} context rendering context
+ * Renders the menu trigger on the given file list row
+ *
+ * @param {Object} $tr file list row element
+ * @param {OCA.Files.FileActionContext} context rendering context
*/
- _renderDeleteAction: function(actionSpec, isDefault, context) {
- var mountType = context.$file.attr('data-mounttype');
- var deleteTitle = t('files', 'Delete');
- if (mountType === 'external-root') {
- deleteTitle = t('files', 'Disconnect storage');
- } else if (mountType === 'shared-root') {
- deleteTitle = t('files', 'Unshare');
- }
- var cssClasses = 'action delete icon-delete';
- if((context.$file.data('permissions') & OC.PERMISSION_DELETE) === 0) {
- // add css class no-permission to delete icon
- cssClasses += ' no-permission';
- deleteTitle = t('files', 'No permission to delete');
- }
- var $actionLink = $('<a href="#" original-title="' +
- escapeHTML(deleteTitle) +
- '" class="' +cssClasses + '">' +
- '<span class="hidden-visually">' + escapeHTML(deleteTitle) + '</span>' +
- '</a>'
- );
- var $container = context.$file.find('td:last');
- $container.find('.delete').remove();
- $container.append($actionLink);
- return $actionLink;
+ _renderMenuTrigger: function($tr, context) {
+ // remove previous
+ $tr.find('.action-menu').remove();
+
+ var $el = this._renderInlineAction({
+ name: 'menu',
+ displayName: '',
+ icon: OC.imagePath('core', 'actions/more'),
+ altText: t('files', 'Actions'),
+ action: this._showMenuClosure
+ }, false, context);
+
+ $el.addClass('permanent');
},
+
/**
* Renders the action element by calling actionSpec.render() and
* registers the click event to process the action.
@@ -312,25 +370,32 @@
* @param {OCA.Files.FileAction} actionSpec file action to render
* @param {boolean} isDefault true if the action is a default action,
* false otherwise
- * @param {OCAFiles.FileActionContext} context rendering context
+ * @param {OCA.Files.FileActionContext} context rendering context
*/
- _renderAction: function(actionSpec, isDefault, context) {
- var $actionEl = actionSpec.render(actionSpec, isDefault, context);
+ _renderInlineAction: function(actionSpec, isDefault, context) {
+ var renderFunc = actionSpec.render || _.bind(this._defaultRenderAction, this);
+ var $actionEl = renderFunc(actionSpec, isDefault, context);
if (!$actionEl || !$actionEl.length) {
return;
}
- $actionEl.addClass('action action-' + actionSpec.name.toLowerCase());
- $actionEl.attr('data-action', actionSpec.name);
$actionEl.on(
'click', {
a: null
},
function(event) {
+ event.stopPropagation();
+ event.preventDefault();
+
+ if ($actionEl.hasClass('open')) {
+ return;
+ }
+
var $file = $(event.target).closest('tr');
+ if ($file.hasClass('busy')) {
+ return;
+ }
var currentFile = $file.find('td.filename');
var fileName = $file.attr('data-file');
- event.stopPropagation();
- event.preventDefault();
context.fileActions.currentFile = currentFile;
// also set on global object for legacy apps
@@ -346,6 +411,7 @@
);
return $actionEl;
},
+
/**
* Display file actions for the given element
* @param parent "td" element of the file for which to display actions
@@ -376,36 +442,29 @@
nameLinks = parent.children('a.name');
nameLinks.find('.fileactions, .nametext .action').remove();
nameLinks.append('<span class="fileactions" />');
- var defaultAction = this.getDefault(
+ var defaultAction = this.getDefaultFileAction(
this.getCurrentMimeType(),
this.getCurrentType(),
this.getCurrentPermissions()
);
+ var context = {
+ $file: $tr,
+ fileActions: this,
+ fileList: fileList
+ };
+
$.each(actions, function (name, actionSpec) {
- if (name !== 'Share') {
- self._renderAction(
+ if (actionSpec.type === FileActions.TYPE_INLINE) {
+ self._renderInlineAction(
actionSpec,
- actionSpec.action === defaultAction, {
- $file: $tr,
- fileActions: this,
- fileList : fileList
- }
+ defaultAction && actionSpec.name === defaultAction.name,
+ context
);
}
});
- // added here to make sure it's always the last action
- var shareActionSpec = actions.Share;
- if (shareActionSpec){
- this._renderAction(
- shareActionSpec,
- shareActionSpec.action === defaultAction, {
- $file: $tr,
- fileActions: this,
- fileList: fileList
- }
- );
- }
+
+ this._renderMenuTrigger($tr, context);
if (triggerEvent){
fileList.$fileList.trigger(jQuery.Event("fileActionsReady", {fileList: fileList, $files: $tr}));
@@ -429,35 +488,42 @@
*/
registerDefaultActions: function() {
this.registerAction({
- name: 'Delete',
- displayName: '',
+ name: 'Download',
+ displayName: t('files', 'Download'),
mime: 'all',
- // permission is READ because we show a hint instead if there is no permission
permissions: OC.PERMISSION_READ,
- icon: function() {
- return OC.imagePath('core', 'actions/delete');
+ icon: function () {
+ return OC.imagePath('core', 'actions/download');
},
- render: _.bind(this._renderDeleteAction, this),
- actionHandler: function(fileName, context) {
- // if there is no permission to delete do nothing
- if((context.$file.data('permissions') & OC.PERMISSION_DELETE) === 0) {
+ actionHandler: function (filename, context) {
+ var dir = context.dir || context.fileList.getCurrentDirectory();
+ var url = context.fileList.getDownloadUrl(filename, dir);
+
+ var downloadFileaction = $(context.$file).find('.fileactions .action-download');
+
+ // don't allow a second click on the download action
+ if(downloadFileaction.hasClass('disabled')) {
return;
}
- context.fileList.do_delete(fileName, context.dir);
- $('.tipsy').remove();
+
+ if (url) {
+ var disableLoadingState = function() {
+ context.fileList.showFileBusyState(filename, false);
+ };
+
+ context.fileList.showFileBusyState(downloadFileaction, true);
+ OCA.Files.Files.handleDownload(url, disableLoadingState);
+ }
}
});
- // t('files', 'Rename')
this.registerAction({
name: 'Rename',
- displayName: '',
mime: 'all',
permissions: OC.PERMISSION_UPDATE,
icon: function() {
return OC.imagePath('core', 'actions/rename');
},
- render: _.bind(this._renderRenameAction, this),
actionHandler: function (filename, context) {
context.fileList.rename(filename);
}
@@ -471,30 +537,25 @@
context.fileList.changeDirectory(dir + filename);
});
- this.setDefault('dir', 'Open');
-
- this.register('all', 'Download', OC.PERMISSION_READ, function () {
- return OC.imagePath('core', 'actions/download');
- }, function (filename, context) {
- var dir = context.dir || context.fileList.getCurrentDirectory();
- var url = context.fileList.getDownloadUrl(filename, dir);
-
- var downloadFileaction = $(context.$file).find('.fileactions .action-download');
-
- // don't allow a second click on the download action
- if(downloadFileaction.hasClass('disabled')) {
- return;
+ this.registerAction({
+ name: 'Delete',
+ mime: 'all',
+ // permission is READ because we show a hint instead if there is no permission
+ permissions: OC.PERMISSION_READ,
+ icon: function() {
+ return OC.imagePath('core', 'actions/delete');
+ },
+ actionHandler: function(fileName, context) {
+ // if there is no permission to delete do nothing
+ if((context.$file.data('permissions') & OC.PERMISSION_DELETE) === 0) {
+ return;
+ }
+ context.fileList.do_delete(fileName, context.dir);
+ $('.tipsy').remove();
}
+ });
- if (url) {
- var disableLoadingState = function(){
- OCA.Files.FileActions.updateFileActionSpinner(downloadFileaction, false);
- };
-
- OCA.Files.FileActions.updateFileActionSpinner(downloadFileaction, true);
- OCA.Files.Files.handleDownload(url, disableLoadingState);
- }
- }, t('files', 'Download'));
+ this.setDefault('dir', 'Open');
}
};
diff --git a/apps/files/js/fileactionsmenu.js b/apps/files/js/fileactionsmenu.js
new file mode 100644
index 00000000000..623ebde5442
--- /dev/null
+++ b/apps/files/js/fileactionsmenu.js
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) 2014
+ *
+ * This file is licensed under the Affero General Public License version 3
+ * or later.
+ *
+ * See the COPYING-README file.
+ *
+ */
+
+(function() {
+
+ var TEMPLATE_MENU =
+ '<ul>' +
+ '{{#each items}}' +
+ '<li>' +
+ '<a href="#" class="action action-{{nameLowerCase}} permanent" data-action="{{name}}">{{#if icon}}<img src="{{icon}}"/>{{else}}<span class="no-icon"></span>{{/if}}<span>{{displayName}}</span></a>' +
+ '</li>' +
+ '{{/each}}' +
+ '</ul>';
+
+ /**
+ * Construct a new FileActionsMenu instance
+ * @constructs FileActionsMenu
+ * @memberof OCA.Files
+ */
+ var FileActionsMenu = OC.Backbone.View.extend({
+ tagName: 'div',
+ className: 'fileActionsMenu bubble hidden open menu',
+
+ /**
+ * Current context
+ *
+ * @type OCA.Files.FileActionContext
+ */
+ _context: null,
+
+ events: {
+ 'click a.action': '_onClickAction'
+ },
+
+ template: function(data) {
+ if (!OCA.Files.FileActionsMenu._TEMPLATE) {
+ OCA.Files.FileActionsMenu._TEMPLATE = Handlebars.compile(TEMPLATE_MENU);
+ }
+ return OCA.Files.FileActionsMenu._TEMPLATE(data);
+ },
+
+ /**
+ * Event handler whenever an action has been clicked within the menu
+ *
+ * @param {Object} event event object
+ */
+ _onClickAction: function(event) {
+ var $target = $(event.target);
+ if (!$target.is('a')) {
+ $target = $target.closest('a');
+ }
+ var fileActions = this._context.fileActions;
+ var actionName = $target.attr('data-action');
+ var actions = fileActions.getActions(
+ fileActions.getCurrentMimeType(),
+ fileActions.getCurrentType(),
+ fileActions.getCurrentPermissions()
+ );
+ var actionSpec = actions[actionName];
+ var fileName = this._context.$file.attr('data-file');
+
+ event.stopPropagation();
+ event.preventDefault();
+
+ OC.hideMenus();
+
+ actionSpec.action(
+ fileName,
+ this._context
+ );
+ },
+
+ /**
+ * Renders the menu with the currently set items
+ */
+ render: function() {
+ var fileActions = this._context.fileActions;
+ var actions = fileActions.getActions(
+ fileActions.getCurrentMimeType(),
+ fileActions.getCurrentType(),
+ fileActions.getCurrentPermissions()
+ );
+
+ var defaultAction = fileActions.getDefaultFileAction(
+ fileActions.getCurrentMimeType(),
+ fileActions.getCurrentType(),
+ fileActions.getCurrentPermissions()
+ );
+
+ var items = _.filter(actions, function(actionSpec) {
+ return (
+ actionSpec.type === OCA.Files.FileActions.TYPE_DROPDOWN &&
+ (!defaultAction || actionSpec.name !== defaultAction.name)
+ );
+ });
+ items = _.map(items, function(item) {
+ item.nameLowerCase = item.name.toLowerCase();
+ return item;
+ });
+
+ this.$el.html(this.template({
+ items: items
+ }));
+ },
+
+ /**
+ * Displays the menu under the given element
+ *
+ * @param {OCA.Files.FileActionContext} context context
+ * @param {Object} $trigger trigger element
+ */
+ show: function(context) {
+ this._context = context;
+
+ this.render();
+ this.$el.removeClass('hidden');
+
+ OC.showMenu(null, this.$el);
+ }
+ });
+
+ OCA.Files.FileActionsMenu = FileActionsMenu;
+
+})();
+
diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js
index f5629ecd2c3..e297edcf11b 100644
--- a/apps/files/js/filelist.js
+++ b/apps/files/js/filelist.js
@@ -1444,9 +1444,7 @@
}
_.each(fileNames, function(fileName) {
var $tr = self.findFileEl(fileName);
- var $thumbEl = $tr.find('.thumbnail');
- var oldBackgroundImage = $thumbEl.css('background-image');
- $thumbEl.css('background-image', 'url('+ OC.imagePath('core', 'loading.gif') + ')');
+ self.showFileBusyState($tr, true);
// TODO: improve performance by sending all file names in a single call
$.post(
OC.filePath('files', 'ajax', 'move.php'),
@@ -1488,7 +1486,7 @@
} else {
OC.dialogs.alert(t('files', 'Error moving file'), t('files', 'Error'));
}
- $thumbEl.css('background-image', oldBackgroundImage);
+ self.showFileBusyState($tr, false);
}
);
});
@@ -1549,14 +1547,13 @@
try {
var newName = input.val();
- var $thumbEl = tr.find('.thumbnail');
input.tipsy('hide');
form.remove();
if (newName !== oldname) {
checkInput();
// mark as loading (temp element)
- $thumbEl.css('background-image', 'url('+ OC.imagePath('core', 'loading.gif') + ')');
+ self.showFileBusyState(tr, true);
tr.attr('data-file', newName);
var basename = newName;
if (newName.indexOf('.') > 0 && tr.data('type') !== 'dir') {
@@ -1564,7 +1561,6 @@
}
td.find('a.name span.nametext').text(basename);
td.children('a.name').show();
- tr.find('.fileactions, .action').addClass('hidden');
$.ajax({
url: OC.filePath('files','ajax','rename.php'),
@@ -1636,6 +1632,44 @@
inList:function(file) {
return this.findFileEl(file).length;
},
+
+ /**
+ * Shows busy state on a given file row or multiple
+ *
+ * @param {string|Array.<string>} files file name or array of file names
+ * @param {bool} [busy=true] busy state, true for busy, false to remove busy state
+ *
+ * @since 8.2
+ */
+ showFileBusyState: function(files, state) {
+ var self = this;
+ if (!_.isArray(files)) {
+ files = [files];
+ }
+
+ if (_.isUndefined(state)) {
+ state = true;
+ }
+
+ _.each(files, function($tr) {
+ // jquery element already ?
+ if (!$tr.is) {
+ $tr = self.findFileEl($tr);
+ }
+
+ var $thumbEl = $tr.find('.thumbnail');
+ $tr.toggleClass('busy', state);
+
+ if (state) {
+ $thumbEl.attr('data-oldimage', $thumbEl.css('background-image'));
+ $thumbEl.css('background-image', 'url('+ OC.imagePath('core', 'loading.gif') + ')');
+ } else {
+ $thumbEl.css('background-image', $thumbEl.attr('data-oldimage'));
+ $thumbEl.removeAttr('data-oldimage');
+ }
+ });
+ },
+
/**
* Delete the given files from the given dir
* @param files file names list (without path)
@@ -1649,9 +1683,8 @@
files=[files];
}
if (files) {
+ this.showFileBusyState(files, true);
for (var i=0; i<files.length; i++) {
- var deleteAction = this.findFileEl(files[i]).children("td.date").children(".action.delete");
- deleteAction.removeClass('icon-delete').addClass('icon-loading-small');
}
}
// Finish any existing actions
@@ -1669,7 +1702,7 @@
// no files passed, delete all in current dir
params.allfiles = true;
// show spinner for all files
- this.$fileList.find('tr>td.date .action.delete').removeClass('icon-delete').addClass('icon-loading-small');
+ this.$fileList.find('tr').addClass('busy');
}
$.post(OC.filePath('files', 'ajax', 'delete.php'),
@@ -1712,8 +1745,7 @@
}
else {
$.each(files,function(index,file) {
- var deleteAction = self.findFileEl(file).find('.action.delete');
- deleteAction.removeClass('icon-loading-small').addClass('icon-delete');
+ self.$fileList.find('tr').removeClass('busy');
});
}
}
diff --git a/apps/files/js/tagsplugin.js b/apps/files/js/tagsplugin.js
index 293e25176f3..ec69ce4b965 100644
--- a/apps/files/js/tagsplugin.js
+++ b/apps/files/js/tagsplugin.js
@@ -81,6 +81,7 @@
displayName: 'Favorite',
mime: 'all',
permissions: OC.PERMISSION_READ,
+ type: OCA.Files.FileActions.TYPE_INLINE,
render: function(actionSpec, isDefault, context) {
var $file = context.$file;
var isFavorite = $file.data('favorite') === true;
diff --git a/apps/files/tests/js/fileactionsSpec.js b/apps/files/tests/js/fileactionsSpec.js
index e420ab828af..236cff6cafd 100644
--- a/apps/files/tests/js/fileactionsSpec.js
+++ b/apps/files/tests/js/fileactionsSpec.js
@@ -20,8 +20,7 @@
*/
describe('OCA.Files.FileActions tests', function() {
- var $filesTable, fileList;
- var FileActions;
+ var fileList, fileActions;
beforeEach(function() {
// init horrible parameters
@@ -29,211 +28,191 @@ describe('OCA.Files.FileActions tests', function() {
$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 = new OCA.Files.FileList($('#testArea'));
- FileActions = new OCA.Files.FileActions();
- FileActions.registerDefaultActions();
+ fileActions = new OCA.Files.FileActions();
+ fileActions.registerAction({
+ name: 'Testdropdown',
+ displayName: 'Testdropdowndisplay',
+ mime: 'all',
+ permissions: OC.PERMISSION_READ,
+ icon: function () {
+ return OC.imagePath('core', 'actions/download');
+ }
+ });
+
+ fileActions.registerAction({
+ name: 'Testinline',
+ displayName: 'Testinlinedisplay',
+ type: OCA.Files.FileActions.TYPE_INLINE,
+ mime: 'all',
+ permissions: OC.PERMISSION_READ
+ });
+
+ fileActions.registerAction({
+ name: 'Testdefault',
+ displayName: 'Testdefaultdisplay',
+ mime: 'all',
+ permissions: OC.PERMISSION_READ
+ });
+ fileActions.setDefault('all', 'Testdefault');
+ fileList = new OCA.Files.FileList($body, {
+ fileActions: fileActions
+ });
});
afterEach(function() {
- FileActions = null;
+ fileActions = null;
fileList.destroy();
fileList = undefined;
$('#dir, #permissions, #filestable').remove();
});
it('calling clear() clears file actions', function() {
- FileActions.clear();
- expect(FileActions.actions).toEqual({});
- expect(FileActions.defaults).toEqual({});
- expect(FileActions.icons).toEqual({});
- expect(FileActions.currentFile).toBe(null);
+ fileActions.clear();
+ expect(fileActions.actions).toEqual({});
+ expect(fileActions.defaults).toEqual({});
+ expect(fileActions.icons).toEqual({});
+ expect(fileActions.currentFile).toBe(null);
});
- it('calling display() sets file actions', function() {
- var fileData = {
- id: 18,
- type: 'file',
- name: 'testName.txt',
- mimetype: 'text/plain',
- size: '1234',
- etag: 'a01234c',
- mtime: '123456'
- };
-
- // note: FileActions.display() is called implicitly
- var $tr = fileList.add(fileData);
-
- // actions defined after call
- expect($tr.find('.action.action-download').length).toEqual(1);
- expect($tr.find('.action.action-download').attr('data-action')).toEqual('Download');
- expect($tr.find('.nametext .action.action-rename').length).toEqual(1);
- expect($tr.find('.nametext .action.action-rename').attr('data-action')).toEqual('Rename');
- expect($tr.find('.action.delete').length).toEqual(1);
- });
- it('calling display() twice correctly replaces file actions', function() {
- var fileData = {
- id: 18,
- type: 'file',
- name: 'testName.txt',
- mimetype: 'text/plain',
- size: '1234',
- etag: 'a01234c',
- mtime: '123456'
- };
- var $tr = fileList.add(fileData);
-
- 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);
- expect($tr.find('.nametext .action.action-rename').length).toEqual(1);
- expect($tr.find('.action.delete').length).toEqual(1);
- });
- it('redirects to download URL when clicking download', function() {
- var redirectStub = sinon.stub(OC, 'redirect');
- var fileData = {
- id: 18,
- type: 'file',
- name: 'testName.txt',
- 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]).toContain(
- 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]).toContain(
- OC.webroot + '/index.php/apps/files/ajax/download.php' +
- '?dir=%2Fanotherpath%2Fthere&files=testName.txt'
- );
- redirectStub.restore();
- });
- it('deletes file when clicking delete', function() {
- var deleteStub = sinon.stub(fileList, 'do_delete');
- var fileData = {
- id: 18,
- type: 'file',
- name: 'testName.txt',
- path: '/somepath/dir',
- 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.delete').click();
-
- expect(deleteStub.calledOnce).toEqual(true);
- expect(deleteStub.getCall(0).args[0]).toEqual('testName.txt');
- expect(deleteStub.getCall(0).args[1]).toEqual('/somepath/dir');
- deleteStub.restore();
- });
- it('shows delete hint when no permission to delete', function() {
- var deleteStub = sinon.stub(fileList, 'do_delete');
- var fileData = {
- id: 18,
- type: 'file',
- name: 'testName.txt',
- path: '/somepath/dir',
- mimetype: 'text/plain',
- size: '1234',
- etag: 'a01234c',
- mtime: '123456',
- permissions: OC.PERMISSION_READ
- };
- var $tr = fileList.add(fileData);
- FileActions.display($tr.find('td.filename'), true, fileList);
+ describe('displaying actions', function() {
+ var $tr;
- var $action = $tr.find('.action.delete');
+ beforeEach(function() {
+ var fileData = {
+ id: 18,
+ type: 'file',
+ name: 'testName.txt',
+ mimetype: 'text/plain',
+ size: '1234',
+ etag: 'a01234c',
+ mtime: '123456',
+ permissions: OC.PERMISSION_READ | OC.PERMISSION_UPDATE
+ };
- expect($action.hasClass('no-permission')).toEqual(true);
- deleteStub.restore();
- });
- it('shows delete hint not when permission to delete', function() {
- var deleteStub = sinon.stub(fileList, 'do_delete');
- var fileData = {
- id: 18,
- type: 'file',
- name: 'testName.txt',
- path: '/somepath/dir',
- mimetype: 'text/plain',
- size: '1234',
- etag: 'a01234c',
- mtime: '123456',
- permissions: OC.PERMISSION_DELETE
- };
- var $tr = fileList.add(fileData);
- FileActions.display($tr.find('td.filename'), true, fileList);
-
- var $action = $tr.find('.action.delete');
-
- expect($action.hasClass('no-permission')).toEqual(false);
- deleteStub.restore();
+ // note: FileActions.display() is called implicitly
+ $tr = fileList.add(fileData);
+ });
+ it('renders inline file actions', function() {
+ // actions defined after call
+ expect($tr.find('.action.action-testinline').length).toEqual(1);
+ expect($tr.find('.action.action-testinline').attr('data-action')).toEqual('Testinline');
+ });
+ it('does not render dropdown actions', function() {
+ expect($tr.find('.action.action-testdropdown').length).toEqual(0);
+ });
+ it('does not render default action', function() {
+ expect($tr.find('.action.action-testdefault').length).toEqual(0);
+ });
+ it('replaces file actions when displayed twice', function() {
+ fileActions.display($tr.find('td.filename'), true, fileList);
+ fileActions.display($tr.find('td.filename'), true, fileList);
+
+ expect($tr.find('.action.action-testinline').length).toEqual(1);
+ });
+ it('renders actions menu trigger', function() {
+ expect($tr.find('.action.action-menu').length).toEqual(1);
+ expect($tr.find('.action.action-menu').attr('data-action')).toEqual('menu');
+ });
+ it('only renders actions relevant to the mime type', function() {
+ fileActions.registerAction({
+ name: 'Match',
+ displayName: 'MatchDisplay',
+ type: OCA.Files.FileActions.TYPE_INLINE,
+ mime: 'text/plain',
+ permissions: OC.PERMISSION_READ
+ });
+ fileActions.registerAction({
+ name: 'Nomatch',
+ displayName: 'NoMatchDisplay',
+ type: OCA.Files.FileActions.TYPE_INLINE,
+ mime: 'application/octet-stream',
+ permissions: OC.PERMISSION_READ
+ });
+
+ fileActions.display($tr.find('td.filename'), true, fileList);
+ expect($tr.find('.action.action-match').length).toEqual(1);
+ expect($tr.find('.action.action-nomatch').length).toEqual(0);
+ });
+ it('only renders actions relevant to the permissions', function() {
+ fileActions.registerAction({
+ name: 'Match',
+ displayName: 'MatchDisplay',
+ type: OCA.Files.FileActions.TYPE_INLINE,
+ mime: 'text/plain',
+ permissions: OC.PERMISSION_UPDATE
+ });
+ fileActions.registerAction({
+ name: 'Nomatch',
+ displayName: 'NoMatchDisplay',
+ type: OCA.Files.FileActions.TYPE_INLINE,
+ mime: 'text/plain',
+ permissions: OC.PERMISSION_DELETE
+ });
+
+ fileActions.display($tr.find('td.filename'), true, fileList);
+ expect($tr.find('.action.action-match').length).toEqual(1);
+ expect($tr.find('.action.action-nomatch').length).toEqual(0);
+ });
});
- 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');
+ describe('action handler', function() {
+ var actionStub, $tr;
+
+ beforeEach(function() {
+ var fileData = {
+ id: 18,
+ type: 'file',
+ name: 'testName.txt',
+ mimetype: 'text/plain',
+ size: '1234',
+ etag: 'a01234c',
+ mtime: '123456'
+ };
+ actionStub = sinon.stub();
+ fileActions.registerAction({
+ name: 'Test',
+ type: OCA.Files.FileActions.TYPE_INLINE,
+ mime: 'all',
+ icon: OC.imagePath('core', 'actions/test'),
+ permissions: OC.PERMISSION_READ,
+ actionHandler: actionStub
+ });
+ $tr = fileList.add(fileData);
+ });
+ it('passes context to action handler', function() {
+ $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');
+ });
+ describe('actions menu', function() {
+ it('shows actions menu inside row when clicking the menu trigger', function() {
+ expect($tr.find('td.filename .fileActionsMenu').length).toEqual(0);
+ $tr.find('.action-menu').click();
+ expect($tr.find('td.filename .fileActionsMenu').length).toEqual(1);
+ });
+ it('shows highlight on current row', function() {
+ $tr.find('.action-menu').click();
+ expect($tr.hasClass('mouseOver')).toEqual(true);
+ });
+ it('cleans up after hiding', function() {
+ var clock = sinon.useFakeTimers();
+ $tr.find('.action-menu').click();
+ expect($tr.find('.fileActionsMenu').length).toEqual(1);
+ OC.hideMenus();
+ // sliding animation
+ clock.tick(500);
+ expect($tr.hasClass('mouseOver')).toEqual(false);
+ expect($tr.find('.fileActionsMenu').length).toEqual(0);
+ });
+ });
});
describe('custom rendering', function() {
var $tr;
@@ -251,10 +230,11 @@ describe('OCA.Files.FileActions tests', function() {
});
it('regular function', function() {
var actionStub = sinon.stub();
- FileActions.registerAction({
+ fileActions.registerAction({
name: 'Test',
displayName: '',
mime: 'all',
+ type: OCA.Files.FileActions.TYPE_INLINE,
permissions: OC.PERMISSION_READ,
render: function(actionSpec, isDefault, context) {
expect(actionSpec.name).toEqual('Test');
@@ -266,13 +246,13 @@ describe('OCA.Files.FileActions tests', function() {
expect(context.fileList).toEqual(fileList);
expect(context.$file[0]).toEqual($tr[0]);
- var $customEl = $('<a href="#"><span>blabli</span><span>blabla</span></a>');
+ var $customEl = $('<a class="action action-test" href="#"><span>blabli</span><span>blabla</span></a>');
$tr.find('td:first').append($customEl);
return $customEl;
},
actionHandler: actionStub
});
- FileActions.display($tr.find('td.filename'), true, fileList);
+ fileActions.display($tr.find('td.filename'), true, fileList);
var $actionEl = $tr.find('td:first .action-test');
expect($actionEl.length).toEqual(1);
@@ -306,20 +286,22 @@ describe('OCA.Files.FileActions tests', function() {
var actions2 = new OCA.Files.FileActions();
var actionStub1 = sinon.stub();
var actionStub2 = sinon.stub();
- actions1.register(
- 'all',
- 'Test',
- OC.PERMISSION_READ,
- OC.imagePath('core', 'actions/test'),
- actionStub1
- );
- actions2.register(
- 'all',
- 'Test2',
- OC.PERMISSION_READ,
- OC.imagePath('core', 'actions/test'),
- actionStub2
- );
+ actions1.registerAction({
+ name: 'Test',
+ type: OCA.Files.FileActions.TYPE_INLINE,
+ mime: 'all',
+ permissions: OC.PERMISSION_READ,
+ icon: OC.imagePath('core', 'actions/test'),
+ actionHandler: actionStub1
+ });
+ actions2.registerAction({
+ name: 'Test2',
+ type: OCA.Files.FileActions.TYPE_INLINE,
+ mime: 'all',
+ permissions: OC.PERMISSION_READ,
+ icon: OC.imagePath('core', 'actions/test'),
+ actionHandler: actionStub2
+ });
actions2.merge(actions1);
actions2.display($tr.find('td.filename'), true, fileList);
@@ -342,20 +324,22 @@ describe('OCA.Files.FileActions tests', function() {
var actions2 = new OCA.Files.FileActions();
var actionStub1 = sinon.stub();
var actionStub2 = sinon.stub();
- actions1.register(
- 'all',
- 'Test',
- OC.PERMISSION_READ,
- OC.imagePath('core', 'actions/test'),
- actionStub1
- );
- actions2.register(
- 'all',
- 'Test', // override
- OC.PERMISSION_READ,
- OC.imagePath('core', 'actions/test'),
- actionStub2
- );
+ actions1.registerAction({
+ name: 'Test',
+ type: OCA.Files.FileActions.TYPE_INLINE,
+ mime: 'all',
+ permissions: OC.PERMISSION_READ,
+ icon: OC.imagePath('core', 'actions/test'),
+ actionHandler: actionStub1
+ });
+ actions2.registerAction({
+ name: 'Test', // override
+ mime: 'all',
+ type: OCA.Files.FileActions.TYPE_INLINE,
+ permissions: OC.PERMISSION_READ,
+ icon: OC.imagePath('core', 'actions/test'),
+ actionHandler: actionStub2
+ });
actions1.merge(actions2);
actions1.display($tr.find('td.filename'), true, fileList);
@@ -371,24 +355,26 @@ describe('OCA.Files.FileActions tests', function() {
var actions2 = new OCA.Files.FileActions();
var actionStub1 = sinon.stub();
var actionStub2 = sinon.stub();
- actions1.register(
- 'all',
- 'Test',
- OC.PERMISSION_READ,
- OC.imagePath('core', 'actions/test'),
- actionStub1
- );
+ actions1.registerAction({
+ mime: 'all',
+ name: 'Test',
+ type: OCA.Files.FileActions.TYPE_INLINE,
+ permissions: OC.PERMISSION_READ,
+ icon: OC.imagePath('core', 'actions/test'),
+ actionHandler: actionStub1
+ });
actions1.merge(actions2);
// late override
- actions1.register(
- 'all',
- 'Test', // override
- OC.PERMISSION_READ,
- OC.imagePath('core', 'actions/test'),
- actionStub2
- );
+ actions1.registerAction({
+ mime: 'all',
+ name: 'Test', // override
+ type: OCA.Files.FileActions.TYPE_INLINE,
+ permissions: OC.PERMISSION_READ,
+ icon: OC.imagePath('core', 'actions/test'),
+ actionHandler: actionStub2
+ });
actions1.display($tr.find('td.filename'), true, fileList);
@@ -403,25 +389,27 @@ describe('OCA.Files.FileActions tests', function() {
var actions2 = new OCA.Files.FileActions();
var actionStub1 = sinon.stub();
var actionStub2 = sinon.stub();
- actions1.register(
- 'all',
- 'Test',
- OC.PERMISSION_READ,
- OC.imagePath('core', 'actions/test'),
- actionStub1
- );
+ actions1.registerAction({
+ mime: 'all',
+ name: 'Test',
+ type: OCA.Files.FileActions.TYPE_INLINE,
+ permissions: OC.PERMISSION_READ,
+ icon: OC.imagePath('core', 'actions/test'),
+ actionHandler: actionStub1
+ });
// copy the Test action to actions2
actions2.merge(actions1);
// late override
- actions2.register(
- 'all',
- 'Test', // override
- OC.PERMISSION_READ,
- OC.imagePath('core', 'actions/test'),
- actionStub2
- );
+ actions2.registerAction({
+ mime: 'all',
+ name: 'Test', // override
+ type: OCA.Files.FileActions.TYPE_INLINE,
+ permissions: OC.PERMISSION_READ,
+ icon: OC.imagePath('core', 'actions/test'),
+ actionHandler: actionStub2
+ });
// check if original actions still call the correct handler
actions1.display($tr.find('td.filename'), true, fileList);
@@ -444,42 +432,45 @@ describe('OCA.Files.FileActions tests', function() {
it('notifies update event handlers once after multiple changes', function() {
var actionStub = sinon.stub();
var handler = sinon.stub();
- FileActions.on('registerAction', handler);
- FileActions.register(
- 'all',
- 'Test',
- OC.PERMISSION_READ,
- OC.imagePath('core', 'actions/test'),
- actionStub
- );
- FileActions.register(
- 'all',
- 'Test2',
- OC.PERMISSION_READ,
- OC.imagePath('core', 'actions/test'),
- actionStub
- );
+ fileActions.on('registerAction', handler);
+ fileActions.registerAction({
+ mime: 'all',
+ name: 'Test',
+ type: OCA.Files.FileActions.TYPE_INLINE,
+ permissions: OC.PERMISSION_READ,
+ icon: OC.imagePath('core', 'actions/test'),
+ actionHandler: actionStub
+ });
+ fileActions.registerAction({
+ mime: 'all',
+ name: 'Test2',
+ permissions: OC.PERMISSION_READ,
+ icon: OC.imagePath('core', 'actions/test'),
+ actionHandler: actionStub
+ });
expect(handler.calledTwice).toEqual(true);
});
it('does not notifies update event handlers after unregistering', function() {
var actionStub = sinon.stub();
var handler = sinon.stub();
- FileActions.on('registerAction', handler);
- FileActions.off('registerAction', handler);
- FileActions.register(
- 'all',
- 'Test',
- OC.PERMISSION_READ,
- OC.imagePath('core', 'actions/test'),
- actionStub
- );
- FileActions.register(
- 'all',
- 'Test2',
- OC.PERMISSION_READ,
- OC.imagePath('core', 'actions/test'),
- actionStub
- );
+ fileActions.on('registerAction', handler);
+ fileActions.off('registerAction', handler);
+ fileActions.registerAction({
+ mime: 'all',
+ name: 'Test',
+ type: OCA.Files.FileActions.TYPE_INLINE,
+ permissions: OC.PERMISSION_READ,
+ icon: OC.imagePath('core', 'actions/test'),
+ actionHandler: actionStub
+ });
+ fileActions.registerAction({
+ mime: 'all',
+ name: 'Test2',
+ type: OCA.Files.FileActions.TYPE_INLINE,
+ permissions: OC.PERMISSION_READ,
+ icon: OC.imagePath('core', 'actions/test'),
+ actionHandler: actionStub
+ });
expect(handler.notCalled).toEqual(true);
});
});
diff --git a/apps/files/tests/js/fileactionsmenuSpec.js b/apps/files/tests/js/fileactionsmenuSpec.js
new file mode 100644
index 00000000000..0cfd12a2d04
--- /dev/null
+++ b/apps/files/tests/js/fileactionsmenuSpec.js
@@ -0,0 +1,273 @@
+/**
+* ownCloud
+*
+* @author Vincent Petry
+* @copyright 2015 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.FileActionsMenu tests', function() {
+ var fileList, fileActions, menu, actionStub, $tr;
+
+ beforeEach(function() {
+ // init horrible parameters
+ 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
+ actionStub = sinon.stub();
+ fileActions = new OCA.Files.FileActions();
+ fileList = new OCA.Files.FileList($body, {
+ fileActions: fileActions
+ });
+
+ fileActions.registerAction({
+ name: 'Testdropdown',
+ displayName: 'Testdropdowndisplay',
+ mime: 'all',
+ permissions: OC.PERMISSION_READ,
+ icon: function () {
+ return OC.imagePath('core', 'actions/download');
+ },
+ actionHandler: actionStub
+ });
+
+ fileActions.registerAction({
+ name: 'Testdropdownnoicon',
+ displayName: 'Testdropdowndisplaynoicon',
+ mime: 'all',
+ permissions: OC.PERMISSION_READ,
+ actionHandler: actionStub
+ });
+
+ fileActions.registerAction({
+ name: 'Testinline',
+ displayName: 'Testinlinedisplay',
+ type: OCA.Files.FileActions.TYPE_INLINE,
+ mime: 'all',
+ permissions: OC.PERMISSION_READ
+ });
+
+ fileActions.registerAction({
+ name: 'Testdefault',
+ displayName: 'Testdefaultdisplay',
+ mime: 'all',
+ permissions: OC.PERMISSION_READ
+ });
+ fileActions.setDefault('all', 'Testdefault');
+
+ var fileData = {
+ id: 18,
+ type: 'file',
+ name: 'testName.txt',
+ mimetype: 'text/plain',
+ size: '1234',
+ etag: 'a01234c',
+ mtime: '123456'
+ };
+ $tr = fileList.add(fileData);
+
+ var menuContext = {
+ $file: $tr,
+ fileList: fileList,
+ fileActions: fileActions,
+ dir: fileList.getCurrentDirectory()
+ };
+ menu = new OCA.Files.FileActionsMenu();
+ menu.show(menuContext);
+ });
+ afterEach(function() {
+ fileActions = null;
+ fileList.destroy();
+ fileList = undefined;
+ menu.remove();
+ $('#dir, #permissions, #filestable').remove();
+ });
+
+ describe('rendering', function() {
+ it('renders dropdown actions in menu', function() {
+ var $action = menu.$el.find('a[data-action=Testdropdown]');
+ expect($action.length).toEqual(1);
+ expect($action.find('img').attr('src'))
+ .toEqual(OC.imagePath('core', 'actions/download'));
+ expect($action.find('.no-icon').length).toEqual(0);
+
+ $action = menu.$el.find('a[data-action=Testdropdownnoicon]');
+ expect($action.length).toEqual(1);
+ expect($action.find('img').length).toEqual(0);
+ expect($action.find('.no-icon').length).toEqual(1);
+ });
+ it('does not render default actions', function() {
+ expect(menu.$el.find('a[data-action=Testdefault]').length).toEqual(0);
+ });
+ it('does not render inline actions', function() {
+ expect(menu.$el.find('a[data-action=Testinline]').length).toEqual(0);
+ });
+ it('only renders actions relevant to the mime type', function() {
+ fileActions.registerAction({
+ name: 'Match',
+ displayName: 'MatchDisplay',
+ mime: 'text/plain',
+ permissions: OC.PERMISSION_READ
+ });
+ fileActions.registerAction({
+ name: 'Nomatch',
+ displayName: 'NoMatchDisplay',
+ mime: 'application/octet-stream',
+ permissions: OC.PERMISSION_READ
+ });
+
+ menu.render();
+ expect(menu.$el.find('a[data-action=Match]').length).toEqual(1);
+ expect(menu.$el.find('a[data-action=NoMatch]').length).toEqual(0);
+ });
+ it('only renders actions relevant to the permissions', function() {
+ fileActions.registerAction({
+ name: 'Match',
+ displayName: 'MatchDisplay',
+ mime: 'text/plain',
+ permissions: OC.PERMISSION_UPDATE
+ });
+ fileActions.registerAction({
+ name: 'Nomatch',
+ displayName: 'NoMatchDisplay',
+ mime: 'text/plain',
+ permissions: OC.PERMISSION_DELETE
+ });
+
+ menu.render();
+ expect(menu.$el.find('a[data-action=Match]').length).toEqual(1);
+ expect(menu.$el.find('a[data-action=NoMatch]').length).toEqual(0);
+ });
+ });
+
+ describe('action handler', function() {
+ it('calls action handler when clicking menu item', function() {
+ var $action = menu.$el.find('a[data-action=Testdropdown]');
+ $action.click();
+
+ expect(actionStub.calledOnce).toEqual(true);
+ expect(actionStub.getCall(0).args[0]).toEqual('testName.txt');
+ expect(actionStub.getCall(0).args[1].$file[0]).toEqual($tr[0]);
+ expect(actionStub.getCall(0).args[1].fileList).toEqual(fileList);
+ expect(actionStub.getCall(0).args[1].fileActions).toEqual(fileActions);
+ expect(actionStub.getCall(0).args[1].dir).toEqual('/subdir');
+ });
+ });
+ describe('default actions from registerDefaultActions', function() {
+ beforeEach(function() {
+ fileActions.clear();
+ fileActions.registerDefaultActions();
+ });
+ it('redirects to download URL when clicking download', function() {
+ var redirectStub = sinon.stub(OC, 'redirect');
+ var fileData = {
+ id: 18,
+ type: 'file',
+ name: 'testName.txt',
+ mimetype: 'text/plain',
+ size: '1234',
+ etag: 'a01234c',
+ mtime: '123456'
+ };
+ var $tr = fileList.add(fileData);
+ fileActions.display($tr.find('td.filename'), true, fileList);
+
+ var menuContext = {
+ $file: $tr,
+ fileList: fileList,
+ fileActions: fileActions,
+ dir: fileList.getCurrentDirectory()
+ };
+ menu = new OCA.Files.FileActionsMenu();
+ menu.show(menuContext);
+
+ menu.$el.find('.action-download').click();
+
+ expect(redirectStub.calledOnce).toEqual(true);
+ expect(redirectStub.getCall(0).args[0]).toContain(
+ 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);
+
+ var menuContext = {
+ $file: $tr,
+ fileList: fileList,
+ fileActions: fileActions,
+ dir: '/anotherpath/there'
+ };
+ menu = new OCA.Files.FileActionsMenu();
+ menu.show(menuContext);
+
+ menu.$el.find('.action-download').click();
+
+ expect(redirectStub.calledOnce).toEqual(true);
+ expect(redirectStub.getCall(0).args[0]).toContain(
+ OC.webroot + '/index.php/apps/files/ajax/download.php' +
+ '?dir=%2Fanotherpath%2Fthere&files=testName.txt'
+ );
+ redirectStub.restore();
+ });
+ it('deletes file when clicking delete', function() {
+ var deleteStub = sinon.stub(fileList, 'do_delete');
+ var fileData = {
+ id: 18,
+ type: 'file',
+ name: 'testName.txt',
+ path: '/somepath/dir',
+ mimetype: 'text/plain',
+ size: '1234',
+ etag: 'a01234c',
+ mtime: '123456'
+ };
+ var $tr = fileList.add(fileData);
+ fileActions.display($tr.find('td.filename'), true, fileList);
+
+ var menuContext = {
+ $file: $tr,
+ fileList: fileList,
+ fileActions: fileActions,
+ dir: '/somepath/dir'
+ };
+ menu = new OCA.Files.FileActionsMenu();
+ menu.show(menuContext);
+
+ menu.$el.find('.action-delete').click();
+
+ expect(deleteStub.calledOnce).toEqual(true);
+ expect(deleteStub.getCall(0).args[0]).toEqual('testName.txt');
+ expect(deleteStub.getCall(0).args[1]).toEqual('/somepath/dir');
+ deleteStub.restore();
+ });
+ });
+});
+
diff --git a/apps/files/tests/js/filelistSpec.js b/apps/files/tests/js/filelistSpec.js
index 5c0c8c96bc5..57e16626403 100644
--- a/apps/files/tests/js/filelistSpec.js
+++ b/apps/files/tests/js/filelistSpec.js
@@ -456,19 +456,19 @@ describe('OCA.Files.FileList tests', function() {
expect(notificationStub.notCalled).toEqual(true);
});
- it('shows spinner on files to be deleted', function() {
+ it('shows busy state on files to be deleted', function() {
fileList.setFiles(testFiles);
doDelete();
- expect(fileList.findFileEl('One.txt').find('.icon-loading-small:not(.icon-delete)').length).toEqual(1);
- expect(fileList.findFileEl('Three.pdf').find('.icon-delete:not(.icon-loading-small)').length).toEqual(1);
+ expect(fileList.findFileEl('One.txt').hasClass('busy')).toEqual(true);
+ expect(fileList.findFileEl('Three.pdf').hasClass('busy')).toEqual(false);
});
- it('shows spinner on all files when deleting all', function() {
+ it('shows busy state on all files when deleting all', function() {
fileList.setFiles(testFiles);
fileList.do_delete();
- expect(fileList.$fileList.find('tr .icon-loading-small:not(.icon-delete)').length).toEqual(4);
+ expect(fileList.$fileList.find('tr.busy').length).toEqual(4);
});
it('updates summary when deleting last file', function() {
var $summary;
@@ -625,7 +625,7 @@ describe('OCA.Files.FileList tests', function() {
doCancelRename();
expect($summary.find('.info').text()).toEqual('1 folder and 3 files');
});
- it('Hides actions while rename in progress', function() {
+ it('Shows busy state while rename in progress', function() {
var $tr;
doRename();
@@ -634,8 +634,7 @@ describe('OCA.Files.FileList tests', function() {
expect($tr.length).toEqual(1);
expect(fileList.findFileEl('One.txt').length).toEqual(0);
// file actions are hidden
- expect($tr.find('.action').hasClass('hidden')).toEqual(true);
- expect($tr.find('.fileactions').hasClass('hidden')).toEqual(true);
+ expect($tr.hasClass('busy')).toEqual(true);
// input and form are gone
expect(fileList.$fileList.find('input.filename').length).toEqual(0);
@@ -1918,16 +1917,17 @@ describe('OCA.Files.FileList tests', 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() {
+ fileList.fileActions.registerAction({
+ mime: 'text/plain',
+ name: 'Test',
+ type: OCA.Files.FileActions.TYPE_INLINE,
+ permissions: OC.PERMISSION_ALL,
+ icon: function() {
// Specify icon for hitory button
return OC.imagePath('core','actions/history');
},
- actionStub
- );
+ actionHandler: actionStub
+ });
fileList.fileActions.setDefault('text/plain', 'Test');
var $tr = fileList.findFileEl('One.txt');
$tr.find('td.filename .nametext').click();
@@ -1958,16 +1958,17 @@ describe('OCA.Files.FileList tests', function() {
fileList.$fileList.on('fileActionsReady', readyHandler);
- fileList.fileActions.register(
- 'text/plain',
- 'Test',
- OC.PERMISSION_ALL,
- function() {
+ fileList.fileActions.registerAction({
+ mime: 'text/plain',
+ name: 'Test',
+ type: OCA.Files.FileActions.TYPE_INLINE,
+ permissions: OC.PERMISSION_ALL,
+ icon: function() {
// Specify icon for hitory button
return OC.imagePath('core','actions/history');
},
- actionStub
- );
+ actionHandler: actionStub
+ });
var $tr = fileList.findFileEl('One.txt');
expect($tr.find('.action-test').length).toEqual(0);
expect(readyHandler.notCalled).toEqual(true);
@@ -2256,6 +2257,8 @@ describe('OCA.Files.FileList tests', function() {
});
});
describe('Handeling errors', function () {
+ var redirectStub;
+
beforeEach(function () {
redirectStub = sinon.stub(OC, 'redirect');
@@ -2281,4 +2284,36 @@ describe('OCA.Files.FileList tests', function() {
expect(redirectStub.calledWith(OC.generateUrl('apps/files'))).toEqual(true);
});
});
+ describe('showFileBusyState', function() {
+ var $tr;
+
+ beforeEach(function() {
+ fileList.setFiles(testFiles);
+ $tr = fileList.findFileEl('Two.jpg');
+ });
+ it('shows spinner on busy rows', function() {
+ fileList.showFileBusyState('Two.jpg', true);
+ expect($tr.hasClass('busy')).toEqual(true);
+ expect(OC.TestUtil.getImageUrl($tr.find('.thumbnail')))
+ .toEqual(OC.imagePath('core', 'loading.gif'));
+
+ fileList.showFileBusyState('Two.jpg', false);
+ expect($tr.hasClass('busy')).toEqual(false);
+ expect(OC.TestUtil.getImageUrl($tr.find('.thumbnail')))
+ .toEqual(OC.imagePath('core', 'filetypes/image.svg'));
+ });
+ it('accepts multiple input formats', function() {
+ _.each([
+ 'Two.jpg',
+ ['Two.jpg'],
+ $tr,
+ [$tr]
+ ], function(testCase) {
+ fileList.showFileBusyState(testCase, true);
+ expect($tr.hasClass('busy')).toEqual(true);
+ fileList.showFileBusyState(testCase, false);
+ expect($tr.hasClass('busy')).toEqual(false);
+ });
+ });
+ });
});
diff --git a/apps/files_sharing/js/share.js b/apps/files_sharing/js/share.js
index 12bec0e8c9a..04700b84011 100644
--- a/apps/files_sharing/js/share.js
+++ b/apps/files_sharing/js/share.js
@@ -89,57 +89,59 @@
}
});
- fileActions.register(
- 'all',
- 'Share',
- OC.PERMISSION_SHARE,
- OC.imagePath('core', 'actions/share'),
- function(filename, context) {
-
- var $tr = context.$file;
- var itemType = 'file';
- if ($tr.data('type') === 'dir') {
- itemType = 'folder';
- }
- var possiblePermissions = $tr.data('share-permissions');
- if (_.isUndefined(possiblePermissions)) {
- possiblePermissions = $tr.data('permissions');
- }
+ fileActions.registerAction({
+ name: 'Share',
+ displayName: '',
+ mime: 'all',
+ permissions: OC.PERMISSION_SHARE,
+ icon: OC.imagePath('core', 'actions/share'),
+ type: OCA.Files.FileActions.TYPE_INLINE,
+ actionHandler: function(filename, context) {
+ var $tr = context.$file;
+ var itemType = 'file';
+ if ($tr.data('type') === 'dir') {
+ itemType = 'folder';
+ }
+ var possiblePermissions = $tr.data('share-permissions');
+ if (_.isUndefined(possiblePermissions)) {
+ possiblePermissions = $tr.data('permissions');
+ }
- var appendTo = $tr.find('td.filename');
- // Check if drop down is already visible for a different file
- if (OC.Share.droppedDown) {
- if ($tr.attr('data-id') !== $('#dropdown').attr('data-item-source')) {
- OC.Share.hideDropDown(function () {
- $tr.addClass('mouseOver');
- OC.Share.showDropDown(itemType, $tr.data('id'), appendTo, true, possiblePermissions, filename);
- });
+ var appendTo = $tr.find('td.filename');
+ // Check if drop down is already visible for a different file
+ if (OC.Share.droppedDown) {
+ if ($tr.attr('data-id') !== $('#dropdown').attr('data-item-source')) {
+ OC.Share.hideDropDown(function () {
+ $tr.addClass('mouseOver');
+ OC.Share.showDropDown(itemType, $tr.data('id'), appendTo, true, possiblePermissions, filename);
+ });
+ } else {
+ OC.Share.hideDropDown();
+ }
} else {
- OC.Share.hideDropDown();
+ $tr.addClass('mouseOver');
+ OC.Share.showDropDown(itemType, $tr.data('id'), appendTo, true, possiblePermissions, filename);
}
- } else {
- $tr.addClass('mouseOver');
- OC.Share.showDropDown(itemType, $tr.data('id'), appendTo, true, possiblePermissions, filename);
+ $('#dropdown').on('sharesChanged', function(ev) {
+ // files app current cannot show recipients on load, so we don't update the
+ // icon when changed for consistency
+ if (context.fileList.$el.closest('#app-content-files').length) {
+ return;
+ }
+ var recipients = _.pluck(ev.shares[OC.Share.SHARE_TYPE_USER], 'share_with_displayname');
+ var groupRecipients = _.pluck(ev.shares[OC.Share.SHARE_TYPE_GROUP], 'share_with_displayname');
+ recipients = recipients.concat(groupRecipients);
+ // note: we only update the data attribute because updateIcon()
+ // is called automatically after this event
+ if (recipients.length) {
+ $tr.attr('data-share-recipients', OCA.Sharing.Util.formatRecipients(recipients));
+ }
+ else {
+ $tr.removeAttr('data-share-recipients');
+ }
+ });
}
- $('#dropdown').on('sharesChanged', function(ev) {
- // files app current cannot show recipients on load, so we don't update the
- // icon when changed for consistency
- if (context.fileList.$el.closest('#app-content-files').length) {
- return;
- }
- var recipients = _.pluck(ev.shares[OC.Share.SHARE_TYPE_USER], 'share_with_displayname');
- var groupRecipients = _.pluck(ev.shares[OC.Share.SHARE_TYPE_GROUP], 'share_with_displayname');
- recipients = recipients.concat(groupRecipients);
- // note: we only update the data attribute because updateIcon()
- // is called automatically after this event
- if (recipients.length) {
- $tr.attr('data-share-recipients', OCA.Sharing.Util.formatRecipients(recipients));
- }
- else {
- $tr.removeAttr('data-share-recipients');
- }
- });
- }, t('files_sharing', 'Share'));
+ });
OC.addScript('files_sharing', 'sharetabview').done(function() {
fileList.registerTabView(new OCA.Sharing.ShareTabView('shareTabView'));
diff --git a/apps/files_sharing/templates/public.php b/apps/files_sharing/templates/public.php
index ffe0472b2b1..2962f62520d 100644
--- a/apps/files_sharing/templates/public.php
+++ b/apps/files_sharing/templates/public.php
@@ -7,6 +7,7 @@ OCP\Util::addStyle('files_sharing', 'public');
OCP\Util::addStyle('files_sharing', 'mobile');
OCP\Util::addScript('files_sharing', 'public');
OCP\Util::addScript('files', 'fileactions');
+OCP\Util::addScript('files', 'fileactionsmenu');
OCP\Util::addScript('files', 'jquery.iframe-transport');
OCP\Util::addScript('files', 'jquery.fileupload');
diff --git a/apps/files_sharing/tests/js/shareSpec.js b/apps/files_sharing/tests/js/shareSpec.js
index aa409285ca4..581e15caf93 100644
--- a/apps/files_sharing/tests/js/shareSpec.js
+++ b/apps/files_sharing/tests/js/shareSpec.js
@@ -97,7 +97,7 @@ describe('OCA.Sharing.Util tests', function() {
}]);
$tr = fileList.$el.find('tbody tr:first');
$action = $tr.find('.action-share');
- expect($action.hasClass('permanent')).toEqual(false);
+ expect($action.hasClass('permanent')).toEqual(true);
expect(OC.basename($action.find('img').attr('src'))).toEqual('share.svg');
expect(OC.basename(getImageUrl($tr.find('.filename .thumbnail')))).toEqual('folder.svg');
expect($action.find('img').length).toEqual(1);
@@ -257,7 +257,7 @@ describe('OCA.Sharing.Util tests', function() {
$action = fileList.$el.find('tbody tr:first .action-share');
$tr = fileList.$el.find('tr:first');
- expect($action.hasClass('permanent')).toEqual(false);
+ expect($action.hasClass('permanent')).toEqual(true);
$tr.find('.action-share').click();
@@ -344,7 +344,7 @@ describe('OCA.Sharing.Util tests', function() {
expect($tr.attr('data-share-recipients')).not.toBeDefined();
OC.Share.updateIcon('file', 1);
- expect($action.hasClass('permanent')).toEqual(false);
+ expect($action.hasClass('permanent')).toEqual(true);
});
it('keep share text after updating reshare', function() {
var $action, $tr;
diff --git a/apps/files_trashbin/js/app.js b/apps/files_trashbin/js/app.js
index 315349d293c..473cce88a71 100644
--- a/apps/files_trashbin/js/app.js
+++ b/apps/files_trashbin/js/app.js
@@ -59,7 +59,6 @@ OCA.Trashbin.App = {
fileActions.registerAction({
name: 'Delete',
- displayName: '',
mime: 'all',
permissions: OC.PERMISSION_READ,
icon: function() {
diff --git a/core/css/apps.css b/core/css/apps.css
index 5769120c5ed..300b186bba2 100644
--- a/core/css/apps.css
+++ b/core/css/apps.css
@@ -292,13 +292,13 @@
list-style-type: none;
}
+.bubble,
#app-navigation .app-navigation-entry-menu {
- display: none;
position: absolute;
background-color: #eee;
color: #333;
border-radius: 3px;
- border-top-right-radius: 0px;
+ border-top-right-radius: 0;
z-index: 110;
margin: -5px 14px 5px 10px;
right: 0;
@@ -310,11 +310,17 @@
filter: drop-shadow(0 0 5px rgba(150, 150, 150, 0.75));
}
+#app-navigation .app-navigation-entry-menu {
+ display: none;
+}
+
#app-navigation .app-navigation-entry-menu.open {
display: block;
}
/* miraculous border arrow stuff */
+.bubble:after,
+.bubble:before,
#app-navigation .app-navigation-entry-menu:after,
#app-navigation .app-navigation-entry-menu:before {
bottom: 100%;
@@ -327,12 +333,15 @@
pointer-events: none;
}
+.bubble:after,
#app-navigation .app-navigation-entry-menu:after {
border-color: rgba(238, 238, 238, 0);
border-bottom-color: #eee;
border-width: 10px;
margin-left: -10px;
}
+
+.bubble:before,
#app-navigation .app-navigation-entry-menu:before {
border-color: rgba(187, 187, 187, 0);
border-bottom-color: #bbb;
diff --git a/core/js/core.json b/core/js/core.json
index 1053debaa99..a67491c4a35 100644
--- a/core/js/core.json
+++ b/core/js/core.json
@@ -7,7 +7,8 @@
"moment/min/moment-with-locales.js",
"handlebars/handlebars.js",
"blueimp-md5/js/md5.js",
- "bootstrap/js/tooltip.js"
+ "bootstrap/js/tooltip.js",
+ "backbone/backbone.js"
],
"libraries": [
"jquery-showpassword.js",
@@ -19,6 +20,7 @@
"jquery.ocdialog.js",
"oc-dialogs.js",
"js.js",
+ "oc-backbone.js",
"l10n.js",
"apps.js",
"share.js",
diff --git a/core/js/js.js b/core/js/js.js
index 72d4edd28dd..89bb9a71430 100644
--- a/core/js/js.js
+++ b/core/js/js.js
@@ -571,21 +571,20 @@ var OC={
* @todo Write documentation
*/
registerMenu: function($toggle, $menuEl) {
+ var self = this;
$menuEl.addClass('menu');
$toggle.on('click.menu', function(event) {
// prevent the link event (append anchor to URL)
event.preventDefault();
if ($menuEl.is(OC._currentMenu)) {
- $menuEl.slideUp(OC.menuSpeed);
- OC._currentMenu = null;
- OC._currentMenuToggle = null;
+ self.hideMenus();
return;
}
// another menu was open?
else if (OC._currentMenu) {
// close it
- OC._currentMenu.hide();
+ self.hideMenus();
}
$menuEl.slideToggle(OC.menuSpeed);
OC._currentMenu = $menuEl;
@@ -599,15 +598,56 @@ var OC={
unregisterMenu: function($toggle, $menuEl) {
// close menu if opened
if ($menuEl.is(OC._currentMenu)) {
- $menuEl.slideUp(OC.menuSpeed);
- OC._currentMenu = null;
- OC._currentMenuToggle = null;
+ this.hideMenus();
}
$toggle.off('click.menu').removeClass('menutoggle');
$menuEl.removeClass('menu');
},
/**
+ * Hides any open menus
+ *
+ * @param {Function} complete callback when the hiding animation is done
+ */
+ hideMenus: function(complete) {
+ if (OC._currentMenu) {
+ var lastMenu = OC._currentMenu;
+ OC._currentMenu.trigger(new $.Event('beforeHide'));
+ OC._currentMenu.slideUp(OC.menuSpeed, function() {
+ lastMenu.trigger(new $.Event('afterHide'));
+ if (complete) {
+ complete.apply(this, arguments);
+ }
+ });
+ }
+ OC._currentMenu = null;
+ OC._currentMenuToggle = null;
+ },
+
+ /**
+ * Shows a given element as menu
+ *
+ * @param {Object} [$toggle=null] menu toggle
+ * @param {Object} $menuEl menu element
+ * @param {Function} complete callback when the showing animation is done
+ */
+ showMenu: function($toggle, $menuEl, complete) {
+ if ($menuEl.is(OC._currentMenu)) {
+ return;
+ }
+ this.hideMenus();
+ OC._currentMenu = $menuEl;
+ OC._currentMenuToggle = $toggle;
+ $menuEl.trigger(new $.Event('beforeShow'));
+ $menuEl.show();
+ $menuEl.trigger(new $.Event('afterShow'));
+ // no animation
+ if (_.isFunction()) {
+ complete();
+ }
+ },
+
+ /**
* Wrapper for matchMedia
*
* This is makes it possible for unit tests to
@@ -1256,11 +1296,8 @@ function initCore() {
// don't close when clicking on the menu directly or a menu toggle
return false;
}
- if (OC._currentMenu) {
- OC._currentMenu.slideUp(OC.menuSpeed);
- }
- OC._currentMenu = null;
- OC._currentMenuToggle = null;
+
+ OC.hideMenus();
});
diff --git a/core/js/share.js b/core/js/share.js
index 99fd08c6411..57dd0dd6553 100644
--- a/core/js/share.js
+++ b/core/js/share.js
@@ -266,7 +266,6 @@ OC.Share={
if (hasShares || owner) {
recipients = $tr.attr('data-share-recipients');
- action.addClass('permanent');
message = t('core', 'Shared');
// even if reshared, only show "Shared by"
if (owner) {
@@ -281,8 +280,7 @@ OC.Share={
}
}
else {
- action.removeClass('permanent');
- action.html(' <span>'+ escapeHTML(t('core', 'Share'))+'</span>').prepend(img);
+ action.html('<span></span>').prepend(img);
}
if (hasLink) {
image = OC.imagePath('core', 'actions/public');