<commit_before>var PUPHPET = {};

/**
 * When element is clicked, update another element.
 *
 * This is good for:
 *
 * 1. User clicks element, user prompted for input, element properties + user input
 *      used to update another element
 * 2. Clicking one element checks another element if target is a radio or checkbox
 *
 * Loops through all data-* type attributes of element
 */
PUPHPET.updateOtherInput = function() {
    $(document).on('click', '.update-other-input', function(e){
        var $parent = $(this);

        $.each($(this).data(), function(key, value) {
            // jQuery changed "data-foo-bar" to "dataFooBar". Change them back.
            key = key.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();

            // Only work with data attributes that have "update-"
            if (key.search('update-') !== 0) {
                return;
            }

            key = key.replace('update-', '');

            var $target = $('#' + key);

            // If target element is not defined as #foo, maybe it is an input,name,value target
            if (!$target.length) {
                $target = $('input[name="' + key + '"][value="'+ value +'"]')
            }

            // If target is a radio element, check it, no need to uncheck in future
            if ($target.is(':radio')) {
                $target.prop('checked', true);

                return;
            }

            /**
             * If target is checkbox element, check if clicked element was checked or unchecked.
             *
             * If unchecked, do not update target. We only want to handle positive actions
             */
            if ($target.is(':checkbox') && $parent.is(':checked')) {
                $target.prop('checked', true);

                return;
            }

            $target.val(value);
        });
    });
};

PUPHPET.updateOtherInputSelect = function() {
    $(document).on('change', 'select.update-other-input', function(e){
        var $parent = $(this);

        $('select.update-other-input option:selected').each(function() {
            $.each($(this).data(), function(key, value) {
                // jQuery changed "data-foo-bar" to "dataFooBar". Change them back.
                key = key.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();

                // Only work with data attributes that have "update-"
                if (key.search('update-') !== 0) {
                    return;
                }

                key = key.replace('update-', '');

                var $target = $('#' + key);

                // If target element is not defined as #foo, maybe it is an input,name,value target
                if (!$target.length) {
                    $target = $('input[name="' + key + '"][value="'+ value +'"]')
                }

                // If target is a radio element, check it, no need to uncheck in future
                if ($target.is(':radio')) {
                    $target.prop('checked', true);

                    return;
                }

                /**
                 * If target is checkbox element, check if clicked element was checked or unchecked.
                 *
                 * If unchecked, do not update target. We only want to handle positive actions
                 */
                if ($target.is(':checkbox') && $parent.is(':checked')) {
                    $target.prop('checked', true);

                    return;
                }

                $target.val(value);
            });
        });
    });
};

/**
 * Run selectize.js on initial page load, and then re-run it whenever
 * new selectize-enabled elements are dynamically added to the DOM.
 *
 * @param $element
 */
PUPHPET.runSelectize = function($element) {
    // input or select elements; allows user to create their own tags
    var $selectTagsEditable = $('.tags, .select-tags-editable', $element).selectize({
        plugins: ['remove_button'],
        delimiter: ',',
        persist: false,
        create: function(input) {
            return {
                value: input,
                text: input
            }
        },
        maxItems: null,
        valueField: 'value',
        labelField: 'text',
        searchField: 'value'
    });

    // select elements; asks user for value of selected tags; cannot create own tags
    var $selectTagsUserInput = PUPHPET.selectizeTagsUserInput($element);

    // select single element; does not allow creating new tag
    var $selectTag = $('.select-tag', $element).selectize({
        persist: false,
        create: false
    });

    // select elements; does not allow creating new tags
    var $selectTags = $('.select-tags', $element).selectize({
        plugins: ['remove_button'],
        delimiter: ',',
        persist: false,
        create: false
    });

    PUPHPET._trackSelectize($selectTagsEditable);
    PUPHPET._trackSelectize($selectTagsUserInput);
    PUPHPET._trackSelectize($selectTag);
    PUPHPET._trackSelectize($selectTags);
};

/**
 * Active for select type elements.
 *
 * On user adding option, prompts user for data, and creates a new, matching
 * hidden element containing user input for easier handling of POSTed data.
 *
 * On user remove option, adds the removed element back to the available options
 * list and deletes the hidden element related to removed option.
 *
 * @param $element
 */
PUPHPET.selectizeTagsUserInput = function($element) {
    var $selectTagsUserInput = $('.select-tags-user-input', $element).selectize({
        plugins: ['remove_button'],
        delimiter: ',',
        persist: false,
        create: false,
        onItemAdd: function(value, $item) {
            var targetContainer     = '#' + $(this['$input'])[0].getAttribute('data-target-container');
            var targetNameStructure = $(this['$input'])[0].getAttribute('data-target-name');
            var elementName         = targetNameStructure + '[' + this.options[value].text + ']';

            var suffix = prompt('Enter Value:') || '0';
            var label  = this.options[value].text + ' = ' + suffix;
            var data   = $.extend({}, this.options[value], {
                text: label
            });

            // Append this user input as a new hidden element
            $('<input>').attr({
                type:  'hidden',
                name:  elementName,
                value: suffix
            }).appendTo(targetContainer);

            this.updateOption(value, data);
        },
        onItemRemove: function(value, $item) {
            var targetContainer     = '#' + $(this['$input'])[0].getAttribute('data-target-container');
            var targetNameStructure = $(this['$input'])[0].getAttribute('data-target-name');
            var elementName         = targetNameStructure + '[' + this.options[value].value + ']';

            $(targetContainer + ' input[name="' + elementName + '"]').remove();

            var data = $.extend({}, this.options[value], {
                text: value
            });

            this.updateOption(value, data);
        }
    });

    // Adds pre-selected option values to selectize field
    for (var i = 0; i < $selectTagsUserInput.length; i++) {
        var $selectElement = $selectTagsUserInput[i].selectize;
        var targetContainer = '#' + $selectTagsUserInput[i].getAttribute('data-target-container');
        var $selectedItems = $(targetContainer);

        if (!$selectedItems.length) {
            continue;
        }

        $selectedItems.children().each(function() {
            var optionName  = this.getAttribute('data-option-name');
            var optionValue = $(this).val();

            var label = $selectElement.options[optionName].text + ' = ' + optionValue;
            var data  = $.extend({}, $selectElement.options[optionName], {
                text: label
            });

            $selectElement.updateOption(optionName, data);
        });
    }

    return $selectTagsUserInput;
};

var selectizedObjects = [];

/**
 * Keep track of all initialized selectize.js elements
 *
 * @param $selectizeElements
 * @private
 */
PUPHPET._trackSelectize = function($selectizeElements) {
    for (var i = 0; i < $selectizeElements.length; i++) {
        selectizedObjects[$selectizeElements[i].id] = $selectizeElements[i];
    }
};

/**
 * Allows adding an item to a selectize.js element on user click
 */
PUPHPET.selectizeAddClickedToElement = function() {
    $(document).on('click', '.addClickedToSelectizeElement', function(e){
        var target    = this.getAttribute('data-target');
        var itemValue = this.getAttribute('data-value');
        var itemTitle = this.getAttribute('data-title') != null
            ? this.getAttribute('data-title')
            : $(this).text();

        if (!(target in selectizedObjects)) {
            return false;
        }

        var control = selectizedObjects[target].selectize;

        control.addOption({
            value: itemValue,
            text: itemTitle
        });
        control.addItem(itemValue);

        return false;
    });
};

/**
 * Adds repeatable containers based on clicked button's data-source-url value.
 *
 * Adds the response right before clicked element, and then re-runs selectize.js
 * on new elements.
 */
PUPHPET.addRepeatableElement = function() {
    $(document).on('click', 'button.addParentContainer', function(e){
        var sourceUrl = this.getAttribute('data-source-url');
        var buttonEle = $(this);

        $.ajax({
            url: sourceUrl,
            cache: false
        }).done(function(response) {
            var $row = $(response).insertBefore(buttonEle.closest('.row'));
            PUPHPET.runSelectize($row);
        });

    });
};

/**
 * Deletes repeatable containers based on button data id
 */
PUPHPET.delRepeatableElement = function() {
    $(document).on('click', 'button.deleteParentContainer', function(e){
        var parentId = this.getAttribute('data-parent-id');
        var $parentContainer = $('#' + parentId);

        $parentContainer.remove();

    });
};

/**
 * If elements are grouped into tabs, set all non-active tab elements as inactive.
 * Do not auto-disable tabs that belong to multi-selectable groups.
 *
 * This is useful so inactive choices do not get POSTed along with rest of form data.
 *
 * Runs on initial page load
 */
PUPHPET.disableInactiveTabElements = function() {
    $('ul.group-tabs li').each(function() {
        if ($(this).parent().hasClass('multi-select')) {
            return;
        }

        if ($(this).hasClass('active')) {
            return;
        }

        var $anchor = $(this).children()[0];
        var extensionId = $anchor.getAttribute('data-target-element');

        $('#' + extensionId).find('input, textarea, button, select').prop('disabled', true);
    });
};

/**
 * When switching tabs, disable all form elements in non-active tabs and
 * enable all form elements in newly active tab. Do not auto-disable
 * tabs that belong to multi-selectable groups
 */
PUPHPET.enableClickedTabElement = function() {
    $('a[data-toggle="tab"]').on('shown.bs.tab', function(e) {
        if ($(this).parentsUntil('.multi-select')!== 0) {
            return;
        }

        var original = e.relatedTarget.getAttribute('data-target-element');
        var target   = e.target.getAttribute('data-target-element');

        $('#' + original).find('input, textarea, button, select').prop('disabled', true);
        $('#' + target).find('input, textarea, button, select').prop('disabled', false);
    });
};

/**
 * Display the information about PuPHPet's beloved contributors!
 */
PUPHPET.githubContributors = function() {
    $.get('https://api.github.com/repos/puphpet/puphpet/contributors', function(githubResponse) {
        $.post('/github-contributors', { contributors: githubResponse }, function(response) {
            $('#contributors').html(response);
        });
    });
};

/**
 * Allows user to drag and drop a pre-generated yaml file containing
 * their VMs configuration.
 */
PUPHPET.uploadConfig = function() {
    var dropzone = document.documentElement;
    var tid;

    dropzone.addEventListener('dragover', handleDragOver, false);
    dropzone.addEventListener('dragleave', handleDragLeave, false);
    dropzone.addEventListener('drop', handleFileSelect, false);

    function handleDragOver(e) {
        clearTimeout(tid);
        e.stopPropagation();
        e.preventDefault && e.preventDefault();
        e.dataTransfer.dropEffect = 'copy'; // Explicitly show this is a copy.

        $('#drag-drop').fadeIn('slow');
    }

    function handleDragLeave(e) {
        tid = setTimeout(function () {
            e.stopPropagation();
            $('#drag-drop').fadeOut('slow');
        }, 300);
    }

    function handleFileSelect(e) {
        e.stopPropagation();
        e.preventDefault && e.preventDefault();

        $('#drag-drop').fadeOut('slow');

        var files = e.dataTransfer.files; // FileList object.

        // Only proceed when a single file is dropped
        if (files.length > 1 || !files.length) {
            return false;
        }

        var file = files[0];

        // Only allow yaml uploads
        if (file.name.split('.').pop().toLowerCase() !== 'yaml') {
            return false;
        }

        var reader = new FileReader();

        // Closure to capture the file information.
        reader.onload = (function (theFile) {
            return function (e) {
                submitForm(e.target.result);
            };
        })(file);

        // Read in the image file as a data URL.
        reader.readAsText(file);

        return false;
    }

    function submitForm(config) {
        if (!config.length) {
            return;
        }

        var form = $(
            '<form action="' + uploadConfigUrl + '" method="post">' +
                '<input type="hidden" name="config" value="' + config + '" />' +
            '</form>'
        );
        $('body').append(form);
        $(form).submit();
    }
};

/**
 * Controls how the static sidebar scrolls with user
 */
PUPHPET.sidebar = function() {
    $('#nav-sidebar').affix({
        offset: {
            top:  function () {
                return $('#top').height() + 50;
            },
            bottom: function () {
                return $('footer').height() + 140;
            }
        }
    });

    $('body').scrollspy({target: '#nav-sidebar', offset: 300});
};

/**
 * Toggles pills in main container by clicking sidebar links
 */
PUPHPET.sidebarPillToggle = function() {
    $(document).on('click', '#nav-sidebar ul li p a', function(e){
        var tagTarget = this.getAttribute('data-tab-target');
        var $container = $(this).closest('p');

        if (tagTarget != null) {
            $(tagTarget).tab('show');
        }

        return _toggleTabs($container, $(this).find('span.glyphicon')[0]);
    });

    $(document).on('click', 'ul.group-tabs a[data-toggle="tab"]', function(e){
        var id = '#' + this.id;
        var $menuTarget = $('#nav-sidebar ul li p a[data-tab-target="' + id + '"]');
        var $container = $menuTarget.closest('p');

        if ($menuTarget == null) {
            return;
        }

        return _toggleTabs($container, $menuTarget.find('span.glyphicon')[0]);
    });

    function _toggleTabs($container, toActive) {
        $container.siblings().find('span.glyphicon').removeClass('active');
        $(toActive).addClass('active');

        return true;
    }
};

PUPHPET.toggleMultiGroupedTab = function() {
    $(document).on('change', 'input.multiselect-grouped-tab', function(e) {
        var targetContainer     = '#' + this.getAttribute('data-tab-target');
        var targetContainerSpan = '#' + this.getAttribute('data-tab-target') + ' span';

        $(targetContainer).toggleClass('text-success');

        if($(this).is(':checked')){
            $(targetContainerSpan).removeClass('glyphicon-unchecked');
            $(targetContainerSpan).addClass('glyphicon-check');
        } else {
            $(targetContainerSpan).removeClass('glyphicon-check');
            $(targetContainerSpan).addClass('glyphicon-unchecked');
        }

        //var $checkbox = $(this).find('input');
        //$checkbox.prop('checked', !$checkbox.prop('checked'));
    });
};

$(document).ready(function() {
    PUPHPET.updateOtherInput();
    PUPHPET.updateOtherInputSelect();
    PUPHPET.runSelectize(null);
    PUPHPET.selectizeAddClickedToElement();
    PUPHPET.addRepeatableElement();
    PUPHPET.delRepeatableElement();
    PUPHPET.disableInactiveTabElements();
    PUPHPET.enableClickedTabElement();
    PUPHPET.githubContributors();
    PUPHPET.uploadConfig();
    PUPHPET.sidebar();
    PUPHPET.sidebarPillToggle();
    PUPHPET.toggleMultiGroupedTab();
});
<commit_msg>Removed incorrect JS preventing enabling disabled tabs<commit_after>var PUPHPET = {};

/**
 * When element is clicked, update another element.
 *
 * This is good for:
 *
 * 1. User clicks element, user prompted for input, element properties + user input
 *      used to update another element
 * 2. Clicking one element checks another element if target is a radio or checkbox
 *
 * Loops through all data-* type attributes of element
 */
PUPHPET.updateOtherInput = function() {
    $(document).on('click', '.update-other-input', function(e){
        var $parent = $(this);

        $.each($(this).data(), function(key, value) {
            // jQuery changed "data-foo-bar" to "dataFooBar". Change them back.
            key = key.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();

            // Only work with data attributes that have "update-"
            if (key.search('update-') !== 0) {
                return;
            }

            key = key.replace('update-', '');

            var $target = $('#' + key);

            // If target element is not defined as #foo, maybe it is an input,name,value target
            if (!$target.length) {
                $target = $('input[name="' + key + '"][value="'+ value +'"]')
            }

            // If target is a radio element, check it, no need to uncheck in future
            if ($target.is(':radio')) {
                $target.prop('checked', true);

                return;
            }

            /**
             * If target is checkbox element, check if clicked element was checked or unchecked.
             *
             * If unchecked, do not update target. We only want to handle positive actions
             */
            if ($target.is(':checkbox') && $parent.is(':checked')) {
                $target.prop('checked', true);

                return;
            }

            $target.val(value);
        });
    });
};

PUPHPET.updateOtherInputSelect = function() {
    $(document).on('change', 'select.update-other-input', function(e){
        var $parent = $(this);

        $('select.update-other-input option:selected').each(function() {
            $.each($(this).data(), function(key, value) {
                // jQuery changed "data-foo-bar" to "dataFooBar". Change them back.
                key = key.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();

                // Only work with data attributes that have "update-"
                if (key.search('update-') !== 0) {
                    return;
                }

                key = key.replace('update-', '');

                var $target = $('#' + key);

                // If target element is not defined as #foo, maybe it is an input,name,value target
                if (!$target.length) {
                    $target = $('input[name="' + key + '"][value="'+ value +'"]')
                }

                // If target is a radio element, check it, no need to uncheck in future
                if ($target.is(':radio')) {
                    $target.prop('checked', true);

                    return;
                }

                /**
                 * If target is checkbox element, check if clicked element was checked or unchecked.
                 *
                 * If unchecked, do not update target. We only want to handle positive actions
                 */
                if ($target.is(':checkbox') && $parent.is(':checked')) {
                    $target.prop('checked', true);

                    return;
                }

                $target.val(value);
            });
        });
    });
};

/**
 * Run selectize.js on initial page load, and then re-run it whenever
 * new selectize-enabled elements are dynamically added to the DOM.
 *
 * @param $element
 */
PUPHPET.runSelectize = function($element) {
    // input or select elements; allows user to create their own tags
    var $selectTagsEditable = $('.tags, .select-tags-editable', $element).selectize({
        plugins: ['remove_button'],
        delimiter: ',',
        persist: false,
        create: function(input) {
            return {
                value: input,
                text: input
            }
        },
        maxItems: null,
        valueField: 'value',
        labelField: 'text',
        searchField: 'value'
    });

    // select elements; asks user for value of selected tags; cannot create own tags
    var $selectTagsUserInput = PUPHPET.selectizeTagsUserInput($element);

    // select single element; does not allow creating new tag
    var $selectTag = $('.select-tag', $element).selectize({
        persist: false,
        create: false
    });

    // select elements; does not allow creating new tags
    var $selectTags = $('.select-tags', $element).selectize({
        plugins: ['remove_button'],
        delimiter: ',',
        persist: false,
        create: false
    });

    PUPHPET._trackSelectize($selectTagsEditable);
    PUPHPET._trackSelectize($selectTagsUserInput);
    PUPHPET._trackSelectize($selectTag);
    PUPHPET._trackSelectize($selectTags);
};

/**
 * Active for select type elements.
 *
 * On user adding option, prompts user for data, and creates a new, matching
 * hidden element containing user input for easier handling of POSTed data.
 *
 * On user remove option, adds the removed element back to the available options
 * list and deletes the hidden element related to removed option.
 *
 * @param $element
 */
PUPHPET.selectizeTagsUserInput = function($element) {
    var $selectTagsUserInput = $('.select-tags-user-input', $element).selectize({
        plugins: ['remove_button'],
        delimiter: ',',
        persist: false,
        create: false,
        onItemAdd: function(value, $item) {
            var targetContainer     = '#' + $(this['$input'])[0].getAttribute('data-target-container');
            var targetNameStructure = $(this['$input'])[0].getAttribute('data-target-name');
            var elementName         = targetNameStructure + '[' + this.options[value].text + ']';

            var suffix = prompt('Enter Value:') || '0';
            var label  = this.options[value].text + ' = ' + suffix;
            var data   = $.extend({}, this.options[value], {
                text: label
            });

            // Append this user input as a new hidden element
            $('<input>').attr({
                type:  'hidden',
                name:  elementName,
                value: suffix
            }).appendTo(targetContainer);

            this.updateOption(value, data);
        },
        onItemRemove: function(value, $item) {
            var targetContainer     = '#' + $(this['$input'])[0].getAttribute('data-target-container');
            var targetNameStructure = $(this['$input'])[0].getAttribute('data-target-name');
            var elementName         = targetNameStructure + '[' + this.options[value].value + ']';

            $(targetContainer + ' input[name="' + elementName + '"]').remove();

            var data = $.extend({}, this.options[value], {
                text: value
            });

            this.updateOption(value, data);
        }
    });

    // Adds pre-selected option values to selectize field
    for (var i = 0; i < $selectTagsUserInput.length; i++) {
        var $selectElement = $selectTagsUserInput[i].selectize;
        var targetContainer = '#' + $selectTagsUserInput[i].getAttribute('data-target-container');
        var $selectedItems = $(targetContainer);

        if (!$selectedItems.length) {
            continue;
        }

        $selectedItems.children().each(function() {
            var optionName  = this.getAttribute('data-option-name');
            var optionValue = $(this).val();

            var label = $selectElement.options[optionName].text + ' = ' + optionValue;
            var data  = $.extend({}, $selectElement.options[optionName], {
                text: label
            });

            $selectElement.updateOption(optionName, data);
        });
    }

    return $selectTagsUserInput;
};

var selectizedObjects = [];

/**
 * Keep track of all initialized selectize.js elements
 *
 * @param $selectizeElements
 * @private
 */
PUPHPET._trackSelectize = function($selectizeElements) {
    for (var i = 0; i < $selectizeElements.length; i++) {
        selectizedObjects[$selectizeElements[i].id] = $selectizeElements[i];
    }
};

/**
 * Allows adding an item to a selectize.js element on user click
 */
PUPHPET.selectizeAddClickedToElement = function() {
    $(document).on('click', '.addClickedToSelectizeElement', function(e){
        var target    = this.getAttribute('data-target');
        var itemValue = this.getAttribute('data-value');
        var itemTitle = this.getAttribute('data-title') != null
            ? this.getAttribute('data-title')
            : $(this).text();

        if (!(target in selectizedObjects)) {
            return false;
        }

        var control = selectizedObjects[target].selectize;

        control.addOption({
            value: itemValue,
            text: itemTitle
        });
        control.addItem(itemValue);

        return false;
    });
};

/**
 * Adds repeatable containers based on clicked button's data-source-url value.
 *
 * Adds the response right before clicked element, and then re-runs selectize.js
 * on new elements.
 */
PUPHPET.addRepeatableElement = function() {
    $(document).on('click', 'button.addParentContainer', function(e){
        var sourceUrl = this.getAttribute('data-source-url');
        var buttonEle = $(this);

        $.ajax({
            url: sourceUrl,
            cache: false
        }).done(function(response) {
            var $row = $(response).insertBefore(buttonEle.closest('.row'));
            PUPHPET.runSelectize($row);
        });

    });
};

/**
 * Deletes repeatable containers based on button data id
 */
PUPHPET.delRepeatableElement = function() {
    $(document).on('click', 'button.deleteParentContainer', function(e){
        var parentId = this.getAttribute('data-parent-id');
        var $parentContainer = $('#' + parentId);

        $parentContainer.remove();

    });
};

/**
 * If elements are grouped into tabs, set all non-active tab elements as inactive.
 * Do not auto-disable tabs that belong to multi-selectable groups.
 *
 * This is useful so inactive choices do not get POSTed along with rest of form data.
 *
 * Runs on initial page load
 */
PUPHPET.disableInactiveTabElements = function() {
    $('ul.group-tabs li').each(function() {
        if ($(this).parent().hasClass('multi-select')) {
            return;
        }

        if ($(this).hasClass('active')) {
            return;
        }

        var $anchor = $(this).children()[0];
        var extensionId = $anchor.getAttribute('data-target-element');

        $('#' + extensionId).find('input, textarea, button, select').prop('disabled', true);
    });
};

/**
 * When switching tabs, disable all form elements in non-active tabs and
 * enable all form elements in newly active tab. Do not auto-disable
 * tabs that belong to multi-selectable groups
 */
PUPHPET.enableClickedTabElement = function() {
    $('a[data-toggle="tab"]').on('shown.bs.tab', function(e) {
        var original = e.relatedTarget.getAttribute('data-target-element');
        var target   = e.target.getAttribute('data-target-element');

        $('#' + original).find('input, textarea, button, select').prop('disabled', true);
        $('#' + target).find('input, textarea, button, select').prop('disabled', false);
    });
};

/**
 * Display the information about PuPHPet's beloved contributors!
 */
PUPHPET.githubContributors = function() {
    $.get('https://api.github.com/repos/puphpet/puphpet/contributors', function(githubResponse) {
        $.post('/github-contributors', { contributors: githubResponse }, function(response) {
            $('#contributors').html(response);
        });
    });
};

/**
 * Allows user to drag and drop a pre-generated yaml file containing
 * their VMs configuration.
 */
PUPHPET.uploadConfig = function() {
    var dropzone = document.documentElement;
    var tid;

    dropzone.addEventListener('dragover', handleDragOver, false);
    dropzone.addEventListener('dragleave', handleDragLeave, false);
    dropzone.addEventListener('drop', handleFileSelect, false);

    function handleDragOver(e) {
        clearTimeout(tid);
        e.stopPropagation();
        e.preventDefault && e.preventDefault();
        e.dataTransfer.dropEffect = 'copy'; // Explicitly show this is a copy.

        $('#drag-drop').fadeIn('slow');
    }

    function handleDragLeave(e) {
        tid = setTimeout(function () {
            e.stopPropagation();
            $('#drag-drop').fadeOut('slow');
        }, 300);
    }

    function handleFileSelect(e) {
        e.stopPropagation();
        e.preventDefault && e.preventDefault();

        $('#drag-drop').fadeOut('slow');

        var files = e.dataTransfer.files; // FileList object.

        // Only proceed when a single file is dropped
        if (files.length > 1 || !files.length) {
            return false;
        }

        var file = files[0];

        // Only allow yaml uploads
        if (file.name.split('.').pop().toLowerCase() !== 'yaml') {
            return false;
        }

        var reader = new FileReader();

        // Closure to capture the file information.
        reader.onload = (function (theFile) {
            return function (e) {
                submitForm(e.target.result);
            };
        })(file);

        // Read in the image file as a data URL.
        reader.readAsText(file);

        return false;
    }

    function submitForm(config) {
        if (!config.length) {
            return;
        }

        var form = $(
            '<form action="' + uploadConfigUrl + '" method="post">' +
                '<input type="hidden" name="config" value="' + config + '" />' +
            '</form>'
        );
        $('body').append(form);
        $(form).submit();
    }
};

/**
 * Controls how the static sidebar scrolls with user
 */
PUPHPET.sidebar = function() {
    $('#nav-sidebar').affix({
        offset: {
            top:  function () {
                return $('#top').height() + 50;
            },
            bottom: function () {
                return $('footer').height() + 140;
            }
        }
    });

    $('body').scrollspy({target: '#nav-sidebar', offset: 300});
};

/**
 * Toggles pills in main container by clicking sidebar links
 */
PUPHPET.sidebarPillToggle = function() {
    $(document).on('click', '#nav-sidebar ul li p a', function(e){
        var tagTarget = this.getAttribute('data-tab-target');
        var $container = $(this).closest('p');

        if (tagTarget != null) {
            $(tagTarget).tab('show');
        }

        return _toggleTabs($container, $(this).find('span.glyphicon')[0]);
    });

    $(document).on('click', 'ul.group-tabs a[data-toggle="tab"]', function(e){
        var id = '#' + this.id;
        var $menuTarget = $('#nav-sidebar ul li p a[data-tab-target="' + id + '"]');
        var $container = $menuTarget.closest('p');

        if ($menuTarget == null) {
            return;
        }

        return _toggleTabs($container, $menuTarget.find('span.glyphicon')[0]);
    });

    function _toggleTabs($container, toActive) {
        $container.siblings().find('span.glyphicon').removeClass('active');
        $(toActive).addClass('active');

        return true;
    }
};

PUPHPET.toggleMultiGroupedTab = function() {
    $(document).on('change', 'input.multiselect-grouped-tab', function(e) {
        var targetContainer     = '#' + this.getAttribute('data-tab-target');
        var targetContainerSpan = '#' + this.getAttribute('data-tab-target') + ' span';

        $(targetContainer).toggleClass('text-success');

        if($(this).is(':checked')){
            $(targetContainerSpan).removeClass('glyphicon-unchecked');
            $(targetContainerSpan).addClass('glyphicon-check');
        } else {
            $(targetContainerSpan).removeClass('glyphicon-check');
            $(targetContainerSpan).addClass('glyphicon-unchecked');
        }

        //var $checkbox = $(this).find('input');
        //$checkbox.prop('checked', !$checkbox.prop('checked'));
    });
};

$(document).ready(function() {
    PUPHPET.updateOtherInput();
    PUPHPET.updateOtherInputSelect();
    PUPHPET.runSelectize(null);
    PUPHPET.selectizeAddClickedToElement();
    PUPHPET.addRepeatableElement();
    PUPHPET.delRepeatableElement();
    PUPHPET.disableInactiveTabElements();
    PUPHPET.enableClickedTabElement();
    PUPHPET.githubContributors();
    PUPHPET.uploadConfig();
    PUPHPET.sidebar();
    PUPHPET.sidebarPillToggle();
    PUPHPET.toggleMultiGroupedTab();
});
<|endoftext|>


<commit_before>messageFormatPostprocess.number = function (object, data) {
  var parameter = data.parameters[object.name];
  var latnSymbols = data.meta.latnSymbols;

  var wrap = function (string) {
    var wrapper = object[parameter >= 0 ? '+' : '-'];
    return wrapper[0] + string + wrapper[1];
  };

  if (! _.isNumber(parameter) || _.isNaN(parameter)) {
    return latnSymbols.nan;
  }

  if (! _.isFinite(parameter)) {
    return wrap(latnSymbols.infinity);
  }

  parameter *= object.multiplicator || 1;

  if (! object.isScientific) {
    //var divider = (object.divider || 1) * Math.pow(10, object.maxPost || 0);
    //parameter = Math.round(parameter / divider) * divider;
    var absString = Math.abs(parameter).toFixed(object.maxPost);
    var number = absString.match(/^[^\.]*/)[0] || '0';
    var point = absString.replace(/^[^\.]*\./, '');
    var pointZeroless = point.replace(/0+$/, '');

    var groups = object.groups;
    var groupResult = [];
    var position = number.length;
    for (var i = 0; position > 0 ; (i + 1) < groups.length && ++i) {
      var groupLength = groups[i];
      position -= groupLength;
      if (position < 0) {
        groupLength += position;
        position = 0;
      }
      groupResult.unshift(number.substr(position, groupLength));
    }
    console.log(parameter, number, point, groupResult);

    var result = groupResult.join(latnSymbols.group);
    if (pointZeroless.length > 0 && object.maxPost > 0 || object.minPost) {
      if (pointZeroless.length < object.minPost) {
        result += latnSymbols.decimal + point.substr(0, object.minPost);
      } else {
        result += latnSymbols.decimal + pointZeroless;
      }
    }
    return wrap(result);
  }
};
<commit_msg>consisten variable naming<commit_after>messageFormatPostprocess.number = function (object, data) {
  var parameter = data.parameters[object.name];
  var latnSymbols = data.meta.latnSymbols;

  var wrap = function (string) {
    var wrapper = object[parameter >= 0 ? '+' : '-'];
    return wrapper[0] + string + wrapper[1];
  };

  if (! _.isNumber(parameter) || _.isNaN(parameter)) {
    return latnSymbols.nan;
  }

  if (! _.isFinite(parameter)) {
    return wrap(latnSymbols.infinity);
  }

  parameter *= object.multiplicator || 1;

  if (! object.isScientific) {
    //var divider = (object.divider || 1) * Math.pow(10, object.maxPost || 0);
    //parameter = Math.round(parameter / divider) * divider;
    var absString = Math.abs(parameter).toFixed(object.maxPost);
    var prePoint = absString.match(/^[^\.]*/)[0] || '0';
    var postPoint = absString.replace(/^[^\.]*\./, '');
    var postPointZeroless = postPoint.replace(/0+$/, '');

    var groups = object.groups;
    var groupResult = [];
    var position = prePoint.length;
    for (var i = 0; position > 0 ; (i + 1) < groups.length && ++i) {
      var groupLength = groups[i];
      position -= groupLength;
      if (position < 0) {
        groupLength += position;
        position = 0;
      }
      groupResult.unshift(prePoint.substr(position, groupLength));
    }
    console.log(parameter, prePoint, postPoint, groupResult);

    var result = groupResult.join(latnSymbols.group);
    if (postPointZeroless.length > 0 && object.maxPost > 0 || object.minPost) {
      if (postPointZeroless.length < object.minPost) {
        result += latnSymbols.decimal + postPoint.substr(0, object.minPost);
      } else {
        result += latnSymbols.decimal + postPointZeroless;
      }
    }
    return wrap(result);
  }
};
<|endoftext|>


<commit_before>define('app/models/rule', ['ember'],
    //
    //  Rule Model
    //
    //  @returns Class
    ///
    function () {

        'use strict';

        return Ember.Object.extend({

            //
            //
            //  Properties
            //
            //

            id: null,
            unit: null,
            value: null,
            metric: null,
            command: null,
            machine: null,
            operator: null,
            maxValue: null,
            aggregate: null,
            timeWindow: null,
            actionToTake: null,
            pendingAction: false,

            init: function () {
                this._super();
                this.updateFromRawData(this);
            },

            updateFromRawData: function (data) {

                this.setProperties({
                    // Rename action attribute because it conflicts with
                    // handlebar's templating "action" keyword
                    actionToTake: data.action,
                    timeWindow: data.reminder_offset,
                    operator: Mist.rulesController.getOperatorByTitle(data.operator),
                    metric: Mist.metricsController.getMetric(data.metric),
                    aggregate: Mist.rulesController.getAggregateByValue(data.aggregate),
                    machine: Mist.backendsController.getMachine(
                        data.machine, data.backend) || data.machine
                });
            }
        });
    }
);
<commit_msg>Fix updating rule value<commit_after>define('app/models/rule', ['ember'],
    //
    //  Rule Model
    //
    //  @returns Class
    ///
    function () {

        'use strict';

        return Ember.Object.extend({

            //
            //
            //  Properties
            //
            //

            id: null,
            unit: null,
            value: null,
            metric: null,
            command: null,
            machine: null,
            operator: null,
            maxValue: null,
            aggregate: null,
            timeWindow: null,
            actionToTake: null,
            pendingAction: false,

            init: function () {
                this._super();
                this.updateFromRawData(this);
            },

            updateFromRawData: function (data) {

                this.setProperties({
                    // Rename action attribute because it conflicts with
                    // handlebar's templating "action" keyword
                    value: data.value,
                    actionToTake: data.action,
                    timeWindow: data.reminder_offset,
                    operator: Mist.rulesController.getOperatorByTitle(data.operator),
                    metric: Mist.metricsController.getMetric(data.metric),
                    aggregate: Mist.rulesController.getAggregateByValue(data.aggregate),
                    machine: Mist.backendsController.getMachine(
                        data.machine, data.backend) || data.machine
                });
            }
        });
    }
);
<|endoftext|>


<commit_before>import crypto from 'crypto';
import winston from 'winston';
// import buildAvatarURLForSize from '../../lib/buildAvatarURLForSize';
import EnhancedError from '../../lib/EnhancedError';
import { APP_PRIORITIES /* , TYPE_USER*/ } from '../../lib/Constants';
import { bus, config } from '../../core-server';
// import { urlTos3 } from '../../lib/upload';

function getDate(long) {
	const date = new Date();

	if (long) {
		return date.toISOString().replace(/[\-:]/g, '').replace(/\.\d+/g, '');
	} else {
		return date.getUTCFullYear().toString() +
		('0' + date.getUTCMonth().toString()).substr(-2) +
		('0' + date.getUTCDate().toString()).substr(-2);
	}
}

function getExpiration() {
	const validity = config.s3.validity;
	return (new Date(Date.now() + validity)).toISOString();
}

function sign(key, data) {
	return crypto.createHmac('sha256', key).update(data).digest();
}

function getKeyPrefix(userId, uploadType, textId) {
	switch (uploadType) {
	case 'avatar':
	case 'banner':
		return 'uploaded/' + uploadType + '/' + userId + '/';
	case 'content':
		return 'uploaded/' + uploadType + '/' + userId + '/' + textId + '/';
	}

	return '';
}

function getCredential() {
	return `${config.s3.accessKey}/${getDate()}\
		/${config.s3.region}/${config.s3.service}\
		/${config.s3.signatureVersion}`;
}

function getPolicy(keyPrefix) {
	return new Buffer(JSON.stringify({
		expiration: getExpiration(),
		conditions: [
			{ bucket: config.s3.bucket },
			{ acl: config.s3.acl },
			[ 'starts-with', '$key', keyPrefix ],
			{ success_action_status: '201' },
			{ 'x-amz-credential': getCredential() },
			{ 'x-amz-algorithm': config.s3.algorithm },
			{ 'x-amz-date': getDate(true) },
		],
	})).toString('base64');
}

function getSignature(policy) {
	const kDate = sign('AWS4' + config.s3.secretKey, getDate());
	const kRegion = sign(kDate, config.s3.region);
	const kService = sign(kRegion, config.s3.service);
	const signingKey = sign(kService, config.s3.signatureVersion);
	const signature = sign(signingKey, policy).toString('hex');
	return signature;
}

export function getResponse(policyReq) {
	const keyPrefix = getKeyPrefix(policyReq.auth.user, policyReq.uploadType, policyReq.textId),
		policy = getPolicy(keyPrefix),
		signature = getSignature(policy);

	policyReq.response = {
		acl: config.s3.acl,
		policy,
		keyPrefix,
		bucket: config.s3.bucket,
		'x-amz-algorithm': config.s3.algorithm,
		'x-amz-credential': getCredential(),
		'x-amz-date': getDate(true),
		'x-amz-signature': signature,
	};
}

if (!config.s3) {
	winston.info('Image upload is disabled');
	bus.on('s3/getPolicy', (policyReq, next) => {
		policyReq.response = {};
		policyReq.response.error = new EnhancedError('Image upload is temporarily disabled', 'NO_CONFIG_FOUND_FO_S3');
		next();
	}, APP_PRIORITIES.IMAGE_UPLOAD);
} else {
	bus.on('s3/getPolicy', (policyReq, next) => {
		getResponse(policyReq);
		next();
	}, APP_PRIORITIES.IMAGE_UPLOAD);
	winston.info('Image upload is ready');
}

/*
const uploadImage = async (userName: string, imageUrl: string, propName: string) => {
	const imageName = 'avatar';
	await urlTos3(buildAvatarURLForSize(imageUrl, 1024), 'a/' + userName + '/' + imageName);
	const changes = {
		entities: {
			[userName]: {
				type: TYPE_USER,
				id: userName,
				[propName]: {
					picture: [ '$delete' ]
				}
			}
		}
	};
	bus.emit('change', changes);
};

if (config.s3) {
		bus.on('postchange', async ({ entities }) => {
		const promises = [];
		if (entities) {
			for (const id in entities) {
				const entity = entities[id];
				if (entity.type !== TYPE_USER) {
					continue;
				} else {
					if (entity.params && entity.params.picture) {
						promises.push(
							uploadImage(entity.id, entity.params.picture, 'params')
						);
					} else if (entity.meta && entity.meta.picture) {
						promises.push(
							uploadImage(entity.id, entity.meta.picture, 'meta')
						);
					}
				}
			}
		}
		await Promise.all(promises);
	});
}

*/
<commit_msg>Enable: uploading user's profile picture to s3<commit_after>import crypto from 'crypto';
import winston from 'winston';
// import buildAvatarURLForSize from '../../lib/buildAvatarURLForSize';
import EnhancedError from '../../lib/EnhancedError';
import { APP_PRIORITIES /* , TYPE_USER*/ } from '../../lib/Constants';
import { bus, config } from '../../core-server';
// import { urlTos3 } from '../../lib/upload';

function getDate(long) {
	const date = new Date();

	if (long) {
		return date.toISOString().replace(/[\-:]/g, '').replace(/\.\d+/g, '');
	} else {
		return date.getUTCFullYear().toString() +
		('0' + date.getUTCMonth().toString()).substr(-2) +
		('0' + date.getUTCDate().toString()).substr(-2);
	}
}

function getExpiration() {
	const validity = config.s3.validity;
	return (new Date(Date.now() + validity)).toISOString();
}

function sign(key, data) {
	return crypto.createHmac('sha256', key).update(data).digest();
}

function getKeyPrefix(userId, uploadType, textId) {
	switch (uploadType) {
	case 'avatar':
	case 'banner':
		return 'uploaded/' + uploadType + '/' + userId + '/';
	case 'content':
		return 'uploaded/' + uploadType + '/' + userId + '/' + textId + '/';
	}

	return '';
}

function getCredential() {
	return `${config.s3.accessKey}/${getDate()}\
		/${config.s3.region}/${config.s3.service}\
		/${config.s3.signatureVersion}`;
}

function getPolicy(keyPrefix) {
	return new Buffer(JSON.stringify({
		expiration: getExpiration(),
		conditions: [
			{ bucket: config.s3.bucket },
			{ acl: config.s3.acl },
			[ 'starts-with', '$key', keyPrefix ],
			{ success_action_status: '201' },
			{ 'x-amz-credential': getCredential() },
			{ 'x-amz-algorithm': config.s3.algorithm },
			{ 'x-amz-date': getDate(true) },
		],
	})).toString('base64');
}

function getSignature(policy) {
	const kDate = sign('AWS4' + config.s3.secretKey, getDate());
	const kRegion = sign(kDate, config.s3.region);
	const kService = sign(kRegion, config.s3.service);
	const signingKey = sign(kService, config.s3.signatureVersion);
	const signature = sign(signingKey, policy).toString('hex');
	return signature;
}

export function getResponse(policyReq) {
	const keyPrefix = getKeyPrefix(policyReq.auth.user, policyReq.uploadType, policyReq.textId),
		policy = getPolicy(keyPrefix),
		signature = getSignature(policy);

	policyReq.response = {
		acl: config.s3.acl,
		policy,
		keyPrefix,
		bucket: config.s3.bucket,
		'x-amz-algorithm': config.s3.algorithm,
		'x-amz-credential': getCredential(),
		'x-amz-date': getDate(true),
		'x-amz-signature': signature,
	};
}

if (!config.s3) {
	winston.info('Image upload is disabled');
	bus.on('s3/getPolicy', (policyReq, next) => {
		policyReq.response = {};
		policyReq.response.error = new EnhancedError('Image upload is temporarily disabled', 'NO_CONFIG_FOUND_FO_S3');
		next();
	}, APP_PRIORITIES.IMAGE_UPLOAD);
} else {
	bus.on('s3/getPolicy', (policyReq, next) => {
		getResponse(policyReq);
		next();
	}, APP_PRIORITIES.IMAGE_UPLOAD);
	winston.info('Image upload is ready');
}

const uploadImage = async (userName: string, imageUrl: string, propName: string) => {
	const imageName = 'avatar';
	await urlTos3(buildAvatarURLForSize(imageUrl, 1024), 'a/' + userName + '/' + imageName);
	const changes = {
		entities: {
			[userName]: {
				type: TYPE_USER,
				id: userName,
				[propName]: {
					picture: [ '$delete' ]
				}
			}
		}
	};
	bus.emit('change', changes);
};

if (config.s3) {
		bus.on('postchange', async ({ entities }) => {
		const promises = [];
		if (entities) {
			for (const id in entities) {
				const entity = entities[id];
				if (entity.type !== TYPE_USER) {
					continue;
				} else {
					if (entity.params && entity.params.picture) {
						promises.push(
							uploadImage(entity.id, entity.params.picture, 'params')
						);
					} else if (entity.meta && entity.meta.picture) {
						promises.push(
							uploadImage(entity.id, entity.meta.picture, 'meta')
						);
					}
				}
			}
		}
		await Promise.all(promises);
	});
}
<|endoftext|>


<commit_before>angular.module('mwUI.List')

  //TODO rename to mwListHeader
  .directive('mwListableHeaderBb', function ($rootScope, $timeout) {
    return {
      require: '^mwListableBb',
      scope: {
        property: '@?sort',
        title: '@?',
        hidden: '=?'
      },
      transclude: true,
      replace: true,
      templateUrl: 'uikit/mw-list/directives/templates/mw_list_header.html',
      link: function (scope, elm, attr, mwListCtrl) {
        var ascending = '+',
          descending = '-',
          collection = mwListCtrl.getCollection();

        var getSortOrder = function () {
          if (collection && collection.filterable) {
            return collection.filterable.getSortOrder();
          } else {
            return false;
          }
        };

        var sort = function (property, order) {
          var sortOrder = order + property;

          collection.filterable.setSortOrder(sortOrder);
          return collection.fetch();
        };

        var getColumn = function(){
          return {
            scope: scope,
            pos: elm.index(),
            id: scope.$id
          };
        };

        var setTitle = function(){
          if(!attr.title){
            scope.title = elm.text().trim();
          }
        };

        var updateCol = function(){
          scope.pos = elm.index();
          setTitle();
          mwListCtrl.updateColumn(getColumn());
        };

        var throttledUpdateCol = _.throttle(updateCol, 100);

        scope.getTitle = function(){
          return scope.title || '';
        };

        scope.isVisible = function(){
          return !scope.hidden;
        };

        scope.hideColumn = function(){
          scope.hidden = true;
          mwListCtrl.updateColumn(getColumn());
        };

        scope.showColumn= function(){
          scope.hidden = false;
          mwListCtrl.updateColumn(getColumn());
        };

        scope.toggleColumn = function(){
          if(scope.hidden){
            scope.showColumn();
          } else {
            scope.hideColumn();
          }
        };

        scope.canBeSorted = function(){
          return angular.isString(scope.property) && scope.property.length > 0 && !!collection.filterable;
        };

        scope.toggleSortOrder = function () {
          if (scope.canBeSorted()) {
            var sortOrder = ascending; //default
            if (getSortOrder() === ascending + scope.property) {
              sortOrder = descending;
            }
            sort(scope.property, sortOrder);
          }
        };

        scope.isSelected = function (prefix) {
          var sortOrder = getSortOrder();

          if (sortOrder && prefix) {
            return sortOrder === prefix + scope.property;
          } else if(sortOrder && !prefix){
            return (sortOrder === '+' + scope.property || sortOrder === '-' + scope.property);
          }
        };

        mwListCtrl.registerColumn(getColumn());

        scope.$on('$destroy', function () {
          mwListCtrl.unRegisterColumn(getColumn());
        });

        scope.$on('mwList:registerColumn', throttledUpdateCol);
        scope.$on('mwList:unRegisterColumn', throttledUpdateCol);
        attr.$observe('title', throttledUpdateCol);
        $rootScope.$on('i18n:localeChanged', function() {
          $timeout(throttledUpdateCol);
        });
        $timeout(throttledUpdateCol);
      }
    };
  });<commit_msg>Handle hidden cases for different viewports<commit_after>angular.module('mwUI.List')

//TODO rename to mwListHeader
  .directive('mwListableHeaderBb', function ($rootScope, $timeout) {
    return {
      require: '^mwListableBb',
      scope: {
        property: '@?sort',
        title: '@?'
      },
      transclude: true,
      replace: true,
      templateUrl: 'uikit/mw-list/directives/templates/mw_list_header.html',
      link: function (scope, elm, attr, mwListCtrl) {
        var ascending = '+',
          descending = '-',
          collection = mwListCtrl.getCollection(),
          hidden;

        var getSortOrder = function () {
          if (collection && collection.filterable) {
            return collection.filterable.getSortOrder();
          } else {
            return false;
          }
        };

        var sort = function (property, order) {
          var sortOrder = order + property;

          collection.filterable.setSortOrder(sortOrder);
          return collection.fetch();
        };

        var getColumn = function () {
          return {
            scope: scope,
            pos: elm.index(),
            id: scope.$id
          };
        };

        var setTitle = function () {
          if (!attr.title) {
            scope.title = elm.text().trim();
          }
        };

        var updateCol = function () {
          $timeout(function(){
            scope.pos = elm.index();
            setTitle();
            mwListCtrl.updateColumn(getColumn());
          });
        };

        var throttledUpdateCol = _.debounce(updateCol, 100);

        scope.getTitle = function () {
          return scope.title || '';
        };

        scope.isVisible = function () {
          // var activeBreakPoint = mwBootstrapBreakpoint.getActiveBreakpoint();
          // if(_.isArray(scope.hidden)){
          //   return scope.hidden.indexOf(activeBreakPoint) === -1;
          // } else {
          //   return !scope.hidden;
          // }
          if (angular.isUndefined(hidden)) {
            return elm.is(':visible');
          } else {
            return !hidden;
          }
        };

        scope.hideColumn = function () {
          hidden = true;
          mwListCtrl.updateColumn(getColumn());
        };

        scope.showColumn = function () {
          hidden = false;
          mwListCtrl.updateColumn(getColumn());
        };

        scope.toggleColumn = function () {
          if (!scope.isVisible()) {
            scope.showColumn();
          } else {
            scope.hideColumn();
          }
        };

        scope.canBeSorted = function () {
          return angular.isString(scope.property) && scope.property.length > 0 && !!collection.filterable;
        };

        scope.toggleSortOrder = function () {
          if (scope.canBeSorted()) {
            var sortOrder = ascending; //default
            if (getSortOrder() === ascending + scope.property) {
              sortOrder = descending;
            }
            sort(scope.property, sortOrder);
          }
        };

        scope.isSelected = function (prefix) {
          var sortOrder = getSortOrder();

          if (sortOrder && prefix) {
            return sortOrder === prefix + scope.property;
          } else if (sortOrder && !prefix) {
            return (sortOrder === '+' + scope.property || sortOrder === '-' + scope.property);
          }
        };

        mwListCtrl.registerColumn(getColumn());

        scope.$on('$destroy', function () {
          mwListCtrl.unRegisterColumn(getColumn());
        });

        scope.$on('mwList:registerColumn', throttledUpdateCol);
        scope.$on('mwList:registerColumn', throttledUpdateCol);
        scope.$on('mwList:unRegisterColumn', throttledUpdateCol);
        attr.$observe('title', throttledUpdateCol);
        $rootScope.$on('i18n:localeChanged', throttledUpdateCol);
        $rootScope.$on('mwBootstrapBreakpoint:changed', throttledUpdateCol);
        $timeout(throttledUpdateCol);
      }
    };
  });<|endoftext|>


<commit_before>// Dependencies
const React = require('react');
import builder from 'focus-core/component/builder';
import dispatcher from 'focus-core/dispatcher';
const {camelCase: camel} = require('lodash/string');
const {capitalize} = require('lodash/string');
const {isFunction} = require('lodash/lang');
const {reduce} = require('lodash/collection');

// Components

const FacetBox = require('./facet-box').component;
const ListActionBar = require('./action-bar').component;
const ListSummary = require('./list-summary').component;
const Results = require('../common/component/results').component;

const BackToTopComponent = require('../../../common/button/back-to-top').component;

// Store

import {advancedSearchStore} from 'focus-core/search/built-in-store';

// Mixins

const CartridgeBehaviour = require('../../mixin/cartridge-behaviour');
import type from 'focus-core/component/types';

// Actions

import actionBuilder from 'focus-core/search/action-builder';

/**
* Page mixin of the advanced search.
* @type {Object}
*/
const AdvancedSearch = {
    /**
    * Component's mixins
    * @type {Array}
    */
    mixins: [CartridgeBehaviour],
    /**
    * Display name.
    */
    displayName: 'advanced-search',
    /**
    * Get the default props
    * @return {object} the default props
    */
    getDefaultProps() {
        return {
            facetConfig: {},
            scopesConfig: {},
            isSelection: true,
            hasBackToTop: true,
            backToTopComponent: BackToTopComponent,
            store: advancedSearchStore,
            action: undefined,
            service: undefined,
            orderableColumnList: [],
            lineOperationList: [],
            openedFacetList: {},
            groupComponent: undefined,
            lineComponentMapper: undefined,
            scrollParentSelector: undefined,
            onLineClick: undefined,
            style: require('./style')
        };
    },
    /**
    * Props validation
    * @type {Object}
    */
    propTypes: {
        scopesConfig: type('object'),
        facetConfig: type('object'),
        isSelection: type('bool'),
        hasBackToTop: type('bool'),
        backToTopComponent: type('func'),
        store: type('object'),
        action: type('object'),
        service: type('object'),
        openedFacetList: type('object'),
        orderableColumnList: type(['array', 'object']),
        lineOperationList: type(['array', 'object']),
        exportAction: type('func'),
        groupComponent: type('func'),
        lineComponentMapper: type('func'),
        scrollParentSelector: type('string'),
        onLineClick: type('func')
    },
    /**
     * Get initial state
     * @return {Object} initial state
     */
    getInitialState() {
        return (this._getNewStateFromStore());
    },
    /**
    * Register the store listeners
    */
    componentWillMount() {
        //listen to search event
        this.props.store.on('advanced-search-criterias:change', this._onStoreChangeWithSearch);

        //listen to data changes
        ['facets', 'results', 'total-count'].forEach((node) => {
            this.props.store[`add${capitalize(camel(node))}ChangeListener`](this._onStoreChangeWithoutSearch);
        });

        // listen to scope change
        this.props.store.addScopeChangeListener(this._onScopeChange);

        this._action = this.props.action || actionBuilder({
            service: this.props.service,
            identifier: this.props.store.identifier,
            getSearchOptions: () => {return this.props.store.getValue.call(this.props.store); } // Binding the store in the function call
        });
        this._action.search();
    },
    /**
    * Un-register the store listeners
    */
    componentWillUnmount() {
        // remove listeners
        this.props.store.removeListener('advanced-search-criterias:change', this._onStoreChangeWithSearch);
        ['facets', 'results', 'total-count'].forEach((node) => {
            this.props.store[`remove${capitalize(camel(node))}ChangeListener`](this._onStoreChangeWithoutSearch);
        });
        this.props.store.removeScopeChangeListener(this._onScopeChange);
    },

    getSelectedItems() {
        const results = this.refs.resultList;
        const selectedItems = reduce(results.refs, (selectedItems, ref) => {
            if (isFunction(ref.getSelectedItems)) {
                selectedItems = selectedItems.concat(ref.getSelectedItems());
            } else if (ref.refs) {
                selectedItems = selectedItems.concat(reduce(ref.refs, (subSelectedItems, subRef) => {
                    if (isFunction(subRef.getSelectedItems)) {
                        subSelectedItems = subSelectedItems.concat(subRef.getSelectedItems());
                    }
                    return subSelectedItems;
                }, []));
            }
            return selectedItems;
        }, []);
        return selectedItems;
    },
    /**
    * Store changed, update the state, trigger a search after update
    */
    _onStoreChangeWithSearch() {
        this.setState(this._getNewStateFromStore(), this._action.search);
    },
    /**
    * Store changed, update the state, do not trigger a search after update
    */
    _onStoreChangeWithoutSearch() {
        this.setState(this._getNewStateFromStore());
    },
    /**
     * Scope changed, need to remove all existing sort.
     */
    _onScopeChange: function _onScopeChange() {
        dispatcher.handleViewAction({data:{sortBy: null, sortAsc: null},
            type: 'update',
            identifier: advancedSearchStore.identifier});
    },
    /**
     * Compute a state object from the store values.
     * @return {[type]} [description]
     */
    _getNewStateFromStore() {
        const {store} = this.props;
        const query = store.getQuery();
        const scope = store.getScope();
        const selectedFacets = store.getSelectedFacets() || {};
        const groupingKey = store.getGroupingKey();
        const sortBy = store.getSortBy();
        const sortAsc = store.getSortAsc();
        const facets = store.getFacets();
        const results = store.getResults();
        const totalCount = store.getTotalCount();
        return {query, scope, selectedFacets, groupingKey, sortBy, sortAsc, facets, results, totalCount};
    },
    /**
    * Export action handler.
    */
    _exportHandler() {
        this.props.exportAction();
    },
    /**
    * Render the facet box.
    * @returns {HTML} the rendered component
    */
    _renderFacetBox() {
        const {facets, selectedFacets} = this.state;
        const {facetConfig, scopesConfig, openedFacetList} = this.props;
        return (
            <FacetBox
                action={this._action}
                facetConfig={facetConfig}
            	openedFacetList={openedFacetList}
                facets={facets}
                ref='facetBox'
                scopesConfig={scopesConfig}
                selectedFacets={selectedFacets}
                />
        );
    },
    /**
    * Render the list summary component.
    * @returns {HTML} the rendered component
    */
    _renderListSummary() {
        const {query, scope, totalCount} = this.state;
        return (
            <ListSummary
                action={this._action}
                query={query}
                ref='summary'
                scope={scope}
                totalCount={totalCount}
                />
        );
    },
    /**
    * Render the action bar.
    * @returns {HTML} the rendered component
    */
    _renderActionBar() {
        const {facets, groupingKey, selectedFacets, selectionStatus, sortBy} = this.state;
        const {isSelection, lineOperationList, orderableColumnList} = this.props;
        const groupableColumnList = facets ? Object.keys(facets).reduce((result, facetKey) => {
            if (Object.keys(facets[facetKey]).length > 1) {
                result[facetKey] = facetKey;
            }
            return result;
        }, {}) : {};
        const selectionAction = (status) => {
            this.setState({selectionStatus: status});
        };
        return (
            <ListActionBar
                action={this._action}
                groupSelectedKey={groupingKey}
                groupableColumnList={groupableColumnList}
                isSelection={isSelection}
                operationList={lineOperationList}
                orderSelected={sortBy}
                orderableColumnList={orderableColumnList}
                ref='actionBar'
                selectedFacets={selectedFacets}
                selectionAction={selectionAction}
                selectionStatus={selectionStatus}
                />
        );
    },
    /**
    * Render the results component
    * @return {HTML} the rendered component
    */
    _renderResults() {
        const {groupComponent, isSelection, lineComponentMapper, lineOperationList, scrollParentSelector, store} = this.props;
        const {groupingKey, facets, results, selectionStatus, totalCount} = this.state;
        return (
            <Results
                action={this._action}
                groupComponent={groupComponent}
                groupingKey={groupingKey}
                isSelection={isSelection}
                lineClickHandler={this._lineClick}
                lineComponentMapper={lineComponentMapper}
                lineOperationList={lineOperationList}
                lineSelectionHandler={this._selectItem}
                ref='resultList'
                renderSingleGroupDecoration={false}
                resultsFacets={facets}
                resultsMap={results}
                scrollParentSelector={scrollParentSelector}
                selectionStatus={selectionStatus}
                store={store}
                totalCount={totalCount}
                />
        );
    },
    /**
    * Line selection handler
    */
    _selectItem() {
        this.setState({selectionStatus: 'partial'});
    },
    /**
    * Action on line click.
    * @param {object} item  the item clicked
    */
    _lineClick(item) {
        if (this.props.onLineClick) {
            this.props.onLineClick(item);
        }
    },
    /**
    * Render the component
    * @return {HTML} the rendered component
    */
    render() {
        // true if a facet is collapsed
        const facetCollapsedClassName = Object.keys(this.props.openedFacetList).length === 0 ? 'facet-collapsed' : '';
        return (

                <div data-focus={`facet-container ${facetCollapsedClassName}`}>
                    {this._renderFacetBox()}

                    {this._renderListSummary()}
                    {this._renderActionBar()}
                    {this._renderResults()}

                {this.props.hasBackToTop && <this.props.backToTopComponent/>}

        );
    }
};

module.exports = builder(AdvancedSearch);
<commit_msg>[facet collapsed] Add css class instead of data-focus<commit_after>// Dependencies
const React = require('react');
import builder from 'focus-core/component/builder';
import dispatcher from 'focus-core/dispatcher';
const {camelCase: camel} = require('lodash/string');
const {capitalize} = require('lodash/string');
const {isFunction} = require('lodash/lang');
const {reduce} = require('lodash/collection');

// Components

const FacetBox = require('./facet-box').component;
const ListActionBar = require('./action-bar').component;
const ListSummary = require('./list-summary').component;
const Results = require('../common/component/results').component;

const BackToTopComponent = require('../../../common/button/back-to-top').component;

// Store

import {advancedSearchStore} from 'focus-core/search/built-in-store';

// Mixins

const CartridgeBehaviour = require('../../mixin/cartridge-behaviour');
import type from 'focus-core/component/types';

// Actions

import actionBuilder from 'focus-core/search/action-builder';

/**
* Page mixin of the advanced search.
* @type {Object}
*/
const AdvancedSearch = {
    /**
    * Component's mixins
    * @type {Array}
    */
    mixins: [CartridgeBehaviour],
    /**
    * Display name.
    */
    displayName: 'advanced-search',
    /**
    * Get the default props
    * @return {object} the default props
    */
    getDefaultProps() {
        return {
            facetConfig: {},
            scopesConfig: {},
            isSelection: true,
            hasBackToTop: true,
            backToTopComponent: BackToTopComponent,
            store: advancedSearchStore,
            action: undefined,
            service: undefined,
            orderableColumnList: [],
            lineOperationList: [],
            openedFacetList: {},
            groupComponent: undefined,
            lineComponentMapper: undefined,
            scrollParentSelector: undefined,
            onLineClick: undefined,
            style: require('./style')
        };
    },
    /**
    * Props validation
    * @type {Object}
    */
    propTypes: {
        scopesConfig: type('object'),
        facetConfig: type('object'),
        isSelection: type('bool'),
        hasBackToTop: type('bool'),
        backToTopComponent: type('func'),
        store: type('object'),
        action: type('object'),
        service: type('object'),
        openedFacetList: type('object'),
        orderableColumnList: type(['array', 'object']),
        lineOperationList: type(['array', 'object']),
        exportAction: type('func'),
        groupComponent: type('func'),
        lineComponentMapper: type('func'),
        scrollParentSelector: type('string'),
        onLineClick: type('func')
    },
    /**
     * Get initial state
     * @return {Object} initial state
     */
    getInitialState() {
        return (this._getNewStateFromStore());
    },
    /**
    * Register the store listeners
    */
    componentWillMount() {
        //listen to search event
        this.props.store.on('advanced-search-criterias:change', this._onStoreChangeWithSearch);

        //listen to data changes
        ['facets', 'results', 'total-count'].forEach((node) => {
            this.props.store[`add${capitalize(camel(node))}ChangeListener`](this._onStoreChangeWithoutSearch);
        });

        // listen to scope change
        this.props.store.addScopeChangeListener(this._onScopeChange);

        this._action = this.props.action || actionBuilder({
            service: this.props.service,
            identifier: this.props.store.identifier,
            getSearchOptions: () => {return this.props.store.getValue.call(this.props.store); } // Binding the store in the function call
        });
        this._action.search();
    },
    /**
    * Un-register the store listeners
    */
    componentWillUnmount() {
        // remove listeners
        this.props.store.removeListener('advanced-search-criterias:change', this._onStoreChangeWithSearch);
        ['facets', 'results', 'total-count'].forEach((node) => {
            this.props.store[`remove${capitalize(camel(node))}ChangeListener`](this._onStoreChangeWithoutSearch);
        });
        this.props.store.removeScopeChangeListener(this._onScopeChange);
    },

    getSelectedItems() {
        const results = this.refs.resultList;
        const selectedItems = reduce(results.refs, (selectedItems, ref) => {
            if (isFunction(ref.getSelectedItems)) {
                selectedItems = selectedItems.concat(ref.getSelectedItems());
            } else if (ref.refs) {
                selectedItems = selectedItems.concat(reduce(ref.refs, (subSelectedItems, subRef) => {
                    if (isFunction(subRef.getSelectedItems)) {
                        subSelectedItems = subSelectedItems.concat(subRef.getSelectedItems());
                    }
                    return subSelectedItems;
                }, []));
            }
            return selectedItems;
        }, []);
        return selectedItems;
    },
    /**
    * Store changed, update the state, trigger a search after update
    */
    _onStoreChangeWithSearch() {
        this.setState(this._getNewStateFromStore(), this._action.search);
    },
    /**
    * Store changed, update the state, do not trigger a search after update
    */
    _onStoreChangeWithoutSearch() {
        this.setState(this._getNewStateFromStore());
    },
    /**
     * Scope changed, need to remove all existing sort.
     */
    _onScopeChange: function _onScopeChange() {
        dispatcher.handleViewAction({data:{sortBy: null, sortAsc: null},
            type: 'update',
            identifier: advancedSearchStore.identifier});
    },
    /**
     * Compute a state object from the store values.
     * @return {[type]} [description]
     */
    _getNewStateFromStore() {
        const {store} = this.props;
        const query = store.getQuery();
        const scope = store.getScope();
        const selectedFacets = store.getSelectedFacets() || {};
        const groupingKey = store.getGroupingKey();
        const sortBy = store.getSortBy();
        const sortAsc = store.getSortAsc();
        const facets = store.getFacets();
        const results = store.getResults();
        const totalCount = store.getTotalCount();
        return {query, scope, selectedFacets, groupingKey, sortBy, sortAsc, facets, results, totalCount};
    },
    /**
    * Export action handler.
    */
    _exportHandler() {
        this.props.exportAction();
    },
    /**
    * Render the facet box.
    * @returns {HTML} the rendered component
    */
    _renderFacetBox() {
        const {facets, selectedFacets} = this.state;
        const {facetConfig, scopesConfig, openedFacetList} = this.props;
        return (
            <FacetBox
                action={this._action}
                facetConfig={facetConfig}
            	openedFacetList={openedFacetList}
                facets={facets}
                ref='facetBox'
                scopesConfig={scopesConfig}
                selectedFacets={selectedFacets}
                />
        );
    },
    /**
    * Render the list summary component.
    * @returns {HTML} the rendered component
    */
    _renderListSummary() {
        const {query, scope, totalCount} = this.state;
        return (
            <ListSummary
                action={this._action}
                query={query}
                ref='summary'
                scope={scope}
                totalCount={totalCount}
                />
        );
    },
    /**
    * Render the action bar.
    * @returns {HTML} the rendered component
    */
    _renderActionBar() {
        const {facets, groupingKey, selectedFacets, selectionStatus, sortBy} = this.state;
        const {isSelection, lineOperationList, orderableColumnList} = this.props;
        const groupableColumnList = facets ? Object.keys(facets).reduce((result, facetKey) => {
            if (Object.keys(facets[facetKey]).length > 1) {
                result[facetKey] = facetKey;
            }
            return result;
        }, {}) : {};
        const selectionAction = (status) => {
            this.setState({selectionStatus: status});
        };
        return (
            <ListActionBar
                action={this._action}
                groupSelectedKey={groupingKey}
                groupableColumnList={groupableColumnList}
                isSelection={isSelection}
                operationList={lineOperationList}
                orderSelected={sortBy}
                orderableColumnList={orderableColumnList}
                ref='actionBar'
                selectedFacets={selectedFacets}
                selectionAction={selectionAction}
                selectionStatus={selectionStatus}
                />
        );
    },
    /**
    * Render the results component
    * @return {HTML} the rendered component
    */
    _renderResults() {
        const {groupComponent, isSelection, lineComponentMapper, lineOperationList, scrollParentSelector, store} = this.props;
        const {groupingKey, facets, results, selectionStatus, totalCount} = this.state;
        return (
            <Results
                action={this._action}
                groupComponent={groupComponent}
                groupingKey={groupingKey}
                isSelection={isSelection}
                lineClickHandler={this._lineClick}
                lineComponentMapper={lineComponentMapper}
                lineOperationList={lineOperationList}
                lineSelectionHandler={this._selectItem}
                ref='resultList'
                renderSingleGroupDecoration={false}
                resultsFacets={facets}
                resultsMap={results}
                scrollParentSelector={scrollParentSelector}
                selectionStatus={selectionStatus}
                store={store}
                totalCount={totalCount}
                />
        );
    },
    /**
    * Line selection handler
    */
    _selectItem() {
        this.setState({selectionStatus: 'partial'});
    },
    /**
    * Action on line click.
    * @param {object} item  the item clicked
    */
    _lineClick(item) {
        if (this.props.onLineClick) {
            this.props.onLineClick(item);
        }
    },
    /**
    * Render the component
    * @return {HTML} the rendered component
    */
    render() {
        // true if a facet is collapsed
        const facetCollapsedClassName = Object.keys(this.props.openedFacetList).length === 0 ? 'facet-collapsed' : '';
        return (

                    {this._renderFacetBox()}

                    {this._renderListSummary()}
                    {this._renderActionBar()}
                    {this._renderResults()}

                {this.props.hasBackToTop && <this.props.backToTopComponent/>}

        );
    }
};

module.exports = builder(AdvancedSearch);
<|endoftext|>


<commit_before>import React  from 'react';
import Footer from '../../components/signup-form/footer.js';
import Header from '../../components/signup-form/header.js';
import Logo from '../../components/signup-form/logo.js';
import CommentFormSticky from '../../components/signup-form/signup-form-sticky.js';
import CommentForm from '../../components/net-neutrality/fcc-comments-form.js';
import CommentFormContainer from '../../components/signup-form/signup-form-container.js';
import SignupForm from '../../components/net-neutrality/signup-form.js';
import FormBody from '../../components/signup-form/form-body.js';
import Modal from '../../components/modal.js';

var Signup = React.createClass({
  getInitialState: function() {
    return {
      showModal: false,
      signupSuccess: false
    };
  },
  componentDidMount: function() {
    if (this.props.test === "signup-modal" || this.props.location.query.subscribed) {
      setTimeout(() => {
        this.setState({
          showModal: true
        });
      }, 3000);
    }
  },
  closeModal: function() {
    this.setState({
      showModal: false
    });
  },
  onSuccess: function() {
    this.setState({
      signupSuccess: true
    });
  },
  onResize: function() {
    if (!this.stickyForm) {
      return;
    }
    this.stickyForm.onResize();
  },
  render: function() {
    var className = "signup net-neutrality-comments";
    if (this.props.test) {
      className += " " + this.props.test;
    }

    var modal = null;
    if (this.state.showModal) {
      if (this.state.signupSuccess) {
        modal = (
          <Modal onClose={this.closeModal}>

Thanks! Please check your inbox or your spam filter for an email from us to confirm your subscription.

              <button className="button" onClick={this.closeModal}>Yes, I got it</button>

          </Modal>
        );
      } else {
        modal = (
          <Modal onClose={this.closeModal}>
            <SignupForm onClose={this.closeModal} onSuccess={this.onSuccess}/>
          </Modal>
        );
      }
    }

    return (

        {modal}

the beginning of the end of the open internet?

not if we do something about it

                    The Federal Communications Commission is inviting public comments about proposed rules that would gut net neutrality and hand over the internet to big cable companies like Comcast, AT&T, and Verizon.

                    Send in YOUR comment in support of net neutrality now.

                    Here are some reasons why Mozilla cares about net neutrality — use these or any that express why you care about this issue.

                    Net neutrality is fundamental to free speech.

                    Without net neutrality, big companies could censor people and perspectives online. Net neutrality has been called the "First Amendment of the Internet."

                    Net neutrality protects small businesses and innovators who are just getting started.

                    Without net neutrality, creators and entrepreneurs could struggle to reach new users. Investment in new ideas would dry up and only the big companies would survive, stifling innovation.

                    Net neutrality allows consumers — not big companies — to choose what they watch & do online.

                    Without net neutrality, ISPs could decide you watched too many videos on Netflix in one day and throttle your Internet speeds, while keeping their own video apps running smooth.

            <CommentFormSticky viewportPadding={0} ref={(input) => { this.stickyForm = input; }}>

                Tell the FCC to leave net neutrality alone.

                Write your comment below to tell the FCC why net neutrality MUST be protected. We’ll deliver your comment straight to the agency.

              <CommentFormContainer cta="Tell the FCC: Leave Net Neutrality Alone">
                <CommentForm
                  onResize={this.onResize}
                  subscribed={this.props.location.query.subscribed || this.state.signupSuccess}
                  cta="Submit your comment"
                />
              </CommentFormContainer>
            </CommentFormSticky>

    );
  }
});

module.exports = Signup;
<commit_msg>Make modal stay up.<commit_after>import React  from 'react';
import Footer from '../../components/signup-form/footer.js';
import Header from '../../components/signup-form/header.js';
import Logo from '../../components/signup-form/logo.js';
import CommentFormSticky from '../../components/signup-form/signup-form-sticky.js';
import CommentForm from '../../components/net-neutrality/fcc-comments-form.js';
import CommentFormContainer from '../../components/signup-form/signup-form-container.js';
import SignupForm from '../../components/net-neutrality/signup-form.js';
import FormBody from '../../components/signup-form/form-body.js';
import Modal from '../../components/modal.js';

var Signup = React.createClass({
  getInitialState: function() {
    return {
      showModal: false,
      signupSuccess: false
    };
  },
  componentDidMount: function() {
    if (!this.props.location.query.subscribed) {
      setTimeout(() => {
        this.setState({
          showModal: true
        });
      }, 3000);
    }
  },
  closeModal: function() {
    this.setState({
      showModal: false
    });
  },
  onSuccess: function() {
    this.setState({
      signupSuccess: true
    });
  },
  onResize: function() {
    if (!this.stickyForm) {
      return;
    }
    this.stickyForm.onResize();
  },
  render: function() {
    var className = "signup net-neutrality-comments";
    if (this.props.test) {
      className += " " + this.props.test;
    }

    var modal = null;
    if (this.state.showModal) {
      if (this.state.signupSuccess) {
        modal = (
          <Modal onClose={this.closeModal}>

Thanks! Please check your inbox or your spam filter for an email from us to confirm your subscription.

              <button className="button" onClick={this.closeModal}>Yes, I got it</button>

          </Modal>
        );
      } else {
        modal = (
          <Modal onClose={this.closeModal}>
            <SignupForm onClose={this.closeModal} onSuccess={this.onSuccess}/>
          </Modal>
        );
      }
    }

    return (

        {modal}

the beginning of the end of the open internet?

not if we do something about it

                    The Federal Communications Commission is inviting public comments about proposed rules that would gut net neutrality and hand over the internet to big cable companies like Comcast, AT&T, and Verizon.

                    Send in YOUR comment in support of net neutrality now.

                    Here are some reasons why Mozilla cares about net neutrality — use these or any that express why you care about this issue.

                    Net neutrality is fundamental to free speech.

                    Without net neutrality, big companies could censor people and perspectives online. Net neutrality has been called the "First Amendment of the Internet."

                    Net neutrality protects small businesses and innovators who are just getting started.

                    Without net neutrality, creators and entrepreneurs could struggle to reach new users. Investment in new ideas would dry up and only the big companies would survive, stifling innovation.

                    Net neutrality allows consumers — not big companies — to choose what they watch & do online.

                    Without net neutrality, ISPs could decide you watched too many videos on Netflix in one day and throttle your Internet speeds, while keeping their own video apps running smooth.

            <CommentFormSticky viewportPadding={0} ref={(input) => { this.stickyForm = input; }}>

                Tell the FCC to leave net neutrality alone.

                Write your comment below to tell the FCC why net neutrality MUST be protected. We’ll deliver your comment straight to the agency.

              <CommentFormContainer cta="Tell the FCC: Leave Net Neutrality Alone">
                <CommentForm
                  onResize={this.onResize}
                  subscribed={this.props.location.query.subscribed || this.state.signupSuccess}
                  cta="Submit your comment"
                />
              </CommentFormContainer>
            </CommentFormSticky>

    );
  }
});

module.exports = Signup;
<|endoftext|>