/// <reference path="../../../js/knockout.d.ts" />
/// <reference path="../../../js/lodash-3.10.d.ts" />
/// <reference path="../../../js/common.d.ts" />
/// <reference path="../../../js/actor-manager.ts" />
/// <reference path="../../../js/jquery.d.ts" />
/// <reference path="../../../js/jqueryui.d.ts" />
/// <reference path="../../../modules/actor-selector/actor-selector.ts" />
/// <reference path="../../ko-dialog-bindings.ts" />
var __extends = (this && this.__extends) || (function () {
    var extendStatics = function (d, b) {
        extendStatics = Object.setPrototypeOf ||
            ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
            function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
        return extendStatics(d, b);
    };
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
var RexPermission = /** @class */ (function () {
    function RexPermission(editor, capability) {
        this.readableAction = null;
        this.mainDescription = '';
        this.isRedundant = false;
        this.editor = editor;
        this.capability = capability;
        var self = this;
        this.labelHtml = ko.pureComputed({
            read: self.getLabelHtml,
            deferEvaluation: true,
            owner: this
        });
        //Prevent freezing when entering a search query. Highlighting keywords in hundreds of capabilities can be slow.
        this.labelHtml.extend({ rateLimit: { timeout: 50, method: "notifyWhenChangesStop" } });
        this.isVisible = ko.computed({
            read: function () {
                if (!editor.capabilityMatchesFilters(self.capability)) {
                    return false;
                }
                //When in list view, check if the capability is inside the selected category.
                if (editor.categoryViewMode() === RexRoleEditor.listView) {
                    if (!editor.isInSelectedCategory(self.capability.name)) {
                        return false;
                    }
                }
                if (self.capability.isDeleted()) {
                    return false;
                }
                return !(self.isRedundant && !editor.showRedundantEnabled());
            },
            owner: this,
            deferEvaluation: true
        });
    }
    RexPermission.prototype.getLabelHtml = function () {
        var text;
        if ((this.readableAction !== null) && this.editor.readableNamesEnabled()) {
            text = this.readableAction;
        }
        else {
            text = this.capability.displayName();
        }
        var html = wsAmeLodash.escape(text);
        if (this.isVisible()) {
            html = this.editor.highlightSearchKeywords(html);
        }
        //Let the browser break words on underscores.
        html = html.replace(/_/g, '_<wbr>');
        return html;
    };
    return RexPermission;
}());
/**
 * A basic representation of any component or extension that can add new capabilities.
 * This includes plugins, themes, and the WordPress core.
 */
var RexWordPressComponent = /** @class */ (function () {
    function RexWordPressComponent(id, name) {
        this.id = id;
        this.name = name;
    }
    RexWordPressComponent.fromJs = function (id, details) {
        var instance = new RexWordPressComponent(id, details.name ? details.name : id);
        if (details.capabilityDocumentationUrl) {
            instance.capabilityDocumentationUrl = details.capabilityDocumentationUrl;
        }
        return instance;
    };
    return RexWordPressComponent;
}());
var RexObservableCapabilityMap = /** @class */ (function () {
    function RexObservableCapabilityMap(initialCapabilities) {
        this.capabilities = {};
        if (initialCapabilities) {
            this.initialCapabilities = wsAmeLodash.clone(initialCapabilities);
        }
        else {
            this.initialCapabilities = {};
        }
    }
    RexObservableCapabilityMap.prototype.getCapabilityState = function (capabilityName) {
        var observable = this.getObservable(capabilityName);
        return observable();
    };
    RexObservableCapabilityMap.prototype.setCapabilityState = function (capabilityName, state) {
        var observable = this.getObservable(capabilityName);
        observable(state);
    };
    RexObservableCapabilityMap.prototype.getAllCapabilities = function () {
        var _ = wsAmeLodash;
        var result = this.initialCapabilities ? _.clone(this.initialCapabilities) : {};
        _.forEach(this.capabilities, function (observable, name) {
            var isGranted = observable();
            if (isGranted === null) {
                delete result[name];
            }
            else {
                result[name] = isGranted;
            }
        });
        return result;
    };
    RexObservableCapabilityMap.prototype.getObservable = function (capabilityName) {
        if (!this.capabilities.hasOwnProperty(capabilityName)) {
            var initialValue = null;
            if (this.initialCapabilities.hasOwnProperty(capabilityName)) {
                initialValue = this.initialCapabilities[capabilityName];
            }
            this.capabilities[capabilityName] = ko.observable(initialValue);
        }
        return this.capabilities[capabilityName];
    };
    return RexObservableCapabilityMap;
}());
var RexBaseActor = /** @class */ (function () {
    function RexBaseActor(id, name, displayName, capabilities) {
        this.canHaveRoles = false;
        this.id = ko.observable(id);
        this.name = ko.observable(name);
        this.displayName = ko.observable(displayName);
        this.capabilities = new RexObservableCapabilityMap(capabilities || {});
    }
    RexBaseActor.prototype.hasCap = function (capability) {
        return (this.capabilities.getCapabilityState(capability) === true);
    };
    RexBaseActor.prototype.getCapabilityState = function (capability) {
        return this.getOwnCapabilityState(capability);
    };
    RexBaseActor.prototype.getOwnCapabilityState = function (capability) {
        return this.capabilities.getCapabilityState(capability);
    };
    RexBaseActor.prototype.setCap = function (capability, enabled) {
        this.capabilities.setCapabilityState(capability, enabled);
    };
    RexBaseActor.prototype.deleteCap = function (capability) {
        this.capabilities.setCapabilityState(capability, null);
    };
    RexBaseActor.prototype.getDisplayName = function () {
        return this.displayName();
    };
    RexBaseActor.prototype.getId = function () {
        return this.id();
    };
    /**
     * Get capabilities that are explicitly assigned/denied to this actor.
     * Does not include capabilities that a user inherits from their role(s).
     */
    RexBaseActor.prototype.getOwnCapabilities = function () {
        return this.capabilities.getAllCapabilities();
    };
    return RexBaseActor;
}());
var RexRole = /** @class */ (function (_super) {
    __extends(RexRole, _super);
    function RexRole(name, displayName, capabilities) {
        var _this = _super.call(this, 'role:' + name, name, displayName, capabilities) || this;
        _this.hasUsers = false;
        return _this;
    }
    RexRole.fromRoleData = function (data) {
        var role = new RexRole(data.name, data.displayName, data.capabilities);
        role.hasUsers = data.hasUsers;
        return role;
    };
    /**
     * Is this one of the default roles that are part of WordPress core?
     *
     * Note: I'm calling this property "built-in" instead of "default" to distinguish it
     * from the default role for new users.
     */
    RexRole.prototype.isBuiltIn = function () {
        return RexRole.builtInRoleNames.indexOf(this.name()) >= 0;
    };
    RexRole.prototype.toJs = function () {
        return {
            name: this.name(),
            displayName: this.displayName(),
            capabilities: this.getOwnCapabilities()
        };
    };
    RexRole.builtInRoleNames = ['administrator', 'editor', 'author', 'subscriber', 'contributor'];
    return RexRole;
}(RexBaseActor));
var RexSuperAdmin = /** @class */ (function (_super) {
    __extends(RexSuperAdmin, _super);
    function RexSuperAdmin() {
        return _super.call(this, 'special:super_admin', 'Super Admin', 'Super Admin') || this;
    }
    RexSuperAdmin.getInstance = function () {
        if (RexSuperAdmin.instance === null) {
            RexSuperAdmin.instance = new RexSuperAdmin();
        }
        return RexSuperAdmin.instance;
    };
    RexSuperAdmin.instance = null;
    return RexSuperAdmin;
}(RexBaseActor));
var RexUser = /** @class */ (function (_super) {
    __extends(RexUser, _super);
    function RexUser(login, displayName, capabilities, userId) {
        var _this = _super.call(this, 'user:' + login, login, displayName, capabilities) || this;
        _this.isSuperAdmin = false;
        _this.userLogin = login;
        _this.canHaveRoles = true;
        _this.roles = ko.observableArray([]);
        _this.userId = userId;
        return _this;
    }
    RexUser.prototype.hasCap = function (capability, outGrantedBy) {
        return (this.getCapabilityState(capability, outGrantedBy) === true);
    };
    RexUser.prototype.getCapabilityState = function (capability, outGrantedBy) {
        if (capability === 'do_not_allow') {
            return false;
        }
        if (this.isSuperAdmin) {
            if (outGrantedBy) {
                outGrantedBy.push(RexSuperAdmin.getInstance());
            }
            return (capability !== 'do_not_allow');
        }
        var result = _super.prototype.getCapabilityState.call(this, capability);
        if (result !== null) {
            if (outGrantedBy) {
                outGrantedBy.push(this);
            }
            return result;
        }
        wsAmeLodash.each(this.roles(), function (role) {
            var roleHasCap = role.getCapabilityState(capability);
            if (roleHasCap !== null) {
                if (outGrantedBy) {
                    outGrantedBy.push(role);
                }
                result = roleHasCap;
            }
        });
        return result;
    };
    // noinspection JSUnusedGlobalSymbols Used in KO templates.
    RexUser.prototype.getInheritanceDetails = function (capability) {
        var _ = wsAmeLodash;
        var results = [];
        //Note: Alternative terms include "Assigned", "Granted", "Yes"/"No".
        if (this.isSuperAdmin) {
            var superAdmin = RexSuperAdmin.getInstance();
            var description_1 = 'Allow everything';
            if (capability.name === 'do_not_allow') {
                description_1 = 'Deny';
            }
            results.push({
                actor: superAdmin,
                name: superAdmin.displayName(),
                description: description_1
            });
        }
        _.each(this.roles(), function (role) {
            var roleHasCap = role.getCapabilityState(capability.name);
            var description;
            if (roleHasCap) {
                description = 'Allow';
            }
            else if (roleHasCap === null) {
                description = '—';
            }
            else {
                description = 'Deny';
            }
            results.push({
                actor: role,
                name: role.displayName(),
                description: description,
            });
        });
        var hasOwnCap = _super.prototype.getCapabilityState.call(this, capability.name);
        var description;
        if (hasOwnCap) {
            description = 'Allow';
        }
        else if (hasOwnCap === null) {
            description = '—';
        }
        else {
            description = 'Deny';
        }
        results.push({
            actor: this,
            name: 'User-specific setting',
            description: description,
        });
        var relevantActors = [];
        this.getCapabilityState(capability.name, relevantActors);
        var decidingActor = _.last(relevantActors);
        _.each(results, function (item) {
            item.isDecisive = (item.actor === decidingActor);
        });
        return results;
    };
    RexUser.fromAmeUser = function (data, editor) {
        var user = new RexUser(data.userLogin, data.displayName, data.capabilities, data.userId);
        wsAmeLodash.forEach(data.roles, function (roleId) {
            var role = editor.getRole(roleId);
            if (role) {
                user.roles.push(role);
            }
        });
        return user;
    };
    RexUser.fromAmeUserProperties = function (properties, editor) {
        var user = new RexUser(properties.user_login, properties.display_name, properties.capabilities);
        if (properties.id) {
            user.userId = properties.id;
        }
        wsAmeLodash.forEach(properties.roles, function (roleId) {
            var role = editor.getRole(roleId);
            if (role) {
                user.roles.push(role);
            }
        });
        return user;
    };
    RexUser.prototype.toJs = function () {
        var _ = wsAmeLodash;
        var roles = _.invoke(this.roles(), 'name');
        return {
            userId: this.userId,
            userLogin: this.userLogin,
            displayName: this.displayName(),
            capabilities: this.getOwnCapabilities(),
            roles: roles
        };
    };
    return RexUser;
}(RexBaseActor));
var RexCategory = /** @class */ (function () {
    function RexCategory(name, editor, slug, capabilities) {
        var _this = this;
        if (slug === void 0) { slug = null; }
        if (capabilities === void 0) { capabilities = []; }
        this.slug = null;
        this.origin = null;
        this.subtitle = null;
        this.htmlId = null;
        this.parent = null;
        this.subcategories = [];
        this.duplicates = [];
        var _ = wsAmeLodash;
        var self = this;
        this.editor = editor;
        this.name = name;
        this.slug = slug;
        if ((this.slug !== null) && (this.slug !== '')) {
            editor.categoriesBySlug[this.slug] = this;
        }
        var initialPermissions = _.map(capabilities, function (capabilityName) {
            return new RexPermission(editor, editor.getCapability(capabilityName));
        });
        this.permissions = ko.observableArray(initialPermissions);
        this.sortPermissions();
        this.contentTemplate = ko.observable('rex-default-category-content-template');
        this.isSelected = ko.observable(false);
        this.enabledCapabilityCount = ko.pureComputed({
            read: function () {
                return self.countUniqueCapabilities({}, function (capability) {
                    return capability.isEnabledForSelectedActor();
                });
            },
            deferEvaluation: true,
            owner: this
        });
        this.enabledCapabilityCount.extend({ rateLimit: { timeout: 5, method: "notifyWhenChangesStop" } });
        this.totalCapabilityCount = ko.pureComputed({
            read: function () {
                return self.countUniqueCapabilities();
            },
            deferEvaluation: true,
            owner: this
        });
        this.isCapCountVisible = ko.pureComputed({
            read: function () {
                if (!editor.showNumberOfCapsEnabled()) {
                    return false;
                }
                var totalCaps = self.totalCapabilityCount(), enabledCaps = self.enabledCapabilityCount();
                if (!editor.showZerosEnabled() && ((totalCaps === 0) || (enabledCaps === 0))) {
                    return false;
                }
                return editor.showTotalCapCountEnabled() || self.isEnabledCapCountVisible();
            },
            deferEvaluation: true,
            owner: this
        });
        this.isEnabledCapCountVisible = ko.pureComputed({
            read: function () {
                if (!editor.showGrantedCapCountEnabled()) {
                    return false;
                }
                return (self.enabledCapabilityCount() > 0) || editor.showZerosEnabled();
            },
            deferEvaluation: true,
            owner: this
        });
        this.areAllPermissionsEnabled = ko.computed({
            read: function () {
                var items = self.permissions();
                var len = items.length;
                for (var i = 0; i < len; i++) {
                    if (!items[i].capability.isEnabledForSelectedActor() && items[i].capability.isEditable()) {
                        return false;
                    }
                }
                for (var i = 0; i < self.subcategories.length; i++) {
                    if (!self.subcategories[i].areAllPermissionsEnabled()) {
                        return false;
                    }
                }
                return true;
            },
            write: function (enabled) {
                var items = self.permissions();
                for (var i = 0; i < items.length; i++) {
                    var item = items[i];
                    if (item.capability.isEditable()) {
                        item.capability.isEnabledForSelectedActor(enabled);
                    }
                }
                for (var i = 0; i < self.subcategories.length; i++) {
                    self.subcategories[i].areAllPermissionsEnabled(enabled);
                }
            },
            deferEvaluation: true,
            owner: this
        });
        this.areAllPermissionsEnabled.extend({ rateLimit: { timeout: 5, method: 'notifyWhenChangesStop' } });
        this.areAnyPermissionsEditable = ko.pureComputed({
            read: function () {
                var items = self.permissions();
                var len = items.length;
                for (var i = 0; i < len; i++) {
                    if (items[i].capability.isEditable()) {
                        return true;
                    }
                }
                for (var i = 0; i < self.subcategories.length; i++) {
                    if (!self.subcategories[i].areAnyPermissionsEditable()) {
                        return true;
                    }
                }
                return false;
            },
            deferEvaluation: true,
            owner: this
        });
        this.areAnyPermissionsEditable.extend({ rateLimit: { timeout: 5, method: 'notifyWhenChangesStop' } });
        this.isVisible = ko.computed({
            read: function () {
                var visible = false;
                var hasVisibleSubcategories = false;
                _.forEach(self.subcategories, function (category) {
                    if (category.isVisible()) {
                        hasVisibleSubcategories = true;
                        return false;
                    }
                });
                //Hide it if not inside the selected category.
                var isInSelectedCategory = false, temp = self;
                while (temp !== null) {
                    if (temp.isSelected()) {
                        isInSelectedCategory = true;
                        break;
                    }
                    temp = temp.parent;
                }
                //In single-category view, the category also counts as "selected"
                //if one of its duplicates is selected.
                if (!isInSelectedCategory
                    && (self.duplicates.length > 0)
                    && (editor.categoryViewMode() === RexRoleEditor.singleCategoryView)) {
                    for (var i = 0; i < self.duplicates.length; i++) {
                        temp = self.duplicates[i];
                        while (temp !== null) {
                            if (temp.isSelected()) {
                                isInSelectedCategory = true;
                                break;
                            }
                            temp = temp.parent;
                        }
                        if (isInSelectedCategory) {
                            break;
                        }
                    }
                }
                if (!isInSelectedCategory && !hasVisibleSubcategories) {
                    return false;
                }
                //Stay visible as long as at least one subcategory or permission is visible.
                visible = hasVisibleSubcategories;
                _.forEach(self.permissions(), function (permission) {
                    if (permission.isVisible()) {
                        visible = true;
                        return false;
                    }
                });
                return visible;
            },
            deferEvaluation: true,
            owner: this,
        });
        this.isVisible.extend({
            rateLimit: {
                timeout: 10,
                method: 'notifyWhenChangesStop'
            }
        });
        this.desiredColumnCount = ko.computed({
            read: function () {
                var visiblePermissions = 0;
                _.forEach(self.permissions(), function (permission) {
                    if (permission.isVisible()) {
                        visiblePermissions++;
                    }
                });
                var minItemsPerColumn = 12;
                if (editor.categoryWidthMode() === 'full') {
                    minItemsPerColumn = 3;
                }
                var desiredColumns = Math.max(Math.ceil(visiblePermissions / minItemsPerColumn), 1);
                //Avoid situations where the last column has only one item (an orphan).
                if ((desiredColumns >= 2) && (visiblePermissions % minItemsPerColumn === 1)) {
                    desiredColumns--;
                }
                if (desiredColumns > 3) {
                    return 'max';
                }
                return desiredColumns.toString(10);
            },
            deferEvaluation: true
        });
        this.nestingDepth = ko.pureComputed({
            read: function () {
                if (self.parent !== null) {
                    return self.parent.nestingDepth() + 1;
                }
                return 1;
            },
            deferEvaluation: true
        });
        this.isNavExpanded = ko.observable((this.slug !== null) ? !editor.userPreferences.collapsedCategories.peek(this.slug) : true);
        if (this.slug) {
            this.isNavExpanded.subscribe(function (newValue) {
                editor.userPreferences.collapsedCategories.toggle(_this.slug, !newValue);
            });
        }
        this.isNavVisible = ko.pureComputed({
            read: function () {
                if (self.parent === null) {
                    return true;
                }
                return self.parent.isNavVisible() && self.parent.isNavExpanded();
                //Idea: We could hide it if all of the capabilities it contains have been deleted.
            },
            deferEvaluation: true
        });
        this.cssClasses = ko.computed({
            read: function () {
                var classes = [];
                if (self.subcategories.length > 0) {
                    classes.push('rex-has-subcategories');
                }
                if (self.parent) {
                    if (self.parent === editor.rootCategory) {
                        classes.push('rex-top-category');
                    }
                    else {
                        classes.push('rex-sub-category');
                    }
                }
                if (self.permissions().length > 0) {
                    classes.push('rex-desired-columns-' + self.desiredColumnCount());
                }
                return classes.join(' ');
            },
            deferEvaluation: true
        });
        this.navCssClasses = ko.pureComputed({
            read: function () {
                var classes = [];
                if (self.isSelected()) {
                    classes.push('rex-selected-nav-item');
                }
                if (self.isNavExpanded()) {
                    classes.push('rex-nav-is-expanded');
                }
                if (self.subcategories.length > 0) {
                    classes.push('rex-nav-has-children');
                }
                classes.push('rex-nav-level-' + self.nestingDepth());
                return classes.join(' ');
            },
            deferEvaluation: true
        });
        this.subcategoryModificationFlag = ko.observable(this.subcategories.length);
        this.sortedSubcategories = ko.pureComputed({
            read: function () {
                //Refresh the sorted list when categories are added or removed.
                _this.subcategoryModificationFlag();
                return _this.getSortedSubcategories();
            },
            deferEvaluation: true
        });
        this.navSubcategories = ko.pureComputed({
            read: function () {
                _this.subcategoryModificationFlag();
                return _this.subcategories;
            },
            deferEvaluation: true
        });
        this.subheading = ko.pureComputed({
            read: function () {
                return _this.getSubheadingItems().join(', ');
            },
            deferEvaluation: true
        });
    }
    RexCategory.prototype.addSubcategory = function (category, afterName) {
        category.parent = this;
        if (afterName) {
            var index = wsAmeLodash.findIndex(this.subcategories, { 'name': afterName });
            if (index > -1) {
                this.subcategories.splice(index + 1, 0, category);
                this.subcategoryModificationFlag(this.subcategories.length);
                return;
            }
        }
        this.subcategories.push(category);
        this.subcategoryModificationFlag(this.subcategories.length);
    };
    // noinspection JSUnusedGlobalSymbols Used in KO templates.
    RexCategory.prototype.toggleSubcategories = function () {
        this.isNavExpanded(!this.isNavExpanded());
    };
    RexCategory.prototype.getSortedSubcategories = function () {
        //In most cases, the subcategory list is already sorted either alphabetically or in a predefined order
        //chosen for specific category. Subcategories can override this method to change the sort order.
        return this.subcategories;
    };
    /**
     * Sort the permissions in this category. Doesn't affect subcategories.
     * The default sort is alphabetical, but subclasses can override this method to specify a custom order.
     */
    RexCategory.prototype.sortPermissions = function () {
        this.permissions.sort(function (a, b) {
            return a.capability.name.toLowerCase().localeCompare(b.capability.name.toLowerCase());
        });
    };
    RexCategory.prototype.countUniqueCapabilities = function (accumulator, predicate) {
        if (accumulator === void 0) { accumulator = {}; }
        if (predicate === void 0) { predicate = null; }
        var total = 0;
        var permissions = this.permissions();
        for (var i = 0; i < permissions.length; i++) {
            var capability = permissions[i].capability;
            if (accumulator.hasOwnProperty(capability.name)) {
                continue;
            }
            if (predicate && !predicate(capability)) {
                continue;
            }
            if (capability.isDeleted()) {
                continue;
            }
            accumulator[capability.name] = true;
            total++;
        }
        for (var i = 0; i < this.subcategories.length; i++) {
            total = total + this.subcategories[i].countUniqueCapabilities(accumulator, predicate);
        }
        return total;
    };
    RexCategory.prototype.findCategoryBySlug = function (slug) {
        if (this.editor.categoriesBySlug.hasOwnProperty(slug)) {
            return this.editor.categoriesBySlug[slug];
        }
        return null;
    };
    RexCategory.fromJs = function (details, editor) {
        var category;
        if (details.variant && details.variant === 'post_type') {
            category = new RexPostTypeCategory(details.name, editor, details.contentTypeId, details.slug, details.permissions);
        }
        else if (details.variant && details.variant === 'taxonomy') {
            category = new RexTaxonomyCategory(details.name, editor, details.contentTypeId, details.slug, details.permissions);
        }
        else {
            category = new RexCategory(details.name, editor, details.slug, details.capabilities);
        }
        if (details.componentId) {
            category.origin = editor.getComponent(details.componentId);
        }
        if (details.subcategories) {
            wsAmeLodash.forEach(details.subcategories, function (childDetails) {
                var subcategory = RexCategory.fromJs(childDetails, editor);
                category.addSubcategory(subcategory);
            });
        }
        return category;
    };
    RexCategory.prototype.usesBaseCapabilities = function () {
        return false;
    };
    RexCategory.prototype.getDeDuplicationKey = function () {
        var _a;
        var key = (_a = this.slug) !== null && _a !== void 0 ? _a : this.name;
        if (this.parent) {
            key = this.parent.getDeDuplicationKey() + '>' + key;
        }
        return key;
    };
    RexCategory.prototype.addDuplicate = function (category) {
        if (this.duplicates.indexOf(category) === -1) {
            this.duplicates.push(category);
        }
    };
    RexCategory.prototype.getSubheadingItems = function () {
        var items = [];
        if (this.parent !== null) {
            items.push(this.parent.name);
        }
        if (this.duplicates.length > 0) {
            for (var i = 0; i < this.duplicates.length; i++) {
                var category = this.duplicates[i];
                if (category.parent) {
                    items.push(category.parent.name);
                }
            }
        }
        return items;
    };
    RexCategory.prototype.getAbsoluteName = function () {
        var components = [this.name];
        var parent = this.parent;
        while (parent !== null) {
            components.unshift(parent.name);
            parent = parent.parent;
        }
        return components.join(' > ');
    };
    RexCategory.defaultSubcategoryComparison = function (a, b) {
        return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
    };
    return RexCategory;
}());
var RexContentTypeCategory = /** @class */ (function (_super) {
    __extends(RexContentTypeCategory, _super);
    function RexContentTypeCategory(name, editor, slug) {
        if (slug === void 0) { slug = null; }
        var _this = _super.call(this, name, editor, slug) || this;
        _this.actions = {};
        _this.baseCategorySlug = null;
        _this.isBaseCapNoticeVisible = ko.pureComputed({
            read: function () {
                if (editor.showBaseCapsEnabled()) {
                    return false;
                }
                return _this.usesBaseCapabilities();
            },
            deferEvaluation: true
        });
        return _this;
    }
    /**
     * Check if the post type or taxonomy represented by this category uses the same capabilities
     * as the built-in "post" type or the "category" taxonomy.
     */
    RexContentTypeCategory.prototype.usesBaseCapabilities = function () {
        var baseCategory = this.getBaseCategory();
        if (baseCategory === null || this === baseCategory) {
            return false;
        }
        var allCapsMatch = true;
        wsAmeLodash.forEach(this.actions, function (item) {
            var isMatch = item.action
                && baseCategory.actions.hasOwnProperty(item.action)
                && (item.capability === baseCategory.actions[item.action].capability);
            if (!isMatch) {
                allCapsMatch = false;
                return false;
            }
        });
        return allCapsMatch;
    };
    RexContentTypeCategory.prototype.getBaseCategory = function () {
        if (this.baseCategorySlug !== null) {
            var result = this.findCategoryBySlug(this.baseCategorySlug);
            if (result instanceof RexContentTypeCategory) {
                return result;
            }
        }
        return null;
    };
    return RexContentTypeCategory;
}(RexCategory));
var RexPostTypePermission = /** @class */ (function (_super) {
    __extends(RexPostTypePermission, _super);
    function RexPostTypePermission(editor, capability, action, pluralNoun) {
        if (pluralNoun === void 0) { pluralNoun = ''; }
        var _this = _super.call(this, editor, capability) || this;
        _this.action = action;
        _this.readableAction = wsAmeLodash.capitalize(_this.action.replace('_posts', '').replace('_', ' '));
        if (RexPostTypePermission.actionDescriptions.hasOwnProperty(action) && pluralNoun) {
            _this.mainDescription = RexPostTypePermission.actionDescriptions[action].replace('%s', pluralNoun);
        }
        return _this;
    }
    RexPostTypePermission.actionDescriptions = {
        'edit_and_create': 'Edit and create %s',
        'edit_posts': 'Edit %s',
        'create_posts': 'Create new %s',
        'edit_published_posts': 'Edit published %s',
        'edit_others_posts': 'Edit %s created by others',
        'edit_private_posts': 'Edit private %s created by others',
        'publish_posts': 'Publish %s',
        'read_private_posts': 'Read private %s',
        'delete_posts': 'Delete %s',
        'delete_published_posts': 'Delete published %s',
        'delete_others_posts': 'Delete %s created by others',
        'delete_private_posts': 'Delete private %s created by others',
    };
    return RexPostTypePermission;
}(RexPermission));
var RexPostTypeCategory = /** @class */ (function (_super) {
    __extends(RexPostTypeCategory, _super);
    function RexPostTypeCategory(name, editor, postTypeId, slug, permissions, isDefault) {
        if (slug === void 0) { slug = null; }
        if (isDefault === void 0) { isDefault = false; }
        var _this = _super.call(this, name, editor, slug) || this;
        _this.pluralLabel = '';
        _this.actions = {};
        var _ = wsAmeLodash;
        _this.baseCategorySlug = 'postTypes/post';
        _this.postType = postTypeId;
        _this.isDefault = isDefault;
        _this.subtitle = _this.postType;
        if (editor.postTypes[postTypeId].pluralLabel) {
            _this.pluralLabel = editor.postTypes[postTypeId].pluralLabel;
        }
        else {
            _this.pluralLabel = name.toLowerCase();
        }
        _this.permissions = ko.observableArray(_.map(permissions, function (capability, action) {
            var permission = new RexPostTypePermission(editor, editor.getCapability(capability), action, _this.pluralLabel);
            //The "read" capability is already shown in the core category and every role has it by default.
            if (capability === 'read') {
                permission.isRedundant = true;
            }
            _this.actions[action] = permission;
            return permission;
        }));
        _this.sortPermissions();
        //The "create" capability is often the same as the "edit" capability.
        var editPerm = _.get(_this.actions, 'edit_posts', null), createPerm = _.get(_this.actions, 'create_posts', null);
        if (editPerm && createPerm && (createPerm.capability.name === editPerm.capability.name)) {
            createPerm.isRedundant = true;
        }
        return _this;
    }
    RexPostTypeCategory.prototype.getDeDuplicationKey = function () {
        return 'postType:' + this.postType;
    };
    RexPostTypeCategory.prototype.sortPermissions = function () {
        this.permissions.sort(function (a, b) {
            var priorityA = RexPostTypeCategory.desiredActionOrder.hasOwnProperty(a.action) ? RexPostTypeCategory.desiredActionOrder[a.action] : 1000;
            var priorityB = RexPostTypeCategory.desiredActionOrder.hasOwnProperty(b.action) ? RexPostTypeCategory.desiredActionOrder[b.action] : 1000;
            var delta = priorityA - priorityB;
            if (delta !== 0) {
                return delta;
            }
            return a.capability.name.localeCompare(b.capability.name);
        });
    };
    RexPostTypeCategory.prototype.getSubheadingItems = function () {
        var items = _super.prototype.getSubheadingItems.call(this);
        items.push(this.postType);
        return items;
    };
    RexPostTypeCategory.desiredActionOrder = {
        'edit_posts': 1,
        'edit_others_posts': 2,
        'edit_published_posts': 3,
        'edit_private_posts': 4,
        'publish_posts': 5,
        'delete_posts': 6,
        'delete_others_posts': 7,
        'delete_published_posts': 8,
        'delete_private_posts': 9,
        'read_private_posts': 10,
        'create_posts': 11,
    };
    return RexPostTypeCategory;
}(RexContentTypeCategory));
var RexTaxonomyPermission = /** @class */ (function (_super) {
    __extends(RexTaxonomyPermission, _super);
    function RexTaxonomyPermission(editor, capability, action, pluralNoun) {
        if (pluralNoun === void 0) { pluralNoun = ''; }
        var _this = _super.call(this, editor, capability) || this;
        _this.action = action;
        _this.readableAction = wsAmeLodash.capitalize(_this.action.replace('_terms', '').replace('_', ' '));
        if (RexTaxonomyPermission.actionDescriptions.hasOwnProperty(action) && pluralNoun) {
            _this.mainDescription = RexTaxonomyPermission.actionDescriptions[action].replace('%s', pluralNoun);
        }
        return _this;
    }
    RexTaxonomyPermission.actionDescriptions = {
        'manage_terms': 'Manage %s',
        'edit_terms': 'Edit %s',
        'delete_terms': 'Delete %s',
        'assign_terms': 'Assign %s',
    };
    return RexTaxonomyPermission;
}(RexPermission));
var RexTaxonomyCategory = /** @class */ (function (_super) {
    __extends(RexTaxonomyCategory, _super);
    function RexTaxonomyCategory(name, editor, taxonomyId, slug, permissions) {
        if (slug === void 0) { slug = null; }
        var _this = _super.call(this, name, editor, slug) || this;
        _this.actions = {};
        var _ = wsAmeLodash;
        _this.baseCategorySlug = 'taxonomies/category';
        _this.taxonomy = taxonomyId;
        _this.subtitle = taxonomyId;
        var noun = name.toLowerCase();
        _this.permissions = ko.observableArray(_.map(permissions, function (capability, action) {
            var permission = new RexTaxonomyPermission(editor, editor.getCapability(capability), action, noun);
            _this.actions[action] = permission;
            return permission;
        }));
        _this.sortPermissions();
        //Permissions that use the same capability as the "manage_terms" permission are redundant.
        if (_this.actions.manage_terms) {
            var manageCap = _this.actions.manage_terms.capability.name;
            for (var action in _this.actions) {
                if (!_this.actions.hasOwnProperty(action)) {
                    continue;
                }
                if ((action !== 'manage_terms') && (_this.actions[action].capability.name === manageCap)) {
                    _this.actions[action].isRedundant = true;
                }
            }
        }
        return _this;
    }
    RexTaxonomyCategory.prototype.getDeDuplicationKey = function () {
        return 'taxonomy:' + this.taxonomy;
    };
    RexTaxonomyCategory.prototype.sortPermissions = function () {
        this.permissions.sort(function (a, b) {
            var priorityA = RexTaxonomyCategory.desiredActionOrder.hasOwnProperty(a.action) ? RexTaxonomyCategory.desiredActionOrder[a.action] : 1000;
            var priorityB = RexTaxonomyCategory.desiredActionOrder.hasOwnProperty(b.action) ? RexTaxonomyCategory.desiredActionOrder[b.action] : 1000;
            var delta = priorityA - priorityB;
            if (delta !== 0) {
                return delta;
            }
            return a.capability.name.localeCompare(b.capability.name);
        });
    };
    RexTaxonomyCategory.prototype.getSubheadingItems = function () {
        var items = _super.prototype.getSubheadingItems.call(this);
        items.push(this.taxonomy);
        return items;
    };
    RexTaxonomyCategory.desiredActionOrder = {
        'manage_terms': 1,
        'edit_terms': 2,
        'delete_terms': 3,
        'assign_terms': 4,
    };
    return RexTaxonomyCategory;
}(RexContentTypeCategory));
var RexTableViewCategory = /** @class */ (function (_super) {
    __extends(RexTableViewCategory, _super);
    function RexTableViewCategory(name, editor, slug) {
        if (slug === void 0) { slug = null; }
        var _this = _super.call(this, name, editor, slug) || this;
        _this.subcategoryComparisonCallback = null;
        _this.contentTemplate = ko.pureComputed(function () {
            if (editor.categoryViewMode() === RexRoleEditor.hierarchyView) {
                return 'rex-permission-table-template';
            }
            return 'rex-default-category-content-template';
        });
        _this.subcategoryComparisonCallback = RexCategory.defaultSubcategoryComparison;
        return _this;
    }
    RexTableViewCategory.prototype.getSortedSubcategories = function () {
        var _this = this;
        if (this.editor.showBaseCapsEnabled()) {
            return _super.prototype.getSortedSubcategories.call(this);
        }
        var cats = wsAmeLodash.clone(this.subcategories);
        return cats.sort(function (a, b) {
            //Special case: Put categories that use base capabilities at the end.
            var aEqualsBase = a.usesBaseCapabilities();
            var bEqualsBase = b.usesBaseCapabilities();
            if (aEqualsBase && !bEqualsBase) {
                return 1;
            }
            else if (!aEqualsBase && bEqualsBase) {
                return -1;
            }
            //Otherwise just sort in the default order.
            return _this.subcategoryComparisonCallback(a, b);
        });
    };
    /**
     * Sort the underlying category array.
     */
    RexTableViewCategory.prototype.sortSubcategories = function () {
        this.subcategories.sort(this.subcategoryComparisonCallback);
    };
    return RexTableViewCategory;
}(RexCategory));
var RexTaxonomyContainerCategory = /** @class */ (function (_super) {
    __extends(RexTaxonomyContainerCategory, _super);
    function RexTaxonomyContainerCategory(name, editor, slug) {
        if (slug === void 0) { slug = null; }
        var _this = _super.call(this, name, editor, slug) || this;
        _this.htmlId = 'rex-taxonomy-summary-category';
        _this.tableColumns = ko.pureComputed({
            read: function () {
                var _ = wsAmeLodash;
                var defaultTaxonomyActions = ['manage_terms', 'assign_terms', 'edit_terms', 'delete_terms'];
                var columns = [
                    {
                        title: 'Manage',
                        actions: ['manage_terms']
                    },
                    {
                        title: 'Assign',
                        actions: ['assign_terms']
                    },
                    {
                        title: 'Edit',
                        actions: ['edit_terms']
                    },
                    {
                        title: 'Delete',
                        actions: ['delete_terms']
                    }
                ];
                var misColumnExists = false, miscColumn = null;
                for (var i = 0; i < _this.subcategories.length; i++) {
                    var category = _this.subcategories[i];
                    if (!(category instanceof RexTaxonomyCategory)) {
                        continue;
                    }
                    //Display any unrecognized actions in a "Misc" column.
                    var customActions = _.omit(category.actions, defaultTaxonomyActions);
                    if (!_.isEmpty(customActions)) {
                        if (!misColumnExists) {
                            miscColumn = { title: 'Misc', actions: [] };
                            columns.push(miscColumn);
                        }
                        miscColumn.actions = _.union(miscColumn.actions, _.keys(customActions));
                    }
                }
                return columns;
            },
            deferEvaluation: true,
        });
        return _this;
    }
    return RexTaxonomyContainerCategory;
}(RexTableViewCategory));
var RexPostTypeContainerCategory = /** @class */ (function (_super) {
    __extends(RexPostTypeContainerCategory, _super);
    function RexPostTypeContainerCategory(name, editor, slug) {
        if (slug === void 0) { slug = null; }
        var _this = _super.call(this, name, editor, slug) || this;
        /* Note: This seems like poor design because the superclass overrides subclass
         * behaviour (subcategory comparison) in some situations. Unfortunately, I haven't
         * come up with anything better so far. Might be something to revisit later.
         */
        _this.subcategoryComparisonCallback = function (a, b) {
            //Special case: Put "Posts" at the top.
            if (a.postType === 'post') {
                return -1;
            }
            else if (b.postType === 'post') {
                return 1;
            }
            //Put other built-in post types above custom post types.
            if (a.isDefault && !b.isDefault) {
                return -1;
            }
            else if (b.isDefault && !a.isDefault) {
                return 1;
            }
            var labelA = a.name.toLowerCase(), labelB = b.name.toLowerCase();
            return labelA.localeCompare(labelB);
        };
        _this.tableColumns = ko.pureComputed({
            read: function () {
                var _ = wsAmeLodash;
                var defaultPostTypeActions = _.keys(RexPostTypePermission.actionDescriptions);
                var columns = [
                    {
                        title: 'Own items',
                        actions: ['create_posts', 'edit_posts', 'delete_posts', 'publish_posts', 'edit_published_posts', 'delete_published_posts']
                    },
                    {
                        title: 'Other\'s items',
                        actions: ['edit_others_posts', 'delete_others_posts', 'edit_private_posts', 'delete_private_posts', 'read_private_posts']
                    }
                ];
                var metaColumn = {
                    title: 'Meta',
                    actions: ['edit_post', 'delete_post', 'read_post']
                };
                columns.push(metaColumn);
                for (var i = 0; i < _this.subcategories.length; i++) {
                    var category = _this.subcategories[i];
                    if (!(category instanceof RexPostTypeCategory)) {
                        continue;
                    }
                    //Display any unrecognized actions in a "Misc" column.
                    var customActions = _.omit(category.actions, defaultPostTypeActions);
                    if (!_.isEmpty(customActions)) {
                        metaColumn.actions = _.union(metaColumn.actions, _.keys(customActions));
                    }
                }
                return columns;
            },
            deferEvaluation: true,
        });
        return _this;
    }
    return RexPostTypeContainerCategory;
}(RexTableViewCategory));
var RexCapability = /** @class */ (function () {
    function RexCapability(name, editor) {
        var _this = this;
        this.originComponent = null;
        this.usedByComponents = [];
        this.menuItems = [];
        this.usedByPostTypeActions = {};
        this.usedByTaxonomyActions = {};
        this.predefinedPermissions = [];
        this.documentationUrl = null;
        this.notes = null;
        this.name = String(name);
        this.editor = editor;
        var self = this;
        this.readableName = wsAmeLodash.capitalize(this.name.replace(/[_\-\s]+/g, ' '));
        this.displayName = ko.pureComputed({
            read: function () {
                return editor.readableNamesEnabled() ? self.readableName : self.name;
            },
            deferEvaluation: true,
            owner: this
        });
        this.isDeleted = ko.observable(false);
        this.responsibleActors = ko.computed({
            read: function () {
                var actor = editor.selectedActor(), list = [];
                if (actor instanceof RexUser) {
                    actor.hasCap(self.name, list);
                }
                return list;
            },
            owner: this,
            deferEvaluation: true
        });
        this.isInherited = ko.computed({
            read: function () {
                var actor = editor.selectedActor();
                if (!actor.canHaveRoles) {
                    return false;
                }
                var responsibleActors = self.responsibleActors();
                return responsibleActors
                    && (responsibleActors.length > 0)
                    && (wsAmeLodash.indexOf(responsibleActors, actor) < (responsibleActors.length - 1));
            },
            owner: this,
            deferEvaluation: true
        });
        this.isPersonalOverride = ko.pureComputed({
            read: function () {
                //This flag applies only to actors that can inherit permissions.
                var actor = editor.selectedActor();
                if (!actor.canHaveRoles) {
                    return false;
                }
                return !self.isInherited();
            },
            owner: this,
            deferEvaluation: true
        });
        this.isEditable = ko.pureComputed({
            read: function () {
                if (self.isInherited() && !editor.inheritanceOverrideEnabled()) {
                    return false;
                }
                return !self.isDeleted();
            },
            deferEvaluation: true
        });
        this.isEnabledForSelectedActor = ko.computed({
            read: function () {
                return editor.selectedActor().hasCap(self.name);
            },
            write: function (newState) {
                var actor = editor.selectedActor();
                if (editor.isShiftKeyDown()) {
                    //Hold the shift key while clicking to cycle the capability between 3 states:
                    //Granted -> Denied -> Not granted.
                    var oldState = actor.getOwnCapabilityState(self.name);
                    if (newState) {
                        if (oldState === false) {
                            actor.deleteCap(self.name); //Denied -> Not granted.
                        }
                        else if (oldState === null) {
                            actor.setCap(self.name, true); //Not granted -> Granted.
                        }
                    }
                    else {
                        if (oldState === true) {
                            actor.setCap(self.name, false); //Granted -> Denied.
                        }
                        else if (oldState === null) {
                            actor.setCap(self.name, true); //Not granted (inherited = Granted) -> Granted.
                        }
                    }
                    //Update the checkbox state.
                    if (actor.hasCap(self.name) !== newState) {
                        self.isEnabledForSelectedActor.notifySubscribers();
                    }
                    return;
                }
                if (newState) {
                    //TODO: If it's a user and the cap is explicitly negated, consider removing that state.
                    actor.setCap(self.name, newState);
                }
                else {
                    //The default is to remove the capability instead of explicitly setting it to false.
                    actor.deleteCap(self.name);
                    //If we're removing a capability from a user but one of their roles also has it,
                    //we have to set it to false after all or it will stay enabled.
                    if (actor.canHaveRoles && actor.hasCap(self.name)) {
                        actor.setCap(self.name, newState);
                    }
                }
            },
            owner: this,
            deferEvaluation: true
        });
        //this.isEnabledForSelectedActor.extend({rateLimit: {timeout: 10, method: "notifyWhenChangesStop"}});
        this.isExplicitlyDenied = ko.pureComputed({
            read: function () {
                var actor = editor.selectedActor();
                if (actor) {
                    return (actor.getCapabilityState(self.name) === false);
                }
                return false;
            },
            deferEvaluation: true
        });
        this.grantedPermissions = ko.computed({
            read: function () {
                var _ = wsAmeLodash;
                var results = [];
                if (_this.predefinedPermissions.length > 0) {
                    results = _this.predefinedPermissions.slice();
                }
                function localeAwareCompare(a, b) {
                    return a.localeCompare(b);
                }
                function actionsToPermissions(actionGroups, labelMap, descriptions) {
                    return _.map(actionGroups, function (ids, action) {
                        var labels = _.map(ids, function (id) { return labelMap[id].pluralLabel; })
                            .sort(localeAwareCompare);
                        var template = descriptions[action];
                        if (!template) {
                            template = action + ': %s';
                        }
                        return template.replace('%s', RexCapability.formatNounList(labels));
                    }).sort(localeAwareCompare);
                }
                //Post permissions.
                var postActionGroups = _.transform(_this.usedByPostTypeActions, function (accumulator, actions, postType) {
                    var actionKeys = _.keys(actions);
                    //Combine "edit" and "create" permissions because they usually use the same capability.
                    var editEqualsCreate = actions.hasOwnProperty('edit_posts') && actions.hasOwnProperty('create_posts');
                    if (editEqualsCreate) {
                        actionKeys = _.without(actionKeys, 'edit_posts', 'create_posts');
                        actionKeys.unshift('edit_and_create');
                    }
                    _.forEach(actionKeys, function (action) {
                        if (!accumulator.hasOwnProperty(action)) {
                            accumulator[action] = [];
                        }
                        accumulator[action].push(postType);
                    });
                }, {});
                var postPermissions = actionsToPermissions(postActionGroups, _this.editor.postTypes, RexPostTypePermission.actionDescriptions);
                Array.prototype.push.apply(results, postPermissions);
                //Taxonomy permissions.
                var taxonomyActionGroups = _.transform(_this.usedByTaxonomyActions, function (accumulator, actions, taxonomy) {
                    var actionKeys = _.keys(actions);
                    //Most taxonomies use the same capability for manage_terms, edit_terms, and delete_terms.
                    //In those cases, let's show only manage_terms.
                    if (actions.hasOwnProperty('manage_terms')) {
                        actionKeys = _.without(actionKeys, 'edit_terms', 'delete_terms');
                    }
                    _.forEach(actionKeys, function (action) {
                        if (!accumulator.hasOwnProperty(action)) {
                            accumulator[action] = [];
                        }
                        accumulator[action].push(taxonomy);
                    });
                }, {});
                var taxonomyPermissions = actionsToPermissions(taxonomyActionGroups, _this.editor.taxonomies, RexTaxonomyPermission.actionDescriptions);
                Array.prototype.push.apply(results, taxonomyPermissions);
                Array.prototype.push.apply(results, _this.menuItems);
                return results;
            },
            deferEvaluation: true,
            owner: this,
        });
    }
    // noinspection JSUnusedGlobalSymbols Used in KO templates.
    RexCapability.prototype.getDocumentationUrl = function () {
        if (this.documentationUrl) {
            return this.documentationUrl;
        }
        if (this.originComponent && this.originComponent.capabilityDocumentationUrl) {
            this.documentationUrl = this.originComponent.capabilityDocumentationUrl;
            return this.documentationUrl;
        }
        return null;
    };
    RexCapability.fromJs = function (name, data, editor) {
        var capability = new RexCapability(name, editor);
        capability.menuItems = data.menuItems.sort(function (a, b) {
            return a.localeCompare(b);
        });
        if (data.componentId) {
            capability.originComponent = editor.getComponent(data.componentId);
        }
        if (data.usedByComponents) {
            for (var id in data.usedByComponents) {
                var component = editor.getComponent(id);
                if (component) {
                    capability.usedByComponents.push(component);
                }
            }
        }
        if (data.documentationUrl) {
            capability.documentationUrl = data.documentationUrl;
        }
        if (data.permissions && (data.permissions.length > 0)) {
            capability.predefinedPermissions = data.permissions;
        }
        if ((capability.originComponent === editor.coreComponent) && (capability.documentationUrl === null)) {
            capability.documentationUrl = 'https://wordpress.org/support/article/roles-and-capabilities/#'
                + encodeURIComponent(capability.name);
        }
        if (data.readableName) {
            capability.readableName = data.readableName;
        }
        return capability;
    };
    RexCapability.formatNounList = function (items) {
        if (items.length <= 2) {
            return items.join(' and ');
        }
        return items.slice(0, -1).join(', ') + ', and ' + items[items.length - 1];
    };
    return RexCapability;
}());
var RexDoNotAllowCapability = /** @class */ (function (_super) {
    __extends(RexDoNotAllowCapability, _super);
    function RexDoNotAllowCapability(editor) {
        var _this = _super.call(this, 'do_not_allow', editor) || this;
        _this.notes = '"do_not_allow" is a special capability. '
            + 'WordPress uses it internally to indicate that access is denied. '
            + 'Normally, it should not be assigned to any roles or users.';
        //Normally, it's impossible to grant this capability to anyone. Doing so would break things.
        //However, if it's already granted, you can remove it.
        _this.isEditable = ko.computed(function () {
            return _this.isEnabledForSelectedActor();
        });
        return _this;
    }
    return RexDoNotAllowCapability;
}(RexCapability));
var RexExistCapability = /** @class */ (function (_super) {
    __extends(RexExistCapability, _super);
    function RexExistCapability(editor) {
        var _this = _super.call(this, 'exist', editor) || this;
        _this.notes = '"exist" is a special capability. '
            + 'WordPress uses it internally to indicate that a role or user exists. '
            + 'Normally, everyone has this capability by default, and it is not necessary '
            + '(or possible) to assign it directly.';
        //Everyone must have this capability. However, if it has somehow become disabled,
        //we'll let the user enable it.
        _this.isEditable = ko.computed(function () {
            return !_this.isEnabledForSelectedActor();
        });
        return _this;
    }
    return RexExistCapability;
}(RexCapability));
var RexInvalidCapability = /** @class */ (function (_super) {
    __extends(RexInvalidCapability, _super);
    function RexInvalidCapability(fakeName, value, editor) {
        var _this = _super.call(this, fakeName, editor) || this;
        var startsWithVowel = /^[aeiou]/i;
        var theType = (typeof value);
        var nounPhrase = (startsWithVowel.test(theType) ? 'an' : 'a') + ' ' + theType;
        _this.notes = 'This is not a valid capability. A capability name must be a string (i.e. text),'
            + ' but this is ' + nounPhrase + '. It was probably created by a bug in another plugin or theme.';
        _this.isEditable = ko.computed(function () {
            return false;
        });
        return _this;
    }
    return RexInvalidCapability;
}(RexCapability));
var RexUserPreferences = /** @class */ (function () {
    function RexUserPreferences(initialPreferences, ajaxUrl, updateNonce) {
        var _this = this;
        var _ = wsAmeLodash;
        initialPreferences = initialPreferences || {};
        if (_.isArray(initialPreferences)) {
            initialPreferences = {};
        }
        this.preferenceObservables = _.mapValues(initialPreferences, ko.observable, ko);
        this.preferenceCount = ko.observable(_.size(this.preferenceObservables));
        this.collapsedCategories = new RexCollapsedCategorySet(_.get(initialPreferences, 'collapsedCategories', []));
        this.plainPreferences = ko.computed(function () {
            //By creating a dependency on the number of preferences, we ensure that the observable will be re-evaluated
            //whenever a preference is added or removed.
            _this.preferenceCount();
            //This converts preferences to a plain JS object and establishes dependencies on all individual observables.
            var result = _.mapValues(_this.preferenceObservables, function (observable) {
                return observable();
            });
            result.collapsedCategories = _this.collapsedCategories.toJs();
            return result;
        });
        //Avoid excessive AJAX requests.
        this.plainPreferences.extend({ rateLimit: { timeout: 5000, method: "notifyWhenChangesStop" } });
        //Save preferences when they change.
        if (ajaxUrl && updateNonce) {
            this.plainPreferences.subscribe(function (preferences) {
                //console.info('Saving user preferences', preferences);
                jQuery.post(ajaxUrl, {
                    action: 'ws_ame_rex_update_user_preferences',
                    _ajax_nonce: updateNonce,
                    preferences: ko.toJSON(preferences)
                });
            });
        }
    }
    RexUserPreferences.prototype.getObservable = function (name, defaultValue) {
        if (defaultValue === void 0) { defaultValue = null; }
        if (this.preferenceObservables.hasOwnProperty(name)) {
            return this.preferenceObservables[name];
        }
        var newPreference = ko.observable(defaultValue || null);
        this.preferenceObservables[name] = newPreference;
        this.preferenceCount(this.preferenceCount() + 1);
        return newPreference;
    };
    return RexUserPreferences;
}());
/**
 * An observable collection of unique strings. In this case, they're category slugs.
 */
var RexCollapsedCategorySet = /** @class */ (function () {
    function RexCollapsedCategorySet(items) {
        if (items === void 0) { items = []; }
        this.isItemInSet = {};
        items = wsAmeLodash.uniq(items);
        for (var i = 0; i < items.length; i++) {
            this.isItemInSet[items[i]] = ko.observable(true);
        }
        this.items = ko.observableArray(items);
    }
    RexCollapsedCategorySet.prototype.getItemObservable = function (item) {
        if (!this.isItemInSet.hasOwnProperty(item)) {
            this.isItemInSet[item] = ko.observable(false);
        }
        return this.isItemInSet[item];
    };
    RexCollapsedCategorySet.prototype.add = function (item) {
        if (!this.contains(item)) {
            this.getItemObservable(item)(true);
            this.items.push(item);
        }
    };
    RexCollapsedCategorySet.prototype.remove = function (item) {
        if (this.contains(item)) {
            this.isItemInSet[item](false);
            this.items.remove(item);
        }
    };
    RexCollapsedCategorySet.prototype.toggle = function (item, addToSet) {
        if (addToSet) {
            this.add(item);
        }
        else {
            this.remove(item);
        }
    };
    RexCollapsedCategorySet.prototype.contains = function (item) {
        return this.getItemObservable(item)();
    };
    RexCollapsedCategorySet.prototype.peek = function (item) {
        if (!this.isItemInSet.hasOwnProperty(item)) {
            return false;
        }
        return this.isItemInSet[item].peek();
    };
    RexCollapsedCategorySet.prototype.toJs = function () {
        return this.items();
    };
    return RexCollapsedCategorySet;
}());
var RexBaseDialog = /** @class */ (function () {
    function RexBaseDialog() {
        var _this = this;
        this.isOpen = ko.observable(false);
        this.isRendered = ko.observable(false);
        this.title = null;
        this.options = {
            buttons: []
        };
        this.isOpen.subscribe(function (isOpenNow) {
            if (isOpenNow && !_this.isRendered()) {
                _this.isRendered(true);
            }
        });
    }
    RexBaseDialog.prototype.setupValidationTooltip = function (inputSelector, message) {
        //Display validation messages next to the input field.
        var element = this.jQueryWidget.find(inputSelector).qtip({
            overwrite: false,
            content: '(Validation errors will appear here.)',
            //Show the tooltip when the input is focused.
            show: {
                event: '',
                ready: false,
                effect: false
            },
            hide: {
                event: '',
                effect: false
            },
            position: {
                my: 'center left',
                at: 'center right',
                effect: false
            },
            style: {
                classes: 'qtip-bootstrap qtip-shadow rex-tooltip'
            }
        });
        message.subscribe(function (newMessage) {
            if (newMessage == '') {
                element.qtip('option', 'content.text', 'OK');
                element.qtip('option', 'show.event', '');
                element.qtip('hide');
            }
            else {
                element.qtip('option', 'content.text', newMessage);
                element.qtip('option', 'show.event', 'focus');
                element.qtip('show');
            }
        });
        //Hide the tooltip when the dialog is closed and prevent it from automatically re-appearing.
        this.isOpen.subscribe(function (isDialogOpen) {
            if (!isDialogOpen) {
                element.qtip('option', 'show.event', '');
                element.qtip('hide');
            }
        });
    };
    ;
    return RexBaseDialog;
}());
var RexDeleteCapDialog = /** @class */ (function (_super) {
    __extends(RexDeleteCapDialog, _super);
    function RexDeleteCapDialog(editor) {
        var _this = _super.call(this) || this;
        _this.options = {
            buttons: [],
            minWidth: 380
        };
        _this.wasEverOpen = ko.observable(false);
        var _ = wsAmeLodash;
        _this.options.buttons.push({
            text: 'Delete Capability',
            'class': 'button button-primary rex-delete-selected-caps',
            click: function () {
                var selectedCapabilities = _.chain(_this.deletableItems())
                    .filter(function (item) {
                    return item.isSelected();
                })
                    .pluck('capability')
                    .value();
                //Note: We could remove confirmation if we get an "Undo" feature.
                var noun = (selectedCapabilities.length === 1) ? 'capability' : 'capabilities';
                var warning = 'Caution: Deleting capabilities could break plugins that use those capabilities. '
                    + 'Delete ' + selectedCapabilities.length + ' ' + noun + '?';
                if (!confirm(warning)) {
                    return;
                }
                _this.isOpen(false);
                editor.deleteCapabilities(selectedCapabilities);
                alert(selectedCapabilities.length + ' capabilities deleted');
            },
            disabled: true
        });
        _this.isOpen.subscribe(function (open) {
            if (open && !_this.wasEverOpen()) {
                _this.wasEverOpen(true);
            }
        });
        _this.deletableItems = ko.pureComputed({
            read: function () {
                var wpCore = editor.getComponent(':wordpress:');
                return _.chain(editor.capabilities)
                    .filter(function (capability) {
                    if (capability.originComponent === wpCore) {
                        return false;
                    }
                    return !capability.isDeleted();
                })
                    //Pre-populate part of the list when the dialog is closed to ensure it has a non-zero height.
                    .take(_this.wasEverOpen() ? 1000000 : 30)
                    .sortBy(function (capability) {
                    return capability.name.toLowerCase();
                })
                    .map(function (capability) {
                    return {
                        'capability': capability,
                        'isSelected': ko.observable(false)
                    };
                })
                    .value();
            },
            deferEvaluation: true
        });
        _this.selectedItemCount = ko.pureComputed({
            read: function () { return _.filter(_this.deletableItems(), function (item) {
                return item.isSelected();
            }).length; },
            deferEvaluation: true
        });
        var deleteButtonText = ko.pureComputed({
            read: function () {
                var count = _this.selectedItemCount();
                if (count <= 0) {
                    return 'Delete Capability';
                }
                else {
                    if (count === 1) {
                        return 'Delete 1 Capability';
                    }
                    else {
                        return ('Delete ' + count + ' Capabilities');
                    }
                }
            },
            deferEvaluation: true
        });
        deleteButtonText.subscribe(function (newText) {
            _this.jQueryWidget
                .closest('.ui-dialog')
                .find('.ui-dialog-buttonset .button-primary .ui-button-text')
                .text(newText);
        });
        _this.isDeleteButtonEnabled = ko.pureComputed({
            read: function () {
                return _this.selectedItemCount() > 0;
            },
            deferEvaluation: true
        });
        return _this;
    }
    RexDeleteCapDialog.prototype.onOpen = function () {
        //Deselect all items when the dialog is opened.
        var items = this.deletableItems();
        for (var i = 0; i < items.length; i++) {
            if (items[i].isSelected()) {
                items[i].isSelected(false);
            }
        }
    };
    return RexDeleteCapDialog;
}(RexBaseDialog));
var RexAddCapabilityDialog = /** @class */ (function (_super) {
    __extends(RexAddCapabilityDialog, _super);
    function RexAddCapabilityDialog(editor) {
        var _this = _super.call(this) || this;
        _this.autoCancelButton = true;
        _this.options = {
            minWidth: 380
        };
        _this.validationState = ko.observable(RexAddCapabilityDialog.states.empty);
        _this.validationMessage = ko.observable('');
        var _ = wsAmeLodash;
        _this.editor = editor;
        var excludedCaps = ['do_not_allow', 'exist', 'customize'];
        var newCapabilityName = ko.observable('');
        _this.capabilityName = ko.computed({
            read: function () {
                return newCapabilityName();
            },
            write: function (value) {
                value = _.trimRight(value);
                //Validate and sanitize the capability name.
                var state = _this.validationState, message = _this.validationMessage;
                //WP API allows completely arbitrary capability names, but this plugin forbids some characters
                //for sanity's sake and to avoid XSS.
                var invalidCharacters = /[><&\r\n\t]/g;
                //While all other characters are allowed, it's recommended to stick to alphanumerics,
                //underscores and dashes. Spaces are also OK because some other plugins use them.
                var suspiciousCharacters = /[^a-z0-9_ -]/ig;
                //PHP doesn't allow numeric string keys, and there's no conceivable reason to start the name with a space.
                var invalidFirstCharacter = /^[\s0-9]/i;
                var foundInvalid = value.match(invalidCharacters);
                var foundSuspicious = value.match(suspiciousCharacters);
                if (foundInvalid !== null) {
                    state(RexAddCapabilityDialog.states.error);
                    message('Sorry, <code>' + _.escape(_.last(foundInvalid)) + '</code> is not allowed here.');
                }
                else if (value.match(invalidFirstCharacter) !== null) {
                    state(RexAddCapabilityDialog.states.error);
                    message('Capability name should start with a letter or an underscore.');
                }
                else if (editor.capabilityExists(value)) {
                    //Duplicates are not allowed.
                    state(RexAddCapabilityDialog.states.error);
                    message('That capability already exists.');
                }
                else if (editor.getRole(value) !== null) {
                    state(RexAddCapabilityDialog.states.error);
                    message('Capability name can\'t be the same as the name of a role.');
                }
                else if (excludedCaps.indexOf(value) >= 0) {
                    state(RexAddCapabilityDialog.states.error);
                    message('That is a meta capability or a reserved capability name.');
                }
                else if (foundSuspicious !== null) {
                    state(RexAddCapabilityDialog.states.notice);
                    message('For best compatibility, we recommend using only English letters, numbers, and underscores.');
                }
                else if (value === '') {
                    //Empty input, nothing to validate.
                    state(RexAddCapabilityDialog.states.empty);
                    message('');
                }
                else {
                    state(RexAddCapabilityDialog.states.valid);
                    message('');
                }
                newCapabilityName(value);
            }
        });
        var acceptableStates = [RexAddCapabilityDialog.states.valid, RexAddCapabilityDialog.states.notice];
        _this.isAddButtonEnabled = ko.pureComputed(function () {
            return (acceptableStates.indexOf(_this.validationState()) >= 0);
        });
        _this.options.buttons = [{
                text: 'Add Capability',
                'class': 'button button-primary',
                click: function () {
                    _this.onConfirm();
                },
                disabled: true
            }];
        return _this;
    }
    RexAddCapabilityDialog.prototype.onOpen = function (event, ui) {
        //Clear the input when the dialog is opened.
        this.capabilityName('');
    };
    RexAddCapabilityDialog.prototype.onConfirm = function () {
        if (!this.isAddButtonEnabled()) {
            return;
        }
        var category = this.editor.addCapability(this.capabilityName().trim());
        this.isOpen(false);
        //Note: Maybe the user doesn't need this alert? Hmm.
        if (!category || (this.editor.categoryViewMode() === RexRoleEditor.listView)) {
            alert('Capability added');
        }
        else {
            alert('Capability added to the "' + category.getAbsoluteName() + '" category.');
        }
    };
    RexAddCapabilityDialog.states = {
        valid: 'valid',
        empty: 'empty',
        notice: 'notice',
        error: 'error'
    };
    return RexAddCapabilityDialog;
}(RexBaseDialog));
var RexAddRoleDialog = /** @class */ (function (_super) {
    __extends(RexAddRoleDialog, _super);
    function RexAddRoleDialog(editor) {
        var _this = _super.call(this) || this;
        _this.roleName = ko.observable('');
        _this.roleDisplayName = ko.observable('');
        _this.roleToCopyFrom = ko.observable(null);
        _this.nameValidationMessage = ko.observable('');
        _this.displayNameValidationMessage = ko.observable('');
        _this.areTooltipsInitialised = false;
        var _ = wsAmeLodash;
        _this.editor = editor;
        _this.options.minWidth = 380;
        _this.options.buttons.push({
            text: 'Add Role',
            'class': 'button button-primary',
            click: _this.onConfirm.bind(_this),
            disabled: true
        });
        _this.roleDisplayName.extend({ rateLimit: 10 });
        _this.roleName.extend({ rateLimit: 10 });
        //Role names are restricted - you can only use lowercase Latin letters, numbers and underscores.
        var roleNameCharacterGroup = 'a-z0-9_';
        var invalidCharacterRegex = new RegExp('[^' + roleNameCharacterGroup + ']', 'g');
        var numbersOnlyRegex = /^[0-9]+$/;
        _this.isNameValid = ko.computed(function () {
            var name = _this.roleName().trim();
            var message = _this.nameValidationMessage;
            //Name must not be empty.
            if (name === '') {
                message('');
                return false;
            }
            //Name can only contain certain characters.
            var invalidChars = name.match(invalidCharacterRegex);
            if (invalidChars !== null) {
                var lastInvalidChar = _.last(invalidChars);
                if (lastInvalidChar === ' ') {
                    lastInvalidChar = 'space';
                }
                message('Sorry, <code>' + _.escape(lastInvalidChar) + '</code> is not allowed here.<br>'
                    + 'Please enter only lowercase English letters, numbers, and underscores.');
                return false;
            }
            //Numeric names could cause problems with how PHP handles associative arrays.
            if (numbersOnlyRegex.test(name)) {
                message('Numeric names are not allowed. Please add at least one letter or underscore.');
                return false;
            }
            //Name must not be a duplicate.
            var existingRole = editor.getRole(name);
            if (existingRole) {
                message('Duplicate role name.');
                return false;
            }
            //WP stores capabilities and role names in the same associative array,
            //so they must be unique with respect to each other.
            if (editor.capabilityExists(name)) {
                message('Role name can\'t be the same as a capability name.');
                return false;
            }
            message('');
            return true;
        });
        _this.isDisplayNameValid = ko.computed(function () {
            var name = _this.roleDisplayName();
            var message = _this.displayNameValidationMessage;
            return RexAddRoleDialog.validateDisplayName(name, message);
        });
        //Automatically generate a role name from the display name. Basically, turn it into a slug.
        var lastAutoRoleName = null;
        _this.roleDisplayName.subscribe(function (displayName) {
            var slug = _.snakeCase(displayName);
            //Use the auto-generated role name only if the user hasn't entered their own.
            var currentValue = _this.roleName();
            if ((currentValue === '') || (currentValue === lastAutoRoleName)) {
                _this.roleName(slug);
            }
            lastAutoRoleName = slug;
        });
        _this.isAddButtonEnabled = ko.pureComputed({
            read: function () {
                return (_this.roleName() !== '') && (_this.roleDisplayName() !== '')
                    && _this.isNameValid() && _this.isDisplayNameValid();
            },
            deferEvaluation: true
        });
        return _this;
    }
    RexAddRoleDialog.validateDisplayName = function (name, validationMessage) {
        name = name.trim();
        if (name === '') {
            validationMessage('');
            return false;
        }
        //You can choose pretty much any display name you like, but we'll forbid special characters
        //that might cause problems for plugins that don't escape output for HTML.
        if (RexAddRoleDialog.invalidDisplayNameRegex.test(name)) {
            validationMessage('Sorry, these characters are not allowed: <code>&lt; &gt; &amp;</code>');
            return false;
        }
        validationMessage('');
        return true;
    };
    RexAddRoleDialog.prototype.onOpen = function (event, ui) {
        //Clear dialog fields when it's opened.
        this.roleName('');
        this.roleDisplayName('');
        this.roleToCopyFrom(null);
        if (!this.areTooltipsInitialised) {
            this.setupValidationTooltip('#rex-new-role-display-name', this.displayNameValidationMessage);
            this.setupValidationTooltip('#rex-new-role-name', this.nameValidationMessage);
            this.areTooltipsInitialised = true;
        }
    };
    RexAddRoleDialog.prototype.onConfirm = function () {
        if (!this.isAddButtonEnabled()) {
            return;
        }
        this.isOpen(false);
        var caps = {};
        if (this.roleToCopyFrom()) {
            caps = this.roleToCopyFrom().getOwnCapabilities();
        }
        this.editor.addRole(this.roleName(), this.roleDisplayName(), caps);
    };
    RexAddRoleDialog.invalidDisplayNameRegex = /[><&\r\n\t]/;
    return RexAddRoleDialog;
}(RexBaseDialog));
var RexDeleteRoleDialog = /** @class */ (function (_super) {
    __extends(RexDeleteRoleDialog, _super);
    function RexDeleteRoleDialog(editor) {
        var _this = _super.call(this) || this;
        _this.isRoleSelected = {};
        _this.editor = editor;
        _this.options.minWidth = 420;
        _this.options.buttons.push({
            text: 'Delete Role',
            'class': 'button button-primary',
            click: _this.onConfirm.bind(_this),
            disabled: true
        });
        _this.isDeleteButtonEnabled = ko.pureComputed({
            read: function () {
                return _this.getSelectedRoles().length > 0;
            },
            deferEvaluation: true
        });
        return _this;
    }
    RexDeleteRoleDialog.prototype.onConfirm = function () {
        var _ = wsAmeLodash;
        var rolesToDelete = this.getSelectedRoles();
        //Warn about the dangers of deleting built-in roles.
        var selectedBuiltInRoles = _.filter(rolesToDelete, _.method('isBuiltIn'));
        if (selectedBuiltInRoles.length > 0) {
            var warning = 'Caution: Deleting default roles like ' + _.first(selectedBuiltInRoles).displayName()
                + ' can prevent you from using certain plugins. This is because some plugins look for specific'
                + ' role names to determine if a user is allowed to access the plugin.'
                + '\nDelete ' + selectedBuiltInRoles.length + ' default role(s)?';
            if (!confirm(warning)) {
                return;
            }
        }
        this.editor.deleteRoles(rolesToDelete);
        this.isOpen(false);
    };
    RexDeleteRoleDialog.prototype.onOpen = function (event, ui) {
        //Deselect all previously selected roles.
        wsAmeLodash.forEach(this.isRoleSelected, function (isSelected) {
            isSelected(false);
        });
    };
    RexDeleteRoleDialog.prototype.getSelectionState = function (roleName) {
        if (!this.isRoleSelected.hasOwnProperty(roleName)) {
            this.isRoleSelected[roleName] = ko.observable(false);
        }
        return this.isRoleSelected[roleName];
    };
    RexDeleteRoleDialog.prototype.getSelectedRoles = function () {
        var _this = this;
        var _ = wsAmeLodash;
        var rolesToDelete = [];
        _.forEach(this.editor.roles(), function (role) {
            if (_this.getSelectionState(role.name())()) {
                rolesToDelete.push(role);
            }
        });
        return rolesToDelete;
    };
    return RexDeleteRoleDialog;
}(RexBaseDialog));
var RexRenameRoleDialog = /** @class */ (function (_super) {
    __extends(RexRenameRoleDialog, _super);
    function RexRenameRoleDialog(editor) {
        var _this = _super.call(this) || this;
        _this.selectedRole = ko.observable(null);
        _this.newDisplayName = ko.observable('');
        _this.displayNameValidationMessage = ko.observable('');
        _this.isTooltipInitialised = false;
        _this.editor = editor;
        _this.options.minWidth = 380;
        _this.options.buttons.push({
            text: 'Rename Role',
            'class': 'button button-primary',
            click: _this.onConfirm.bind(_this),
            disabled: true
        });
        _this.selectedRole.subscribe(function (role) {
            if (role) {
                _this.newDisplayName(role.displayName());
            }
        });
        _this.isConfirmButtonEnabled = ko.computed({
            read: function () {
                return RexAddRoleDialog.validateDisplayName(_this.newDisplayName(), _this.displayNameValidationMessage);
            },
            deferEvaluation: true
        });
        return _this;
    }
    RexRenameRoleDialog.prototype.onOpen = function (event, ui) {
        var _ = wsAmeLodash;
        if (!this.isTooltipInitialised) {
            this.setupValidationTooltip('#rex-edited-role-display-name', this.displayNameValidationMessage);
            this.isTooltipInitialised = true;
        }
        //Select either the currently selected role or the first available role.
        var selectedActor = this.editor.selectedActor();
        if (selectedActor && (selectedActor instanceof RexRole)) {
            this.selectedRole(selectedActor);
        }
        else {
            this.selectedRole(_.first(this.editor.roles()));
        }
    };
    RexRenameRoleDialog.prototype.onConfirm = function () {
        if (!this.isConfirmButtonEnabled()) {
            return;
        }
        if (this.selectedRole()) {
            var name_1 = this.newDisplayName().trim();
            this.selectedRole().displayName(name_1);
            this.editor.actorSelector.repopulate();
        }
        this.isOpen(false);
    };
    return RexRenameRoleDialog;
}(RexBaseDialog));
var RexEagerObservableStringSet = /** @class */ (function () {
    function RexEagerObservableStringSet() {
        this.items = {};
    }
    RexEagerObservableStringSet.prototype.contains = function (item) {
        if (!this.items.hasOwnProperty(item)) {
            this.items[item] = ko.observable(false);
            return false;
        }
        return this.items[item]();
    };
    RexEagerObservableStringSet.prototype.add = function (item) {
        if (!this.items.hasOwnProperty(item)) {
            this.items[item] = ko.observable(true);
        }
        else {
            this.items[item](true);
        }
    };
    RexEagerObservableStringSet.prototype.remove = function (item) {
        if (this.items.hasOwnProperty(item)) {
            this.items[item](false);
        }
    };
    RexEagerObservableStringSet.prototype.clear = function () {
        var _ = wsAmeLodash;
        _.forEach(this.items, function (isInSet) {
            isInSet(false);
        });
    };
    RexEagerObservableStringSet.prototype.getPresenceObservable = function (item) {
        if (!this.items.hasOwnProperty(item)) {
            this.items[item] = ko.observable(false);
        }
        return this.items[item];
    };
    RexEagerObservableStringSet.prototype.getAsObject = function (fillValue) {
        if (fillValue === void 0) { fillValue = true; }
        var _ = wsAmeLodash;
        var output = {};
        _.forEach(this.items, function (isInSet, item) {
            if (isInSet()) {
                output[item] = fillValue;
            }
        });
        return output;
    };
    return RexEagerObservableStringSet;
}());
var RexObservableEditableRoleSettings = /** @class */ (function () {
    function RexObservableEditableRoleSettings() {
        this.strategy = ko.observable('auto');
        this.userDefinedList = new RexEagerObservableStringSet();
    }
    RexObservableEditableRoleSettings.prototype.toPlainObject = function () {
        var roleList = this.userDefinedList.getAsObject(true);
        if (wsAmeLodash.isEmpty(roleList)) {
            roleList = null;
        }
        return {
            strategy: this.strategy(),
            userDefinedList: roleList
        };
    };
    return RexObservableEditableRoleSettings;
}());
var RexUserRoleModule = /** @class */ (function () {
    function RexUserRoleModule(selectedActor, roles) {
        var _this = this;
        this.roleObservables = {};
        this.selectedActor = selectedActor;
        this.sortedRoles = ko.computed(function () {
            return roles();
        });
        this.primaryRole = ko.computed({
            read: function () {
                var actor = selectedActor();
                if ((actor === null) || !actor.canHaveRoles) {
                    return null;
                }
                if (actor instanceof RexUser) {
                    var roles_1 = actor.roles();
                    if (roles_1.length < 1) {
                        return null;
                    }
                    return roles_1[0];
                }
                return null;
            },
            write: function (newRole) {
                var actor = selectedActor();
                if ((actor === null) || !actor.canHaveRoles || !(actor instanceof RexUser)) {
                    return;
                }
                //No primary role = no roles at all.
                if (newRole === null) {
                    actor.roles.removeAll();
                    return;
                }
                //Sanity check.
                if (!(newRole instanceof RexRole)) {
                    return;
                }
                if (!_this.canAssignRoleToActor(newRole)) {
                    return;
                }
                //Remove the previous primary role.
                var oldPrimaryRole = (actor.roles().length > 0) ? actor.roles()[0] : null;
                if (oldPrimaryRole !== null) {
                    actor.roles.remove(oldPrimaryRole);
                }
                //If the user already has the new role, remove it from its old position first.
                if (actor.roles.indexOf(newRole) !== -1) {
                    actor.roles.remove(newRole);
                }
                //Add the role to the top of the list.
                actor.roles.unshift(newRole);
            }
        });
        this.isVisible = ko.pureComputed(function () {
            var actor = _this.selectedActor();
            return (actor !== null) && actor.canHaveRoles;
        });
    }
    // noinspection JSUnusedGlobalSymbols Used in Knockout templates.
    RexUserRoleModule.prototype.actorHasRole = function (role) {
        var _this = this;
        var roleActorId = role.getId();
        if (this.roleObservables.hasOwnProperty(roleActorId) && (this.roleObservables[roleActorId].role === role)) {
            return this.roleObservables[roleActorId].selectedActorHasRole;
        }
        var selectedActorHasRole = ko.computed({
            read: function () {
                var actor = _this.selectedActor();
                if ((actor === null) || !actor.canHaveRoles) {
                    return false;
                }
                if (actor instanceof RexUser) {
                    return (actor.roles.indexOf(role) !== -1);
                }
                return false;
            },
            write: function (shouldHaveRole) {
                var actor = _this.selectedActor();
                if ((actor === null) || !actor.canHaveRoles || !(actor instanceof RexUser)) {
                    return;
                }
                if (!_this.canAssignRoleToActor(role)) {
                    return;
                }
                var alreadyHasRole = (actor.roles.indexOf(role) !== -1);
                if (shouldHaveRole !== alreadyHasRole) {
                    if (shouldHaveRole) {
                        actor.roles.push(role);
                    }
                    else {
                        actor.roles.remove(role);
                    }
                }
            }
        });
        this.roleObservables[roleActorId] = {
            role: role,
            selectedActorHasRole: selectedActorHasRole
        };
        return selectedActorHasRole;
    };
    RexUserRoleModule.prototype.canAssignRoleToActor = function (role) {
        //This is a stub. The role editor currently doesn't check editable role settings at edit time.
        var actor = this.selectedActor();
        if ((actor === null) || !actor.canHaveRoles) {
            return false;
        }
        return (role instanceof RexRole);
    };
    return RexUserRoleModule;
}());
var RexEditableRolesDialog = /** @class */ (function (_super) {
    __extends(RexEditableRolesDialog, _super);
    function RexEditableRolesDialog(editor) {
        var _this = _super.call(this) || this;
        _this.selectedActor = ko.observable(null);
        _this.actorSettings = {};
        _this.editor = editor;
        _this.visibleActors = ko.observableArray([]);
        _this.options.minWidth = 600;
        _this.options.buttons.push({
            text: 'Save Changes',
            'class': 'button button-primary',
            click: _this.onConfirm.bind(_this),
            disabled: false
        });
        //Super Admin is always set to "leave unchanged" because
        //they can edit all roles.
        var superAdmin = editor.getSuperAdmin();
        var superAdminSettings = new RexObservableEditableRoleSettings();
        superAdminSettings.strategy('none');
        var dummySettings = new RexObservableEditableRoleSettings();
        _this.selectedActorSettings = ko.computed(function () {
            if (_this.selectedActor() === null) {
                return dummySettings;
            }
            if (_this.selectedActor() === superAdmin) {
                return superAdminSettings;
            }
            var actorId = _this.selectedActor().getId();
            if (!_this.actorSettings.hasOwnProperty(actorId)) {
                //This should never happen; the dictionary should be initialised when opening the dialog.
                _this.actorSettings[actorId] = new RexObservableEditableRoleSettings();
            }
            return _this.actorSettings[actorId];
        });
        _this.editableRoleStrategy = ko.computed({
            read: function () {
                return _this.selectedActorSettings().strategy();
            },
            write: function (newValue) {
                _this.selectedActorSettings().strategy(newValue);
            }
        });
        _this.isAutoStrategyAllowed = ko.computed(function () {
            var actor = _this.selectedActor();
            if (actor == null) {
                return true;
            }
            return !((actor === superAdmin)
                || ((actor instanceof RexUser) && actor.isSuperAdmin));
        });
        _this.isListStrategyAllowed = _this.isAutoStrategyAllowed;
        return _this;
    }
    RexEditableRolesDialog.prototype.onOpen = function (event, ui) {
        var _this = this;
        var _ = wsAmeLodash;
        //Copy editable role settings into observables.
        _.forEach(this.editor.actorEditableRoles, function (settings, actorId) {
            if (!_this.actorSettings.hasOwnProperty(actorId)) {
                _this.actorSettings[actorId] = new RexObservableEditableRoleSettings();
            }
            var observableSettings = _this.actorSettings[actorId];
            observableSettings.strategy(settings.strategy);
            observableSettings.userDefinedList.clear();
            if (settings.userDefinedList !== null) {
                _.forEach(settings.userDefinedList, function (ignored, roleId) {
                    observableSettings.userDefinedList.add(roleId);
                });
            }
        });
        this.visibleActors(this.editor.actorSelector.getVisibleActors());
        //Select either the currently selected actor or the first role.
        var selectedActor = this.editor.selectedActor();
        if (selectedActor) {
            this.selectedActor(selectedActor);
        }
        else {
            this.selectedActor(_.first(this.editor.roles()));
        }
    };
    RexEditableRolesDialog.prototype.onConfirm = function () {
        //Save editable roles
        var _ = wsAmeLodash;
        var settings = this.editor.actorEditableRoles;
        _.forEach(this.actorSettings, function (observableSettings, actorId) {
            if (observableSettings.strategy() === 'auto') {
                //"auto" is the default so we don't need to store anything.
                delete settings[actorId];
            }
            else {
                settings[actorId] = observableSettings.toPlainObject();
            }
        });
        this.isOpen(false);
    };
    RexEditableRolesDialog.prototype.isRoleSetToEditable = function (role) {
        return this.selectedActorSettings().userDefinedList.getPresenceObservable(role.name());
    };
    RexEditableRolesDialog.prototype.isRoleEnabled = function (role) {
        return this.editableRoleStrategy() === 'user-defined-list';
    };
    RexEditableRolesDialog.prototype.selectItem = function (actor) {
        this.selectedActor(actor);
    };
    RexEditableRolesDialog.prototype.getItemText = function (actor) {
        return this.editor.actorSelector.getNiceName(actor);
    };
    return RexEditableRolesDialog;
}(RexBaseDialog));
var RexRoleEditor = /** @class */ (function () {
    function RexRoleEditor(data) {
        var _this = this;
        // noinspection JSUnusedGlobalSymbols
        this.categoryViewOptions = [
            RexRoleEditor.hierarchyView,
            RexRoleEditor.singleCategoryView,
            RexRoleEditor.listView
        ];
        this.deprecatedCapabilities = {};
        this.userDefinedCapabilities = {};
        this.categoriesBySlug = {};
        this.actorLookup = {};
        var self = this;
        var _ = wsAmeLodash;
        this.areBindingsApplied = ko.observable(false);
        this.isLoaded = ko.computed(function () {
            return _this.areBindingsApplied();
        });
        this.userPreferences = new RexUserPreferences(data.userPreferences, data.adminAjaxUrl, data.updatePreferencesNonce);
        var preferences = this.userPreferences;
        this.showDeprecatedEnabled = preferences.getObservable('showDeprecatedEnabled', true);
        this.showRedundantEnabled = preferences.getObservable('showRedundantEnabled', false);
        this.showBaseCapsEnabled = ko.computed(this.showRedundantEnabled);
        this.showOnlyCheckedEnabled = preferences.getObservable('showOnlyCheckedEnabled', false);
        this.categoryWidthMode = preferences.getObservable('categoryWidthMode', 'adaptive');
        this.readableNamesEnabled = preferences.getObservable('readableNamesEnabled', true);
        this.showNumberOfCapsEnabled = preferences.getObservable('showNumberOfCapsEnabled', true);
        this.showGrantedCapCountEnabled = preferences.getObservable('showGrantedCapCountEnabled', true);
        this.showTotalCapCountEnabled = preferences.getObservable('showTotalCapCountEnabled', true);
        this.showZerosEnabled = preferences.getObservable('showZerosEnabled', false);
        this.inheritanceOverrideEnabled = preferences.getObservable('inheritanceOverrideEnabled', false);
        //Remember and restore the selected view mode.
        var viewModeId = preferences.getObservable('categoryVewMode', 'hierarchy');
        var initialViewMode = _.find(this.categoryViewOptions, 'id', viewModeId());
        if (!initialViewMode) {
            initialViewMode = RexRoleEditor.hierarchyView;
        }
        this.categoryViewMode = ko.observable(initialViewMode);
        this.categoryViewMode.subscribe(function (newMode) {
            viewModeId(newMode.id);
        });
        this.isShiftKeyDown = ko.observable(false);
        this.capabilityViewClasses = ko.pureComputed({
            read: function () {
                var viewMode = _this.categoryViewMode();
                var classes = ['rex-category-view-mode-' + viewMode.id];
                if (viewMode === RexRoleEditor.singleCategoryView) {
                    classes.push('rex-show-category-subheadings');
                }
                if (_this.readableNamesEnabled()) {
                    classes.push('rex-readable-names-enabled');
                }
                if (_this.categoryWidthMode() === 'full') {
                    classes.push('rex-full-width-categories');
                }
                return classes.join(' ');
            },
            deferEvaluation: true
        });
        this.searchQuery = ko.observable('').extend({ rateLimit: { timeout: 100, method: "notifyWhenChangesStop" } });
        this.searchKeywords = ko.computed(function () {
            var query = self.searchQuery().trim();
            if (query === '') {
                return [];
            }
            return wsAmeLodash(query.split(' '))
                .map(function (keyword) {
                return keyword.trim();
            })
                .filter(function (keyword) {
                return (keyword !== '');
            })
                .value();
        });
        this.components = _.mapValues(data.knownComponents, function (details, id) {
            return RexWordPressComponent.fromJs(id, details);
        });
        this.coreComponent = new RexWordPressComponent(':wordpress:', 'WordPress core');
        this.components[':wordpress:'] = this.coreComponent;
        //Populate roles and users.
        var tempRoleList = [];
        _.forEach(data.roles, function (roleData) {
            var role = new RexRole(roleData.name, roleData.displayName, roleData.capabilities);
            role.hasUsers = roleData.hasUsers;
            tempRoleList.push(role);
            _this.actorLookup[role.id()] = role;
        });
        this.roles = ko.observableArray(tempRoleList);
        var tempUserList = [];
        _.forEach(AmeActors.getUsers(), function (data) {
            var user = RexUser.fromAmeUser(data, self);
            tempUserList.push(user);
            _this.actorLookup[user.id()] = user;
        });
        this.users = ko.observableArray(tempUserList);
        this.dummyActor = new RexRole('rex-invalid-role', 'Invalid Role');
        this.defaultNewUserRoleName = data.defaultRoleName;
        this.trashedRoles = ko.observableArray(_.map(data.trashedRoles, function (roleData) {
            return RexRole.fromRoleData(roleData);
        }));
        this.actorSelector = new AmeActorSelector(this, true, false);
        //Wrap the selected actor in a computed observable so that it can be used with Knockout.
        var _selectedActor = ko.observable(this.getActor(this.actorSelector.selectedActor));
        this.selectedActor = ko.computed({
            read: function () {
                return _selectedActor();
            },
            write: function (newActor) {
                _this.actorSelector.setSelectedActor(newActor.id());
            }
        });
        this.actorSelector.onChange(function (newSelectedActor) {
            _selectedActor(_this.getActor(newSelectedActor));
        });
        //Refresh the actor selector when roles are added or removed.
        this.roles.subscribe(function () {
            _this.actorSelector.repopulate();
        });
        //Re-select the previously selected actor if possible.
        var initialActor = null;
        if (data.selectedActor) {
            initialActor = this.getActor(data.selectedActor);
        }
        if (!initialActor || (initialActor === this.dummyActor)) {
            initialActor = this.roles()[0];
        }
        this.selectedActor(initialActor);
        //Populate capabilities.
        this.deprecatedCapabilities = data.deprecatedCapabilities;
        this.metaCapabilityMap = data.metaCapMap;
        this.userDefinedCapabilities = data.userDefinedCapabilities;
        this.capabilities = _.mapValues(data.capabilities, function (metadata, name) {
            return RexCapability.fromJs(name, metadata, self);
        });
        //Add the special "do_not_allow" capability. Normally, it's impossible to assign it to anyone,
        //but it can still be used in post type permissions and other places.
        var doNotAllow = new RexDoNotAllowCapability(this);
        doNotAllow.originComponent = this.components[':wordpress:'];
        this.capabilities['do_not_allow'] = doNotAllow;
        //Similarly, "exist" is always enabled for all roles and users. Everyone can exist.
        if (this.capabilities.hasOwnProperty('exist')) {
            this.capabilities['exist'] = new RexExistCapability(this);
            this.capabilities['exist'].originComponent = this.components[':wordpress:'];
        }
        //Store editable roles.
        this.actorEditableRoles = (!_.isEmpty(data.editableRoles)) ? data.editableRoles : {};
        this.rootCategory = new RexCategory('All', this);
        var coreCategory = RexCategory.fromJs(data.coreCategory, this);
        this.rootCategory.addSubcategory(coreCategory);
        var postTypeCategory = new RexPostTypeContainerCategory('Post Types', this, 'postTypes');
        this.postTypes = _.indexBy(data.postTypes, 'name');
        _.forEach(this.postTypes, function (details, id) {
            var category = new RexPostTypeCategory(details.label, self, id, 'postTypes/' + id, details.permissions, details.isDefault);
            if (details.componentId) {
                category.origin = _this.getComponent(details.componentId);
            }
            postTypeCategory.addSubcategory(category);
            //Record the post type actions associated with each capability.
            for (var action in details.permissions) {
                var capability = self.getCapability(details.permissions[action]);
                _.set(capability.usedByPostTypeActions, [details.name, action], true);
            }
        });
        //Sort the actual subcategory array.
        postTypeCategory.sortSubcategories();
        this.rootCategory.addSubcategory(postTypeCategory);
        //Taxonomies.
        this.taxonomies = data.taxonomies;
        var taxonomyCategory = new RexTaxonomyContainerCategory('Taxonomies', this, 'taxonomies');
        _.forEach(data.taxonomies, function (details, id) {
            var category = new RexTaxonomyCategory(details.label, self, id, 'taxonomies/' + id, details.permissions);
            taxonomyCategory.addSubcategory(category);
            //Record taxonomy type actions associated with each capability.
            for (var action in details.permissions) {
                var capability = self.getCapability(details.permissions[action]);
                _.set(capability.usedByTaxonomyActions, [details.name, action], true);
            }
        });
        taxonomyCategory.subcategories.sort(function (a, b) {
            return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
        });
        this.rootCategory.addSubcategory(taxonomyCategory);
        var customParentCategory = new RexCategory('Plugins', this, 'custom');
        function initCustomCategory(details, parent) {
            var category = RexCategory.fromJs(details, self);
            //Sort subcategories by title.
            category.subcategories.sort(function (a, b) {
                //Keep the "General" category at the top if there is one.
                if (a.name === b.name) {
                    return 0;
                }
                else if (a.name === 'General') {
                    return -1;
                }
                else if (b.name === 'General') {
                    return 1;
                }
                return a.name.localeCompare(b.name);
            });
            parent.addSubcategory(category);
        }
        _.forEach(data.customCategories, function (details) {
            initCustomCategory(details, customParentCategory);
        });
        customParentCategory.subcategories.sort(function (a, b) {
            return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
        });
        this.rootCategory.addSubcategory(customParentCategory);
        //Make a category for uncategorized capabilities. This one is always at the bottom.
        var uncategorizedCategory = new RexCategory('Uncategorized', self, 'custom/uncategorized', data.uncategorizedCapabilities);
        customParentCategory.addSubcategory(uncategorizedCategory);
        var _selectedCategory = ko.observable(null);
        this.selectedCategory = ko.computed({
            read: function () {
                return _selectedCategory();
            },
            write: function (newSelection) {
                var oldSelection = _selectedCategory();
                if (newSelection === oldSelection) {
                    return;
                }
                if (newSelection) {
                    newSelection.isSelected(true);
                }
                if (oldSelection) {
                    oldSelection.isSelected(false);
                }
                _selectedCategory(newSelection);
            }
        });
        this.selectedCategory(this.rootCategory);
        this.permissionTipSubject = ko.observable(null);
        this.allCapabilitiesAsPermissions = ko.pureComputed({
            read: function () {
                //Create a permission for each unique, non-deleted capability.
                //Exclude special caps like do_not_allow and exist because they can't be enabled.
                var excludedCaps = ['do_not_allow', 'exist'];
                return _.chain(_this.capabilities)
                    .map(function (capability) {
                    if (excludedCaps.indexOf(capability.name) >= 0) {
                        return null;
                    }
                    return new RexPermission(self, capability);
                })
                    .filter(function (value) {
                    return value !== null;
                })
                    .value();
            },
            deferEvaluation: true
        });
        this.capsInSelectedCategory = ko.pureComputed({
            read: function () {
                var category = _this.selectedCategory();
                if (!category) {
                    return {};
                }
                var caps = {};
                category.countUniqueCapabilities(caps);
                return caps;
            },
            deferEvaluation: true
        });
        this.leafCategories = ko.computed({
            read: function () {
                //So what we want here is a depth-first traversal of the category tree.
                var results = [];
                var addedUniqueCategories = {};
                function traverse(category) {
                    if (category.subcategories.length < 1) {
                        //Eliminate duplicates, like CPTs that show up in the post type category and a plugin category.
                        var key = category.getDeDuplicationKey();
                        if (!addedUniqueCategories.hasOwnProperty(key)) {
                            results.push(category);
                            addedUniqueCategories[key] = category;
                        }
                        else {
                            addedUniqueCategories[key].addDuplicate(category);
                        }
                        return;
                    }
                    for (var i = 0; i < category.subcategories.length; i++) {
                        traverse(category.subcategories[i]);
                    }
                }
                traverse(_this.rootCategory);
                results.sort(function (a, b) {
                    return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
                });
                return results;
            },
            deferEvaluation: true
        });
        var compareRoleDisplayNames = function (a, b) {
            return a.displayName().toLowerCase().localeCompare(b.displayName().toLowerCase());
        };
        this.defaultRoles = ko.pureComputed({
            read: function () {
                return _.filter(self.roles(), function (role) {
                    return role.isBuiltIn();
                }).sort(compareRoleDisplayNames);
            },
            deferEvaluation: true
        });
        this.customRoles = ko.computed({
            read: function () {
                return _.difference(self.roles(), self.defaultRoles()).sort(compareRoleDisplayNames);
            },
            deferEvaluation: true
        });
        this.deleteCapabilityDialog = new RexDeleteCapDialog(this);
        this.addCapabilityDialog = new RexAddCapabilityDialog(this);
        this.addRoleDialog = new RexAddRoleDialog(this);
        this.deleteRoleDialog = new RexDeleteRoleDialog(this);
        this.renameRoleDialog = new RexRenameRoleDialog(this);
        this.editableRolesDialog = new RexEditableRolesDialog(this);
        this.userRoleModule = new RexUserRoleModule(this.selectedActor, this.roles);
        this.settingsFieldData = ko.observable('');
        this.isSaving = ko.observable(false);
        this.isGlobalSettingsUpdate = ko.observable(false);
    }
    RexRoleEditor.prototype.capabilityMatchesFilters = function (capability) {
        if (!this.showDeprecatedEnabled() && this.isDeprecated(capability.name)) {
            return false;
        }
        if (this.showOnlyCheckedEnabled() && !capability.isEnabledForSelectedActor()) {
            return false;
        }
        var keywords = this.searchKeywords(), capabilityName = capability.name;
        if (keywords.length > 0) {
            var haystack_1 = capabilityName.toLowerCase();
            var matchesKeywords = wsAmeLodash.all(keywords, function (keyword) {
                return haystack_1.indexOf(keyword) >= 0;
            });
            if (!matchesKeywords) {
                return false;
            }
        }
        return true;
    };
    RexRoleEditor.prototype.isDeprecated = function (capability) {
        return this.deprecatedCapabilities.hasOwnProperty(capability);
    };
    RexRoleEditor.prototype.getComponent = function (componentId) {
        if (this.components.hasOwnProperty(componentId)) {
            return this.components[componentId];
        }
        return null;
    };
    /**
     * Get or create a capability instance.
     */
    RexRoleEditor.prototype.getCapability = function (capabilityName, recursionDepth) {
        if (recursionDepth === void 0) { recursionDepth = 0; }
        //Un-map meta capabilities where possible.
        if (this.metaCapabilityMap.hasOwnProperty(capabilityName) && (recursionDepth < 10)) {
            return this.getCapability(this.metaCapabilityMap[capabilityName], recursionDepth + 1);
        }
        if (!this.capabilities.hasOwnProperty(capabilityName)) {
            var _1 = wsAmeLodash;
            if (!_1.isString(capabilityName) && !_1.isFinite(capabilityName)) {
                return this.getInvalidCapability(capabilityName);
            }
            if (console && console.info) {
                console.info('Capability not found: "' + capabilityName + '". It will be created.');
            }
            capabilityName = String(capabilityName);
            this.capabilities[capabilityName] = new RexCapability(capabilityName, this);
        }
        return this.capabilities[capabilityName];
    };
    RexRoleEditor.prototype.getInvalidCapability = function (invalidName) {
        var capabilityName = '[Invalid capability: ' + String(invalidName) + ']';
        if (!this.capabilities.hasOwnProperty(capabilityName)) {
            if (console && console.error) {
                console.error('Invalid capability detected - expected a string but got this: ', invalidName);
            }
            this.capabilities[capabilityName] = new RexInvalidCapability(capabilityName, invalidName, this);
        }
        return this.capabilities[capabilityName];
    };
    RexRoleEditor.prototype.getActor = function (actorId) {
        if (this.actorLookup.hasOwnProperty(actorId)) {
            return this.actorLookup[actorId];
        }
        return this.dummyActor;
    };
    RexRoleEditor.prototype.getRole = function (name) {
        var actorId = 'role:' + name;
        if (this.actorLookup.hasOwnProperty(actorId)) {
            var role = this.actorLookup[actorId];
            if (role instanceof RexRole) {
                return role;
            }
        }
        return null;
    };
    // noinspection JSUnusedGlobalSymbols Testing method used in KO templates.
    RexRoleEditor.prototype.setSubjectPermission = function (permission) {
        this.permissionTipSubject(permission);
    };
    /**
     * Search a string for the current search keywords and add the "rex-search-highlight" CSS class to each match.
     *
     * @param inputString
     */
    RexRoleEditor.prototype.highlightSearchKeywords = function (inputString) {
        var _ = wsAmeLodash;
        var keywordList = this.searchKeywords();
        if (keywordList.length === 0) {
            return inputString;
        }
        var keywordGroup = _.map(keywordList, _.escapeRegExp).join('|');
        var regex = new RegExp('((?:' + keywordGroup + ')(?:\\s*))+', 'gi');
        return inputString.replace(regex, function (foundKeywords) {
            //Don't highlight the trailing space after the keyword(s).
            var trailingSpace = '';
            var parts = foundKeywords.match(/^(.+?)(\s+)$/);
            if (parts) {
                foundKeywords = parts[1];
                trailingSpace = parts[2];
            }
            return '<mark class="rex-search-highlight">' + foundKeywords + '</mark>' + trailingSpace;
        });
    };
    RexRoleEditor.prototype.actorExists = function (actorId) {
        return this.actorLookup.hasOwnProperty(actorId);
    };
    RexRoleEditor.prototype.addUsers = function (newUsers) {
        var _this = this;
        wsAmeLodash.forEach(newUsers, function (user) {
            if (!(user instanceof RexUser)) {
                if (console.error) {
                    console.error('Cannot add a user. Expected an instance of RexUser, got this:', user);
                }
                return;
            }
            if (!_this.actorLookup.hasOwnProperty(user.getId())) {
                _this.users.push(user);
                _this.actorLookup[user.getId()] = user;
            }
        });
    };
    RexRoleEditor.prototype.createUserFromProperties = function (properties) {
        return RexUser.fromAmeUserProperties(properties, this);
    };
    RexRoleEditor.prototype.getRoles = function () {
        return wsAmeLodash.indexBy(this.roles(), function (role) {
            return role.name();
        });
    };
    RexRoleEditor.prototype.getSuperAdmin = function () {
        return RexSuperAdmin.getInstance();
    };
    RexRoleEditor.prototype.getUser = function (login) {
        var actorId = 'user:' + login;
        if (this.actorLookup.hasOwnProperty(actorId)) {
            var user = this.actorLookup[actorId];
            if (user instanceof RexUser) {
                return user;
            }
        }
        return null;
    };
    RexRoleEditor.prototype.getUsers = function () {
        return wsAmeLodash.indexBy(this.users(), 'userLogin');
    };
    RexRoleEditor.prototype.isInSelectedCategory = function (capabilityName) {
        var caps = this.capsInSelectedCategory();
        return caps.hasOwnProperty(capabilityName);
    };
    RexRoleEditor.prototype.addCapability = function (capabilityName) {
        var capability;
        if (this.capabilities.hasOwnProperty(capabilityName)) {
            capability = this.capabilities[capabilityName];
            if (!capability.isDeleted()) {
                throw 'Cannot add capability "' + capabilityName + '" because it already exists.';
            }
            capability.isDeleted(false);
            this.userDefinedCapabilities[capabilityName] = true;
            return null;
        }
        else {
            capability = new RexCapability(capabilityName, this);
            capability.notes = 'This capability has not been saved yet. Click the "Save Changes" button to save it.';
            this.capabilities[capabilityName] = capability;
            //Add the new capability to the "Other" or "Uncategorized" category.
            var category = this.categoriesBySlug['custom/uncategorized'];
            var permission = new RexPermission(this, capability);
            category.permissions.push(permission);
            category.sortPermissions();
            this.userDefinedCapabilities[capabilityName] = true;
            return category;
        }
    };
    RexRoleEditor.prototype.deleteCapabilities = function (selectedCapabilities) {
        var self = this, _ = wsAmeLodash;
        var targetActors = _.union(this.roles(), this.users());
        _.forEach(selectedCapabilities, function (capability) {
            //Remove it from all roles and visible users.
            _.forEach(targetActors, function (actor) {
                actor.deleteCap(capability.name);
            });
            capability.isDeleted(true);
            delete self.userDefinedCapabilities[capability.name];
        });
    };
    RexRoleEditor.prototype.capabilityExists = function (capabilityName) {
        return this.capabilities.hasOwnProperty(capabilityName) && !this.capabilities[capabilityName].isDeleted();
    };
    RexRoleEditor.prototype.addRole = function (name, displayName, capabilities) {
        if (capabilities === void 0) { capabilities = {}; }
        var role = new RexRole(name, displayName, capabilities);
        this.actorLookup[role.id()] = role;
        this.roles.push(role);
        //Select the new role.
        this.selectedActor(role);
        return role;
    };
    RexRoleEditor.prototype.deleteRoles = function (roles) {
        var _this = this;
        var _ = wsAmeLodash;
        _.forEach(roles, function (role) {
            if (!_this.canDeleteRole(role)) {
                throw 'Cannot delete role "' + role.name() + '"';
            }
        });
        this.roles.removeAll(roles);
        this.trashedRoles.push.apply(this.trashedRoles, roles);
        //TODO: Later, add an option to restore deleted roles.
    };
    RexRoleEditor.prototype.canDeleteRole = function (role) {
        //Was the role already assigned to any users when the editor was opened?
        if (role.hasUsers) {
            return false;
        }
        //We also need to take into account any unsaved user role changes.
        //Is the role assigned to any of the users currently loaded in the editor?
        var _ = wsAmeLodash;
        if (_.some(this.users(), function (user) {
            return (user.roles.indexOf(role) !== -1);
        })) {
            return false;
        }
        return !this.isDefaultRoleForNewUsers(role);
    };
    RexRoleEditor.prototype.isDefaultRoleForNewUsers = function (role) {
        return (role.name() === this.defaultNewUserRoleName);
    };
    // noinspection JSUnusedGlobalSymbols Used in KO templates.
    RexRoleEditor.prototype.saveChanges = function () {
        this.isSaving(true);
        var _ = wsAmeLodash;
        var data = {
            'roles': _.invoke(this.roles(), 'toJs'),
            'users': _.invoke(this.users(), 'toJs'),
            'trashedRoles': _.invoke(this.trashedRoles(), 'toJs'),
            'userDefinedCaps': _.keys(this.userDefinedCapabilities),
            'editableRoles': this.actorEditableRoles
        };
        this.settingsFieldData(ko.toJSON(data));
        jQuery('#rex-save-settings-form').submit();
    };
    RexRoleEditor.prototype.updateAllSites = function () {
        if (!confirm('Apply these role settings to ALL sites? Any changes that you\'ve made to individual sites will be lost.')) {
            return false;
        }
        this.isGlobalSettingsUpdate(true);
        this.saveChanges();
    };
    RexRoleEditor.hierarchyView = {
        label: 'Hierarchy view',
        id: 'hierarchy',
        templateName: 'rex-hierarchy-view-template'
    };
    RexRoleEditor.singleCategoryView = {
        label: 'Category view',
        id: 'category',
        templateName: 'rex-single-category-view-template'
    };
    RexRoleEditor.listView = { label: 'List view', id: 'list', templateName: 'rex-list-view-template' };
    return RexRoleEditor;
}());
(function () {
    jQuery(function ($) {
        var rootElement = jQuery('#ame-role-editor-root');
        //Initialize the application.
        var app = new RexRoleEditor(wsRexRoleEditorData);
        //The input data can be quite large, so let's give the browser a chance to free up that memory.
        wsRexRoleEditorData = null;
        window['ameRoleEditor'] = app;
        //console.time('Apply Knockout bindings');
        //ko.options.deferUpdates = true;
        ko.applyBindings(app, rootElement.get(0));
        app.areBindingsApplied(true);
        //console.timeEnd('Apply Knockout bindings');
        //Track the state of the Shift key.
        var isShiftKeyDown = false;
        function handleKeyboardEvent(event) {
            var newState = !!(event.shiftKey);
            if (newState !== isShiftKeyDown) {
                isShiftKeyDown = newState;
                app.isShiftKeyDown(isShiftKeyDown);
            }
        }
        $(document).on('keydown.adminMenuEditorRex keyup.adminMenuEditorRex mousedown.adminMenuEditorRex', handleKeyboardEvent);
        //Initialize permission tooltips.
        var visiblePermissionTooltips = [];
        rootElement.find('#rex-capability-view').on('mouseenter click', '.rex-permission-tip-trigger', function (event) {
            $(this).qtip({
                overwrite: false,
                content: {
                    text: 'Loading...'
                },
                //Show the tooltip on focus.
                show: {
                    event: 'click mouseenter',
                    delay: 80,
                    solo: '#ame-role-editor-root',
                    ready: true,
                    effect: false
                },
                hide: {
                    event: 'mouseleave unfocus',
                    fixed: true,
                    delay: 300,
                    leave: false,
                    effect: false
                },
                position: {
                    my: 'center left',
                    at: 'center right',
                    effect: false,
                    viewport: $(window),
                    adjust: {
                        method: 'flipinvert shift',
                        scroll: false,
                    }
                },
                style: {
                    classes: 'qtip-bootstrap qtip-shadow rex-tooltip'
                },
                events: {
                    show: function (event, api) {
                        //Immediately hide all other permission tooltips.
                        for (var i = visiblePermissionTooltips.length - 1; i >= 0; i--) {
                            visiblePermissionTooltips[i].hide();
                        }
                        var permission = ko.dataFor(api.target.get(0));
                        if (permission && (permission instanceof RexPermission)) {
                            app.permissionTipSubject(permission);
                        }
                        //Move the content container to the current tooltip.
                        var tipContent = $('#rex-permission-tip');
                        if (!$.contains(api.elements.content.get(0), tipContent.get(0))) {
                            api.elements.content.empty().append(tipContent);
                        }
                        visiblePermissionTooltips.push(api);
                    },
                    hide: function (event, api) {
                        var index = visiblePermissionTooltips.indexOf(api);
                        if (index >= 0) {
                            visiblePermissionTooltips.splice(index, 1);
                        }
                    }
                }
            }, event);
        });
        //Tooltips must have a higher z-index than the modal widget overlay and the Toolbar.
        jQuery.fn.qtip.zindex = 100101 + 5000;
        //Set up dropdown menus.
        $('.rex-dropdown-trigger').on('click', function (event) {
            var $trigger = $(this);
            var $dropdown = $('#' + $trigger.data('target-dropdown-id'));
            event.stopPropagation();
            event.preventDefault();
            function hideThisDropdown(event) {
                //Only do it if the user clicked something outside the dropdown.
                var $clickedDropdown = $(event.target).closest($dropdown.get(0));
                if ($clickedDropdown.length < 1) {
                    $dropdown.hide();
                    $(document).off('click', hideThisDropdown);
                }
            }
            if ($dropdown.is(':visible')) {
                $dropdown.hide();
                $(document).off('click', hideThisDropdown);
                return;
            }
            $dropdown.show().position({
                my: 'left top',
                at: 'left bottom',
                of: $trigger
            });
            $(document).on('click', hideThisDropdown);
        });
    });
})();
