owncloud/core

View on GitHub
apps/files/js/fileactions.js

Summary

Maintainability
D
2 days
Test Coverage
/*
 * 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_FILE_ACTION_TRIGGER =
        '<a class="action action-{{nameLowerCase}}" href="#" data-action="{{name}}">' +
        '{{#if icon}}' +
            '<img class="svg" alt="{{altText}}" src="{{icon}}" />' +
        '{{else}}' +
            '{{#if iconClass}}<span class="icon {{iconClass}}" />{{/if}}' +
            '{{#unless hasDisplayName}}<span class="hidden-visually">{{altText}}</span>{{/unless}}' +
        '{{/if}}' +
        '{{#if displayName}}<span> {{displayName}}</span>{{/if}}' +
        '</a>';

    /**
     * Construct a new FileActions instance
     * @constructs FileActions
     * @memberof OCA.Files
     */
    var FileActions = function() {
        this.initialize();
    };
    FileActions.TYPE_DROPDOWN = 0;
    FileActions.TYPE_INLINE = 1;
    FileActions.prototype = {
        /** @lends FileActions.prototype */
        actions: {},
        defaults: {},
        icons: {},

        _actionFilters: [],

        /**
         * @deprecated
         */
        currentFile: null,

        /**
         * Dummy jquery element, for events
         */
        $el: null,

        /**
         * List of handlers to be notified whenever a register() or
         * setDefault() was called.
         *
         * @member {Function[]}
         */
        _updateListeners: {},

        _fileActionTriggerTemplate: null,

        /**
         * @private
         */
        initialize: function() {
            this.clear();
            // 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);
        },

        /**
         * Adds an event handler
         *
         * @param {String} eventName event name
         * @param {Function} callback
         */
        on: function(eventName, callback) {
            this.$el.on(eventName, callback);
        },

        /**
         * Removes an event handler
         *
         * @param {String} eventName event name
         * @param Function callback
         */
        off: function(eventName, callback) {
            this.$el.off(eventName, callback);
        },

        /**
         * Notifies the event handlers
         *
         * @param {String} eventName event name
         * @param {Object} data data
         */
        _notifyUpdateListeners: function(eventName, data) {
            this.$el.trigger(new $.Event(eventName, data));
        },

        /**
         * Merges the actions from the given fileActions into
         * this instance.
         *
         * @param {OCA.Files.FileActions} fileActions instance of OCA.Files.FileActions
         */
        merge: function(fileActions) {
            var self = this;
            // merge first level to avoid unintended overwriting
            _.each(fileActions.actions, function(sourceMimeData, mime) {
                var targetMimeData = self.actions[mime];
                if (!targetMimeData) {
                    targetMimeData = {};
                }
                self.actions[mime] = _.extend(targetMimeData, sourceMimeData);
            });

            this.defaults = _.extend(this.defaults, fileActions.defaults);
            this.icons = _.extend(this.icons, fileActions.icons);
        },
        /**
         * @deprecated use #registerAction() instead
         */
        register: function(mime, name, permissions, icon, action, displayName) {
            return this.registerAction({
                name: name,
                mime: mime,
                permissions: permissions,
                icon: icon,
                actionHandler: action,
                displayName: displayName || name
            });
        },

        /**
         * Register action
         *
         * @param {OCA.Files.FileAction} action object
         */
        registerAction: function (action) {
            var mime = action.mime;
            var name = action.name;
            var actionSpec = {
                action: action.actionHandler,
                name: name,
                displayName: action.displayName,
                mime: mime,
                order: action.order || 0,
                icon: action.icon,
                iconClass: action.iconClass,
                permissions: action.permissions,
                type: action.type || FileActions.TYPE_DROPDOWN,
                altText: action.altText || ''
            };
            if (_.isUndefined(action.displayName)) {
                actionSpec.displayName = t('files', name);
            }
            if (_.isFunction(action.render)) {
                actionSpec.render = action.render;
            }
            if (!this.actions[mime]) {
                this.actions[mime] = {};
            }
            this.actions[mime][name] = actionSpec;
            this.icons[name] = action.icon;
            this._notifyUpdateListeners('registerAction', {action: action});
        },
        /**
         * Clears all registered file actions.
         */
        clear: function() {
            this.actions = {};
            this.defaults = {};
            this.icons = {};
            this.currentFile = null;
            this._updateListeners = [];
            this._actionFilters = [];
        },
        /**
         * Adds an app to the list of default actions for a given mime type.
         *
         * @param {String} mime mime type
         * @param {String} name action name
         */
        setDefault: function (mime, name) {
            if (!this.defaults[mime]) {
                this.defaults[mime] = [];
            }
            if (this.defaults[mime].indexOf(name) < 0) {
                this.defaults[mime].push(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 = {};
            $.each(actions, function (name, action) {
                filteredActions[name] = action.action;
            });
            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 {Object.<OCA.Files.FileAction>} array of action specs
         */
        getActions: function (mime, type, permissions) {
            var actions = {};

            if (this.actions.all) {
                actions = $.extend(actions, this.actions.all);
            }
            if (type) {//type is 'dir' or 'file'
                if (this.actions[type]) {
                    actions = $.extend(actions, this.actions[type]);
                }
            }
            if (mime) {
                var mimePart = mime.substr(0, mime.indexOf('/'));
                if (this.actions[mimePart]) {
                    actions = $.extend(actions, this.actions[mimePart]);
                }
                if (this.actions[mime]) {
                    actions = $.extend(actions, this.actions[mime]);
                }
            }
            var filteredActions = {};
            $.each(actions, function (name, action) {
                if (action.permissions & permissions) {
                    filteredActions[name] = action;
                }
            });
            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 getDefaultFileActions instead
         */
        getDefault: function (mime, type, permissions) {
            var defaultActionSpec = this.getDefaultFileAction(mime, type, permissions);
            if (defaultActionSpec) {
                return defaultActionSpec.action;
            }
            return undefined;
        },

        /**
         * Returns the last 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
         *
         * @deprecated use getDefaultFileActions instead
         */
        getDefaultFileAction: function(mime, type, permissions) {
            const defaultActions = this.getDefaultFileActions(mime, type, permissions);
            if (!defaultActions.length) {
                return undefined;
            }
            return defaultActions[defaultActions.length - 1];
        },

        /**
         * Returns all default file action handlers for the given conditions
         *
         * @param {string} mime mime type
         * @param {string} type "dir" or "file"
         * @param {int} permissions permissions
         *
         * @return {Array.<OCA.Files.FileActions~actionHandler>} action handlers
         * @since 10.9
         */
         getDefaultFileActions: function(mime, type, permissions) {
            var mimePart;
            if (mime) {
                mimePart = mime.substr(0, mime.indexOf('/'));
            }

            var defaultNames = [];
            if (mime && this.defaults[mime]) {
                defaultNames = defaultNames.concat(this.defaults[mime]);
            }

            if (mimePart && this.defaults[mimePart]) {
                defaultNames = defaultNames.concat(this.defaults[mimePart]);
            }

            if (type && this.defaults[type]) {
                defaultNames = defaultNames.concat(this.defaults[type]);
            }

            if (!defaultNames.length) {
                if (!this.defaults.all) {
                    return [];
                }

                defaultNames = this.defaults.all;
            }

            var actions = this.getActions(mime, type, permissions);
            var defaultActions = [];
            _.each(actions, function (action) {
                if (defaultNames.indexOf(action.name) >= 0) {
                    defaultActions.push(action)
                }
            });
            return defaultActions;
        },

        /**
         * Default function to render actions
         *
         * @param {OCA.Files.FileAction} actionSpec file action spec
         * @param {boolean} isDefault true if the action is a default one,
         * false otherwise
         * @param {OCA.Files.FileActionContext} context action context
         */
        _defaultRenderAction: function(actionSpec, isDefault, context) {
            if (!isDefault) {
                var params = {
                    name: actionSpec.name,
                    nameLowerCase: actionSpec.name.toLowerCase(),
                    displayName: actionSpec.displayName,
                    icon: actionSpec.icon,
                    iconClass: actionSpec.iconClass,
                    altText: actionSpec.altText,
                    hasDisplayName: !!actionSpec.displayName
                };
                if (_.isFunction(actionSpec.icon)) {
                    params.icon = actionSpec.icon(context.$file.attr('data-file'), context);
                }
                if (_.isFunction(actionSpec.iconClass)) {
                    params.iconClass = actionSpec.iconClass(context.$file.attr('data-file'), context);
                }

                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 {Object} params action params
         */
        _makeActionLink: function(params) {
            if (!this._fileActionTriggerTemplate) {
                this._fileActionTriggerTemplate = Handlebars.compile(TEMPLATE_FILE_ACTION_TRIGGER);
            }

            return $(this._fileActionTriggerTemplate(params));
        },

        /**
         * Displays the file actions dropdown menu
         *
         * @param {string} fileName file name
         * @param {OCA.Files.FileActionContext} context rendering context
         */
        _showMenu: function(fileName, context) {
            var menu;
            var $trigger = context.$file.closest('tr').find('.fileactions .action-menu');
            $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');
                menu.remove();
            });

            context.$file.addClass('mouseOver');
            menu.show(context);
        },

        /**
         * Renders the menu trigger on the given file list row
         * 
         * @param {Object} $tr file list row element
         * @param {OCA.Files.FileActionContext} context rendering context
         */
        _renderMenuTrigger: function($tr, context) {
            // remove previous
            $tr.find('.action-menu').remove();

            var $el = this._renderInlineAction({
                name: 'menu',
                displayName: '',
                iconClass: 'icon-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.
         *
         * @param {OCA.Files.FileAction} actionSpec file action to render
         * @param {boolean} isDefault true if the action is a default action,
         * false otherwise
         * @param {OCA.Files.FileActionContext} context rendering 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.on(
                'click', {
                    a: null
                },
                function(event) {
                    context.fileList._setCurrentRow(context.$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;

                    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($file);
                        if (!callContext.fileInfoModel) {
                            console.warn('No file info model found for file "' + fileName + '"');
                        }
                    }

                    actionSpec.action(
                        fileName,
                        callContext
                    );
                }
            );
            $actionEl.tooltip({placement:'top'});
            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
         * @param triggerEvent if true, triggers the fileActionsReady on the file
         * list afterwards (false by default)
         * @param fileList OCA.Files.FileList instance on which the action is
         * done, defaults to OCA.Files.App.fileList
         */
        display: function (parent, triggerEvent, fileList) {
            if (!fileList) {
                console.warn('FileActions.display() MUST be called with a OCA.Files.FileList instance');
                return;
            }
            this.currentFile = parent;
            var self = this;
            var $tr = parent.closest('tr');
            var actions = this.getActions(
                this.getCurrentMimeType(),
                this.getCurrentType(),
                this.getCurrentPermissions()
            );

            var context = {
                $file: $tr,
                fileActions: this,
                fileList: fileList
            };

            actions = this._advancedFilter(actions, context);
            var nameLinks;
            if ($tr.data('renaming')) {
                return;
            }

            // recreate fileactions container
            nameLinks = parent.children('a.name');
            nameLinks.find('.fileactions, .nametext .action').remove();
            nameLinks.append('<span class="fileactions" />');
            var defaultActions = this.getDefaultFileActions(
                this.getCurrentMimeType(),
                this.getCurrentType(),
                this.getCurrentPermissions()
            );
            var defaultActionNames = _.map(defaultActions, function(actionSpec) {
                return actionSpec.name;
            });
            var hasDropDownActions = false;
            $.each(actions, function (name, actionSpec) {
                var isDefault = defaultActionNames.indexOf(actionSpec.name) >= 0;
                if (actionSpec.type && actionSpec.type === FileActions.TYPE_INLINE) {
                    self._renderInlineAction(
                        actionSpec,
                        isDefault,
                        context
                    );
                }
                if (!isDefault && (!actionSpec.type || actionSpec.type === FileActions.TYPE_DROPDOWN)) {
                    hasDropDownActions = true;
                }
            });

            if (hasDropDownActions) {
                this._renderMenuTrigger($tr, context);
            }

            if (triggerEvent){
                fileList.$fileList.trigger(jQuery.Event("fileActionsReady", {fileList: fileList, $files: $tr}));
            }
        },
        getCurrentFile: function () {
            return this.currentFile.parent().attr('data-file');
        },
        getCurrentMimeType: function () {
            return this.currentFile.parent().attr('data-mime');
        },
        getCurrentType: function () {
            return this.currentFile.parent().attr('data-type');
        },
        getCurrentPermissions: function () {
            return this.currentFile.parent().data('permissions');
        },

        /**
         * Register the actions that are used by default for the files app.
         * @param {['Download', 'Rename', 'Delete']|null} [excludeActions] exclude actions
         */
        registerDefaultActions: function (excludeActions) {
            excludeActions = excludeActions || [];

            var self = this;
            var defaultActions = [
                {
                    name: 'Download',
                    displayName: t('files', 'Download'),
                    order: -20,
                    mime: 'all',
                    permissions: OC.PERMISSION_READ,
                    iconClass: 'icon-download',
                    actionHandler: function (filename, context) {
                        var dir = context.dir || context.fileList.getCurrentDirectory();
                        var isDir = context.$file.attr('data-type') === 'dir';
                        var url = context.fileList.getDownloadUrl(filename, dir, isDir);

                        var downloadFileaction = $(context.$file).find('.fileactions .action-download');

                        // don't allow a second click on the download action
                        if (downloadFileaction.hasClass('disabled')) {
                            return;
                        }

                        if (url) {
                            var disableLoadingState = function () {
                                context.fileList.showFileBusyState(filename, false);
                            };

                            context.fileList.showFileBusyState(filename, true);
                            OCA.Files.Files.handleDownload(url, disableLoadingState);
                        }
                    }
                },
                {
                    name: 'Rename',
                    displayName: t('files', 'Rename'),
                    mime: 'all',
                    order: -30,
                    permissions: OC.PERMISSION_UPDATE,
                    iconClass: 'icon-rename',
                    actionHandler: function (filename, context) {
                        context.fileList.rename(filename);
                    }
                },
                {
                    name: 'Delete',
                    displayName: function (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');
                        }
                        return deleteTitle;
                    },
                    mime: 'all',
                    order: 1000,
                    // permission is READ because we show a hint instead if there is no permission
                    permissions: OC.PERMISSION_DELETE,
                    iconClass: 'icon-delete',
                    actionHandler: function (fileName, context) {
                        // if there is no permission to delete do nothing
                        if ((context.$file.data('permissions') & OC.PERMISSION_DELETE) === 0) {
                            return;
                        }
                        context.fileList.do_delete(fileName, context.dir);
                        $('.tipsy').remove();
                    }
                }
            ];


            defaultActions.forEach(function (action) {
                if (excludeActions.indexOf(action.name) === -1) {
                    self.registerAction(action);
                }
            });

            this.register('dir', 'Open', OC.PERMISSION_READ, '', function (filename, context) {
                var dir = context.$file.attr('data-path') || context.fileList.getCurrentDirectory();
                context.fileList.changeDirectory(OC.joinPaths(dir, filename), true, false, parseInt(context.$file.attr('data-id'), 10));
            });


            this.setDefault('dir', 'Open');
        },

        /**
         * To be overridden by subclasses to add additional filtering of actions based
         * on file properties
         *
         * @param {String.<String,OCA.Files.FileAction>} actions action list with action name as string
         * @param {OCA.Files.FileActionContext} context context
         */
        _advancedFilter: function(actions, context) {
            for (var i = 0; i < this._actionFilters.length; i++) {
                actions = this._actionFilters[i](actions, context);
            }

            return actions;
        },

        /**
         * Adds a callback to filter actions
         *
         * @param {Function} callback callback
         */
        addAdvancedFilter: function(callback) {
            this._actionFilters.push(callback);
        }
    };

    OCA.Files.FileActions = FileActions;

    /**
     * Replaces the download icon with a loading spinner and vice versa
     * - also adds the class disabled to the passed in element
     *
     * @param {jQuery} $downloadButtonElement download fileaction
     * @param {boolean} showIt whether to show the spinner(true) or to hide it(false)
     */
    OCA.Files.FileActions.updateFileActionSpinner = function($downloadButtonElement, showIt) {
        var $icon = $downloadButtonElement.find('.icon');
        if (showIt) {
            var $loadingIcon = $('<span class="icon icon-loading-small"></span>');
            $icon.after($loadingIcon);
            $icon.addClass('hidden');
        } else {
            $downloadButtonElement.find('.icon-loading-small').remove();
            $downloadButtonElement.find('.icon').removeClass('hidden');
        }
    };

    /**
     * File action attributes.
     *
     * @todo make this a real class in the future
     * @typedef {Object} OCA.Files.FileAction
     *
     * @property {String} name identifier of the action
     * @property {(String|OCA.Files.FileActions~displayNameFunction)} displayName
     * display name string for the action, or function that returns the display name.
     * Defaults to the name given in name property
     * @property {String} mime mime type
     * @property {int} permissions permissions
     * @property {(Function|String)} icon icon path to the icon or function that returns it (deprecated, use iconClass instead)
     * @property {(Function|String)} iconClass class name of the icon (recommended for theming)
     * @property {OCA.Files.FileActions~renderActionFunction} [render] optional rendering function
     * @property {OCA.Files.FileActions~actionHandler} actionHandler action handler function
     */

    /**
     * File action context attributes.
     *
     * @typedef {Object} OCA.Files.FileActionContext
     *
     * @property {Object} $file jQuery file row element
     * @property {OCA.Files.FileActions} fileActions file actions object
     * @property {OCA.Files.FileList} fileList file list object
     * @property {String} dir file dir
     */

    /**
     * Render function for actions.
     * The function must render a link element somewhere in the DOM
     * and return it. The function should NOT register the event handler
     * as this will be done after the link was returned.
     *
     * @callback OCA.Files.FileActions~renderActionFunction
     * @param {OCA.Files.FileAction} actionSpec action definition
     * @param {Object} $row row container
     * @param {boolean} isDefault true if the action is the default one,
     * false otherwise
     * @return {Object} jQuery link object
     */

    /**
     * Display name function for actions.
     * The function returns the display name of the action using
     * the given context information..
     *
     * @callback OCA.Files.FileActions~displayNameFunction
     * @param {OCA.Files.FileActionContext} context action context
     * @return {String} display name
     */

    /**
     * Action handler function for file actions
     *
     * @callback OCA.Files.FileActions~actionHandler
     * @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 {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
     */

    // global file actions to be used by all lists
    OCA.Files.fileActions = new OCA.Files.FileActions();
    OCA.Files.legacyFileActions = new OCA.Files.FileActions();

    // for backward compatibility
    // 
    // legacy apps are expecting a stateful global FileActions object to register
    // their actions on. Since legacy apps are very likely to break with other
    // FileList views than the main one ("All files"), actions registered
    // through window.FileActions will be limited to the main file list.
    // @deprecated use OCA.Files.FileActions instead
    window.FileActions = OCA.Files.legacyFileActions;
    window.FileActions.register = function (mime, name, permissions, icon, action, displayName) {
        console.warn('FileActions.register() is deprecated, please use OCA.Files.fileActions.register() instead', arguments);
        OCA.Files.FileActions.prototype.register.call(
                window.FileActions, mime, name, permissions, icon, action, displayName
        );
    };
    window.FileActions.display = function (parent, triggerEvent, fileList) {
        fileList = fileList || OCA.Files.App.fileList;
        console.warn('FileActions.display() is deprecated, please use OCA.Files.fileActions.register() which automatically redisplays actions', mime, name);
        OCA.Files.FileActions.prototype.display.call(window.FileActions, parent, triggerEvent, fileList);
    };
})();