<commit_before>var share = (function(){

	var shareProtected = {};

    var getOptions = function(requestedOptions) {
        var availableOptions = ["message", "subject", "files", "chooserTitle"];
        var options = {
            url: requestedOptions.url
        };
        availableOptions.forEach(function(availableOption) {
            if (availableOption in requestedOptions) {
                options[availableOption] = requestedOptions[availableOption];
            }
        });
        return options;
    };

	var shareWithChooser = function(requestedOptions, resolve, reject) {
        // this is the complete list of currently supported params you can pass to the plugin (all optional)
        //var fullOptions = {
        //    message: 'share this', // not supported on some apps (Facebook, Instagram)
        //    subject: 'the subject', // fi. for email
        //    files: ['', ''], // an array of filenames either locally or remotely
        //    url: 'https://www.website.com/foo/#bar?a=b',
        //    chooserTitle: 'Pick an app' // Android only, you can override the default share sheet title
        //};

        var options = getOptions(requestedOptions);

        var onSuccess = function(result) {
            log("[share] Share completed? " + result.completed); // On Android apps mostly return false even while it's true
            log("[share] Shared to app: " + result.app); // On Android result.app is currently empty. On iOS it's empty when sharing is cancelled (result.completed=false)

            resolve(result);
        };

        var onError = function(msg) {
            err("[share] Sharing failed with message: " + msg);

            reject(msg);
        };

        window.plugins.socialsharing.shareWithOptions(options, onSuccess, onError);
    };

    var shareWithFacebook = function(requestedOptions, resolve, reject) {
        var onSuccess = function(result) {
            log("[share] Facebook share completed, result: ", result);
            resolve(result);
        };

        var onError = function(msg) {
            err("[share] Facebook sharing failed with message: " + msg);
            reject(msg);
        };

        window.plugins.socialsharing.shareViaFacebook(
            "",
            null,
            requestedOptions.url,
            onSuccess,
            onError
        );
    };

    var shareWithTwitter = function(requestedOptions, resolve, reject) {
        var onSuccess = function(result) {
            log("[share] Twitter share completed, result: ", result);
            resolve(result);
        };

        var onError = function(msg) {
            err("[share] Twitter sharing failed with message: " + msg);
            reject(msg);
        };

        var message = "";
        if ("message" in requestedOptions) {
            message = requestedOptions.message;
        }
        window.plugins.socialsharing.shareViaTwitter(
            message,
            null,
            requestedOptions.url,
            onSuccess,
            onError
        );
    };
    var shareWithWhatsapp = function(requestedOptions, resolve, reject) {
        var onSuccess = function(result) {
            log("[share] Whatsapp share completed, result: ", result);
            resolve(result);
        };

        var onError = function(msg) {
            err("[share] Whatsapp sharing failed with message: " + msg);
            reject(msg);
        };

        var message = "";
        if ("message" in requestedOptions) {
            message = requestedOptions.message;
        }

        window.plugins.socialsharing.shareViaWhatsApp(
            message,
            null,
            requestedOptions.url,
            onSuccess,
            onError
        );
    };

    shareProtected.canShareVia = function(via, url) {

        return new Promise(function(resolve){

            // canShareVia:
            //   via, message, subject, fileOrFileArray, url, successCallback, errorCallback
            window.plugins.socialsharing.canShareVia(
                via,
                null,
                null,
                null,
                url,
                function(e){
                    log("[share] canShareVia "+via+" result true: ", e);
                    resolve({
                        "network": via,
                        "available": true
                    });
                },
                function(e){
                    log("[share] canShareVia "+via+" result false: ", e);
                    resolve({
                        "network": via,
                        "available": false
                    });
                }
            );
        });
    };

	shareProtected.socialShare = function(options, resolve, reject) {

		if (typeof options !== 'object') {
            options = {};
			war("[share] parameter options must be object!");
		}

        if (!options.type) {
            options.type = "chooser";
        }

        if (!window.plugins || !window.plugins.socialsharing) {

			// plugin is not installed
            err("[share] missing cordova plugin");
			return reject("missing cordova plugin");
		}

        if (!options.url) {
            err("[share] missing parameter url");
            return reject("missing parameter url");
        }

        if (options.type == "chooser") {
            return shareWithChooser(options, resolve, reject);
        }

        if (options.type == "facebook") {
            return shareWithFacebook(options, resolve, reject);
        }

        if (options.type == "twitter") {
            return shareWithTwitter(options, resolve, reject);
        }

        if (options.type == "whatsapp") {
            return shareWithWhatsapp(options, resolve, reject);
        }

        err("[share] type not valid");
        return reject("type not valid");

	};

    return shareProtected;
})();

/**
 * @name Stargate#socialShare
 * @memberof Stargate
 *
 * @description share an url on a social network
 *
 * @param {object} options
 */
stargatePublic.socialShare = function(options) {

    if (!isStargateInitialized) {
        return Promise.reject("Stargate not initialized, call Stargate.initialize first!");
    }
    if (!isStargateOpen) {
        return Promise.reject("Stargate closed, wait for Stargate.initialize to complete!");
    }

    var result = new Promise(function(resolve,reject){

        share.socialShare(options, resolve, reject);
    });

    return result;
};

/**
 * @name Stargate#socialShareAvailable
 * @memberof Stargate
 *
 * @description return a promise with an array of available social networks
 *
 * @param {object} options
 * @param {Array} options.socials - list of social network to check
 *
 */
stargatePublic.socialShareAvailable = function(options) {

    if (!isStargateInitialized) {
        return Promise.reject("Stargate not initialized, call Stargate.initialize first!");
    }
    if (!isStargateOpen) {
        return Promise.reject("Stargate closed, wait for Stargate.initialize to complete!");
    }

    if (!window.plugins || !window.plugins.socialsharing) {
        // plugin is not installed
        err("[share] missing cordova plugin");
        return Promise.reject("missing cordova plugin");
    }

    if (!options.socials || options.socials.constructor !== Array) {
        err("[share] missing array parameter socials");
        return Promise.reject("missing array parameter socials");
    }

    if (!options.url) {
        err("[share] missing parameter url");
        return Promise.reject("missing parameter url");
    }

    var result = new Promise(function(resolve,reject){

        var socialsAvailabilityPromises = [];

        var knownSocialNetworks = [
            "facebook",
            "whatsapp",
            "twitter",
            "instagram"
        ];
        knownSocialNetworks.forEach(function(element) {
            // check only requested networks
            if (element in options.socials) {

                socialsAvailabilityPromises.push(

                    share.canShareVia(element, options.url)
                );
            }
        });

        Promise.all(socialsAvailabilityPromises).then(function(values) {

            var availableNetworks = [];
            // values is like:
            //  [{"network": "facebook", "available": false},
            //   {"network": "twitter", "available": false}]
            values.forEach(function(element) {
                if (element.available) {
                    availableNetworks.push(element.network);
                }
            });

            resolve(availableNetworks);

        }, function(reason) {

            err(reason);
            reject(reason);
        });
    });

    return result;
};
<commit_msg>proper chack of parameter sent<commit_after>var share = (function(){

	var shareProtected = {};

    var getOptions = function(requestedOptions) {
        var availableOptions = ["message", "subject", "files", "chooserTitle"];
        var options = {
            url: requestedOptions.url
        };
        availableOptions.forEach(function(availableOption) {
            if (availableOption in requestedOptions) {
                options[availableOption] = requestedOptions[availableOption];
            }
        });
        return options;
    };

	var shareWithChooser = function(requestedOptions, resolve, reject) {
        // this is the complete list of currently supported params you can pass to the plugin (all optional)
        //var fullOptions = {
        //    message: 'share this', // not supported on some apps (Facebook, Instagram)
        //    subject: 'the subject', // fi. for email
        //    files: ['', ''], // an array of filenames either locally or remotely
        //    url: 'https://www.website.com/foo/#bar?a=b',
        //    chooserTitle: 'Pick an app' // Android only, you can override the default share sheet title
        //};

        var options = getOptions(requestedOptions);

        var onSuccess = function(result) {
            log("[share] Share completed? " + result.completed); // On Android apps mostly return false even while it's true
            log("[share] Shared to app: " + result.app); // On Android result.app is currently empty. On iOS it's empty when sharing is cancelled (result.completed=false)

            resolve(result);
        };

        var onError = function(msg) {
            err("[share] Sharing failed with message: " + msg);

            reject(msg);
        };

        window.plugins.socialsharing.shareWithOptions(options, onSuccess, onError);
    };

    var shareWithFacebook = function(requestedOptions, resolve, reject) {
        var onSuccess = function(result) {
            log("[share] Facebook share completed, result: ", result);
            resolve(result);
        };

        var onError = function(msg) {
            err("[share] Facebook sharing failed with message: " + msg);
            reject(msg);
        };

        window.plugins.socialsharing.shareViaFacebook(
            "",
            null,
            requestedOptions.url,
            onSuccess,
            onError
        );
    };

    var shareWithTwitter = function(requestedOptions, resolve, reject) {
        var onSuccess = function(result) {
            log("[share] Twitter share completed, result: ", result);
            resolve(result);
        };

        var onError = function(msg) {
            err("[share] Twitter sharing failed with message: " + msg);
            reject(msg);
        };

        var message = "";
        if ("message" in requestedOptions) {
            message = requestedOptions.message;
        }
        window.plugins.socialsharing.shareViaTwitter(
            message,
            null,
            requestedOptions.url,
            onSuccess,
            onError
        );
    };
    var shareWithWhatsapp = function(requestedOptions, resolve, reject) {
        var onSuccess = function(result) {
            log("[share] Whatsapp share completed, result: ", result);
            resolve(result);
        };

        var onError = function(msg) {
            err("[share] Whatsapp sharing failed with message: " + msg);
            reject(msg);
        };

        var message = "";
        if ("message" in requestedOptions) {
            message = requestedOptions.message;
        }

        window.plugins.socialsharing.shareViaWhatsApp(
            message,
            null,
            requestedOptions.url,
            onSuccess,
            onError
        );
    };

    shareProtected.canShareVia = function(via, url) {

        return new Promise(function(resolve){

            // canShareVia:
            //   via, message, subject, fileOrFileArray, url, successCallback, errorCallback
            window.plugins.socialsharing.canShareVia(
                via,
                null,
                null,
                null,
                url,
                function(e){
                    log("[share] canShareVia "+via+" result true: ", e);
                    resolve({
                        "network": via,
                        "available": true
                    });
                },
                function(e){
                    log("[share] canShareVia "+via+" result false: ", e);
                    resolve({
                        "network": via,
                        "available": false
                    });
                }
            );
        });
    };

	shareProtected.socialShare = function(options, resolve, reject) {

		if (typeof options !== 'object') {
            options = {};
			war("[share] parameter options must be object!");
		}

        if (!options.type) {
            options.type = "chooser";
        }

        if (!window.plugins || !window.plugins.socialsharing) {

			// plugin is not installed
            err("[share] missing cordova plugin");
			return reject("missing cordova plugin");
		}

        if (!options.url) {
            err("[share] missing parameter url");
            return reject("missing parameter url");
        }

        if (options.type == "chooser") {
            return shareWithChooser(options, resolve, reject);
        }

        if (options.type == "facebook") {
            return shareWithFacebook(options, resolve, reject);
        }

        if (options.type == "twitter") {
            return shareWithTwitter(options, resolve, reject);
        }

        if (options.type == "whatsapp") {
            return shareWithWhatsapp(options, resolve, reject);
        }

        err("[share] type not valid");
        return reject("type not valid");

	};

    return shareProtected;
})();

/**
 * @name Stargate#socialShare
 * @memberof Stargate
 *
 * @description share an url on a social network
 *
 * @param {object} options
 */
stargatePublic.socialShare = function(options) {

    if (!isStargateInitialized) {
        return Promise.reject("Stargate not initialized, call Stargate.initialize first!");
    }
    if (!isStargateOpen) {
        return Promise.reject("Stargate closed, wait for Stargate.initialize to complete!");
    }

    var result = new Promise(function(resolve,reject){

        share.socialShare(options, resolve, reject);
    });

    return result;
};

/**
 * @name Stargate#socialShareAvailable
 * @memberof Stargate
 *
 * @description return a promise with an array of available social networks
 *
 * @param {object} options
 * @param {Array} options.socials - list of social network to check
 *
 */
stargatePublic.socialShareAvailable = function(options) {

    if (!isStargateInitialized) {
        return Promise.reject("Stargate not initialized, call Stargate.initialize first!");
    }
    if (!isStargateOpen) {
        return Promise.reject("Stargate closed, wait for Stargate.initialize to complete!");
    }

    if (!window.plugins || !window.plugins.socialsharing) {
        // plugin is not installed
        err("[share] missing cordova plugin");
        return Promise.reject("missing cordova plugin");
    }

    if (!options.socials || options.socials.constructor !== Array) {
        err("[share] missing array parameter socials");
        return Promise.reject("missing array parameter socials");
    }

    if (!options.url) {
        err("[share] missing parameter url");
        return Promise.reject("missing parameter url");
    }

    var result = new Promise(function(resolve,reject){

        var socialsAvailabilityPromises = [];

        var knownSocialNetworks = [
            "facebook",
            "whatsapp",
            "twitter",
            "instagram"
        ];
        knownSocialNetworks.forEach(function(element) {
            // check only requested networks

            if (options.socials.indexOf(element) !== -1) {

                socialsAvailabilityPromises.push(

                    share.canShareVia(element, options.url)
                );

            }
        });

        Promise.all(socialsAvailabilityPromises).then(function(values) {

            var availableNetworks = [];
            // values is like:
            //  [{"network": "facebook", "available": false},
            //   {"network": "twitter", "available": false}]
            values.forEach(function(element) {
                if (element.available) {
                    availableNetworks.push(element.network);
                }
                //log("element: ", element);
            });
            //log("values: ", values);
            //log("availableNetworks: ", availableNetworks);
            resolve(availableNetworks);

        }, function(reason) {

            err(reason);
            reject(reason);
        });
    });

    return result;
};
<|endoftext|>


<commit_before>// Required for Meteor package, the use of window prevents export by Meteor
(function(window){
  if(window.Package){
    M = {};
  } else {
    window.M = {};
  }

  // Check for jQuery
  M.jQueryLoaded = !!window.jQuery;
})(window);

// AMD
if ( typeof define === "function" && define.amd ) {
	define( "M", [], function() {
		return M;
	} );
}

// CommonJS
if (typeof exports !== 'undefined' && !exports.nodeType) {
  if (typeof module !== 'undefined' && !module.nodeType && module.exports) {
    exports = module.exports = M;
  }
  exports.default = M;
}

/*
 * raf.js
 * https://github.com/ngryman/raf.js
 *
 * original requestAnimationFrame polyfill by Erik Möller
 * inspired from paul_irish gist and post
 *
 * Copyright (c) 2013 ngryman
 * Licensed under the MIT license.
 */
(function(window) {
  let lastTime = 0,
    vendors = ['webkit', 'moz'],
    requestAnimationFrame = window.requestAnimationFrame,
    cancelAnimationFrame = window.cancelAnimationFrame,
    i = vendors.length;

  // try to un-prefix existing raf
  while (--i >= 0 && !requestAnimationFrame) {
    requestAnimationFrame = window[vendors[i] + 'RequestAnimationFrame'];
    cancelAnimationFrame = window[vendors[i] + 'CancelRequestAnimationFrame'];
  }

  // polyfill with setTimeout fallback
  // heavily inspired from @darius gist mod: https://gist.github.com/paulirish/1579671#comment-837945
  if (!requestAnimationFrame || !cancelAnimationFrame) {
    requestAnimationFrame = function(callback) {
      let now = +Date.now(),
        nextTime = Math.max(lastTime + 16, now);
      return setTimeout(function() {
        callback(lastTime = nextTime);
      }, nextTime - now);
    };

    cancelAnimationFrame = clearTimeout;
  }

  // export to window
  window.requestAnimationFrame = requestAnimationFrame;
  window.cancelAnimationFrame = cancelAnimationFrame;
}(window));

M.keys = {
  TAB: 9,
  ENTER: 13,
  ESC: 27,
  ARROW_UP: 38,
  ARROW_DOWN: 40
};

/**
 * Initialize jQuery wrapper for plugin
 * @param {Class} plugin  javascript class
 * @param {string} pluginName  jQuery plugin name
 * @param {string} classRef  Class reference name
 */
M.initializeJqueryWrapper = function(plugin, pluginName, classRef) {
  jQuery.fn[pluginName] = function(methodOrOptions) {
    // Call plugin method if valid method name is passed in
    if (plugin.prototype[methodOrOptions]) {
      let params = Array.prototype.slice.call( arguments, 1 );

      // Getter methods
      if (methodOrOptions.slice(0,3) === 'get') {
        let instance = this.first()[0][classRef];
        return instance[methodOrOptions].apply(instance, params);

      // Void methods
      } else {
        return this.each(function() {
          let instance = this[classRef];
          instance[methodOrOptions].apply(instance, params);
        });
      }

    // Initialize plugin if options or no argument is passed in
    } else if ( typeof methodOrOptions === 'object' || ! methodOrOptions ) {
      plugin.init(this, arguments[0]);
      return this;

    // Return error if an unrecognized  method name is passed in
    } else {
      jQuery.error(`Method ${methodOrOptions} does not exist on jQuery.${pluginName}`);
    }
  };
};

/**
 * Generate approximated selector string for a jQuery object
 * @param {jQuery} obj  jQuery object to be parsed
 * @returns {string}
 */
M.objectSelectorString = function(obj) {
  let tagStr = obj.prop('tagName') || '';
  let idStr = obj.attr('id') || '';
  let classStr = obj.attr('class') || '';
  return (tagStr + idStr + classStr).replace(/\s/g,'');
};

// Unique Random ID
M.guid = (function() {
  function s4() {
    return Math.floor((1 + Math.random()) * 0x10000)
      .toString(16)
      .substring(1);
  }
  return function() {
    return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
           s4() + '-' + s4() + s4() + s4();
  };
})();

/**
 * Escapes hash from special characters
 * @param {string} hash  String returned from this.hash
 * @returns {string}
 */
M.escapeHash = function(hash) {
  return hash.replace( /(:|\.|\[|\]|,|=)/g, "\\$1" );
};

M.elementOrParentIsFixed = function(element) {
  let $element = $(element);
  let $checkElements = $element.add($element.parents());
  let isFixed = false;
  $checkElements.each(function(){
    if ($(this).css("position") === "fixed") {
      isFixed = true;
      return false;
    }
  });
  return isFixed;
};

/**
 * @typedef {Object} Edges
 * @property {Boolean} top  If the top edge was exceeded
 * @property {Boolean} right  If the right edge was exceeded
 * @property {Boolean} bottom  If the bottom edge was exceeded
 * @property {Boolean} left  If the left edge was exceeded
 */

/**
 * @typedef {Object} Bounding
 * @property {Number} left  left offset coordinate
 * @property {Number} top  top offset coordinate
 * @property {Number} width
 * @property {Number} height
 */

/**
 * Escapes hash from special characters
 * @param {Element} container  Container element that acts as the boundary
 * @param {Bounding} bounding  element bounding that is being checked
 * @param {Number} offset  offset from edge that counts as exceeding
 * @returns {Edges}
 */
M.checkWithinContainer = function(container, bounding, offset) {
  let edges = {
    top: false,
    right: false,
    bottom: false,
    left: false
  };

  let containerRect = container.getBoundingClientRect();

  let scrollLeft = container.scrollLeft;
  let scrollTop = container.scrollTop;

  let scrolledX = bounding.left - scrollLeft;
  let scrolledY = bounding.top - scrollTop;

  // Check for container and viewport for each edge
  if (scrolledX < containerRect.left + offset ||
      scrolledX < offset) {
    edges.left = true;
  }

  if (scrolledX + bounding.width > containerRect.right - offset ||
      scrolledX + bounding.width > window.innerWidth - offset) {
    edges.right = true;
  }

  if (scrolledY < containerRect.top + offset ||
      scrolledY < offset) {
    edges.top = true;
  }

  if (scrolledY + bounding.height > containerRect.bottom - offset ||
      scrolledY + bounding.height > window.innerHeight - offset) {
    edges.bottom = true;
  }

  return edges;
};

M.checkPossibleAlignments = function(el, container, bounding, offset) {
  let canAlign = {
    top: true,
    right: true,
    bottom: true,
    left: true,
    spaceOnTop: null,
    spaceOnRight: null,
    spaceOnBottom: null,
    spaceOnLeft: null
  };

  let containerAllowsOverflow = getComputedStyle(container).overflow === 'visible';
  let containerRect = container.getBoundingClientRect();
  let elOffsetRect = el.getBoundingClientRect();

  let scrollLeft = container.scrollLeft;
  let scrollTop = container.scrollTop;

  let scrolledX = bounding.left - scrollLeft;
  let scrolledY = bounding.top - scrollTop;

  // Check for container and viewport for left
  canAlign.spaceOnRight = !containerAllowsOverflow ? container.offsetWidth - (scrolledX + bounding.width) :
    window.innerWidth - (elOffsetRect.left + bounding.width);
  if ((!containerAllowsOverflow && scrolledX + bounding.width > container.offsetWidth) ||
      containerAllowsOverflow && (elOffsetRect.left + bounding.width > window.innerWidth)) {
    canAlign.left = false;
  }

  // Check for container and viewport for Right
  canAlign.spaceOnLeft = !containerAllowsOverflow ? scrolledX - bounding.width + elOffsetRect.width :
    elOffsetRect.right - bounding.width;
  if ((!containerAllowsOverflow && scrolledX - bounding.width + elOffsetRect.width < 0) ||
      containerAllowsOverflow && (elOffsetRect.right - bounding.width < 0)) {
    canAlign.right = false;
  }

  // Check for container and viewport for Top
  canAlign.spaceOnBottom = !containerAllowsOverflow ? containerRect.height - (scrolledY + bounding.height + offset) :
    window.innerHeight - (elOffsetRect.top + bounding.height + offset);
  if ((!containerAllowsOverflow && scrolledY + bounding.height + offset > containerRect.height) ||
      containerAllowsOverflow && (elOffsetRect.top + bounding.height + offset > window.innerHeight)) {
    canAlign.top = false;
  }

  // Check for container and viewport for Bottom
  canAlign.spaceOnTop = !containerAllowsOverflow ? scrolledY - (bounding.height + offset) :
    elOffsetRect.bottom - (bounding.height + offset);
  if ((!containerAllowsOverflow && scrolledY - bounding.height - offset < 0) ||
      containerAllowsOverflow && (elOffsetRect.bottom - bounding.height - offset < 0)) {
    canAlign.bottom = false;
  }

  return canAlign;
};

M.getOverflowParent = function(element) {
  if (element == null) {
    return null;
  }

  if (element === document.body || getComputedStyle(element).overflow !== 'visible') {
    return element;
  } else {
    return M.getOverflowParent(element.parentElement);
  }
};

/**
 * Gets id of component from a trigger
 * @param {Element} trigger  trigger
 * @returns {string}
 */
M.getIdFromTrigger = function(trigger) {
  let id = trigger.getAttribute('data-target');
  if (!id) {
    id = trigger.getAttribute('href');
    if (id) {
      id = id.slice(1);
    } else {
      id = "";
    }
  }
  return id;
};

/**
 * Multi browser support for document scroll top
 * @returns {Number}
 */
M.getDocumentScrollTop = function() {
  return window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
};

/**
 * Multi browser support for document scroll left
 * @returns {Number}
 */
M.getDocumentScrollLeft = function() {
  return window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0;
};

/**
 * @typedef {Object} Edges
 * @property {Boolean} top  If the top edge was exceeded
 * @property {Boolean} right  If the right edge was exceeded
 * @property {Boolean} bottom  If the bottom edge was exceeded
 * @property {Boolean} left  If the left edge was exceeded
 */

/**
 * @typedef {Object} Bounding
 * @property {Number} left  left offset coordinate
 * @property {Number} top  top offset coordinate
 * @property {Number} width
 * @property {Number} height
 */

/**
 * Get time in ms
 * @license https://raw.github.com/jashkenas/underscore/master/LICENSE
 * @type {function}
 * @return {number}
 */
let getTime = (Date.now || function () {
  return new Date().getTime();
});

/**
 * Returns a function, that, when invoked, will only be triggered at most once
 * during a given window of time. Normally, the throttled function will run
 * as much as it can, without ever going more than once per `wait` duration;
 * but if you'd like to disable the execution on the leading edge, pass
 * `{leading: false}`. To disable execution on the trailing edge, ditto.
 * @license https://raw.github.com/jashkenas/underscore/master/LICENSE
 * @param {function} func
 * @param {number} wait
 * @param {Object=} options
 * @returns {Function}
 */
M.throttle = function(func, wait, options) {
  let context, args, result;
  let timeout = null;
  let previous = 0;
  options || (options = {});
  let later = function () {
    previous = options.leading === false ? 0 : getTime();
    timeout = null;
    result = func.apply(context, args);
    context = args = null;
  };
  return function () {
    let now = getTime();
    if (!previous && options.leading === false) previous = now;
    let remaining = wait - (now - previous);
    context = this;
    args = arguments;
    if (remaining <= 0) {
      clearTimeout(timeout);
      timeout = null;
      previous = now;
      result = func.apply(context, args);
      context = args = null;
    } else if (!timeout && options.trailing !== false) {
      timeout = setTimeout(later, remaining);
    }
    return result;
  };
};

// Velocity has conflicts when loaded with jQuery, this will check for it
// First, check if in noConflict mode
let Vel;
if (M.jQueryLoaded) {
  Vel = jQuery.Velocity;
} else {
  Vel = Velocity;
}

if (Vel) {
  M.Vel = Vel;
} else {
  M.Vel = Velocity;
}
<commit_msg>put commonjs module loading under else if to amd<commit_after>// Required for Meteor package, the use of window prevents export by Meteor
(function(window){
  if(window.Package){
    M = {};
  } else {
    window.M = {};
  }

  // Check for jQuery
  M.jQueryLoaded = !!window.jQuery;
})(window);

// AMD
if ( typeof define === "function" && define.amd ) {
	define( "M", [], function() {
		return M;
	} );

// Common JS
} else if (typeof exports !== 'undefined' && !exports.nodeType) {
  if (typeof module !== 'undefined' && !module.nodeType && module.exports) {
    exports = module.exports = M;
  }
  exports.default = M;
}

/*
 * raf.js
 * https://github.com/ngryman/raf.js
 *
 * original requestAnimationFrame polyfill by Erik Möller
 * inspired from paul_irish gist and post
 *
 * Copyright (c) 2013 ngryman
 * Licensed under the MIT license.
 */
(function(window) {
  let lastTime = 0,
    vendors = ['webkit', 'moz'],
    requestAnimationFrame = window.requestAnimationFrame,
    cancelAnimationFrame = window.cancelAnimationFrame,
    i = vendors.length;

  // try to un-prefix existing raf
  while (--i >= 0 && !requestAnimationFrame) {
    requestAnimationFrame = window[vendors[i] + 'RequestAnimationFrame'];
    cancelAnimationFrame = window[vendors[i] + 'CancelRequestAnimationFrame'];
  }

  // polyfill with setTimeout fallback
  // heavily inspired from @darius gist mod: https://gist.github.com/paulirish/1579671#comment-837945
  if (!requestAnimationFrame || !cancelAnimationFrame) {
    requestAnimationFrame = function(callback) {
      let now = +Date.now(),
        nextTime = Math.max(lastTime + 16, now);
      return setTimeout(function() {
        callback(lastTime = nextTime);
      }, nextTime - now);
    };

    cancelAnimationFrame = clearTimeout;
  }

  // export to window
  window.requestAnimationFrame = requestAnimationFrame;
  window.cancelAnimationFrame = cancelAnimationFrame;
}(window));

M.keys = {
  TAB: 9,
  ENTER: 13,
  ESC: 27,
  ARROW_UP: 38,
  ARROW_DOWN: 40
};

/**
 * Initialize jQuery wrapper for plugin
 * @param {Class} plugin  javascript class
 * @param {string} pluginName  jQuery plugin name
 * @param {string} classRef  Class reference name
 */
M.initializeJqueryWrapper = function(plugin, pluginName, classRef) {
  jQuery.fn[pluginName] = function(methodOrOptions) {
    // Call plugin method if valid method name is passed in
    if (plugin.prototype[methodOrOptions]) {
      let params = Array.prototype.slice.call( arguments, 1 );

      // Getter methods
      if (methodOrOptions.slice(0,3) === 'get') {
        let instance = this.first()[0][classRef];
        return instance[methodOrOptions].apply(instance, params);

      // Void methods
      } else {
        return this.each(function() {
          let instance = this[classRef];
          instance[methodOrOptions].apply(instance, params);
        });
      }

    // Initialize plugin if options or no argument is passed in
    } else if ( typeof methodOrOptions === 'object' || ! methodOrOptions ) {
      plugin.init(this, arguments[0]);
      return this;

    // Return error if an unrecognized  method name is passed in
    } else {
      jQuery.error(`Method ${methodOrOptions} does not exist on jQuery.${pluginName}`);
    }
  };
};

/**
 * Generate approximated selector string for a jQuery object
 * @param {jQuery} obj  jQuery object to be parsed
 * @returns {string}
 */
M.objectSelectorString = function(obj) {
  let tagStr = obj.prop('tagName') || '';
  let idStr = obj.attr('id') || '';
  let classStr = obj.attr('class') || '';
  return (tagStr + idStr + classStr).replace(/\s/g,'');
};

// Unique Random ID
M.guid = (function() {
  function s4() {
    return Math.floor((1 + Math.random()) * 0x10000)
      .toString(16)
      .substring(1);
  }
  return function() {
    return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
           s4() + '-' + s4() + s4() + s4();
  };
})();

/**
 * Escapes hash from special characters
 * @param {string} hash  String returned from this.hash
 * @returns {string}
 */
M.escapeHash = function(hash) {
  return hash.replace( /(:|\.|\[|\]|,|=)/g, "\\$1" );
};

M.elementOrParentIsFixed = function(element) {
  let $element = $(element);
  let $checkElements = $element.add($element.parents());
  let isFixed = false;
  $checkElements.each(function(){
    if ($(this).css("position") === "fixed") {
      isFixed = true;
      return false;
    }
  });
  return isFixed;
};

/**
 * @typedef {Object} Edges
 * @property {Boolean} top  If the top edge was exceeded
 * @property {Boolean} right  If the right edge was exceeded
 * @property {Boolean} bottom  If the bottom edge was exceeded
 * @property {Boolean} left  If the left edge was exceeded
 */

/**
 * @typedef {Object} Bounding
 * @property {Number} left  left offset coordinate
 * @property {Number} top  top offset coordinate
 * @property {Number} width
 * @property {Number} height
 */

/**
 * Escapes hash from special characters
 * @param {Element} container  Container element that acts as the boundary
 * @param {Bounding} bounding  element bounding that is being checked
 * @param {Number} offset  offset from edge that counts as exceeding
 * @returns {Edges}
 */
M.checkWithinContainer = function(container, bounding, offset) {
  let edges = {
    top: false,
    right: false,
    bottom: false,
    left: false
  };

  let containerRect = container.getBoundingClientRect();

  let scrollLeft = container.scrollLeft;
  let scrollTop = container.scrollTop;

  let scrolledX = bounding.left - scrollLeft;
  let scrolledY = bounding.top - scrollTop;

  // Check for container and viewport for each edge
  if (scrolledX < containerRect.left + offset ||
      scrolledX < offset) {
    edges.left = true;
  }

  if (scrolledX + bounding.width > containerRect.right - offset ||
      scrolledX + bounding.width > window.innerWidth - offset) {
    edges.right = true;
  }

  if (scrolledY < containerRect.top + offset ||
      scrolledY < offset) {
    edges.top = true;
  }

  if (scrolledY + bounding.height > containerRect.bottom - offset ||
      scrolledY + bounding.height > window.innerHeight - offset) {
    edges.bottom = true;
  }

  return edges;
};

M.checkPossibleAlignments = function(el, container, bounding, offset) {
  let canAlign = {
    top: true,
    right: true,
    bottom: true,
    left: true,
    spaceOnTop: null,
    spaceOnRight: null,
    spaceOnBottom: null,
    spaceOnLeft: null
  };

  let containerAllowsOverflow = getComputedStyle(container).overflow === 'visible';
  let containerRect = container.getBoundingClientRect();
  let elOffsetRect = el.getBoundingClientRect();

  let scrollLeft = container.scrollLeft;
  let scrollTop = container.scrollTop;

  let scrolledX = bounding.left - scrollLeft;
  let scrolledY = bounding.top - scrollTop;

  // Check for container and viewport for left
  canAlign.spaceOnRight = !containerAllowsOverflow ? container.offsetWidth - (scrolledX + bounding.width) :
    window.innerWidth - (elOffsetRect.left + bounding.width);
  if ((!containerAllowsOverflow && scrolledX + bounding.width > container.offsetWidth) ||
      containerAllowsOverflow && (elOffsetRect.left + bounding.width > window.innerWidth)) {
    canAlign.left = false;
  }

  // Check for container and viewport for Right
  canAlign.spaceOnLeft = !containerAllowsOverflow ? scrolledX - bounding.width + elOffsetRect.width :
    elOffsetRect.right - bounding.width;
  if ((!containerAllowsOverflow && scrolledX - bounding.width + elOffsetRect.width < 0) ||
      containerAllowsOverflow && (elOffsetRect.right - bounding.width < 0)) {
    canAlign.right = false;
  }

  // Check for container and viewport for Top
  canAlign.spaceOnBottom = !containerAllowsOverflow ? containerRect.height - (scrolledY + bounding.height + offset) :
    window.innerHeight - (elOffsetRect.top + bounding.height + offset);
  if ((!containerAllowsOverflow && scrolledY + bounding.height + offset > containerRect.height) ||
      containerAllowsOverflow && (elOffsetRect.top + bounding.height + offset > window.innerHeight)) {
    canAlign.top = false;
  }

  // Check for container and viewport for Bottom
  canAlign.spaceOnTop = !containerAllowsOverflow ? scrolledY - (bounding.height + offset) :
    elOffsetRect.bottom - (bounding.height + offset);
  if ((!containerAllowsOverflow && scrolledY - bounding.height - offset < 0) ||
      containerAllowsOverflow && (elOffsetRect.bottom - bounding.height - offset < 0)) {
    canAlign.bottom = false;
  }

  return canAlign;
};

M.getOverflowParent = function(element) {
  if (element == null) {
    return null;
  }

  if (element === document.body || getComputedStyle(element).overflow !== 'visible') {
    return element;
  } else {
    return M.getOverflowParent(element.parentElement);
  }
};

/**
 * Gets id of component from a trigger
 * @param {Element} trigger  trigger
 * @returns {string}
 */
M.getIdFromTrigger = function(trigger) {
  let id = trigger.getAttribute('data-target');
  if (!id) {
    id = trigger.getAttribute('href');
    if (id) {
      id = id.slice(1);
    } else {
      id = "";
    }
  }
  return id;
};

/**
 * Multi browser support for document scroll top
 * @returns {Number}
 */
M.getDocumentScrollTop = function() {
  return window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
};

/**
 * Multi browser support for document scroll left
 * @returns {Number}
 */
M.getDocumentScrollLeft = function() {
  return window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0;
};

/**
 * @typedef {Object} Edges
 * @property {Boolean} top  If the top edge was exceeded
 * @property {Boolean} right  If the right edge was exceeded
 * @property {Boolean} bottom  If the bottom edge was exceeded
 * @property {Boolean} left  If the left edge was exceeded
 */

/**
 * @typedef {Object} Bounding
 * @property {Number} left  left offset coordinate
 * @property {Number} top  top offset coordinate
 * @property {Number} width
 * @property {Number} height
 */

/**
 * Get time in ms
 * @license https://raw.github.com/jashkenas/underscore/master/LICENSE
 * @type {function}
 * @return {number}
 */
let getTime = (Date.now || function () {
  return new Date().getTime();
});

/**
 * Returns a function, that, when invoked, will only be triggered at most once
 * during a given window of time. Normally, the throttled function will run
 * as much as it can, without ever going more than once per `wait` duration;
 * but if you'd like to disable the execution on the leading edge, pass
 * `{leading: false}`. To disable execution on the trailing edge, ditto.
 * @license https://raw.github.com/jashkenas/underscore/master/LICENSE
 * @param {function} func
 * @param {number} wait
 * @param {Object=} options
 * @returns {Function}
 */
M.throttle = function(func, wait, options) {
  let context, args, result;
  let timeout = null;
  let previous = 0;
  options || (options = {});
  let later = function () {
    previous = options.leading === false ? 0 : getTime();
    timeout = null;
    result = func.apply(context, args);
    context = args = null;
  };
  return function () {
    let now = getTime();
    if (!previous && options.leading === false) previous = now;
    let remaining = wait - (now - previous);
    context = this;
    args = arguments;
    if (remaining <= 0) {
      clearTimeout(timeout);
      timeout = null;
      previous = now;
      result = func.apply(context, args);
      context = args = null;
    } else if (!timeout && options.trailing !== false) {
      timeout = setTimeout(later, remaining);
    }
    return result;
  };
};

// Velocity has conflicts when loaded with jQuery, this will check for it
// First, check if in noConflict mode
let Vel;
if (M.jQueryLoaded) {
  Vel = jQuery.Velocity;
} else {
  Vel = Velocity;
}

if (Vel) {
  M.Vel = Vel;
} else {
  M.Vel = Velocity;
}
<|endoftext|>


<commit_before>// Gruntfile.js
module.exports = function(grunt) {
  require('time-grunt')(grunt);
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    watch: {
      sass: {
        files: 'scss/**/*.scss',
        tasks: ['build']
      },
      eslint: {
        files: 'js/**/*.js',
        tasks: ['eslint']
      },
      docs: {
        files: ['docs/**/*.*', 'scss/**/**/*.scss'],
        tasks: ['docs']
      }
    },
    clean: {
      grunticon: ['dist/assets/icons/ui', 'dist/assets/icons/thematic'],
      svg: ['icons/ui/dist']
    },
    pure_grids: {
      responsive: {
        dest: 'scss/modules/_grid-units.scss',
        options: {
          units: [5,12,24],
          includeOldIEWidths: false,
          mediaQueries: {
            sm: 'screen and (min-width: 36.5em)', //584px
            md: 'screen and (min-width: 48em)', //768px
            lg: 'screen and (min-width: 64em)', //1024px
            xl: 'screen and (min-width: 80em)'  //1200px
          },
          selectorPrefix: '.wfp-u-'
        }
      }
    },
    sass: {
      options: {
        outputStyle: 'expanded',
        sourceMap: true,
        indentedSyntax: true,
        sassDir: 'scss',
        cssDir: 'dist/css'
      },
      dev: {
        files: [{
          expand: true,
          cwd: 'scss/',
          src: ['*.scss'],
          dest: 'dist/css/',
          ext: '.css'
        }]
      },
      dist: {
        options: {
          sourceMap: false
        },
        files: [{
          expand: true,
          cwd: 'scss/',
          src: ['*.scss'],
          dest: 'dist/css/',
          ext: '.css'
        }]
      },
      docs: {
        files: {
          'docs/css/main.css': 'docs/_sass/main.scss'
        }
      },
      docsDist: {
        options: {
          sourceMap: false
        },
        files: {
          'docs/css/main.css': 'docs/_sass/main.scss'
        }
      }
    },
    postcss: {
      options: {
        processors: [
          require('autoprefixer')({ browsers: ['last 2 version'] })
        ],
        map: {
          inline: false,
        }
      },
      dist: {
        options: {
          processors: [ require('cssnano')() ],
          map: false
        },
        src: 'dist/css/*.css'
      },
      dev: {
        src: 'dist/css/*.css'
      },
      docs: {
        src: 'docs/css/*.css'
      },
      docsDist: {
        options: {
          processors: [ require('cssnano')() ],
          map: false
        },
        src: 'docs/css/*.css'
      }
    },
    grunticon: {
      ui: {
        files: [{
          expand: true,
          cwd: 'icons/ui/src',
          src: ['*.svg'],
          dest: 'dist/assets/icons/ui'
        }],
        options: {
          dynamicColorOnly: true,
          colors: {
            light: '#ffffff',
            dark: '#000000'
          },
          datasvgcss: 'ui-icons.svg.css',
          datapngcss: 'ui-icons.png.css',
          urlpngcss: 'ui-icons.fallback.css'
        }
      },
      thematic: {
        files: [{
          expand: true,
          cwd: 'icons/thematic',
          src: ['*.svg'],
          dest: 'dist/assets/icons/thematic'
        }],
        options: {
          datasvgcss: 'thematic-icons.svg.css',
          datapngcss: 'thematic-icons.png.css',
          urlpngcss: 'thematic-icons.fallback.css',
          cssprefix: '.thematic-'
        }
      }
    },
    svgtoolkit: {
      light: {
        options: {
          generatePNGs: false,
          colorize: '#ffffff'
        },
        files: [{
          expand: true,
          cwd: 'icons/ui/src',
          src: '*.svg',
          dest: 'icons/ui/dist/light'
        }]
      },
      dark: {
        options: {
          generatePNGs: false,
          colorize: '#232323'
        },
        files: [{
          expand: true,
          cwd: 'icons/ui/src',
          src: '*.svg',
          dest: 'icons/ui/dist/dark'
        }]
      },
      primary: {
        options: {
          generatePNGs: false,
          colorize: '#2A93FC'
        },
        files: [{
          expand: true,
          cwd: 'icons/ui/src',
          src: '*.svg',
          dest: 'icons/ui/dist/primary'
        }]
      }
    },
    datauri: {
      light: {
        options: {
          classPrefix: 'icon-light-',
          usePlaceholder: true
        },
        files: [{
          src: 'icons/ui/dist/light/**/*.svg',
          dest: 'scss/modules/icons/_icons-light.scss'
        }]
      },
      dark: {
        options: {
          classPrefix: 'icon-dark-',
          usePlaceholder: true
        },
        files: [{
          src: 'icons/ui/dist/dark/**/*.svg',
          dest: 'scss/modules/icons/_icons-dark.scss'
        }]
      },
      primary: {
        options: {
          classPrefix: 'icon-primary-',
          usePlaceholder: true
        },
        files: [{
          src: 'icons/ui/dist/primary/**/*.svg',
          dest: 'scss/modules/icons/_icons-primary.scss'
        }]
      }
    },
    jekyll: {
      dist: {
        options: {
          src: './docs',
          config: './docs/_config.yml',
          dest: './dist/docs'
        }
      }
    },
    eslint: {
      options: {
        config: '.eslintrc.json',
        force: true
      },
      all: ['js/**/*.js']
    },
    sasslint: {
      options: {
        config: '.sass-lint.yml',
      },
      target: ['scss/**/*.scss']
    }
  });

  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-contrib-clean');
  grunt.loadNpmTasks('grunt-grunticon');
  grunt.loadNpmTasks('grunt-datauri');
  grunt.loadNpmTasks('grunt-jekyll');
  grunt.loadNpmTasks('grunt-postcss');
  grunt.loadNpmTasks('grunt-sass');
  grunt.loadNpmTasks('grunt-sass-lint');
  grunt.loadNpmTasks('grunt-svg-toolkit');
  grunt.loadNpmTasks('grunt-pure-grids');
  grunt.loadNpmTasks('eslint-grunt');

  // Build SVGs and SCSS
  grunt.registerTask('gen-svg', ['clean:svg', 'svgtoolkit', 'datauri', 'clean:svg']);
  // Build Grunticon Icons
  grunt.registerTask('gen-icons', ['clean:grunticon', 'grunticon']);
  // Build WFP UI Docs locally
  grunt.registerTask('docs', ['sasslint', 'sass:docs', 'postcss:docs', 'jekyll']);
  // Build WFP UI Docs
  grunt.registerTask('docs-build', ['sasslint', 'sass:docs', 'postcss:docs', 'jekyll']);
  // Build dist-ready WFP UI Docs
  grunt.registerTask('docs-dist', ['sass:docsDist', 'postcss:docsDist', 'jekyll']);
  // Build WFP UI
  grunt.registerTask('build', ['eslint', 'sasslint', 'sass:dev', 'postcss:dev']);
  // Build a dist-ready WFP UI
  grunt.registerTask('dist', ['gen-svg', 'gen-icons', 'sass:dist', 'postcss:dist']);
  // Run build and dist task in sequence (for Travis CI)
  grunt.registerTask('test', ['build', 'docs-build', 'dist', 'docs-dist']);
  // Set default grunt task to `dist`
  grunt.registerTask('default', ['dist']);
};
<commit_msg>Remove `sasslint` from docs generation<commit_after>// Gruntfile.js
module.exports = function(grunt) {
  require('time-grunt')(grunt);
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    watch: {
      sass: {
        files: 'scss/**/*.scss',
        tasks: ['build']
      },
      eslint: {
        files: 'js/**/*.js',
        tasks: ['eslint']
      },
      docs: {
        files: ['docs/**/*.*', 'scss/**/**/*.scss'],
        tasks: ['docs']
      }
    },
    clean: {
      grunticon: ['dist/assets/icons/ui', 'dist/assets/icons/thematic'],
      svg: ['icons/ui/dist']
    },
    pure_grids: {
      responsive: {
        dest: 'scss/modules/_grid-units.scss',
        options: {
          units: [5,12,24],
          includeOldIEWidths: false,
          mediaQueries: {
            sm: 'screen and (min-width: 36.5em)', //584px
            md: 'screen and (min-width: 48em)', //768px
            lg: 'screen and (min-width: 64em)', //1024px
            xl: 'screen and (min-width: 80em)'  //1200px
          },
          selectorPrefix: '.wfp-u-'
        }
      }
    },
    sass: {
      options: {
        outputStyle: 'expanded',
        sourceMap: true,
        indentedSyntax: true,
        sassDir: 'scss',
        cssDir: 'dist/css'
      },
      dev: {
        files: [{
          expand: true,
          cwd: 'scss/',
          src: ['*.scss'],
          dest: 'dist/css/',
          ext: '.css'
        }]
      },
      dist: {
        options: {
          sourceMap: false
        },
        files: [{
          expand: true,
          cwd: 'scss/',
          src: ['*.scss'],
          dest: 'dist/css/',
          ext: '.css'
        }]
      },
      docs: {
        files: {
          'docs/css/main.css': 'docs/_sass/main.scss'
        }
      },
      docsDist: {
        options: {
          sourceMap: false
        },
        files: {
          'docs/css/main.css': 'docs/_sass/main.scss'
        }
      }
    },
    postcss: {
      options: {
        processors: [
          require('autoprefixer')({ browsers: ['last 2 version'] })
        ],
        map: {
          inline: false,
        }
      },
      dist: {
        options: {
          processors: [ require('cssnano')() ],
          map: false
        },
        src: 'dist/css/*.css'
      },
      dev: {
        src: 'dist/css/*.css'
      },
      docs: {
        src: 'docs/css/*.css'
      },
      docsDist: {
        options: {
          processors: [ require('cssnano')() ],
          map: false
        },
        src: 'docs/css/*.css'
      }
    },
    grunticon: {
      ui: {
        files: [{
          expand: true,
          cwd: 'icons/ui/src',
          src: ['*.svg'],
          dest: 'dist/assets/icons/ui'
        }],
        options: {
          dynamicColorOnly: true,
          colors: {
            light: '#ffffff',
            dark: '#000000'
          },
          datasvgcss: 'ui-icons.svg.css',
          datapngcss: 'ui-icons.png.css',
          urlpngcss: 'ui-icons.fallback.css'
        }
      },
      thematic: {
        files: [{
          expand: true,
          cwd: 'icons/thematic',
          src: ['*.svg'],
          dest: 'dist/assets/icons/thematic'
        }],
        options: {
          datasvgcss: 'thematic-icons.svg.css',
          datapngcss: 'thematic-icons.png.css',
          urlpngcss: 'thematic-icons.fallback.css',
          cssprefix: '.thematic-'
        }
      }
    },
    svgtoolkit: {
      light: {
        options: {
          generatePNGs: false,
          colorize: '#ffffff'
        },
        files: [{
          expand: true,
          cwd: 'icons/ui/src',
          src: '*.svg',
          dest: 'icons/ui/dist/light'
        }]
      },
      dark: {
        options: {
          generatePNGs: false,
          colorize: '#232323'
        },
        files: [{
          expand: true,
          cwd: 'icons/ui/src',
          src: '*.svg',
          dest: 'icons/ui/dist/dark'
        }]
      },
      primary: {
        options: {
          generatePNGs: false,
          colorize: '#2A93FC'
        },
        files: [{
          expand: true,
          cwd: 'icons/ui/src',
          src: '*.svg',
          dest: 'icons/ui/dist/primary'
        }]
      }
    },
    datauri: {
      light: {
        options: {
          classPrefix: 'icon-light-',
          usePlaceholder: true
        },
        files: [{
          src: 'icons/ui/dist/light/**/*.svg',
          dest: 'scss/modules/icons/_icons-light.scss'
        }]
      },
      dark: {
        options: {
          classPrefix: 'icon-dark-',
          usePlaceholder: true
        },
        files: [{
          src: 'icons/ui/dist/dark/**/*.svg',
          dest: 'scss/modules/icons/_icons-dark.scss'
        }]
      },
      primary: {
        options: {
          classPrefix: 'icon-primary-',
          usePlaceholder: true
        },
        files: [{
          src: 'icons/ui/dist/primary/**/*.svg',
          dest: 'scss/modules/icons/_icons-primary.scss'
        }]
      }
    },
    jekyll: {
      dist: {
        options: {
          src: './docs',
          config: './docs/_config.yml',
          dest: './dist/docs'
        }
      }
    },
    eslint: {
      options: {
        config: '.eslintrc.json',
        force: true
      },
      all: ['js/**/*.js']
    },
    sasslint: {
      options: {
        config: '.sass-lint.yml',
      },
      target: ['scss/**/*.scss']
    }
  });

  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-contrib-clean');
  grunt.loadNpmTasks('grunt-grunticon');
  grunt.loadNpmTasks('grunt-datauri');
  grunt.loadNpmTasks('grunt-jekyll');
  grunt.loadNpmTasks('grunt-postcss');
  grunt.loadNpmTasks('grunt-sass');
  grunt.loadNpmTasks('grunt-sass-lint');
  grunt.loadNpmTasks('grunt-svg-toolkit');
  grunt.loadNpmTasks('grunt-pure-grids');
  grunt.loadNpmTasks('eslint-grunt');

  // Build SVGs and SCSS
  grunt.registerTask('gen-svg', ['clean:svg', 'svgtoolkit', 'datauri', 'clean:svg']);
  // Build Grunticon Icons
  grunt.registerTask('gen-icons', ['clean:grunticon', 'grunticon']);
  // Build WFP UI Docs locally
  grunt.registerTask('docs', ['sass:docs', 'postcss:docs', 'jekyll']);
  // Build WFP UI Docs
  grunt.registerTask('docs-build', ['sasslint', 'sass:docs', 'postcss:docs', 'jekyll']);
  // Build dist-ready WFP UI Docs
  grunt.registerTask('docs-dist', ['sass:docsDist', 'postcss:docsDist', 'jekyll']);
  // Build WFP UI
  grunt.registerTask('build', ['eslint', 'sasslint', 'sass:dev', 'postcss:dev']);
  // Build a dist-ready WFP UI
  grunt.registerTask('dist', ['gen-svg', 'gen-icons', 'sass:dist', 'postcss:dist']);
  // Run build and dist task in sequence (for Travis CI)
  grunt.registerTask('test', ['build', 'docs-build', 'dist', 'docs-dist']);
  // Set default grunt task to `dist`
  grunt.registerTask('default', ['dist']);
};
<|endoftext|>


<commit_before>var audio;

$(function() {
  $('.navbar').load('../navbar.html');
  $('#landing').load('../landing.html');
});

$(window).load(function() {
	console.log("audio");
  $(".navbar").on("click", "a", function(event){
  	event.preventDefault();
  	if(typeof pauseMusic == 'function') {
  		pauseMusic();
  		pauseToons();
			}
  	$("#landing").hide();
  	$("#canvas").remove();
  	var link = $(this).attr("href");
  	var request = $.ajax({url: link});
  	request.done(function(response){
	  	if(link == "http://peterhinners.github.io"){
	  		$(".main-content").html("");
	  		$("#landing").show();
	  		window.scrollTo(0,0);
	  	} else {
	  		$(".main-content").html(response);
	  		audio = document.getElementById("#audiotag1");
	  		window.scrollTo(0,0);
	  	}
  	});
	});

	$(".main-content").on("click", "a", function(event){
		var link = $(this).attr("href");
		if(link == "http://catbreak.herokuapp.com"){
			}	else {
			event.preventDefault();

			audio.play();

			var request = $.ajax({url: link});
			  request.done(function(response){
			  	$(".main-content").html(response);

			  });
		}
	});
});

window.addEventListener("keydown", function(e) {
    // space and arrow keys
    if([32, 37, 38, 39, 40].indexOf(e.keyCode) > -1) {
        e.preventDefault();
    }
}, false);

<commit_msg>formatting<commit_after>var audio;

$(function() {
  $('.navbar').load('../navbar.html');
  $('#landing').load('../landing.html');
});

$(window).load(function() {
	console.log("audio2");
  $(".navbar").on("click", "a", function(event){
  	event.preventDefault();
  	if(typeof pauseMusic == 'function') {
  		pauseMusic();
  		pauseToons();
			}
  	$("#landing").hide();
  	$("#canvas").remove();
  	var link = $(this).attr("href");
  	var request = $.ajax({url: link});
  	request.done(function(response){
	  	if(link == "http://peterhinners.github.io"){
	  		$(".main-content").html("");
	  		$("#landing").show();
	  		window.scrollTo(0,0);
	  	} else {
	  		$(".main-content").html(response);

	  		window.scrollTo(0,0);
	  	}
  	});
	});

	$(".main-content").on("click", "a", function(event){
		var link = $(this).attr("href");
		if(link == "http://catbreak.herokuapp.com"){
			}	else {
			event.preventDefault();

			audio = document.getElementById("#audiotag1");
			audio.play();

			var request = $.ajax({url: link});
			  request.done(function(response){
			  	$(".main-content").html(response);

			  });
		}
	});
});

window.addEventListener("keydown", function(e) {
    // space and arrow keys
    if([32, 37, 38, 39, 40].indexOf(e.keyCode) > -1) {
        e.preventDefault();
    }
}, false);

<|endoftext|>


<commit_before>define(
  'ephox.alloy.api.system.ForeignGui',

  [
    'ephox.alloy.api.component.GuiFactory',
    'ephox.alloy.api.system.Gui',
    'ephox.alloy.foreign.ForeignCache',
    'ephox.alloy.registry.Tagger',
    'ephox.boulder.api.FieldSchema',
    'ephox.boulder.api.Objects',
    'ephox.boulder.api.ValueSchema',
    'ephox.katamari.api.Arr',
    'ephox.katamari.api.Cell',
    'ephox.katamari.api.Fun',
    'ephox.katamari.api.Options',
    'ephox.sugar.api.dom.Insert',
    'ephox.sugar.api.events.DomEvent'
  ],

  function (GuiFactory, Gui, ForeignCache, Tagger, FieldSchema, Objects, ValueSchema, Arr, Cell, Fun, Options, Insert, DomEvent) {
    var schema = ValueSchema.objOf([
      FieldSchema.strict('root'),
      FieldSchema.strictArrayOfObj('dispatchers', [
        FieldSchema.strict('getTarget'),
        FieldSchema.strict('alloyConfig')
      ]),
      FieldSchema.defaulted('insertion', function (root, system) {
        Insert.append(root, system.element());
      })
    ]);

    var supportedEvents = [
      'click', 'mousedown', 'mousemove', 'touchstart', 'touchend', 'gesturestart'
    ];

    // Find the dispatcher information for the target if available. Note, the
    // dispatcher may also change the target.
    var findDispatcher = function (dispatchers, target) {
      return Options.findMap(dispatchers, function (dispatcher) {
        return dispatcher.getTarget()(target).map(function (newTarget) {
          return {
            target: Fun.constant(newTarget),
            dispatcher: Fun.constant(dispatcher)
          };
        });
      });
    };

    var createEvent = function (event, target) {
      var source = Cell(target);

      var stopper = Cell(false);

      var cutter = Cell(false);

      var stop = function () {
        stopper.set(true);
      };

      var cut = function () {
        cutter.set(true);
      };

      return {
        stop: stop,
        cut: cut,
        event: Fun.constant(event),
        setSource: source.set,
        getSource: source.get
      };
    };

    var getProxy = function (event, target) {
      // Setup the component wrapping for the target element
      var component = GuiFactory.build(
        GuiFactory.external({ element: target })
      );

      // Create a simulated event
      var simulatedEvent = createEvent(event, target);

      return {
        component: Fun.constant(component),
        simulatedEvent: Fun.constant(simulatedEvent)
      };
    };

    var engage = function (spec) {
      var detail = ValueSchema.asStructOrDie('ForeignGui', schema, spec);

      // Creates an inner GUI and inserts it appropriately. This will be used
      // as the system for all behaviours
      var gui = Gui.create();
      detail.insertion()(detail.root(), gui);

      var cache = ForeignCache();

      var domEvents = Arr.map(supportedEvents, function (type) {
        return DomEvent.bind(detail.root(), type, function (event) {
          dispatchTo(type, event);
        });
      });

      var proxyFor = function (event, target, handler) {
        // create a simple alloy wrapping around the element, and add it to the world
        var proxy = getProxy(event, target);
        var component = proxy.component();
        gui.addToWorld(component);
        // fire the event
        handler(component, proxy.simulatedEvent());

        // now remove from the world and
        unproxy(component);
      };

      var dispatchTo = function (type, event) {
        // Do not delegate anything that happened within the internal system. It is
        // already being handled.
        if (gui.element().dom().contains(event.target().dom())) return;

        // Find if the target has an assigned dispatcher
        findDispatcher(detail.dispatchers(), event.target()).each(function (dispatchData) {
          var target = dispatchData.target();
          var dispatcher = dispatchData.dispatcher();

          // get any info for this current element, creating it if necessary
          var data = cache.getEvents(target, dispatcher.alloyConfig());
          var events = data.evts();

          // if this dispatcher defines this event, proxy it and fire the handler
          if (Objects.hasKey(events, type)) proxyFor(event, target, events[type].handler);
        });
      };

      // Remove any traces of the foreign component from the internal alloy system.
      var unproxy = function (component) {
        gui.removeFromWorld(component);
        Tagger.revoke(component.element());
      };

      var disengage = function () {
        Arr.each(domEvents, function (e) {
          e.unbind();
        });
      };

      return {
        dispatchTo: dispatchTo,
        unproxy: unproxy,
        disengage: disengage
      };
    };

    return {
      engage: engage
    };
  }
);
<commit_msg>TM-12: using DescribedHandler<commit_after>define(
  'ephox.alloy.api.system.ForeignGui',

  [
    'ephox.alloy.api.component.GuiFactory',
    'ephox.alloy.api.system.Gui',
    'ephox.alloy.events.DescribedHandler',
    'ephox.alloy.foreign.ForeignCache',
    'ephox.alloy.registry.Tagger',
    'ephox.boulder.api.FieldSchema',
    'ephox.boulder.api.Objects',
    'ephox.boulder.api.ValueSchema',
    'ephox.katamari.api.Arr',
    'ephox.katamari.api.Cell',
    'ephox.katamari.api.Fun',
    'ephox.katamari.api.Options',
    'ephox.sugar.api.dom.Insert',
    'ephox.sugar.api.events.DomEvent'
  ],

  function (GuiFactory, Gui, DescribedHandler, ForeignCache, Tagger, FieldSchema, Objects, ValueSchema, Arr, Cell, Fun, Options, Insert, DomEvent) {
    var schema = ValueSchema.objOf([
      FieldSchema.strict('root'),
      FieldSchema.strictArrayOfObj('dispatchers', [
        FieldSchema.strict('getTarget'),
        FieldSchema.strict('alloyConfig')
      ]),
      FieldSchema.defaulted('insertion', function (root, system) {
        Insert.append(root, system.element());
      })
    ]);

    var supportedEvents = [
      'click', 'mousedown', 'mousemove', 'touchstart', 'touchend', 'gesturestart'
    ];

    // Find the dispatcher information for the target if available. Note, the
    // dispatcher may also change the target.
    var findDispatcher = function (dispatchers, target) {
      return Options.findMap(dispatchers, function (dispatcher) {
        return dispatcher.getTarget()(target).map(function (newTarget) {
          return {
            target: Fun.constant(newTarget),
            dispatcher: Fun.constant(dispatcher)
          };
        });
      });
    };

    var createEvent = function (event, target) {
      var source = Cell(target);

      var stopper = Cell(false);

      var cutter = Cell(false);

      var stop = function () {
        stopper.set(true);
      };

      var cut = function () {
        cutter.set(true);
      };

      return {
        stop: stop,
        cut: cut,
        event: Fun.constant(event),
        setSource: source.set,
        getSource: source.get
      };
    };

    var getProxy = function (event, target) {
      // Setup the component wrapping for the target element
      var component = GuiFactory.build(
        GuiFactory.external({ element: target })
      );

      // Create a simulated event
      var simulatedEvent = createEvent(event, target);

      return {
        component: Fun.constant(component),
        simulatedEvent: Fun.constant(simulatedEvent)
      };
    };

    var engage = function (spec) {
      var detail = ValueSchema.asStructOrDie('ForeignGui', schema, spec);

      // Creates an inner GUI and inserts it appropriately. This will be used
      // as the system for all behaviours
      var gui = Gui.create();
      detail.insertion()(detail.root(), gui);

      var cache = ForeignCache();

      var domEvents = Arr.map(supportedEvents, function (type) {
        return DomEvent.bind(detail.root(), type, function (event) {
          dispatchTo(type, event);
        });
      });

      var proxyFor = function (event, target, descHandler) {
        // create a simple alloy wrapping around the element, and add it to the world
        var proxy = getProxy(event, target);
        var component = proxy.component();
        gui.addToWorld(component);
        // fire the event
        var handler = DescribedHandler.getHandler(descHandler);
        handler(component, proxy.simulatedEvent());

        // now remove from the world and
        unproxy(component);
      };

      var dispatchTo = function (type, event) {
        // Do not delegate anything that happened within the internal system. It is
        // already being handled.
        if (gui.element().dom().contains(event.target().dom())) return;

        // Find if the target has an assigned dispatcher
        findDispatcher(detail.dispatchers(), event.target()).each(function (dispatchData) {
          var target = dispatchData.target();
          var dispatcher = dispatchData.dispatcher();

          // get any info for this current element, creating it if necessary
          var data = cache.getEvents(target, dispatcher.alloyConfig());
          var events = data.evts();

          // if this dispatcher defines this event, proxy it and fire the handler\
          if (Objects.hasKey(events, type)) proxyFor(event, target, events[type]);
        });
      };

      // Remove any traces of the foreign component from the internal alloy system.
      var unproxy = function (component) {
        gui.removeFromWorld(component);
        Tagger.revoke(component.element());
      };

      var disengage = function () {
        Arr.each(domEvents, function (e) {
          e.unbind();
        });
      };

      return {
        dispatchTo: dispatchTo,
        unproxy: unproxy,
        disengage: disengage
      };
    };

    return {
      engage: engage
    };
  }
);
<|endoftext|>


<commit_before>(function() {
  /*jshint multistr:true */
  var EDGE = -999;

  var root = this,   // Root object, this is going to be the window for now
      document = this.document, // Safely store a document here for us to use
      editableNodes = document.querySelectorAll(".g-body article"),
      editNode = editableNodes[0], // TODO: cross el support for imageUpload
      isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1,
      options = {
        animate: true
      },
      textMenu,
      optionsNode,
      urlInput,
      previouslySelectedText,
      imageTooltip,
      imageInput,
      imageBound;

      grande = {
        bind: function(bindableNodes, opts) {
          if (bindableNodes) {
            editableNodes = bindableNodes;
          }

          options = opts || options;

          attachToolbarTemplate();
          bindTextSelectionEvents();
          bindTextStylingEvents();
        },
        select: function() {
          triggerTextSelection();
        }
      },

      tagClassMap = {
        "b": "bold",
        "i": "italic",
        "h1": "header1",
        "h2": "header2",
        "a": "url",
        "blockquote": "quote"
      };

  function attachToolbarTemplate() {
    var div = document.createElement("div"),
        toolbarTemplate = "
 \
           \
             \
              <button class='bold'>B</button> \
              <button class='italic'>i</button> \
              <button class='header1'>h1</button> \
              <button class='header2'>h2</button> \
              <button class='quote'>”</button> \
              <button class='url useicons'></button> \
              <input class='url-input' type='text' placeholder='Paste or type a link'/> \
             \
           \

",
        imageTooltipTemplate = document.createElement("div"),
        toolbarContainer = document.createElement("div");

    toolbarContainer.className = "g-body";
    document.body.appendChild(toolbarContainer);

    imageTooltipTemplate.innerHTML = "
Insert image
 \
                                        <input class='file-hidden pos-abs' type='file' id='files' name='files[]' accept='image/*' multiple/>";
    imageTooltipTemplate.className = "image-tooltip hide";

    div.className = "text-menu hide";
    div.innerHTML = toolbarTemplate;

    if (document.querySelectorAll(".text-menu").length === 0) {
      toolbarContainer.appendChild(div);
      toolbarContainer.appendChild(imageTooltipTemplate);
    }

    imageInput = document.querySelectorAll(".file-label + input")[0];
    imageTooltip = document.querySelectorAll(".image-tooltip")[0];
    textMenu = document.querySelectorAll(".text-menu")[0];
    optionsNode = document.querySelectorAll(".text-menu .options")[0];
    urlInput = document.querySelectorAll(".text-menu .url-input")[0];
  }

  function bindTextSelectionEvents() {
    var i,
        len,
        node;

    // Trigger on both mousedown and mouseup so that the click on the menu
    // feels more instantaneously active
    document.onmousedown = triggerTextSelection;
    document.onmouseup = function(event) {
      setTimeout(function() {
        triggerTextSelection(event);
      }, 1);
    };

    document.onkeydown = preprocessKeyDown;

    document.onkeyup = function(event){
      var sel = window.getSelection();

      // FF will return sel.anchorNode to be the parentNode when the triggered keyCode is 13
      if (sel.anchorNode && sel.anchorNode.nodeName !== "ARTICLE") {
        triggerNodeAnalysis(event);

        if (sel.isCollapsed) {
          triggerTextParse(event);
        }
      }
    };

    // Handle window resize events
    root.onresize = triggerTextSelection;

    urlInput.onblur = triggerUrlBlur;
    urlInput.onkeydown = triggerUrlSet;

    if (options.allowImages) {
      imageTooltip.onmousedown = triggerImageUpload;
      imageInput.onchange = uploadImage;
      document.onmousemove = triggerOverlayStyling;
    }

    for (i = 0, len = editableNodes.length; i < len; i++) {
      node = editableNodes[i];
      node.contentEditable = true;
      node.onmousedown = node.onkeyup = node.onmouseup = triggerTextSelection;
    }
  }

  function triggerOverlayStyling(event) {
    toggleImageTooltip(event, event.target);
  }

  function triggerImageUpload(event) {
    // Cache the bound that was originally clicked on before the image upload
    var childrenNodes = editNode.children,
        editBounds = editNode.getBoundingClientRect();

    imageBound = getHorizontalBounds(childrenNodes, editBounds);
  }

  function uploadImage(event) {
    // Only allow uploading of 1 image for now, this is the first file
    var file = this.files[0],
        reader = new FileReader(),
        figEl;

    reader.onload = (function(f) {
      return function(e) {
        figEl = document.createElement("figure");
        figEl.innerHTML = "";
        editNode.insertBefore(figEl, imageBound.bottomElement);
      };
    }(file));

    reader.readAsDataURL(file);
  }

  function toggleImageTooltip(event, element) {
    var childrenNodes = editNode.children,
        editBounds = editNode.getBoundingClientRect(),
        bound = getHorizontalBounds(childrenNodes, editBounds);

    if (bound) {
      imageTooltip.style.left = (editBounds.left - 90 ) + "px";
      imageTooltip.style.top = (bound.top - 17) + "px";
    } else {
      imageTooltip.style.left = EDGE + "px";
      imageTooltip.style.top = EDGE + "px";
    }
  }

  function getHorizontalBounds(nodes, target) {
    var bounds = [],
        bound,
        i,
        len,
        preNode,
        postNode,
        bottomBound,
        topBound,
        coordY;

    // Compute top and bottom bounds for each child element
    for (i = 0, len = nodes.length - 1; i < len; i++) {
      preNode = nodes[i];
      postNode = nodes[i+1] || null;

      bottomBound = preNode.getBoundingClientRect().bottom - 5;
      topBound = postNode.getBoundingClientRect().top;

      bounds.push({
        top: topBound,
        bottom: bottomBound,
        topElement: preNode,
        bottomElement: postNode,
        index: i+1
      });
    }

    coordY = event.pageY - root.scrollY;

    // Find if there is a range to insert the image tooltip between two elements
    for (i = 0, len = bounds.length; i < len; i++) {
      bound = bounds[i];
      if (coordY < bound.top && coordY > bound.bottom) {
        return bound;
      }
    }

    return null;
  }

  function iterateTextMenuButtons(callback) {
    var textMenuButtons = document.querySelectorAll(".text-menu button"),
        i,
        len,
        node,
        fnCallback = function(n) {
          callback(n);
        };

    for (i = 0, len = textMenuButtons.length; i < len; i++) {
      node = textMenuButtons[i];

      fnCallback(node);
    }
  }

  function bindTextStylingEvents() {
    iterateTextMenuButtons(function(node) {
      node.onmousedown = function(event) {
        triggerTextStyling(node);
      };
    });
  }

  function getFocusNode() {
    return root.getSelection().focusNode;
  }

  function reloadMenuState() {
    var className,
        focusNode = getFocusNode(),
        tagClass,
        reTag;

    iterateTextMenuButtons(function(node) {
      className = node.className;

      for (var tag in tagClassMap) {
        tagClass = tagClassMap[tag];
        reTag = new RegExp(tagClass);

        if (reTag.test(className)) {
          if (hasParentWithTag(focusNode, tag)) {
            node.className = tagClass + " active";
          } else {
            node.className = tagClass;
          }

          break;
        }
      }
    });
  }

  function preprocessKeyDown(event) {
    var sel = window.getSelection(),
        parentParagraph = getParentWithTag(sel.anchorNode, "p"),
        p,
        isHr;

    if (event.keyCode === 13 && parentParagraph) {
      prevSibling = parentParagraph.previousSibling;
      isHr = prevSibling && prevSibling.nodeName === "HR" &&
        !parentParagraph.textContent.length;

      // Stop enters from creating another
 after a
 on enter
      if (isHr) {
        event.preventDefault();
      }
    }
  }

  function triggerNodeAnalysis(event) {
    var sel = window.getSelection(),
        anchorNode,
        parentParagraph;

    if (event.keyCode === 13) {

      // Enters should replace it's parent
 with a

      if (sel.anchorNode.nodeName === "DIV") {
        toggleFormatBlock("p");
      }

      parentParagraph = getParentWithTag(sel.anchorNode, "p");

      if (parentParagraph) {
        insertHorizontalRule(parentParagraph);
      }
    }
  }

  function insertHorizontalRule(parentParagraph) {
    var prevSibling,
        prevPrevSibling,
        hr;

    prevSibling = parentParagraph.previousSibling;
    prevPrevSibling = prevSibling;

    while (prevPrevSibling) {
      if (prevPrevSibling.nodeType != Node.TEXT_NODE) {
        break;
      }

      prevPrevSibling = prevPrevSibling.previousSibling;
    }

    if (prevSibling.nodeName === "P" && !prevSibling.textContent.length && prevPrevSibling.nodeName !== "HR") {
      hr = document.createElement("hr");
      hr.contentEditable = false;
      parentParagraph.parentNode.replaceChild(hr, prevSibling);
    }
  }

  function getTextProp(el) {
    var textProp;

    if (el.nodeType === Node.TEXT_NODE) {
      textProp = "data";
    } else if (isFirefox) {
      textProp = "textContent";
    } else {
      textProp = "innerText";
    }

    return textProp;
  }

  function insertListOnSelection(sel, textProp, listType) {
    var execListCommand = listType === "ol" ? "insertOrderedList" : "insertUnorderedList",
        nodeOffset = listType === "ol" ? 3 : 2;

    document.execCommand(execListCommand);
    sel.anchorNode[textProp] = sel.anchorNode[textProp].substring(nodeOffset);

    return getParentWithTag(sel.anchorNode, listType);
  }

  function triggerTextParse(event) {
    var sel = window.getSelection(),
        textProp,
        subject,
        insertedNode,
        unwrap,
        node,
        parent,
        range;

    // FF will return sel.anchorNode to be the parentNode when the triggered keyCode is 13
    if (!sel.isCollapsed || !sel.anchorNode || sel.anchorNode.nodeName === "ARTICLE") {
      return;
    }

    textProp = getTextProp(sel.anchorNode);
    subject = sel.anchorNode[textProp];

    if (subject.match(/^[-*]\s/) && sel.anchorNode.parentNode.nodeName !== "LI") {
      insertedNode = insertListOnSelection(sel, textProp, "ul");
    }

    if (subject.match(/^1\.\s/) && sel.anchorNode.parentNode.nodeName !== "LI") {
      insertedNode = insertListOnSelection(sel, textProp, "ol");
    }

    unwrap = insertedNode &&
            ["ul", "ol"].indexOf(insertedNode.nodeName.toLocaleLowerCase()) >= 0 &&
            ["p", "div"].indexOf(insertedNode.parentNode.nodeName.toLocaleLowerCase()) >= 0;

    if (unwrap) {
      node = sel.anchorNode;
      parent = insertedNode.parentNode;
      parent.parentNode.insertBefore(insertedNode, parent);
      parent.parentNode.removeChild(parent);
      moveCursorToBeginningOfSelection(sel, node);
    }
  }

  function moveCursorToBeginningOfSelection(selection, node) {
    range = document.createRange();
    range.setStart(node, 0);
    range.setEnd(node, 0);
    selection.removeAllRanges();
    selection.addRange(range);
  }

  function triggerTextStyling(node) {
    var className = node.className,
        sel = window.getSelection(),
        selNode = sel.anchorNode,
        tagClass,
        reTag;

    for (var tag in tagClassMap) {
      tagClass = tagClassMap[tag];
      reTag = new RegExp(tagClass);

      if (reTag.test(className)) {
        switch(tag) {
          case "b":
            if (selNode && !hasParentWithTag(selNode, "h1") && !hasParentWithTag(selNode, "h2")) {
              document.execCommand(tagClass, false);
            }
            return;
          case "i":
            document.execCommand(tagClass, false);
            return;

          case "h1":
          case "h2":
          case "h3":
          case "blockquote":
            toggleFormatBlock(tag);
            return;

          case "a":
            toggleUrlInput();
            optionsNode.className = "options url-mode";
            return;
        }
      }
    }

    triggerTextSelection();
  }

  function triggerUrlBlur(event) {
    var url = urlInput.value;

    optionsNode.className = "options";
    window.getSelection().addRange(previouslySelectedText);

    document.execCommand("unlink", false);

    if (url === "") {
      return false;
    }

    if (!url.match("^(http://|https://|mailto:)")) {
      url = "http://" + url;
    }

    document.execCommand("createLink", false, url);

    urlInput.value = "";
  }

  function triggerUrlSet(event) {
    if (event.keyCode === 13) {
      event.preventDefault();
      event.stopPropagation();

      urlInput.blur();
    }
  }

  function toggleFormatBlock(tag) {
    if (hasParentWithTag(getFocusNode(), tag)) {
      document.execCommand("formatBlock", false, "p");
      document.execCommand("outdent");
    } else {
      document.execCommand("formatBlock", false, tag);
    }
  }

  function toggleUrlInput() {
    setTimeout(function() {
      var url = getParentHref(getFocusNode());

      if (typeof url !== "undefined") {
        urlInput.value = url;
      } else {
        document.execCommand("createLink", false, "/");
      }

      previouslySelectedText = window.getSelection().getRangeAt(0);

      urlInput.focus();
    }, 150);
  }

  function getParent(node, condition, returnCallback) {
    while (node.parentNode) {
      if (condition(node)) {
        return returnCallback(node);
      }

      node = node.parentNode;
    }
  }

  function getParentWithTag(node, nodeType) {
    var checkNodeType = function(node) { return node.nodeName.toLowerCase() === nodeType; },
        returnNode = function(node) { return node; };

    return getParent(node, checkNodeType, returnNode);
  }

  function hasParentWithTag(node, nodeType) {
    return !!getParentWithTag(node, nodeType);
  }

  function getParentHref(node) {
    var checkHref = function(node) { return typeof node.href !== "undefined"; },
        returnHref = function(node) { return node.href; };

    return getParent(node, checkHref, returnHref);
  }

  function triggerTextSelection(e) {
    var selectedText = root.getSelection(),
        range,
        clientRectBounds,
        target = e.target || e.srcElement;

    // The selected text is not editable
    if (!target.isContentEditable) {
      reloadMenuState();
      return;
    }

    // The selected text is collapsed, push the menu out of the way
    if (selectedText.isCollapsed) {
      setTextMenuPosition(EDGE, EDGE);
      textMenu.className = "text-menu hide";
    } else {
      range = selectedText.getRangeAt(0);
      clientRectBounds = range.getBoundingClientRect();

      // Every time we show the menu, reload the state
      reloadMenuState();
      setTextMenuPosition(
        clientRectBounds.top - 5 + root.pageYOffset,
        (clientRectBounds.left + clientRectBounds.right) / 2
      );
    }
  }

  function setTextMenuPosition(top, left) {
    textMenu.style.top = top + "px";
    textMenu.style.left = left + "px";

    if (options.animate) {
      if (top === EDGE) {
        textMenu.className = "text-menu hide";
      } else {
        textMenu.className = "text-menu active";
      }
    }
  }

  root.grande = grande;

}).call(this);<commit_msg>Use h2 & h3 instead of h1 & h2 in articles, just like Medium.<commit_after>(function() {
  /*jshint multistr:true */
  var EDGE = -999;

  var root = this,   // Root object, this is going to be the window for now
      document = this.document, // Safely store a document here for us to use
      editableNodes = document.querySelectorAll(".g-body article"),
      editNode = editableNodes[0], // TODO: cross el support for imageUpload
      isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1,
      options = {
        animate: true
      },
      textMenu,
      optionsNode,
      urlInput,
      previouslySelectedText,
      imageTooltip,
      imageInput,
      imageBound;

      grande = {
        bind: function(bindableNodes, opts) {
          if (bindableNodes) {
            editableNodes = bindableNodes;
          }

          options = opts || options;

          attachToolbarTemplate();
          bindTextSelectionEvents();
          bindTextStylingEvents();
        },
        select: function() {
          triggerTextSelection();
        }
      },

      tagClassMap = {
        "b": "bold",
        "i": "italic",
        "h2": "header1",
        "h3": "header2",
        "a": "url",
        "blockquote": "quote"
      };

  function attachToolbarTemplate() {
    var div = document.createElement("div"),
        toolbarTemplate = "
 \
           \
             \
              <button class='bold'>B</button> \
              <button class='italic'>i</button> \
              <button class='header1'>h1</button> \
              <button class='header2'>h2</button> \
              <button class='quote'>”</button> \
              <button class='url useicons'></button> \
              <input class='url-input' type='text' placeholder='Paste or type a link'/> \
             \
           \

",
        imageTooltipTemplate = document.createElement("div"),
        toolbarContainer = document.createElement("div");

    toolbarContainer.className = "g-body";
    document.body.appendChild(toolbarContainer);

    imageTooltipTemplate.innerHTML = "
Insert image
 \
                                        <input class='file-hidden pos-abs' type='file' id='files' name='files[]' accept='image/*' multiple/>";
    imageTooltipTemplate.className = "image-tooltip hide";

    div.className = "text-menu hide";
    div.innerHTML = toolbarTemplate;

    if (document.querySelectorAll(".text-menu").length === 0) {
      toolbarContainer.appendChild(div);
      toolbarContainer.appendChild(imageTooltipTemplate);
    }

    imageInput = document.querySelectorAll(".file-label + input")[0];
    imageTooltip = document.querySelectorAll(".image-tooltip")[0];
    textMenu = document.querySelectorAll(".text-menu")[0];
    optionsNode = document.querySelectorAll(".text-menu .options")[0];
    urlInput = document.querySelectorAll(".text-menu .url-input")[0];
  }

  function bindTextSelectionEvents() {
    var i,
        len,
        node;

    // Trigger on both mousedown and mouseup so that the click on the menu
    // feels more instantaneously active
    document.onmousedown = triggerTextSelection;
    document.onmouseup = function(event) {
      setTimeout(function() {
        triggerTextSelection(event);
      }, 1);
    };

    document.onkeydown = preprocessKeyDown;

    document.onkeyup = function(event){
      var sel = window.getSelection();

      // FF will return sel.anchorNode to be the parentNode when the triggered keyCode is 13
      if (sel.anchorNode && sel.anchorNode.nodeName !== "ARTICLE") {
        triggerNodeAnalysis(event);

        if (sel.isCollapsed) {
          triggerTextParse(event);
        }
      }
    };

    // Handle window resize events
    root.onresize = triggerTextSelection;

    urlInput.onblur = triggerUrlBlur;
    urlInput.onkeydown = triggerUrlSet;

    if (options.allowImages) {
      imageTooltip.onmousedown = triggerImageUpload;
      imageInput.onchange = uploadImage;
      document.onmousemove = triggerOverlayStyling;
    }

    for (i = 0, len = editableNodes.length; i < len; i++) {
      node = editableNodes[i];
      node.contentEditable = true;
      node.onmousedown = node.onkeyup = node.onmouseup = triggerTextSelection;
    }
  }

  function triggerOverlayStyling(event) {
    toggleImageTooltip(event, event.target);
  }

  function triggerImageUpload(event) {
    // Cache the bound that was originally clicked on before the image upload
    var childrenNodes = editNode.children,
        editBounds = editNode.getBoundingClientRect();

    imageBound = getHorizontalBounds(childrenNodes, editBounds);
  }

  function uploadImage(event) {
    // Only allow uploading of 1 image for now, this is the first file
    var file = this.files[0],
        reader = new FileReader(),
        figEl;

    reader.onload = (function(f) {
      return function(e) {
        figEl = document.createElement("figure");
        figEl.innerHTML = "";
        editNode.insertBefore(figEl, imageBound.bottomElement);
      };
    }(file));

    reader.readAsDataURL(file);
  }

  function toggleImageTooltip(event, element) {
    var childrenNodes = editNode.children,
        editBounds = editNode.getBoundingClientRect(),
        bound = getHorizontalBounds(childrenNodes, editBounds);

    if (bound) {
      imageTooltip.style.left = (editBounds.left - 90 ) + "px";
      imageTooltip.style.top = (bound.top - 17) + "px";
    } else {
      imageTooltip.style.left = EDGE + "px";
      imageTooltip.style.top = EDGE + "px";
    }
  }

  function getHorizontalBounds(nodes, target) {
    var bounds = [],
        bound,
        i,
        len,
        preNode,
        postNode,
        bottomBound,
        topBound,
        coordY;

    // Compute top and bottom bounds for each child element
    for (i = 0, len = nodes.length - 1; i < len; i++) {
      preNode = nodes[i];
      postNode = nodes[i+1] || null;

      bottomBound = preNode.getBoundingClientRect().bottom - 5;
      topBound = postNode.getBoundingClientRect().top;

      bounds.push({
        top: topBound,
        bottom: bottomBound,
        topElement: preNode,
        bottomElement: postNode,
        index: i+1
      });
    }

    coordY = event.pageY - root.scrollY;

    // Find if there is a range to insert the image tooltip between two elements
    for (i = 0, len = bounds.length; i < len; i++) {
      bound = bounds[i];
      if (coordY < bound.top && coordY > bound.bottom) {
        return bound;
      }
    }

    return null;
  }

  function iterateTextMenuButtons(callback) {
    var textMenuButtons = document.querySelectorAll(".text-menu button"),
        i,
        len,
        node,
        fnCallback = function(n) {
          callback(n);
        };

    for (i = 0, len = textMenuButtons.length; i < len; i++) {
      node = textMenuButtons[i];

      fnCallback(node);
    }
  }

  function bindTextStylingEvents() {
    iterateTextMenuButtons(function(node) {
      node.onmousedown = function(event) {
        triggerTextStyling(node);
      };
    });
  }

  function getFocusNode() {
    return root.getSelection().focusNode;
  }

  function reloadMenuState() {
    var className,
        focusNode = getFocusNode(),
        tagClass,
        reTag;

    iterateTextMenuButtons(function(node) {
      className = node.className;

      for (var tag in tagClassMap) {
        tagClass = tagClassMap[tag];
        reTag = new RegExp(tagClass);

        if (reTag.test(className)) {
          if (hasParentWithTag(focusNode, tag)) {
            node.className = tagClass + " active";
          } else {
            node.className = tagClass;
          }

          break;
        }
      }
    });
  }

  function preprocessKeyDown(event) {
    var sel = window.getSelection(),
        parentParagraph = getParentWithTag(sel.anchorNode, "p"),
        p,
        isHr;

    if (event.keyCode === 13 && parentParagraph) {
      prevSibling = parentParagraph.previousSibling;
      isHr = prevSibling && prevSibling.nodeName === "HR" &&
        !parentParagraph.textContent.length;

      // Stop enters from creating another
 after a
 on enter
      if (isHr) {
        event.preventDefault();
      }
    }
  }

  function triggerNodeAnalysis(event) {
    var sel = window.getSelection(),
        anchorNode,
        parentParagraph;

    if (event.keyCode === 13) {

      // Enters should replace it's parent
 with a

      if (sel.anchorNode.nodeName === "DIV") {
        toggleFormatBlock("p");
      }

      parentParagraph = getParentWithTag(sel.anchorNode, "p");

      if (parentParagraph) {
        insertHorizontalRule(parentParagraph);
      }
    }
  }

  function insertHorizontalRule(parentParagraph) {
    var prevSibling,
        prevPrevSibling,
        hr;

    prevSibling = parentParagraph.previousSibling;
    prevPrevSibling = prevSibling;

    while (prevPrevSibling) {
      if (prevPrevSibling.nodeType != Node.TEXT_NODE) {
        break;
      }

      prevPrevSibling = prevPrevSibling.previousSibling;
    }

    if (prevSibling.nodeName === "P" && !prevSibling.textContent.length && prevPrevSibling.nodeName !== "HR") {
      hr = document.createElement("hr");
      hr.contentEditable = false;
      parentParagraph.parentNode.replaceChild(hr, prevSibling);
    }
  }

  function getTextProp(el) {
    var textProp;

    if (el.nodeType === Node.TEXT_NODE) {
      textProp = "data";
    } else if (isFirefox) {
      textProp = "textContent";
    } else {
      textProp = "innerText";
    }

    return textProp;
  }

  function insertListOnSelection(sel, textProp, listType) {
    var execListCommand = listType === "ol" ? "insertOrderedList" : "insertUnorderedList",
        nodeOffset = listType === "ol" ? 3 : 2;

    document.execCommand(execListCommand);
    sel.anchorNode[textProp] = sel.anchorNode[textProp].substring(nodeOffset);

    return getParentWithTag(sel.anchorNode, listType);
  }

  function triggerTextParse(event) {
    var sel = window.getSelection(),
        textProp,
        subject,
        insertedNode,
        unwrap,
        node,
        parent,
        range;

    // FF will return sel.anchorNode to be the parentNode when the triggered keyCode is 13
    if (!sel.isCollapsed || !sel.anchorNode || sel.anchorNode.nodeName === "ARTICLE") {
      return;
    }

    textProp = getTextProp(sel.anchorNode);
    subject = sel.anchorNode[textProp];

    if (subject.match(/^[-*]\s/) && sel.anchorNode.parentNode.nodeName !== "LI") {
      insertedNode = insertListOnSelection(sel, textProp, "ul");
    }

    if (subject.match(/^1\.\s/) && sel.anchorNode.parentNode.nodeName !== "LI") {
      insertedNode = insertListOnSelection(sel, textProp, "ol");
    }

    unwrap = insertedNode &&
            ["ul", "ol"].indexOf(insertedNode.nodeName.toLocaleLowerCase()) >= 0 &&
            ["p", "div"].indexOf(insertedNode.parentNode.nodeName.toLocaleLowerCase()) >= 0;

    if (unwrap) {
      node = sel.anchorNode;
      parent = insertedNode.parentNode;
      parent.parentNode.insertBefore(insertedNode, parent);
      parent.parentNode.removeChild(parent);
      moveCursorToBeginningOfSelection(sel, node);
    }
  }

  function moveCursorToBeginningOfSelection(selection, node) {
    range = document.createRange();
    range.setStart(node, 0);
    range.setEnd(node, 0);
    selection.removeAllRanges();
    selection.addRange(range);
  }

  function triggerTextStyling(node) {
    var className = node.className,
        sel = window.getSelection(),
        selNode = sel.anchorNode,
        tagClass,
        reTag;

    for (var tag in tagClassMap) {
      tagClass = tagClassMap[tag];
      reTag = new RegExp(tagClass);

      if (reTag.test(className)) {
        switch(tag) {
          case "b":
            if (selNode && !hasParentWithTag(selNode, "h1") && !hasParentWithTag(selNode, "h2")) {
              document.execCommand(tagClass, false);
            }
            return;
          case "i":
            document.execCommand(tagClass, false);
            return;

          case "h1":
          case "h2":
          case "h3":
          case "blockquote":
            toggleFormatBlock(tag);
            return;

          case "a":
            toggleUrlInput();
            optionsNode.className = "options url-mode";
            return;
        }
      }
    }

    triggerTextSelection();
  }

  function triggerUrlBlur(event) {
    var url = urlInput.value;

    optionsNode.className = "options";
    window.getSelection().addRange(previouslySelectedText);

    document.execCommand("unlink", false);

    if (url === "") {
      return false;
    }

    if (!url.match("^(http://|https://|mailto:)")) {
      url = "http://" + url;
    }

    document.execCommand("createLink", false, url);

    urlInput.value = "";
  }

  function triggerUrlSet(event) {
    if (event.keyCode === 13) {
      event.preventDefault();
      event.stopPropagation();

      urlInput.blur();
    }
  }

  function toggleFormatBlock(tag) {
    if (hasParentWithTag(getFocusNode(), tag)) {
      document.execCommand("formatBlock", false, "p");
      document.execCommand("outdent");
    } else {
      document.execCommand("formatBlock", false, tag);
    }
  }

  function toggleUrlInput() {
    setTimeout(function() {
      var url = getParentHref(getFocusNode());

      if (typeof url !== "undefined") {
        urlInput.value = url;
      } else {
        document.execCommand("createLink", false, "/");
      }

      previouslySelectedText = window.getSelection().getRangeAt(0);

      urlInput.focus();
    }, 150);
  }

  function getParent(node, condition, returnCallback) {
    while (node.parentNode) {
      if (condition(node)) {
        return returnCallback(node);
      }

      node = node.parentNode;
    }
  }

  function getParentWithTag(node, nodeType) {
    var checkNodeType = function(node) { return node.nodeName.toLowerCase() === nodeType; },
        returnNode = function(node) { return node; };

    return getParent(node, checkNodeType, returnNode);
  }

  function hasParentWithTag(node, nodeType) {
    return !!getParentWithTag(node, nodeType);
  }

  function getParentHref(node) {
    var checkHref = function(node) { return typeof node.href !== "undefined"; },
        returnHref = function(node) { return node.href; };

    return getParent(node, checkHref, returnHref);
  }

  function triggerTextSelection(e) {
    var selectedText = root.getSelection(),
        range,
        clientRectBounds,
        target = e.target || e.srcElement;

    // The selected text is not editable
    if (!target.isContentEditable) {
      reloadMenuState();
      return;
    }

    // The selected text is collapsed, push the menu out of the way
    if (selectedText.isCollapsed) {
      setTextMenuPosition(EDGE, EDGE);
      textMenu.className = "text-menu hide";
    } else {
      range = selectedText.getRangeAt(0);
      clientRectBounds = range.getBoundingClientRect();

      // Every time we show the menu, reload the state
      reloadMenuState();
      setTextMenuPosition(
        clientRectBounds.top - 5 + root.pageYOffset,
        (clientRectBounds.left + clientRectBounds.right) / 2
      );
    }
  }

  function setTextMenuPosition(top, left) {
    textMenu.style.top = top + "px";
    textMenu.style.left = left + "px";

    if (options.animate) {
      if (top === EDGE) {
        textMenu.className = "text-menu hide";
      } else {
        textMenu.className = "text-menu active";
      }
    }
  }

  root.grande = grande;

}).call(this);<|endoftext|>