<commit_before>angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.DocTypePickerController", [

    "$scope",
    "Umbraco.PropertyEditors.NestedContent.Resources",

    function ($scope, ncResources) {

        $scope.add = function () {
            $scope.model.value.push({
                // As per PR #4, all stored content type aliases must be prefixed "nc" for easier recognition.
                // For good measure we'll also prefix the tab alias "nc"
                ncAlias: "",
                ncTabAlias: "",
                nameTemplate: ""
            }
            );
        }

        $scope.remove = function (index) {
            $scope.model.value.splice(index, 1);
        }

        $scope.sortableOptions = {
            axis: 'y',
            cursor: "move",
            handle: ".icon-navigation"
        };

        $scope.docTypeTabs = {};

        ncResources.getContentTypes().then(function (docTypes) {
            $scope.model.docTypes = docTypes;

            // Populate document type tab dictionary
            docTypes.forEach(function (value) {
                $scope.docTypeTabs[value.alias] = value.tabs;
            });
        });

        $scope.selectableDocTypesFor = function (config) {
            // return all doctypes that are:
            // 1. either already selected for this config, or
            // 2. not selected in any other config
            return _.filter($scope.model.docTypes, function (docType) {
                return docType.alias === config.ncAlias || !_.find($scope.model.value, function(c) {
                    return docType.alias === c.ncAlias;
                });
            });

        }

        if (!$scope.model.value) {
            $scope.model.value = [];
            $scope.add();
        }
    }
]);

angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.PropertyEditorController", [

    "$scope",
    "$interpolate",
    "$filter",
    "$timeout",
    "contentResource",
    "localizationService",
    "iconHelper",

    function ($scope, $interpolate, $filter, $timeout, contentResource, localizationService, iconHelper) {

        //$scope.model.config.contentTypes;
        //$scope.model.config.minItems;
        //$scope.model.config.maxItems;

        var inited = false;

        _.each($scope.model.config.contentTypes, function (contentType) {
            contentType.nameExp = !!contentType.nameTemplate
                ? $interpolate(contentType.nameTemplate)
                : undefined;
        });

        $scope.nodes = [];
        $scope.currentNode = undefined;
        $scope.realCurrentNode = undefined;
        $scope.scaffolds = undefined;
        $scope.sorting = false;

        $scope.minItems = $scope.model.config.minItems || 0;
        $scope.maxItems = $scope.model.config.maxItems || 0;

        if ($scope.maxItems == 0)
            $scope.maxItems = 1000;

        $scope.singleMode = $scope.minItems == 1 && $scope.maxItems == 1;
        $scope.showIcons = Object.toBoolean($scope.model.config.showIcons);
        $scope.wideMode = Object.toBoolean($scope.model.config.hideLabel);

        $scope.labels = {};
        localizationService.localizeMany(["grid_insertControl"]).then(function(data) {
            $scope.labels.docTypePickerTitle = data[0];
        });

        // helper to force the current form into the dirty state
        $scope.setDirty = function () {
            if ($scope.propertyForm) {
                $scope.propertyForm.$setDirty();
            }
        };

        $scope.addNode = function (alias) {
            var scaffold = $scope.getScaffold(alias);

            var newNode = initNode(scaffold, null);

            $scope.currentNode = newNode;
            $scope.setDirty();
        };

        $scope.openNodeTypePicker = function ($event) {
            if ($scope.nodes.length >= $scope.maxItems) {
                return;
            }

            $scope.overlayMenu = {
                title: $scope.labels.docTypePickerTitle,
                show: false,
                style: {},
                filter: $scope.scaffolds.length > 15 ? true : false,
                orderBy: "$index",
                view: "itempicker",
                event: $event,
                submit: function(model) {
                    if(model && model.selectedItem) {
                        $scope.addNode(model.selectedItem.alias);
                    }
                    $scope.overlayMenu.show = false;
                    $scope.overlayMenu = null;
                },
                close: function() {
                    $scope.overlayMenu.show = false;
                    $scope.overlayMenu = null;
                }
            };

            // this could be used for future limiting on node types
            $scope.overlayMenu.availableItems = [];
            _.each($scope.scaffolds, function (scaffold) {
                $scope.overlayMenu.availableItems.push({
                    alias: scaffold.contentTypeAlias,
                    name: scaffold.contentTypeName,
                    icon: iconHelper.convertFromLegacyIcon(scaffold.icon)
                });
            });

            if ($scope.overlayMenu.availableItems.length === 0) {
                return;
            }

            if ($scope.overlayMenu.availableItems.length === 1) {
                // only one scaffold type - no need to display the picker
                $scope.addNode($scope.scaffolds[0].contentTypeAlias);
                return;
            }

            $scope.overlayMenu.show = true;
        };

        $scope.editNode = function (idx) {
            if ($scope.currentNode && $scope.currentNode.key == $scope.nodes[idx].key) {
                $scope.currentNode = undefined;
            } else {
                $scope.currentNode = $scope.nodes[idx];
            }
        };

        $scope.deleteNode = function (idx) {
            if ($scope.nodes.length > $scope.model.config.minItems) {
                if ($scope.model.config.confirmDeletes && $scope.model.config.confirmDeletes == 1) {
                    localizationService.localize('content_nestedContentDeleteItem').then(function (value) {
                        if (confirm(value)) {
                            $scope.nodes.splice(idx, 1);
                            $scope.setDirty();
                            updateModel();
                        }
                    });
                } else {
                    $scope.nodes.splice(idx, 1);
                    $scope.setDirty();
                    updateModel();
                }
            }
        };

        $scope.getName = function (idx) {

            var name = "Item " + (idx + 1);

            if ($scope.model.value[idx]) {

                var contentType = $scope.getContentTypeConfig($scope.model.value[idx].ncContentTypeAlias);

                if (contentType != null && contentType.nameExp) {
                    // Run the expression against the stored dictionary value, NOT the node object
                    var item = $scope.model.value[idx];

                    // Add a temporary index property
                    item['$index'] = (idx + 1);

                    var newName = contentType.nameExp(item);
                    if (newName && (newName = $.trim(newName))) {
                        name = newName;
                    }

                    // Delete the index property as we don't want to persist it
                    delete item['$index'];
                }

            }

            // Update the nodes actual name value
            if ($scope.nodes[idx].name !== name) {
                $scope.nodes[idx].name = name;
            }

            return name;
        };

        $scope.getIcon = function (idx) {
            var scaffold = $scope.getScaffold($scope.model.value[idx].ncContentTypeAlias);
            return scaffold && scaffold.icon ? iconHelper.convertFromLegacyIcon(scaffold.icon) : "icon-folder";
        }

        $scope.sortableOptions = {
            axis: 'y',
            cursor: "move",
            handle: ".umb-nested-content__icon--move",
            start: function (ev, ui) {
                updateModel();
                // Yea, yea, we shouldn't modify the dom, sue me
                $("#umb-nested-content--" + $scope.model.id + " .umb-rte textarea").each(function () {
                    tinymce.execCommand('mceRemoveEditor', false, $(this).attr('id'));
                    $(this).css("visibility", "hidden");
                });
                $scope.$apply(function () {
                    $scope.sorting = true;
                });
            },
            update: function (ev, ui) {
                $scope.setDirty();
            },
            stop: function (ev, ui) {
                $("#umb-nested-content--" + $scope.model.id + " .umb-rte textarea").each(function () {
                    tinymce.execCommand('mceAddEditor', true, $(this).attr('id'));
                    $(this).css("visibility", "visible");
                });
                $scope.$apply(function () {
                    $scope.sorting = false;
                    updateModel();
                });
            }
        };

        $scope.getScaffold = function (alias) {
            return _.find($scope.scaffolds, function (scaffold) {
                return scaffold.contentTypeAlias == alias;
            });
        }

        $scope.getContentTypeConfig = function (alias) {
            return _.find($scope.model.config.contentTypes, function (contentType) {
                return contentType.ncAlias == alias;
            });
        }

        var notSupported = [
          "Umbraco.Tags",
          "Umbraco.UploadField",
          "Umbraco.ImageCropper"
        ];

        // Initialize
        var scaffoldsLoaded = 0;
        $scope.scaffolds = [];
        _.each($scope.model.config.contentTypes, function (contentType) {
            contentResource.getScaffold(-20, contentType.ncAlias).then(function (scaffold) {
                // make sure it's an element type before allowing the user to create new ones
                if (scaffold.isElement) {
                    // remove all tabs except the specified tab
                    var tabs = scaffold.variants[0].tabs;
                    var tab = _.find(tabs, function (tab) {
                        return tab.id != 0 && (tab.alias.toLowerCase() == contentType.ncTabAlias.toLowerCase() || contentType.ncTabAlias == "");
                    });
                    scaffold.tabs = [];
                    if (tab) {
                        scaffold.tabs.push(tab);

                        angular.forEach(tab.properties,
                            function (property) {
                                if (_.find(notSupported, function (x) { return x === property.editor; })) {
                                    property.notSupported = true;
                                    // TODO: Not supported message to be replaced with 'content_nestedContentEditorNotSupported' dictionary key. Currently not possible due to async/timing quirk.
                                    property.notSupportedMessage = "Property " + property.label + " uses editor " + property.editor + " which is not supported by Nested Content.";
                                }
                            });
                    }

                    // Store the scaffold object
                    $scope.scaffolds.push(scaffold);
                }

                scaffoldsLoaded++;
                initIfAllScaffoldsHaveLoaded();
            }, function (error) {
                scaffoldsLoaded++;
                initIfAllScaffoldsHaveLoaded();
            });
        });

        var initIfAllScaffoldsHaveLoaded = function () {
            // Initialize when all scaffolds have loaded
            if ($scope.model.config.contentTypes.length == scaffoldsLoaded) {
                // Because we're loading the scaffolds async one at a time, we need to
                // sort them explicitly according to the sort order defined by the data type.
                var contentTypeAliases = [];
                _.each($scope.model.config.contentTypes, function (contentType) {
                    contentTypeAliases.push(contentType.ncAlias);
                });
                $scope.scaffolds = $filter('orderBy')($scope.scaffolds, function (s) {
                    return contentTypeAliases.indexOf(s.contentTypeAlias);
                });

                // Convert stored nodes
                if ($scope.model.value) {
                    for (var i = 0; i < $scope.model.value.length; i++) {
                        var item = $scope.model.value[i];
                        var scaffold = $scope.getScaffold(item.ncContentTypeAlias);
                        if (scaffold == null) {
                            // No such scaffold - the content type might have been deleted. We need to skip it.
                            continue;
                        }
                        initNode(scaffold, item);
                    }
                }

                // Enforce min items
                if ($scope.nodes.length < $scope.model.config.minItems) {
                    for (var i = $scope.nodes.length; i < $scope.model.config.minItems; i++) {
                        $scope.addNode($scope.scaffolds[0].contentTypeAlias);
                    }
                }

                // If there is only one item, set it as current node
                if ($scope.singleMode || ($scope.nodes.length == 1 && $scope.maxItems == 1)) {
                    $scope.currentNode = $scope.nodes[0];
                }

                inited = true;
            }
        }

        var initNode = function (scaffold, item) {
            var node = angular.copy(scaffold);

            node.key = item && item.key ? item.key : UUID.generate();
            node.ncContentTypeAlias = scaffold.contentTypeAlias;

            for (var t = 0; t < node.tabs.length; t++) {
                var tab = node.tabs[t];
                for (var p = 0; p < tab.properties.length; p++) {
                    var prop = tab.properties[p];
                    prop.propertyAlias = prop.alias;
                    prop.alias = $scope.model.alias + "___" + prop.alias;
                    // Force validation to occur server side as this is the
                    // only way we can have consistancy between mandatory and
                    // regex validation messages. Not ideal, but it works.
                    prop.validation = {
                        mandatory: false,
                        pattern: ""
                    };
                    if (item) {
                        if (item[prop.propertyAlias]) {
                            prop.value = item[prop.propertyAlias];
                        }
                    }
                }
            }

            $scope.nodes.push(node);

            return node;
        }

        var updateModel = function () {
            if ($scope.realCurrentNode) {
                $scope.$broadcast("ncSyncVal", { key: $scope.realCurrentNode.key });
            }
            if (inited) {
                var newValues = [];
                for (var i = 0; i < $scope.nodes.length; i++) {
                    var node = $scope.nodes[i];
                    var newValue = {
                        key: node.key,
                        name: node.name,
                        ncContentTypeAlias: node.ncContentTypeAlias
                    };
                    for (var t = 0; t < node.tabs.length; t++) {
                        var tab = node.tabs[t];
                        for (var p = 0; p < tab.properties.length; p++) {
                            var prop = tab.properties[p];
                            if (typeof prop.value !== "function") {
                                newValue[prop.propertyAlias] = prop.value;
                            }
                        }
                    }
                    newValues.push(newValue);
                }
                $scope.model.value = newValues;
            }
        }

        $scope.$watch("currentNode", function (newVal) {
            updateModel();
            $scope.realCurrentNode = newVal;
        });

        var unsubscribe = $scope.$on("formSubmitting", function (ev, args) {
            updateModel();
        });

        $scope.$on('$destroy', function () {
            unsubscribe();
        });

        // TODO: Move this into a shared location?
        var UUID = (function () {
            var self = {};
            var lut = []; for (var i = 0; i < 256; i++) { lut[i] = (i < 16 ? '0' : '') + (i).toString(16); }
            self.generate = function () {
                var d0 = Math.random() * 0xffffffff | 0;
                var d1 = Math.random() * 0xffffffff | 0;
                var d2 = Math.random() * 0xffffffff | 0;
                var d3 = Math.random() * 0xffffffff | 0;
                return lut[d0 & 0xff] + lut[d0 >> 8 & 0xff] + lut[d0 >> 16 & 0xff] + lut[d0 >> 24 & 0xff] + '-' +
                  lut[d1 & 0xff] + lut[d1 >> 8 & 0xff] + '-' + lut[d1 >> 16 & 0x0f | 0x40] + lut[d1 >> 24 & 0xff] + '-' +
                  lut[d2 & 0x3f | 0x80] + lut[d2 >> 8 & 0xff] + '-' + lut[d2 >> 16 & 0xff] + lut[d2 >> 24 & 0xff] +
                  lut[d3 & 0xff] + lut[d3 >> 8 & 0xff] + lut[d3 >> 16 & 0xff] + lut[d3 >> 24 & 0xff];
            }
            return self;
        })();
    }

]);
<commit_msg>General clean up, use === and double quotes everywhere<commit_after>angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.DocTypePickerController", [

    "$scope",
    "Umbraco.PropertyEditors.NestedContent.Resources",

    function ($scope, ncResources) {

        $scope.add = function () {
            $scope.model.value.push({
                // As per PR #4, all stored content type aliases must be prefixed "nc" for easier recognition.
                // For good measure we'll also prefix the tab alias "nc"
                ncAlias: "",
                ncTabAlias: "",
                nameTemplate: ""
            }
            );
        }

        $scope.remove = function (index) {
            $scope.model.value.splice(index, 1);
        }

        $scope.sortableOptions = {
            axis: "y",
            cursor: "move",
            handle: ".icon-navigation"
        };

        $scope.docTypeTabs = {};

        ncResources.getContentTypes().then(function (docTypes) {
            $scope.model.docTypes = docTypes;

            // Populate document type tab dictionary
            docTypes.forEach(function (value) {
                $scope.docTypeTabs[value.alias] = value.tabs;
            });
        });

        $scope.selectableDocTypesFor = function (config) {
            // return all doctypes that are:
            // 1. either already selected for this config, or
            // 2. not selected in any other config
            return _.filter($scope.model.docTypes, function (docType) {
                return docType.alias === config.ncAlias || !_.find($scope.model.value, function(c) {
                    return docType.alias === c.ncAlias;
                });
            });

        }

        if (!$scope.model.value) {
            $scope.model.value = [];
            $scope.add();
        }
    }
]);

angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.PropertyEditorController", [

    "$scope",
    "$interpolate",
    "$filter",
    "$timeout",
    "contentResource",
    "localizationService",
    "iconHelper",

    function ($scope, $interpolate, $filter, $timeout, contentResource, localizationService, iconHelper) {

        var inited = false;

        _.each($scope.model.config.contentTypes, function (contentType) {
            contentType.nameExp = !!contentType.nameTemplate
                ? $interpolate(contentType.nameTemplate)
                : undefined;
        });

        $scope.nodes = [];
        $scope.currentNode = undefined;
        $scope.realCurrentNode = undefined;
        $scope.scaffolds = undefined;
        $scope.sorting = false;

        $scope.minItems = $scope.model.config.minItems || 0;
        $scope.maxItems = $scope.model.config.maxItems || 0;

        if ($scope.maxItems === 0)
            $scope.maxItems = 1000;

        $scope.singleMode = $scope.minItems === 1 && $scope.maxItems === 1;
        $scope.showIcons = Object.toBoolean($scope.model.config.showIcons);
        $scope.wideMode = Object.toBoolean($scope.model.config.hideLabel);

        $scope.labels = {};
        localizationService.localizeMany(["grid_insertControl"]).then(function(data) {
            $scope.labels.docTypePickerTitle = data[0];
        });

        // helper to force the current form into the dirty state
        $scope.setDirty = function () {
            if ($scope.propertyForm) {
                $scope.propertyForm.$setDirty();
            }
        };

        $scope.addNode = function (alias) {
            var scaffold = $scope.getScaffold(alias);

            var newNode = initNode(scaffold, null);

            $scope.currentNode = newNode;
            $scope.setDirty();
        };

        $scope.openNodeTypePicker = function ($event) {
            if ($scope.nodes.length >= $scope.maxItems) {
                return;
            }

            $scope.overlayMenu = {
                title: $scope.labels.docTypePickerTitle,
                show: false,
                style: {},
                filter: $scope.scaffolds.length > 15 ? true : false,
                orderBy: "$index",
                view: "itempicker",
                event: $event,
                submit: function(model) {
                    if(model && model.selectedItem) {
                        $scope.addNode(model.selectedItem.alias);
                    }
                    $scope.overlayMenu.show = false;
                    $scope.overlayMenu = null;
                },
                close: function() {
                    $scope.overlayMenu.show = false;
                    $scope.overlayMenu = null;
                }
            };

            // this could be used for future limiting on node types
            $scope.overlayMenu.availableItems = [];
            _.each($scope.scaffolds, function (scaffold) {
                $scope.overlayMenu.availableItems.push({
                    alias: scaffold.contentTypeAlias,
                    name: scaffold.contentTypeName,
                    icon: iconHelper.convertFromLegacyIcon(scaffold.icon)
                });
            });

            if ($scope.overlayMenu.availableItems.length === 0) {
                return;
            }

            if ($scope.overlayMenu.availableItems.length === 1) {
                // only one scaffold type - no need to display the picker
                $scope.addNode($scope.scaffolds[0].contentTypeAlias);
                return;
            }

            $scope.overlayMenu.show = true;
        };

        $scope.editNode = function (idx) {
            if ($scope.currentNode && $scope.currentNode.key === $scope.nodes[idx].key) {
                $scope.currentNode = undefined;
            } else {
                $scope.currentNode = $scope.nodes[idx];
            }
        };

        $scope.deleteNode = function (idx) {
            if ($scope.nodes.length > $scope.model.config.minItems) {
                if ($scope.model.config.confirmDeletes && $scope.model.config.confirmDeletes === 1) {
                    localizationService.localize("content_nestedContentDeleteItem").then(function (value) {
                        if (confirm(value)) {
                            $scope.nodes.splice(idx, 1);
                            $scope.setDirty();
                            updateModel();
                        }
                    });
                } else {
                    $scope.nodes.splice(idx, 1);
                    $scope.setDirty();
                    updateModel();
                }
            }
        };

        $scope.getName = function (idx) {

            var name = "Item " + (idx + 1);

            if ($scope.model.value[idx]) {

                var contentType = $scope.getContentTypeConfig($scope.model.value[idx].ncContentTypeAlias);

                if (contentType != null && contentType.nameExp) {
                    // Run the expression against the stored dictionary value, NOT the node object
                    var item = $scope.model.value[idx];

                    // Add a temporary index property
                    item["$index"] = (idx + 1);

                    var newName = contentType.nameExp(item);
                    if (newName && (newName = $.trim(newName))) {
                        name = newName;
                    }

                    // Delete the index property as we don't want to persist it
                    delete item["$index"];
                }

            }

            // Update the nodes actual name value
            if ($scope.nodes[idx].name !== name) {
                $scope.nodes[idx].name = name;
            }

            return name;
        };

        $scope.getIcon = function (idx) {
            var scaffold = $scope.getScaffold($scope.model.value[idx].ncContentTypeAlias);
            return scaffold && scaffold.icon ? iconHelper.convertFromLegacyIcon(scaffold.icon) : "icon-folder";
        }

        $scope.sortableOptions = {
            axis: "y",
            cursor: "move",
            handle: ".umb-nested-content__icon--move",
            start: function (ev, ui) {
                updateModel();
                // Yea, yea, we shouldn't modify the dom, sue me
                $("#umb-nested-content--" + $scope.model.id + " .umb-rte textarea").each(function () {
                    tinymce.execCommand("mceRemoveEditor", false, $(this).attr("id"));
                    $(this).css("visibility", "hidden");
                });
                $scope.$apply(function () {
                    $scope.sorting = true;
                });
            },
            update: function (ev, ui) {
                $scope.setDirty();
            },
            stop: function (ev, ui) {
                $("#umb-nested-content--" + $scope.model.id + " .umb-rte textarea").each(function () {
                    tinymce.execCommand("mceAddEditor", true, $(this).attr("id"));
                    $(this).css("visibility", "visible");
                });
                $scope.$apply(function () {
                    $scope.sorting = false;
                    updateModel();
                });
            }
        };

        $scope.getScaffold = function (alias) {
            return _.find($scope.scaffolds, function (scaffold) {
                return scaffold.contentTypeAlias === alias;
            });
        }

        $scope.getContentTypeConfig = function (alias) {
            return _.find($scope.model.config.contentTypes, function (contentType) {
                return contentType.ncAlias === alias;
            });
        }

        var notSupported = [
          "Umbraco.Tags",
          "Umbraco.UploadField",
          "Umbraco.ImageCropper"
        ];

        // Initialize
        var scaffoldsLoaded = 0;
        $scope.scaffolds = [];
        _.each($scope.model.config.contentTypes, function (contentType) {
            contentResource.getScaffold(-20, contentType.ncAlias).then(function (scaffold) {
                // make sure it's an element type before allowing the user to create new ones
                if (scaffold.isElement) {
                    // remove all tabs except the specified tab
                    var tabs = scaffold.variants[0].tabs;
                    var tab = _.find(tabs, function (tab) {
                        return tab.id !== 0 && (tab.alias.toLowerCase() === contentType.ncTabAlias.toLowerCase() || contentType.ncTabAlias === "");
                    });
                    scaffold.tabs = [];
                    if (tab) {
                        scaffold.tabs.push(tab);

                        angular.forEach(tab.properties,
                            function (property) {
                                if (_.find(notSupported, function (x) { return x === property.editor; })) {
                                    property.notSupported = true;
                                    // TODO: Not supported message to be replaced with 'content_nestedContentEditorNotSupported' dictionary key. Currently not possible due to async/timing quirk.
                                    property.notSupportedMessage = "Property " + property.label + " uses editor " + property.editor + " which is not supported by Nested Content.";
                                }
                            });
                    }

                    // Store the scaffold object
                    $scope.scaffolds.push(scaffold);
                }

                scaffoldsLoaded++;
                initIfAllScaffoldsHaveLoaded();
            }, function (error) {
                scaffoldsLoaded++;
                initIfAllScaffoldsHaveLoaded();
            });
        });

        var initIfAllScaffoldsHaveLoaded = function () {
            // Initialize when all scaffolds have loaded
            if ($scope.model.config.contentTypes.length === scaffoldsLoaded) {
                // Because we're loading the scaffolds async one at a time, we need to
                // sort them explicitly according to the sort order defined by the data type.
                var contentTypeAliases = [];
                _.each($scope.model.config.contentTypes, function (contentType) {
                    contentTypeAliases.push(contentType.ncAlias);
                });
                $scope.scaffolds = $filter("orderBy")($scope.scaffolds, function (s) {
                    return contentTypeAliases.indexOf(s.contentTypeAlias);
                });

                // Convert stored nodes
                if ($scope.model.value) {
                    for (var i = 0; i < $scope.model.value.length; i++) {
                        var item = $scope.model.value[i];
                        var scaffold = $scope.getScaffold(item.ncContentTypeAlias);
                        if (scaffold == null) {
                            // No such scaffold - the content type might have been deleted. We need to skip it.
                            continue;
                        }
                        initNode(scaffold, item);
                    }
                }

                // Enforce min items
                if ($scope.nodes.length < $scope.model.config.minItems) {
                    for (var i = $scope.nodes.length; i < $scope.model.config.minItems; i++) {
                        $scope.addNode($scope.scaffolds[0].contentTypeAlias);
                    }
                }

                // If there is only one item, set it as current node
                if ($scope.singleMode || ($scope.nodes.length === 1 && $scope.maxItems === 1)) {
                    $scope.currentNode = $scope.nodes[0];
                }

                inited = true;
            }
        }

        var initNode = function (scaffold, item) {
            var node = angular.copy(scaffold);

            node.key = item && item.key ? item.key : UUID.generate();
            node.ncContentTypeAlias = scaffold.contentTypeAlias;

            for (var t = 0; t < node.tabs.length; t++) {
                var tab = node.tabs[t];
                for (var p = 0; p < tab.properties.length; p++) {
                    var prop = tab.properties[p];
                    prop.propertyAlias = prop.alias;
                    prop.alias = $scope.model.alias + "___" + prop.alias;
                    // Force validation to occur server side as this is the
                    // only way we can have consistency between mandatory and
                    // regex validation messages. Not ideal, but it works.
                    prop.validation = {
                        mandatory: false,
                        pattern: ""
                    };
                    if (item) {
                        if (item[prop.propertyAlias]) {
                            prop.value = item[prop.propertyAlias];
                        }
                    }
                }
            }

            $scope.nodes.push(node);

            return node;
        }

        var updateModel = function () {
            if ($scope.realCurrentNode) {
                $scope.$broadcast("ncSyncVal", { key: $scope.realCurrentNode.key });
            }
            if (inited) {
                var newValues = [];
                for (var i = 0; i < $scope.nodes.length; i++) {
                    var node = $scope.nodes[i];
                    var newValue = {
                        key: node.key,
                        name: node.name,
                        ncContentTypeAlias: node.ncContentTypeAlias
                    };
                    for (var t = 0; t < node.tabs.length; t++) {
                        var tab = node.tabs[t];
                        for (var p = 0; p < tab.properties.length; p++) {
                            var prop = tab.properties[p];
                            if (typeof prop.value !== "function") {
                                newValue[prop.propertyAlias] = prop.value;
                            }
                        }
                    }
                    newValues.push(newValue);
                }
                $scope.model.value = newValues;
            }
        }

        $scope.$watch("currentNode", function (newVal) {
            updateModel();
            $scope.realCurrentNode = newVal;
        });

        var unsubscribe = $scope.$on("formSubmitting", function (ev, args) {
            updateModel();
        });

        $scope.$on("$destroy", function () {
            unsubscribe();
        });

        // TODO: Move this into a shared location?
        var UUID = (function () {
            var self = {};
            var lut = []; for (var i = 0; i < 256; i++) { lut[i] = (i < 16 ? "0" : "") + (i).toString(16); }
            self.generate = function () {
                var d0 = Math.random() * 0xffffffff | 0;
                var d1 = Math.random() * 0xffffffff | 0;
                var d2 = Math.random() * 0xffffffff | 0;
                var d3 = Math.random() * 0xffffffff | 0;
                return lut[d0 & 0xff] + lut[d0 >> 8 & 0xff] + lut[d0 >> 16 & 0xff] + lut[d0 >> 24 & 0xff] + "-" +
                  lut[d1 & 0xff] + lut[d1 >> 8 & 0xff] + "-" + lut[d1 >> 16 & 0x0f | 0x40] + lut[d1 >> 24 & 0xff] + "-" +
                  lut[d2 & 0x3f | 0x80] + lut[d2 >> 8 & 0xff] + "-" + lut[d2 >> 16 & 0xff] + lut[d2 >> 24 & 0xff] +
                  lut[d3 & 0xff] + lut[d3 >> 8 & 0xff] + lut[d3 >> 16 & 0xff] + lut[d3 >> 24 & 0xff];
            }
            return self;
        })();
    }

]);
<|endoftext|>


<commit_before>/*
 * ! ${copyright}
 */

sap.ui.define([
	"sap/ui/mdc/odata/v4/TableDelegate",
	"delegates/odata/v4/TableDelegate",
	"sap/ui/mdc/odata/v4/ODataMetaModelUtil",
	"sap/ui/mdc/odata/v4/TypeUtil",
	"sap/ui/mdc/table/Column",
	"sap/ui/unified/Currency",
	"sap/base/Log"
], function (
	TableDelegate,
	TestDelegate,
	ODataMetaModelUtil,
	TypeUtil,
	Column,
	Currency,
	Log
) {
	"use strict";

	/**
	 * Test delegate for OData V4 with analytical capabilities.
	 */
	var AnalyticsTableDelegate = Object.assign({}, TableDelegate, TestDelegate);

	function getMetadataInfo(oTable) {
		return oTable ? oTable.getDelegate().payload : undefined;
	}

	/**
	 * Fetches the relevant metadata for the table and returns property extensions map in a promise.
	 *
	 * @param {Object} oTable Instance of the mdc Table
	 * @param {object[]} aPropertyInfos The property infos received from fetchProperties
	 * @returns {Promise} property extensions promise
	 */
	AnalyticsTableDelegate.fetchPropertyExtensions = function(oTable, aPropertyInfos) {
		var mExtensions = {};
		var oModel = this._getModel(oTable);
		var oMetadataInfo = getMetadataInfo(oTable);
		var sEntitySetPath = "/" + oMetadataInfo.collectionName;
		var oMetaModel = oModel.getMetaModel();

		return Promise.all([
			oMetaModel.requestObject(sEntitySetPath + "/"), oMetaModel.requestObject(sEntitySetPath + "/@")
		]).then(function(aResults) {
			var mEntitySet = aResults[0],
				mAnnotations = aResults[1],
				mAllCustomAggregates = ODataMetaModelUtil.getAllCustomAggregates(mAnnotations);

			// process the Qualifier of the CustomAggregate annotation if it matches the property name in the EntityType
			for (var sKey in mAllCustomAggregates) {
				if (sKey in mEntitySet) {
					if ("contextDefiningProperties" in mAllCustomAggregates[sKey]) {
						var aContextDefiningProperties = mAllCustomAggregates[sKey].contextDefiningProperties,
							aContextDefiningPropertiesPaths = [];

						for (var i = 0; i < aContextDefiningProperties.length; i++) {
							aContextDefiningPropertiesPaths.push(aContextDefiningProperties[i].$PropertyPath);
						}

						mExtensions[sKey] = {
							defaultAggregate: {
								contextDefiningProperties: aContextDefiningPropertiesPaths
							}
						};
					} else {
						mExtensions[sKey] = {
							defaultAggregate: {}
						};
					}
				}
			}

			return mExtensions;
		});
	};

	AnalyticsTableDelegate._createColumnTemplate = function(oPropertyInfo, oTable) {
		var oProperty = oTable.getPropertyHelper().getProperty(oPropertyInfo.name);

		if (oProperty.isComplex()) {
			var aReferencedProperties = oProperty.getReferencedProperties();
			return Promise.resolve(new Currency({
				useSymbol: false,
				value: {
					path: aReferencedProperties[0].getPath()
				},
				currency: {
					path: aReferencedProperties[1].getPath()
				}
			}));
		}

		return TestDelegate._createColumnTemplate.apply(this, arguments);
	};

	AnalyticsTableDelegate._getColumnInfo = function(oPropertyInfo, oTable) {
		var oColumnInfo = TestDelegate._getColumnInfo.apply(this, arguments);

		if (oTable.getPropertyHelper().isComplex(oPropertyInfo.name)) {
			oColumnInfo.hAlign = "Right";
			oColumnInfo.width = "15rem";
		}

		return oColumnInfo;
	};

	AnalyticsTableDelegate._createPropertyInfos = function(oTable, oModel) {
		var oMetadataInfo = getMetadataInfo(oTable), aProperties = [], oPropertyInfo, oComplexPropertyInfo, oObj, oPropertyAnnotations;
		var sEntitySetPath = "/" + oMetadataInfo.collectionName;
		var oMetaModel = oModel.getMetaModel();

		return Promise.all([
			oMetaModel.requestObject(sEntitySetPath + "/"), oMetaModel.requestObject(sEntitySetPath + "@")
		]).then(function(aResults) {
			var oEntityType = aResults[0], mEntitySetAnnotations = aResults[1];
			var oSortRestrictions = mEntitySetAnnotations["@Org.OData.Capabilities.V1.SortRestrictions"] || {};
			var oSortRestrictionsInfo = ODataMetaModelUtil.getSortRestrictionsInfo(oSortRestrictions);
			var oFilterRestrictions = mEntitySetAnnotations["@Org.OData.Capabilities.V1.FilterRestrictions"];
			var oFilterRestrictionsInfo = ODataMetaModelUtil.getFilterRestrictionsInfo(oFilterRestrictions);

			for (var sKey in oEntityType) {
				oObj = oEntityType[sKey];
				if (oObj && oObj.$kind === "Property") {

					if (oObj.$isCollection) {
						Log.warning("Complex property with type " + oObj.$Type + " has been ignored");
						continue;
					}

					oPropertyAnnotations = oMetaModel.getObject(sEntitySetPath + "/" + sKey + "@");
					var oUnitAnnotation = oPropertyAnnotations["@Org.OData.Measures.V1.ISOCurrency"] || oPropertyAnnotations["@Org.OData.Measures.V1.Unit"];
					oPropertyInfo = {
						name: sKey,
						label: oPropertyAnnotations["@com.sap.vocabularies.Common.v1.Label"] || sKey,
						sortable: oSortRestrictionsInfo[sKey] ? oSortRestrictionsInfo[sKey].sortable : true,
						filterable: oFilterRestrictionsInfo[sKey] ? oFilterRestrictionsInfo[sKey].filterable : true,
						typeConfig: TypeUtil.getTypeConfig(oObj.$Type),
						maxConditions: ODataMetaModelUtil.isMultiValueFilterExpression(oFilterRestrictionsInfo.propertyInfo[sKey]) ? -1 : 1,

						// relevant for Analytics
						groupable: oPropertyAnnotations["@Org.OData.Aggregation.V1.Groupable"] || false,
						unit: oUnitAnnotation ? oUnitAnnotation.$Path : undefined,
						key: oEntityType.$Key.indexOf(sKey) > -1
					};
					aProperties.push(oPropertyInfo);

					if (oPropertyInfo.unit) {
						oComplexPropertyInfo = {
							name: sKey + "_" + oPropertyInfo.unit,
							label: (oPropertyAnnotations["@com.sap.vocabularies.Common.v1.Label"] || sKey) + " Complex",
							propertyInfos: [sKey, oPropertyInfo.unit],
							exportSettings: {
								type: "Currency",
								unitProperty: oPropertyInfo.unit,
								delimiter: true
							}
						};

						aProperties.push(oComplexPropertyInfo);
					}
				}
			}

			return aProperties;
		});
	};

	return AnalyticsTableDelegate;
});<commit_msg>[INTERNAL] Table: Fix ODataV4 analytics example<commit_after>/*
 * ! ${copyright}
 */

sap.ui.define([
	"sap/ui/mdc/odata/v4/TableDelegate",
	"delegates/odata/v4/TableDelegate",
	"sap/ui/mdc/odata/v4/ODataMetaModelUtil",
	"sap/ui/mdc/odata/v4/TypeUtil",
	"sap/ui/mdc/table/Column",
	"sap/ui/unified/Currency",
	"sap/base/Log"
], function (
	TableDelegate,
	TestDelegate,
	ODataMetaModelUtil,
	TypeUtil,
	Column,
	Currency,
	Log
) {
	"use strict";

	/**
	 * Test delegate for OData V4 with analytical capabilities.
	 */
	var AnalyticsTableDelegate = Object.assign({}, TableDelegate, TestDelegate);

	function getMetadataInfo(oTable) {
		return oTable ? oTable.getDelegate().payload : undefined;
	}

	/**
	 * Fetches the relevant metadata for the table and returns property extensions map in a promise.
	 *
	 * @param {Object} oTable Instance of the mdc Table
	 * @param {object[]} aPropertyInfos The property infos received from fetchProperties
	 * @returns {Promise} property extensions promise
	 */
	AnalyticsTableDelegate.fetchPropertyExtensions = function(oTable, aPropertyInfos) {
		var mExtensions = {};
		var oModel = this._getModel(oTable);
		var oMetadataInfo = getMetadataInfo(oTable);
		var sEntitySetPath = "/" + oMetadataInfo.collectionName;
		var oMetaModel = oModel.getMetaModel();

		return Promise.all([
			oMetaModel.requestObject(sEntitySetPath + "/"), oMetaModel.requestObject(sEntitySetPath + "/@")
		]).then(function(aResults) {
			var mEntitySet = aResults[0],
				mAnnotations = aResults[1],
				mAllCustomAggregates = ODataMetaModelUtil.getAllCustomAggregates(mAnnotations);

			// process the Qualifier of the CustomAggregate annotation if it matches the property name in the EntityType
			for (var sKey in mAllCustomAggregates) {
				if (sKey in mEntitySet) {
					if ("contextDefiningProperties" in mAllCustomAggregates[sKey]) {
						var aContextDefiningProperties = mAllCustomAggregates[sKey].contextDefiningProperties,
							aContextDefiningPropertiesPaths = [];

						for (var i = 0; i < aContextDefiningProperties.length; i++) {
							aContextDefiningPropertiesPaths.push(aContextDefiningProperties[i].$PropertyPath);
						}

						mExtensions[sKey] = {
							defaultAggregate: {
								contextDefiningProperties: aContextDefiningPropertiesPaths
							}
						};
					} else {
						mExtensions[sKey] = {
							defaultAggregate: {}
						};
					}
				}
			}

			return mExtensions;
		});
	};

	AnalyticsTableDelegate._createColumnTemplate = function(oPropertyInfo, oTable) {
		var oProperty = oTable.getPropertyHelper().getProperty(oPropertyInfo.name);

		if (oProperty.isComplex()) {
			var aReferencedProperties = oProperty.getReferencedProperties();
			return Promise.resolve(new Currency({
				useSymbol: false,
				value: {
					path: aReferencedProperties[0].getPath()
				},
				currency: {
					path: aReferencedProperties[1].getPath()
				}
			}));
		}

		return TestDelegate._createColumnTemplate.apply(this, arguments);
	};

	AnalyticsTableDelegate._getColumnInfo = function(oPropertyInfo, oTable) {
		var oColumnInfo = TestDelegate._getColumnInfo.apply(this, arguments);

		if (oTable.getPropertyHelper().isComplex(oPropertyInfo.name)) {
			oColumnInfo.hAlign = "Right";
			oColumnInfo.width = "15rem";
		}

		return oColumnInfo;
	};

	AnalyticsTableDelegate._createPropertyInfos = function(oTable, oModel) {
		var oMetadataInfo = getMetadataInfo(oTable), aProperties = [], oPropertyInfo, oComplexPropertyInfo, oObj, oPropertyAnnotations;
		var sEntitySetPath = "/" + oMetadataInfo.collectionName;
		var oMetaModel = oModel.getMetaModel();

		return Promise.all([
			oMetaModel.requestObject(sEntitySetPath + "/"), oMetaModel.requestObject(sEntitySetPath + "@")
		]).then(function(aResults) {
			var oEntityType = aResults[0], mEntitySetAnnotations = aResults[1];
			var oSortRestrictions = mEntitySetAnnotations["@Org.OData.Capabilities.V1.SortRestrictions"] || {};
			var oSortRestrictionsInfo = ODataMetaModelUtil.getSortRestrictionsInfo(oSortRestrictions);
			var oFilterRestrictions = mEntitySetAnnotations["@Org.OData.Capabilities.V1.FilterRestrictions"];
			var oFilterRestrictionsInfo = ODataMetaModelUtil.getFilterRestrictionsInfo(oFilterRestrictions);

			for (var sKey in oEntityType) {
				oObj = oEntityType[sKey];
				if (oObj && oObj.$kind === "Property") {

					if (oObj.$isCollection) {
						Log.warning("Complex property with type " + oObj.$Type + " has been ignored");
						continue;
					}

					oPropertyAnnotations = oMetaModel.getObject(sEntitySetPath + "/" + sKey + "@");
					var oUnitAnnotation = oPropertyAnnotations["@Org.OData.Measures.V1.ISOCurrency"] || oPropertyAnnotations["@Org.OData.Measures.V1.Unit"];
					oPropertyInfo = {
						name: sKey,
						path: sKey,
						label: oPropertyAnnotations["@com.sap.vocabularies.Common.v1.Label"] || sKey,
						sortable: oSortRestrictionsInfo[sKey] ? oSortRestrictionsInfo[sKey].sortable : true,
						filterable: oFilterRestrictionsInfo[sKey] ? oFilterRestrictionsInfo[sKey].filterable : true,
						typeConfig: TypeUtil.getTypeConfig(oObj.$Type),
						maxConditions: ODataMetaModelUtil.isMultiValueFilterExpression(oFilterRestrictionsInfo.propertyInfo[sKey]) ? -1 : 1,

						// relevant for Analytics
						groupable: oPropertyAnnotations["@Org.OData.Aggregation.V1.Groupable"] || false,
						unit: oUnitAnnotation ? oUnitAnnotation.$Path : undefined,
						key: oEntityType.$Key.indexOf(sKey) > -1
					};
					aProperties.push(oPropertyInfo);

					if (oPropertyInfo.unit) {
						oComplexPropertyInfo = {
							name: sKey + "_" + oPropertyInfo.unit,
							label: (oPropertyAnnotations["@com.sap.vocabularies.Common.v1.Label"] || sKey) + " Complex",
							propertyInfos: [sKey, oPropertyInfo.unit],
							exportSettings: {
								type: "Currency",
								unitProperty: oPropertyInfo.unit,
								delimiter: true
							}
						};

						aProperties.push(oComplexPropertyInfo);
					}
				}
			}

			return aProperties;
		});
	};

	return AnalyticsTableDelegate;
});<|endoftext|>


<commit_before>// Make sure the XWiki 'namespace' and the ModalPopup class exist.
if(typeof(XWiki) == "undefined" || typeof(XWiki.widgets) == "undefined" || typeof(XWiki.widgets.ModalPopup) == "undefined") {
  if (typeof console != "undefined" && typeof console.warn == "function") {
    console.warn("[MessageBox widget] Required class missing: XWiki.widgets.ModalPopup");
  }
} else {

XWiki.widgets.ConfirmationBox = Class.create(XWiki.widgets.ModalPopup, {
  /** Default displayed texts */
  defaultInteractionParameters : {
    confirmationText: "$msg.get('core.widgets.confirmationBox.defaultQuestion')",
    yesButtonText: "$msg.get('core.widgets.confirmationBox.button.yes')",
    noButtonText: "$msg.get('core.widgets.confirmationBox.button.no')"
  },
  /** Constructor. Registers the key listener that pops up the dialog. */
  initialize : function($super, behavior, interactionParameters) {
    this.interactionParameters = Object.extend(Object.clone(this.defaultInteractionParameters), interactionParameters || {});
    $super(
      this.createContent(this.interactionParameters),
      {
        "show"  : { method : this.showDialog,  keys : [] },
        "yes"   : { method : this.onYes,       keys : ['Enter', 'Space'] },
        "no"    : { method : this.onNo,        keys : ['Esc'] },
        "close" : { method : this.closeDialog, keys : [] }
      },
      {
         displayCloseButton : false
      }
    );
    this.showDialog();
    this.setClass("confirmation");
    this.behavior = behavior || { };
  },
  /** Create the content of the confirmation dialog: icon + question text, buttons */
  createContent : function (data) {
    var question = new Element("div", {"class" : "question"}).update(data.confirmationText);
    var buttons = new Element("div", {"class" : "buttons"});
    var yesButton = this.createButton("button", data.yesButtonText, "(Enter)", "");
    var noButton = this.createButton("button", data.noButtonText, "(Esc)", "");
    buttons.insert(yesButton);
    buttons.insert(noButton);
    var content =  new Element("div");
    content.insert(question).insert(buttons);
    Event.observe(yesButton, "click", this.onYes.bindAsEventListener(this));
    Event.observe(noButton, "click", this.onNo.bindAsEventListener(this));
    return content;
  },
  onYes : function() {
    this.closeDialog();
    if (typeof (this.behavior.onYes) == 'function') {
      this.behavior.onYes();
    }
  },
  onNo : function() {
    this.closeDialog();
    if (typeof (this.behavior.onNo) == 'function') {
      this.behavior.onNo();
    }
  }
});
} // if the parent widget is defined<commit_msg>XWIKI-5349: Closed confirmation dialogs should be removed from the DOM Done.<commit_after>// Make sure the XWiki 'namespace' and the ModalPopup class exist.
if(typeof(XWiki) == "undefined" || typeof(XWiki.widgets) == "undefined" || typeof(XWiki.widgets.ModalPopup) == "undefined") {
  if (typeof console != "undefined" && typeof console.warn == "function") {
    console.warn("[MessageBox widget] Required class missing: XWiki.widgets.ModalPopup");
  }
} else {

XWiki.widgets.ConfirmationBox = Class.create(XWiki.widgets.ModalPopup, {
  /** Default displayed texts */
  defaultInteractionParameters : {
    confirmationText: "$msg.get('core.widgets.confirmationBox.defaultQuestion')",
    yesButtonText: "$msg.get('core.widgets.confirmationBox.button.yes')",
    noButtonText: "$msg.get('core.widgets.confirmationBox.button.no')"
  },
  /** Constructor. Registers the key listener that pops up the dialog. */
  initialize : function($super, behavior, interactionParameters) {
    this.interactionParameters = Object.extend(Object.clone(this.defaultInteractionParameters), interactionParameters || {});
    $super(
      this.createContent(this.interactionParameters),
      {
        "show"  : { method : this.showDialog,  keys : [] },
        "yes"   : { method : this.onYes,       keys : ['Enter', 'Space'] },
        "no"    : { method : this.onNo,        keys : ['Esc'] },
        "close" : { method : this.closeDialog, keys : [] }
      },
      {
         displayCloseButton : false
      }
    );
    this.showDialog();
    this.setClass("confirmation");
    this.behavior = behavior || { };
  },
  /** Create the content of the confirmation dialog: icon + question text, buttons */
  createContent : function (data) {
    var question = new Element("div", {"class" : "question"}).update(data.confirmationText);
    var buttons = new Element("div", {"class" : "buttons"});
    var yesButton = this.createButton("button", data.yesButtonText, "(Enter)", "");
    var noButton = this.createButton("button", data.noButtonText, "(Esc)", "");
    buttons.insert(yesButton);
    buttons.insert(noButton);
    var content =  new Element("div");
    content.insert(question).insert(buttons);
    Event.observe(yesButton, "click", this.onYes.bindAsEventListener(this));
    Event.observe(noButton, "click", this.onNo.bindAsEventListener(this));
    return content;
  },
  onYes : function() {
    this.closeDialog();
    if (typeof (this.behavior.onYes) == 'function') {
      this.behavior.onYes();
    }
    this.dialog.remove();
  },
  onNo : function() {
    this.closeDialog();
    if (typeof (this.behavior.onNo) == 'function') {
      this.behavior.onNo();
    }
    this.dialog.remove();
  }
});
} // if the parent widget is defined<|endoftext|>


<commit_before>'use strict';

var path = require('path');
var resolve = require('resolve');
var assign = require('lodash').assign;
var chalk = require('chalk');
var defaultRules = require('./rules');
var defaultConfigurations = require('./config/index');

var KNOWN_ROOT_PROPERTIES = ['extends', 'rules', 'pending', 'ignore', 'plugins'];

function resolveConfigPath(configPath, pluginPath) {

  configPath = configPath || path.join(process.cwd(), '.template-lintrc');

  if (pluginPath) {

    //throws exception if not found
    pluginPath = resolve.sync(pluginPath, { basedir: path.dirname(configPath) });

  }

  try {
    var filePath = pluginPath || configPath;
    return require(filePath);
  } catch(e) {
    return {};
  }

}

function ensureRootProperties(config) {
  config.rules = config.rules || {};
  config.pending = config.pending || [];
  config.ignore = config.ignore || [];
  config.plugins = config.plugins || [];
}

function migrateRulesFromRoot(config, options) {
  var logger = options.console || console;

  var invalidKeysFound = [];
  for (var key in config) {
    if (KNOWN_ROOT_PROPERTIES.indexOf(key) === -1) {
      invalidKeysFound.push(key);

      config.rules[key] = config[key];
      delete config[key];
    }
  }

  if (invalidKeysFound.length > 0) {
    logger.log(chalk.yellow(
      [
        'Rule configuration has been moved into a `rules` property.',
        'Please update your `.template-lintrc.js` file.',
        'The following rules have been migrated to the `rules`',
        'property: ' + invalidKeysFound + '.'
      ].join(' ')
    ));
  }
}

function processPlugins(config, options) {

  var logger = options.console || console;

  var configPlugins = config.plugins;
  var pluginsHash = {};

  for (var i = 0; i < configPlugins.length; i++) {

    var plugin = configPlugins[i];
    var pluginName;

    if (typeof plugin === 'string') {
      pluginName = plugin;
      plugin = resolveConfigPath(options.configPath, pluginName);
    }

    var errorMessage;
    if (typeof plugin === 'object') {

      if (plugin.name) {
        pluginsHash[plugin.name] = plugin;
      } else if (pluginName) {
        errorMessage = 'Plugin (' + pluginName + ') has not defined the plugin `name` property';
      } else {
        errorMessage = 'Inline plugin object has not defined the plugin `name` property';
      }

    } else if (pluginName) {
      errorMessage = 'Plugin (' + pluginName + ') did not return a plain object';
    } else {
      errorMessage = 'Inline plugin is not a plain object';
    }

    if (errorMessage) {
      logger.log(chalk.yellow(errorMessage));
    }

  }

  config.plugins = pluginsHash;
}

function processLoadedRules(config) {

  //load all the default rules in `ember-template-lint`
  var loadedRules = assign({}, defaultRules);

  //load plugin rules
  for (var name in config.plugins) {

    var pluginRules = config.plugins[name].rules;
    if (pluginRules) {
      loadedRules = assign(loadedRules, pluginRules);
    }

  }

  config.loadedRules = loadedRules;

}

function processLoadedConfigurations(config) {

  //load all the default configurations in `ember-template-lint`
  var loadedConfigurations = assign({}, defaultConfigurations);

  //load plugin configurations
  for (var pluginName in config.plugins) {

    var pluginConfigurations = config.plugins[pluginName].configurations;
    if (pluginConfigurations) {

      for (var configurationName in pluginConfigurations) {

        var name = pluginName + ':' + configurationName;
        loadedConfigurations[name] = pluginConfigurations[configurationName];

      }
    }

  }

  config.loadedConfigurations = loadedConfigurations;

}

function processExtends(config, options) {

  var logger = options.console || console;

  if (config.extends) {

    var extendedList = [];
    if (typeof config.extends === 'string') {
      extendedList = [config.extends];
    } else if (config.extends instanceof Array) {
      extendedList = config.extends;
    } else {
      logger.log(chalk.yellow('config.extends should be string or array '));
    }

    for (var i = 0; i < extendedList.length; i++) {

      var extendName = extendedList[i];

      var configuration = config.loadedConfigurations[extendName];
      if (configuration) {

        if (configuration.rules) {

          config.rules = assign(config.rules, configuration.rules);

        } else {

          logger.log(chalk.yellow('Missing rules for extends: ' + extendName));

        }

      } else {

        logger.log(chalk.yellow('Cannot find configuration for extends: ' + extendName));

      }

    }

    delete config.extends;
  }

}

function validateRules(config, options) {
  var logger = options.console || console;
  var invalidKeysFound = [];

  for (var key in config.rules) {
    if (!defaultRules[key]) {
      invalidKeysFound.push(key);
    }
  }

  if (invalidKeysFound.length > 0) {
    logger.log(chalk.yellow('Invalid rule configuration found: ' + invalidKeysFound));
  }
}

module.exports = function(options) {
  var config = options.config || resolveConfigPath(options.configPath);

  ensureRootProperties(config);
  migrateRulesFromRoot(config, options);

  processPlugins(config, options);
  processLoadedRules(config);
  processLoadedConfigurations(config);
  processExtends(config, options);

  validateRules(config, options);

  return config;
};
<commit_msg>Fix issue with plugin configuration<commit_after>'use strict';

var path = require('path');
var resolve = require('resolve');
var assign = require('lodash').assign;
var chalk = require('chalk');
var defaultRules = require('./rules');
var defaultConfigurations = require('./config/index');

var KNOWN_ROOT_PROPERTIES = ['extends', 'rules', 'pending', 'ignore', 'plugins'];

function resolveConfigPath(configPath, pluginPath) {

  configPath = configPath || path.join(process.cwd(), '.template-lintrc');

  if (pluginPath) {

    //throws exception if not found
    pluginPath = resolve.sync(pluginPath, { basedir: path.dirname(configPath) });

  }

  try {
    var filePath = pluginPath || configPath;
    return require(filePath);
  } catch(e) {
    return {};
  }

}

function ensureRootProperties(config) {
  config.rules = config.rules || {};
  config.pending = config.pending || [];
  config.ignore = config.ignore || [];
  config.plugins = config.plugins || [];
}

function migrateRulesFromRoot(config, options) {
  var logger = options.console || console;

  var invalidKeysFound = [];
  for (var key in config) {
    if (KNOWN_ROOT_PROPERTIES.indexOf(key) === -1) {
      invalidKeysFound.push(key);

      config.rules[key] = config[key];
      delete config[key];
    }
  }

  if (invalidKeysFound.length > 0) {
    logger.log(chalk.yellow(
      [
        'Rule configuration has been moved into a `rules` property.',
        'Please update your `.template-lintrc.js` file.',
        'The following rules have been migrated to the `rules`',
        'property: ' + invalidKeysFound + '.'
      ].join(' ')
    ));
  }
}

function processPlugins(config, options) {

  var logger = options.console || console;

  var configPlugins = config.plugins;
  var pluginsHash = {};

  for (var i = 0; i < configPlugins.length; i++) {

    var plugin = configPlugins[i];
    var pluginName;

    if (typeof plugin === 'string') {
      pluginName = plugin;
      plugin = resolveConfigPath(options.configPath, pluginName);
    }

    var errorMessage;
    if (typeof plugin === 'object') {

      if (plugin.name) {
        pluginsHash[plugin.name] = plugin;
      } else if (pluginName) {
        errorMessage = 'Plugin (' + pluginName + ') has not defined the plugin `name` property';
      } else {
        errorMessage = 'Inline plugin object has not defined the plugin `name` property';
      }

    } else if (pluginName) {
      errorMessage = 'Plugin (' + pluginName + ') did not return a plain object';
    } else {
      errorMessage = 'Inline plugin is not a plain object';
    }

    if (errorMessage) {
      logger.log(chalk.yellow(errorMessage));
    }

  }

  config.plugins = pluginsHash;
}

function processLoadedRules(config) {

  //load all the default rules in `ember-template-lint`
  var loadedRules = assign({}, defaultRules);

  //load plugin rules
  for (var name in config.plugins) {

    var pluginRules = config.plugins[name].rules;
    if (pluginRules) {
      loadedRules = assign(loadedRules, pluginRules);
    }

  }

  config.loadedRules = loadedRules;

}

function processLoadedConfigurations(config) {

  //load all the default configurations in `ember-template-lint`
  var loadedConfigurations = assign({}, defaultConfigurations);

  //load plugin configurations
  for (var pluginName in config.plugins) {

    var pluginConfigurations = config.plugins[pluginName].configurations;
    if (pluginConfigurations) {

      for (var configurationName in pluginConfigurations) {

        var name = pluginName + ':' + configurationName;
        loadedConfigurations[name] = pluginConfigurations[configurationName];

      }
    }

  }

  config.loadedConfigurations = loadedConfigurations;

}

function processExtends(config, options) {

  var logger = options.console || console;

  if (config.extends) {

    var extendedList = [];
    if (typeof config.extends === 'string') {
      extendedList = [config.extends];
    } else if (config.extends instanceof Array) {
      extendedList = config.extends;
    } else {
      logger.log(chalk.yellow('config.extends should be string or array '));
    }

    for (var i = 0; i < extendedList.length; i++) {

      var extendName = extendedList[i];

      var configuration = config.loadedConfigurations[extendName];
      if (configuration) {

        if (configuration.rules) {

          config.rules = assign(config.rules, configuration.rules);

        } else {

          logger.log(chalk.yellow('Missing rules for extends: ' + extendName));

        }

      } else {

        logger.log(chalk.yellow('Cannot find configuration for extends: ' + extendName));

      }

    }

    delete config.extends;
  }

}

function validateRules(config, options) {
  var logger = options.console || console;
  var invalidKeysFound = [];

  for (var key in config.rules) {
    if (!config.loadedRules[key]) {
      invalidKeysFound.push(key);
    }
  }

  if (invalidKeysFound.length > 0) {
    logger.log(chalk.yellow('Invalid rule configuration found: ' + invalidKeysFound));
  }
}

module.exports = function(options) {
  var config = options.config || resolveConfigPath(options.configPath);

  if (!config._processed) {
    ensureRootProperties(config);
    migrateRulesFromRoot(config, options);

    processPlugins(config, options);
    processLoadedRules(config);
    processLoadedConfigurations(config);
    processExtends(config, options);

    validateRules(config, options);

    config._processed = true;
  }

  return config;
};
<|endoftext|>


<commit_before>/*
 * Copyright 2016 resin.io
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

'use strict'

const electron = require('electron')
const path = require('path')
const EXIT_CODES = require('../shared/exit-codes')
const buildWindowMenu = require('./menu')
let mainWindow = null

electron.app.on('window-all-closed', electron.app.quit)

// Sending a `SIGINT` (e.g: Ctrl-C) to an Electron app that registers
// a `beforeunload` window event handler results in a disconnected white
// browser window in GNU/Linux and macOS.
// The `before-quit` Electron event is triggered in `SIGINT`, so we can
// make use of it to ensure the browser window is completely destroyed.
// See https://github.com/electron/electron/issues/5273
electron.app.on('before-quit', () => {
  process.exit(EXIT_CODES.SUCCESS)
})

electron.app.on('ready', () => {
  mainWindow = new electron.BrowserWindow({
    width: 800,
    height: 380,
    useContentSize: true,
    show: false,
    resizable: Boolean(process.env.ETCHER_FULLSCREEN),
    maximizable: false,
    fullscreen: Boolean(process.env.ETCHER_FULLSCREEN),
    fullscreenable: Boolean(process.env.ETCHER_FULLSCREEN),
    kiosk: true,
    autoHideMenuBar: true,
    titleBarStyle: 'hidden-inset',
    icon: path.join(__dirname, '..', '..', 'assets', 'icon.png'),
    darkTheme: true,
    webPreferences: {
      backgroundThrottling: false
    }
  })

  buildWindowMenu(mainWindow)

  // Prevent flash of white when starting the application
  mainWindow.on('ready-to-show', mainWindow.show)

  mainWindow.on('closed', () => {
    mainWindow = null
  })

  // Prevent the user from being allowed to zoom-in the application.
  //
  // This function should be called on the renderer process. We use
  // `executeJavaScript()` rather than moving this to a file in the
  // renderer process for convenience, since we have all other
  // electron desktop experience fixes in this file.
  //
  // See https://github.com/electron/electron/issues/3609
  mainWindow.webContents.executeJavaScript('require(\'electron\').webFrame.setZoomLevelLimits(1, 1);')

  // Prevent external resources from being loaded (like images)
  // when dropping them on the WebView.
  // See https://github.com/electron/electron/issues/5919
  mainWindow.webContents.on('will-navigate', (event) => {
    event.preventDefault()
  })

  mainWindow.loadURL(`file://${path.join(__dirname, 'app', 'index.html')}`)
})
<commit_msg>minifix(gui): Only enable Kiosk Mode when FULLSCREEN is set<commit_after>/*
 * Copyright 2016 resin.io
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

'use strict'

const electron = require('electron')
const path = require('path')
const EXIT_CODES = require('../shared/exit-codes')
const buildWindowMenu = require('./menu')
let mainWindow = null

electron.app.on('window-all-closed', electron.app.quit)

// Sending a `SIGINT` (e.g: Ctrl-C) to an Electron app that registers
// a `beforeunload` window event handler results in a disconnected white
// browser window in GNU/Linux and macOS.
// The `before-quit` Electron event is triggered in `SIGINT`, so we can
// make use of it to ensure the browser window is completely destroyed.
// See https://github.com/electron/electron/issues/5273
electron.app.on('before-quit', () => {
  process.exit(EXIT_CODES.SUCCESS)
})

electron.app.on('ready', () => {
  mainWindow = new electron.BrowserWindow({
    width: 800,
    height: 380,
    useContentSize: true,
    show: false,
    resizable: Boolean(process.env.ETCHER_FULLSCREEN),
    maximizable: false,
    fullscreen: Boolean(process.env.ETCHER_FULLSCREEN),
    fullscreenable: Boolean(process.env.ETCHER_FULLSCREEN),
    kiosk: Boolean(process.env.ETCHER_FULLSCREEN),
    autoHideMenuBar: true,
    titleBarStyle: 'hidden-inset',
    icon: path.join(__dirname, '..', '..', 'assets', 'icon.png'),
    darkTheme: true,
    webPreferences: {
      backgroundThrottling: false
    }
  })

  buildWindowMenu(mainWindow)

  // Prevent flash of white when starting the application
  mainWindow.on('ready-to-show', mainWindow.show)

  mainWindow.on('closed', () => {
    mainWindow = null
  })

  // Prevent the user from being allowed to zoom-in the application.
  //
  // This function should be called on the renderer process. We use
  // `executeJavaScript()` rather than moving this to a file in the
  // renderer process for convenience, since we have all other
  // electron desktop experience fixes in this file.
  //
  // See https://github.com/electron/electron/issues/3609
  mainWindow.webContents.executeJavaScript('require(\'electron\').webFrame.setZoomLevelLimits(1, 1);')

  // Prevent external resources from being loaded (like images)
  // when dropping them on the WebView.
  // See https://github.com/electron/electron/issues/5919
  mainWindow.webContents.on('will-navigate', (event) => {
    event.preventDefault()
  })

  mainWindow.loadURL(`file://${path.join(__dirname, 'app', 'index.html')}`)
})
<|endoftext|>


<commit_before>/**
 * i18n plugin
 * register handlebars handler which looks up translations in a dictionary
 */

const path = require("path");
const fs = require("fs");
const handlebars = require("handlebars");

module.exports = function makePlugin() {
    return function (opts) {
        const data = {
            "en": {}
        };

            // load translation data
        if (opts.locales) {
            try {
                opts.locales.forEach(function (loc) {
                    try {
                        const content = fs.readFileSync(path.join(__dirname + "../../../", loc.file));
                        data[loc.locale] = JSON.parse(content);
                    } catch (e) {
                        console.error("Failed to load: " + e.path);
                    }
                });
            } catch (e) {
                console.error(e);
            }
        }

        opts.output.msgs = [];

        handlebars.registerHelper("i18n", function (options) {
            const locale = options.data.root.locale;
            const msg = options.fn(this);

                // save out a list of all requests
                // we can output this to a file for localization
            opts.output.msgs[msg] = true;

            if (msg && data[locale] && data[locale][msg]) {
                return data[locale][msg];
            }
            return msg;
        });

        return function (files, metalsmith, done) {
            for (const filename in files) {
                    // add locale metadata to files

                    // Ensure Windows paths are converted to forward slashes as necessary
                const filenameForwardSlashes = filename.replace(/\\/g, "/");

                const locale = filenameForwardSlashes.split("/")[0];
                files[filename].locale = locale; // locale: 'en'
                files[filename][locale] = true;     // 'en': true
            }
            done();
        };
    };
};
<commit_msg>Fix comment indentation<commit_after>/**
 * i18n plugin
 * register handlebars handler which looks up translations in a dictionary
 */

const path = require("path");
const fs = require("fs");
const handlebars = require("handlebars");

module.exports = function makePlugin() {
    return function (opts) {
        const data = {
            "en": {}
        };

        // load translation data
        if (opts.locales) {
            try {
                opts.locales.forEach(function (loc) {
                    try {
                        const content = fs.readFileSync(path.join(__dirname + "../../../", loc.file));
                        data[loc.locale] = JSON.parse(content);
                    } catch (e) {
                        console.error("Failed to load: " + e.path);
                    }
                });
            } catch (e) {
                console.error(e);
            }
        }

        opts.output.msgs = [];

        handlebars.registerHelper("i18n", function (options) {
            const locale = options.data.root.locale;
            const msg = options.fn(this);

            // save out a list of all requests
            // we can output this to a file for localization
            opts.output.msgs[msg] = true;

            if (msg && data[locale] && data[locale][msg]) {
                return data[locale][msg];
            }
            return msg;
        });

        return function (files, metalsmith, done) {
            for (const filename in files) {
                // add locale metadata to files

                // Ensure Windows paths are converted to forward slashes as necessary
                const filenameForwardSlashes = filename.replace(/\\/g, "/");

                const locale = filenameForwardSlashes.split("/")[0];
                files[filename].locale = locale; // locale: 'en'
                files[filename][locale] = true;     // 'en': true
            }
            done();
        };
    };
};
<|endoftext|>


<commit_before>'use strict';

var TextHandler = require('./text-handler');

/*
 * IMEOutput is poor man's solution for inserting characters into
 * the nsIDOMElement.
 * It does not handle cursor position nor file key/composition
 * events. To do this right we will have to re-implement/copy
 * half of mozKeyboard API in B2G instead. Not something worth doing, yet.
 */
exports.IMEOutput = {
  // XXX: allow more types.
  inputTagNames: ['html:input', 'INPUT', 'html:textarea', 'TEXTAREA'],

  // See http://developers.whatwg.org/the-input-element.html#attr-input-type
  inputTagTypeNames: ['text', 'search', 'tel', 'url', 'email'],

  focusedElement: null,

  compositionData: '',

  setFocusedElement: function io_setFocusedElement(focusedElement) {
    if (this.compositionData &&
        this.focusedElement !== focusedElement) {
      // Reset composition before setting the focused element.
      this.setComposition('');
    }

    this.focusedElement = focusedElement;

  },

  canHandle: function io_canHandle(element) {
    if (!element)
      return false;

    if (this.inputTagNames.indexOf(element.tagName) === -1)
      return false;

    if (/input/i.test(element.tagName.toLowerCase()) &&
        this.inputTagTypeNames.indexOf(element.type) === -1)
      return false;

    // XXX: handle inputmode?

    return true;
  },

  regainFocus: function io_regainFocus() {
    if (!this.focusedElement)
      return;

    this.focusedElement.focus();
  },

  sendKey: function io_sendKey(code) {
    if (!this.focusedElement) {
      throw 'IMEOutput has no focused element.';
    }

    var keyCode = code;
    var charCode = code;

    // Because of
    // https://mxr.mozilla.org/mozilla-central/source/dom/base/
    // nsDOMWindowUtils.cpp#896, we will need to set charCode of
    // non-printable charcaters to 0. This is a non-exhaust list of that,
    // but covers what will be sent by JSZhuyin.
    if ([8, 13].indexOf(code) !== -1)
      charCode = 0;

    // XXX: send the three events at once without giving others the means
    // to control it.
    // Note that even though we send the same keyCode and charCode to
    // the function, the real code in the key events may be set to 0.
    ['keydown', 'keypress', 'keyup'].forEach((function sendKey(type) {
      TextHandler.sendKeyEvent(this.focusedElement, type, keyCode, charCode);
    }).bind(this));
  },

  setComposition: function io_setComposition(text) {
    // XXX: This function is working if the focus is kept on the element
    // during the composition process. Disabling because it currently doesn't.

    /*
    if (!this.focusedElement) {
      throw 'IMEOutput has no focused element.';
    }

    // If composition is not started and there is no text to set,
    // do nothing.
    if (!this.compositionData && !text)
      return;

    // If composition is started and no text to set,
    // that concludes the composition process (w/o inserting any text)
    if (!text) {
      TextHandler.sendCompositionEvent(this.focusedElement,
                                       'compositionend', this.compositionData);
      this.compositionData = '';

      return;
    }

    // If there is no composition data and we need to set something
    // start the composition.
    if (!this.compositionData) {
      TextHandler.sendCompositionEvent(this.focusedElement,
                                       'compositionstart', '');
    }

    // If the text changed,
    // send the compositionupdate event with the updated text.
    if (this.compositionData !== text) {
      TextHandler.sendCompositionEvent(this.focusedElement,
                                       'compositionupdate', text);
    }

    this.compositionData = text;
    */
  },

  finishComposition: function io_finishComposition(text) {
    if (!this.focusedElement) {
      throw 'IMEOutput has no focused element.';
    }

    // Do the final update ...
    this.setComposition(text);

    // ... and finish the composition
    // ('compositionend' event generates 'input' event),
    this.setComposition('');

    // and, insert the text for real.
    // XXX: this too generates the 'input' event.
    TextHandler.insertText(this.focusedElement, text);
  }
};
<commit_msg>Enable contenteditable, fix #10<commit_after>'use strict';

var TextHandler = require('./text-handler');

/*
 * IMEOutput is poor man's solution for inserting characters into
 * the nsIDOMElement.
 * It does not handle cursor position nor file key/composition
 * events. To do this right we will have to re-implement/copy
 * half of mozKeyboard API in B2G instead. Not something worth doing, yet.
 */
exports.IMEOutput = {
  // XXX: allow more types.
  inputTagNames: ['html:input', 'INPUT', 'html:textarea', 'TEXTAREA'],

  // See http://developers.whatwg.org/the-input-element.html#attr-input-type
  inputTagTypeNames: ['text', 'search', 'tel', 'url', 'email'],

  focusedElement: null,

  compositionData: '',

  setFocusedElement: function io_setFocusedElement(focusedElement) {
    if (this.compositionData &&
        this.focusedElement !== focusedElement) {
      // Reset composition before setting the focused element.
      this.setComposition('');
    }

    this.focusedElement = focusedElement;

  },

  canHandle: function io_canHandle(element) {
    if (!element)
      return false;

    if (element.contentEditable === 'true')
      return true;

    if (this.inputTagNames.indexOf(element.tagName) === -1)
      return false;

    if (/input/i.test(element.tagName.toLowerCase()) &&
        this.inputTagTypeNames.indexOf(element.type) === -1)
      return false;

    // XXX: handle inputmode?

    return true;
  },

  regainFocus: function io_regainFocus() {
    if (!this.focusedElement)
      return;

    this.focusedElement.focus();
  },

  sendKey: function io_sendKey(code) {
    if (!this.focusedElement) {
      throw 'IMEOutput has no focused element.';
    }

    var keyCode = code;
    var charCode = code;

    // Because of
    // https://mxr.mozilla.org/mozilla-central/source/dom/base/
    // nsDOMWindowUtils.cpp#896, we will need to set charCode of
    // non-printable charcaters to 0. This is a non-exhaust list of that,
    // but covers what will be sent by JSZhuyin.
    if ([8, 13].indexOf(code) !== -1)
      charCode = 0;

    // XXX: send the three events at once without giving others the means
    // to control it.
    // Note that even though we send the same keyCode and charCode to
    // the function, the real code in the key events may be set to 0.
    ['keydown', 'keypress', 'keyup'].forEach((function sendKey(type) {
      TextHandler.sendKeyEvent(this.focusedElement, type, keyCode, charCode);
    }).bind(this));
  },

  setComposition: function io_setComposition(text) {
    // XXX: This function is working if the focus is kept on the element
    // during the composition process. Disabling because it currently doesn't.

    /*
    if (!this.focusedElement) {
      throw 'IMEOutput has no focused element.';
    }

    // If composition is not started and there is no text to set,
    // do nothing.
    if (!this.compositionData && !text)
      return;

    // If composition is started and no text to set,
    // that concludes the composition process (w/o inserting any text)
    if (!text) {
      TextHandler.sendCompositionEvent(this.focusedElement,
                                       'compositionend', this.compositionData);
      this.compositionData = '';

      return;
    }

    // If there is no composition data and we need to set something
    // start the composition.
    if (!this.compositionData) {
      TextHandler.sendCompositionEvent(this.focusedElement,
                                       'compositionstart', '');
    }

    // If the text changed,
    // send the compositionupdate event with the updated text.
    if (this.compositionData !== text) {
      TextHandler.sendCompositionEvent(this.focusedElement,
                                       'compositionupdate', text);
    }

    this.compositionData = text;
    */
  },

  finishComposition: function io_finishComposition(text) {
    if (!this.focusedElement) {
      throw 'IMEOutput has no focused element.';
    }

    // Do the final update ...
    this.setComposition(text);

    // ... and finish the composition
    // ('compositionend' event generates 'input' event),
    this.setComposition('');

    // and, insert the text for real.
    // XXX: this too generates the 'input' event.
    TextHandler.insertText(this.focusedElement, text);
  }
};
<|endoftext|>


<commit_before>var CometIO = function(){
  this.url = "<%= cometio_url %>";
  this.session = null;
  var self = this;

  this.push = function(type, data){
    $.ajax(
      {
        url : self.url,
        data : {type : type, data : data, session : self.session},
        success : function(data){
        },
        error : function(req, stat, e){
          self.emit("error", "CometIO push error");
        },
        complete : function(e){
        },
        type : "POST",
        dataType : "json",
        timeout : 10000
      }
    );
  };

  this.connect = function(){
    self.on("__session_id", function(session){
      self.session = session;
      self.emit("connect", self.session);
    });
    self.get();
    return self;
  };

  this.get = function(){
    $.ajax(
      {
        url : self.url,
        data : {session : self.session},
        success : function(data){
          if(data){
            self.emit(data.type, data.data);
          }
          self.get();
        },
        error : function(req, stat, e){
          self.emit("error", "CometIO get error");
          setTimeout(self.get, 10000);
        },
        complete : function(e){
        },
        type : "GET",
        dataType : "json",
        timeout : 60000
      }
    );
  };

  this.events = new Array();
  this.on = function(type, listener){
    if(typeof listener !== "function") return;
    var event_id = self.events.length > 0 ? 1 + self.events[self.events.length-1].id : 0
    self.events.push({
      id: event_id,
      type: type,
      listener: listener
    });
    return event_id;
  };

  this.emit = function(type, data){
    for(var i = 0; i < self.events.length; i++){
      var e = self.events[i];
      if(e.type == type) e.listener(data);
    }
  };

  this.removeListener = function(id_or_type){
    for(var i = self.events.length-1; i >= 0; i--){
      var e = self.events[i];
      switch(typeof id_or_type){
      case "number":
        if(e.id == id_or_type) self.events.splice(i,1);
        break
      case "string":
        if(e.type == id_or_type) self.events.splice(i,1);
        break
      }
    }
  };

};
<commit_msg>io.once in client side<commit_after>var CometIO = function(){
  this.url = "<%= cometio_url %>";
  this.session = null;
  var self = this;

  this.push = function(type, data){
    $.ajax(
      {
        url : self.url,
        data : {type : type, data : data, session : self.session},
        success : function(data){
        },
        error : function(req, stat, e){
          self.emit("error", "CometIO push error");
        },
        complete : function(e){
        },
        type : "POST",
        dataType : "json",
        timeout : 10000
      }
    );
  };

  this.connect = function(){
    self.on("__session_id", function(session){
      self.session = session;
      self.emit("connect", self.session);
    });
    self.get();
    return self;
  };

  this.get = function(){
    $.ajax(
      {
        url : self.url,
        data : {session : self.session},
        success : function(data){
          if(data){
            self.emit(data.type, data.data);
          }
          self.get();
        },
        error : function(req, stat, e){
          self.emit("error", "CometIO get error");
          setTimeout(self.get, 10000);
        },
        complete : function(e){
        },
        type : "GET",
        dataType : "json",
        timeout : 60000
      }
    );
  };

  this.events = new Array();
  this.on = function(type, listener, opts){
    if(typeof listener !== "function") return;
    var event_id = self.events.length > 0 ? 1 + self.events[self.events.length-1].id : 0
    var params = {
      id: event_id,
      type: type,
      listener: listener
    };
    for(i in opts){
      if(!params[i]) params[i] = opts[i];
    };
    self.events.push(params);
    return event_id;
  };

  this.once = function(type, listener){
    this.on(type, listener, {once: true});
  };

  this.emit = function(type, data){
    for(var i = 0; i < self.events.length; i++){
      var e = self.events[i];
      if(e.type == type) e.listener(data);
      if(e.once) this.removeListener(e.id);
    }
  };

  this.removeListener = function(id_or_type){
    for(var i = self.events.length-1; i >= 0; i--){
      var e = self.events[i];
      switch(typeof id_or_type){
      case "number":
        if(e.id == id_or_type) self.events.splice(i,1);
        break
      case "string":
        if(e.type == id_or_type) self.events.splice(i,1);
        break
      }
    }
  };

};
<|endoftext|>


<commit_before>function makeMap(str) {
  return str.split(",").reduce((map, cur) => {
    map[cur] = true
    return map
  }, {})
}

const REGEXP = {
  startTag: /^<([-A-Za-z0-9_]+)((?:\s+[a-zA-Z_:][-a-zA-Z0-9_:.]*(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/,
  endTag: /^<\/([-A-Za-z0-9_]+)[^>]*>/,
  attr: /([a-zA-Z_:][-a-zA-Z0-9_:.]*)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g
}

const MAKER = {
  empty: makeMap("area,base,basefont,br,col,frame,hr,img,input,link,meta,param,embed,command,keygen,source,track,wbr"),
  block: makeMap("a,address,article,applet,aside,audio,blockquote,button,canvas,center,dd,del,dir,div,dl,dt,fieldset,figcaption,figure,footer,form,frameset,h1,h2,h3,h4,h5,h6,header,hgroup,hr,iframe,ins,isindex,li,map,menu,noframes,noscript,object,ol,output,p,pre,section,script,table,tbody,td,tfoot,th,thead,tr,ul,video"),
  inline: makeMap("abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,code,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,textarea,tt,u,var"),
  closeSelf: makeMap("colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr"),
  fillAttrs: makeMap("checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected"),
  special: makeMap("script,style")
}

function getAttributes(str) {
  let attrsMap = {}
  str.replace(REGEXP.attr, function(match, name) {
    const value = arguments[2] ? arguments[2] :
      arguments[3] ? arguments[3] :
      arguments[4] ? arguments[4] :
      MAKER.fillAttrs[name] ? name : ""

    attrsMap[name] = value.replace(/(^|[^\\])"/g, '$1\\\"')
  })
  return attrsMap
}

function lex(html) {
  let string = html
  let tokens = []

  while (string) {
    if (string.indexOf("</") === 0) {
      const match = string.match(REGEXP.endTag)
      if (!match) continue
      string = string.substring(match[0].length)
      tokens.push({
        tag: match[1],
        type: 'tag-end',
      })
      continue
    }
    if (string.indexOf("<") === 0) {
      const match = string.match(REGEXP.startTag)
      if (!match) continue
      string = string.substring(match[0].length)
      const tag = match[1]
      const isEmpty = !!MAKER.empty[tag]
      const type = isEmpty ? 'tag-empty' : 'tag-start'
      const attributes = getAttributes(match[2])

      tokens.push({
        tag,
        type,
        attributes
      })
      continue
    }

    const index = string.indexOf('<')
    const text = index < 0 ? string : string.substring(0, index)

    string = index < 0 ? "" : string.substring(index)
    tokens.push({
      type: "text",
      text
    })
  }
  return tokens
}

function parse(tokens) {
  let root = {
    tag: "root",
    children: []
  }
  let tagArray = [root]
  tagArray.last = () => tagArray[tagArray.length - 1]

  for (var i = 0; i < tokens.length; i++) {
    const token = tokens[i]
    if (token.type === 'tag-start') {
      const node = {
        type: "Element",
        tagName: token.tag,
        attributes: token.attributes,
        children: []
      }
      tagArray.push(node)
      continue
    }
    if (token.type === 'tag-end') {
      let parent = tagArray[tagArray.length - 2]
      let node = tagArray.pop()
      parent.children.push(node)
      continue
    }
    if (token.type === 'text') {
      tagArray.last().children.push({
        type: 'text',
        content: token.text
      })
      continue
    }
    if (token.type === 'tag-empty') {
      tagArray.last().children.push({
        type: "Element",
        tagName: token.tag,
        attributes: token.attributes
      })
      continue
    }
  }
  return root
}

export default function htmlParser(html) {
  return parse(lex(html))
}<commit_msg>remove useless MAKER<commit_after>function makeMap(str) {
  return str.split(",").reduce((map, cur) => {
    map[cur] = true
    return map
  }, {})
}

const REGEXP = {
  startTag: /^<([-A-Za-z0-9_]+)((?:\s+[a-zA-Z_:][-a-zA-Z0-9_:.]*(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/,
  endTag: /^<\/([-A-Za-z0-9_]+)[^>]*>/,
  attr: /([a-zA-Z_:][-a-zA-Z0-9_:.]*)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g
}

const MAKER = {
  empty: makeMap("area,base,basefont,br,col,frame,hr,img,input,link,meta,param,embed,command,keygen,source,track,wbr"),
  fillAttrs: makeMap("checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected"),
}

function getAttributes(str) {
  let attrsMap = {}
  str.replace(REGEXP.attr, function(match, name) {
    const value = arguments[2] ? arguments[2] :
      arguments[3] ? arguments[3] :
      arguments[4] ? arguments[4] :
      MAKER.fillAttrs[name] ? name : ""

    attrsMap[name] = value.replace(/(^|[^\\])"/g, '$1\\\"')
  })
  return attrsMap
}

function lex(html) {
  let string = html
  let tokens = []

  while (string) {
    if (string.indexOf("</") === 0) {
      const match = string.match(REGEXP.endTag)
      if (!match) continue
      string = string.substring(match[0].length)
      tokens.push({
        tag: match[1],
        type: 'tag-end',
      })
      continue
    }
    if (string.indexOf("<") === 0) {
      const match = string.match(REGEXP.startTag)
      if (!match) continue
      string = string.substring(match[0].length)
      const tag = match[1]
      const isEmpty = !!MAKER.empty[tag]
      const type = isEmpty ? 'tag-empty' : 'tag-start'
      const attributes = getAttributes(match[2])

      tokens.push({
        tag,
        type,
        attributes
      })
      continue
    }

    const index = string.indexOf('<')
    const text = index < 0 ? string : string.substring(0, index)

    string = index < 0 ? "" : string.substring(index)
    tokens.push({
      type: "text",
      text
    })
  }
  return tokens
}

function parse(tokens) {
  let root = {
    tag: "root",
    children: []
  }
  let tagArray = [root]
  tagArray.last = () => tagArray[tagArray.length - 1]

  for (var i = 0; i < tokens.length; i++) {
    const token = tokens[i]
    if (token.type === 'tag-start') {
      const node = {
        type: "Element",
        tagName: token.tag,
        attributes: token.attributes,
        children: []
      }
      tagArray.push(node)
      continue
    }
    if (token.type === 'tag-end') {
      let parent = tagArray[tagArray.length - 2]
      let node = tagArray.pop()
      parent.children.push(node)
      continue
    }
    if (token.type === 'text') {
      tagArray.last().children.push({
        type: 'text',
        content: token.text
      })
      continue
    }
    if (token.type === 'tag-empty') {
      tagArray.last().children.push({
        type: "Element",
        tagName: token.tag,
        attributes: token.attributes
      })
      continue
    }
  }
  return root
}

export default function htmlParser(html) {
  return parse(lex(html))
}<|endoftext|>