<commit_before>/**
 * ___________                     __      _____
 * \__    ___/___________    ____ |  | __ /     \   ____
 *   |    |  \_  __ \__  \ _/ ___\|  |/ //  \ /  \_/ __ \
 *   |    |   |  | \// __ \\  \___|    </    Y    \  ___/
 *   |____|   |__|  (____  /\___  >__|_ \____|__  /\___  >
 *                       \/     \/     \/       \/     \/
 *
 *
 * jQuery TrackMe v2.1.2 - 13/04/2016
 * --------------------------------
 * Original author: Marco Trevisani (marco.trevisani@ynap.com)
 * Further changes, comments:
 * - added simple description :)
 * - added new public function trackEvent{trackingInfoObject}
 *
 *
 **** Description:
 * What does this plugin?
 * - It attaches the event tracking on elements with specific classes reading data attributes for the tracking info.
 * - It tracks the user behavior during filling of the form.
 *
 **** Available custom classes:
 * js-track-me          {any element} - track event on click.
 * js-track-me-hover:   {any element} - track event on mouseover.
 * js-track-me-focus:   {any element} - track event on focus.
 * js-track-me-form:    {form} - track user behavior during filling of the form (skipped inputs and inputs not valid).
 *
 **** Data Attributes:
 * data-tracking-category               - Set the event category.
 * data-tracking-action                 - Set the event action.
 * data-tracking-label                  - Set the event label.
 * data-tracking-form-id                - Track only if this form is valid.
 * data-tracking-event                  - Track only when this event will dispatched.
 * data-tracking-label-checked          - {checkbox only} this is the event label when the checkbox is checked.
 * data-tracking-label-not-checked      - {checkbox only} this is the event label when the checkbox isn't checked.
 * data-tracking-label-on               - {element with a switch functionality} Set the event label when the switch is on. (es: accordion open, option turned on...).
 * data-tracking-label-off              - {element with a switch functionality} Set the event label when the switch is off. (es: accordion closed, option turned off...).
 *
 **** Initialisation Options:
 *      $(selector).trackMe({
 *       debug: false,
 *       formTracking {
 *           completedEvents: false,
 *           oneTimeOnly: true,
 *           labelCompleted: "completed",
 *           labelNotValid: "not valid",
 *           labelSkipped: "skipped"
 *       },
 *       category: "custom global category",
 *       action: "custom global action",
 *       callback: function() {
 *          console.log("Event tracked.");
 *
 **** Usage Example:
 *
 *  JS: $(selector).trackMe();
 *  HTML: Click Me!
 *
 **** More Examples:
 *
 * You can set custom global action and label using public functions:
 *
 *      $(selector).trackMe();
 *      $(selector).data("plugin_trackMe").setTrackingCategory("custom global category");
 *      $(selector).data("plugin_trackMe").setTrackingAction("custom global action");
 *
 */
;(function ($, window, document) {
    "use strict";

    var pluginName = "trackMe";

    function TrackMe(element, options) {
        this.element = element;
        this._name = pluginName;
        this._defaults = $.fn.trackMe.defaults;
        this.options = $.extend({}, this._defaults, options);
        this.trackingData = {};
        this.init();
    }

    // - Private Functions - - -
    var formIsValid = function (formId) {
        if (typeof (formId) !== "undefined") {
            var $formToValidate = $("#" + formId);
            if ($formToValidate.length && !$formToValidate.valid()) {
                return false;
            }
        }
        return true;
    };

    // ( Debug Layer )
    var onlineOrStageEnvironment = function () {
            if (document.location.hostname.match("yoox.com$") || document.location.hostname.match("yoox.cn$")) {
                return true;
            }
            return false;
        },
        checkQueryString = function () {
            //if (location.search.match("td=1$")) {
            //    return true;
            //}
            //return false;
            return true;
        },
        debugTrackedEvent = function (category, action, label) {
            if (!onlineOrStageEnvironment() && checkQueryString()) {
                var messageText = "[EVENT] --------------------\nCategory: " + category + "\nAction: " + action + "\nLabel: " + label + "\n----------------------------",
                    messageHtml = "Category: " + category + "
 Action: " + action + "
 Label: " + label + "
",
                    $message = $("
").html("New Event:").after($("
").html(messageHtml)),
                    $messageContainer = $("
", {"class": "track-event-notification-layer fade-in"});

                if (!$(".track-event-notification-layer").length) {
                    $messageContainer.append($message);
                    $("body").append($messageContainer);
                } else {
                    clearTimeout(window.closeDebugLayerTimeout);
                    $(".track-event-notification-layer").append($message).removeClass("fade-out").addClass("fade-in");
                }

                window.closeDebugLayerTimeout = setTimeout(function () {
                    $(".track-event-notification-layer").removeClass("fade-in").addClass("fade-out").html("");
                }, 3000);

                console.log(messageText);
            }
        };

    // - Public Functions - - -
    $.extend(TrackMe.prototype, {
        init: function () {
            this.buildCache();
            this.bindEvents();
        },

        destroy: function () {
            this.unbindEvents();
            this.$element.removeData();
        },

        // Cache DOM nodes for performance
        buildCache: function () {
            this.$element = $(this.element);
        },

        // Bind events that trigger methods
        bindEvents: function () {
            var plugin = this;

            plugin.$element.on("click" + "." + plugin._name, ".js-track-me", function () {
                plugin.startTracking.call(plugin, $(this));
            });

            plugin.$element.on("hover" + "." + plugin._name, ".js-track-me-hover", function () {
                plugin.startTracking.call(plugin, $(this));
            });

            plugin.$element.on("focus" + "." + plugin._name, ".js-track-me-focus", function () {
                plugin.startTracking.call(plugin, $(this));
            });

            plugin.$element.on("blur" + "." + plugin._name, ".js-track-form input:not([type=submit]), .js-track-form select", function (event) {
                // track only REQUIRED input not already tracked.
                if (!$(this).data("alreadyTracked") && typeof ($(this).data("valRequired")) !== 'undefined') {
                    plugin.startTrackingForm.call(plugin, $(this));
                }
            });
        },

        // Unbind events that trigger methods
        unbindEvents: function () {
            this.$element.off("." + this._name);
        },

        getTrackingData: function ($elm) {
            return {
                category: typeof ($elm.data("trackingCategory")) === "undefined" ? this.options.category : $elm.data("trackingCategory"),
                action: typeof ($elm.data("trackingAction")) === "undefined" ? this.options.action : $elm.data("trackingAction"),
                label: $elm.data("trackingLabel"),
                labelChecked: $elm.data("trackingLabelChecked"),
                labelNotChecked: $elm.data("trackingLabelNotChecked"),
                labelOn: $elm.data("trackingLabelOn"),
                labelOff: $elm.data("trackingLabelOff"),
                event: $elm.data("trackingEvent"),
                formToValidate: $elm.data("trackingFormId")
            };
        },

        getFormTrackingData: function ($elm) {
            var $form = $elm.closest('form');
            return {
                category: typeof ($form.data("trackingCategory")) === "undefined" ? this.options.category : $form.data("trackingCategory"),
                action: typeof ($form.data("trackingAction")) === "undefined" ? this.options.action : $form.data("trackingAction"),
                label: $form.data("trackingFormName"),
                fieldName: typeof ($elm.data("trackingName")) === "undefined" ? $elm.attr('name') : $elm.data("trackingName")
            };
        },

        startTracking: function ($element) {
            var plugin = this;
            this.trackingData = this.getTrackingData($element);

            // Track checkbox events
            if ($element.is("input[type=checkbox]") && typeof (plugin.trackingData.labelChecked) !== "undefined") {
                var label = $element.is(":checked") ? plugin.trackingData.labelChecked : plugin.trackingData.labelNotChecked;

                if (typeof (plugin.trackingData.event) === "undefined" && label !== "undefined") {
                    plugin.trackUserEvent(plugin.trackingData.category, plugin.trackingData.action, label);
                } else {
                    $("body").off(plugin.trackingData.event).one(plugin.trackingData.event, function () {
                        plugin.trackUserEvent(plugin.trackingData.category, plugin.trackingData.action, label);
                    });
                }
                // Track switch events
            } else if (typeof (plugin.trackingData.labelOn) !== "undefined" || typeof (plugin.trackingData.labelOff) !== "undefined") {
                if ($element.hasClass("js-track-me-on")) {
                    $element.removeClass("js-track-me-on");
                    plugin.trackUserEvent(plugin.trackingData.category, plugin.trackingData.action, plugin.trackingData.labelOff);
                } else {
                    $element.addClass("js-track-me-on");
                    plugin.trackUserEvent(plugin.trackingData.category, plugin.trackingData.action, plugin.trackingData.labelOn);
                }
                // Track only if form is valid
            } else if (typeof (plugin.trackingData.formToValidate) !== "undefined") {
                if (formIsValid(plugin.trackingData.formToValidate)) {
                    plugin.trackUserEvent(plugin.trackingData.category, plugin.trackingData.action, plugin.trackingData.label);
                }

                // Track on custom event
            } else {
                if (typeof (plugin.trackingData.event) === "undefined") {
                    plugin.trackUserEvent(plugin.trackingData.category, plugin.trackingData.action, plugin.trackingData.label);
                } else {
                    $("body").off(plugin.trackingData.event).one(plugin.trackingData.event, function () {
                        plugin.trackUserEvent(plugin.trackingData.category, plugin.trackingData.action, plugin.trackingData.label);
                    });
                }
            }

            this.callback();
        },

        startTrackingForm: function ($element) {
            var plugin = this;
            this.trackingData = this.getFormTrackingData($element);

            if ($element.val().length === 0) {
                plugin.trackUserEvent(plugin.trackingData.category, plugin.trackingData.action, plugin.trackingData.label + " " + plugin.options.formTracking.labelSkipped + " " + plugin.trackingData.fieldName);
            } else if (!$element.valid()) {
                plugin.trackUserEvent(plugin.trackingData.category, plugin.trackingData.action, plugin.trackingData.label + " " + plugin.options.formTracking.labelNotValid + " " + plugin.trackingData.fieldName);
            } else {
                if (this.options.formTracking.completedEvents) {
                    plugin.trackUserEvent(plugin.trackingData.category, plugin.trackingData.action, plugin.trackingData.label + " " + plugin.options.formTracking.labelCompleted + " " + plugin.trackingData.fieldName);
                }
            }
            if (this.options.formTracking.oneTimeOnly) {
                $element.data("alreadyTracked", true);
            }

            this.callback();
        },

        setTrackingCategory: function (category) {
            this.options.category = category;
        },

        setTrackingAction: function (action) {
            this.options.action = action;
        },

        updateTrackingData: function (updatedTrackingData) {
            $.extend(true, this.trackingData, updatedTrackingData);
        },

        getCurrentTrackingCategory: function () {
            return this.options.category;
        },

        getCurrentTrackingAction: function () {
            return this.options.action;
        },

        trackUserEvent: function (category, action, label) {
            if (typeof (label) !== "undefined" && typeof (action) !== "undefined" && label !== "" && action !== "") {
                if (this.options.debug) {
                    debugTrackedEvent(category, action, label);
                }

                $Y.track.userEvent("ga", {category: category, action: action, label: label});
            }
        },

        trackEvent: function (trackInfo) {
            trackInfo.category = trackInfo.category || this.options.category;
            trackInfo.action = trackInfo.action || this.options.action;

            if (typeof (trackInfo.label) !== "undefined" && typeof (trackInfo.action) !== "undefined" && trackInfo.label !== "" && trackInfo.action !== "") {
                if (this.options.debug) {
                    debugTrackedEvent(trackInfo.category, trackInfo.action, trackInfo.label);
                }

                $Y.track.userEvent("ga", { category: trackInfo.category, action: trackInfo.action, label: trackInfo.label });
            }
        },

        callback: function () {
            // Cache onComplete option
            var onComplete = this.options.onComplete;

            if (typeof onComplete === "function") {
                onComplete.call(this.element);
            }
        },

        trackingData: {
            category: "",
            action: "",
            label: "",
            labelChecked: "",
            labelNotChecked: "",
            labelOn: "",
            labelOff: "",
            event: "",
            formToValidate: ""
        }
    });

    $.fn.trackMe = function (options) {
        this.each(function () {
            if (!$.data(this, "plugin_" + pluginName)) {
                $.data(this, "plugin_" + pluginName, new TrackMe(this, options));
            }
        });

        return this;
    };

    $.fn.trackMe.defaults = {
        debug: false, // true: activate the event notification layer
        formTracking: {
            completedEvents: false,
            oneTimeOnly: true,
            labelCompleted: "completed",
            labelNotValid: "not valid",
            labelSkipped: "skipped"
        },
        category: "",
        action: "",
        onComplete: function () {
        }
    };

    $.fn.triggerDelayed = function (event, delay) {
        delay = delay || 50;
        var element = $(this);
        setTimeout(function () {
            element.trigger(event);
        }, delay);
    };

})(jQuery, window, document);<commit_msg>added placeholder feature for labels | code clean<commit_after>/**
 * ___________                     __      _____
 * \__    ___/___________    ____ |  | __ /     \   ____
 *   |    |  \_  __ \__  \ _/ ___\|  |/ //  \ /  \_/ __ \
 *   |    |   |  | \// __ \\  \___|    </    Y    \  ___/
 *   |____|   |__|  (____  /\___  >__|_ \____|__  /\___  >
 *                       \/     \/     \/       \/     \/
 *
 *
 * jQuery TrackMe v2.1.3 - 08/07/2016
 * --------------------------------
 * Original author: Marco Trevisani (marco.trevisani@ynap.com)
 * Further changes, comments:
 * --- v2.1.3:
 * - added placeholder feature for labels.
 * --- v2.1.2:
 * - added simple description :)
 * - added new public function trackEvent{trackingInfoObject}
 *
 *
 **** Description:
 * What does this plugin?
 * - It attaches the event tracking on elements with specific classes reading data attributes for the tracking info.
 * - It tracks the user behavior during filling of the form.
 *
 **** Available custom classes:
 * js-track-me          {any element} - track event on click.
 * js-track-me-hover:   {any element} - track event on mouseover.
 * js-track-me-focus:   {any element} - track event on focus.
 * js-track-me-form:    {form} - track user behavior during filling of the form (skipped inputs and inputs not valid).
 *
 **** Data Attributes:
 * data-tracking-category               - Set the event category.
 * data-tracking-action                 - Set the event action.
 * data-tracking-label                  - Set the event label.
 * data-tracking-form-id                - Track only if this form is valid.
 * data-tracking-event                  - Track only when this event will dispatched.
 * data-tracking-label-checked          - {checkbox only} this is the event label when the checkbox is checked.
 * data-tracking-label-not-checked      - {checkbox only} this is the event label when the checkbox isn't checked.
 * data-tracking-label-on               - {element with a switch functionality} Set the event label when the switch is on. (es: accordion open, option turned on...).
 * data-tracking-label-off              - {element with a switch functionality} Set the event label when the switch is off. (es: accordion closed, option turned off...).
 *
 **** Initialisation Options:
 *      $(selector).trackMe({
 *       debug: false,
 *       formTracking {
 *           completedEvents: false,
 *           oneTimeOnly: true,
 *           labelCompleted: "completed",
 *           labelNotValid: "not valid",
 *           labelSkipped: "skipped"
 *       },
 *       category: "custom global category",
 *       action: "custom global action",
 *       callback: function() {
 *          console.log("Event tracked.");
 *
 **** Usage Example:
 *
 *  JS: $(selector).trackMe();
 *  HTML: Click Me!
 *
 **** More Examples:
 *
 * You can set custom global action and label using public functions:
 *
 *      $(selector).trackMe();
 *      $(selector).data("plugin_trackMe").setTrackingCategory("custom global category");
 *      $(selector).data("plugin_trackMe").setTrackingAction("custom global action");
 *
 */
;
(function ($, window, document) {
    "use strict";

    var pluginName = "trackMe";

    function TrackMe (element, options) {
        this.element = element;
        this._name = pluginName;
        this._defaults = $.fn.trackMe.defaults;
        this.options = $.extend({}, this._defaults, options);
        this.trackingData = {};
        this.init();
    }

    // - Private Functions - - -
    var formIsValid = function (formId) {
        if (formId !== undefined) {
            var $formToValidate = $("#" + formId);
            if ($formToValidate.length && !$formToValidate.valid()) {
                return false;
            }
        }
        return true;
    };

    // ( Debug Layer )
    var onlineOrStageEnvironment = function () {
            if (document.location.hostname.match("yoox.com$") || document.location.hostname.match("yoox.cn$")) {
                return true;
            }
            return false;
        },
        checkQueryString = function () {
            //if (location.search.match("td=1$")) {
            //    return true;
            //}
            //return false;
            return true;
        },
        debugTrackedEvent = function (category, action, label) {
            if (!onlineOrStageEnvironment() && checkQueryString()) {
                var messageText = "[EVENT] --------------------\nCategory: " +
                        category +
                        "\nAction: " +
                        action +
                        "\nLabel: " +
                        label +
                        "\n----------------------------",
                    messageHtml = "Category: " +
                        category +
                        "
 Action: " +
                        action +
                        "
 Label: " +
                        label +
                        "
",
                    $message = $("
").html("New Event:").after($("
").html(messageHtml)),
                    $messageContainer = $("
", { "class": "track-event-notification-layer fade-in" });

                if (!$(".track-event-notification-layer").length) {
                    $messageContainer.append($message);
                    $("body").append($messageContainer);
                } else {
                    clearTimeout(window.closeDebugLayerTimeout);
                    $(".track-event-notification-layer").append($message).removeClass("fade-out").addClass("fade-in");
                }

                window.closeDebugLayerTimeout = setTimeout(function () {
                        $(".track-event-notification-layer").removeClass("fade-in").addClass("fade-out").html("");
                    },
                    3000);

                console.log(messageText);
            }
        };

    // - Public Functions - - -
    $.extend(TrackMe.prototype,
    {
        init: function () {
            this.buildCache();
            this.bindEvents();
        },

        destroy: function () {
            this.unbindEvents();
            this.$element.removeData();
        },

        // Cache DOM nodes for performance
        buildCache: function () {
            this.$element = $(this.element);
        },

        // Bind events that trigger methods
        bindEvents: function () {
            var plugin = this;

            plugin.$element.on("click" + "." + plugin._name,
                ".js-track-me",
                function () {
                    plugin.startTracking.call(plugin, $(this));
                });

            plugin.$element.on("hover" + "." + plugin._name,
                ".js-track-me-hover",
                function () {
                    plugin.startTracking.call(plugin, $(this));
                });

            plugin.$element.on("focus" + "." + plugin._name,
                ".js-track-me-focus",
                function () {
                    plugin.startTracking.call(plugin, $(this));
                });

            plugin.$element.on("blur" + "." + plugin._name,
                ".js-track-form input:not([type=submit]), .js-track-form select",
                function () {
                    // track only REQUIRED input not already tracked.
                    if (!$(this).data("alreadyTracked") && $(this).data("valRequired") !== undefined) {
                        plugin.startTrackingForm.call(plugin, $(this));
                    }
                });
        },

        // Unbind events that trigger methods
        unbindEvents: function () {
            this.$element.off("." + this._name);
        },

        getTrackingData: function ($elm) {
            return {
                category: ($elm.data("trackingCategory")) === undefined
                    ? this.options.category
                    : $elm.data("trackingCategory"),
                action: ($elm.data("trackingAction")) === undefined ? this.options.action : $elm.data("trackingAction"),
                label: $elm.data("trackingLabel"),
                labelChecked: $elm.data("trackingLabelChecked"),
                labelNotChecked: $elm.data("trackingLabelNotChecked"),
                labelOn: $elm.data("trackingLabelOn"),
                labelOff: $elm.data("trackingLabelOff"),
                event: $elm.data("trackingEvent"),
                formToValidate: $elm.data("trackingFormId")
            };
        },

        getFormTrackingData: function ($elm) {
            var $form = $elm.closest("form");
            return {
                category: ($form.data("trackingCategory")) === undefined
                    ? this.options.category
                    : $form.data("trackingCategory"),
                action: ($form.data("trackingAction")) === undefined
                    ? this.options.action
                    : $form.data("trackingAction"),
                label: $form.data("trackingFormName"),
                fieldName: ($elm.data("trackingName")) === undefined ? $elm.attr("name") : $elm.data("trackingName")
            };
        },

        startTracking: function ($element) {
            var plugin = this;
            this.trackingData = this.getTrackingData($element);

            // Track checkbox events
            if ($element.is("input[type=checkbox]") && plugin.trackingData.labelChecked !== undefined) {
                var label = $element.is(":checked")
                    ? plugin.trackingData.labelChecked
                    : plugin.trackingData.labelNotChecked;

                if (plugin.trackingData.event === undefined && label !== undefined) {
                    plugin.trackUserEvent(plugin.trackingData.category, plugin.trackingData.action, label);
                } else {
                    $("body")
                        .off(plugin.trackingData.event)
                        .one(plugin.trackingData.event,
                            function () {
                                plugin.trackUserEvent(plugin.trackingData.category, plugin.trackingData.action, label);
                            });
                }
                // Track switch events
            } else if (plugin.trackingData.labelOn !== undefined || plugin.trackingData.labelOff !== undefined) {
                if ($element.hasClass("js-track-me-on")) {
                    $element.removeClass("js-track-me-on");
                    plugin.trackUserEvent(plugin.trackingData.category,
                        plugin.trackingData.action,
                        plugin.trackingData.labelOff);
                } else {
                    $element.addClass("js-track-me-on");
                    plugin.trackUserEvent(plugin.trackingData.category,
                        plugin.trackingData.action,
                        plugin.trackingData.labelOn);
                }
                // Track only if form is valid
            } else if (plugin.trackingData.formToValidate !== undefined) {
                if (formIsValid(plugin.trackingData.formToValidate)) {
                    plugin.trackUserEvent(plugin.trackingData.category,
                        plugin.trackingData.action,
                        plugin.trackingData.label);
                }

                // Track on custom event
            } else {
                if (plugin.trackingData.event === undefined) {
                    plugin.trackUserEvent(plugin.trackingData.category,
                        plugin.trackingData.action,
                        plugin.trackingData.label);
                } else {
                    $("body")
                        .off(plugin.trackingData.event)
                        .one(plugin.trackingData.event,
                            function () {
                                plugin.trackUserEvent(plugin.trackingData.category,
                                    plugin.trackingData.action,
                                    plugin.trackingData.label);
                            });
                }
            }

            this.callback();
        },

        startTrackingForm: function ($element) {
            var plugin = this;
            this.trackingData = this.getFormTrackingData($element);

            if ($element.val().length === 0) {
                plugin.trackUserEvent(plugin.trackingData.category,
                    plugin.trackingData.action,
                    plugin.trackingData.label +
                    " " +
                    plugin.options.formTracking.labelSkipped +
                    " " +
                    plugin.trackingData.fieldName);
            } else if (!$element.valid()) {
                plugin.trackUserEvent(plugin.trackingData.category,
                    plugin.trackingData.action,
                    plugin.trackingData.label +
                    " " +
                    plugin.options.formTracking.labelNotValid +
                    " " +
                    plugin.trackingData.fieldName);
            } else {
                if (this.options.formTracking.completedEvents) {
                    plugin.trackUserEvent(plugin.trackingData.category,
                        plugin.trackingData.action,
                        plugin.trackingData.label +
                        " " +
                        plugin.options.formTracking.labelCompleted +
                        " " +
                        plugin.trackingData.fieldName);
                }
            }
            if (this.options.formTracking.oneTimeOnly) {
                $element.data("alreadyTracked", true);
            }

            this.callback();
        },

        setTrackingCategory: function (category) {
            this.options.category = category;
        },

        setTrackingAction: function (action) {
            this.options.action = action;
        },

        addPlaceholder: function (placeholder) {
            $.extend(this.options.placeholders, placeholder);
        },

        updateTrackingData: function (updatedTrackingData) {
            $.extend(true, this.trackingData, updatedTrackingData);
        },

        getCurrentTrackingCategory: function () {
            return this.options.category;
        },

        getCurrentTrackingAction: function () {
            return this.options.action;
        },
        managePlaceholder: function (text) {
            var myRegexp = /#([a-zA-Z0-9]*)#/g,
                result = text.match(myRegexp),
                placeholderText,
                i;
            if (result) {
                for (i = 0; i <= result.length; i = i + 1) {
                    placeholderText = this.options.placeholders[result[i]] !== undefined
                        ? this.options.placeholders[result[i]]
                        : "";
                    text = text.replace(result[i], placeholderText);
                }
            }
            return text;
        },
        trackUserEvent: function (category, action, label) {
            if (label !== undefined && action !== undefined && label !== "" && action !== "") {
                if (this.options.debug) {
                    debugTrackedEvent(category, action, label);
                }

                label = this.managePlaceholder(label);

                $Y.track.userEvent("ga", { category: category, action: action, label: label });
            }
        },

        trackEvent: function (trackInfo) {
            trackInfo.category = trackInfo.category || this.options.category;
            trackInfo.action = trackInfo.action || this.options.action;

            if (trackInfo.label !== undefined &&
                trackInfo.action !== undefined &&
                trackInfo.label !== "" &&
                trackInfo.action !== "") {
                if (this.options.debug) {
                    debugTrackedEvent(trackInfo.category, trackInfo.action, trackInfo.label);
                }

                $Y.track.userEvent("ga",
                { category: trackInfo.category, action: trackInfo.action, label: trackInfo.label });
            }
        },

        callback: function () {
            // Cache onComplete option
            var onComplete = this.options.onComplete;

            if (typeof onComplete === "function") {
                onComplete.call(this.element);
            }
        },

        trackingData: {
            category: "",
            action: "",
            label: "",
            labelChecked: "",
            labelNotChecked: "",
            labelOn: "",
            labelOff: "",
            event: "",
            formToValidate: ""
        }
    });

    $.fn.trackMe = function (options) {
        this.each(function () {
            if (!$.data(this, "plugin_" + pluginName)) {
                $.data(this, "plugin_" + pluginName, new TrackMe(this, options));
            }
        });

        return this;
    };

    $.fn.trackMe.defaults = {
        debug: false, // true: activate the event notification layer
        formTracking: {
            completedEvents: false,
            oneTimeOnly: true,
            labelCompleted: "completed",
            labelNotValid: "not valid",
            labelSkipped: "skipped"
        },
        category: "",
        action: "",
        placeholders: {},
        onComplete: function () {
        }
    };

    $.fn.triggerDelayed = function (event, delay) {
        delay = delay || 50;
        var element = $(this);
        setTimeout(function () {
                element.trigger(event);
            },
            delay);
    };

}(jQuery, window, document));<|endoftext|>


<commit_before>'use strict';

var _ = require( '../utils' );

var AudioContext = window.AudioContext || window.webkitAudioContext;
var audioContext = new AudioContext();
var sampleRate = audioContext.sampleRate;

function toFreq( note ) {
  // A4 is 69.
  return Math.pow( 2, ( note - 69 ) / 12 ) * 440;
}

// delay is in seconds.
function playSound( sound, delay ) {
  var source = audioContext.createBufferSource();
  source.buffer = sound;
  source.connect( audioContext.destination );
  source.start( delay ? audioContext.currentTime + delay : 0 );
}

// duration is in seconds.
function generateAudioBuffer( freq, fn, duration, volume ) {
  var length = duration * sampleRate;

  var buffer = audioContext.createBuffer( 1, length, sampleRate );
  var channel = buffer.getChannelData(0);
  for ( var i = 0; i < length; i++ ) {
    channel[i] = fn( freq * i / sampleRate, i / length ) * volume;
  }

  return buffer;
}

var BPM = 140;
var NOTE = 2 * 60 / BPM * 1000;

var N = 1e-3 * NOTE;
var N2 = N / 2;
var N4 = N2 / 2;
var N8 = N4 / 2;

function sine( sample ) {
  return Math.sin( sample * 2  * Math.PI );
}

function createWaveform( noise, decay ) {
  return function waveform( sample, time ) {
    var wave = _.clamp( sine( sample ) + _.randFloatSpread( noise ), -1, 1 );
    var env = Math.exp( -time * decay );
    return wave * env;
  };
}

function bass( sample, time ) {
  var wave = 0.5 * ( sine( sample ) + sine( 0.5 * sample ) );
  var env = Math.exp( -time * 8 );
  return wave * env;
}

var kick = createWaveform( 0.05, 24 );
var snare = createWaveform( 0.8, 16 );

var E3 = toFreq( 52 );

var snareNote = generateAudioBuffer( E3, snare, N2, 0.5 );
// playSound( snareNote );

var kickNote = generateAudioBuffer( toFreq( 28 ), kick, N2 + N8, 1 );
playSound( kickNote );

var bassNote = generateAudioBuffer( toFreq( 32 ), bass, N, 0.5 );
// playSound( bassNote );
<commit_msg>Add batch sound generation functions.<commit_after>'use strict';

var _ = require( '../utils' );

var AudioContext = window.AudioContext || window.webkitAudioContext;
var audioContext = new AudioContext();
var sampleRate = audioContext.sampleRate;

function toFreq( note ) {
  // A4 is 69.
  return Math.pow( 2, ( note - 69 ) / 12 ) * 440;
}

// delay is in seconds.
function playSound( sound, delay ) {
  var source = audioContext.createBufferSource();
  source.buffer = sound;
  source.connect( audioContext.destination );
  source.start( delay ? audioContext.currentTime + delay : 0 );
}

// duration is in seconds.
function generateAudioBuffer( freq, fn, duration, volume ) {
  var length = duration * sampleRate;

  var buffer = audioContext.createBuffer( 1, length, sampleRate );
  var channel = buffer.getChannelData(0);
  for ( var i = 0; i < length; i++ ) {
    channel[i] = fn( freq * i / sampleRate, i / length ) * volume;
  }

  return buffer;
}

var BPM = 140;
var NOTE = 2 * 60 / BPM * 1000;

var N = 1e-3 * NOTE;
var N2 = N / 2;
var N4 = N2 / 2;
var N8 = N4 / 2;

function sine( sample ) {
  return Math.sin( sample * 2  * Math.PI );
}

function createWaveform( noise, decay ) {
  return function waveform( sample, time ) {
    var wave = _.clamp( sine( sample ) + _.randFloatSpread( noise ), -1, 1 );
    var env = Math.exp( -time * decay );
    return wave * env;
  };
}

function bass( sample, time ) {
  var wave = 0.5 * ( sine( sample ) + sine( 0.5 * sample ) );
  var env = Math.exp( -time * 8 );
  return wave * env;
}

var kick = createWaveform( 0.05, 24 );
var snare = createWaveform( 0.8, 16 );

var E3 = toFreq( 52 );

var snareNote = generateAudioBuffer( E3, snare, N2, 0.5 );
// playSound( snareNote );

var kickNote = generateAudioBuffer( toFreq( 28 ), kick, N2 + N8, 1 );
playSound( kickNote );

var bassNote = generateAudioBuffer( toFreq( 32 ), bass, N, 0.5 );
// playSound( bassNote );

var noteNames = [ 'c', 'cs', 'd', 'ds', 'e', 'f', 'fs', 'g', 'gs', 'a', 'as', 'b' ];

function toNoteString( note ) {
  var name = noteNames[ note % 12 ];
  var octave = Math.floor( note / 12 ) - 1;
  return name + octave;
}

function generateNotes( fn, duration, volume ) {
  var notes = {};

  // From A1 (21) to A6 (93).
  for ( var i = 21; i <= 93; i++ ) {
    notes[ toNoteString( i ) ] = generateAudioBuffer( toFreq( i ), fn, duration, volume );
  }

  return notes;
}

console.time( 'generate' );
var snares = generateNotes( snare, N4, 0.5 );
var basses = generateNotes( bass, N2, 0.5 );
console.timeEnd( 'generate' );
<|endoftext|>


<commit_before>import Media from '../mixin/media';
import Togglable from '../mixin/togglable';
import {
    attr,
    closest,
    hasClass,
    includes,
    isBoolean,
    isFocusable,
    isTag,
    isTouch,
    matches,
    once,
    pointerDown,
    pointerEnter,
    pointerLeave,
    queryAll,
    trigger,
    within,
} from 'uikit-util';

const KEY_SPACE = 32;

export default {
    mixins: [Media, Togglable],

    args: 'target',

    props: {
        href: String,
        target: null,
        mode: 'list',
        queued: Boolean,
    },

    data: {
        href: false,
        target: false,
        mode: 'click',
        queued: true,
    },

    connected() {
        if (!includes(this.mode, 'media') && !isFocusable(this.$el)) {
            attr(this.$el, 'tabindex', '0');
        }
    },

    computed: {
        target: {
            get({ href, target }, $el) {
                target = queryAll(target || href, $el);
                return (target.length && target) || [$el];
            },

            watch() {
                this.updateAria();
            },

            immediate: true,
        },
    },

    events: [
        {
            name: pointerDown,

            filter() {
                return includes(this.mode, 'hover');
            },

            handler(e) {
                if (!isTouch(e) || this._showState) {
                    return;
                }

                // Clicking a button does not give it focus on all browsers and platforms
                // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#clicking_and_focus
                trigger(this.$el, 'focus');
                once(
                    document,
                    pointerDown,
                    () => trigger(this.$el, 'blur'),
                    true,
                    (e) => !within(e.target, this.$el)
                );

                // Prevent initial click to prevent double toggle through focus + click
                if (includes(this.mode, 'click')) {
                    this._preventClick = true;
                }
            },
        },

        {
            name: `${pointerEnter} ${pointerLeave} focus blur`,

            filter() {
                return includes(this.mode, 'hover');
            },

            handler(e) {
                if (isTouch(e)) {
                    return;
                }

                const show = includes([pointerEnter, 'focus'], e.type);
                const expanded = attr(this.$el, 'aria-expanded');

                // Skip hide if still hovered or focused
                if (
                    !show &&
                    ((e.type === pointerLeave && matches(this.$el, ':focus')) ||
                        (e.type === 'blur' && matches(this.$el, ':hover')))
                ) {
                    return;
                }

                // Skip if state does not change e.g. hover + focus received
                if (this._showState && show === (expanded !== this._showState)) {
                    // Ensure reset if state has changed through click
                    if (!show) {
                        this._showState = null;
                    }
                    return;
                }

                this._showState = show ? expanded : null;

                this.toggle(`toggle${show ? 'show' : 'hide'}`);
            },
        },

        {
            name: 'keydown',

            filter() {
                return includes(this.mode, 'click') && isTag(this.$el, 'input');
            },

            handler(e) {
                if (e.keyCode === KEY_SPACE) {
                    e.preventDefault();
                    this.$el.click();
                }
            },
        },

        {
            name: 'click',

            filter() {
                return includes(this.mode, 'click');
            },

            handler(e) {
                if (this._preventClick) {
                    return (this._preventClick = null);
                }

                let link;
                if (
                    closest(e.target, 'a[href="#"], a[href=""]') ||
                    ((link = closest(e.target, 'a[href]')) &&
                        (attr(this.$el, 'aria-expanded') !== 'true' ||
                            (link.hash && matches(this.target, link.hash))))
                ) {
                    e.preventDefault();
                }

                this.toggle();
            },
        },

        {
            name: 'toggled',

            self: true,

            el() {
                return this.target;
            },

            handler(e, toggled) {
                if (e.target === this.target[0]) {
                    this.updateAria(toggled);
                }
            },
        },
    ],

    update: {
        read() {
            return includes(this.mode, 'media') && this.media ? { match: this.matchMedia } : false;
        },

        write({ match }) {
            const toggled = this.isToggled(this.target);
            if (match ? !toggled : toggled) {
                this.toggle();
            }
        },

        events: ['resize'],
    },

    methods: {
        async toggle(type) {
            if (!trigger(this.target, type || 'toggle', [this])) {
                return;
            }

            if (!this.queued) {
                return this.toggleElement(this.target);
            }

            const leaving = this.target.filter((el) => hasClass(el, this.clsLeave));

            if (leaving.length) {
                for (const el of this.target) {
                    const isLeaving = includes(leaving, el);
                    this.toggleElement(el, isLeaving, isLeaving);
                }
                return;
            }

            const toggled = this.target.filter(this.isToggled);
            await this.toggleElement(toggled, false);
            await this.toggleElement(
                this.target.filter((el) => !includes(toggled, el)),
                true
            );
        },

        updateAria(toggled) {
            if (includes(this.mode, 'media')) {
                return;
            }

            attr(
                this.$el,
                'aria-expanded',
                isBoolean(toggled) ? toggled : this.isToggled(this.target)
            );
        },
    },
};
<commit_msg>fix: check for input https://github.com/uikit/uikit/commit/b32d3452ff0ff9782cb48f613bec3912dcea65cd#r67642999 Thanks @Lumelomser<commit_after>import Media from '../mixin/media';
import Togglable from '../mixin/togglable';
import {
    attr,
    closest,
    hasClass,
    includes,
    isBoolean,
    isFocusable,
    isTag,
    isTouch,
    matches,
    once,
    pointerDown,
    pointerEnter,
    pointerLeave,
    queryAll,
    trigger,
    within,
} from 'uikit-util';

const KEY_SPACE = 32;

export default {
    mixins: [Media, Togglable],

    args: 'target',

    props: {
        href: String,
        target: null,
        mode: 'list',
        queued: Boolean,
    },

    data: {
        href: false,
        target: false,
        mode: 'click',
        queued: true,
    },

    connected() {
        if (!includes(this.mode, 'media') && !isFocusable(this.$el)) {
            attr(this.$el, 'tabindex', '0');
        }
    },

    computed: {
        target: {
            get({ href, target }, $el) {
                target = queryAll(target || href, $el);
                return (target.length && target) || [$el];
            },

            watch() {
                this.updateAria();
            },

            immediate: true,
        },
    },

    events: [
        {
            name: pointerDown,

            filter() {
                return includes(this.mode, 'hover');
            },

            handler(e) {
                if (!isTouch(e) || this._showState) {
                    return;
                }

                // Clicking a button does not give it focus on all browsers and platforms
                // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#clicking_and_focus
                trigger(this.$el, 'focus');
                once(
                    document,
                    pointerDown,
                    () => trigger(this.$el, 'blur'),
                    true,
                    (e) => !within(e.target, this.$el)
                );

                // Prevent initial click to prevent double toggle through focus + click
                if (includes(this.mode, 'click')) {
                    this._preventClick = true;
                }
            },
        },

        {
            name: `${pointerEnter} ${pointerLeave} focus blur`,

            filter() {
                return includes(this.mode, 'hover');
            },

            handler(e) {
                if (isTouch(e)) {
                    return;
                }

                const show = includes([pointerEnter, 'focus'], e.type);
                const expanded = attr(this.$el, 'aria-expanded');

                // Skip hide if still hovered or focused
                if (
                    !show &&
                    ((e.type === pointerLeave && matches(this.$el, ':focus')) ||
                        (e.type === 'blur' && matches(this.$el, ':hover')))
                ) {
                    return;
                }

                // Skip if state does not change e.g. hover + focus received
                if (this._showState && show === (expanded !== this._showState)) {
                    // Ensure reset if state has changed through click
                    if (!show) {
                        this._showState = null;
                    }
                    return;
                }

                this._showState = show ? expanded : null;

                this.toggle(`toggle${show ? 'show' : 'hide'}`);
            },
        },

        {
            name: 'keydown',

            filter() {
                return includes(this.mode, 'click') && !isTag(this.$el, 'input');
            },

            handler(e) {
                if (e.keyCode === KEY_SPACE) {
                    e.preventDefault();
                    this.$el.click();
                }
            },
        },

        {
            name: 'click',

            filter() {
                return includes(this.mode, 'click');
            },

            handler(e) {
                if (this._preventClick) {
                    return (this._preventClick = null);
                }

                let link;
                if (
                    closest(e.target, 'a[href="#"], a[href=""]') ||
                    ((link = closest(e.target, 'a[href]')) &&
                        (attr(this.$el, 'aria-expanded') !== 'true' ||
                            (link.hash && matches(this.target, link.hash))))
                ) {
                    e.preventDefault();
                }

                this.toggle();
            },
        },

        {
            name: 'toggled',

            self: true,

            el() {
                return this.target;
            },

            handler(e, toggled) {
                if (e.target === this.target[0]) {
                    this.updateAria(toggled);
                }
            },
        },
    ],

    update: {
        read() {
            return includes(this.mode, 'media') && this.media ? { match: this.matchMedia } : false;
        },

        write({ match }) {
            const toggled = this.isToggled(this.target);
            if (match ? !toggled : toggled) {
                this.toggle();
            }
        },

        events: ['resize'],
    },

    methods: {
        async toggle(type) {
            if (!trigger(this.target, type || 'toggle', [this])) {
                return;
            }

            if (!this.queued) {
                return this.toggleElement(this.target);
            }

            const leaving = this.target.filter((el) => hasClass(el, this.clsLeave));

            if (leaving.length) {
                for (const el of this.target) {
                    const isLeaving = includes(leaving, el);
                    this.toggleElement(el, isLeaving, isLeaving);
                }
                return;
            }

            const toggled = this.target.filter(this.isToggled);
            await this.toggleElement(toggled, false);
            await this.toggleElement(
                this.target.filter((el) => !includes(toggled, el)),
                true
            );
        },

        updateAria(toggled) {
            if (includes(this.mode, 'media')) {
                return;
            }

            attr(
                this.$el,
                'aria-expanded',
                isBoolean(toggled) ? toggled : this.isToggled(this.target)
            );
        },
    },
};
<|endoftext|>


<commit_before>/*===============================================================================
************   Fast Clicks   ************
************   Impressed by https://github.com/ftlabs/fastclick   ************
===============================================================================*/
app.initFastClicks = function () {
    if (!$.supportTouch) return;
    var touchStartX, touchStartY, touchStartTime, targetElement, trackClick, activeSelection, scrollParent;

    function targetNeedsFocus(el) {
        var tag = el.nodeName.toLowerCase();
        var skipInputs = ('button checkbox file image radio submit').split(' ');
        if (el.disabled || el.readOnly) return false;
        if (tag === 'textarea') return true;
        if (tag === 'select') {
            if (app.device.os === 'android') return false;
            else return true;
        }
        if (tag === 'input' && skipInputs.indexOf(el.type) < 0) return true;
    }
    function handleTouchStart(e) {
        if (e.targetTouches.length > 1) {
            return true;
        }
        if (app.device.os === 'ios') {
            var selection = window.getSelection();
            if (selection.rangeCount && !selection.isCollapsed) {
                activeSelection = true;
                return true;
            }
            else {
                activeSelection = false;
            }
        }
        trackClick = true;
        targetElement = e.target;
        touchStartTime = (new Date()).getTime();
        touchStartX = e.targetTouches[0].pageX;
        touchStartY = e.targetTouches[0].pageY;

        // Detect scroll parent
        if (app.device.os === 'ios') {
            scrollParent = undefined;
            $(targetElement).parents().each(function () {
                var parent = this;
                if (parent.scrollHeight > parent.offsetHeight && !scrollParent) {
                    scrollParent = parent;
                    scrollParent.f7ScrollTop = scrollParent.scrollTop;
                }
            });
        }
    }
    function handleTouchMove(e) {
        if (!trackClick) return;
        trackClick = false;
        targetElement = null;
    }
    function handleTouchEnd(e) {
        if (!trackClick) {
            if (!activeSelection) e.preventDefault();
            return true;
        }

        if (!activeSelection) {
            e.preventDefault();
        }

        var touchEndTime = (new Date()).getTime();
        if (touchEndTime - touchStartTime > 200) {
            return true;
        }
        e.preventDefault();

        trackClick = false;
        if (app.device.os === 'ios' && scrollParent) {
            if (scrollParent.scrollTop !== scrollParent.f7ScrollTop) {
                return false;
            }
        }

        // Trigger focus where required
        if (targetNeedsFocus(targetElement)) targetElement.focus();

        // Trigger click
        var touch = e.changedTouches[0];
        var evt = document.createEvent('MouseEvents');
        var eventType = 'click';
        if (app.device.os === 'android' && targetElement.nodeName.toLowerCase() === 'select') {
            eventType = 'mousedown';
        }
        evt.initMouseEvent(eventType, true, true, window, 1, touch.screenX, touch.screenY, touch.clientX, touch.clientY, false, false, false, false, 0, null);

        targetElement.dispatchEvent(evt);
    }
    $(document).on('touchstart', handleTouchStart);
    $(document).on('touchmove', handleTouchMove);
    $(document).on('touchend', handleTouchEnd);
};<commit_msg>Small improvements in fast clicks<commit_after>/*===============================================================================
************   Fast Clicks   ************
************   Impressed by https://github.com/ftlabs/fastclick   ************
===============================================================================*/
app.initFastClicks = function () {
    if (!$.supportTouch) return;
    var touchStartX, touchStartY, touchStartTime, targetElement, trackClick, activeSelection, scrollParent, cancelNextClick;

    function targetNeedsFocus(el) {
        var tag = el.nodeName.toLowerCase();
        var skipInputs = ('button checkbox file image radio submit').split(' ');
        if (el.disabled || el.readOnly) return false;
        if (tag === 'textarea') return true;
        if (tag === 'select') {
            if (app.device.os === 'android') return false;
            else return true;
        }
        if (tag === 'input' && skipInputs.indexOf(el.type) < 0) return true;
    }
    function handleTouchStart(e) {

        if (e.targetTouches.length > 1) {
            return true;
        }
        if (app.device.os === 'ios') {
            var selection = window.getSelection();
            if (selection.rangeCount && !selection.isCollapsed) {
                activeSelection = true;
                return true;
            }
            else {
                activeSelection = false;
            }
        }
        trackClick = true;
        targetElement = e.target;
        touchStartTime = (new Date()).getTime();
        touchStartX = e.targetTouches[0].pageX;
        touchStartY = e.targetTouches[0].pageY;

        // Detect scroll parent
        if (app.device.os === 'ios') {
            scrollParent = undefined;
            $(targetElement).parents().each(function () {
                var parent = this;
                if (parent.scrollHeight > parent.offsetHeight && !scrollParent) {
                    scrollParent = parent;
                    scrollParent.f7ScrollTop = scrollParent.scrollTop;
                }
            });
        }
    }
    function handleTouchMove(e) {
        if (!trackClick) return;
        trackClick = false;
        targetElement = null;
    }
    function handleTouchEnd(e) {
        if (!trackClick) {
            if (!activeSelection) e.preventDefault();
            return true;
        }

        if (!activeSelection) {
            e.preventDefault();
        }

        var touchEndTime = (new Date()).getTime();

        cancelNextClick = true;
        if (touchEndTime - touchStartTime > 200) {
            cancelNextClick = false;
            return true;
        }

        e.preventDefault();

        trackClick = false;
        if (app.device.os === 'ios' && scrollParent) {
            if (scrollParent.scrollTop !== scrollParent.f7ScrollTop) {
                return false;
            }
        }

        // Trigger focus where required
        if (targetNeedsFocus(targetElement)) targetElement.focus();

        // Trigger click
        var touch = e.changedTouches[0];
        var evt = document.createEvent('MouseEvents');
        var eventType = 'click';
        if (app.device.os === 'android' && targetElement.nodeName.toLowerCase() === 'select') {
            eventType = 'mousedown';
        }
        evt.initMouseEvent(eventType, true, true, window, 1, touch.screenX, touch.screenY, touch.clientX, touch.clientY, false, false, false, false, 0, null);
        evt.forwardedTouchEvent = true;
        targetElement.dispatchEvent(evt);
    }
    function handleClick(e) {
        if (!targetElement) {
            return true;
        }

        if (e.forwardedTouchEvent) {
            return true;
        }

        if (!e.cancelable) {
            return true;
        }

        if (cancelNextClick) {
            e.stopPropagation();
            e.stopImmediatePropagation();
            return false;
        }
    }
    $(document).on('touchstart', handleTouchStart);
    $(document).on('touchmove', handleTouchMove);
    $(document).on('touchend', handleTouchEnd);
    $(document).on('click', handleClick);

};<|endoftext|>


<commit_before>import { separate } from './files';

export default [
  {
    test: /^(text|application)\//,
    loader: (type, seed) => textLoader(type, seed),
    composer: (file) => Promise.resolve(file.text)
  },
  {
    test: /^(image|audio|video)\//,
    loader: (type, seed) => blobLoader(type, seed),
    composer: (file) => blobToBase64(file.blob)
  },
  {
    // The Sentinel (will throw error)
    test: /./,
    loader: null,
    composer: null
  }
];

export const defaultValue = {
  key: null,
  name: '',
  moduleName: '',
  type: '',
  isText: true,
  text: '',
  blob: null,
  blobURL: null,
  options: {
    isEntryPoint: false,
    isReadOnly: false,
  },
  author: {
    name: '',
    url: '',
  },
};

const getUniqueId = (i => () => ++i)(0);

export const merger = (...args) => {
  const parts = [{}].concat(defaultValue).concat(args);
  const file = Object.assign.apply(null, parts); // merge objects
  file.key = getUniqueId(); // unique key (Number)
  return file;
};

export const textLoader = (type, seed) =>
  new Promise((resolve, reject) => {
    if (sanitize(seed, reject)) return;

    const path = separate(seed.name);

    // when Blob => text, it must be asynchronous
    new Promise((resolve, reject) => {
      if (typeof seed.text === 'string') {
        resolve(seed.text);
        return;
      }
      const reader = new FileReader();
      reader.onload = (event) => {
        resolve(event.target.result);
      };
      reader.readAsText(seed.blob);
    })
    .then(text => resolve(merger({
      name: path.name,
      moduleName: path.moduleName,
      type,
      isText: true,
      text,
      options: Object.assign({}, defaultValue.options, seed.options)
    })));
  });

export const blobLoader = (type, seed) =>
  new Promise((resolve, reject) => {
    if (sanitize(seed, reject)) return;

    const blob = seed.blob || (base64 => {
      var bin = atob(base64);
      var byteArray = new Uint8Array(bin.length);
      for (var i = bin.length - 1; i >= 0; i--) {
        byteArray[i] = bin.charCodeAt(i);
      }
      return new Blob([byteArray.buffer], { type });
    })(seed.text);

    const path = separate(seed.name);

    resolve(merger({
      name: path.name,
      moduleName: path.moduleName,
      type,
      isText: false,
      blob,
      blobURL: URL.createObjectURL(blob),
      options: Object.assign({}, defaultValue.options, seed.options)
    }));
  });

const blobToBase64 = (blob) =>
  new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = (event) => {
      const { result } = event.target;
      resolve(result.substr(result.indexOf(',') + 1));
    };
    reader.readAsDataURL(blob);
  });

const sanitize = (seed, reject) => {
  if (typeof seed.text !== 'string' && !seed.blob) {
    reject(new Error(`Needed text or blob, but given: ${JSON.stringify(seed)}`));
    return true;
  }
  return false;
};
<commit_msg>json type loader<commit_after>import { separate } from './files';

export default [
  {
    test: /^(text|application)\/json$/,
    loader: (type, seed) => textLoader(type, seed).then(jsonLoader),
    composer: (file) => Promise.resolve(file.text)
  },
  {
    test: /^(text|application)\//,
    loader: (type, seed) => textLoader(type, seed),
    composer: (file) => Promise.resolve(file.text)
  },
  {
    test: /^(image|audio|video)\//,
    loader: (type, seed) => blobLoader(type, seed),
    composer: (file) => blobToBase64(file.blob)
  },
  {
    // The Sentinel (will throw error)
    test: /./,
    loader: null,
    composer: null
  }
];

export const defaultValue = {
  key: null,
  name: '',
  moduleName: '',
  type: '',
  isText: true,
  text: '',
  blob: null,
  blobURL: null,
  options: {
    isEntryPoint: false,
    isReadOnly: false,
  },
  author: {
    name: '',
    url: '',
  },
};

const getUniqueId = (i => () => ++i)(0);

export const merger = (...args) => {
  const parts = [{}].concat(defaultValue).concat(args);
  const file = Object.assign.apply(null, parts); // merge objects
  file.key = getUniqueId(); // unique key (Number)
  return file;
};

export const textLoader = (type, seed) =>
  new Promise((resolve, reject) => {
    if (sanitize(seed, reject)) return;

    const path = separate(seed.name);

    // when Blob => text, it must be asynchronous
    new Promise((resolve, reject) => {
      if (typeof seed.text === 'string') {
        resolve(seed.text);
        return;
      }
      const reader = new FileReader();
      reader.onload = (event) => {
        resolve(event.target.result);
      };
      reader.readAsText(seed.blob);
    })
    .then(text => resolve(merger({
      name: path.name,
      moduleName: path.moduleName,
      type,
      isText: true,
      text,
      options: Object.assign({}, defaultValue.options, seed.options)
    })));
  });

export const blobLoader = (type, seed) =>
  new Promise((resolve, reject) => {
    if (sanitize(seed, reject)) return;

    const blob = seed.blob || (base64 => {
      var bin = atob(base64);
      var byteArray = new Uint8Array(bin.length);
      for (var i = bin.length - 1; i >= 0; i--) {
        byteArray[i] = bin.charCodeAt(i);
      }
      return new Blob([byteArray.buffer], { type });
    })(seed.text);

    const path = separate(seed.name);

    resolve(merger({
      name: path.name,
      moduleName: path.moduleName,
      type,
      isText: false,
      blob,
      blobURL: URL.createObjectURL(blob),
      options: Object.assign({}, defaultValue.options, seed.options)
    }));
  });

export const jsonLoader = (file) =>
  new Promise((resolve, reject) => {
    const json = JSON.parse(file.text);
    resolve(Object.assign({}, file, { json }));
  });

const blobToBase64 = (blob) =>
  new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = (event) => {
      const { result } = event.target;
      resolve(result.substr(result.indexOf(',') + 1));
    };
    reader.readAsDataURL(blob);
  });

const sanitize = (seed, reject) => {
  if (typeof seed.text !== 'string' && !seed.blob) {
    reject(new Error(`Needed text or blob, but given: ${JSON.stringify(seed)}`));
    return true;
  }
  return false;
};
<|endoftext|>


<commit_before>/*global Logger Rules Utils state */
/*eslint no-loop-func:0 */
var Libs = {
  add: function(libraryName, librayTopLevelFunction, forceAdd) {
    // Check if the method is already defined
    forceAdd = forceAdd || false;
    if (this.hasOwnProperty(libraryName) && !forceAdd) {
      Logger.info("[libs.js] Did not add '" + libraryName + "' to Libs, because it already exists as a function().");
      return;
    }
    this[libraryName] = librayTopLevelFunction;
    Logger.info("[libs.js] Added library as Libs." + libraryName);
  },
  import: function() {
    return new Promise(function (resolve) {
      Rules.all().then(function (rules) {
        rules.forEach(function (rule) {
          if (typeof rule.export !== "undefined" && typeof rule.lib === "function") {
            // Add the rule into the scope of all library functions
            Libs.add(rule.export, rule.lib, true);
          }
        });
        Logger.info("[libs.js] Current libraries : " + Object.keys(Libs).toString());
        resolve(true);
      });
    });
  },
  // Dectects libraries used in a rulecode string
  // returns an array of found libraries
  detectVendoredLibraries: function(ruleCodeString) {
    var detectedLibs = [];
    Object.keys(Utils.vendoredLibs).forEach(function dtctLib(vLibKey) {
      if (ruleCodeString.match(Utils.vendoredLibs[vLibKey].detectWith) !== null) {
        // Found!
        detectedLibs.push(vLibKey);
      }
    });
    return detectedLibs;
  },
  loadLibs: function(scriptPaths, whoCallsMe) {
    /*eslint-disable complexity */
    return new Promise(function (resolve) {
      if (typeof scriptPaths === "string") {
        scriptPaths = [scriptPaths];
      }

      // If there is no script to inject
      // OR we run in the context of the content page
      // resolve now.
      // The content page gets its libraries by using the chrome API (see background/form_util.js#injectAndAttachToLibs)
      if (scriptPaths.length === 0 || !Utils.isBgPage()) {
        resolve(0);
        return;
      }

      var anchor = document.querySelector("body");
      var fragment = document.createDocumentFragment();

      var loadedScriptCount = 0;
      var targetScriptCount = scriptPaths.length;
      var scriptPath;

      for (var i = 0; i < targetScriptCount; i++) {
        scriptPath = scriptPaths[i];

        var vLib = Utils.vendoredLibs[scriptPath];
        var libName = vLib.name;

        // If a lib with that name is already present, don't load it again
        if (typeof Libs[libName] !== "undefined") {
          loadedScriptCount++;
          Logger.info("[libs.js] Didn't load '" + scriptPath + "' again");
          continue;
        }

        // If the requested lbrary is not vendored, break loop
        if (typeof vLib === "undefined") {
          Logger.info("[libs.js] Didn't load '" + scriptPath + "' since it is not vendored (see utils.js)");
          loadedScriptCount++;
          continue;
        }

        // If the library is present on window (somehow) just add it to Libs (again)
        if (typeof window[vLib.onWindowName] !== "undefined" && typeof Libs[libName] === "undefined") {
          Libs.add(libName, window[vLib.onWindowName]);
          loadedScriptCount++;
          continue;
        }

        var script = document.createElement("script");
        script.async = false;
        script.dataset.who = whoCallsMe;
        script.className = "injectedByFormOFill";
        script.dataset.script = scriptPath;
        script.src = chrome.extension.getURL(scriptPath);
        script.onload = function() {
          // Add Library to Libs
          Libs.add(libName, window[vLib.onWindowName]);
          loadedScriptCount++;
          Logger.info("[libs.js] Loaded '" + scriptPath + "'");

          // If all script are loaded, resolve promise
          if (loadedScriptCount >= targetScriptCount) {
            resolve(loadedScriptCount);
          }
        };
        script.onerror = function() {
          resolve(loadedScriptCount);
        };

        // Since this is all async make sure nobody has already
        // inserted it while we worked on this script:
        if (document.querySelectorAll("script[data-script='" + scriptPath + "']").length === 0) {
          fragment.appendChild(script);
        }
      }

      // If the loop is ready and the count already matches,
      // there was nothing to to and that's ok
      if (loadedScriptCount >= targetScriptCount) {
        resolve(loadedScriptCount);
      }

      // Only insert the fragment if it has something inside
      if (fragment.childNodes.length > 0) {
        anchor.appendChild(fragment);
      }
    });
    /*eslint-enable complexity */
  },
  setThrobberText: function(text) {
    // Change the text of the throbber
    if (state.lastActiveTab === null) {
      return null;
    }

    // Since this is called from the background pages
    // we need to send a message to the content.js
    chrome.tabs.sendMessage(state.lastActiveTab.id, {action: "showOverlay", message: text});
    return null;
  }
};

// This is a function "dummy"
// It represents code to copy the content of one
// DOM node to another.
// The SELECTOR will be replaced and re-compiled later.
// see Libs.h.copyValue
var _copyValueFunction = function() {
  if (!Utils.isBgPage()) {
    var $source = document.querySelector("##SELECTOR##");
    if ($source === null) {
      // element not found
      Libs.setThrobberText(chrome.i18n.getMessage("lib_h_copyvalue_field_not_found", [ "##SELECTOR##" ]));
      return null;
    }

    // element found
    return $source.value;
  }
  return null;
};

// Helper for use in value functions
//
// "value" : Libs.h.click                          => Clicks on the element specified by 'selector'
//           Libs.h.screenshot("save_as_filename") => Save a screenshot of visible area as [filename]
//           Libs.h.copyValue("#selector")         => copies the *value* ofthe chosen field
//           Libs.h.displayMessage("Some text")    => shows a message to the user#
//           Libs.h.select/unselect                => Activate/checks a field if it is a radiobutton/checkbox
//                                                    and doesn't toggle on/off like Libs.h.click does.
var valueFunctionHelper = {
  click: function($domNode) {
    $domNode.click();
  },
  screenshot: function(saveAs) {
    chrome.runtime.sendMessage({action: "takeScreenshot", value: state.currentRuleMetadata, flag: saveAs});
  },
  copyValue: function(selector) {
    selector = selector.replace(/"/g, "");
    // This needs explaining:
    // Since FoF looks for a function to execute (the typical value function) and Libs.h.copyValue("selector") must return one
    // we need to dynamically create a function here since the 'selector' parameter gets lost while serializing.
    // This creates a new function where SELECTOR is replaced by that variable so there is no parameter at all.
    //
    // return new Function(_copyValueFunction <-- Take the body of the dummy function
    // .toString()                            <-- As a string
    // .replace(/##SELECTOR##/g, selector)    <-- Replace the selector placeholder with it's real value
    // .replace(/^.*?[\n|{]/,"")              <-- Strip the anonymous function declaration in the first line (might not be newline sep. for builds)
    // .replace(/}$/,""));                    <-- Strip the last closing bracket
    /*eslint-disable no-new-func*/
    return new Function(_copyValueFunction.toString().replace(/##SELECTOR##/g, selector).replace(/^.*?[\n|{]/,"").replace(/}$/,""));
    /*eslint-enable no-new-func*/
  },
  displayMessage: Libs.setThrobberText,
  select: function($domNode) {
    $domNode.prop("checked", true);
  },
  unselect: function($domNode) {
    $domNode.prop("checked", false);
  }
};
Libs.add("h", valueFunctionHelper);

// Process control functions
var processFunctionsHalt = function(msg) {
  return function() {
    if (typeof state.lastActiveTab === "undefined") {
      return null;
    }

    if (typeof msg === "undefined") {
      msg = chrome.i18n.getMessage("lib_halt_canceled");
    }

    Libs.setThrobberText(msg);
    return null;
  };
};
Libs.add("halt", processFunctionsHalt);

// Import all saved libs
Libs.import();
<commit_msg>allow Libs.h.copyValue on non-input fields<commit_after>/*global Logger Rules Utils state */
/*eslint no-loop-func:0 */
var Libs = {
  add: function(libraryName, librayTopLevelFunction, forceAdd) {
    // Check if the method is already defined
    forceAdd = forceAdd || false;
    if (this.hasOwnProperty(libraryName) && !forceAdd) {
      Logger.info("[libs.js] Did not add '" + libraryName + "' to Libs, because it already exists as a function().");
      return;
    }
    this[libraryName] = librayTopLevelFunction;
    Logger.info("[libs.js] Added library as Libs." + libraryName);
  },
  import: function() {
    return new Promise(function (resolve) {
      Rules.all().then(function (rules) {
        rules.forEach(function (rule) {
          if (typeof rule.export !== "undefined" && typeof rule.lib === "function") {
            // Add the rule into the scope of all library functions
            Libs.add(rule.export, rule.lib, true);
          }
        });
        Logger.info("[libs.js] Current libraries : " + Object.keys(Libs).toString());
        resolve(true);
      });
    });
  },
  // Dectects libraries used in a rulecode string
  // returns an array of found libraries
  detectVendoredLibraries: function(ruleCodeString) {
    var detectedLibs = [];
    Object.keys(Utils.vendoredLibs).forEach(function dtctLib(vLibKey) {
      if (ruleCodeString.match(Utils.vendoredLibs[vLibKey].detectWith) !== null) {
        // Found!
        detectedLibs.push(vLibKey);
      }
    });
    return detectedLibs;
  },
  loadLibs: function(scriptPaths, whoCallsMe) {
    /*eslint-disable complexity */
    return new Promise(function (resolve) {
      if (typeof scriptPaths === "string") {
        scriptPaths = [scriptPaths];
      }

      // If there is no script to inject
      // OR we run in the context of the content page
      // resolve now.
      // The content page gets its libraries by using the chrome API (see background/form_util.js#injectAndAttachToLibs)
      if (scriptPaths.length === 0 || !Utils.isBgPage()) {
        resolve(0);
        return;
      }

      var anchor = document.querySelector("body");
      var fragment = document.createDocumentFragment();

      var loadedScriptCount = 0;
      var targetScriptCount = scriptPaths.length;
      var scriptPath;

      for (var i = 0; i < targetScriptCount; i++) {
        scriptPath = scriptPaths[i];

        var vLib = Utils.vendoredLibs[scriptPath];
        var libName = vLib.name;

        // If a lib with that name is already present, don't load it again
        if (typeof Libs[libName] !== "undefined") {
          loadedScriptCount++;
          Logger.info("[libs.js] Didn't load '" + scriptPath + "' again");
          continue;
        }

        // If the requested lbrary is not vendored, break loop
        if (typeof vLib === "undefined") {
          Logger.info("[libs.js] Didn't load '" + scriptPath + "' since it is not vendored (see utils.js)");
          loadedScriptCount++;
          continue;
        }

        // If the library is present on window (somehow) just add it to Libs (again)
        if (typeof window[vLib.onWindowName] !== "undefined" && typeof Libs[libName] === "undefined") {
          Libs.add(libName, window[vLib.onWindowName]);
          loadedScriptCount++;
          continue;
        }

        var script = document.createElement("script");
        script.async = false;
        script.dataset.who = whoCallsMe;
        script.className = "injectedByFormOFill";
        script.dataset.script = scriptPath;
        script.src = chrome.extension.getURL(scriptPath);
        script.onload = function() {
          // Add Library to Libs
          Libs.add(libName, window[vLib.onWindowName]);
          loadedScriptCount++;
          Logger.info("[libs.js] Loaded '" + scriptPath + "'");

          // If all script are loaded, resolve promise
          if (loadedScriptCount >= targetScriptCount) {
            resolve(loadedScriptCount);
          }
        };
        script.onerror = function() {
          resolve(loadedScriptCount);
        };

        // Since this is all async make sure nobody has already
        // inserted it while we worked on this script:
        if (document.querySelectorAll("script[data-script='" + scriptPath + "']").length === 0) {
          fragment.appendChild(script);
        }
      }

      // If the loop is ready and the count already matches,
      // there was nothing to to and that's ok
      if (loadedScriptCount >= targetScriptCount) {
        resolve(loadedScriptCount);
      }

      // Only insert the fragment if it has something inside
      if (fragment.childNodes.length > 0) {
        anchor.appendChild(fragment);
      }
    });
    /*eslint-enable complexity */
  },
  setThrobberText: function(text) {
    // Change the text of the throbber
    if (state.lastActiveTab === null) {
      return null;
    }

    // Since this is called from the background pages
    // we need to send a message to the content.js
    chrome.tabs.sendMessage(state.lastActiveTab.id, {action: "showOverlay", message: text});
    return null;
  }
};

// This is a function "dummy"
// It represents code to copy the content of one
// DOM node to another.
// The SELECTOR will be replaced and re-compiled later.
// see Libs.h.copyValue
var _copyValueFunction = function() {
  if (!Utils.isBgPage()) {
    var $source = document.querySelector("##SELECTOR##");
    if ($source === null) {
      // element not found
      Libs.setThrobberText(chrome.i18n.getMessage("lib_h_copyvalue_field_not_found", [ "##SELECTOR##" ]));
      return null;
    }

    // element found : input
    if (typeof $source.value !== "undefined") {
      return $source.value;
    }

    // Other elements: use innerText
    return $source.innerText;
  }
  return null;
};

// Helper for use in value functions
//
// "value" : Libs.h.click                          => Clicks on the element specified by 'selector'
//           Libs.h.screenshot("save_as_filename") => Save a screenshot of visible area as [filename]
//           Libs.h.copyValue("#selector")         => copies the *value* ofthe chosen field
//           Libs.h.displayMessage("Some text")    => shows a message to the user#
//           Libs.h.select/unselect                => Activate/checks a field if it is a radiobutton/checkbox
//                                                    and doesn't toggle on/off like Libs.h.click does.
var valueFunctionHelper = {
  click: function($domNode) {
    $domNode.click();
  },
  screenshot: function(saveAs) {
    chrome.runtime.sendMessage({action: "takeScreenshot", value: state.currentRuleMetadata, flag: saveAs});
  },
  copyValue: function(selector) {
    selector = selector.replace(/"/g, "");
    // This needs explaining:
    // Since FoF looks for a function to execute (the typical value function) and Libs.h.copyValue("selector") must return one
    // we need to dynamically create a function here since the 'selector' parameter gets lost while serializing.
    // This creates a new function where SELECTOR is replaced by that variable so there is no parameter at all.
    //
    // return new Function(_copyValueFunction <-- Take the body of the dummy function
    // .toString()                            <-- As a string
    // .replace(/##SELECTOR##/g, selector)    <-- Replace the selector placeholder with it's real value
    // .replace(/^.*?[\n|{]/,"")              <-- Strip the anonymous function declaration in the first line (might not be newline sep. for builds)
    // .replace(/}$/,""));                    <-- Strip the last closing bracket
    /*eslint-disable no-new-func*/
    return new Function(_copyValueFunction.toString().replace(/##SELECTOR##/g, selector).replace(/^.*?[\n|{]/,"").replace(/}$/,""));
    /*eslint-enable no-new-func*/
  },
  displayMessage: Libs.setThrobberText,
  select: function($domNode) {
    $domNode.prop("checked", true);
  },
  unselect: function($domNode) {
    $domNode.prop("checked", false);
  }
};
Libs.add("h", valueFunctionHelper);

// Process control functions
var processFunctionsHalt = function(msg) {
  return function() {
    if (typeof state.lastActiveTab === "undefined") {
      return null;
    }

    if (typeof msg === "undefined") {
      msg = chrome.i18n.getMessage("lib_halt_canceled");
    }

    Libs.setThrobberText(msg);
    return null;
  };
};
Libs.add("halt", processFunctionsHalt);

// Import all saved libs
Libs.import();
<|endoftext|>