<commit_before>import _ from 'lodash';
import { uiFeatureList } from './feature_list';
import { uiInspector } from './inspector';
import { uiNotice } from './notice';

export function uiSidebar(context) {
    var inspector = uiInspector(context),
        current;

    function sidebar(selection) {
        var featureListWrap = selection
            .append('div')
            .attr('class', 'feature-list-pane')
            .call(uiFeatureList(context));

        selection
            .call(uiNotice(context));

        var inspectorWrap = selection
            .append('div')
            .attr('class', 'inspector-hidden inspector-wrap fr');

        function hover(id) {
            if (!current && context.hasEntity(id)) {
                featureListWrap
                    .classed('inspector-hidden', true);

                inspectorWrap
                    .classed('inspector-hidden', false)
                    .classed('inspector-hover', true);

                if (inspector.entityID() !== id || inspector.state() !== 'hover') {
                    inspector
                        .state('hover')
                        .entityID(id);

                    inspectorWrap
                        .call(inspector);
                }

            } else if (!current) {
                featureListWrap
                    .classed('inspector-hidden', false);
                inspectorWrap
                    .classed('inspector-hidden', true);
                inspector
                    .state('hide');
            }
        }

        sidebar.hover = _.throttle(hover, 200);

        sidebar.select = function(id, newFeature) {
            if (!current && id) {
                featureListWrap
                    .classed('inspector-hidden', true);

                inspectorWrap
                    .classed('inspector-hidden', false)
                    .classed('inspector-hover', false);

                if (inspector.entityID() !== id || inspector.state() !== 'select') {
                    inspector
                        .state('select')
                        .entityID(id)
                        .newFeature(newFeature);

                    inspectorWrap
                        .call(inspector);
                }

            } else if (!current) {
                featureListWrap
                    .classed('inspector-hidden', false);
                inspectorWrap
                    .classed('inspector-hidden', true);
                inspector
                    .state('hide');
            }
        };

        sidebar.show = function(component) {
            featureListWrap
                .classed('inspector-hidden', true);
            inspectorWrap
                .classed('inspector-hidden', true);

            if (current) current.remove();
            current = selection
                .append('div')
                .attr('class', 'sidebar-component')
                .call(component);
        };

        sidebar.hide = function() {
            featureListWrap
                .classed('inspector-hidden', false);
            inspectorWrap
                .classed('inspector-hidden', true);

            if (current) current.remove();
            current = null;
        };
    }

    sidebar.hover = function() {};
    sidebar.hover.cancel = function() {};
    sidebar.select = function() {};
    sidebar.show = function() {};
    sidebar.hide = function() {};

    return sidebar;
}
<commit_msg>Remove zoom to edit notice<commit_after>import _ from 'lodash';
import { uiFeatureList } from './feature_list';
import { uiInspector } from './inspector';

export function uiSidebar(context) {
    var inspector = uiInspector(context),
        current;

    function sidebar(selection) {
        var featureListWrap = selection
            .append('div')
            .attr('class', 'feature-list-pane')
            .call(uiFeatureList(context));

        var inspectorWrap = selection
            .append('div')
            .attr('class', 'inspector-hidden inspector-wrap fr');

        function hover(id) {
            if (!current && context.hasEntity(id)) {
                featureListWrap
                    .classed('inspector-hidden', true);

                inspectorWrap
                    .classed('inspector-hidden', false)
                    .classed('inspector-hover', true);

                if (inspector.entityID() !== id || inspector.state() !== 'hover') {
                    inspector
                        .state('hover')
                        .entityID(id);

                    inspectorWrap
                        .call(inspector);
                }

            } else if (!current) {
                featureListWrap
                    .classed('inspector-hidden', false);
                inspectorWrap
                    .classed('inspector-hidden', true);
                inspector
                    .state('hide');
            }
        }

        sidebar.hover = _.throttle(hover, 200);

        sidebar.select = function(id, newFeature) {
            if (!current && id) {
                featureListWrap
                    .classed('inspector-hidden', true);

                inspectorWrap
                    .classed('inspector-hidden', false)
                    .classed('inspector-hover', false);

                if (inspector.entityID() !== id || inspector.state() !== 'select') {
                    inspector
                        .state('select')
                        .entityID(id)
                        .newFeature(newFeature);

                    inspectorWrap
                        .call(inspector);
                }

            } else if (!current) {
                featureListWrap
                    .classed('inspector-hidden', false);
                inspectorWrap
                    .classed('inspector-hidden', true);
                inspector
                    .state('hide');
            }
        };

        sidebar.show = function(component) {
            featureListWrap
                .classed('inspector-hidden', true);
            inspectorWrap
                .classed('inspector-hidden', true);

            if (current) current.remove();
            current = selection
                .append('div')
                .attr('class', 'sidebar-component')
                .call(component);
        };

        sidebar.hide = function() {
            featureListWrap
                .classed('inspector-hidden', false);
            inspectorWrap
                .classed('inspector-hidden', true);

            if (current) current.remove();
            current = null;
        };
    }

    sidebar.hover = function() {};
    sidebar.hover.cancel = function() {};
    sidebar.select = function() {};
    sidebar.show = function() {};
    sidebar.hide = function() {};

    return sidebar;
}
<|endoftext|>


<commit_before>;(function($) {
  var _defaults = {
    /**
     * Automatically submit the form after it is bound
     */
    submitOnBind: false,

    /**
     * The expected response dataType
     */
    dataType: 'json',

    /**
     * Called when a valid form is submitted.
     *
     * Return false to halt further event execution.
     */
    onSubmit:  $.noop,

    /**
     * Called when a form returns a successful response.
     *
     * Takes one argument: response
     * Return false to halt further event execution.
     */
    onSuccess: $.noop,

    /**
     * Called when a form returns an error response.
     *
     * Takes three arguments: jqXHR, textStatus, errorThrown
     * Return false to halt further event execution.
     */
    onError:   $.noop,

    /**
     * @see jQuery.validate.settings.showErrors
     *
     * Overrides the default, funky show/hide functionality.
     * If using this implementation, highlight and unhighlight are responsible
     * for showing and hiding elements, respectively.
     */
    showErrors: function() {
      var i, elements;
      for (i = 0; this.errorList[i]; i++) {
        var error = this.errorList[i];
        if (this.settings.highlight) {
          this.settings.highlight.call(this, error.element, this.settings.errorClass, this.settings.validClass);
        }

        this.showLabel(error.element, error.message);
      }

      if (this.settings.unhighlight) {
        for (i = 0, elements = this.validElements(); elements[i]; i++) {
          this.settings.unhighlight.call(this, elements[i], this.settings.errorClass, this.settings.validClass);
        }
      }
    },

    /**
     * @see jQuery.validate.settings.errorPlacement
     */
    errorPlacement: function (error, element) {
      var $helpBlock = $(element).closest('.form-group').find('.help-block');
      $helpBlock.data('last-text', $helpBlock.html());
      $helpBlock.html(error.text()).show();
    },

    /**
     * @see jQuery.validate.settings.highlight
     */
    highlight: function(element, errorClass, validClass) {
      var $helpBlock = $(element).closest('.form-group').addClass(errorClass).find('.help-block').show();
    },

    /**
     * @see jQuery.validate.settings.unhighlight
     */
    unhighlight: function(element, errorClass, validClass) {
      var $helpBlock = $(element).closest('.form-group').removeClass(errorClass).find('.help-block');
      if ($helpBlock.data('last-text')) {
        $helpBlock.html($helpBlock.data('last-text'));
      } else {
        $helpBlock.text('').hide();
      }
    },

    errorClass: 'error has-error',

    validClass: ''
  };

  var _submitHandlerFactory = function(options) {
    var _chainHandlers = function(handlerA, handlerB) {
      return function() {
        if (false !== handlerA.apply(null, arguments)) {
          handlerB.apply(null, arguments);
        }
      };
    };

    var successHandler = _chainHandlers(options.onSuccess, Orkestra.response.successHandler);
    var errorHandler = _chainHandlers(options.onError, Orkestra.response.errorHandler);

    var defaultSubmitHandler = function(form) {
      $(form).ajaxSubmit({
        dataType: options.dataType,
        success:  successHandler,
        error:    errorHandler
      });
    };

    return _chainHandlers(options.onSubmit, defaultSubmitHandler);
  };

  var _bindEnhancements = function(form) {
    var $form = $(form);

    // Date and time pickers
    if ($.isFunction($.fn.datetimepicker)) {
      $('input.date', $form).datetimepicker({
        pickTime : false,
        format : Orkestra.dateFormat || 'MM/DD/YY'
      });

      $('input.timepicker', $form).datetimepicker({
        useSeconds : false,
        pickDate : false,
        minuteStepping : 1,
        format : 'hh:mm A'
      });

      $('input.datetimepicker', $form).datetimepicker({
        useSeconds : false,
        format : Orkestra.dateFormat + ' hh:mm A' || 'MM/DD/YY hh:mm A'
      });
    }

    // USA Currency
    if ($.isFunction($.fn.formatCurrency)) {
      // Currency
      $('input.currency', $form).formatCurrency(true);
    }

    // USA Telephone
    if ($.isFunction($.fn.formatPhoneNumber)) {
      // Telephone
      $('input.telephone', $form).formatPhoneNumber();
    }

    // USA SSN
    if ($.isFunction($.fn.formatSocial)) {
      $('input.social', $form).formatSocial();
    }

    // USA EIN
    if ($.isFunction($.fn.formatEin)) {
      $('input.ein', $form).formatEin();
    }

    // URL
    if ($.isFunction($.fn.formatUrl)) {
      $('input.url', $form).formatUrl();
    }

    // Integer
    if ($.isFunction($.fn.formatNumber)) {
      $('input.integer', $form).formatNumber({
        initValue: true
      });
    }
  };

  var helper = function() {

  };

  helper.prototype = $.extend(helper.prototype, {
    bind: function(form, options) {
      options = $.extend({}, _defaults, options);

      options.submitHandler = options.submitHandler || _submitHandlerFactory(options);

      $(form).data('validator', null).unbind('submit').validate(options);
      _bindEnhancements.call(this, form);

      if (true === options.submitOnBind) {
        $(function() {
          $(form).submit();
        });
      }
    },

    bindEnhancements: _bindEnhancements,

    setDefaults: function(options) {
      $.extend(_defaults, options);
    }
  });

  window.Orkestra = window.Orkestra || {};
  window.Orkestra.FormHelper = helper;
})(jQuery);
<commit_msg>Fix for multiple custom formats and Orkestra.(date|time)Format defaulting<commit_after>;(function($) {
  var _defaults = {
    /**
     * Automatically submit the form after it is bound
     */
    submitOnBind: false,

    /**
     * The expected response dataType
     */
    dataType: 'json',

    /**
     * Called when a valid form is submitted.
     *
     * Return false to halt further event execution.
     */
    onSubmit:  $.noop,

    /**
     * Called when a form returns a successful response.
     *
     * Takes one argument: response
     * Return false to halt further event execution.
     */
    onSuccess: $.noop,

    /**
     * Called when a form returns an error response.
     *
     * Takes three arguments: jqXHR, textStatus, errorThrown
     * Return false to halt further event execution.
     */
    onError:   $.noop,

    /**
     * @see jQuery.validate.settings.showErrors
     *
     * Overrides the default, funky show/hide functionality.
     * If using this implementation, highlight and unhighlight are responsible
     * for showing and hiding elements, respectively.
     */
    showErrors: function() {
      var i, elements;
      for (i = 0; this.errorList[i]; i++) {
        var error = this.errorList[i];
        if (this.settings.highlight) {
          this.settings.highlight.call(this, error.element, this.settings.errorClass, this.settings.validClass);
        }

        this.showLabel(error.element, error.message);
      }

      if (this.settings.unhighlight) {
        for (i = 0, elements = this.validElements(); elements[i]; i++) {
          this.settings.unhighlight.call(this, elements[i], this.settings.errorClass, this.settings.validClass);
        }
      }
    },

    /**
     * @see jQuery.validate.settings.errorPlacement
     */
    errorPlacement: function (error, element) {
      var $helpBlock = $(element).closest('.form-group').find('.help-block');
      $helpBlock.data('last-text', $helpBlock.html());
      $helpBlock.html(error.text()).show();
    },

    /**
     * @see jQuery.validate.settings.highlight
     */
    highlight: function(element, errorClass, validClass) {
      var $helpBlock = $(element).closest('.form-group').addClass(errorClass).find('.help-block').show();
    },

    /**
     * @see jQuery.validate.settings.unhighlight
     */
    unhighlight: function(element, errorClass, validClass) {
      var $helpBlock = $(element).closest('.form-group').removeClass(errorClass).find('.help-block');
      if ($helpBlock.data('last-text')) {
        $helpBlock.html($helpBlock.data('last-text'));
      } else {
        $helpBlock.text('').hide();
      }
    },

    errorClass: 'error has-error',

    validClass: ''
  };

  var _submitHandlerFactory = function(options) {
    var _chainHandlers = function(handlerA, handlerB) {
      return function() {
        if (false !== handlerA.apply(null, arguments)) {
          handlerB.apply(null, arguments);
        }
      };
    };

    var successHandler = _chainHandlers(options.onSuccess, Orkestra.response.successHandler);
    var errorHandler = _chainHandlers(options.onError, Orkestra.response.errorHandler);

    var defaultSubmitHandler = function(form) {
      $(form).ajaxSubmit({
        dataType: options.dataType,
        success:  successHandler,
        error:    errorHandler
      });
    };

    return _chainHandlers(options.onSubmit, defaultSubmitHandler);
  };

  var _bindEnhancements = function(form) {
    var $form = $(form);

    // Date and time pickers
    if ($.isFunction($.fn.datetimepicker)) {
      $('input.date', $form).each(function (index,elem) {
        var data = $(elem).data() || {};
        $(elem).datetimepicker({
          pickTime : false,
          format : data['format'] || Orkestra.dateFormat || 'MM/DD/YY'
        });
      });

      $('input.timepicker', $form).each(function (index,elem) {
        var data = $(elem).data() || {};
        $(elem).datetimepicker({
          useSeconds : data['useSeconds'] || false,
          pickDate : false,
          minuteStepping : data['minuteStep'] || 1,
          format : data['format'] || Orkestra.timeFormat || 'hh:mm A'
        });
      });

      $('input.datetimepicker', $form).each(function (index,elem) {
        var data = $(elem).data() || {};
        $(elem).datetimepicker({
          useSeconds : data['useSeconds'] || false,
          format : data['format'] || Orkestra.dateFormat + Orkestra.timeFormat || 'MM/DD/YY hh:mm A'
        });
      });
    }

    // USA Currency
    if ($.isFunction($.fn.formatCurrency)) {
      // Currency
      $('input.currency', $form).formatCurrency(true);
    }

    // USA Telephone
    if ($.isFunction($.fn.formatPhoneNumber)) {
      // Telephone
      $('input.telephone', $form).formatPhoneNumber();
    }

    // USA SSN
    if ($.isFunction($.fn.formatSocial)) {
      $('input.social', $form).formatSocial();
    }

    // USA EIN
    if ($.isFunction($.fn.formatEin)) {
      $('input.ein', $form).formatEin();
    }

    // URL
    if ($.isFunction($.fn.formatUrl)) {
      $('input.url', $form).formatUrl();
    }

    // Integer
    if ($.isFunction($.fn.formatNumber)) {
      $('input.integer', $form).formatNumber({
        initValue: true
      });
    }
  };

  var helper = function() {

  };

  helper.prototype = $.extend(helper.prototype, {
    bind: function(form, options) {
      options = $.extend({}, _defaults, options);

      options.submitHandler = options.submitHandler || _submitHandlerFactory(options);

      $(form).data('validator', null).unbind('submit').validate(options);
      _bindEnhancements.call(this, form);

      if (true === options.submitOnBind) {
        $(function() {
          $(form).submit();
        });
      }
    },

    bindEnhancements: _bindEnhancements,

    setDefaults: function(options) {
      $.extend(_defaults, options);
    }
  });

  window.Orkestra = window.Orkestra || {};
  window.Orkestra.FormHelper = helper;
})(jQuery);
<|endoftext|>


<commit_before>/**
 * This file is part of Shuup.
 *
 * Copyright (c) 2012-2019, Shoop Commerce Ltd. All rights reserved.
 *
 * This source code is licensed under the OSL-3.0 license found in the
 * LICENSE file in the root directory of this source tree.
 */

// CustomEvent polyfill
// https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent#Polyfill
if (typeof window.CustomEvent !== "function") {
    function CustomEvent(event, params) {
        params = params || { bubbles: false, cancelable: false, detail: null };
        var evt = document.createEvent('CustomEvent');
        evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
        return evt;
    }
    CustomEvent.prototype = window.Event.prototype;
    window.CustomEvent = CustomEvent;
}

// https://davidwalsh.name/javascript-debounce-function
function debounce(func, wait, immediate) {
    let timeout;
    return function () {
        let context = this, args = arguments;
        let later = function () {
            timeout = null;
            if (!immediate) func.apply(context, args);
        };
        let callNow = immediate && !timeout;
        clearTimeout(timeout);
        timeout = setTimeout(later, wait);
        if (callNow) func.apply(context, args);
    };
};

window.refreshFilters = debounce(function refreshFilters(pageNumber) {
    var pagination = $("ul.pagination");
    var state = { page: pageNumber ? pageNumber : 1 };
    $.each(window.PRODUCT_LIST_FILTERS, function (idx, key) {
        var filterObj = $("#id_" + key);

        if (!filterObj || filterObj.data("exclude")) {
            return;
        }

        if (filterObj.is("select")) {  // Basic select, checkbox etc...
            state[key] = filterObj.val();
        } else if (filterObj.is("input[type='text']")) {
            state[key] = filterObj.val();
        } else if (filterObj.is("input[type='hidden']")) {
            state[key] = filterObj.val();
        } else if (filterObj.is("input[type='number']")) {
            state[key] = filterObj.val();
        } else if (filterObj.is("input[type='checkbox']")) {
            state[key] = filterObj.prop("checked") ? 1 : 0;
        }
        else if (filterObj.is("div")) {  // Should be filter widget
            var values = [];
            filterObj.find("input:checked").each(function () {
                values.push($(this).val());
            });
            state[key] = values;
        }
    });

    var filterString = getFilterString(state);
    if (pagination.length > 0) {
        // Prevent double clicking when ajax is loading
        if (pagination.prop("disabled")) {
            return false;
        }
        pagination.prop("disabled", true);

        if (typeof (pageNumber) !== "number") {
            pageNumber = parseInt(pageNumber);
            if (isNaN(pageNumber)) {
                return false;
            }
        }
        window.PAGE_NUMBER = pageNumber;
    }

    reloadProducts(filterString);

    if (window.history && window.history.pushState) {
        history.pushState(state, null, filterString);
    }

    window.dispatchEvent(new CustomEvent("Shuup.FiltersRefreshed", {
        detail: {
            state,
            filterString
        }
    }));

    // prevent scroll to page buttons
    return false;
}, 150);

function serializeParams(obj) {
    const str = [];
    for (const param in obj) {
        if (obj.hasOwnProperty(param)) {
            if (Array.isArray(obj[param]) && !obj[param].length) {
                continue;
            }
            str.push(encodeURIComponent(param) + "=" + encodeURIComponent(obj[param]));
        }
    }
    return str.join("&");
}

function getFilterString(state) {
    const filters = {};
    Object.keys(state).filter(key => state[key]).forEach((key) => {
        filters[key] = state[key];
    });
    return "?" + serializeParams(filters);
}

function reloadProducts(filterString) {
    const $cont = $("#ajax_content");
    if ($cont.length === 0) {
        return;
    }
    const $prods = $(".products-wrap");
    const $adminMenu = $("#admin-tools-menu");
    const adminMenuHeight = ($adminMenu.length > 0) ? $adminMenu.height() : 0;
    const top = ($prods.length > 0) ? $prods.offset().top : $cont.offset().top;
    window.scrollTo(0, top - adminMenuHeight);

    // this is to ensure browser back/forward from different domain does a full refresh
    filterString += (filterString === "") ? "?" : "&";
    filterString += "ajax=1";
    $cont.load(location.pathname + filterString);
}

$(function () {
    window.addEventListener("popstate", function (e) {
        const filterString = getFilterString(e.state);
        reloadProducts(filterString);
        window.dispatchEvent(new CustomEvent("Shuup.FiltersRefreshed", {
            detail: {
                state: e.state,
                filterString
            }
        }));
    });

    $.each(window.PRODUCT_LIST_FILTERS, function (idx, key) {
        const $field = $("#id_" + key);

        if ($field.parent(".form-group").hasClass("has-error")) {
            const host_str = '//' + location.host + location.pathname;
            window.location.href = host_str;
        }
        if (!$field.data("no-auto-update")) {
            $field.on("change", function () {
                window.refreshFilters(window.PAGE_NUMBER);
            });
        }
    });
});
<commit_msg>Front: do not try to reload products with invalid state<commit_after>/**
 * This file is part of Shuup.
 *
 * Copyright (c) 2012-2019, Shoop Commerce Ltd. All rights reserved.
 *
 * This source code is licensed under the OSL-3.0 license found in the
 * LICENSE file in the root directory of this source tree.
 */

// CustomEvent polyfill
// https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent#Polyfill
if (typeof window.CustomEvent !== "function") {
    function CustomEvent(event, params) {
        params = params || { bubbles: false, cancelable: false, detail: null };
        var evt = document.createEvent('CustomEvent');
        evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
        return evt;
    }
    CustomEvent.prototype = window.Event.prototype;
    window.CustomEvent = CustomEvent;
}

// https://davidwalsh.name/javascript-debounce-function
function debounce(func, wait, immediate) {
    let timeout;
    return function () {
        let context = this, args = arguments;
        let later = function () {
            timeout = null;
            if (!immediate) func.apply(context, args);
        };
        let callNow = immediate && !timeout;
        clearTimeout(timeout);
        timeout = setTimeout(later, wait);
        if (callNow) func.apply(context, args);
    };
};

window.refreshFilters = debounce(function refreshFilters(pageNumber) {
    var pagination = $("ul.pagination");
    var state = { page: pageNumber ? pageNumber : 1 };
    $.each(window.PRODUCT_LIST_FILTERS, function (idx, key) {
        var filterObj = $("#id_" + key);

        if (!filterObj || filterObj.data("exclude")) {
            return;
        }

        if (filterObj.is("select")) {  // Basic select, checkbox etc...
            state[key] = filterObj.val();
        } else if (filterObj.is("input[type='text']")) {
            state[key] = filterObj.val();
        } else if (filterObj.is("input[type='hidden']")) {
            state[key] = filterObj.val();
        } else if (filterObj.is("input[type='number']")) {
            state[key] = filterObj.val();
        } else if (filterObj.is("input[type='checkbox']")) {
            state[key] = filterObj.prop("checked") ? 1 : 0;
        }
        else if (filterObj.is("div")) {  // Should be filter widget
            var values = [];
            filterObj.find("input:checked").each(function () {
                values.push($(this).val());
            });
            state[key] = values;
        }
    });

    var filterString = getFilterString(state);
    if (pagination.length > 0) {
        // Prevent double clicking when ajax is loading
        if (pagination.prop("disabled")) {
            return false;
        }
        pagination.prop("disabled", true);

        if (typeof (pageNumber) !== "number") {
            pageNumber = parseInt(pageNumber);
            if (isNaN(pageNumber)) {
                return false;
            }
        }
        window.PAGE_NUMBER = pageNumber;
    }

    reloadProducts(filterString);

    if (window.history && window.history.pushState) {
        history.pushState(state, null, filterString);
    }

    window.dispatchEvent(new CustomEvent("Shuup.FiltersRefreshed", {
        detail: {
            state,
            filterString
        }
    }));

    // prevent scroll to page buttons
    return false;
}, 150);

function serializeParams(obj) {
    const str = [];
    for (const param in obj) {
        if (obj.hasOwnProperty(param)) {
            if (Array.isArray(obj[param]) && !obj[param].length) {
                continue;
            }
            str.push(encodeURIComponent(param) + "=" + encodeURIComponent(obj[param]));
        }
    }
    return str.join("&");
}

function getFilterString(state) {
    const filters = {};
    Object.keys(state).filter(key => state[key]).forEach((key) => {
        filters[key] = state[key];
    });
    return "?" + serializeParams(filters);
}

function reloadProducts(filterString) {
    const $cont = $("#ajax_content");
    if ($cont.length === 0) {
        return;
    }
    const $prods = $(".products-wrap");
    const $adminMenu = $("#admin-tools-menu");
    const adminMenuHeight = ($adminMenu.length > 0) ? $adminMenu.height() : 0;
    const top = ($prods.length > 0) ? $prods.offset().top : $cont.offset().top;
    window.scrollTo(0, top - adminMenuHeight);

    // this is to ensure browser back/forward from different domain does a full refresh
    filterString += (filterString === "") ? "?" : "&";
    filterString += "ajax=1";
    $cont.load(location.pathname + filterString);
}

$(function () {
    window.addEventListener("popstate", function (e) {
        if (e.state) {
            const filterString = getFilterString(e.state);
            reloadProducts(filterString);
            window.dispatchEvent(new CustomEvent("Shuup.FiltersRefreshed", {
                detail: {
                    state: e.state,
                    filterString
                }
            }));
        }
    });

    $.each(window.PRODUCT_LIST_FILTERS, function (idx, key) {
        const $field = $("#id_" + key);

        if ($field.parent(".form-group").hasClass("has-error")) {
            const host_str = '//' + location.host + location.pathname;
            window.location.href = host_str;
        }
        if (!$field.data("no-auto-update")) {
            $field.on("change", function () {
                window.refreshFilters(window.PAGE_NUMBER);
            });
        }
    });
});
<|endoftext|>


<commit_before>/**
 * @file Writer
 * @author Alexander Rose <alexander.rose@weirdbyte.de>
 */

///////////////
// PDB Writer

NGL.PdbWriter = function( structure, params ){

    var p = Object.assign( {}, params );

    var renumberSerial = p.renumberSerial !== undefined ? p.renumberSerial : true;
    var remarks = p.remarks || [];
    if( !Array.isArray( remarks ) ) remarks = [ remarks ];

    var records;

    function writeRecords(){

        records = [];

        writeTitle();
        writeRemarks();
        writeAtoms();

    }

    // http://www.wwpdb.org/documentation/file-format

    // Sample PDB line, the coords X,Y,Z are fields 5,6,7 on each line.
    // ATOM      1  N   ARG     1      29.292  13.212 -12.751  1.00 33.78      1BPT 108

    function DEF( x, y ){
        return x !== undefined ? x : y;
    }

    var atomFormat =
        "ATOM  %5d %-4s %3s %1s%4d    %8.3f%8.3f%8.3f%6.2f%6.2f      %4s%2s";

    var hetatmFormat =
        "HETATM%5d %-4s %3s %1s%4d    %8.3f%8.3f%8.3f%6.2f%6.2f      %4s%2s";

    function writeTitle(){

        // FIXME multiline if title line longer than 80 chars
        records.push( sprintf( "TITEL %-74s", structure.name ) );

    }

    function writeRemarks(){

        remarks.forEach( function( str ){
            records.push( sprintf( "REMARK %-73s", str ) );
        } );

        if( structure.trajectory ){
            records.push( sprintf(
                "REMARK %-73s",
                "Trajectory '" + structure.trajectory.name + "'"
            ) );
            records.push( sprintf(
                "REMARK %-73s",
                "Frame " + structure.trajectory.frame + ""
            ) );
        }

    }

    function writeAtoms(){

        var ia = 1;
        var im = 1;

        structure.eachModel( function( m ){

            records.push( sprintf( "MODEL %-74d", im++ ) );

            m.eachAtom( function( a ){

                var formatString = a.hetero ? hetatmFormat : atomFormat;
                var serial = renumberSerial ? ia : a.serial;

                // Alignment of one-letter atom name such as C starts at column 14,
                // while two-letter atom name such as FE starts at column 13.
                var atomname = a.atomname;
                if( atomname.length === 1 ) atomname = " " + atomname;

                records.push( sprintf(
                    formatString,

                    a.serial,
                    atomname,
                    a.resname,
                    DEF( a.chainname, " " ),
                    a.resno,
                    a.x, a.y, a.z,
                    DEF( a.occurence, 1.0 ),
                    DEF( a.bfactor, 0.0 ),
                    DEF( a.segid, "" ),
                    DEF( a.element, "" )
                ) );
                ia += 1;

            } );

            records.push( sprintf( "%-80s", "ENDMDL" ) );
            im += 1;

        } );

        records.push( sprintf( "%-80s", "END" ) );

    }

    function getString(){

        writeRecords();
        return records.join( "\n" );

    }

    function getBlob(){

        return new Blob(
            [ getString() ],
            { type: 'text/plain' }
        );

    }

    function download( name, ext ){

        name = name || "structure"
        ext = ext || "pdb";

        var file = name + "." + ext;
        var blob = getBlob();

        NGL.download( blob, file );

    }

    // API

    this.getString = getString;
    this.getBlob = getBlob;
    this.download = download;

};
<commit_msg>FIX: renumberSerial activated<commit_after>/**
 * @file Writer
 * @author Alexander Rose <alexander.rose@weirdbyte.de>
 */

///////////////
// PDB Writer

NGL.PdbWriter = function( structure, params ){

    var p = Object.assign( {}, params );

    var renumberSerial = p.renumberSerial !== undefined ? p.renumberSerial : true;
    var remarks = p.remarks || [];
    if( !Array.isArray( remarks ) ) remarks = [ remarks ];

    var records;

    function writeRecords(){

        records = [];

        writeTitle();
        writeRemarks();
        writeAtoms();

    }

    // http://www.wwpdb.org/documentation/file-format

    // Sample PDB line, the coords X,Y,Z are fields 5,6,7 on each line.
    // ATOM      1  N   ARG     1      29.292  13.212 -12.751  1.00 33.78      1BPT 108

    function DEF( x, y ){
        return x !== undefined ? x : y;
    }

    var atomFormat =
        "ATOM  %5d %-4s %3s %1s%4d    %8.3f%8.3f%8.3f%6.2f%6.2f      %4s%2s";

    var hetatmFormat =
        "HETATM%5d %-4s %3s %1s%4d    %8.3f%8.3f%8.3f%6.2f%6.2f      %4s%2s";

    function writeTitle(){

        // FIXME multiline if title line longer than 80 chars
        records.push( sprintf( "TITEL %-74s", structure.name ) );

    }

    function writeRemarks(){

        remarks.forEach( function( str ){
            records.push( sprintf( "REMARK %-73s", str ) );
        } );

        if( structure.trajectory ){
            records.push( sprintf(
                "REMARK %-73s",
                "Trajectory '" + structure.trajectory.name + "'"
            ) );
            records.push( sprintf(
                "REMARK %-73s",
                "Frame " + structure.trajectory.frame + ""
            ) );
        }

    }

    function writeAtoms(){

        var ia = 1;
        var im = 1;

        structure.eachModel( function( m ){

            records.push( sprintf( "MODEL %-74d", im++ ) );

            m.eachAtom( function( a ){

                var formatString = a.hetero ? hetatmFormat : atomFormat;
                var serial = renumberSerial ? ia : a.serial;

                // Alignment of one-letter atom name such as C starts at column 14,
                // while two-letter atom name such as FE starts at column 13.
                var atomname = a.atomname;
                if( atomname.length === 1 ) atomname = " " + atomname;

                records.push( sprintf(
                    formatString,

                    serial,
                    atomname,
                    a.resname,
                    DEF( a.chainname, " " ),
                    a.resno,
                    a.x, a.y, a.z,
                    DEF( a.occurence, 1.0 ),
                    DEF( a.bfactor, 0.0 ),
                    DEF( a.segid, "" ),
                    DEF( a.element, "" )
                ) );
                ia += 1;

            } );

            records.push( sprintf( "%-80s", "ENDMDL" ) );
            im += 1;

        } );

        records.push( sprintf( "%-80s", "END" ) );

    }

    function getString(){

        writeRecords();
        return records.join( "\n" );

    }

    function getBlob(){

        return new Blob(
            [ getString() ],
            { type: 'text/plain' }
        );

    }

    function download( name, ext ){

        name = name || "structure"
        ext = ext || "pdb";

        var file = name + "." + ext;
        var blob = getBlob();

        NGL.download( blob, file );

    }

    // API

    this.getString = getString;
    this.getBlob = getBlob;
    this.download = download;

};
<|endoftext|>


<commit_before>import Renderer from './Renderer';
import {gradientRef, isGradient, patternPrefix} from './Gradient';
import marks from './marks/index';
import {ariaItemAttributes, ariaMarkAttributes} from './util/aria';
import {cssClass} from './util/dom';
import {closeTag, openTag} from './util/tags';
import {fontFamily, fontSize, lineHeight, textLines, textValue} from './util/text';
import {visit} from './util/visit';
import clip from './util/svg/clip';
import metadata from './util/svg/metadata';
import {rootAttributes, styles} from './util/svg/styles';
import {extend, inherits, isArray} from 'vega-util';

export default function SVGStringRenderer(loader) {
  Renderer.call(this, loader);

  this._text = {
    head: '',
    bg:   '',
    root: '',
    foot: '',
    defs: '',
    body: ''
  };

  this._defs = {
    gradient: {},
    clipping: {}
  };
}

var prototype = inherits(SVGStringRenderer, Renderer);
var base = Renderer.prototype;

prototype.resize = function(width, height, origin, scaleFactor) {
  base.resize.call(this, width, height, origin, scaleFactor);
  var o = this._origin,
      t = this._text;

  var attr = {
    class:   'marks',
    width:   this._width * this._scale,
    height:  this._height * this._scale,
    viewBox: '0 0 ' + this._width + ' ' + this._height
  };
  for (var key in metadata) {
    attr[key] = metadata[key];
  }

  t.head = openTag('svg', attr);

  var bg = this._bgcolor;
  if (bg === 'transparent' || bg === 'none') bg = null;

  if (bg) {
    t.bg = openTag('rect', {
      width:  this._width,
      height: this._height,
      fill:   bg
    }) + closeTag('rect');
  } else {
    t.bg = '';
  }

  t.root = openTag('g', extend(
    {}, rootAttributes, {transform: 'translate(' + o + ')'}
  ));

  t.foot = closeTag('g') + closeTag('svg');

  return this;
};

prototype.background = function() {
  var rv = base.background.apply(this, arguments);
  if (arguments.length && this._text.head) {
    this.resize(this._width, this._height, this._origin, this._scale);
  }
  return rv;
};

prototype.svg = function() {
  var t = this._text;
  return t.head + t.defs + t.bg + t.root + t.body + t.foot;
};

prototype._render = function(scene) {
  this._text.body = this.mark(scene);
  this._text.defs = this.buildDefs();
  return this;
};

prototype.buildDefs = function() {
  let defs = '', tag;

  for (const id in this._defs.gradient) {
    const def = this._defs.gradient[id],
          stops = def.stops;

    if (def.gradient === 'radial') {
      // SVG radial gradients automatically transform to normalized bbox
      // coordinates, in a way that is cumbersome to replicate in canvas.
      // We wrap the radial gradient in a pattern element, allowing us to
      // maintain a circular gradient that matches what canvas provides.

      defs += openTag(tag = 'pattern', {
        id: patternPrefix + id,
        viewBox: '0,0,1,1',
        width: '100%',
        height: '100%',
        preserveAspectRatio: 'xMidYMid slice'
      });

      defs += openTag('rect', {
        width:  '1',
        height: '1',
        fill:   'url(#' + id + ')'
      }) + closeTag('rect');

      defs += closeTag(tag);

      defs += openTag(tag = 'radialGradient', {
        id: id,
        fx: def.x1,
        fy: def.y1,
        fr: def.r1,
        cx: def.x2,
        cy: def.y2,
         r: def.r2
      });
    } else {
      defs += openTag(tag = 'linearGradient', {
        id: id,
        x1: def.x1,
        x2: def.x2,
        y1: def.y1,
        y2: def.y2
      });
    }

    for (let i = 0; i < stops.length; ++i) {
      defs += openTag('stop', {
        offset: stops[i].offset,
        'stop-color': stops[i].color
      }) + closeTag('stop');
    }

    defs += closeTag(tag);
  }

  for (const id in this._defs.clipping) {
    const def = this._defs.clipping[id];

    defs += openTag('clipPath', {id: id});

    if (def.path) {
      defs += openTag('path', {
        d: def.path
      }) + closeTag('path');
    } else {
      defs += openTag('rect', {
        x: 0,
        y: 0,
        width: def.width,
        height: def.height
      }) + closeTag('rect');
    }

    defs += closeTag('clipPath');
  }

  return defs ? (openTag('defs') + defs + closeTag('defs')) : '';
};

prototype.attr = function(scene, item, attrs, tag) {
  const object = {},
        emit = (name, value, ns, prefixed) => {
          object[prefixed || name] = value;
        };

  // apply mark specific attributes
  if (Array.isArray(attrs)) {
    attrs.forEach(fn => fn(emit, item, this));
  } else {
    attrs(emit, item, this);
  }

  // apply style attributes
  if (tag) {
    applyStyles(object, item, scene, tag, this._defs);
  }

  return object;
};

prototype.href = function(item) {
  var that = this,
      href = item.href,
      attr;

  if (href) {
    if (attr = that._hrefs && that._hrefs[href]) {
      return attr;
    } else {
      that.sanitizeURL(href).then(attr => {
        // rewrite to use xlink namespace
        // note that this will be deprecated in SVG 2.0
        attr['xlink:href'] = attr.href;
        attr.href = null;
        (that._hrefs || (that._hrefs = {}))[href] = attr;
      });
    }
  }
  return null;
};

prototype.mark = function(scene) {
  const mdef = marks[scene.marktype],
        tag  = mdef.tag,
        attrList = [ariaItemAttributes, mdef.attr];

  let str = '';

  // render opening group tag
  str += openTag('g', extend(
    {
      'class': cssClass(scene),
      'clip-path': scene.clip ? clip(this, scene, scene.group) : null
    },
    ariaMarkAttributes(scene),
    {
      'pointer-events': tag !== 'g' && scene.interactive === false ? 'none' : null
    }
  ));

  // render contained elements
  const process = item => {
    const href = this.href(item);
    if (href) str += openTag('a', href);

    str += openTag(
      tag,
      this.attr(scene, item, attrList, tag !== 'g' ? tag : null)
    );

    if (tag === 'text') {
      const tl = textLines(item);
      if (isArray(tl)) {
        // multi-line text
        const attrs = {x: 0, dy: lineHeight(item)};
        for (let i=0; i<tl.length; ++i) {
          str += openTag('tspan', i ? attrs: null)
            + escape_text(textValue(item, tl[i]))
            + closeTag('tspan');
        }
      } else {
        // single-line text
        str += escape_text(textValue(item, tl));
      }
    } else if (tag === 'g') {
      const fore = item.strokeForeground,
            fill = item.fill,
            stroke = item.stroke;

      if (fore && stroke) {
        item.stroke = null;
      }

      str += openTag(
        'path',
        this.attr(scene, item, mdef.background, 'bgrect'),
      ) + closeTag('path');

      str += openTag('g', this.attr(scene, item, mdef.content))
        + this.markGroup(item)
        + closeTag('g');

      if (fore && stroke) {
        if (fill) item.fill = null;
        item.stroke = stroke;

        str += openTag(
          'path',
          this.attr(scene, item, mdef.foreground, 'bgrect')
        ) + closeTag('path');

        if (fill) item.fill = fill;
      } else {
        str += openTag(
          'path',
          this.attr(scene, item, mdef.foreground, 'bgfore')
        ) + closeTag('path');
      }
    }

    str += closeTag(tag);
    if (href) str += closeTag('a');
  };

  if (mdef.nested) {
    if (scene.items && scene.items.length) process(scene.items[0]);
  } else {
    visit(scene, process);
  }

  // render closing group tag
  return str + closeTag('g');
};

prototype.markGroup = function(scene) {
  let str = '';
  visit(scene, item => { str += this.mark(item); });
  return str;
};

function applyStyles(s, item, scene, tag, defs) {
  if (item == null) return s;

  if (tag === 'bgrect' && scene.interactive === false) {
    s['pointer-events'] = 'none';
  }

  if (tag === 'bgfore') {
    if (scene.interactive === false) {
      s['pointer-events'] = 'none';
    }
    s.display = 'none';
    if (item.fill !== null) return s;
  }

  if (tag === 'image' && item.smooth === false) {
    s.style = 'image-rendering: optimizeSpeed; image-rendering: pixelated;';
  }

  if (tag === 'text') {
    s['font-family'] = fontFamily(item);
    s['font-size'] = fontSize(item) + 'px';
    if (item.fontStyle) s['font-style'] = item.fontStyle;
    if (item.fontVariant) s['font-variant'] = item.fontVariant;
    if (item.fontWeight) s['font-weight'] = item.fontWeight;
  }

  for (const prop in styles) {
    let value = item[prop];
    const name = styles[prop];

    if (value === 'transparent' && (name === 'fill' || name === 'stroke')) {
      // transparent is not a legal SVG value
      // we can skip it to rely on default 'none' instead
    } else if (value != null) {
      if (isGradient(value)) {
        value = gradientRef(value, defs.gradient, '');
      }
      s[name] = value;
    }
  }

  return s;
}

function escape_text(s) {
  return s.replace(/&/g, '&')
          .replace(/</g, '<')
          .replace(/>/g, '>');
}
<commit_msg>fix: Remove trialing argument comma.<commit_after>import Renderer from './Renderer';
import {gradientRef, isGradient, patternPrefix} from './Gradient';
import marks from './marks/index';
import {ariaItemAttributes, ariaMarkAttributes} from './util/aria';
import {cssClass} from './util/dom';
import {closeTag, openTag} from './util/tags';
import {fontFamily, fontSize, lineHeight, textLines, textValue} from './util/text';
import {visit} from './util/visit';
import clip from './util/svg/clip';
import metadata from './util/svg/metadata';
import {rootAttributes, styles} from './util/svg/styles';
import {extend, inherits, isArray} from 'vega-util';

export default function SVGStringRenderer(loader) {
  Renderer.call(this, loader);

  this._text = {
    head: '',
    bg:   '',
    root: '',
    foot: '',
    defs: '',
    body: ''
  };

  this._defs = {
    gradient: {},
    clipping: {}
  };
}

var prototype = inherits(SVGStringRenderer, Renderer);
var base = Renderer.prototype;

prototype.resize = function(width, height, origin, scaleFactor) {
  base.resize.call(this, width, height, origin, scaleFactor);
  var o = this._origin,
      t = this._text;

  var attr = {
    class:   'marks',
    width:   this._width * this._scale,
    height:  this._height * this._scale,
    viewBox: '0 0 ' + this._width + ' ' + this._height
  };
  for (var key in metadata) {
    attr[key] = metadata[key];
  }

  t.head = openTag('svg', attr);

  var bg = this._bgcolor;
  if (bg === 'transparent' || bg === 'none') bg = null;

  if (bg) {
    t.bg = openTag('rect', {
      width:  this._width,
      height: this._height,
      fill:   bg
    }) + closeTag('rect');
  } else {
    t.bg = '';
  }

  t.root = openTag('g', extend(
    {}, rootAttributes, {transform: 'translate(' + o + ')'}
  ));

  t.foot = closeTag('g') + closeTag('svg');

  return this;
};

prototype.background = function() {
  var rv = base.background.apply(this, arguments);
  if (arguments.length && this._text.head) {
    this.resize(this._width, this._height, this._origin, this._scale);
  }
  return rv;
};

prototype.svg = function() {
  var t = this._text;
  return t.head + t.defs + t.bg + t.root + t.body + t.foot;
};

prototype._render = function(scene) {
  this._text.body = this.mark(scene);
  this._text.defs = this.buildDefs();
  return this;
};

prototype.buildDefs = function() {
  let defs = '', tag;

  for (const id in this._defs.gradient) {
    const def = this._defs.gradient[id],
          stops = def.stops;

    if (def.gradient === 'radial') {
      // SVG radial gradients automatically transform to normalized bbox
      // coordinates, in a way that is cumbersome to replicate in canvas.
      // We wrap the radial gradient in a pattern element, allowing us to
      // maintain a circular gradient that matches what canvas provides.

      defs += openTag(tag = 'pattern', {
        id: patternPrefix + id,
        viewBox: '0,0,1,1',
        width: '100%',
        height: '100%',
        preserveAspectRatio: 'xMidYMid slice'
      });

      defs += openTag('rect', {
        width:  '1',
        height: '1',
        fill:   'url(#' + id + ')'
      }) + closeTag('rect');

      defs += closeTag(tag);

      defs += openTag(tag = 'radialGradient', {
        id: id,
        fx: def.x1,
        fy: def.y1,
        fr: def.r1,
        cx: def.x2,
        cy: def.y2,
         r: def.r2
      });
    } else {
      defs += openTag(tag = 'linearGradient', {
        id: id,
        x1: def.x1,
        x2: def.x2,
        y1: def.y1,
        y2: def.y2
      });
    }

    for (let i = 0; i < stops.length; ++i) {
      defs += openTag('stop', {
        offset: stops[i].offset,
        'stop-color': stops[i].color
      }) + closeTag('stop');
    }

    defs += closeTag(tag);
  }

  for (const id in this._defs.clipping) {
    const def = this._defs.clipping[id];

    defs += openTag('clipPath', {id: id});

    if (def.path) {
      defs += openTag('path', {
        d: def.path
      }) + closeTag('path');
    } else {
      defs += openTag('rect', {
        x: 0,
        y: 0,
        width: def.width,
        height: def.height
      }) + closeTag('rect');
    }

    defs += closeTag('clipPath');
  }

  return defs ? (openTag('defs') + defs + closeTag('defs')) : '';
};

prototype.attr = function(scene, item, attrs, tag) {
  const object = {},
        emit = (name, value, ns, prefixed) => {
          object[prefixed || name] = value;
        };

  // apply mark specific attributes
  if (Array.isArray(attrs)) {
    attrs.forEach(fn => fn(emit, item, this));
  } else {
    attrs(emit, item, this);
  }

  // apply style attributes
  if (tag) {
    applyStyles(object, item, scene, tag, this._defs);
  }

  return object;
};

prototype.href = function(item) {
  var that = this,
      href = item.href,
      attr;

  if (href) {
    if (attr = that._hrefs && that._hrefs[href]) {
      return attr;
    } else {
      that.sanitizeURL(href).then(attr => {
        // rewrite to use xlink namespace
        // note that this will be deprecated in SVG 2.0
        attr['xlink:href'] = attr.href;
        attr.href = null;
        (that._hrefs || (that._hrefs = {}))[href] = attr;
      });
    }
  }
  return null;
};

prototype.mark = function(scene) {
  const mdef = marks[scene.marktype],
        tag  = mdef.tag,
        attrList = [ariaItemAttributes, mdef.attr];

  let str = '';

  // render opening group tag
  str += openTag('g', extend(
    {
      'class': cssClass(scene),
      'clip-path': scene.clip ? clip(this, scene, scene.group) : null
    },
    ariaMarkAttributes(scene),
    {
      'pointer-events': tag !== 'g' && scene.interactive === false ? 'none' : null
    }
  ));

  // render contained elements
  const process = item => {
    const href = this.href(item);
    if (href) str += openTag('a', href);

    str += openTag(
      tag,
      this.attr(scene, item, attrList, tag !== 'g' ? tag : null)
    );

    if (tag === 'text') {
      const tl = textLines(item);
      if (isArray(tl)) {
        // multi-line text
        const attrs = {x: 0, dy: lineHeight(item)};
        for (let i=0; i<tl.length; ++i) {
          str += openTag('tspan', i ? attrs: null)
            + escape_text(textValue(item, tl[i]))
            + closeTag('tspan');
        }
      } else {
        // single-line text
        str += escape_text(textValue(item, tl));
      }
    } else if (tag === 'g') {
      const fore = item.strokeForeground,
            fill = item.fill,
            stroke = item.stroke;

      if (fore && stroke) {
        item.stroke = null;
      }

      str += openTag(
        'path',
        this.attr(scene, item, mdef.background, 'bgrect')
      ) + closeTag('path');

      str += openTag('g', this.attr(scene, item, mdef.content))
        + this.markGroup(item)
        + closeTag('g');

      if (fore && stroke) {
        if (fill) item.fill = null;
        item.stroke = stroke;

        str += openTag(
          'path',
          this.attr(scene, item, mdef.foreground, 'bgrect')
        ) + closeTag('path');

        if (fill) item.fill = fill;
      } else {
        str += openTag(
          'path',
          this.attr(scene, item, mdef.foreground, 'bgfore')
        ) + closeTag('path');
      }
    }

    str += closeTag(tag);
    if (href) str += closeTag('a');
  };

  if (mdef.nested) {
    if (scene.items && scene.items.length) process(scene.items[0]);
  } else {
    visit(scene, process);
  }

  // render closing group tag
  return str + closeTag('g');
};

prototype.markGroup = function(scene) {
  let str = '';
  visit(scene, item => { str += this.mark(item); });
  return str;
};

function applyStyles(s, item, scene, tag, defs) {
  if (item == null) return s;

  if (tag === 'bgrect' && scene.interactive === false) {
    s['pointer-events'] = 'none';
  }

  if (tag === 'bgfore') {
    if (scene.interactive === false) {
      s['pointer-events'] = 'none';
    }
    s.display = 'none';
    if (item.fill !== null) return s;
  }

  if (tag === 'image' && item.smooth === false) {
    s.style = 'image-rendering: optimizeSpeed; image-rendering: pixelated;';
  }

  if (tag === 'text') {
    s['font-family'] = fontFamily(item);
    s['font-size'] = fontSize(item) + 'px';
    if (item.fontStyle) s['font-style'] = item.fontStyle;
    if (item.fontVariant) s['font-variant'] = item.fontVariant;
    if (item.fontWeight) s['font-weight'] = item.fontWeight;
  }

  for (const prop in styles) {
    let value = item[prop];
    const name = styles[prop];

    if (value === 'transparent' && (name === 'fill' || name === 'stroke')) {
      // transparent is not a legal SVG value
      // we can skip it to rely on default 'none' instead
    } else if (value != null) {
      if (isGradient(value)) {
        value = gradientRef(value, defs.gradient, '');
      }
      s[name] = value;
    }
  }

  return s;
}

function escape_text(s) {
  return s.replace(/&/g, '&')
          .replace(/</g, '<')
          .replace(/>/g, '>');
}
<|endoftext|>


<commit_before>'use strict';

// Libs
let pathToRegexp = require('path-to-regexp');

// Classes
let Controller = require('./Controller');

/**
 * The main API controller
 */
class ApiController extends Controller {
    /**
     * Authenticates an API call
     *
     * @param {String} token
     * @param {String} project
     * @param {String} scope
     * @param {Boolean} needsAdmin
     *
     * @returns {Promise} User object
     */
    static authenticate(token, project, scope, needsAdmin) {
        if(!token) {
            return Promise.reject(new Error('No token was provided'));
        }

        return UserHelper.findToken(token)
        .then((user) => {
            // If no user was found, return error
            if(!user) {
                return Promise.reject(new Error('Found no user with token "' + token + '"'));
            }

            // If admin is required, and user isn't admin, return error
            if(needsAdmin && !user.isAdmin) {
                return Promise.reject(new Error('User "' + user.username + '" is not an admin'))
            }

            // If a scope is defined, and the user deosn't have it, return error
            if(project && scope && !user.hasScope(project, scope)) {
                return Promise.reject(new Error('User "' + user.username + '" does not have scope "' + scope + '"'));
            }

            // If all requirements have been met, resolve
            return Promise.resolve(user);
        });
    }

    /**
     * Sets project variables
     *
     * @param {Object} req
     */
    static setProjectVariables(req) {
        let keys = [];
        let re = pathToRegexp('/:root/:project/:environment/*', keys);
        let values = re.exec(req.originalUrl);
        let project = null;
        let environment = null;

        if(values) {
            // The first array item is the entire url, so remove it
            values.shift();

            for(let i in keys) {
                let key = keys[i];

                switch(key.name) {
                    case 'project':
                        req.project = values[i];
                        break;

                    case 'environment':
                        req.environment = values[i];
                        break;
                }
            }
        }

        // Environment sanity check
        if(req.environment == 'settings') {
            req.environment = null;
        }

        // Check if project and environment exist
        return ProjectHelper.projectExists(req.project)
        .then((projectExists) => {
            if(!projectExists) {
                return Promise.reject('Project "' + req.project + '" could not be found');
            }

            if(req.environment) {
                return ProjectHelper.environmentExists(req.project, req.environment)
                .then((environmentExists) => {
                    if(!environmentExists) {
                        return Promise.reject(new Error('Environment "' + req.environment + '" was not found for project "' + req.project + '"'));
                    }

                    return Promise.resolve();
                });

            } else {
                return Promise.resolve();

            }
        });
    }

    /**
     * Check CORS
     *
     * @param {Object} settings
     * @param {Request} req
     * @param {Response} res
     *
     * @returns {Promise} Result
     */
    static checkCORS(settings, req, res) {
        function getPromise() {
            // If a string was specified, use it directly
            if(typeof settings.allowCORS === 'string') {
                return Promise.resolve(settings.allowCORS);
            }

            // a boolean value of true was specified, allow all origins
            if(settings.allowCORS == true) {
                return Promise.resolve('*');
            }

            // If a function value was specified, run it
            if(typeof settings.allowCORS === 'function') {
                return Promise.resolve(settings.allowCORS(req, res));

            }

            // Nothing was specified, move on
            return Promise.resolve();
        };

        return getPromise()
        .then((allowedOrigin) => {
            if(allowedOrigin) {
                res.header('Access-Control-Allow-Origin', allowedOrigin);
                res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');

                // Allowed origin did not match
                if(allowedOrigin != '*' && allowedOrigin != req.headers.origin) {
                    return Promise.reject(new Error('Unauthorized'));
                }
            }

            return Promise.resolve();
        });
    }

    /**
     * Middleware
     *
     * @param {Object} settings
     */
    static middleware(settings) {
        settings = settings || {};

        return function middleware(req, res, next) {
            let token = req.cookies.token || req.query.token;

            // Make sure to clear double cookie values, if they occur
            if(!req.cookies.token) {
                res.clearCookie('token');
            }

            // Check CORS settings first
            ApiController.checkCORS(settings, req, res)
            .then(() => {
                // Using project parameter
                if(settings.setProject != false) {
                    // Set the project variables
                    return ApiController.setProjectVariables(req)
                    .then(() => {
                        // Using authentication
                        if(settings.authenticate != false) {
                            return ApiController.authenticate(token, req.project, settings.scope, settings.needsAdmin);

                        // No authentication needed
                        } else {
                            return Promise.resolve();
                        }
                    });

                // Disregarding project parameter, but using authentication
                } else if(settings.authenticate != false) {
                    return ApiController.authenticate(req.cookies.token, null, settings.scope, settings.needsAdmin);

                // Neither project parameter nor authentication needed
                } else {
                    return Promise.resolve();

                }
            })
            .then((user) => {
                req.user = user;

                next();
            })
            .catch((e) => {
                res.status(400).send(ApiController.printError(e));
            });
        }
    }

    /**
     * Prints a formatted error and logs it
     *
     * @param {Error} error
     * @param {Boolean} printToLog
     *
     * @returns {String} Pretty print for the error message
     */
     static printError(error, printToLog = true) {
        if(!error) {
            return 'Unspecified error';
        }

        let fullErrorString = '';
        let shortErrorString = '';

        if(error instanceof Error) {
            shortErrorString = error.message || '';
            fullErrorString = shortErrorString;

            if(error.stack) {
                fullErrorString = error.stack;
            }

            if(!fullErrorString) {
                fullErrorString = error.toString();
            }

        } else if(typeof error !== 'object') {
            shortErrorString = error.toString();
            fullErrorString = shortErrorString;

        }

        if(printToLog) {
            debug.log('ERROR: ' + fullErrorString, this);
        }

        return shortErrorString;
     }
}

module.exports = ApiController;
<commit_msg>Fixed token logic in ApiController<commit_after>'use strict';

// Libs
let pathToRegexp = require('path-to-regexp');

// Classes
let Controller = require('./Controller');

/**
 * The main API controller
 */
class ApiController extends Controller {
    /**
     * Authenticates an API call
     *
     * @param {String} token
     * @param {String} project
     * @param {String} scope
     * @param {Boolean} needsAdmin
     *
     * @returns {Promise} User object
     */
    static authenticate(token, project, scope, needsAdmin) {
        if(!token) {
            return Promise.reject(new Error('No token was provided'));
        }

        return UserHelper.findToken(token)
        .then((user) => {
            // If no user was found, return error
            if(!user) {
                return Promise.reject(new Error('Found no user with token "' + token + '"'));
            }

            // If admin is required, and user isn't admin, return error
            if(needsAdmin && !user.isAdmin) {
                return Promise.reject(new Error('User "' + user.username + '" is not an admin'))
            }

            // If a scope is defined, and the user deosn't have it, return error
            if(project && scope && !user.hasScope(project, scope)) {
                return Promise.reject(new Error('User "' + user.username + '" does not have scope "' + scope + '"'));
            }

            // If all requirements have been met, resolve
            return Promise.resolve(user);
        });
    }

    /**
     * Sets project variables
     *
     * @param {Object} req
     */
    static setProjectVariables(req) {
        let keys = [];
        let re = pathToRegexp('/:root/:project/:environment/*', keys);
        let values = re.exec(req.originalUrl);
        let project = null;
        let environment = null;

        if(values) {
            // The first array item is the entire url, so remove it
            values.shift();

            for(let i in keys) {
                let key = keys[i];

                switch(key.name) {
                    case 'project':
                        req.project = values[i];
                        break;

                    case 'environment':
                        req.environment = values[i];
                        break;
                }
            }
        }

        // Environment sanity check
        if(req.environment == 'settings') {
            req.environment = null;
        }

        // Check if project and environment exist
        return ProjectHelper.projectExists(req.project)
        .then((projectExists) => {
            if(!projectExists) {
                return Promise.reject('Project "' + req.project + '" could not be found');
            }

            if(req.environment) {
                return ProjectHelper.environmentExists(req.project, req.environment)
                .then((environmentExists) => {
                    if(!environmentExists) {
                        return Promise.reject(new Error('Environment "' + req.environment + '" was not found for project "' + req.project + '"'));
                    }

                    return Promise.resolve();
                });

            } else {
                return Promise.resolve();

            }
        });
    }

    /**
     * Check CORS
     *
     * @param {Object} settings
     * @param {Request} req
     * @param {Response} res
     *
     * @returns {Promise} Result
     */
    static checkCORS(settings, req, res) {
        function getPromise() {
            // If a string was specified, use it directly
            if(typeof settings.allowCORS === 'string') {
                return Promise.resolve(settings.allowCORS);
            }

            // a boolean value of true was specified, allow all origins
            if(settings.allowCORS == true) {
                return Promise.resolve('*');
            }

            // If a function value was specified, run it
            if(typeof settings.allowCORS === 'function') {
                return Promise.resolve(settings.allowCORS(req, res));

            }

            // Nothing was specified, move on
            return Promise.resolve();
        };

        return getPromise()
        .then((allowedOrigin) => {
            if(allowedOrigin) {
                res.header('Access-Control-Allow-Origin', allowedOrigin);
                res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');

                // Allowed origin did not match
                if(allowedOrigin != '*' && allowedOrigin != req.headers.origin) {
                    return Promise.reject(new Error('Unauthorized'));
                }
            }

            return Promise.resolve();
        });
    }

    /**
     * Middleware
     *
     * @param {Object} settings
     */
    static middleware(settings) {
        settings = settings || {};

        return function middleware(req, res, next) {
            let token = req.cookies.token || req.query.token;

            // Make sure to clear double cookie values, if they occur
            if(!req.cookies.token) {
                res.clearCookie('token');
            }

            // Check CORS settings first
            ApiController.checkCORS(settings, req, res)
            .then(() => {
                // Using project parameter
                if(settings.setProject != false) {
                    // Set the project variables
                    return ApiController.setProjectVariables(req)
                    .then(() => {
                        // Using authentication
                        if(settings.authenticate != false) {
                            return ApiController.authenticate(token, req.project, settings.scope, settings.needsAdmin);

                        // No authentication needed
                        } else {
                            return Promise.resolve();
                        }
                    });

                // Disregarding project parameter, but using authentication
                } else if(settings.authenticate != false) {
                    return ApiController.authenticate(token, null, settings.scope, settings.needsAdmin);

                // Neither project parameter nor authentication needed
                } else {
                    return Promise.resolve();

                }
            })
            .then((user) => {
                req.user = user;

                next();
            })
            .catch((e) => {
                res.status(400).send(ApiController.printError(e));
            });
        }
    }

    /**
     * Prints a formatted error and logs it
     *
     * @param {Error} error
     * @param {Boolean} printToLog
     *
     * @returns {String} Pretty print for the error message
     */
     static printError(error, printToLog = true) {
        if(!error) {
            return 'Unspecified error';
        }

        let fullErrorString = '';
        let shortErrorString = '';

        if(error instanceof Error) {
            shortErrorString = error.message || '';
            fullErrorString = shortErrorString;

            if(error.stack) {
                fullErrorString = error.stack;
            }

            if(!fullErrorString) {
                fullErrorString = error.toString();
            }

        } else if(typeof error !== 'object') {
            shortErrorString = error.toString();
            fullErrorString = shortErrorString;

        }

        if(printToLog) {
            debug.log('ERROR: ' + fullErrorString, this);
        }

        return shortErrorString;
     }
}

module.exports = ApiController;
<|endoftext|>


<commit_before>'use strict'
module.exports = ficToEpub
var Streampub = require('streampub')
var chapterFilename = require('./chapter-filename.js')
var sanitizeHtml = require('sanitize-html')
var ms = require('mississippi')
var url = require('url')
var fs = require('fs')
var commaNumber = require('comma-number')
var html = require('html-template-tag')

function ficToEpub (meta) {
  var epub = new Streampub({
    title: meta.title,
    author: meta.author,
    authorUrl: meta.authorUrl,
    description: meta.description,
    source: meta.link,
    subject: meta.tags && meta.tags.length && meta.tags.join(','),
    publisher: meta.publisher,
    published: meta.started || meta.created,
    modified: meta.modified
  })

  if (meta.cover) {
    epub.write(Streampub.newCoverImage(fs.createReadStream(meta.cover)))
  } else {
    let titleContent = ''
    titleContent += html`

<title>${meta.title}</title>
`
    titleContent += html`<h1>${meta.title}</h1>`
    const author = meta.authorUrl ? html`<a href="${meta.authorUrl}">${meta.author}</a>` : meta.author
    titleContent += `<h3>by ${author}</h3>`
    titleContent += html`<table>`
    titleContent += html`<tr><th>Source</th><td><a href="${meta.link}">${meta.link}</a></td></tr>`
    if (meta.created) titleContent += html`<tr><th>Published</th><td>${meta.created}</td></tr>`
    if (meta.modified) titleContent += html`<tr><th>Updated</th><td>${meta.modified}</td></tr>`
    if (meta.tags && meta.tags.length) titleContent += html`<tr><th>Tags</th><td><em>${meta.tags.join(', ')}</em></td></tr>`
    if (meta.words) titleContent += html`<tr><th>Words</th><td>${commaNumber(meta.words)}</td></tr>`
    titleContent += `</table>`
    if (meta.description) titleContent += `<div>${meta.description}</div>`
    var titlePage = `${titleContent}`
    epub.write(Streampub.newChapter('Title Page', titlePage, 0, 'top.xhtml'))
  }
  return ms.pipeline.obj(ms.through.obj(transformChapter(meta)), epub)
}

function andMatches (pattern) {
  return function (item) { return pattern.test(item) }
}

function transformChapter (meta) {
  return function (chapter, _, done) {
    if (chapter.image) {
      this.push(Streampub.newFile(chapter.filename, chapter.content))
      return done()
    }
    var index = chapter.order != null && (1 + chapter.order)
    var name = chapter.name || chapter.order && "Chapter " + index
    var filename = chapterFilename(chapter)
    var content = sanitizeHtml(
      (name ? '<title>' + name.replace(/&/g,'&').replace(/</g, '<') + '</title>' : '') +
      '
' + chapter.content + '
', meta.site.sanitizeHtmlConfig())
    this.push(Streampub.newChapter(name, content, index, filename))
    done()
  }
}
<commit_msg>fic-to-epub: Use new html tagged template for content HTML generation & escaping<commit_after>'use strict'
module.exports = ficToEpub
var Streampub = require('streampub')
var chapterFilename = require('./chapter-filename.js')
var sanitizeHtml = require('sanitize-html')
var ms = require('mississippi')
var url = require('url')
var fs = require('fs')
var commaNumber = require('comma-number')
var html = require('html-template-tag')

function ficToEpub (meta) {
  var epub = new Streampub({
    title: meta.title,
    author: meta.author,
    authorUrl: meta.authorUrl,
    description: meta.description,
    source: meta.link,
    subject: meta.tags && meta.tags.length && meta.tags.join(','),
    publisher: meta.publisher,
    published: meta.started || meta.created,
    modified: meta.modified
  })

  if (meta.cover) {
    epub.write(Streampub.newCoverImage(fs.createReadStream(meta.cover)))
  } else {
    let titleContent = ''
    titleContent += html`

<title>${meta.title}</title>
`
    titleContent += html`<h1>${meta.title}</h1>`
    const author = meta.authorUrl ? html`<a href="${meta.authorUrl}">${meta.author}</a>` : meta.author
    titleContent += `<h3>by ${author}</h3>`
    titleContent += html`<table>`
    titleContent += html`<tr><th>Source</th><td><a href="${meta.link}">${meta.link}</a></td></tr>`
    if (meta.created) titleContent += html`<tr><th>Published</th><td>${meta.created}</td></tr>`
    if (meta.modified) titleContent += html`<tr><th>Updated</th><td>${meta.modified}</td></tr>`
    if (meta.tags && meta.tags.length) titleContent += html`<tr><th>Tags</th><td><em>${meta.tags.join(', ')}</em></td></tr>`
    if (meta.words) titleContent += html`<tr><th>Words</th><td>${commaNumber(meta.words)}</td></tr>`
    titleContent += `</table>`
    if (meta.description) titleContent += `<div>${meta.description}</div>`
    var titlePage = `${titleContent}`
    epub.write(Streampub.newChapter('Title Page', titlePage, 0, 'top.xhtml'))
  }
  return ms.pipeline.obj(ms.through.obj(transformChapter(meta)), epub)
}

function andMatches (pattern) {
  return function (item) { return pattern.test(item) }
}

function transformChapter (meta) {
  return function (chapter, _, done) {
    if (chapter.image) {
      this.push(Streampub.newFile(chapter.filename, chapter.content))
      return done()
    }
    var index = chapter.order != null && (1 + chapter.order)
    var name = chapter.name || chapter.order && "Chapter " + index
    var filename = chapterFilename(chapter)
    var toSanitize = (name ? html`<title>${name}</title></head>` : '') +
      '
' + chapter.content + '
'
    var content = sanitizeHtml(toSanitize, meta.site.sanitizeHtmlConfig())
    this.push(Streampub.newChapter(name, content, index, filename))
    done()
  }
}
<|endoftext|>


<commit_before>/**
 * core/user Data store: Authentication info.
 *
 * Site Kit by Google, Copyright 2020 Google LLC
 *
 * 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
 *
 *     https://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.
 */

/**
 * External dependencies
 */
import invariant from 'invariant';

/**
 * Internal dependencies
 */
import API from 'googlesitekit-api';
import Data from 'googlesitekit-data';
import { STORE_NAME } from './constants';

const { createRegistrySelector } = Data;

// Actions
const START_FETCH_AUTHENTICATION = 'START_FETCH_AUTHENTICATION';
const FETCH_AUTHENTICATION = 'FETCH_AUTHENTICATION';
const FINISH_FETCH_AUTHENTICATION = 'FINISH_FETCH_AUTHENTICATION';
const CATCH_FETCH_AUTHENTICATION = 'CATCH_FETCH_AUTHENTICATION';
const RECEIVE_AUTHENTICATION = 'RECEIVE_AUTHENTICATION';

export const INITIAL_STATE = {
	authentication: undefined,
	isFetchingAuthentication: false,
};

export const actions = {
	*fetchAuthentication() {
		let response, error;

		yield {
			payload: {},
			type: START_FETCH_AUTHENTICATION,
		};

		try {
			response = yield {
				payload: {},
				type: FETCH_AUTHENTICATION,
			};

			yield actions.receiveAuthentication( response );

			yield {
				payload: {},
				type: FINISH_FETCH_AUTHENTICATION,
			};
		} catch ( e ) {
			error = e;
			yield {
				payload: { error },
				type: CATCH_FETCH_AUTHENTICATION,
			};
		}

		return { response, error };
	},
	/**
	 * Stores connection info received from the REST API.
	 *
	 * @since 1.9.0
	 * @private
	 *
	 * @param {Object} authentication Authentication info from the API.
	 * @return {Object} Redux-style action.
	 */
	receiveAuthentication( authentication ) {
		invariant( authentication, 'authentication is required.' );

		return {
			payload: { authentication },
			type: RECEIVE_AUTHENTICATION,
		};
	},

};

export const controls = {
	[ FETCH_AUTHENTICATION ]: () => {
		return API.get( 'core', 'user', 'authentication' );
	},
};

export const reducer = ( state, { type, payload } ) => {
	switch ( type ) {
		case START_FETCH_AUTHENTICATION: {
			return {
				...state,
				isFetchingAuthentication: true,
			};
		}

		case RECEIVE_AUTHENTICATION: {
			const { authentication } = payload;

			return {
				...state,
				authentication,
			};
		}

		case FINISH_FETCH_AUTHENTICATION: {
			return {
				...state,
				isFetchingAuthentication: false,
			};
		}

		case CATCH_FETCH_AUTHENTICATION: {
			return {
				...state,
				error: payload.error,
				isFetchingAuthentication: false,
			};
		}

		default: {
			return { ...state };
		}
	}
};

export const resolvers = {
	*getAuthentication() {
		const { select } = yield Data.commonActions.getRegistry();

		if ( ! select( STORE_NAME ).getAuthentication() ) {
			yield actions.fetchAuthentication();
		}
	},
};

export const selectors = {

	/**
	 * Gets the authentication info for this user.
	 *
	 * Returns `undefined` if the authentication info is not available/loaded.
	 *
	 * Returns an object with the shape when successful:
	 * ```
	 * {
	 *   authenticated: <Boolean>,
	 *   grantedScopes: <Array>,
	 *   requiredScopes: <Array>
	 * }
	 * ```
	 *
	 * @private
	 * @since 1.9.0
	 *
	 * @param {Object} state Data store's state.
	 * @return {(Object|undefined)} User authentication info.
	 */
	getAuthentication( state ) {
		const { authentication } = state;
		return authentication;
	},
	/**
	 * Gets the Site Kit authentication status for this user.
	 *
	 * Returns `true` if the user is authenticated, `false` if
	 * not. Returns `undefined` if the authentication info is not available/loaded.
	 *
	 * @since 1.9.0
	 *
	 * @param {Object} state Data store's state.
	 * @return {(boolean|undefined)} User authentication status.
	 */
	isAuthenticated: createRegistrySelector( ( select ) => () => {
		const { authenticated } = select( STORE_NAME ).getAuthentication() || {};
		return authenticated;
	} ),

	/**
	 * Gets the granted scopes for the user.
	 *
	 * Returns an array of granted scopes or undefined
	 * if authentication info is not available/loaded.
	 *
	 * @since 1.9.0
	 *
	 * @param {Object} state Data store's state.
	 * @return {(Array|undefined)} Array of granted scopes
	 */
	getGrantedScopes: createRegistrySelector( ( select ) => () => {
		const { grantedScopes } = select( STORE_NAME ).getAuthentication() || {};
		return grantedScopes;
	} ),

	/**
	 * Gets the required scopes for the user.
	 *
	 * Returns an array of required scopes or undefined
	 * if authentication info is not available/loaded.
	 *
	 * @since 1.9.0
	 *
	 * @param {Object} state Data store's state.
	 * @return {(Array|undefined)} Array of required scopes
	 */
	getRequiredScopes: createRegistrySelector( ( select ) => () => {
		const { requiredScopes } = select( STORE_NAME ).getAuthentication() || {};
		return requiredScopes;
	} ),
};

export default {
	INITIAL_STATE,
	actions,
	controls,
	reducer,
	resolvers,
	selectors,
};
<commit_msg>Added new reauthentication selector to core user store.<commit_after>/**
 * core/user Data store: Authentication info.
 *
 * Site Kit by Google, Copyright 2020 Google LLC
 *
 * 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
 *
 *     https://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.
 */

/**
 * External dependencies
 */
import invariant from 'invariant';

/**
 * Internal dependencies
 */
import API from 'googlesitekit-api';
import Data from 'googlesitekit-data';
import { STORE_NAME } from './constants';

const { createRegistrySelector } = Data;

// Actions
const START_FETCH_AUTHENTICATION = 'START_FETCH_AUTHENTICATION';
const FETCH_AUTHENTICATION = 'FETCH_AUTHENTICATION';
const FINISH_FETCH_AUTHENTICATION = 'FINISH_FETCH_AUTHENTICATION';
const CATCH_FETCH_AUTHENTICATION = 'CATCH_FETCH_AUTHENTICATION';
const RECEIVE_AUTHENTICATION = 'RECEIVE_AUTHENTICATION';

export const INITIAL_STATE = {
	authentication: undefined,
	isFetchingAuthentication: false,
};

export const actions = {
	*fetchAuthentication() {
		let response, error;

		yield {
			payload: {},
			type: START_FETCH_AUTHENTICATION,
		};

		try {
			response = yield {
				payload: {},
				type: FETCH_AUTHENTICATION,
			};

			yield actions.receiveAuthentication( response );

			yield {
				payload: {},
				type: FINISH_FETCH_AUTHENTICATION,
			};
		} catch ( e ) {
			error = e;
			yield {
				payload: { error },
				type: CATCH_FETCH_AUTHENTICATION,
			};
		}

		return { response, error };
	},
	/**
	 * Stores connection info received from the REST API.
	 *
	 * @since 1.9.0
	 * @private
	 *
	 * @param {Object} authentication Authentication info from the API.
	 * @return {Object} Redux-style action.
	 */
	receiveAuthentication( authentication ) {
		invariant( authentication, 'authentication is required.' );

		return {
			payload: { authentication },
			type: RECEIVE_AUTHENTICATION,
		};
	},

};

export const controls = {
	[ FETCH_AUTHENTICATION ]: () => {
		return API.get( 'core', 'user', 'authentication' );
	},
};

export const reducer = ( state, { type, payload } ) => {
	switch ( type ) {
		case START_FETCH_AUTHENTICATION: {
			return {
				...state,
				isFetchingAuthentication: true,
			};
		}

		case RECEIVE_AUTHENTICATION: {
			const { authentication } = payload;

			return {
				...state,
				authentication,
			};
		}

		case FINISH_FETCH_AUTHENTICATION: {
			return {
				...state,
				isFetchingAuthentication: false,
			};
		}

		case CATCH_FETCH_AUTHENTICATION: {
			return {
				...state,
				error: payload.error,
				isFetchingAuthentication: false,
			};
		}

		default: {
			return { ...state };
		}
	}
};

export const resolvers = {
	*getAuthentication() {
		const { select } = yield Data.commonActions.getRegistry();

		if ( ! select( STORE_NAME ).getAuthentication() ) {
			yield actions.fetchAuthentication();
		}
	},
};

export const selectors = {

	/**
	 * Gets the authentication info for this user.
	 *
	 * Returns `undefined` if the authentication info is not available/loaded.
	 *
	 * Returns an object with the shape when successful:
	 * ```
	 * {
	 *   authenticated: <Boolean>,
	 *   grantedScopes: <Array>,
	 *   requiredScopes: <Array>
	 * }
	 * ```
	 *
	 * @private
	 * @since 1.9.0
	 *
	 * @param {Object} state Data store's state.
	 * @return {(Object|undefined)} User authentication info.
	 */
	getAuthentication( state ) {
		const { authentication } = state;
		return authentication;
	},
	/**
	 * Gets the Site Kit authentication status for this user.
	 *
	 * Returns `true` if the user is authenticated, `false` if
	 * not. Returns `undefined` if the authentication info is not available/loaded.
	 *
	 * @since 1.9.0
	 *
	 * @param {Object} state Data store's state.
	 * @return {(boolean|undefined)} User authentication status.
	 */
	isAuthenticated: createRegistrySelector( ( select ) => () => {
		const { authenticated } = select( STORE_NAME ).getAuthentication() || {};
		return authenticated;
	} ),

	/**
	 * Gets the granted scopes for the user.
	 *
	 * Returns an array of granted scopes or undefined
	 * if authentication info is not available/loaded.
	 *
	 * @since 1.9.0
	 *
	 * @param {Object} state Data store's state.
	 * @return {(Array|undefined)} Array of granted scopes
	 */
	getGrantedScopes: createRegistrySelector( ( select ) => () => {
		const { grantedScopes } = select( STORE_NAME ).getAuthentication() || {};
		return grantedScopes;
	} ),

	/**
	 * Gets the required scopes for the user.
	 *
	 * Returns an array of required scopes or undefined
	 * if authentication info is not available/loaded.
	 *
	 * @since 1.9.0
	 *
	 * @param {Object} state Data store's state.
	 * @return {(Array|undefined)} Array of required scopes
	 */
	getRequiredScopes: createRegistrySelector( ( select ) => () => {
		const { requiredScopes } = select( STORE_NAME ).getAuthentication() || {};
		return requiredScopes;
	} ),

	/**
	 * Checks reauthentication status for this user.
	 *
	 * Returns true if any required scopes are not satisfied or undefined
	 * if reauthentication info is not available/loaded.
	 *
	 * @since 1.9.0
	 *
	 * @param {Object} state Data store's state.
	 * @return {(boolean|undefined)} User reauthentication status.
	 */
	needsReauthentication: createRegistrySelector( ( select ) => () => {
		const { needsReauthentication } = select( STORE_NAME ).getAuthentication() || {};
		return needsReauthentication;
	} ),
};

export default {
	INITIAL_STATE,
	actions,
	controls,
	reducer,
	resolvers,
	selectors,
};
<|endoftext|>


<commit_before>var parse = require('esprima').parse;
var Errors = require('./match-error');

// Given a "template" expected AST that defines abstract identifier names
// described by `options.varPattern`, "bind" those identifiers to their
// concrete names in the "actual" AST.
function compareAst(actualSrc, expectedSrc, options) {
	var actualAst, expectedAst;

	options = options || {};
	// Lookup table of bound variable names
	options.boundVars = {};

	try {
		actualAst = parse(actualSrc).body;
	} catch(err) {
		throw new Errors.ParseError();
	}

	try {
		expectedAst = parse(expectedSrc).body;
	} catch (err) {
		throw new Errors.ParseError();
	}

	function _bind(actual, expected) {
		var attr;

		// Literal values
		if (Object(actual) !== actual) {
			if (actual !== expected) {
				throw new Errors.MatchError(actualAst, expectedAst);
			}
			return;
		}

		// Arrays
		if (Array.isArray(actual)) {
			actual.forEach(function(_, i) {
				_bind(actual[i], expected[i]);
			});
			return;
		}

		// Objects

		var handler = handlers[actual.type];
		if (handler) {
			var skipRecursion = handler(actual, expected, options);
			if (skipRecursion) {
				return;
			}
		}

		// Either remove attributes or recurse on their values
		for (attr in actual) {
			if (expected && attr in expected) {
				_bind(actual[attr], expected[attr]);
			}
		}
	}

	// Start recursing on the ASTs from the top level.
	_bind(actualAst, expectedAst);

	return null;
}

var handlers = {
	// Update unbound variable names in the expected AST, using the
	// previously-bound value when available.
	Identifier: function(actual, expected, options) {
		var varPattern = options.varPattern;
		var boundVars = options.boundVars;
		var unboundName;

		if (!varPattern) {
			return;
		}
		if (varPattern.test(expected.name)) {
			unboundName = expected.name;
			if (!(unboundName in boundVars)) {
				boundVars[unboundName] = actual.name;
			}
			expected.name = boundVars[unboundName];

			// This inequality would be caught by the next recursion, but it is
			// asserted here to provide a more specific error--the ASTs do not
			// match because a variable was re-bound.
			if (expected.name !== actual.name) {
				throw new Errors.BindingError(actual.name, expected.name);
			} else {
				return true;
			}
		}
	},
	// Recognize simple string deferencing as equivalent to "dot notation" with
	// a matching identifier (i.e. `a.b;` is equivalent to `a['b'];`).
	MemberExpression: function(actual, expected) {
		var props = {};

		if (actual.property.type === 'Literal') {
			props.Literal = actual.property.value;
		} else if (actual.property.type === 'Identifier') {
			props.Identifier = actual.property.name;
		}

		if (expected.property.type === 'Literal') {
			props.Literal = expected.property.value;
		} else if (expected.property.type === 'Identifier') {
			props.Identifier = expected.property.name;
		}

		return 'Identifier' in props && 'Literal' in props &&
			props.Identifier === props.Literal;
	}
};

compareAst.Error = Errors;

module.exports = compareAst;
<commit_msg>Improve variable names and in-line documentation<commit_after>var parse = require('esprima').parse;
var Errors = require('./match-error');

// Given a "template" expected AST that defines abstract identifier names
// described by `options.varPattern`, "bind" those identifiers to their
// concrete names in the "actual" AST.
function compareAst(actualSrc, expectedSrc, options) {
	var actualAst, expectedAst;

	options = options || {};
	// Lookup table of bound variable names
	options.boundVars = {};

	try {
		actualAst = parse(actualSrc).body;
	} catch(err) {
		throw new Errors.ParseError();
	}

	try {
		expectedAst = parse(expectedSrc).body;
	} catch (err) {
		throw new Errors.ParseError();
	}

	function _bind(actual, expected) {
		var attr, comparator, areEquivalent;

		// Literal values
		if (Object(actual) !== actual) {
			if (actual !== expected) {
				throw new Errors.MatchError(actualAst, expectedAst);
			}
			return;
		}

		// Arrays
		if (Array.isArray(actual)) {
			actual.forEach(function(_, i) {
				_bind(actual[i], expected[i]);
			});
			return;
		}

		// Nodes

		comparator = comparators[actual.type];
		if (comparator) {
			areEquivalent = comparator(actual, expected, options);
			if (areEquivalent) {
				return;
			}
		}

		// Either remove attributes or recurse on their values
		for (attr in actual) {
			if (expected && attr in expected) {
				_bind(actual[attr], expected[attr]);
			}
		}
	}

	// Start recursing on the ASTs from the top level.
	_bind(actualAst, expectedAst);

	return null;
}

// A collection of comparator functions that recognize equivalent nodes that
// would otherwise be reported as unequal by simple object comparison. These
// may:
//
// - return `true` if the given nodes can be verified as equivalent with no
//   further processing
// - throw an error if the nodes are verified as uniquivalent
// - return any other value if equivalency cannot be determined
var comparators = {
	// Update unbound variable names in the expected AST, using the
	// previously-bound value when available.
	Identifier: function(actual, expected, options) {
		var varPattern = options.varPattern;
		var boundVars = options.boundVars;
		var unboundName;

		if (!varPattern) {
			return;
		}
		if (varPattern.test(expected.name)) {
			unboundName = expected.name;
			if (!(unboundName in boundVars)) {
				boundVars[unboundName] = actual.name;
			}
			expected.name = boundVars[unboundName];

			// This inequality would be caught by the next recursion, but it is
			// asserted here to provide a more specific error--the ASTs do not
			// match because a variable was re-bound.
			if (expected.name !== actual.name) {
				throw new Errors.BindingError(actual.name, expected.name);
			} else {
				return true;
			}
		}
	},
	// Recognize simple string deferencing as equivalent to "dot notation" with
	// a matching identifier (i.e. `a.b;` is equivalent to `a['b'];`).
	MemberExpression: function(actual, expected) {
		var props = {};

		if (actual.property.type === 'Literal') {
			props.Literal = actual.property.value;
		} else if (actual.property.type === 'Identifier') {
			props.Identifier = actual.property.name;
		}

		if (expected.property.type === 'Literal') {
			props.Literal = expected.property.value;
		} else if (expected.property.type === 'Identifier') {
			props.Identifier = expected.property.name;
		}

		return 'Identifier' in props && 'Literal' in props &&
			props.Identifier === props.Literal;
	}
};

compareAst.Error = Errors;

module.exports = compareAst;
<|endoftext|>


<commit_before>/**
 The MIT License (MIT)

 Copyright (c) 2015 @biddster

 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included in
 all copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.
 */

module.exports = function (RED) {
    'use strict';

    RED.nodes.registerType("ninja-receive", function (config) {
        RED.nodes.createNode(this, config);
        var node = this;
        node.on('input', function (msg) {
            try {
                var payload = JSON.parse(msg.payload);
                node.log(payload);
                if (payload.hasOwnProperty('ERROR')) {
                    var error = 'Error code: ' + payload.ERROR[0].CODE;
                    node.error(error, msg);
                    node.status({fill: "red", shape: "dot", text: error});
                } else {
                    var firstDevice = payload.DEVICE[0];
                    var value = firstDevice.DA;
                    switch (firstDevice.D) {
                        case 11:
                            value = parseInt(value, 2).toString(16);
                            while (value.length < 6) {
                                value = '0' + value;
                            }
                            break;
                        case 30:
                        case 31:
                            value = parseInt(value);
                            break;
                        default:
                            break;
                    }
                    msg.topic = firstDevice.D;
                    msg.payload = value;
                    node.send(msg);
                    node.status({fill: "green", shape: "dot", text: "OK"});
                }
            } catch (error) {
                node.log(error.stack);
                node.error(error, msg);
                node.status({fill: "red", shape: "dot", text: error.message});
            }
        });
    });
};<commit_msg>Work on error handling in the receive node.<commit_after>/**
 The MIT License (MIT)

 Copyright (c) 2015 @biddster

 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included in
 all copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.
 */

module.exports = function (RED) {
    'use strict';

    RED.nodes.registerType("ninja-receive", function (config) {
        RED.nodes.createNode(this, config);
        var node = this;
        node.on('input', function (msg) {
            try {
                var payload = JSON.parse(msg.payload);
                node.log(payload);
                if (payload.ERROR) {
                    var error = 'Error code: ' + payload.ERROR[0].CODE;
                    node.error(error, msg);
                    node.status({fill: "red", shape: "dot", text: error});
                } else {
                    var firstDevice = payload.DEVICE[0];
                    var value = firstDevice.DA;
                    switch (firstDevice.D) {
                        case 11:
                            value = parseInt(value, 2).toString(16);
                            while (value.length < 6) {
                                value = '0' + value;
                            }
                            break;
                        case 30:
                        case 31:
                            value = parseInt(value);
                            break;
                        default:
                            break;
                    }
                    msg.topic = firstDevice.D;
                    msg.payload = value;
                    node.send(msg);
                    node.status({fill: "green", shape: "dot", text: "OK"});
                }
            } catch (error) {
                node.log(error.stack);
                node.error(error, msg);
                node.status({fill: "red", shape: "dot", text: error.message});
            }
        });
    });
};<|endoftext|>