<commit_before>/**
 jQuery.kinetic v2.0.6
 Dave Taylor http://davetayls.me

 @license The MIT License (MIT)
 @preserve Copyright (c) 2012 Dave Taylor http://davetayls.me
 */
(function ($){
  'use strict';

  var ACTIVE_CLASS = 'kinetic-active';

  /**
   * Provides requestAnimationFrame in a cross browser way.
   * http://paulirish.com/2011/requestanimationframe-for-smart-animating/
   */
  if (!window.requestAnimationFrame){

    window.requestAnimationFrame = ( function (){

      return window.webkitRequestAnimationFrame ||
        window.mozRequestAnimationFrame ||
        window.oRequestAnimationFrame ||
        window.msRequestAnimationFrame ||
        function (/* function FrameRequestCallback */ callback, /* DOMElement Element */ element){
          window.setTimeout(callback, 1000 / 60);
        };

    }());

  }

  // add touch checker to jQuery.support
  $.support = $.support || {};
  $.extend($.support, {
    touch: 'ontouchend' in document
  });
  var selectStart = function (){
    return false;
  };

  // KINETIC CLASS DEFINITION
  // ======================

  var Kinetic = function (element, settings) {
    this.settings = settings;
    this.el       = element;
    this.$el      = $(element);

    this._initElements();

    return this;
  };

  Kinetic.DATA_KEY = 'kinetic';
  Kinetic.DEFAULTS = {
    cursor: 'move',
    decelerate: true,
    triggerHardware: false,
    y: true,
    x: true,
    slowdown: 0.9,
    maxvelocity: 40,
    throttleFPS: 60,
    movingClass: {
      up: 'kinetic-moving-up',
      down: 'kinetic-moving-down',
      left: 'kinetic-moving-left',
      right: 'kinetic-moving-right'
    },
    deceleratingClass: {
      up: 'kinetic-decelerating-up',
      down: 'kinetic-decelerating-down',
      left: 'kinetic-decelerating-left',
      right: 'kinetic-decelerating-right'
    }
  };

  // Public functions

  Kinetic.prototype.start = function (options){
    this.settings = $.extend(this.settings, options);
    this.velocity = options.velocity || this.velocity;
    this.velocityY = options.velocityY || this.velocityY;
    this.settings.decelerate = false;
    this._move();
  };

  Kinetic.prototype.end = function (){
    this.settings.decelerate = true;
  };

  Kinetic.prototype.stop = function (){
    this.velocity = 0;
    this.velocityY = 0;
    this.settings.decelerate = true;
    if ($.isFunction(this.settings.stopped)){
      this.settings.stopped.call(this);
    }
  };

  Kinetic.prototype.detach = function (){
    this._detachListeners();
    this.$el
      .removeClass(ACTIVE_CLASS)
      .css('cursor', '');
  };

  Kinetic.prototype.attach = function (){
    if (this.$el.hasClass(ACTIVE_CLASS)) {
      return;
    }
    this._attachListeners(this.$el);
    this.$el
      .addClass(ACTIVE_CLASS)
      .css('cursor', this.settings.cursor);
  };

  // Internal functions

  Kinetic.prototype._initElements = function (){
    this.$el.addClass(ACTIVE_CLASS);

    $.extend(this, {
      xpos: null,
      prevXPos: false,
      ypos: null,
      prevYPos: false,
      mouseDown: false,
      throttleTimeout: 1000 / this.settings.throttleFPS,
      lastMove: null,
      elementFocused: null
    });

    this.velocity = 0;
    this.velocityY = 0;

    // make sure we reset everything when mouse up
    $(document)
      .mouseup($.proxy(this._resetMouse, this))
      .click($.proxy(this._resetMouse, this));

    this._initEvents();

    this.$el.css('cursor', this.settings.cursor);

    if (this.settings.triggerHardware){
      this.$el.css({
        '-webkit-transform': 'translate3d(0,0,0)',
        '-webkit-perspective': '1000',
        '-webkit-backface-visibility': 'hidden'
      });
    }
  };

  Kinetic.prototype._initEvents = function(){
    var self = this;
    this.settings.events = {
      touchStart: function (e){
        var touch;
        if (self._useTarget(e.target, e)){
          touch = e.originalEvent.touches[0];
          self._start(touch.clientX, touch.clientY);
          e.stopPropagation();
        }
      },
      touchMove: function (e){
        var touch;
        if (self.mouseDown){
          touch = e.originalEvent.touches[0];
          self._inputmove(touch.clientX, touch.clientY);
          if (e.preventDefault){
            e.preventDefault();
          }
        }
      },
      inputDown: function (e){
        if (self._useTarget(e.target, e)){
          self._start(e.clientX, e.clientY);
          self.elementFocused = e.target;
          if (e.target.nodeName === 'IMG'){
            e.preventDefault();
          }
          e.stopPropagation();
        }
      },
      inputEnd: function (e){
        if (self._useTarget(e.target, e)){
          self._end();
          self.elementFocused = null;
          if (e.preventDefault){
            e.preventDefault();
          }
        }
      },
      inputMove: function (e){
        if (self.mouseDown){
          self._inputmove(e.clientX, e.clientY);
          if (e.preventDefault){
            e.preventDefault();
          }
        }
      },
      scroll: function (e){
        if ($.isFunction(self.settings.moved)){
          self.settings.moved.call(self, self.settings);
        }
        if (e.preventDefault){
          e.preventDefault();
        }
      },
      inputClick: function (e){
        if (Math.abs(self.velocity) > 0){
          e.preventDefault();
          return false;
        }
      },
      // prevent drag and drop images in ie
      dragStart: function (e){
        if (self._useTarget(e.target, e) && self.elementFocused){
          return false;
        }
      }
    };

    this._attachListeners(this.$el, this.settings);

  };

  Kinetic.prototype._inputmove = function (clientX, clientY){
    var $this = this.$el;
    var el = this.el;

    if (!this.lastMove || new Date() > new Date(this.lastMove.getTime() + this.throttleTimeout)){
      this.lastMove = new Date();

      if (this.mouseDown && (this.xpos || this.ypos)){
        if (this.elementFocused){
          $(this.elementFocused).blur();
          this.elementFocused = null;
          $this.focus();
        }

        this.settings.decelerate = false;
        this.velocity = this.velocityY = 0;

        var scrollLeft = this.scrollLeft();
        var scrollTop = this.scrollTop();
        var movedX = (clientX - this.xpos);
        var movedY = (clientY - this.ypos);

        this.scrollLeft(this.settings.x ? scrollLeft - movedX : scrollLeft);
        this.scrollTop(this.settings.y ? scrollTop - movedY : scrollTop);

        this.prevXPos = this.xpos;
        this.prevYPos = this.ypos;
        this.xpos = clientX;
        this.ypos = clientY;

        this._calculateVelocities();
        this._setMoveClasses(this.settings.movingClass);

        if ($.isFunction(this.settings.moved)){
          this.settings.moved.call($this, this.settings);
        }
      }
    }
  };

  Kinetic.prototype._calculateVelocities = function (){
    this.velocity = this._capVelocity(this.prevXPos - this.xpos, this.settings.maxvelocity);
    this.velocityY = this._capVelocity(this.prevYPos - this.ypos, this.settings.maxvelocity);
  };

  Kinetic.prototype._end = function (){
    if (this.xpos && this.prevXPos && this.settings.decelerate === false){
      this.settings.decelerate = true;
      this._calculateVelocities();
      this.xpos = this.prevXPos = this.mouseDown = false;
      this._move();
    }
  };

  Kinetic.prototype._useTarget = function (target, event){
    if ($.isFunction(this.settings.filterTarget)){
      return this.settings.filterTarget.call(this, target, event) !== false;
    }
    return true;
  };

  Kinetic.prototype._start = function (clientX, clientY){
    this.mouseDown = true;
    this.velocity = this.prevXPos = 0;
    this.velocityY = this.prevYPos = 0;
    this.xpos = clientX;
    this.ypos = clientY;
  };

  Kinetic.prototype._resetMouse = function (){
    this.xpos = false;
    this.ypos = false;
    this.mouseDown = false;
  };

  Kinetic.prototype._decelerateVelocity = function (velocity, slowdown){
    return Math.floor(Math.abs(velocity)) === 0 ? 0 // is velocity less than 1?
      : velocity * slowdown; // reduce slowdown
  };

  Kinetic.prototype._capVelocity = function (velocity, max){
    var newVelocity = velocity;
    if (velocity > 0){
      if (velocity > max){
        newVelocity = max;
      }
    } else {
      if (velocity < (0 - max)){
        newVelocity = (0 - max);
      }
    }
    return newVelocity;
  };

  Kinetic.prototype._setMoveClasses = function (classes){
    // FIXME: consider if we want to apply PL #44, this should not remove
    // classes we have not defined on the element!
    var settings = this.settings;
    var $this = this.$el;

    $this.removeClass(settings.movingClass.up)
      .removeClass(settings.movingClass.down)
      .removeClass(settings.movingClass.left)
      .removeClass(settings.movingClass.right)
      .removeClass(settings.deceleratingClass.up)
      .removeClass(settings.deceleratingClass.down)
      .removeClass(settings.deceleratingClass.left)
      .removeClass(settings.deceleratingClass.right);

    if (this.velocity > 0){
      $this.addClass(classes.right);
    }
    if (this.velocity < 0){
      $this.addClass(classes.left);
    }
    if (this.velocityY > 0){
      $this.addClass(classes.down);
    }
    if (this.velocityY < 0){
      $this.addClass(classes.up);
    }

  };

  // do the actual kinetic movement
  Kinetic.prototype._move = function (){
    var $scroller = this.$el;
    var scroller = this.el;
    var self = this;
    var settings = self.settings;

    // set scrollLeft
    if (settings.x && scroller.scrollWidth > 0){
      this.scrollLeft(this.scrollLeft() + this.velocity);
      if (Math.abs(this.velocity) > 0){
        this.velocity = settings.decelerate ?
          self._decelerateVelocity(this.velocity, settings.slowdown) : this.velocity;
      }
    } else {
      this.velocity = 0;
    }

    // set scrollTop
    if (settings.y && scroller.scrollHeight > 0){
      this.scrollTop(this.scrollTop() + this.velocityY);
      if (Math.abs(this.velocityY) > 0){
        this.velocityY = settings.decelerate ?
          self._decelerateVelocity(this.velocityY, settings.slowdown) : this.velocityY;
      }
    } else {
      this.velocityY = 0;
    }

    self._setMoveClasses(settings.deceleratingClass);

    if ($.isFunction(settings.moved)){
      settings.moved.call(this, settings);
    }

    if (Math.abs(this.velocity) > 0 || Math.abs(this.velocityY) > 0){
      if (!this.moving) {
        this.moving = true;
        // tick for next movement
        window.requestAnimationFrame(function (){
          self.moving = false;
          self._move();
        });
      }
    } else {
      self.stop();
    }
  };

  // get current scroller to apply positioning to
  Kinetic.prototype._getScroller = function(){
    var $scroller = this.$el;
    if (this.$el.is('body') || this.$el.is('html')){
      $scroller = $(window);
    }
    return $scroller;
  };

  // set the scroll position
  Kinetic.prototype.scrollLeft = function(left){
    var $scroller = this._getScroller();
    if (typeof left === 'number'){
      $scroller.scrollLeft(left);
      this.settings.scrollLeft = left;
    } else {
      return $scroller.scrollLeft();
    }
  };
  Kinetic.prototype.scrollTop = function(top){
    var $scroller = this._getScroller();
    if (typeof top === 'number'){
      $scroller.scrollTop(top);
      this.settings.scrollTop = top;
    } else {
      return $scroller.scrollTop();
    }
  };

  Kinetic.prototype._attachListeners = function (){
    var $this = this.$el;
    var settings = this.settings;

    if ($.support.touch){
      $this
        .bind('touchstart', settings.events.touchStart)
        .bind('touchend', settings.events.inputEnd)
        .bind('touchmove', settings.events.touchMove);
    }

    $this
      .mousedown(settings.events.inputDown)
      .mouseup(settings.events.inputEnd)
      .mousemove(settings.events.inputMove);

    $this
      .click(settings.events.inputClick)
      .scroll(settings.events.scroll)
      .bind('selectstart', selectStart) // prevent selection when dragging
      .bind('dragstart', settings.events.dragStart);
  };

  Kinetic.prototype._detachListeners = function (){
    var $this = this.$el;
    var settings = this.settings;
    if ($.support.touch){
      $this
        .unbind('touchstart', settings.events.touchStart)
        .unbind('touchend', settings.events.inputEnd)
        .unbind('touchmove', settings.events.touchMove);
    }

    $this
      .unbind('mousedown', settings.events.inputDown)
      .unbind('mouseup', settings.events.inputEnd)
      .unbind('mousemove', settings.events.inputMove);

    $this
      .unbind('click', settings.events.inputClick)
      .unbind('scroll', settings.events.scroll)
      .unbind('selectstart', selectStart) // prevent selection when dragging
      .unbind('dragstart', settings.events.dragStart);
  };

  // EXPOSE KINETIC CONSTRUCTOR
  // ==========================
  $.Kinetic = Kinetic;

  // KINETIC PLUGIN DEFINITION
  // =======================

  $.fn.kinetic = function (option, callOptions) {
    return this.each(function () {
      var $this    = $(this);
      var instance = $this.data(Kinetic.DATA_KEY);
      var options  = $.extend({}, Kinetic.DEFAULTS, $this.data(), typeof option === 'object' && option);

      if (!instance) {
        $this.data(Kinetic.DATA_KEY, (instance = new Kinetic(this, options)));
      }

      if (typeof option === 'string') {
        instance[option](callOptions);
      }

    });
  };

}(window.jQuery || window.Zepto));

<commit_msg>add threshold setting<commit_after>/**
 jQuery.kinetic v2.0.6
 Dave Taylor http://davetayls.me

 @license The MIT License (MIT)
 @preserve Copyright (c) 2012 Dave Taylor http://davetayls.me
 */
(function ($){
  'use strict';

  var ACTIVE_CLASS = 'kinetic-active';

  /**
   * Provides requestAnimationFrame in a cross browser way.
   * http://paulirish.com/2011/requestanimationframe-for-smart-animating/
   */
  if (!window.requestAnimationFrame){

    window.requestAnimationFrame = ( function (){

      return window.webkitRequestAnimationFrame ||
        window.mozRequestAnimationFrame ||
        window.oRequestAnimationFrame ||
        window.msRequestAnimationFrame ||
        function (/* function FrameRequestCallback */ callback, /* DOMElement Element */ element){
          window.setTimeout(callback, 1000 / 60);
        };

    }());

  }

  // add touch checker to jQuery.support
  $.support = $.support || {};
  $.extend($.support, {
    touch: 'ontouchend' in document
  });
  var selectStart = function (){
    return false;
  };

  // KINETIC CLASS DEFINITION
  // ======================

  var Kinetic = function (element, settings) {
    this.settings = settings;
    this.el       = element;
    this.$el      = $(element);

    this._initElements();

    return this;
  };

  Kinetic.DATA_KEY = 'kinetic';
  Kinetic.DEFAULTS = {
    cursor: 'move',
    decelerate: true,
    triggerHardware: false,
    threshold: 0,
    y: true,
    x: true,
    slowdown: 0.9,
    maxvelocity: 40,
    throttleFPS: 60,
    movingClass: {
      up: 'kinetic-moving-up',
      down: 'kinetic-moving-down',
      left: 'kinetic-moving-left',
      right: 'kinetic-moving-right'
    },
    deceleratingClass: {
      up: 'kinetic-decelerating-up',
      down: 'kinetic-decelerating-down',
      left: 'kinetic-decelerating-left',
      right: 'kinetic-decelerating-right'
    }
  };

  // Public functions

  Kinetic.prototype.start = function (options){
    this.settings = $.extend(this.settings, options);
    this.velocity = options.velocity || this.velocity;
    this.velocityY = options.velocityY || this.velocityY;
    this.settings.decelerate = false;
    this._move();
  };

  Kinetic.prototype.end = function (){
    this.settings.decelerate = true;
  };

  Kinetic.prototype.stop = function (){
    this.velocity = 0;
    this.velocityY = 0;
    this.settings.decelerate = true;
    if ($.isFunction(this.settings.stopped)){
      this.settings.stopped.call(this);
    }
  };

  Kinetic.prototype.detach = function (){
    this._detachListeners();
    this.$el
      .removeClass(ACTIVE_CLASS)
      .css('cursor', '');
  };

  Kinetic.prototype.attach = function (){
    if (this.$el.hasClass(ACTIVE_CLASS)) {
      return;
    }
    this._attachListeners(this.$el);
    this.$el
      .addClass(ACTIVE_CLASS)
      .css('cursor', this.settings.cursor);
  };

  // Internal functions

  Kinetic.prototype._initElements = function (){
    this.$el.addClass(ACTIVE_CLASS);

    $.extend(this, {
      xpos: null,
      prevXPos: false,
      ypos: null,
      prevYPos: false,
      mouseDown: false,
      throttleTimeout: 1000 / this.settings.throttleFPS,
      lastMove: null,
      elementFocused: null
    });

    this.velocity = 0;
    this.velocityY = 0;

    // make sure we reset everything when mouse up
    $(document)
      .mouseup($.proxy(this._resetMouse, this))
      .click($.proxy(this._resetMouse, this));

    this._initEvents();

    this.$el.css('cursor', this.settings.cursor);

    if (this.settings.triggerHardware){
      this.$el.css({
        '-webkit-transform': 'translate3d(0,0,0)',
        '-webkit-perspective': '1000',
        '-webkit-backface-visibility': 'hidden'
      });
    }
  };

  Kinetic.prototype._initEvents = function(){
    var self = this;
    this.settings.events = {
      touchStart: function (e){
        var touch;
        if (self._useTarget(e.target, e)){
          touch = e.originalEvent.touches[0];
          self.threshold = self._threshold(e.target, e);
          self._start(touch.clientX, touch.clientY);
          e.stopPropagation();
        }
      },
      touchMove: function (e){
        var touch;
        if (self.mouseDown){
          touch = e.originalEvent.touches[0];
          self._inputmove(touch.clientX, touch.clientY);
          if (e.preventDefault){
            e.preventDefault();
          }
        }
      },
      inputDown: function (e){
        if (self._useTarget(e.target, e)){
          self.threshold = self._threshold(e.target, e);
          self._start(e.clientX, e.clientY);
          self.elementFocused = e.target;
          if (e.target.nodeName === 'IMG'){
            e.preventDefault();
          }
          e.stopPropagation();
        }
      },
      inputEnd: function (e){
        if (self._useTarget(e.target, e)){
          self._end();
          self.elementFocused = null;
          if (e.preventDefault){
            e.preventDefault();
          }
        }
      },
      inputMove: function (e){
        if (self.mouseDown){
          self._inputmove(e.clientX, e.clientY);
          if (e.preventDefault){
            e.preventDefault();
          }
        }
      },
      scroll: function (e){
        if ($.isFunction(self.settings.moved)){
          self.settings.moved.call(self, self.settings);
        }
        if (e.preventDefault){
          e.preventDefault();
        }
      },
      inputClick: function (e){
        if (Math.abs(self.velocity) > 0){
          e.preventDefault();
          return false;
        }
      },
      // prevent drag and drop images in ie
      dragStart: function (e){
        if (self._useTarget(e.target, e) && self.elementFocused){
          return false;
        }
      }
    };

    this._attachListeners(this.$el, this.settings);

  };

  Kinetic.prototype._inputmove = function (clientX, clientY){
    var $this = this.$el;
    var el = this.el;

    if (!this.lastMove || new Date() > new Date(this.lastMove.getTime() + this.throttleTimeout)){
      this.lastMove = new Date();

      if (this.mouseDown && (this.xpos || this.ypos)){
        if(this.threshold <= 0){
          if (this.elementFocused){
            $(this.elementFocused).blur();
            this.elementFocused = null;
            $this.focus();
          }

          this.settings.decelerate = false;
          this.velocity = this.velocityY = 0;

          var scrollLeft = this.scrollLeft();
          var scrollTop = this.scrollTop();
          var movedX = (clientX - this.xpos);
          var movedY = (clientY - this.ypos);

          this.scrollLeft(this.settings.x ? scrollLeft - movedX : scrollLeft);
          this.scrollTop(this.settings.y ? scrollTop - movedY : scrollTop);

          this.prevXPos = this.xpos;
          this.prevYPos = this.ypos;
          this.xpos = clientX;
          this.ypos = clientY;

          this._calculateVelocities();
          this._setMoveClasses(this.settings.movingClass);

          if ($.isFunction(this.settings.moved)){
            this.settings.moved.call($this, this.settings);
          }
        } else {
          var movedX = (clientX - this.xpos);
          var movedY = (clientY - this.ypos);
          var moved = Math.sqrt(movedX * movedX + movedY * movedY);
          this.threshold -= moved;
        }
      }
    }
  };

  Kinetic.prototype._calculateVelocities = function (){
    this.velocity = this._capVelocity(this.prevXPos - this.xpos, this.settings.maxvelocity);
    this.velocityY = this._capVelocity(this.prevYPos - this.ypos, this.settings.maxvelocity);
  };

  Kinetic.prototype._end = function (){
    if (this.xpos && this.prevXPos && this.settings.decelerate === false){
      this.settings.decelerate = true;
      this._calculateVelocities();
      this.xpos = this.prevXPos = this.mouseDown = false;
      this._move();
    }
  };

  Kinetic.prototype._useTarget = function (target, event){
    if ($.isFunction(this.settings.filterTarget)){
      return this.settings.filterTarget.call(this, target, event) !== false;
    }
    return true;
  };

  Kinetic.prototype._threshold = function (target, event){
    if ($.isFunction(this.settings.threshold)){
      return this.settings.threshold.call(this, target, event);
    }
    return this.settings.threshold;
  };

  Kinetic.prototype._start = function (clientX, clientY){
    this.mouseDown = true;
    this.velocity = this.prevXPos = 0;
    this.velocityY = this.prevYPos = 0;
    this.xpos = clientX;
    this.ypos = clientY;
  };

  Kinetic.prototype._resetMouse = function (){
    this.xpos = false;
    this.ypos = false;
    this.mouseDown = false;
  };

  Kinetic.prototype._decelerateVelocity = function (velocity, slowdown){
    return Math.floor(Math.abs(velocity)) === 0 ? 0 // is velocity less than 1?
      : velocity * slowdown; // reduce slowdown
  };

  Kinetic.prototype._capVelocity = function (velocity, max){
    var newVelocity = velocity;
    if (velocity > 0){
      if (velocity > max){
        newVelocity = max;
      }
    } else {
      if (velocity < (0 - max)){
        newVelocity = (0 - max);
      }
    }
    return newVelocity;
  };

  Kinetic.prototype._setMoveClasses = function (classes){
    // FIXME: consider if we want to apply PL #44, this should not remove
    // classes we have not defined on the element!
    var settings = this.settings;
    var $this = this.$el;

    $this.removeClass(settings.movingClass.up)
      .removeClass(settings.movingClass.down)
      .removeClass(settings.movingClass.left)
      .removeClass(settings.movingClass.right)
      .removeClass(settings.deceleratingClass.up)
      .removeClass(settings.deceleratingClass.down)
      .removeClass(settings.deceleratingClass.left)
      .removeClass(settings.deceleratingClass.right);

    if (this.velocity > 0){
      $this.addClass(classes.right);
    }
    if (this.velocity < 0){
      $this.addClass(classes.left);
    }
    if (this.velocityY > 0){
      $this.addClass(classes.down);
    }
    if (this.velocityY < 0){
      $this.addClass(classes.up);
    }

  };

  // do the actual kinetic movement
  Kinetic.prototype._move = function (){
    var $scroller = this.$el;
    var scroller = this.el;
    var self = this;
    var settings = self.settings;

    // set scrollLeft
    if (settings.x && scroller.scrollWidth > 0){
      this.scrollLeft(this.scrollLeft() + this.velocity);
      if (Math.abs(this.velocity) > 0){
        this.velocity = settings.decelerate ?
          self._decelerateVelocity(this.velocity, settings.slowdown) : this.velocity;
      }
    } else {
      this.velocity = 0;
    }

    // set scrollTop
    if (settings.y && scroller.scrollHeight > 0){
      this.scrollTop(this.scrollTop() + this.velocityY);
      if (Math.abs(this.velocityY) > 0){
        this.velocityY = settings.decelerate ?
          self._decelerateVelocity(this.velocityY, settings.slowdown) : this.velocityY;
      }
    } else {
      this.velocityY = 0;
    }

    self._setMoveClasses(settings.deceleratingClass);

    if ($.isFunction(settings.moved)){
      settings.moved.call(this, settings);
    }

    if (Math.abs(this.velocity) > 0 || Math.abs(this.velocityY) > 0){
      if (!this.moving) {
        this.moving = true;
        // tick for next movement
        window.requestAnimationFrame(function (){
          self.moving = false;
          self._move();
        });
      }
    } else {
      self.stop();
    }
  };

  // get current scroller to apply positioning to
  Kinetic.prototype._getScroller = function(){
    var $scroller = this.$el;
    if (this.$el.is('body') || this.$el.is('html')){
      $scroller = $(window);
    }
    return $scroller;
  };

  // set the scroll position
  Kinetic.prototype.scrollLeft = function(left){
    var $scroller = this._getScroller();
    if (typeof left === 'number'){
      $scroller.scrollLeft(left);
      this.settings.scrollLeft = left;
    } else {
      return $scroller.scrollLeft();
    }
  };
  Kinetic.prototype.scrollTop = function(top){
    var $scroller = this._getScroller();
    if (typeof top === 'number'){
      $scroller.scrollTop(top);
      this.settings.scrollTop = top;
    } else {
      return $scroller.scrollTop();
    }
  };

  Kinetic.prototype._attachListeners = function (){
    var $this = this.$el;
    var settings = this.settings;

    if ($.support.touch){
      $this
        .bind('touchstart', settings.events.touchStart)
        .bind('touchend', settings.events.inputEnd)
        .bind('touchmove', settings.events.touchMove);
    }

    $this
      .mousedown(settings.events.inputDown)
      .mouseup(settings.events.inputEnd)
      .mousemove(settings.events.inputMove);

    $this
      .click(settings.events.inputClick)
      .scroll(settings.events.scroll)
      .bind('selectstart', selectStart) // prevent selection when dragging
      .bind('dragstart', settings.events.dragStart);
  };

  Kinetic.prototype._detachListeners = function (){
    var $this = this.$el;
    var settings = this.settings;
    if ($.support.touch){
      $this
        .unbind('touchstart', settings.events.touchStart)
        .unbind('touchend', settings.events.inputEnd)
        .unbind('touchmove', settings.events.touchMove);
    }

    $this
      .unbind('mousedown', settings.events.inputDown)
      .unbind('mouseup', settings.events.inputEnd)
      .unbind('mousemove', settings.events.inputMove);

    $this
      .unbind('click', settings.events.inputClick)
      .unbind('scroll', settings.events.scroll)
      .unbind('selectstart', selectStart) // prevent selection when dragging
      .unbind('dragstart', settings.events.dragStart);
  };

  // EXPOSE KINETIC CONSTRUCTOR
  // ==========================
  $.Kinetic = Kinetic;

  // KINETIC PLUGIN DEFINITION
  // =======================

  $.fn.kinetic = function (option, callOptions) {
    return this.each(function () {
      var $this    = $(this);
      var instance = $this.data(Kinetic.DATA_KEY);
      var options  = $.extend({}, Kinetic.DEFAULTS, $this.data(), typeof option === 'object' && option);

      if (!instance) {
        $this.data(Kinetic.DATA_KEY, (instance = new Kinetic(this, options)));
      }

      if (typeof option === 'string') {
        instance[option](callOptions);
      }

    });
  };

}(window.jQuery || window.Zepto));

<|endoftext|>


<commit_before>Controller.ready("write", function() {
	// Resize Handler
	(function() {
		var handleResize = function() {
			var topHeight = $("#write-title").outerHeight();
			var bottomHeight = $("#navigation-bar").outerHeight();
			var totalHeight = window.innerHeight;
			var totalWidth = window.innerWidth;

			$("#write-body").css({
				top: topHeight,
				height: totalHeight - topHeight - bottomHeight,
				width: totalWidth - 4
			});
		};

		$(window).resize(handleResize);
		handleResize();
	})();

	// Editable Handler
	(function() {
		$("#write-title").on("click", "#document-title", function() {
			$(this).edit();
		});
	})();

	// Document
	var Document = new (function() {
		var UPDATE_DELAY = 5000; // ms

		var $content;
		var $save;
		var $words;
		var $goal;
		var contentValue;
		var timer;
		var saving = false;

		this.initialize = function() {
			$content = $("#write-body");
			$save = $("#save-display");
			$words = $("#document-words-current");
			$goal = $("#document-words-goal");

			initializeCalendarBorders();

			contentValue = $content.val();
			$content.on("keyup paste", onChange);

			updateWordDisplay();
		};

		this.save = function() {
			postContent();
		};

		var countWords = function(words) {
			return !words ? 0 : words.match(/[^\s]+/g).length;
		};

		var onChange = function(event) {
			if (timer) {
				clearTimeout(timer);
			}
			timer = setTimeout(postContent, UPDATE_DELAY);
			updateWordDisplay();
			$save.addClass("unsaved");
		};

		var postContent = function() {
			if ($content.val() === contentValue || saving) {
				return;
			}
			saving = true;
			$.ajax({
				url: $content.data("update-path"),
				method: "PUT",
				data: { content: $content.val() },
				async: false
			}).done(function() {
				trace("Saved");
				updateSaveDisplay();
				$save.removeClass("unsaved");
				contentValue = $content.val();
			}).fail(function() {
				trace("Failed to save");
			}).always(function() {
				saving = false;
			});
		};

		var updateSaveDisplay = function() {
			$save.find(".timestamp").livestamp(new Date);
		};

		var updateWordDisplay = function() {
			var numWords = countWords($content.val());
			var color = calculateWordColor(numWords, parseInt($goal.text()));
			$words.text(numWords).css("color", color);
			$(".calendar-day.selected").css({
				"borderColor": color,
				"backgroundColor": color
			});
		};

		var calculateWordColor = function(words, goal) {
			return calculateColor(words / goal);
		};

		var calculateColor = function(progress) {
			if (progress > 1) {
				progress = 1;
			}
			var redProgress = progress < 0.5 ? 1 : 1 - ((progress - 0.5) * 2);
			var greenProgress = progress > 0.5 ? 1 : progress * 2;
			var redHex = Math.floor(redProgress * 255).toString(16);
			var greenHex = Math.floor(greenProgress * 255).toString(16);
			var blueHex = "00";
			return "#" + (redHex.length == 1 ? "0" + redHex : redHex) + (greenHex.length == 1 ? "0" + greenHex : greenHex) + blueHex;
		};

		var initializeCalendarBorders = function() {
			$("[data-progress]").each(function() {
				var element = this, $element = $(element);
				var color = calculateColor($element.data("progress"));
				$element.css("borderColor", color);
			});
		};

		this.initialize();
	})();

	// Nav Bar Fader
	(function() {
		var timer;
		var $nav = $("#navigation-bar");

		var setFadeoutTimer = function() {
			timer = setTimeout(function() {
				$nav.fadeOut(2000);
			}, 10000);
		};

		$(window).on("mousemove", function() {
			if (timer) {
				clearTimeout(timer);
			}
			$nav.fadeIn();
			setFadeoutTimer();
		});

		setFadeoutTimer();
	})();

	// Other bindings
	(function() {
		// control+s
		$(window).on("keydown", function(event) {
			if (((event.which == 115 || event.which == 83) && event.ctrlKey) || event.which == 19) {
				event.preventDefault();
				Document.save();
			}
		});
		// save on page leave
		$(window).unload(Document.save);
	})();
});
<commit_msg>Immediate fadein if moved while fading out.<commit_after>Controller.ready("write", function() {
	// Resize Handler
	(function() {
		var handleResize = function() {
			var topHeight = $("#write-title").outerHeight();
			var bottomHeight = $("#navigation-bar").outerHeight();
			var totalHeight = window.innerHeight;
			var totalWidth = window.innerWidth;

			$("#write-body").css({
				top: topHeight,
				height: totalHeight - topHeight - bottomHeight,
				width: totalWidth - 4
			});
		};

		$(window).resize(handleResize);
		handleResize();
	})();

	// Editable Handler
	(function() {
		$("#write-title").on("click", "#document-title", function() {
			$(this).edit();
		});
	})();

	// Document
	var Document = new (function() {
		var UPDATE_DELAY = 5000; // ms

		var $content;
		var $save;
		var $words;
		var $goal;
		var contentValue;
		var timer;
		var saving = false;

		this.initialize = function() {
			$content = $("#write-body");
			$save = $("#save-display");
			$words = $("#document-words-current");
			$goal = $("#document-words-goal");

			initializeCalendarBorders();

			contentValue = $content.val();
			$content.on("keyup paste", onChange);

			updateWordDisplay();
		};

		this.save = function() {
			postContent();
		};

		var countWords = function(words) {
			return !words ? 0 : words.match(/[^\s]+/g).length;
		};

		var onChange = function(event) {
			if (timer) {
				clearTimeout(timer);
			}
			timer = setTimeout(postContent, UPDATE_DELAY);
			updateWordDisplay();
			$save.addClass("unsaved");
		};

		var postContent = function() {
			if ($content.val() === contentValue || saving) {
				return;
			}
			saving = true;
			$.ajax({
				url: $content.data("update-path"),
				method: "PUT",
				data: { content: $content.val() },
				async: false
			}).done(function() {
				trace("Saved");
				updateSaveDisplay();
				$save.removeClass("unsaved");
				contentValue = $content.val();
			}).fail(function() {
				trace("Failed to save");
			}).always(function() {
				saving = false;
			});
		};

		var updateSaveDisplay = function() {
			$save.find(".timestamp").livestamp(new Date);
		};

		var updateWordDisplay = function() {
			var numWords = countWords($content.val());
			var color = calculateWordColor(numWords, parseInt($goal.text()));
			$words.text(numWords).css("color", color);
			$(".calendar-day.selected").css({
				"borderColor": color,
				"backgroundColor": color
			});
		};

		var calculateWordColor = function(words, goal) {
			return calculateColor(words / goal);
		};

		var calculateColor = function(progress) {
			if (progress > 1) {
				progress = 1;
			}
			var redProgress = progress < 0.5 ? 1 : 1 - ((progress - 0.5) * 2);
			var greenProgress = progress > 0.5 ? 1 : progress * 2;
			var redHex = Math.floor(redProgress * 255).toString(16);
			var greenHex = Math.floor(greenProgress * 255).toString(16);
			var blueHex = "00";
			return "#" + (redHex.length == 1 ? "0" + redHex : redHex) + (greenHex.length == 1 ? "0" + greenHex : greenHex) + blueHex;
		};

		var initializeCalendarBorders = function() {
			$("[data-progress]").each(function() {
				var element = this, $element = $(element);
				var color = calculateColor($element.data("progress"));
				$element.css("borderColor", color);
			});
		};

		this.initialize();
	})();

	// Nav Bar Fader
	(function() {
		var timer;
		var $nav = $("#navigation-bar");

		var setFadeoutTimer = function() {
			timer = setTimeout(function() {
				$nav.fadeOut(2000);
				timer = null;
			}, 10000);
		};

		$(window).on("mousemove", function() {
			if (timer) {
				clearTimeout(timer);
			} else {
				$nav.stop(true, true);
			}
			$nav.fadeIn();
			setFadeoutTimer();
		});

		setFadeoutTimer();
	})();

	// Other bindings
	(function() {
		// control+s
		$(window).on("keydown", function(event) {
			if (((event.which == 115 || event.which == 83) && event.ctrlKey) || event.which == 19) {
				event.preventDefault();
				Document.save();
			}
		});
		// save on page leave
		$(window).unload(Document.save);
	})();
});
<|endoftext|>


<commit_before>describe('deumdify', function () {
  'use strict';

  var browserify = require('browserify')
    , concat = require('concat-stream')
    , assert = require('assert')
    , jsdom = require('jsdom')
    , deumdify = require('..')
    , path = require('path')
    , fs = require('fs')
    , b;

  var html = fs.readFileSync(path.join(__dirname, 'fixture/index.html'), {
    encoding: 'utf8'
  });

  var options = {
    entries: [ path.resolve(__dirname, 'input/main.js') ],
    detectGlobals: false,
    standalone: 'foo'
  };

  beforeEach(function () {
    b = browserify(options).plugin(deumdify);
  });

  it('is a Browserify plugin', function () {
    assert.strictEqual(typeof deumdify, 'function');
  });

  it('bails out if there is no UMD wrapper', function () {
    assert.ok(b.pipeline.get('prune-umd'));
    options.standalone = '';
    b = browserify(options).plugin(deumdify);
    assert.ok(!b.pipeline.get('prune-umd'));
    options.standalone = 'foo';
  });

  it('readds the transform stream when the `reset` event is fired', function (done) {
    assert.ok(b.pipeline.get('prune-umd'));
    b.once('reset', function () {
      assert.ok(b.pipeline.get('prune-umd'));
      done();
    });
    b.reset();
  });

  it('works', function (done) {
    b.bundle().pipe(concat({ encoding: 'string' }, function (output) {
      jsdom.env({
        html: html.replace(/<script[\s\S]+\/script>/, ''),
        src: [ output, 'window.foo();' ],
        done: function (err, window) {
          assert.ifError(err);
          assert.strictEqual(typeof window.foo, 'function');
          assert.strictEqual(window.bar, 'baz qux; typeof process is: undefined');
          done();
        }
      });
    }));
  });

  it('removes AMD support', function (done) {
    var ws = fs.createWriteStream(path.join(__dirname, 'fixture/bundle.js'));

    ws.on('finish', function () {
      var window = jsdom.jsdom(html).parentWindow;

      window.onModuleLoaded = function () {
        try {
          assert.strictEqual(typeof window.foo, 'function');
          window.foo();
          assert.strictEqual(window.bar, 'baz qux; typeof process is: undefined');
        } catch (e) {
          return done(e);
        }

        done();
      };
    });

    b.bundle().pipe(ws);
  });

  it('removes CommonJS support', function () {
    assert.throws(function () {
      require('./fixture/bundle');
    });
  });
});
<commit_msg>[test] Tweak tests for node 0.8<commit_after>describe('deumdify', function () {
  'use strict';

  var browserify = require('browserify')
    , concat = require('concat-stream')
    , assert = require('assert')
    , jsdom = require('jsdom')
    , deumdify = require('..')
    , path = require('path')
    , fs = require('fs')
    , b;

  var html = fs.readFileSync(path.join(__dirname, 'fixture/index.html'), 'utf8');

  var options = {
    entries: [ path.resolve(__dirname, 'input/main.js') ],
    detectGlobals: false,
    standalone: 'foo'
  };

  beforeEach(function () {
    b = browserify(options).plugin(deumdify);
  });

  it('is a Browserify plugin', function () {
    assert.strictEqual(typeof deumdify, 'function');
  });

  it('bails out if there is no UMD wrapper', function () {
    assert.ok(b.pipeline.get('prune-umd'));
    options.standalone = '';
    b = browserify(options).plugin(deumdify);
    assert.ok(!b.pipeline.get('prune-umd'));
    options.standalone = 'foo';
  });

  it('readds the transform stream when the `reset` event is fired', function (done) {
    assert.ok(b.pipeline.get('prune-umd'));
    b.once('reset', function () {
      assert.ok(b.pipeline.get('prune-umd'));
      done();
    });
    b.reset();
  });

  it('works', function (done) {
    b.bundle().pipe(concat({ encoding: 'string' }, function (output) {
      jsdom.env({
        html: html.replace(/<script[\s\S]+\/script>/, ''),
        src: [ output, 'window.foo();' ],
        done: function (err, window) {
          assert.ifError(err);
          assert.strictEqual(typeof window.foo, 'function');
          assert.strictEqual(window.bar, 'baz qux; typeof process is: undefined');
          done();
        }
      });
    }));
  });

  it('removes AMD support', function (done) {
    var ws = fs.createWriteStream(path.join(__dirname, 'fixture/bundle.js'))
      , bundle = b.bundle();

    bundle.on('data', ws.write.bind(ws));
    bundle.on('end', function () {
      var window = jsdom.jsdom(html).parentWindow;

      window.onModuleLoaded = function () {
        try {
          assert.strictEqual(typeof window.foo, 'function');
          window.foo();
          assert.strictEqual(window.bar, 'baz qux; typeof process is: undefined');
        } catch (e) {
          return done(e);
        }

        done();
      };
    });
  });

  it('removes CommonJS support', function () {
    assert.throws(function () {
      require('./fixture/bundle');
    });
  });
});
<|endoftext|>


<commit_before>var request = require('superagent');
var cheerio = require('cheerio');
var config = require('../config');

// cheerio配置
var cheerioConfig = {
  // True 屏蔽urp不规范源码
  xmlMode: true,
  decodeEntities: true,
  lowerCaseTags: true,
  lowerCaseAttributeNames: true,
  ignoreWhitespace: true
};

/**
 * 处理校历信息
 * @return {Promise} 处理结果
 */
exports.calendar = function () {
  // 发起请求
  return request
    .get('http://my.hpu.edu.cn/viewschoolcalendar3.jsp')
    .then(calendarContent => {
      return new Promise((resolve, reject) => {
        var calendar = {};

        // 处理日历
        $ = cheerio.load(calendarContent.text, cheerioConfig);

        var fontItem = $('.red');
        var aItem = $('a').text().split('--');
        var weekItem = $('span').eq(0).text().match(/星期[\u4e00-\u9fa5]/);

        // 年月日
        calendar.date = fontItem.eq(0).text();
        // 周次
        calendar.weekly = fontItem.eq(1).text();
        // 总周数
        calendar.totalWeekly = fontItem.eq(2).text();
        // 星期几
        calendar.week = weekItem !== null ? weekItem[0] : '';
        // 开学日期
        calendar.termStart = aItem.length > 0 ? aItem[0] : '';
        // 放假日期
        calendar.termEnd = aItem.length > 0 ? aItem[1] : '';
        // 当前学期 1 秋季 2 春季
        calendar.currentTerm = calendar.termEnd ? (calendar.termEnd.split('-')[1] < 4 ? 1 : 2) : '';

        if (calendar.weekly) {
          resolve(calendar);
        } else {
          reject('日历获取失败');
        }
      })
    })
}<commit_msg>Fix currentTerm<commit_after>var request = require('superagent');
var cheerio = require('cheerio');
var config = require('../config');

// cheerio配置
var cheerioConfig = {
  // True 屏蔽urp不规范源码
  xmlMode: true,
  decodeEntities: true,
  lowerCaseTags: true,
  lowerCaseAttributeNames: true,
  ignoreWhitespace: true
};

/**
 * 处理校历信息
 * @return {Promise} 处理结果
 */
exports.calendar = function () {
  // 发起请求
  return request
    .get('http://my.hpu.edu.cn/viewschoolcalendar3.jsp')
    .then(calendarContent => {
      return new Promise((resolve, reject) => {
        var calendar = {};

        // 处理日历
        $ = cheerio.load(calendarContent.text, cheerioConfig);

        var fontItem = $('.red');
        var aItem = $('a').text().split('--');
        var weekItem = $('span').eq(0).text().match(/星期[\u4e00-\u9fa5]/);

        // 年月日
        calendar.date = fontItem.eq(0).text();
        // 周次
        calendar.weekly = fontItem.eq(1).text();
        // 总周数
        calendar.totalWeekly = fontItem.eq(2).text();
        // 星期几
        calendar.week = weekItem !== null ? weekItem[0] : '';
        // 开学日期
        calendar.termStart = aItem.length > 0 ? aItem[0] : '';
        // 放假日期
        calendar.termEnd = aItem.length > 0 ? aItem[1] : '';
        // 当前学期 1 秋季 2 春季
        calendar.currentTerm = calendar.termEnd ? calendar.termStart.split('-')[0] + '-' + calendar.termEnd.split('-')[0] + '-' + (calendar.termEnd.split('-')[1] < 4 ? 1 : 2) + '-1' : '';

        if (calendar.weekly) {
          resolve(calendar);
        } else {
          reject('日历获取失败');
        }
      })
    })
}<|endoftext|>


<commit_before>import loconfig from '../utils/config.js';
import message from '../utils/message.js';
import notification from '../utils/notification.js';
import postcss, { pluginsMap as postcssPluginsMap } from '../utils/postcss.js';
import { PurgeCSS } from 'purgecss';
import resolve from '../utils/template.js';
import { writeFile } from 'node:fs/promises';
import { basename } from 'node:path';
import { promisify } from 'node:util';
import sass from 'node-sass';

const sassRender = promisify(sass.render);

let postcssProcessor;

/**
 * @const {object} defaultSassOptions     - The default shared Sass options.
 * @const {object} developmentSassOptions - The predefined Sass options for development.
 * @const {object} productionSassOptions  - The predefined Sass options for production.
 */
export const defaultSassOptions = {
    omitSourceMapUrl: true,
    sourceMap: true,
    sourceMapContents: true,
};
export const developmentSassOptions = Object.assign({}, defaultSassOptions, {
    outputStyle: 'expanded',
});
export const productionSassOptions = Object.assign({}, defaultSassOptions, {
    outputStyle: 'compressed',
});

/**
 * @const {object} defaultPostCSSOptions     - The default shared PostCSS options.
 * @const {object} developmentPostCSSOptions - The predefined PostCSS options for development.
 * @const {object} productionPostCSSOptions  - The predefined PostCSS options for production.
 */
export const defaultPostCSSOptions = {
    processor: {
        map: {
            annotation: false,
            inline: false,
            sourcesContent: true,
        },
    },
};
export const developmentPostCSSOptions = Object.assign({}, defaultPostCSSOptions);
export const productionPostCSSOptions  = Object.assign({}, defaultPostCSSOptions);

/**
 * @const {object|boolean} developmentStylesArgs - The predefined `compileStyles()` options for development.
 * @const {object|boolean} productionStylesArgs  - The predefined `compileStyles()` options for production.
 */
export const developmentStylesArgs = [
    developmentSassOptions,
    developmentPostCSSOptions,
];
export const productionStylesArgs  = [
    productionSassOptions,
    productionPostCSSOptions,
];

/**
 * Compiles and minifies main Sass files to CSS.
 *
 * @todo Add deep merge of `postcssOptions` to better support customization
 *     of default processor options.
 *
 * @async
 * @param  {object}         [sassOptions=null]    - Customize the Sass render API options.
 *     If `null`, default production options are used.
 * @param  {object|boolean} [postcssOptions=null] - Customize the PostCSS processor API options.
 *     If `null`, default production options are used.
 *     If `false`, PostCSS processing will be ignored.
 * @return {Promise}
 */
export default async function compileStyles(sassOptions = null, postcssOptions = null) {
    if (sassOptions == null) {
        sassOptions = productionSassOptions;
    } else if (
        sassOptions !== developmentSassOptions &&
        sassOptions !== productionSassOptions
    ) {
        sassOptions = Object.assign({}, defaultSassOptions, sassOptions);
    }

    if (postcss) {
        if (postcssOptions == null) {
            postcssOptions = productionPostCSSOptions;
        } else if (
            postcssOptions !== false &&
            postcssOptions !== developmentPostCSSOptions &&
            postcssOptions !== productionPostCSSOptions
        ) {
            postcssOptions = Object.assign({}, defaultPostCSSOptions, postcssOptions);
        }
    }

    loconfig.tasks.styles.forEach(async ({
        infile,
        outfile,
        label = null
    }) => {
        const filestem = basename((outfile || 'undefined'), '.css');

        const timeLabel = `${label || `${filestem}.css`} compiled in`;
        console.time(timeLabel);

        try {
            infile  = resolve(infile);
            outfile = resolve(outfile);

            let result = await sassRender(Object.assign({}, sassOptions, {
                file: infile,
                outFile: outfile,
            }));

            if (postcss && postcssOptions) {
                if (typeof postcssProcessor === 'undefined') {
                    postcssProcessor = createPostCSSProcessor(
                        postcssPluginsMap,
                        postcssOptions
                    );
                }

                result = await postcssProcessor.process(
                    result.css,
                    Object.assign({}, postcssOptions.processor, {
                        from: outfile,
                        to: outfile,
                    })
                );

                if (result.warnings) {
                    const warnings = result.warnings();
                    if (warnings.length) {
                        message(`Error processing ${label || `${filestem}.css`}`, 'warning');
                        warnings.forEach((warn) => {
                            message(warn.toString());
                        });
                    }
                }
            }

            try {
                await writeFile(outfile, result.css).then(() => {
                    // Purge CSS once file exists.
                    purgeUnusedCSS(outfile, `${label || `${filestem}.css`}`);
                });

                if (result.css) {
                    message(`${label || `${filestem}.css`} compiled`, 'success', timeLabel);
                } else {
                    message(`${label || `${filestem}.css`} is empty`, 'notice', timeLabel);
                }
            } catch (err) {
                message(`Error compiling ${label || `${filestem}.css`}`, 'error');
                message(err);

                notification({
                    title:   `${label || `${filestem}.css`} save failed 🚨`,
                    message: `Could not save stylesheet to ${label || `${filestem}.css`}`
                });
            }

            if (result.map) {
                try {
                    await writeFile(outfile + '.map', result.map.toString());
                } catch (err) {
                    message(`Error compiling ${label || `${filestem}.css.map`}`, 'error');
                    message(err);

                    notification({
                        title:   `${label || `${filestem}.css.map`} save failed 🚨`,
                        message: `Could not save sourcemap to ${label || `${filestem}.css.map`}`
                    });
                }
            }
        } catch (err) {
            message(`Error compiling ${label || `${filestem}.scss`}`, 'error');
            message(err.formatted || err);

            notification({
                title:   `${label || `${filestem}.scss`} compilation failed 🚨`,
                message: (err.formatted || `${err.name}: ${err.message}`)
            });
        }
    });
};

/**
 * Creates a PostCSS Processor with the given plugins and options.
 *
 * @param {array<(function|object)>|object<string, (function|object)>} pluginsListOrMap -
 *     A list or map of plugins.
 *     If a map of plugins, the plugin name looks up `options`.
 * @param {object}              options - The PostCSS options.
 */
function createPostCSSProcessor(pluginsListOrMap, options)
{
    let plugins;

    if (Array.isArray(pluginsListOrMap)) {
        plugins = pluginsListOrMap;
    } else {
        plugins = [];

        for (let [ name, plugin ] of Object.entries(pluginsListOrMap)) {
            if (name in options) {
                plugin = plugin[name](options[name]);
            }

            plugins.push(plugin);
        }
    }

    return postcss(plugins);
}

/**
 * Purge unused styles of minified CSS files.
 *
 * @async
 *
 * @param  {string} outfile  - The path of the CSS file.
 * @param  {string} filestem - The CSS file name.
 */
async function purgeUnusedCSS(outfile = false, filestem) {

    const timeLabel = `${filestem}.css purged in`;
        console.time(timeLabel);

    const purgeCSSContentFiles = Array.from(loconfig.tasks.purgeCSS.content);

    const purgeCSSResults = await new PurgeCSS().purge({
        content: purgeCSSContentFiles,
        css: [outfile],
        rejected: true,
        defaultExtractor: content => content.match(/[a-z0-9_\-\\\/\@]+/gi) || [],
        safelist: {
            standard: [/^((?!\bu-gc-).)*$/]
        }
    })

    for(let result of purgeCSSResults) {
        await writeFile(outfile, result.css)

        message(`${filestem}.css purged`, 'cleaning', timeLabel);
    }
}
<commit_msg>Manually apply some #108 suggestions<commit_after>import loconfig from '../utils/config.js';
import message from '../utils/message.js';
import notification from '../utils/notification.js';
import postcss, { pluginsMap as postcssPluginsMap } from '../utils/postcss.js';
import { PurgeCSS } from 'purgecss';
import resolve from '../utils/template.js';
import { writeFile } from 'node:fs/promises';
import { basename } from 'node:path';
import { promisify } from 'node:util';
import sass from 'node-sass';

const sassRender = promisify(sass.render);

let postcssProcessor;

/**
 * @const {object} defaultSassOptions     - The default shared Sass options.
 * @const {object} developmentSassOptions - The predefined Sass options for development.
 * @const {object} productionSassOptions  - The predefined Sass options for production.
 */
export const defaultSassOptions = {
    omitSourceMapUrl: true,
    sourceMap: true,
    sourceMapContents: true,
};
export const developmentSassOptions = Object.assign({}, defaultSassOptions, {
    outputStyle: 'expanded',
});
export const productionSassOptions = Object.assign({}, defaultSassOptions, {
    outputStyle: 'compressed',
});

/**
 * @const {object} defaultPostCSSOptions     - The default shared PostCSS options.
 * @const {object} developmentPostCSSOptions - The predefined PostCSS options for development.
 * @const {object} productionPostCSSOptions  - The predefined PostCSS options for production.
 */
export const defaultPostCSSOptions = {
    processor: {
        map: {
            annotation: false,
            inline: false,
            sourcesContent: true,
        },
    },
};
export const developmentPostCSSOptions = Object.assign({}, defaultPostCSSOptions);
export const productionPostCSSOptions  = Object.assign({}, defaultPostCSSOptions);

/**
 * @const {object|boolean} developmentStylesArgs - The predefined `compileStyles()` options for development.
 * @const {object|boolean} productionStylesArgs  - The predefined `compileStyles()` options for production.
 */
export const developmentStylesArgs = [
    developmentSassOptions,
    developmentPostCSSOptions,
];
export const productionStylesArgs  = [
    productionSassOptions,
    productionPostCSSOptions,
];

/**
 * Compiles and minifies main Sass files to CSS.
 *
 * @todo Add deep merge of `postcssOptions` to better support customization
 *     of default processor options.
 *
 * @async
 * @param  {object}         [sassOptions=null]    - Customize the Sass render API options.
 *     If `null`, default production options are used.
 * @param  {object|boolean} [postcssOptions=null] - Customize the PostCSS processor API options.
 *     If `null`, default production options are used.
 *     If `false`, PostCSS processing will be ignored.
 * @return {Promise}
 */
export default async function compileStyles(sassOptions = null, postcssOptions = null) {
    if (sassOptions == null) {
        sassOptions = productionSassOptions;
    } else if (
        sassOptions !== developmentSassOptions &&
        sassOptions !== productionSassOptions
    ) {
        sassOptions = Object.assign({}, defaultSassOptions, sassOptions);
    }

    if (postcss) {
        if (postcssOptions == null) {
            postcssOptions = productionPostCSSOptions;
        } else if (
            postcssOptions !== false &&
            postcssOptions !== developmentPostCSSOptions &&
            postcssOptions !== productionPostCSSOptions
        ) {
            postcssOptions = Object.assign({}, defaultPostCSSOptions, postcssOptions);
        }
    }

    loconfig.tasks.styles.forEach(async ({
        infile,
        outfile,
        label = null
    }) => {
        const filestem = basename((outfile || 'undefined'), '.css');

        const timeLabel = `${label || `${filestem}.css`} compiled in`;
        console.time(timeLabel);

        try {
            infile  = resolve(infile);
            outfile = resolve(outfile);

            let result = await sassRender(Object.assign({}, sassOptions, {
                file: infile,
                outFile: outfile,
            }));

            if (postcss && postcssOptions) {
                if (typeof postcssProcessor === 'undefined') {
                    postcssProcessor = createPostCSSProcessor(
                        postcssPluginsMap,
                        postcssOptions
                    );
                }

                result = await postcssProcessor.process(
                    result.css,
                    Object.assign({}, postcssOptions.processor, {
                        from: outfile,
                        to: outfile,
                    })
                );

                if (result.warnings) {
                    const warnings = result.warnings();
                    if (warnings.length) {
                        message(`Error processing ${label || `${filestem}.css`}`, 'warning');
                        warnings.forEach((warn) => {
                            message(warn.toString());
                        });
                    }
                }
            }

            try {
                await writeFile(outfile, result.css).then(() => {
                    // Purge CSS once file exists.
                    if (outfile) {
                        purgeUnusedCSS(outfile, filestem);
                    }
                });

                if (result.css) {
                    message(`${label || `${filestem}.css`} compiled`, 'success', timeLabel);
                } else {
                    message(`${label || `${filestem}.css`} is empty`, 'notice', timeLabel);
                }
            } catch (err) {
                message(`Error compiling ${label || `${filestem}.css`}`, 'error');
                message(err);

                notification({
                    title:   `${label || `${filestem}.css`} save failed 🚨`,
                    message: `Could not save stylesheet to ${label || `${filestem}.css`}`
                });
            }

            if (result.map) {
                try {
                    await writeFile(outfile + '.map', result.map.toString());
                } catch (err) {
                    message(`Error compiling ${label || `${filestem}.css.map`}`, 'error');
                    message(err);

                    notification({
                        title:   `${label || `${filestem}.css.map`} save failed 🚨`,
                        message: `Could not save sourcemap to ${label || `${filestem}.css.map`}`
                    });
                }
            }
        } catch (err) {
            message(`Error compiling ${label || `${filestem}.scss`}`, 'error');
            message(err.formatted || err);

            notification({
                title:   `${label || `${filestem}.scss`} compilation failed 🚨`,
                message: (err.formatted || `${err.name}: ${err.message}`)
            });
        }
    });
};

/**
 * Creates a PostCSS Processor with the given plugins and options.
 *
 * @param {array<(function|object)>|object<string, (function|object)>} pluginsListOrMap -
 *     A list or map of plugins.
 *     If a map of plugins, the plugin name looks up `options`.
 * @param {object}              options - The PostCSS options.
 */
function createPostCSSProcessor(pluginsListOrMap, options)
{
    let plugins;

    if (Array.isArray(pluginsListOrMap)) {
        plugins = pluginsListOrMap;
    } else {
        plugins = [];

        for (let [ name, plugin ] of Object.entries(pluginsListOrMap)) {
            if (name in options) {
                plugin = plugin[name](options[name]);
            }

            plugins.push(plugin);
        }
    }

    return postcss(plugins);
}

/**
 * Purge unused styles of minified CSS files.
 *
 * @async
 *
 * @param  {string} outfile  - The path of the CSS file.
 * @param  {string} filestem - The CSS file name.
 */
async function purgeUnusedCSS(outfile, filestem) {

    const timeLabel = `${filestem}.css purged in`;
        console.time(timeLabel);

    const purgeCSSContentFiles = Array.from(loconfig.tasks.purgeCSS.content);

    const purgeCSSResults = await new PurgeCSS().purge({
        content: purgeCSSContentFiles,
        css: [outfile],
        rejected: true,
        defaultExtractor: content => content.match(/[a-z0-9_\-\\\/\@]+/gi) || [],
        safelist: {
            standard: [/^((?!\bu-gc-).)*$/]
        }
    })

    for(let result of purgeCSSResults) {
        await writeFile(outfile, result.css)

        message(`${filestem}.css purged`, 'chore', timeLabel);
    }
}
<|endoftext|>


<commit_before>/**
 * Manager.js
 *
 * @param  {Object} window    Window
 * @param  {[type]} factory [description]
 */
( function ( window, factory ) {

    'use strict';

    if ( typeof define === 'function' && define.amd ) {

        define( [
            'jquery',
            './Util',
            './Config',
            './Ajax',
        ], function ( $, Util, Config, Ajax ) {
            return factory( window, $, Util, Config, Ajax );
        } );

    } else if ( typeof exports === 'object' ) {

        module.exports = factory(
            window,
            require( 'jquery' ),
            require( './Util' ),
            require( './Config' ),
            require( './Ajax' )
        );

    } else {

        window.StateManager = window.StateManager || {};

        window.StateManager.Manager = factory(
            window,
            window.jQuery,
            window.StateManager.Util,
            window.StateManager.Config,
            window.StateManager.Ajax
        );

    }

} ( window, function ( window, $, Util, Config, Ajax ) {

    'use strict';

    // define any private variables
    var name = 'Manager',
        debugEnabled = true,
        debug = debugEnabled ? Util.debug : function () {},
        initialized = false,
        history = null,
        mode = 'traditional', // will either be 'dynamic' or 'traditional'
        $contentHolder,
        $window = $( window ),
        $body = $( 'body' ),
        prefetchCache = {
            list: [],
            limit: null
        },
        ajaxCache = {
            list: [],
            limit: null
        },
        ajaxQueue = []
    ;

    //////////////////////////////////////////////////////////////////////////////////////

    function init() {

        if ( initialized ) {
            return;
        }

        prefetchCache.limit = Config.get( 'prefetchCacheLimit' );
        ajaxCache.limit = Config.get( 'ajaxCacheLimit' );

        $contentHolder = $( Config.get( 'content' ) ).first();

        if ( $contentHolder.length < 1 ) {
            return;
        }

        initHistory();

        initState({
            isInitLoad: true
        });

        ajaxEventListener();

        initialized = true;

    }

    /**
     * Set up history object and add popstate event listener
     */
    function initHistory() {

        // set up our private alias to the history.js adapter
        history = window.history;

        mode = Util.getMode();

        if ( mode === 'traditional' ) {
            return;
        }

        Ajax.ajaxifyLinks( $body );

        // bind to statechanges
        $window.on( 'popstate', function () {

            gotoUrl(
                currentStateUrl(),
                { popstate: true }
            );

            $window.trigger( 'StateManager.PopState' );

        } );

    }

    /**
     * Initializes whatever state has been loaded
     * (either on page load or on a history push.)
     *
     * @param  {Object} options
     */
    function initState( options ) {

        var isInitLoad = false,
            pageTitle = 'title' in options ? options.title : document.title
        ;

        if ( typeof options !== 'undefined' && typeof options.isInitLoad !== 'undefined' ) {
            isInitLoad = options.isInitLoad
        }

        Util.setDocumentTitle( pageTitle );

        if ( ! isInitLoad ) {
            $window.trigger(
                'EventTrackAjax.RecordPageview',
                {
                    url: currentStateUrl(),
                    title: pageTitle
                }
            );
        }

        prefetchUpcomingUrls();

        $window.trigger( 'StateManager.AfterInitState' );

    }

    /**
     * Loop through anchor tags with data-prefetch added
     */
    function prefetchUpcomingUrls() {

        if ( mode !== 'dynamic' ) {
            return false;
        }

        // stagger prefetch of additional URLs
        $( 'a[data-prefetch]' ).each( function ( index ) {

            var thisHref = Util.fullyQualifyUrl( $( this ).attr( 'href' ) );

            // make sure we don't reload the page we're on
            if ( thisHref !== currentStateUrl() ) {
                setTimeout( function () {
                    prefetchUrl( thisHref );
                }, 50 * ( index + 1 ) );
            }

        });
    }

    /**
     * Inserts url data to the DOM. Called by gotoUrl().
     *
     * @param  {Object} data
     * @param  {Object} options
     */
    function renderUrl( data ) {

        // drop in image_box HTML
        $contentHolder.html( data.data );

        Ajax.ajaxifyLinks( $contentHolder );

        $body.removeClass().addClass( data.classes );

        initState( data );

    }

    /**
     * Toggles whether we're waiting for content to load.
     *
     * @param  {Boolean} isLoading
     */
    function toggleLoading( isLoading ) {

        if ( isLoading ) {

            // reveal loading state
            $window.trigger( 'StateManager.LoadingReveal' );

        } else {

            // hide loading state
            $window.trigger( 'StateManager.LoadingComplete' );

        }

    }

    /**
     * Handles loading of a particular url, then moves along to rendering.
     *
     * @param  {String} url
     * @param  {Object} options
     */
    function gotoUrl( url, options ) {

        options = options || {};
        options = $.extend( { url: url, popstate: false }, options );

        $( window )
            .off( 'StateManager.FetchedData' )
            .on( 'StateManager.FetchedData', function ( event, data ) {

                // Only proceed for currently request url.
                if ( data.url !== url ) {
                    return;
                }

                if ( ! options.popstate ) {
                    // history.pushState has to happen before rendering.
                    // Otherwise the page title in the history gets messed up.
                    $window.trigger( 'StateManager.PushState', options );
                }

                toggleLoading( false );
                renderUrl( data );

                // Unbind window event.
                $( this ).off( 'StateManager.FetchedData' );

            } );

        var data = getUrlData({
                url: url,
                afterAjaxLoad: function ( data, textStatus, xhr ) {

                    // save the new data to the cache
                    data = saveCacheData( ajaxCache, xhr.requestUrl, data );

                    $( window ).trigger( 'StateManager.FetchedData', data );

                }
            })
        ;

        // Waiting on an ajax request to be completed.
        if ( typeof data.loading !== 'undefined' && data.loading ) {
            toggleLoading( true );
        }

    }

    /**
     * Returns html data for a particular url. This data may be cached already. If not
     *       we make an AJAX call to load the data.
     *
     * @param  {Object} options Includes: url, isPrefetch
     * @return {Object}         Trigger the loading function
     */
    function getUrlData( options ) {

        var trackProgress = true,
            data
        ;

        if ( data = checkCacheForData( ajaxCache.list, options.url ) ) {
            return $( window ).trigger( 'StateManager.FetchedData', data );
        }

        if ( data = checkCacheForData( prefetchCache.list, options.url ) ) {
            return $( window ).trigger( 'StateManager.FetchedData', data );
        }

        if ( data = checkCacheForData( ajaxQueue, options.url ) ) {
            return {
                loading: true
            };
        }

        // Disable tracking progress if this is a prefetch request
        if ( options.isPrefetch ) {
            trackProgress = false;
        }

        ajaxQueue.push({ url: options.url });

        // if we reach this point, the data wasn't in the cache. make ajax request.
        Ajax.loadAjax({
            url: options.url,
            dataType: 'html',
            trackProgress: trackProgress,
            success: options.afterAjaxLoad,
            error: function () {
                location.reload( true );
            }
        });

        return {
            loading: true
        };

    }

    /**
     * Prefetches URLs we think the user is likely to load in the cache.
     *
     * @param {String} url Marker to decide which pages should be cached
     */
    function prefetchUrl( url ) {

        if ( mode === 'traditional' ) {
            return;
        }

        // no, let's make the request
        getUrlData({
            url: url,
            isPrefetch: true,
            afterAjaxLoad: function ( data ) {
                removeUrlFromAjaxQueue( url );
                data = saveCacheData( prefetchCache, url, data );

                $( window ).trigger( 'StateManager.FetchedData', data );
            }
        });
    }

    /**
     * Check supplied cache array for a match against the URI
     *
     * @param  {Array} cacheList  prefetch, ajax, or ajaxQueue cache
     * @param  {String} url       used to identify the correct data in the loop
     * @return {Object|Boolean}   cacheList data if present or false
     */
    function checkCacheForData( cacheList, url ) {

        if ( cacheList.length > 0 ) {
            for ( var i = 0; i < cacheList.length; i++ ) {
                if ( cacheList[i].url === url ) {
                    return cacheList[i];
                }
            }
        }

        return false;

    }

    /**
     * A function for passing different cache arrays with url and data to save
     *
     * @param  {Array} cacheType  prefetch or ajax cache
     * @param  {String} url       Page URI to cache
     * @param  {String} data      Data from page to be cached, it get's parsed first
     */
    function saveCacheData( cacheType, url, data ) {

        var cacheObj = {
            url: url,
            title: Ajax.parseTitle( data ),
            data: Ajax.parseHtml( data ),
            classes: data.match(/body\sclass=['|"]([^'|"]*)['|"]/)[1]
        };

        cacheType.list.push( cacheObj );

        // check if cacheType has grown too large
        if ( cacheType.list.length > cacheType.limit ) {
            // remove the oldest data
            debug( 'over cache limit of ' + cacheType.limit + ', removing oldest data' );

            cacheType.list = $.grep( cacheType.list, function ( element, index ) {
                return index !== 0;
            } );

        }

        return cacheObj;

    }

    /**
     * Loop through the queue and remove the url from the array
     *
     * @param  {String} url  string to remove from the queue
     */
    function removeUrlFromAjaxQueue( url ) {

        // remove url from ajaxQueue
        ajaxQueue = $.grep( ajaxQueue, function ( element, index ) {
            return element.url !== url;
        } );

    }

    /**
     * Add any Listeners to avoid exposing functions
     */
    function ajaxEventListener() {

        $window.on( 'StateManager.GotoUrl', function ( event, url, optionsObj ) {

            gotoUrl( url, optionsObj );

        } );

    }

    /**
     * Get the current state URL from the history.
     *
     * @return {String}
     */
    function currentStateUrl() {

        var history = window.history;

        if ( history.state !== null ) {
            return history.state.url;
        }

        return window.location.href;

    }

    //////////////////////////////////////////////////////////////////////////////////////

    return {
        init: init
    };

} ) );<commit_msg>Spacing.<commit_after>/**
 * Manager.js
 *
 * @param  {Object} window    Window
 * @param  {[type]} factory [description]
 */
( function ( window, factory ) {

    'use strict';

    if ( typeof define === 'function' && define.amd ) {

        define( [
            'jquery',
            './Util',
            './Config',
            './Ajax',
        ], function ( $, Util, Config, Ajax ) {
            return factory( window, $, Util, Config, Ajax );
        } );

    } else if ( typeof exports === 'object' ) {

        module.exports = factory(
            window,
            require( 'jquery' ),
            require( './Util' ),
            require( './Config' ),
            require( './Ajax' )
        );

    } else {

        window.StateManager = window.StateManager || {};

        window.StateManager.Manager = factory(
            window,
            window.jQuery,
            window.StateManager.Util,
            window.StateManager.Config,
            window.StateManager.Ajax
        );

    }

} ( window, function ( window, $, Util, Config, Ajax ) {

    'use strict';

    // define any private variables
    var name = 'Manager',
        debugEnabled = true,
        debug = debugEnabled ? Util.debug : function () {},
        initialized = false,
        history = null,
        mode = 'traditional', // will either be 'dynamic' or 'traditional'
        $contentHolder,
        $window = $( window ),
        $body = $( 'body' ),
        prefetchCache = {
            list: [],
            limit: null
        },
        ajaxCache = {
            list: [],
            limit: null
        },
        ajaxQueue = []
    ;

    //////////////////////////////////////////////////////////////////////////////////////

    function init() {

        if ( initialized ) {
            return;
        }

        prefetchCache.limit = Config.get( 'prefetchCacheLimit' );
        ajaxCache.limit = Config.get( 'ajaxCacheLimit' );

        $contentHolder = $( Config.get( 'content' ) ).first();

        if ( $contentHolder.length < 1 ) {
            return;
        }

        initHistory();

        initState({
            isInitLoad: true
        });

        ajaxEventListener();

        initialized = true;

    }

    /**
     * Set up history object and add popstate event listener
     */
    function initHistory() {

        // set up our private alias to the history.js adapter
        history = window.history;

        mode = Util.getMode();

        if ( mode === 'traditional' ) {
            return;
        }

        Ajax.ajaxifyLinks( $body );

        // bind to statechanges
        $window.on( 'popstate', function () {

            gotoUrl(
                currentStateUrl(),
                { popstate: true }
            );

            $window.trigger( 'StateManager.PopState' );

        } );

    }

    /**
     * Initializes whatever state has been loaded
     * (either on page load or on a history push.)
     *
     * @param  {Object} options
     */
    function initState( options ) {

        var isInitLoad = false,
            pageTitle = 'title' in options ? options.title : document.title
        ;

        if ( typeof options !== 'undefined' && typeof options.isInitLoad !== 'undefined' ) {
            isInitLoad = options.isInitLoad
        }

        Util.setDocumentTitle( pageTitle );

        if ( ! isInitLoad ) {
            $window.trigger(
                'EventTrackAjax.RecordPageview',
                {
                    url: currentStateUrl(),
                    title: pageTitle
                }
            );
        }

        prefetchUpcomingUrls();

        $window.trigger( 'StateManager.AfterInitState' );

    }

    /**
     * Loop through anchor tags with data-prefetch added
     */
    function prefetchUpcomingUrls() {

        if ( mode !== 'dynamic' ) {
            return false;
        }

        // stagger prefetch of additional URLs
        $( 'a[data-prefetch]' ).each( function ( index ) {

            var thisHref = Util.fullyQualifyUrl( $( this ).attr( 'href' ) );

            // make sure we don't reload the page we're on
            if ( thisHref !== currentStateUrl() ) {
                setTimeout( function () {
                    prefetchUrl( thisHref );
                }, 50 * ( index + 1 ) );
            }

        });
    }

    /**
     * Inserts url data to the DOM. Called by gotoUrl().
     *
     * @param  {Object} data
     * @param  {Object} options
     */
    function renderUrl( data ) {

        // drop in image_box HTML
        $contentHolder.html( data.data );

        Ajax.ajaxifyLinks( $contentHolder );

        $body.removeClass().addClass( data.classes );

        initState( data );

    }

    /**
     * Toggles whether we're waiting for content to load.
     *
     * @param  {Boolean} isLoading
     */
    function toggleLoading( isLoading ) {

        if ( isLoading ) {

            // reveal loading state
            $window.trigger( 'StateManager.LoadingReveal' );

        } else {

            // hide loading state
            $window.trigger( 'StateManager.LoadingComplete' );

        }

    }

    /**
     * Handles loading of a particular url, then moves along to rendering.
     *
     * @param  {String} url
     * @param  {Object} options
     */
    function gotoUrl( url, options ) {

        options = options || {};
        options = $.extend( { url: url, popstate: false }, options );

        $( window )
            .off( 'StateManager.FetchedData' )
            .on( 'StateManager.FetchedData', function ( event, data ) {

                // Only proceed for currently request url.
                if ( data.url !== url ) {
                    return;
                }

                if ( ! options.popstate ) {
                    // history.pushState has to happen before rendering.
                    // Otherwise the page title in the history gets messed up.
                    $window.trigger( 'StateManager.PushState', options );
                }

                toggleLoading( false );
                renderUrl( data );

                // Unbind window event.
                $( this ).off( 'StateManager.FetchedData' );

            } )
        ;

        var data = getUrlData( {
                url: url,
                afterAjaxLoad: function ( data, textStatus, xhr ) {

                    // save the new data to the cache
                    data = saveCacheData( ajaxCache, xhr.requestUrl, data );

                    $( window ).trigger( 'StateManager.FetchedData', data );

                }
            } )
        ;

        // Waiting on an ajax request to be completed.
        if ( typeof data.loading !== 'undefined' && data.loading ) {
            toggleLoading( true );
        }

    }

    /**
     * Returns html data for a particular url. This data may be cached already. If not
     *       we make an AJAX call to load the data.
     *
     * @param  {Object} options Includes: url, isPrefetch
     * @return {Object}         Trigger the loading function
     */
    function getUrlData( options ) {

        var trackProgress = true,
            data
        ;

        if ( data = checkCacheForData( ajaxCache.list, options.url ) ) {
            return $( window ).trigger( 'StateManager.FetchedData', data );
        }

        if ( data = checkCacheForData( prefetchCache.list, options.url ) ) {
            return $( window ).trigger( 'StateManager.FetchedData', data );
        }

        if ( data = checkCacheForData( ajaxQueue, options.url ) ) {
            return {
                loading: true
            };
        }

        // Disable tracking progress if this is a prefetch request
        if ( options.isPrefetch ) {
            trackProgress = false;
        }

        ajaxQueue.push({ url: options.url });

        // if we reach this point, the data wasn't in the cache. make ajax request.
        Ajax.loadAjax({
            url: options.url,
            dataType: 'html',
            trackProgress: trackProgress,
            success: options.afterAjaxLoad,
            error: function () {
                location.reload( true );
            }
        });

        return {
            loading: true
        };

    }

    /**
     * Prefetches URLs we think the user is likely to load in the cache.
     *
     * @param {String} url Marker to decide which pages should be cached
     */
    function prefetchUrl( url ) {

        if ( mode === 'traditional' ) {
            return;
        }

        // no, let's make the request
        getUrlData({
            url: url,
            isPrefetch: true,
            afterAjaxLoad: function ( data ) {
                removeUrlFromAjaxQueue( url );
                data = saveCacheData( prefetchCache, url, data );

                $( window ).trigger( 'StateManager.FetchedData', data );
            }
        });
    }

    /**
     * Check supplied cache array for a match against the URI
     *
     * @param  {Array} cacheList  prefetch, ajax, or ajaxQueue cache
     * @param  {String} url       used to identify the correct data in the loop
     * @return {Object|Boolean}   cacheList data if present or false
     */
    function checkCacheForData( cacheList, url ) {

        if ( cacheList.length > 0 ) {
            for ( var i = 0; i < cacheList.length; i++ ) {
                if ( cacheList[i].url === url ) {
                    return cacheList[i];
                }
            }
        }

        return false;

    }

    /**
     * A function for passing different cache arrays with url and data to save
     *
     * @param  {Array} cacheType  prefetch or ajax cache
     * @param  {String} url       Page URI to cache
     * @param  {String} data      Data from page to be cached, it get's parsed first
     */
    function saveCacheData( cacheType, url, data ) {

        var cacheObj = {
            url: url,
            title: Ajax.parseTitle( data ),
            data: Ajax.parseHtml( data ),
            classes: data.match(/body\sclass=['|"]([^'|"]*)['|"]/)[1]
        };

        cacheType.list.push( cacheObj );

        // check if cacheType has grown too large
        if ( cacheType.list.length > cacheType.limit ) {
            // remove the oldest data
            debug( 'over cache limit of ' + cacheType.limit + ', removing oldest data' );

            cacheType.list = $.grep( cacheType.list, function ( element, index ) {
                return index !== 0;
            } );

        }

        return cacheObj;

    }

    /**
     * Loop through the queue and remove the url from the array
     *
     * @param  {String} url  string to remove from the queue
     */
    function removeUrlFromAjaxQueue( url ) {

        // remove url from ajaxQueue
        ajaxQueue = $.grep( ajaxQueue, function ( element, index ) {
            return element.url !== url;
        } );

    }

    /**
     * Add any Listeners to avoid exposing functions
     */
    function ajaxEventListener() {

        $window.on( 'StateManager.GotoUrl', function ( event, url, optionsObj ) {

            gotoUrl( url, optionsObj );

        } );

    }

    /**
     * Get the current state URL from the history.
     *
     * @return {String}
     */
    function currentStateUrl() {

        var history = window.history;

        if ( history.state !== null ) {
            return history.state.url;
        }

        return window.location.href;

    }

    //////////////////////////////////////////////////////////////////////////////////////

    return {
        init: init
    };

} ) );<|endoftext|>


<commit_before>import {Component} from 'react'
import {observer} from 'mobx-react'
import r from 'r-dom'

@observer
export default class OneChord extends Component {
  render () {
    const {chord: {quality, rootNote}, height} = this.props
    return r('g', [
      r(
        'text',
        { dy: quality === 'm7b5' ? '1.2em' : '1em', style: { textAnchor: 'middle' }, x: '50%', y: '50%' },
        r(
          'tspan',
          { style: { fontSize: height * 0.8 } },
          rootNote
        ),
        r(
          'tspan',
          {
            isRendered: quality !== 'M',
            style: { fontSize: height * 0.5 }
          },
          quality === 'm7b5' ? 'm7' : quality
        )
      ),
      r(
        'text',
        {
          dx: '-0.3em',
          dy: '1em',
          isRendered: quality === 'm7b5',
          style: { fontSize: height * 0.4, textAnchor: 'end' },
          x: '100%',
          y: '0%'
        },
        'b5'
      )
    ])
  }
}
<commit_msg>Fix component with r-dom<commit_after>import {Component} from 'react'
import {observer} from 'mobx-react'
import r from 'r-dom'

@observer
export default class OneChord extends Component {
  render () {
    const {chord: {quality, rootNote}, height} = this.props
    return r('g', [
      r(
        'text',
        { dy: quality === 'm7b5' ? '1.2em' : '1em', style: { textAnchor: 'middle' }, x: '50%', y: '50%' },
        [
          r(
            'tspan',
            { style: { fontSize: height * 0.8 } },
            rootNote
          ),
          quality !== 'M' && r('tspan', { style: { fontSize: height * 0.5 } }, quality === 'm7b5' ? 'm7' : quality)
        ]
      ),
      quality === 'm7b5' && r(
        'text',
        {
          dx: '-0.3em',
          dy: '1em',
          style: { fontSize: height * 0.4, textAnchor: 'end' },
          x: '100%',
          y: '0%'
        },
        'b5'
      )
    ])
  }
}
<|endoftext|>


<commit_before>import {Fragment, useReducer, useCallback} from 'react';
import {
  Switch,
  Route,
  Redirect,
  useRouteMatch,
  useParams,
} from 'react-router-dom';
import {useAPI} from '@xorkevin/substation';
import {useAuthCall, useAuthResource} from '@xorkevin/turbine';
import {
  Grid,
  Column,
  ListGroup,
  ListItem,
  ModalSurface,
  useModal,
  useMenu,
  Menu,
  MenuItem,
  FieldDynMultiSelect,
  Form,
  useForm,
  useFormSearch,
  ButtonGroup,
  FaIcon,
  Time,
} from '@xorkevin/nuke';
import ButtonPrimary from '@xorkevin/nuke/src/component/button/primary';
import ButtonTertiary from '@xorkevin/nuke/src/component/button/tertiary';

const CHATS_LIMIT = 32;
const USERS_LIMIT = 8;

const selectAPILatestChats = (api) => api.conduit.chat.latest;
const selectAPICreateChat = (api) => api.conduit.chat.create;
const selectAPISearch = (api) => api.u.user.search;

const SelectAChat = () => {
  return
Select a chat
;
};

const Chat = () => {
  const {chatid} = useParams();

  return
Hello, World, chatid: {chatid}
;
};

const ChatRow = ({chatid}) => {
  const match = useRouteMatch();
  const menu = useMenu();

  const menuToggle = menu.toggle;
  const toggleMenu = useCallback(
    (e) => {
      // prevent link from being clicked. anchor tag lies outside of event bubbling system.
      e.preventDefault();
      menuToggle();
    },
    [menuToggle],
  );

  const preventDefault = useCallback((e) => {
    // prevent link from being clicked. anchor tag lies outside of event bubbling system.
    e.preventDefault();
  }, []);

  return (
    <ListItem local link={`${match.url}/${chatid}`}>
      <Grid justify="space-between" align="center" nowrap>
        <Column>

{chatid}

          <Time value={Date.now()} />
        </Column>
        <Column shrink="0">
          <ButtonTertiary forwardedRef={menu.anchorRef} onClick={toggleMenu}>
            <FaIcon icon="ellipsis-v" />
          </ButtonTertiary>
          {menu.show && (
            <Menu
              size="md"
              anchor={menu.anchor}
              close={menu.close}
              onClick={preventDefault}
            >
              <MenuItem icon={<FaIcon icon="bars" />}>Action</MenuItem>
            </Menu>
          )}
        </Column>
      </Grid>
    </ListItem>
  );
};

const CreateChat = ({close}) => {
  const form = useForm({
    userids: [],
  });

  const posthook = useCallback(() => {
    close();
  }, [close]);
  const [create, execCreate] = useAuthCall(
    selectAPICreateChat,
    ['dm', '', '{}', form.state.userids],
    {},
    {posthook},
  );

  const apiSearch = useAPI(selectAPISearch);
  const searchUsers = useCallback(
    async (search) => {
      const [data, status, err] = await apiSearch(search, USERS_LIMIT);
      if (err || status < 200 || status >= 300 || !Array.isArray(data)) {
        return [];
      }
      return data.map((i) => ({value: i.userid, display: i.username}));
    },
    [apiSearch],
  );
  const userSuggest = useFormSearch(searchUsers, 256);

  return (
    <Fragment>

New Chat

      <Form
        formState={form.state}
        onChange={form.update}
        onSubmit={execCreate}
        displays={form.displays}
        putDisplays={form.putDisplays}
        addDisplay={form.addDisplay}
        compactDisplays={form.compactDisplays}
      >
        <FieldDynMultiSelect
          name="userids"
          label="Users"
          onSearch={userSuggest.setSearch}
          options={userSuggest.opts}
          nohint
          fullWidth
        />
      </Form>
      <ButtonGroup>
        <ButtonTertiary onClick={close}>Cancel</ButtonTertiary>
        <ButtonPrimary onClick={execCreate}>Create Chat</ButtonPrimary>
      </ButtonGroup>
      {create.err &&
{create.err.message}
}
    </Fragment>
  );
};

const CHATS_RESET = Symbol('CHATS_RESET');
const CHATS_RCV = Symbol('CHATS_RCV');
const CHATS_APPEND = Symbol('CHATS_APPEND');

const ChatsReset = (chatids) => ({
  type: CHATS_RESET,
  chatids,
});

const chatsReducer = (state, action) => {
  switch (action.type) {
    case CHATS_RESET:
      return {
        chatids: action.chatids,
      };
    case CHATS_RCV: {
      return state;
    }
    case CHATS_APPEND: {
      return state;
    }
    default:
      return state;
  }
};

const ConduitChat = () => {
  const match = useRouteMatch();

  const [chats, dispatchChats] = useReducer(chatsReducer, {chatids: []});

  const posthookInit = useCallback(
    (_status, chatids) => {
      dispatchChats(ChatsReset(chatids));
    },
    [dispatchChats],
  );
  useAuthResource(selectAPILatestChats, ['dm', 0, CHATS_LIMIT], [], {
    posthook: posthookInit,
  });

  const modal = useModal();

  return (
    <Grid>
      <Column fullWidth sm={6}>
        <Grid justify="space-between" align="flex-end">
          <Column grow="1">

Direct Messages

          </Column>
          <Column>
            <ButtonGroup>
              <ButtonTertiary
                forwardedRef={modal.anchorRef}
                onClick={modal.toggle}
              >
                <FaIcon icon="plus" />
              </ButtonTertiary>
            </ButtonGroup>
            {modal.show && (
              <ModalSurface size="md" anchor={modal.anchor} close={modal.close}>
                <CreateChat close={modal.close} />
              </ModalSurface>
            )}
          </Column>
        </Grid>
        <ListGroup className="conduit-chat-list">
          {chats.chatids.map((i) => (
            <ChatRow key={i} chatid={i} />
          ))}
        </ListGroup>
      </Column>
      <Column fullWidth sm={18}>
        <Switch>
          <Route exact path={`${match.path}`}>
            <SelectAChat />
          </Route>
          <Route path={`${match.path}/:chatid`}>
            <Chat />
          </Route>
          <Redirect to={`${match.url}`} />
        </Switch>
      </Column>
    </Grid>
  );
};

export default ConduitChat;
<commit_msg>Filter userid from create chat<commit_after>import {Fragment, useReducer, useCallback} from 'react';
import {
  Switch,
  Route,
  Redirect,
  useRouteMatch,
  useParams,
} from 'react-router-dom';
import {useAPI} from '@xorkevin/substation';
import {useAuthValue, useAuthCall, useAuthResource} from '@xorkevin/turbine';
import {
  Grid,
  Column,
  ListGroup,
  ListItem,
  ModalSurface,
  useModal,
  useMenu,
  Menu,
  MenuItem,
  FieldDynMultiSelect,
  Form,
  useForm,
  useFormSearch,
  ButtonGroup,
  FaIcon,
  Time,
} from '@xorkevin/nuke';
import ButtonPrimary from '@xorkevin/nuke/src/component/button/primary';
import ButtonTertiary from '@xorkevin/nuke/src/component/button/tertiary';

const CHATS_LIMIT = 32;
const USERS_LIMIT = 8;

const selectAPILatestChats = (api) => api.conduit.chat.latest;
const selectAPICreateChat = (api) => api.conduit.chat.create;
const selectAPISearch = (api) => api.u.user.search;

const SelectAChat = () => {
  return
Select a chat
;
};

const Chat = () => {
  const {chatid} = useParams();

  return
Hello, World, chatid: {chatid}
;
};

const ChatRow = ({chatid}) => {
  const match = useRouteMatch();
  const menu = useMenu();

  const menuToggle = menu.toggle;
  const toggleMenu = useCallback(
    (e) => {
      // prevent link from being clicked. anchor tag lies outside of event bubbling system.
      e.preventDefault();
      menuToggle();
    },
    [menuToggle],
  );

  const preventDefault = useCallback((e) => {
    // prevent link from being clicked. anchor tag lies outside of event bubbling system.
    e.preventDefault();
  }, []);

  return (
    <ListItem local link={`${match.url}/${chatid}`}>
      <Grid justify="space-between" align="center" nowrap>
        <Column>

{chatid}

          <Time value={Date.now()} />
        </Column>
        <Column shrink="0">
          <ButtonTertiary forwardedRef={menu.anchorRef} onClick={toggleMenu}>
            <FaIcon icon="ellipsis-v" />
          </ButtonTertiary>
          {menu.show && (
            <Menu
              size="md"
              anchor={menu.anchor}
              close={menu.close}
              onClick={preventDefault}
            >
              <MenuItem icon={<FaIcon icon="bars" />}>Action</MenuItem>
            </Menu>
          )}
        </Column>
      </Grid>
    </ListItem>
  );
};

const CreateChat = ({close}) => {
  const {userid} = useAuthValue();

  const form = useForm({
    userids: [],
  });

  const posthook = useCallback(() => {
    close();
  }, [close]);
  const [create, execCreate] = useAuthCall(
    selectAPICreateChat,
    ['dm', '', '{}', form.state.userids],
    {},
    {posthook},
  );

  const apiSearch = useAPI(selectAPISearch);
  const searchUsers = useCallback(
    async (search) => {
      const [data, status, err] = await apiSearch(search, USERS_LIMIT);
      if (err || status < 200 || status >= 300 || !Array.isArray(data)) {
        return [];
      }
      return data
        .filter((i) => i.userid !== userid)
        .map((i) => ({value: i.userid, display: i.username}));
    },
    [userid, apiSearch],
  );
  const userSuggest = useFormSearch(searchUsers, 256);

  return (
    <Fragment>

New Chat

      <Form
        formState={form.state}
        onChange={form.update}
        onSubmit={execCreate}
        displays={form.displays}
        putDisplays={form.putDisplays}
        addDisplay={form.addDisplay}
        compactDisplays={form.compactDisplays}
      >
        <FieldDynMultiSelect
          name="userids"
          label="Users"
          onSearch={userSuggest.setSearch}
          options={userSuggest.opts}
          nohint
          fullWidth
        />
      </Form>
      <ButtonGroup>
        <ButtonTertiary onClick={close}>Cancel</ButtonTertiary>
        <ButtonPrimary onClick={execCreate}>Create Chat</ButtonPrimary>
      </ButtonGroup>
      {create.err &&
{create.err.message}
}
    </Fragment>
  );
};

const CHATS_RESET = Symbol('CHATS_RESET');
const CHATS_RCV = Symbol('CHATS_RCV');
const CHATS_APPEND = Symbol('CHATS_APPEND');

const ChatsReset = (chatids) => ({
  type: CHATS_RESET,
  chatids,
});

const chatsReducer = (state, action) => {
  switch (action.type) {
    case CHATS_RESET:
      return {
        chatids: action.chatids,
      };
    case CHATS_RCV: {
      return state;
    }
    case CHATS_APPEND: {
      return state;
    }
    default:
      return state;
  }
};

const ConduitChat = () => {
  const match = useRouteMatch();

  const [chats, dispatchChats] = useReducer(chatsReducer, {chatids: []});

  const posthookInit = useCallback(
    (_status, chatids) => {
      dispatchChats(ChatsReset(chatids));
    },
    [dispatchChats],
  );
  useAuthResource(selectAPILatestChats, ['dm', 0, CHATS_LIMIT], [], {
    posthook: posthookInit,
  });

  const modal = useModal();

  return (
    <Grid>
      <Column fullWidth sm={6}>
        <Grid justify="space-between" align="flex-end">
          <Column grow="1">

Direct Messages

          </Column>
          <Column>
            <ButtonGroup>
              <ButtonTertiary
                forwardedRef={modal.anchorRef}
                onClick={modal.toggle}
              >
                <FaIcon icon="plus" />
              </ButtonTertiary>
            </ButtonGroup>
            {modal.show && (
              <ModalSurface size="md" anchor={modal.anchor} close={modal.close}>
                <CreateChat close={modal.close} />
              </ModalSurface>
            )}
          </Column>
        </Grid>
        <ListGroup className="conduit-chat-list">
          {chats.chatids.map((i) => (
            <ChatRow key={i} chatid={i} />
          ))}
        </ListGroup>
      </Column>
      <Column fullWidth sm={18}>
        <Switch>
          <Route exact path={`${match.path}`}>
            <SelectAChat />
          </Route>
          <Route path={`${match.path}/:chatid`}>
            <Chat />
          </Route>
          <Redirect to={`${match.url}`} />
        </Switch>
      </Column>
    </Grid>
  );
};

export default ConduitChat;
<|endoftext|>