Merge pull request #18178 from owncloud/files-sidebar-actions
Sidebar file actions
This commit is contained in:
commit
4e53b5922d
|
@ -41,6 +41,7 @@ OCP\Util::addscript('files', 'file-upload');
|
|||
OCP\Util::addscript('files', 'jquery.iframe-transport');
|
||||
OCP\Util::addscript('files', 'jquery.fileupload');
|
||||
OCP\Util::addscript('files', 'jquery-visibility');
|
||||
OCP\Util::addscript('files', 'fileinfomodel');
|
||||
OCP\Util::addscript('files', 'filesummary');
|
||||
OCP\Util::addscript('files', 'breadcrumb');
|
||||
OCP\Util::addscript('files', 'filelist');
|
||||
|
|
|
@ -16,34 +16,12 @@
|
|||
* Displays a block of details about the file info.
|
||||
*
|
||||
*/
|
||||
var DetailFileInfoView = function() {
|
||||
this.initialize();
|
||||
};
|
||||
/**
|
||||
* @memberof OCA.Files
|
||||
*/
|
||||
DetailFileInfoView.prototype = {
|
||||
/**
|
||||
* jQuery element
|
||||
*/
|
||||
$el: null,
|
||||
var DetailFileInfoView = OC.Backbone.View.extend({
|
||||
tagName: 'div',
|
||||
className: 'detailFileInfoView',
|
||||
|
||||
_template: null,
|
||||
|
||||
/**
|
||||
* Currently displayed file info
|
||||
*
|
||||
* @type OCA.Files.FileInfo
|
||||
*/
|
||||
_fileInfo: null,
|
||||
|
||||
/**
|
||||
* Initialize the details view
|
||||
*/
|
||||
initialize: function() {
|
||||
this.$el = $('<div class="detailFileInfoView"></div>');
|
||||
},
|
||||
|
||||
/**
|
||||
* returns the jQuery object for HTML output
|
||||
*
|
||||
|
@ -53,31 +31,13 @@
|
|||
return this.$el;
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroy / uninitialize this instance.
|
||||
*/
|
||||
destroy: function() {
|
||||
if (this.$el) {
|
||||
this.$el.remove();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Renders this details view
|
||||
*
|
||||
* @abstract
|
||||
*/
|
||||
render: function() {
|
||||
// to be implemented in subclass
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the file info to be displayed in the view
|
||||
*
|
||||
* @param {OCA.Files.FileInfo} fileInfo file info to set
|
||||
*/
|
||||
setFileInfo: function(fileInfo) {
|
||||
this._fileInfo = fileInfo;
|
||||
this.model = fileInfo;
|
||||
this.render();
|
||||
},
|
||||
|
||||
|
@ -87,9 +47,9 @@
|
|||
* @return {OCA.Files.FileInfo} file info
|
||||
*/
|
||||
getFileInfo: function() {
|
||||
return this._fileInfo;
|
||||
return this.model;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
OCA.Files.DetailFileInfoView = DetailFileInfoView;
|
||||
})();
|
||||
|
|
|
@ -33,30 +33,14 @@
|
|||
* The details view show details about a selected file.
|
||||
*
|
||||
*/
|
||||
var DetailsView = function() {
|
||||
this.initialize();
|
||||
};
|
||||
|
||||
/**
|
||||
* @memberof OCA.Files
|
||||
*/
|
||||
DetailsView.prototype = {
|
||||
|
||||
/**
|
||||
* jQuery element
|
||||
*/
|
||||
$el: null,
|
||||
var DetailsView = OC.Backbone.View.extend({
|
||||
id: 'app-sidebar',
|
||||
tabName: 'div',
|
||||
className: 'detailsView',
|
||||
|
||||
_template: null,
|
||||
_templateTabHeader: null,
|
||||
|
||||
/**
|
||||
* Currently displayed file info
|
||||
*
|
||||
* @type OCA.Files.FileInfo
|
||||
*/
|
||||
_fileInfo: null,
|
||||
|
||||
/**
|
||||
* List of detail tab views
|
||||
*
|
||||
|
@ -78,33 +62,25 @@
|
|||
*/
|
||||
_currentTabId: null,
|
||||
|
||||
events: {
|
||||
'click a.close': '_onClose',
|
||||
'click .tabHeaders .tabHeader': '_onClickTab'
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialize the details view
|
||||
*/
|
||||
initialize: function() {
|
||||
this.$el = $('<div id="app-sidebar"></div>');
|
||||
this.fileInfo = null;
|
||||
this._tabViews = [];
|
||||
this._detailFileInfoViews = [];
|
||||
|
||||
this.$el.on('click', 'a.close', function(event) {
|
||||
OC.Apps.hideAppSidebar();
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
this.$el.on('click', '.tabHeaders .tabHeader', _.bind(this._onClickTab, this));
|
||||
|
||||
// uncomment to add some dummy tabs for testing
|
||||
// this._addTestTabs();
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroy / uninitialize this instance.
|
||||
*/
|
||||
destroy: function() {
|
||||
if (this.$el) {
|
||||
this.$el.remove();
|
||||
}
|
||||
_onClose: function(event) {
|
||||
OC.Apps.hideAppSidebar();
|
||||
event.preventDefault();
|
||||
},
|
||||
|
||||
_onClickTab: function(e) {
|
||||
|
@ -148,7 +124,6 @@
|
|||
*/
|
||||
render: function() {
|
||||
var self = this;
|
||||
this.$el.empty();
|
||||
|
||||
if (!this._template) {
|
||||
this._template = Handlebars.compile(TEMPLATE);
|
||||
|
@ -158,12 +133,13 @@
|
|||
this._templateTabHeader = Handlebars.compile(TEMPLATE_TAB_HEADER);
|
||||
}
|
||||
|
||||
var $el = $(this._template({
|
||||
this.$el.html(this._template({
|
||||
closeLabel: t('files', 'Close')
|
||||
}));
|
||||
var $tabsContainer = $el.find('.tabsContainer');
|
||||
var $tabHeadsContainer = $el.find('.tabHeaders');
|
||||
var $detailsContainer = $el.find('.detailFileInfoContainer');
|
||||
|
||||
var $tabsContainer = this.$el.find('.tabsContainer');
|
||||
var $tabHeadsContainer = this.$el.find('.tabHeaders');
|
||||
var $detailsContainer = this.$el.find('.detailFileInfoContainer');
|
||||
|
||||
// render details
|
||||
_.each(this._detailFileInfoViews, function(detailView) {
|
||||
|
@ -172,40 +148,36 @@
|
|||
|
||||
if (this._tabViews.length > 0) {
|
||||
if (!this._currentTab) {
|
||||
this._currentTab = this._tabViews[0].getId();
|
||||
this._currentTab = this._tabViews[0].id;
|
||||
}
|
||||
|
||||
// render tabs
|
||||
_.each(this._tabViews, function(tabView, i) {
|
||||
// hidden by default
|
||||
var $el = tabView.get$();
|
||||
var isCurrent = (tabView.getId() === self._currentTab);
|
||||
var isCurrent = (tabView.id === self._currentTab);
|
||||
if (!isCurrent) {
|
||||
$el.addClass('hidden');
|
||||
}
|
||||
$tabsContainer.append($el);
|
||||
|
||||
$tabHeadsContainer.append(self._templateTabHeader({
|
||||
tabId: tabView.getId(),
|
||||
tabId: tabView.id,
|
||||
tabIndex: i,
|
||||
label: tabView.getLabel(),
|
||||
selected: isCurrent
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: select current tab
|
||||
|
||||
this.$el.append($el);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the file info to be displayed in the view
|
||||
*
|
||||
* @param {OCA.Files.FileInfo} fileInfo file info to set
|
||||
* @param {OCA.Files.FileInfoModel} fileInfo file info to set
|
||||
*/
|
||||
setFileInfo: function(fileInfo) {
|
||||
this._fileInfo = fileInfo;
|
||||
this.model = fileInfo;
|
||||
|
||||
this.render();
|
||||
|
||||
|
@ -221,10 +193,10 @@
|
|||
/**
|
||||
* Returns the file info.
|
||||
*
|
||||
* @return {OCA.Files.FileInfo} file info
|
||||
* @return {OCA.Files.FileInfoModel} file info
|
||||
*/
|
||||
getFileInfo: function() {
|
||||
return this._fileInfo;
|
||||
return this.model;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -244,7 +216,7 @@
|
|||
addDetailView: function(detailView) {
|
||||
this._detailFileInfoViews.push(detailView);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
OCA.Files.DetailsView = DetailsView;
|
||||
})();
|
||||
|
|
|
@ -17,23 +17,10 @@
|
|||
* Base class for tab views to display file information.
|
||||
*
|
||||
*/
|
||||
var DetailTabView = function(id) {
|
||||
this.initialize(id);
|
||||
};
|
||||
var DetailTabView = OC.Backbone.View.extend({
|
||||
tag: 'div',
|
||||
|
||||
/**
|
||||
* @memberof OCA.Files
|
||||
*/
|
||||
DetailTabView.prototype = {
|
||||
/**
|
||||
* jQuery element
|
||||
*/
|
||||
$el: null,
|
||||
|
||||
/**
|
||||
* Tab id
|
||||
*/
|
||||
_id: null,
|
||||
className: 'tab',
|
||||
|
||||
/**
|
||||
* Tab label
|
||||
|
@ -42,43 +29,11 @@
|
|||
|
||||
_template: null,
|
||||
|
||||
/**
|
||||
* Currently displayed file info
|
||||
*
|
||||
* @type OCA.Files.FileInfo
|
||||
*/
|
||||
_fileInfo: null,
|
||||
|
||||
/**
|
||||
* Initialize the details view
|
||||
*
|
||||
* @param {string} id tab id
|
||||
*/
|
||||
initialize: function(id) {
|
||||
if (!id) {
|
||||
throw 'Argument "id" is required';
|
||||
initialize: function() {
|
||||
if (!this.id) {
|
||||
this.id = 'detailTabView' + DetailTabView._TAB_COUNT;
|
||||
DetailTabView._TAB_COUNT++;
|
||||
}
|
||||
this._id = id;
|
||||
this.$el = $('<div class="tab"></div>');
|
||||
this.$el.attr('data-tabid', id);
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroy / uninitialize this instance.
|
||||
*/
|
||||
destroy: function() {
|
||||
if (this.$el) {
|
||||
this.$el.remove();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the tab element id
|
||||
*
|
||||
* @return {string} tab id
|
||||
*/
|
||||
getId: function() {
|
||||
return this._id;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -87,7 +42,7 @@
|
|||
* @return {String} label
|
||||
*/
|
||||
getLabel: function() {
|
||||
return 'Tab ' + this._id;
|
||||
return 'Tab ' + this.id;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -107,29 +62,29 @@
|
|||
render: function() {
|
||||
// to be implemented in subclass
|
||||
// FIXME: code is only for testing
|
||||
this.$el.empty();
|
||||
this.$el.append('<div>Hello ' + this._id + '</div>');
|
||||
this.$el.html('<div>Hello ' + this.id + '</div>');
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the file info to be displayed in the view
|
||||
*
|
||||
* @param {OCA.Files.FileInfo} fileInfo file info to set
|
||||
* @param {OCA.Files.FileInfoModel} fileInfo file info to set
|
||||
*/
|
||||
setFileInfo: function(fileInfo) {
|
||||
this._fileInfo = fileInfo;
|
||||
this.model = fileInfo;
|
||||
this.render();
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the file info.
|
||||
*
|
||||
* @return {OCA.Files.FileInfo} file info
|
||||
* @return {OCA.Files.FileInfoModel} file info
|
||||
*/
|
||||
getFileInfo: function() {
|
||||
return this._fileInfo;
|
||||
return this.model;
|
||||
}
|
||||
};
|
||||
});
|
||||
DetailTabView._TAB_COUNT = 0;
|
||||
|
||||
OCA.Files.DetailTabView = DetailTabView;
|
||||
})();
|
||||
|
|
|
@ -31,6 +31,10 @@
|
|||
actions: {},
|
||||
defaults: {},
|
||||
icons: {},
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
currentFile: null,
|
||||
|
||||
/**
|
||||
|
@ -331,6 +335,9 @@
|
|||
$trigger.addClass('open');
|
||||
|
||||
menu = new OCA.Files.FileActionsMenu();
|
||||
|
||||
context.$file.find('td.filename').append(menu.$el);
|
||||
|
||||
menu.$el.on('afterHide', function() {
|
||||
context.$file.removeClass('mouseOver');
|
||||
$trigger.removeClass('open');
|
||||
|
@ -338,7 +345,6 @@
|
|||
});
|
||||
|
||||
context.$file.addClass('mouseOver');
|
||||
context.$file.find('td.filename').append(menu.$el);
|
||||
menu.show(context);
|
||||
},
|
||||
|
||||
|
@ -401,11 +407,22 @@
|
|||
// also set on global object for legacy apps
|
||||
window.FileActions.currentFile = currentFile;
|
||||
|
||||
var callContext = _.extend({}, context);
|
||||
|
||||
if (!context.dir && context.fileList) {
|
||||
callContext.dir = $file.attr('data-path') || context.fileList.getCurrentDirectory();
|
||||
}
|
||||
|
||||
if (!context.fileInfoModel && context.fileList) {
|
||||
callContext.fileInfoModel = context.fileList.getModelForFile(fileName);
|
||||
if (!callContext.fileInfoModel) {
|
||||
console.warn('No file info model found for file "' + fileName + '"');
|
||||
}
|
||||
}
|
||||
|
||||
actionSpec.action(
|
||||
fileName,
|
||||
_.extend(context, {
|
||||
dir: $file.attr('data-path') || context.fileList.getCurrentDirectory()
|
||||
})
|
||||
callContext
|
||||
);
|
||||
}
|
||||
);
|
||||
|
@ -413,6 +430,63 @@
|
|||
return $actionEl;
|
||||
},
|
||||
|
||||
/**
|
||||
* Trigger the given action on the given file.
|
||||
*
|
||||
* @param {string} actionName action name
|
||||
* @param {OCA.Files.FileInfoModel} fileInfoModel file info model
|
||||
* @param {OCA.Files.FileList} [fileList] file list, for compatibility with older action handlers [DEPRECATED]
|
||||
*
|
||||
* @return {boolean} true if the action handler was called, false otherwise
|
||||
*
|
||||
* @since 8.2
|
||||
*/
|
||||
triggerAction: function(actionName, fileInfoModel, fileList) {
|
||||
var actionFunc;
|
||||
var actions = this.get(
|
||||
fileInfoModel.get('mimetype'),
|
||||
fileInfoModel.isDirectory() ? 'dir' : 'file',
|
||||
fileInfoModel.get('permissions')
|
||||
);
|
||||
|
||||
if (actionName) {
|
||||
actionFunc = actions[actionName];
|
||||
} else {
|
||||
actionFunc = this.getDefault(
|
||||
fileInfoModel.get('mimetype'),
|
||||
fileInfoModel.isDirectory() ? 'dir' : 'file',
|
||||
fileInfoModel.get('permissions')
|
||||
);
|
||||
}
|
||||
|
||||
if (!actionFunc) {
|
||||
actionFunc = actions['Download'];
|
||||
}
|
||||
|
||||
if (!actionFunc) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var context = {
|
||||
fileActions: this,
|
||||
fileInfoModel: fileInfoModel,
|
||||
dir: fileInfoModel.get('path')
|
||||
};
|
||||
|
||||
var fileName = fileInfoModel.get('name');
|
||||
this.currentFile = fileName;
|
||||
// also set on global object for legacy apps
|
||||
window.FileActions.currentFile = fileName;
|
||||
|
||||
if (fileList) {
|
||||
// compatibility with action handlers that expect these
|
||||
context.fileList = fileList;
|
||||
context.$file = fileList.findFileEl(fileName);
|
||||
}
|
||||
|
||||
actionFunc(fileName, context);
|
||||
},
|
||||
|
||||
/**
|
||||
* Display file actions for the given element
|
||||
* @param parent "td" element of the file for which to display actions
|
||||
|
@ -627,11 +701,12 @@
|
|||
* Action handler function for file actions
|
||||
*
|
||||
* @callback OCA.Files.FileActions~actionHandler
|
||||
* @param {String} fileName name of the clicked file
|
||||
* @param {String} fileName name of the file on which the action must be performed
|
||||
* @param context context
|
||||
* @param {String} context.dir directory of the file
|
||||
* @param context.$file jQuery element of the file
|
||||
* @param {OCA.Files.FileList} context.fileList the FileList instance on which the action occurred
|
||||
* @param {OCA.Files.FileInfoModel} fileInfoModel file info model
|
||||
* @param {Object} [context.$file] jQuery element of the file [DEPRECATED]
|
||||
* @param {OCA.Files.FileList} [context.fileList] the FileList instance on which the action occurred [DEPRECATED]
|
||||
* @param {OCA.Files.FileActions} context.fileActions the FileActions instance on which the action occurred
|
||||
*/
|
||||
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* Copyright (c) 2015
|
||||
*
|
||||
* This file is licensed under the Affero General Public License version 3
|
||||
* or later.
|
||||
*
|
||||
* See the COPYING-README file.
|
||||
*
|
||||
*/
|
||||
|
||||
(function(OC, OCA) {
|
||||
|
||||
/**
|
||||
* @class OC.Files.FileInfo
|
||||
* @classdesc File information
|
||||
*
|
||||
* @param {Object} attributes file data
|
||||
* @param {int} attributes.id file id
|
||||
* @param {string} attributes.name file name
|
||||
* @param {string} attributes.path path leading to the file,
|
||||
* without the file name and with a leading slash
|
||||
* @param {int} attributes.size size
|
||||
* @param {string} attributes.mimetype mime type
|
||||
* @param {string} attributes.icon icon URL
|
||||
* @param {int} attributes.permissions permissions
|
||||
* @param {Date} attributes.mtime modification time
|
||||
* @param {string} attributes.etag etag
|
||||
* @param {string} mountType mount type
|
||||
*
|
||||
* @since 8.2
|
||||
*/
|
||||
var FileInfoModel = OC.Backbone.Model.extend({
|
||||
|
||||
initialize: function(data) {
|
||||
if (!_.isUndefined(data.id)) {
|
||||
data.id = parseInt(data.id, 10);
|
||||
}
|
||||
|
||||
// TODO: normalize path
|
||||
data.path = data.path || '';
|
||||
data.name = data.name;
|
||||
|
||||
data.mimetype = data.mimetype || 'application/octet-stream';
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns whether this file is a directory
|
||||
*
|
||||
* @return {boolean} true if this is a directory, false otherwise
|
||||
*/
|
||||
isDirectory: function() {
|
||||
return this.get('mimetype') === 'httpd/unix-directory';
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the full path to this file
|
||||
*
|
||||
* @return {string} full path
|
||||
*/
|
||||
getFullPath: function() {
|
||||
return OC.joinPaths(this.get('path'), this.get('name'));
|
||||
}
|
||||
});
|
||||
|
||||
if (!OCA.Files) {
|
||||
OCA.Files = {};
|
||||
}
|
||||
OCA.Files.FileInfoModel = FileInfoModel;
|
||||
|
||||
})(OC, OCA);
|
||||
|
|
@ -213,7 +213,7 @@
|
|||
|
||||
if (_.isUndefined(options.detailsViewEnabled) || options.detailsViewEnabled) {
|
||||
this._detailsView = new OCA.Files.DetailsView();
|
||||
this._detailsView.addDetailView(new OCA.Files.MainFileInfoDetailView());
|
||||
this._detailsView.addDetailView(new OCA.Files.MainFileInfoDetailView({fileList: this, fileActions: this.fileActions}));
|
||||
this._detailsView.$el.insertBefore(this.$el);
|
||||
this._detailsView.$el.addClass('disappear');
|
||||
}
|
||||
|
@ -284,33 +284,75 @@
|
|||
this.fileActions.on('setDefault', this._onFileActionsUpdated);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a unique model for the given file name.
|
||||
*
|
||||
* @param {string|object} fileName file name or jquery row
|
||||
* @return {OCA.Files.FileInfoModel} file info model
|
||||
*/
|
||||
getModelForFile: function(fileName) {
|
||||
var $tr;
|
||||
// jQuery object ?
|
||||
if (fileName.is) {
|
||||
$tr = fileName;
|
||||
fileName = $tr.attr('data-file');
|
||||
} else {
|
||||
$tr = this.findFileEl(fileName);
|
||||
}
|
||||
|
||||
if (!$tr || !$tr.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// if requesting the selected model, return it
|
||||
if (this._currentFileModel && this._currentFileModel.get('name') === fileName) {
|
||||
return this._currentFileModel;
|
||||
}
|
||||
|
||||
// TODO: note, this is a temporary model required for synchronising
|
||||
// state between different views.
|
||||
// In the future the FileList should work with Backbone.Collection
|
||||
// and contain existing models that can be used.
|
||||
// This method would in the future simply retrieve the matching model from the collection.
|
||||
var model = new OCA.Files.FileInfoModel(this.elementToFile($tr));
|
||||
if (!model.has('path')) {
|
||||
model.set('path', this.getCurrentDirectory(), {silent: true});
|
||||
}
|
||||
return model;
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the details view to display the given file
|
||||
*
|
||||
* @param {OCA.Files.FileInfo} fileInfo file info to display
|
||||
* @param {string} fileName file name from the current list
|
||||
*/
|
||||
_updateDetailsView: function(fileInfo) {
|
||||
_updateDetailsView: function(fileName) {
|
||||
if (!this._detailsView) {
|
||||
return;
|
||||
}
|
||||
|
||||
var self = this;
|
||||
var oldFileInfo = this._detailsView.getFileInfo();
|
||||
if (oldFileInfo) {
|
||||
// TODO: use more efficient way, maybe track the highlight
|
||||
this.$fileList.children().filterAttr('data-id', '' + oldFileInfo.id).removeClass('highlighted');
|
||||
this.$fileList.children().filterAttr('data-id', '' + oldFileInfo.get('id')).removeClass('highlighted');
|
||||
oldFileInfo.off('change', this._onSelectedModelChanged, this);
|
||||
}
|
||||
|
||||
if (!fileInfo) {
|
||||
if (!fileName) {
|
||||
OC.Apps.hideAppSidebar();
|
||||
this._detailsView.setFileInfo(null);
|
||||
this._currentFileModel = null;
|
||||
return;
|
||||
}
|
||||
|
||||
this.$fileList.children().filterAttr('data-id', '' + fileInfo.id).addClass('highlighted');
|
||||
this._detailsView.setFileInfo(_.extend({
|
||||
path: this.getCurrentDirectory()
|
||||
}, fileInfo));
|
||||
var $tr = this.findFileEl(fileName);
|
||||
var model = this.getModelForFile($tr);
|
||||
|
||||
this._currentFileModel = model;
|
||||
|
||||
$tr.addClass('highlighted');
|
||||
|
||||
this._detailsView.setFileInfo(model);
|
||||
this._detailsView.$el.scrollTop(0);
|
||||
_.defer(OC.Apps.showAppSidebar);
|
||||
},
|
||||
|
@ -369,7 +411,7 @@
|
|||
this._selectionSummary.remove(data);
|
||||
}
|
||||
if (this._selectionSummary.getTotal() === 1) {
|
||||
this._updateDetailsView(_.values(this._selectedFiles)[0]);
|
||||
this._updateDetailsView(_.values(this._selectedFiles)[0].name);
|
||||
} else {
|
||||
// show nothing when multiple files are selected
|
||||
this._updateDetailsView(null);
|
||||
|
@ -434,8 +476,7 @@
|
|||
$(event.target).closest('a').blur();
|
||||
}
|
||||
} else {
|
||||
var fileInfo = this.files[$tr.index()];
|
||||
this._updateDetailsView(fileInfo);
|
||||
this._updateDetailsView($tr.attr('data-file'));
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
|
@ -1191,6 +1232,8 @@
|
|||
sortdirection: this._sortDirection
|
||||
}
|
||||
});
|
||||
// close sidebar
|
||||
this._updateDetailsView(null);
|
||||
var callBack = this.reloadCallback.bind(this);
|
||||
return this._reloadCall.then(callBack, callBack);
|
||||
},
|
||||
|
@ -1590,7 +1633,7 @@
|
|||
tr.remove();
|
||||
tr = self.add(fileInfo, {updateSummary: false, silent: true});
|
||||
self.$fileList.trigger($.Event('fileActionsReady', {fileList: self, $files: $(tr)}));
|
||||
self._updateDetailsView(fileInfo);
|
||||
self._updateDetailsView(fileInfo.name);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
(function() {
|
||||
var TEMPLATE =
|
||||
'<div class="thumbnail"></div><div title="{{name}}" class="fileName ellipsis">{{name}}</div>' +
|
||||
'<a href="#" class="thumbnail action-default"></a><div title="{{name}}" class="fileName ellipsis">{{name}}</div>' +
|
||||
'<div class="file-details ellipsis">' +
|
||||
' <a href="#" ' +
|
||||
' alt="{{starAltText}}"' +
|
||||
|
@ -27,58 +27,104 @@
|
|||
* Displays main details about a file
|
||||
*
|
||||
*/
|
||||
var MainFileInfoDetailView = function() {
|
||||
this.initialize();
|
||||
};
|
||||
/**
|
||||
* @memberof OCA.Files
|
||||
*/
|
||||
MainFileInfoDetailView.prototype = _.extend({}, OCA.Files.DetailFileInfoView.prototype,
|
||||
var MainFileInfoDetailView = OCA.Files.DetailFileInfoView.extend(
|
||||
/** @lends OCA.Files.MainFileInfoDetailView.prototype */ {
|
||||
_template: null,
|
||||
|
||||
className: 'mainFileInfoView',
|
||||
|
||||
/**
|
||||
* Initialize the details view
|
||||
* Associated file list instance, for file actions
|
||||
*
|
||||
* @type {OCA.Files.FileList}
|
||||
*/
|
||||
initialize: function() {
|
||||
this.$el = $('<div class="mainFileInfoView"></div>');
|
||||
_fileList: null,
|
||||
|
||||
/**
|
||||
* File actions
|
||||
*
|
||||
* @type {OCA.Files.FileActions}
|
||||
*/
|
||||
_fileActions: null,
|
||||
|
||||
events: {
|
||||
'click a.action-favorite': '_onClickFavorite',
|
||||
'click a.action-default': '_onClickDefaultAction'
|
||||
},
|
||||
|
||||
template: function(data) {
|
||||
if (!this._template) {
|
||||
this._template = Handlebars.compile(TEMPLATE);
|
||||
}
|
||||
return this._template(data);
|
||||
},
|
||||
|
||||
initialize: function(options) {
|
||||
options = options || {};
|
||||
this._fileList = options.fileList;
|
||||
this._fileActions = options.fileActions;
|
||||
if (!this._fileList) {
|
||||
throw 'Missing requird parameter "fileList"';
|
||||
}
|
||||
if (!this._fileActions) {
|
||||
throw 'Missing requird parameter "fileActions"';
|
||||
}
|
||||
},
|
||||
|
||||
_onClickFavorite: function(event) {
|
||||
event.preventDefault();
|
||||
this._fileActions.triggerAction('Favorite', this.model, this._fileList);
|
||||
},
|
||||
|
||||
_onClickDefaultAction: function(event) {
|
||||
event.preventDefault();
|
||||
this._fileActions.triggerAction(null, this.model, this._fileList);
|
||||
},
|
||||
|
||||
_onModelChanged: function() {
|
||||
// simply re-render
|
||||
this.render();
|
||||
},
|
||||
|
||||
setFileInfo: function(fileInfo) {
|
||||
if (this.model) {
|
||||
this.model.off('change', this._onModelChanged, this);
|
||||
}
|
||||
this.model = fileInfo;
|
||||
if (this.model) {
|
||||
this.model.on('change', this._onModelChanged, this);
|
||||
}
|
||||
this.render();
|
||||
},
|
||||
|
||||
/**
|
||||
* Renders this details view
|
||||
*/
|
||||
render: function() {
|
||||
this.$el.empty();
|
||||
|
||||
if (!this._template) {
|
||||
this._template = Handlebars.compile(TEMPLATE);
|
||||
}
|
||||
|
||||
if (this._fileInfo) {
|
||||
var isFavorite = (this._fileInfo.tags || []).indexOf(OC.TAG_FAVORITE) >= 0;
|
||||
this.$el.append(this._template({
|
||||
if (this.model) {
|
||||
var isFavorite = (this.model.get('tags') || []).indexOf(OC.TAG_FAVORITE) >= 0;
|
||||
this.$el.html(this.template({
|
||||
nameLabel: t('files', 'Name'),
|
||||
name: this._fileInfo.name,
|
||||
name: this.model.get('name'),
|
||||
pathLabel: t('files', 'Path'),
|
||||
path: this._fileInfo.path,
|
||||
path: this.model.get('path'),
|
||||
sizeLabel: t('files', 'Size'),
|
||||
size: OC.Util.humanFileSize(this._fileInfo.size, true),
|
||||
altSize: n('files', '%n byte', '%n bytes', this._fileInfo.size),
|
||||
size: OC.Util.humanFileSize(this.model.get('size'), true),
|
||||
altSize: n('files', '%n byte', '%n bytes', this.model.get('size')),
|
||||
dateLabel: t('files', 'Modified'),
|
||||
altDate: OC.Util.formatDate(this._fileInfo.mtime),
|
||||
date: OC.Util.relativeModifiedDate(this._fileInfo.mtime),
|
||||
altDate: OC.Util.formatDate(this.model.get('mtime')),
|
||||
date: OC.Util.relativeModifiedDate(this.model.get('mtime')),
|
||||
starAltText: isFavorite ? t('files', 'Favorited') : t('files', 'Favorite'),
|
||||
starIcon: OC.imagePath('core', isFavorite ? 'actions/starred' : 'actions/star')
|
||||
}));
|
||||
|
||||
// TODO: we really need OC.Previews
|
||||
var $iconDiv = this.$el.find('.thumbnail');
|
||||
if (this._fileInfo.mimetype !== 'httpd/unix-directory') {
|
||||
if (!this.model.isDirectory()) {
|
||||
// TODO: inject utility class?
|
||||
FileList.lazyLoadPreview({
|
||||
path: this._fileInfo.path + '/' + this._fileInfo.name,
|
||||
mime: this._fileInfo.mimetype,
|
||||
etag: this._fileInfo.etag,
|
||||
path: this.model.getFullPath(),
|
||||
mime: this.model.get('mimetype'),
|
||||
etag: this.model.get('etag'),
|
||||
x: 50,
|
||||
y: 50,
|
||||
callback: function(previewUrl) {
|
||||
|
@ -90,7 +136,10 @@
|
|||
$iconDiv.css('background-image', 'url("' + OC.MimeType.getIconUrl('dir') + '")');
|
||||
}
|
||||
this.$el.find('[title]').tooltip({placement: 'bottom'});
|
||||
} else {
|
||||
this.$el.empty();
|
||||
}
|
||||
this.delegateEvents();
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -77,7 +77,7 @@
|
|||
var self = this;
|
||||
// register "star" action
|
||||
fileActions.registerAction({
|
||||
name: 'favorite',
|
||||
name: 'Favorite',
|
||||
displayName: 'Favorite',
|
||||
mime: 'all',
|
||||
permissions: OC.PERMISSION_READ,
|
||||
|
@ -124,6 +124,7 @@
|
|||
toggleStar($actionEl, (newTags.indexOf(OC.TAG_FAVORITE) >= 0));
|
||||
$file.attr('data-tags', newTags.join('|'));
|
||||
$file.attr('data-favorite', !isFavorite);
|
||||
context.fileInfoModel.set('tags', newTags);
|
||||
fileInfo.tags = newTags;
|
||||
});
|
||||
}
|
||||
|
@ -145,6 +146,12 @@
|
|||
$tr.find('td:first').prepend('<div class="favorite"></div>');
|
||||
return $tr;
|
||||
};
|
||||
var oldElementToFile = fileList.elementToFile;
|
||||
fileList.elementToFile = function($el) {
|
||||
var fileInfo = oldElementToFile.apply(this, arguments);
|
||||
fileInfo.tags = $el.attr('data-tags') || [];
|
||||
return fileInfo;
|
||||
};
|
||||
},
|
||||
|
||||
attach: function(fileList) {
|
||||
|
|
|
@ -26,7 +26,7 @@ describe('OCA.Files.DetailsView tests', function() {
|
|||
detailsView = new OCA.Files.DetailsView();
|
||||
});
|
||||
afterEach(function() {
|
||||
detailsView.destroy();
|
||||
detailsView.remove();
|
||||
detailsView = undefined;
|
||||
});
|
||||
it('renders itself empty when nothing registered', function() {
|
||||
|
|
|
@ -27,6 +27,7 @@ describe('OCA.Files.FileActions tests', function() {
|
|||
var $body = $('#testArea');
|
||||
$body.append('<input type="hidden" id="dir" value="/subdir"></input>');
|
||||
$body.append('<input type="hidden" id="permissions" value="31"></input>');
|
||||
$body.append('<table id="filestable"><tbody id="fileList"></tbody></table>');
|
||||
// dummy files table
|
||||
fileActions = new OCA.Files.FileActions();
|
||||
fileActions.registerAction({
|
||||
|
@ -152,9 +153,10 @@ describe('OCA.Files.FileActions tests', function() {
|
|||
});
|
||||
});
|
||||
describe('action handler', function() {
|
||||
var actionStub, $tr;
|
||||
var actionStub, $tr, clock;
|
||||
|
||||
beforeEach(function() {
|
||||
clock = sinon.useFakeTimers();
|
||||
var fileData = {
|
||||
id: 18,
|
||||
type: 'file',
|
||||
|
@ -175,6 +177,12 @@ describe('OCA.Files.FileActions tests', function() {
|
|||
});
|
||||
$tr = fileList.add(fileData);
|
||||
});
|
||||
afterEach(function() {
|
||||
OC.hideMenus();
|
||||
// jump past animations
|
||||
clock.tick(1000);
|
||||
clock.restore();
|
||||
});
|
||||
it('passes context to action handler', function() {
|
||||
$tr.find('.action-test').click();
|
||||
expect(actionStub.calledOnce).toEqual(true);
|
||||
|
@ -184,6 +192,7 @@ describe('OCA.Files.FileActions tests', function() {
|
|||
expect(context.fileList).toBeDefined();
|
||||
expect(context.fileActions).toBeDefined();
|
||||
expect(context.dir).toEqual('/subdir');
|
||||
expect(context.fileInfoModel.get('name')).toEqual('testName.txt');
|
||||
|
||||
// when data-path is defined
|
||||
actionStub.reset();
|
||||
|
@ -192,6 +201,22 @@ describe('OCA.Files.FileActions tests', function() {
|
|||
context = actionStub.getCall(0).args[1];
|
||||
expect(context.dir).toEqual('/somepath');
|
||||
});
|
||||
it('also triggers action handler when calling triggerAction()', function() {
|
||||
var model = new OCA.Files.FileInfoModel({
|
||||
id: 1,
|
||||
name: 'Test.txt',
|
||||
path: '/subdir',
|
||||
mime: 'text/plain',
|
||||
permissions: 31
|
||||
});
|
||||
fileActions.triggerAction('Test', model, fileList);
|
||||
|
||||
expect(actionStub.calledOnce).toEqual(true);
|
||||
expect(actionStub.getCall(0).args[0]).toEqual('Test.txt');
|
||||
expect(actionStub.getCall(0).args[1].fileList).toEqual(fileList);
|
||||
expect(actionStub.getCall(0).args[1].fileActions).toEqual(fileActions);
|
||||
expect(actionStub.getCall(0).args[1].fileInfoModel).toEqual(model);
|
||||
});
|
||||
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);
|
||||
|
@ -203,12 +228,13 @@ describe('OCA.Files.FileActions tests', function() {
|
|||
expect($tr.hasClass('mouseOver')).toEqual(true);
|
||||
});
|
||||
it('cleans up after hiding', function() {
|
||||
var clock = sinon.useFakeTimers();
|
||||
var slideUpStub = sinon.stub($.fn, 'slideUp');
|
||||
$tr.find('.action-menu').click();
|
||||
expect($tr.find('.fileActionsMenu').length).toEqual(1);
|
||||
OC.hideMenus();
|
||||
// sliding animation
|
||||
clock.tick(500);
|
||||
expect(slideUpStub.calledOnce).toEqual(true);
|
||||
slideUpStub.getCall(0).args[1]();
|
||||
expect($tr.hasClass('mouseOver')).toEqual(false);
|
||||
expect($tr.find('.fileActionsMenu').length).toEqual(0);
|
||||
});
|
||||
|
|
|
@ -1912,6 +1912,20 @@ describe('OCA.Files.FileList tests', function() {
|
|||
expect($tr.hasClass('highlighted')).toEqual(false);
|
||||
expect(fileList._detailsView.getFileInfo()).toEqual(null);
|
||||
});
|
||||
it('returns the currently selected model instance when calling getModelForFile', function() {
|
||||
var $tr = fileList.findFileEl('One.txt');
|
||||
$tr.find('td.filename>a.name').click();
|
||||
|
||||
var model1 = fileList.getModelForFile('One.txt');
|
||||
var model2 = fileList.getModelForFile('One.txt');
|
||||
model1.set('test', true);
|
||||
|
||||
// it's the same model
|
||||
expect(model2).toEqual(model1);
|
||||
|
||||
var model3 = fileList.getModelForFile($tr);
|
||||
expect(model3).toEqual(model1);
|
||||
});
|
||||
});
|
||||
describe('File actions', function() {
|
||||
it('Clicking on a file name will trigger default action', function() {
|
||||
|
|
|
@ -20,32 +20,37 @@
|
|||
*/
|
||||
|
||||
describe('OCA.Files.MainFileInfoDetailView tests', function() {
|
||||
var view, tooltipStub, previewStub, fncLazyLoadPreview, fileListMock;
|
||||
var view, tooltipStub, fileListMock, fileActions, fileList, testFileInfo;
|
||||
|
||||
beforeEach(function() {
|
||||
tooltipStub = sinon.stub($.fn, 'tooltip');
|
||||
fileListMock = sinon.mock(OCA.Files.FileList.prototype);
|
||||
view = new OCA.Files.MainFileInfoDetailView();
|
||||
fileActions = new OCA.Files.FileActions();
|
||||
fileList = new OCA.Files.FileList($('<table></table>'), {
|
||||
fileActions: fileActions
|
||||
});
|
||||
view = new OCA.Files.MainFileInfoDetailView({
|
||||
fileList: fileList,
|
||||
fileActions: fileActions
|
||||
});
|
||||
testFileInfo = new OCA.Files.FileInfoModel({
|
||||
id: 5,
|
||||
name: 'One.txt',
|
||||
mimetype: 'text/plain',
|
||||
permissions: 31,
|
||||
path: '/subdir',
|
||||
size: 123456789,
|
||||
mtime: Date.UTC(2015, 6, 17, 1, 2, 0, 0)
|
||||
});
|
||||
});
|
||||
afterEach(function() {
|
||||
view.destroy();
|
||||
view.remove();
|
||||
view = undefined;
|
||||
tooltipStub.restore();
|
||||
fileListMock.restore();
|
||||
|
||||
});
|
||||
describe('rendering', function() {
|
||||
var testFileInfo;
|
||||
beforeEach(function() {
|
||||
view = new OCA.Files.MainFileInfoDetailView();
|
||||
testFileInfo = {
|
||||
id: 5,
|
||||
name: 'One.txt',
|
||||
path: '/subdir',
|
||||
size: 123456789,
|
||||
mtime: Date.UTC(2015, 6, 17, 1, 2, 0, 0)
|
||||
};
|
||||
});
|
||||
it('displays basic info', function() {
|
||||
var clock = sinon.useFakeTimers(Date.UTC(2015, 6, 17, 1, 2, 0, 3));
|
||||
var dateExpected = OC.Util.formatDate(Date(Date.UTC(2015, 6, 17, 1, 2, 0, 0)));
|
||||
|
@ -59,39 +64,34 @@ describe('OCA.Files.MainFileInfoDetailView tests', function() {
|
|||
clock.restore();
|
||||
});
|
||||
it('displays favorite icon', function() {
|
||||
view.setFileInfo(_.extend(testFileInfo, {
|
||||
tags: [OC.TAG_FAVORITE]
|
||||
}));
|
||||
testFileInfo.set('tags', [OC.TAG_FAVORITE]);
|
||||
view.setFileInfo(testFileInfo);
|
||||
expect(view.$el.find('.favorite img').attr('src'))
|
||||
.toEqual(OC.imagePath('core', 'actions/starred'));
|
||||
|
||||
view.setFileInfo(_.extend(testFileInfo, {
|
||||
tags: []
|
||||
}));
|
||||
testFileInfo.set('tags', []);
|
||||
view.setFileInfo(testFileInfo);
|
||||
expect(view.$el.find('.favorite img').attr('src'))
|
||||
.toEqual(OC.imagePath('core', 'actions/star'));
|
||||
});
|
||||
it('displays mime icon', function() {
|
||||
// File
|
||||
view.setFileInfo(_.extend(testFileInfo, {
|
||||
mimetype: 'text/calendar'
|
||||
}));
|
||||
testFileInfo.set('mimetype', 'text/calendar');
|
||||
view.setFileInfo(testFileInfo);
|
||||
|
||||
expect(view.$el.find('.thumbnail').css('background-image'))
|
||||
.toContain('filetypes/text-calendar.svg');
|
||||
|
||||
// Folder
|
||||
view.setFileInfo(_.extend(testFileInfo, {
|
||||
mimetype: 'httpd/unix-directory'
|
||||
}));
|
||||
testFileInfo.set('mimetype', 'httpd/unix-directory');
|
||||
view.setFileInfo(testFileInfo);
|
||||
|
||||
expect(view.$el.find('.thumbnail').css('background-image'))
|
||||
.toContain('filetypes/folder.svg');
|
||||
});
|
||||
it('displays thumbnail', function() {
|
||||
view.setFileInfo(_.extend(testFileInfo, {
|
||||
mimetype: 'text/plain'
|
||||
}));
|
||||
testFileInfo.set('mimetype', 'test/plain');
|
||||
view.setFileInfo(testFileInfo);
|
||||
|
||||
var expectation = fileListMock.expects('lazyLoadPreview');
|
||||
expectation.once();
|
||||
|
@ -100,5 +100,76 @@ describe('OCA.Files.MainFileInfoDetailView tests', function() {
|
|||
|
||||
fileListMock.verify();
|
||||
});
|
||||
it('rerenders when changes are made on the model', function() {
|
||||
view.setFileInfo(testFileInfo);
|
||||
|
||||
testFileInfo.set('tags', [OC.TAG_FAVORITE]);
|
||||
|
||||
expect(view.$el.find('.favorite img').attr('src'))
|
||||
.toEqual(OC.imagePath('core', 'actions/starred'));
|
||||
|
||||
testFileInfo.set('tags', []);
|
||||
|
||||
expect(view.$el.find('.favorite img').attr('src'))
|
||||
.toEqual(OC.imagePath('core', 'actions/star'));
|
||||
});
|
||||
it('unbinds change listener from model', function() {
|
||||
view.setFileInfo(testFileInfo);
|
||||
view.setFileInfo(new OCA.Files.FileInfoModel({
|
||||
id: 999,
|
||||
name: 'test.txt',
|
||||
path: '/'
|
||||
}));
|
||||
|
||||
// set value on old model
|
||||
testFileInfo.set('tags', [OC.TAG_FAVORITE]);
|
||||
|
||||
// no change
|
||||
expect(view.$el.find('.favorite img').attr('src'))
|
||||
.toEqual(OC.imagePath('core', 'actions/star'));
|
||||
});
|
||||
});
|
||||
describe('events', function() {
|
||||
it('triggers default action when clicking on the thumbnail', function() {
|
||||
var actionHandler = sinon.stub();
|
||||
|
||||
fileActions.registerAction({
|
||||
name: 'Something',
|
||||
mime: 'all',
|
||||
permissions: OC.PERMISSION_READ,
|
||||
actionHandler: actionHandler
|
||||
});
|
||||
fileActions.setDefault('text/plain', 'Something');
|
||||
|
||||
view.setFileInfo(testFileInfo);
|
||||
|
||||
view.$el.find('.thumbnail').click();
|
||||
|
||||
expect(actionHandler.calledOnce).toEqual(true);
|
||||
expect(actionHandler.getCall(0).args[0]).toEqual('One.txt');
|
||||
expect(actionHandler.getCall(0).args[1].fileList).toEqual(fileList);
|
||||
expect(actionHandler.getCall(0).args[1].fileActions).toEqual(fileActions);
|
||||
expect(actionHandler.getCall(0).args[1].fileInfoModel).toEqual(testFileInfo);
|
||||
});
|
||||
it('triggers "Favorite" action when clicking on the star', function() {
|
||||
var actionHandler = sinon.stub();
|
||||
|
||||
fileActions.registerAction({
|
||||
name: 'Favorite',
|
||||
mime: 'all',
|
||||
permissions: OC.PERMISSION_READ,
|
||||
actionHandler: actionHandler
|
||||
});
|
||||
|
||||
view.setFileInfo(testFileInfo);
|
||||
|
||||
view.$el.find('.action-favorite').click();
|
||||
|
||||
expect(actionHandler.calledOnce).toEqual(true);
|
||||
expect(actionHandler.getCall(0).args[0]).toEqual('One.txt');
|
||||
expect(actionHandler.getCall(0).args[1].fileList).toEqual(fileList);
|
||||
expect(actionHandler.getCall(0).args[1].fileActions).toEqual(fileActions);
|
||||
expect(actionHandler.getCall(0).args[1].fileInfoModel).toEqual(testFileInfo);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -12,30 +12,15 @@
|
|||
var TEMPLATE =
|
||||
'<div>Owner: {{owner}}';
|
||||
|
||||
/**
|
||||
* @class OCA.Sharing.ShareTabView
|
||||
* @classdesc
|
||||
*
|
||||
* Displays sharing information
|
||||
*
|
||||
*/
|
||||
var ShareTabView = function(id) {
|
||||
this.initialize(id);
|
||||
};
|
||||
/**
|
||||
* @memberof OCA.Sharing
|
||||
*/
|
||||
ShareTabView.prototype = _.extend({}, OCA.Files.DetailTabView.prototype,
|
||||
var ShareTabView = OCA.Files.DetailTabView.extend(
|
||||
/** @lends OCA.Sharing.ShareTabView.prototype */ {
|
||||
_template: null,
|
||||
id: 'shareTabView',
|
||||
className: 'tab shareTabView',
|
||||
|
||||
/**
|
||||
* Initialize the details view
|
||||
*/
|
||||
initialize: function() {
|
||||
OCA.Files.DetailTabView.prototype.initialize.apply(this, arguments);
|
||||
this.$el.addClass('shareTabView');
|
||||
},
|
||||
_template: null,
|
||||
|
||||
getLabel: function() {
|
||||
return t('files_sharing', 'Sharing');
|
||||
|
@ -51,9 +36,9 @@
|
|||
this._template = Handlebars.compile(TEMPLATE);
|
||||
}
|
||||
|
||||
if (this._fileInfo) {
|
||||
if (this.model) {
|
||||
this.$el.append(this._template({
|
||||
owner: this._fileInfo.shareOwner || OC.currentUser
|
||||
owner: this.model.get('shareOwner') || OC.currentUser
|
||||
}));
|
||||
|
||||
} else {
|
||||
|
|
|
@ -642,7 +642,7 @@ var OC={
|
|||
$menuEl.show();
|
||||
$menuEl.trigger(new $.Event('afterShow'));
|
||||
// no animation
|
||||
if (_.isFunction()) {
|
||||
if (_.isFunction(complete)) {
|
||||
complete();
|
||||
}
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue