Merge pull request #17709 from owncloud/fileactions-dropdown
Move file actions to dropdown
This commit is contained in:
commit
d04a6bce6f
18 changed files with 1176 additions and 581 deletions
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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'));
|
||||
_makeActionLink: function(params) {
|
||||
if (!this._fileActionTriggerTemplate) {
|
||||
this._fileActionTriggerTemplate = Handlebars.compile(TEMPLATE_FILE_ACTION_TRIGGER);
|
||||
}
|
||||
var html = '<a href="#">';
|
||||
if (img) {
|
||||
html += '<img class="svg" alt="" src="' + img + '" />';
|
||||
}
|
||||
if (actionSpec.displayName) {
|
||||
html += '<span> ' + actionSpec.displayName + '</span>';
|
||||
}
|
||||
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.
|
||||
* Renders the menu trigger on the given file list 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
|
||||
* @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,26 +370,33 @@
|
|||
* @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) {
|
||||
var $file = $(event.target).closest('tr');
|
||||
var currentFile = $file.find('td.filename');
|
||||
var fileName = $file.attr('data-file');
|
||||
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');
|
||||
|
||||
context.fileActions.currentFile = currentFile;
|
||||
// also set on global object for legacy apps
|
||||
window.FileActions.currentFile = currentFile;
|
||||
|
@ -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()
|
||||
);
|
||||
|
||||
$.each(actions, function (name, actionSpec) {
|
||||
if (name !== 'Share') {
|
||||
self._renderAction(
|
||||
actionSpec,
|
||||
actionSpec.action === defaultAction, {
|
||||
$file: $tr,
|
||||
fileActions: this,
|
||||
fileList : fileList
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
// added here to make sure it's always the last action
|
||||
var shareActionSpec = actions.Share;
|
||||
if (shareActionSpec){
|
||||
this._renderAction(
|
||||
shareActionSpec,
|
||||
shareActionSpec.action === defaultAction, {
|
||||
var context = {
|
||||
$file: $tr,
|
||||
fileActions: this,
|
||||
fileList: fileList
|
||||
}
|
||||
};
|
||||
|
||||
$.each(actions, function (name, actionSpec) {
|
||||
if (actionSpec.type === FileActions.TYPE_INLINE) {
|
||||
self._renderInlineAction(
|
||||
actionSpec,
|
||||
defaultAction && actionSpec.name === defaultAction.name,
|
||||
context
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
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')) {
|
||||
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;
|
||||
}
|
||||
|
||||
if (url) {
|
||||
var disableLoadingState = function(){
|
||||
OCA.Files.FileActions.updateFileActionSpinner(downloadFileaction, false);
|
||||
};
|
||||
|
||||
OCA.Files.FileActions.updateFileActionSpinner(downloadFileaction, true);
|
||||
OCA.Files.Files.handleDownload(url, disableLoadingState);
|
||||
context.fileList.do_delete(fileName, context.dir);
|
||||
$('.tipsy').remove();
|
||||
}
|
||||
}, t('files', 'Download'));
|
||||
});
|
||||
|
||||
this.setDefault('dir', 'Open');
|
||||
}
|
||||
};
|
||||
|
||||
|
|
132
apps/files/js/fileactionsmenu.js
Normal file
132
apps/files/js/fileactionsmenu.js
Normal file
|
@ -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;
|
||||
|
||||
})();
|
||||
|
|
@ -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');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -20,8 +20,7 @@
|
|||
*/
|
||||
|
||||
describe('OCA.Files.FileActions tests', function() {
|
||||
var $filesTable, fileList;
|
||||
var FileActions;
|
||||
var fileList, fileActions;
|
||||
|
||||
beforeEach(function() {
|
||||
// init horrible parameters
|
||||
|
@ -29,25 +28,53 @@ 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() {
|
||||
describe('displaying actions', function() {
|
||||
var $tr;
|
||||
|
||||
beforeEach(function() {
|
||||
var fileData = {
|
||||
id: 18,
|
||||
type: 'file',
|
||||
|
@ -55,170 +82,100 @@ describe('OCA.Files.FileActions tests', function() {
|
|||
mimetype: 'text/plain',
|
||||
size: '1234',
|
||||
etag: 'a01234c',
|
||||
mtime: '123456'
|
||||
mtime: '123456',
|
||||
permissions: OC.PERMISSION_READ | OC.PERMISSION_UPDATE
|
||||
};
|
||||
|
||||
// note: FileActions.display() is called implicitly
|
||||
var $tr = fileList.add(fileData);
|
||||
|
||||
$tr = fileList.add(fileData);
|
||||
});
|
||||
it('renders inline file actions', function() {
|
||||
// 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);
|
||||
expect($tr.find('.action.action-testinline').length).toEqual(1);
|
||||
expect($tr.find('.action.action-testinline').attr('data-action')).toEqual('Testinline');
|
||||
});
|
||||
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('does not render dropdown actions', function() {
|
||||
expect($tr.find('.action.action-testdropdown').length).toEqual(0);
|
||||
});
|
||||
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('does not render default action', function() {
|
||||
expect($tr.find('.action.action-testdefault').length).toEqual(0);
|
||||
});
|
||||
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);
|
||||
it('replaces file actions when displayed twice', function() {
|
||||
fileActions.display($tr.find('td.filename'), true, fileList);
|
||||
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();
|
||||
expect($tr.find('.action.action-testinline').length).toEqual(1);
|
||||
});
|
||||
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('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('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',
|
||||
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
|
||||
};
|
||||
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(true);
|
||||
deleteStub.restore();
|
||||
});
|
||||
it('shows delete hint not when permission to delete', function() {
|
||||
var deleteStub = sinon.stub(fileList, 'do_delete');
|
||||
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);
|
||||
});
|
||||
});
|
||||
describe('action handler', function() {
|
||||
var actionStub, $tr;
|
||||
|
||||
beforeEach(function() {
|
||||
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
|
||||
mtime: '123456'
|
||||
};
|
||||
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();
|
||||
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() {
|
||||
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');
|
||||
|
@ -235,6 +192,28 @@ describe('OCA.Files.FileActions tests', function() {
|
|||
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;
|
||||
beforeEach(function() {
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
273
apps/files/tests/js/fileactionsmenuSpec.js
Normal file
273
apps/files/tests/js/fileactionsmenuSpec.js
Normal file
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -89,13 +89,14 @@
|
|||
}
|
||||
});
|
||||
|
||||
fileActions.register(
|
||||
'all',
|
||||
'Share',
|
||||
OC.PERMISSION_SHARE,
|
||||
OC.imagePath('core', 'actions/share'),
|
||||
function(filename, context) {
|
||||
|
||||
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') {
|
||||
|
@ -139,7 +140,8 @@
|
|||
$tr.removeAttr('data-share-recipients');
|
||||
}
|
||||
});
|
||||
}, t('files_sharing', 'Share'));
|
||||
}
|
||||
});
|
||||
|
||||
OC.addScript('files_sharing', 'sharetabview').done(function() {
|
||||
fileList.registerTabView(new OCA.Sharing.ShareTabView('shareTabView'));
|
||||
|
|
|
@ -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');
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -59,7 +59,6 @@ OCA.Trashbin.App = {
|
|||
|
||||
fileActions.registerAction({
|
||||
name: 'Delete',
|
||||
displayName: '',
|
||||
mime: 'all',
|
||||
permissions: OC.PERMISSION_READ,
|
||||
icon: function() {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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,14 +598,55 @@ 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
|
||||
*
|
||||
|
@ -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();
|
||||
});
|
||||
|
||||
|
||||
|
|
|
@ -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');
|
||||
|
|
Loading…
Reference in a new issue